counting age in sql and view the data in sql table - mysql

|---------------------|
|DUN|NAME |BIRTHDAY |
--------------------
|A |MELAYU |21/2/2014|
|A |CINA |21/2/2002|
|B |CINA |21/2/2011|
|-------------------- |
My table name is maklumat. I want view the data based on age using sql. below are the example table to view.
|---------------------|
|DUN|AGE<3 |AGE 3-6 |
--------------------
|A |1 |1 |
|B |0 |1 |
|-------------------- |
THIS IS WHAT I'VE TRIED
SELECT jantina,dun, count(tarikh_lahir > 1954-04-07) as 60KEBAWAH, count(1950-04-07>=tarikh_lahir <= 1954-04-07) as 60HINGGA64, count(1945-04-07>=tarikh_lahir <= 1949-04-07) as 65HINGGA69, count(1940-04-07>=tarikh_lahir <= 1944-04-07) as 70HINGGA74,count(1935-04-07>=tarikh_lahir <= 1939-04-07) as 75HINGGA79,count(1930-04-07>=tarikh_lahir <= 1934-04-07) as 80HINGGA85, count(1925-04-07>=tarikh_lahir <= 1929-04-07) as 86HINGGA89, count(1920-04-07>=tarikh_lahir <= 1924-04-07) as 90HINGGA94, count(1915-04-07>=tarikh_lahir <= 1919-04-07) as 95HINGGA99, count(1910-04-07>=tarikh_lahir <= 1914-04-07) as 100HINGGA104,count(tarikh_lahir <1910-04-07) as 60KEBAWAH
FROM maklumat_ahli
WHERE jantina = 'lelaki' AND (
kematian_tarikh IS NULL
AND bayaran_pertama IS NULL
AND bayaran_kedua IS NULL
)
GROUP BY dun

SELECT M.DUN, COUNT(M.AGE) AS 'AGE<3', COUNT(M1.AGE) AS 'AGE 3-6' FROM MAKLUMAT M
LEFT JOIN MAKLUMAT M1 ON M.DUN = M1.DUN AND M1.AGE BETWEEN 3 AND 6
WHERE M.AGE < 3
GROUP BY M.DUN

First calculate the age in years. This is this year minus the birth year. Exception: The birthday date is yet to come this year. In this case we must subtract one. With the ages thus found we can start to count, which we can do with the sum function by adding one for every match.
select
dun,
sum(age < 3) as age0to2,
sum(age between 3 and 6) as age3to6
from
(
select
dun,
year(now()) - year(birthday) -
case when month(now()) * 100 + day(now()) < month(birthday) * 100 + day(birthday) then
1
else
0
end
as age
from maklumat
)
group by dun;

Here is why your select statement fails: MySQL knows a boolean datatype, which is zero for FALSE and non-zero for TRUE. When evaluating an expression the result is either 0 or 1 (or NULL).
The expression (tarikh_lahir > 1954-04-07) is 1 for birth dates after the date stated, 0 for birth dates before and only NULL when the birth date is NULL. Count counts all non-null values, zeros and ones alike, i.e. all dates given. This is not what you want.
Instead of COUNT you can use SUM, to sum the positive results (i.e. the 1s). Besides: The dates should be strings as far as I know, otherwise they would be treated as numeric expressions, i.e. 1954-04-07 = 1954 minus 04 minus 07.
SELECT
jantina,
dun,
sum(tarikh_lahir > '1954-04-07') as 60KEBAWAH,
sum('1950-04-07' >= tarikh_lahir <= '1954-04-07') as 60HINGGA64,
sum('1945-04-07' >= tarikh_lahir <= '1949-04-07') as 65HINGGA69,
sum('1940-04-07' >= tarikh_lahir <= '1944-04-07') as 70HINGGA74,
sum('1935-04-07' >= tarikh_lahir <= '1939-04-07') as 75HINGGA79,
sum('1930-04-07' >= tarikh_lahir <= '1934-04-07') as 80HINGGA85,
sum('1925-04-07' >= tarikh_lahir <= '1929-04-07') as 86HINGGA89,
sum('1920-04-07' >= tarikh_lahir <= '1924-04-07') as 90HINGGA94,
sum('1915-04-07' >= tarikh_lahir <= '1919-04-07') as 95HINGGA99,
sum('1910-04-07' >= tarikh_lahir <= '1914-04-07') as 100HINGGA104,
sum(tarikh_lahir <'1910-04-07') as 60KEBAWAH
FROM maklumat_ahli
WHERE jantina = 'lelaki'
AND kematian_tarikh IS NULL
AND bayaran_pertama IS NULL
AND bayaran_kedua IS NULL
GROUP BY jantina, dun;
BTW: Are names starting with digits allowed in MySQL? Otherwise you will have to find other names for 60HINGGA64 etc. or use quotes.

Related

How should I use NOT IN statement in MySQL

I am trying to use NOT IN statement with MySQL. However, I get 0 row with code below (no syntax error). I am sure there should be more than 0 row with the statement. What syntax should I adjust?
SELECT DISTINCT member_id
FROM client_payments
INNER JOIN client_purchase_records ON client_purchase_records.id = client_payments.purchase_record_id
WHERE status = 1
AND client_payments.created_at > '2021-10-28 00:00:00'
AND client_payments.created_at < '2021-10-31 23:59:00'
NOT IN(
SELECT DISTINCT member_id
FROM client_payments
INNER JOIN client_purchase_records ON client_purchase_records.id = client_payments.purchase_record_id
WHERE status = 1
AND client_payments.created_at > '2020-9-30 00:00:00'
AND client_payments.created_at < '2021-10-27 23:59:00'
);
Difference about two query is mainly about created_at column, I want to do "set difference operation" with period A(2021-10-28 00:00:00 - 2021-10-31 23:59:00 )and period B(2020-9-30 00:00:00 - 2021-10-27 23:59:00)
I want to query out member_id who pay during 2020-9-30 00:00:00 - 2021-10-27 23:59:00
Subtract with member_id who pay during 2021-10-28 00:00:00 - 2021-10-31 23:59:00
Finally I get member_id who pay during 2021-10-28 00:00:00 - 2021-10-31 but not pay during 2020-9-30 00:00:00 - 2021-10-27 23:59:00 ( new member_id never show before)
no syntax error
The error is in the logic.
Your condition, after adding the parenthesis according to operators priority, looks like
AND ( (client_payments.created_at < '2021-10-31 23:59:00') NOT IN ( {subquery} ) )
I.e. the result of comparing client_payments.created_at < '2021-10-31 23:59:00' (which is 0, 1 or NULL) is searching in the subquery output which is obviously illogical.
You presumably want to be saying AND member_id NOT IN (...your subquery...); as is, it is using the previous condition instead of member_id.
Also, it looks like you want >= and <=, not > and <.
I would do this like so instead:
SELECT member_id
FROM client_payments
INNER JOIN client_purchase_records ON client_purchase_records.id = client_payments.purchase_record_id
WHERE status = 1
AND client_payments.created_at >= '2021-09-30 00:00:00'
AND client_payments.created_at <= '2021-10-31 23:59:00'
GROUP BY member_id
HAVING MIN(client_payments.created_at) >= '2021-10-28 00:00:00'

MYSQL join with where clause excluding matching value

I am confused with a basic query, I have 2 tables employees and employee_time_off. I wanted to fetch all those employees who are not off on any specific date.
What I have tried is,
SELECT
employees.id,
employees.FIRST_NAME
FROM `employees`
LEFT JOIN `employee_time_off` ON `employees`.`id` = `employee_time_off`.`employee_id`
AND `START_DATE` < '2019-06-30 00:00:00' AND `END_DATE` > '2019-06-30 12:59:59'
START_DATE is actually when the leaves start, END_DATE is when the leave ends.
So, in this case, employee with id=1 shouldn't be in the result set. But the result set gets all the employees from 1 to 6.
How about NOT EXISTS?
SELECT e.*
FROM employees e
WHERE NOT EXISTS (SELECT 1
FROM employee_time_off eto
WHERE eto.employee_id = e.id AND
eto.start_date <= '2019-06-30' AND
eto.end_date >= '2019-07-01'
);
Note: I'm not sure if the end_date should be '2019-06-30' or '2019-07-01'. Your question doesn't have enough information (off the whole day or any part of the day).
Your version returns all employees because you are using a LEFT JOIN.
from the SQL, we can't tell which table has the start_date and end_date columns. we suspect its the employee_time_off table. to reduce ambiguity, qualify all column references
We can add a condition to the WHERE clause, to throw out the rows where we find a matching row in time off. Also looks like we need to do <= and >= comparisons to get the result we're after.
SELECT e.id
, e.first_name
FROM employees e
LEFT
JOIN employee_time_off o
ON o.employee_id = e.id
AND o.start_date <= '2019-06-30 00:00:00'
AND o.end_date >= '2019-06-30 23:59:59'
WHERE o.employee_id IS NOT NULL
Note that with that query, we would get a different result if we had these rows in the time_off table:
employee_id start_date end_date
----------- ---------------- -------------------
1 2019-06-30 00:00:00 2019-06-30 07:24:59
1 2019-06-30 07:25:00 2019-06-30 23:59:59
What should the query return when time off table contains a row like this?
employee_id start_date end_date
----------- ---------------- -------------------
2 2019-06-29 00:00:00 2019-06-30 11:14:59
The specification isn't clear.
Also, we tend to do datetime overlap checks with conditions like
WHERE foo >= '2019-06-30 00:00'
AND foo < '2019-06-30 00:00' + INTERVAL 1 DAY

mysql LAST_DAY() only reads 1 subquery result, how to process all results? using joins?

I have an insurance policies table like this:
+-------------------------------------------------------------+
| id | cancellation_val | cancellation_interval | expire_date |
+-------------------------------------------------------------+
| 1 | 30 | day | 2019-06-09 |
| 2 | 2 | month | 2019-12-01 |
+-------------------------------------------------------------+
I need to get the ids of the policies that are going to expire based on cancellation, from today and within 4 months, calculating the last day of the month, like this pseudo-code:
'today' <= LAST_DAY( expire_date - cancellation_val/interval ) < 'today + 4 months'
Being not a pro I think I should use JOINs but I don't know how, after days of trying the only thing I achieved was this:
SELECT LAST_DAY(
DATE_FORMAT(
STR_TO_DATE(
(SELECT CASE cancellation_interval
WHEN "day" THEN date_sub(expire_date, INTERVAL cancellation_val DAY)
WHEN "month" THEN date_sub(data_scadenzaexpire_date, INTERVAL cancellation_val MONTH)
END
AS newDate
FROM insurance WHERE id=2
), '%Y-%m-%d'
), '%Y-%m-%d'
)
)
This is working but I don't need the "WHERE id=2" clause (because I need to process ALL rows of the table), and if I remove it I got error "subquery returns more than 1 row".
So how I can proceed? And using the result to stay between 'today' AND 'today + 4 months' ?
I think with some kind of JOIN I could do it in a easier way but I don't know how.
Thank you all
The problem is the structure of the query, not the LAST_DAY function.
We want to return the id values of rows that meet some condition. So the query would be of the form:
SELECT t.id
, ...
FROM insurance t
WHERE ...
HAVING ...
Introducing another SELECT keyword basically introduces a subquery. There are restrictions on subqueries... in the SELECT list, a subquery can return a single column and (at most) a single row.
So let's ditch that extra SELECT keyword.
We can derive the newdate as an expression of the SELECT list, and then we can reference that derived column in the HAVING clause. The spec said we wanted to return the id value, so we include that in the SELECT list. We don't have to return any other columns, but for testing/debugging, it can be useful to return the values that were used to derive the newdate column.
Something like this:
SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
, t.expire_date
, t.cancellation_interval
, t.cancellation_val
FROM insurance t
HAVING newdate >= DATE(NOW())
AND newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY newdate ASC
We don't have to include the newdate in the SELECT list; we could just replace occurrences of newdate in the HAVING clause with the expression.
We could also use an inline view to "hide" the derivation of the newdate column
SELECT v.id
, v.newdate
FROM ( SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
FROM insurance t
) v
WHERE v.newdate >= DATE(NOW())
AND v.newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY v.newdate ASC
check this query: remove the HAVING Line to see all rows
SELECT
IF(cancellation_interval = 'day',
i.expire_date - INTERVAL i.`cancellation_val` DAY,
i.expire_date - INTERVAL i.`cancellation_val` MONTH
) as cancellation_day,
i.*
FROM `insurance` i
HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
SAMPLES
MariaDB [test]> SELECT IF(cancellation_interval = 'day', i.expire_date - INTERVAL i.`cancellation_val` DAY, i.expire_date - INTERVAL i.`cancellation_val` MONTH ) as cancellation_day, i.* FROM `insurance` i HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
+------------------+----+------------------+-----------------------+-------------+
| cancellation_day | id | cancellation_val | cancellation_interval | expire_date |
+------------------+----+------------------+-----------------------+-------------+
| 2019-05-10 | 1 | 30 | day | 2019-06-09 |
+------------------+----+------------------+-----------------------+-------------+
1 row in set (0.001 sec)
When you use a SELECT query as an expression, it can only return one row.
If you want to process all the rows, you need to call LAST_DAY() inside the query, not on the result.
SELECT *
FROM insurance
WHERE CURDATE() <= LAST_DAY(
expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH))
AND LAST_DAY(expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH)) < CURDATE + INTERVAL 4 MONTH

Aggregating a column by multiple date ranges in a single query

I have a MySQL table with 5 columns:
Device | Name | Date | Source | Income
Neither Device nor Name nor Source are unique.
I am trying to write the SQL for getting the following:
Device | Name | Source | Income (for last 3 days) | Income (for last 9 days) | Income (for last 12 days)
What is the best way to do this?
You can get a conditional SUM() via CASE statements:
SELECT Device
,Name
,Source
,SUM(CASE WHEN date BETWEEN UNIX_TIMESTAMP(NOW() - INTERVAL 3 day) AND UNIX_TIMESTAMP(NOW()) THEN Income END) AS Last_3_Days
,SUM(CASE WHEN date BETWEEN UNIX_TIMESTAMP(NOW() - INTERVAL 9 day) AND UNIX_TIMESTAMP(NOW()) THEN Income END) AS Last_9_Days
FROM YourTable
GROUP BY Device
,Name
,Source
SELECT COUNT(*) as total_l3d FROM table WHERE DATEDIFF(Date, NOW()) <= 3
UNION
SELECT COUNT(*) as total_l9d FROM table WHERE DATEDIFF(Date, NOW()) <= 9
UNION
SELECT COUNT(*) as total_l12d FROM table WHERE DATEDIFF(Date, NOW()) <= 12

Selecting records overlapping with a given time range

Suppose I have the following table:
id | value | start_time | stop_time
-----------------------------------------------------------------
1 | value1 | 06:00:00 | 11:00:00
2 | value2 | 12:00:00 | 13:00:00
I need to select records whose time range overlap with 05:00–11:30. That means the record to be returned is row 1.
I tried using a query like this but it fails when the given time range crosses midnight. Perhaps it's impossible to do it without involving the date.
SELECT * FROM table WHERE
(
(start_time >= '05:00:00' AND stop_time <= '11:30:00') ||
(start_time >= '05:00:00' AND start_time <= '05:00:00') ||
(start_time <= '05:00:00' AND stop_time >= '11:30:00') ||
(stop_time >= '11:30:00' AND stop_time <= '05:00:00')
)
I'm thinking of enumerating all the minutes for the given time range and filtering using BETWEEN but there may be a better solution.
Dave,
The easiest way which i can see for you to achieve the desired result will be to use 'TIME' type for start_time and 'stop_time` fields.
Then you can select like this:
SELECT * FROM `YOUR_TABLE_NAME_HERE` WHERE `start_time` > '05:00:00' AND `stop_time` < '11:30:00'
Every range will need to be represented with a conditional, like so:
IF(start_time < stop_time, start_time >= '05:00:00' AND stop_time <= '11:30:00', start_time <= '05:00:00' AND stop_time >= '11:30:00')