Merging 2 Table and GROUP BY date - mysql

I need to merge multiple table group by the count base on date's day.
Below are my table structure :
#table1
id date
1 2015-07-01 00:00:00
2 2015-07-02 00:00:00
3 2015-07-03 00:00:00
#table2
id date
1 2015-07-02 00:00:00
2 2015-07-02 00:00:00
3 2015-07-02 00:00:00
4 2015-07-10 00:00:00
What I wanted to achieve :
#query result
date t1_count t2_count
2015-07-01 1 NULL
2015-07-02 1 3
2015-07-03 1 NULL
2015-07-10 NULL 1
Below are my query that refer to this link:
SELECT left(A.date,10) AS `day`
, COUNT(A.ID) AS `a_count`
, COUNT(B.ID) AS `b_count`
FROM table1 A
LEFT JOIN table2 B
ON LEFT(A.date,10) = LEFT(B.date,10)
GROUP BY LEFT(A.date,10)
UNION
SELECT left(B.date,10) AS `day`
, COUNT(A.ID) AS `a_count`
, COUNT(B.ID) AS `b_count`
FROM table1 A
RIGHT JOIN table2 B
ON LEFT(A.date,10) = LEFT(B.date,10)
GROUP BY LEFT(A.date,10);
but the result was
#query result
date t1_count t2_count
2015-07-01 1 0
2015-07-02 3 3
2015-07-03 1 0
2015-07-10 0 1
I'd try to modified and search other solution like UNION ALL, LEFT JOIN, etc, but I'd no luck to solve this problem.

You can do this using union all and group by:
select date, sum(istable1) as numtable1, sum(istable2) as numtable2
from ((select date(date) as date, 1 as istable1, NULL as istable2
from table1
) union all
(select date(date) as date, NULL as istable1, 1 as istable2
from table2
)
) t
group by date
order by 1;
Under some circumstances, it can be faster to aggregate the data in the subqueries as well:
select date, sum(numtable1) as numtable1, sum(numtable2) as numtable2
from ((select date(date) as date, count(*) as numtable1, NULL as numtable2
from table1
group by date(date)
) union all
(select date(date) as date, NULL as numtable1, count(*) as numtable2
from table2
group by date(date)
)
) t
group by date
order by 1;
If you want 0 instead of NULL in the desired results, use 0 instead of NULL in the subqueries.

Related

need help writing a query (restructuring the table)

I need to write a select statement that will rewrite the table in the following manner... I'm not sure how to go about this using MySQL.
Example of table
user_id date a b c
123456 2020-01-01 1 1 1
234567 2020-03-04 1 0 0
453576 2020-05-05 1 0 1
Desired result
user_id date results
123456 2020-01-01 a
123456 2020-01-01 b
123456 2020-01-01 c
234567 2020-03-04 a
453576 2020-05-05 a
453576 2020-05-05 c
In MySQL you can unpivot with union all, while filtering on 1 values:
select user_id, date, 'a' as result from mytable where a = 1
union all select user_id, date, 'b' from mytable where b = 1
union all select user_id, date, 'c' from mytable where c = 1
order by user_id, date, result
If you have a large amount of data or your "table" is really a complex query (say a subquery or view), then unpivoting is usually faster with cross join than with union all:
select t.user_id, t.date, r.result
from t cross join
(select 'a' as result union all
select 'b' as result union all
select 'c' as result
) r
where (t.a = 1 and r.result = 'a') or
(t.b = 1 and r.result = 'b') or
(t.c = 1 and r.result = 'c') ;
For a single smallish table, performance probably doesn't matter.

sql query for diff between rows

I have a table having dates in it, I would want to subtract the first date with the second, the second with the third and so on till the last n-1 with n.
How do I write a query for this?
The table would is called Random and the column name is date
date
+------------+
| 2009-06-20 |
| 2010-02-12 |
| 2012-03-14 |
| 2013-09-10 |
| 2014-01-01 |
| 2015-04-10 |
| 2015-05-01 |
| 2016-01-01 |
+------------+
You need to get the next date. I would use a correlated subquery:
select t.date,
(select min(t2.date)
from t t2
where t2.date > t.date
) as next_date
from t;
You just need to use datediff() to get the difference in days.
Use ROW_NUMBER
For line numbering and then use a sub query to calculate the difference
SELECT column_date
,DATEDIFF( D , column_date
,(SELECT column_date FROM
(
SELECT column_date , ROW_NUMBER() OVER ( ORDER BY column_date) AS RowMum
FROM table_Random AS tBL_1
) AS tbl_2
WHERE tbl_2.RowMum= tBL_1.RowMum-1
)
) DIFF
FROM
(
SELECT column_date , ROW_NUMBER() OVER ( ORDER BY column_date) AS RowMum
FROM table_Random
) AS tBL_1
I did not notice the mysql tag when I wrote my answer first so am updating it now with link to MySQL 8.0 fiddle
https://www.db-fiddle.com/f/myUJYeFrMXmU1piQXAmnv4/0
/* tested against MySQL v8.0 */
WITH T(d) AS (
SELECT '2009-06-20' as d
UNION
SELECT '2010-02-12'
UNION
SELECT '2012-03-14'
UNION
SELECT '2013-09-10'
UNION
SELECT '2014-01-01'
UNION
SELECT '2015-04-10'
UNION
SELECT '2015-05-01'
UNION
SELECT '2016-01-01'
), LAGGED(d, next_d) AS (
SELECT d, LEAD(d) OVER (ORDER BY d ASC) AS next_d
FROM T
)
/* datediff args are in opposite order to SQL server. Also,
only day part is considered */
SELECT l.d, l.next_d, DATEDIFF(l.next_d, l.d) AS n_days
FROM LAGGED AS l
Here is my original answer that targeted SQL Server:
WITH T(d) AS (
SELECT d FROM (
VALUES
('2009-06-20'),
('2010-02-12'),
('2012-03-14'),
('2013-09-10'),
('2014-01-01'),
('2015-04-10'),
('2015-05-01'),
('2016-01-01')
) AS T1(d)
), LAGGED(d, next_d) AS (
SELECT d, LEAD(d) OVER (ORDER BY d ASC) AS next_d
FROM T
)
SELECT l.d, l.next_d, DATEDIFF(DAY, l.d, l.next_d) AS n_days
FROM LAGGED AS l
and produces this output (modulo the fussy hand-editing I have done):
d next_d n_days
2009-06-20 2010-02-12 237
2010-02-12 2012-03-14 761
2012-03-14 2013-09-10 545
2013-09-10 2014-01-01 113
2014-01-01 2015-04-10 464
2015-04-10 2015-05-01 21
2015-05-01 2016-01-01 245
2016-01-01 NULL NULL

MySQL - SELECT MAX(), and get corresponding fields

The task is: get the list with ID of every employee and the ID of the last department where he worked. It's becoming more complicated cause one person can work in different departments at one time, so we need to get his last department where he has the max rate.
table:
ID_employee| ID_department | end_date | rate
1 22 2016-01-01 1
2 25 NULL 0.3
2 27 NULL 1
3 22 2013-12-12 0.5
3 22 2014-05-05 0.5
end_date is the last day when employee worked, and NULL value means that his contract is actual today.
The result must look like:
ID_employee | ID_department | end_date | rate
1 22 2016-01-01 1
2 27 NULL 1
3 22 2014-05-05 0.5
I found out how to select max() with corresponding fields by using join:
SELECT table.id_employee, id_department
FROM table
JOIN ( SELECT id_employee,
IF (MAX( end_date IS NULL ) = 1 , "0000-00-00", MAX( end_date )) as max_end_date
FROM table GROUP BY id_employee) maxs ON maxs.id_employee = table.id_employee
WHERE maxs.max_end_date = IFNULL(table.end_date, "0000-00-00")
GROUP BY table.id_employee
However, there are ALL corresponding rows in the result:
ID_employee | ID_department | end_date | rate
1 22 2016-01-01 1
2 25 NULL 0.3
2 27 NULL 1
3 22 2014-05-05 0.5
The question is, how to get NOT JUST corresponding rows to MAX(end_date), but with MAX(rate) too? I assume that HAVING might help, but I still don't know what exactly must be there.
And maybe there are other ways to solve problem with better performance, because this query works about 16s while the table has ~30 000 rows.
Could you try with the query below:
SELECT T1.ID_employee,
T1.ID_department,
CASE WHEN maxs.max_end_date = "0000-00-00" THEN NULL ELSE maxs.max_end_date END AS end_date,
T1.rate
FROM TestTable T1
JOIN ( SELECT id_employee,
MAX(ID_department) AS ID_department,
IF (MAX( end_date IS NULL ) = 1, "0000-00-00", MAX( end_date )) AS max_end_date
FROM TestTable
GROUP BY id_employee ) maxs ON maxs.id_employee = T1.id_employee AND maxs.ID_department = T1.ID_department
WHERE maxs.max_end_date = IFNULL(T1.end_date, "0000-00-00")
GROUP BY T1.id_employee
Please find the Live Demo
UPDATE:
As per the comments the following query helped to achieve the result:
SET #CurrentDate := CURDATE();
SELECT T2.ID_employee,
T2.ID_department,
CASE WHEN MR.Max_end_date = #CurrentDate THEN NULL ELSE T2.end_date END AS end_date,
MR.MaxRate AS rate
FROM TestTable T2
JOIN (
SELECT T1.ID_employee, MAX(T1.rate) AS MaxRate, MD.Max_end_date
FROM TestTable T1
JOIN (
SELECT ID_employee,
MAX(CASE WHEN end_date IS NULL THEN #CurrentDate ELSE end_date END) AS Max_end_date
FROM TestTable
GROUP BY ID_employee
) MD ON MD.ID_employee = T1.ID_employee
WHERE MD.Max_end_date = IFNULL(T1.end_date, #CurrentDate)
GROUP BY T1.ID_employee
) MR ON MR.ID_employee = T2.ID_employee AND MR.MaxRate = T2.rate
WHERE MR.Max_end_date = IFNULL(T2.end_date, #CurrentDate)
Working Demo
I think this query will work for you.
SELECT ID_employee, ID_department, end_date, MAX(rate)
FROM test_max
GROUP BY ID_employee

How to make query return 0 instead of empty set if there is no result

How can i make this query to return a row with 0 value if there is no value for each date
SELECT COUNT(id) FROM `panel_messages` WHERE `sent_by` = 'root'
AND `send_date` IN ("1395-4-25","1395-4-24","1395-4-23","1395-4-22","1395-4-21","1395-4-20","1395-4-19")
GROUP BY `send_date`
ORDER BY `send_date` DESC
My expected result is 7 rows like this :
| row1 |
| row2 |
| row3 |
| row4 |
| row5 |
| row6 |
| row7 |
and if there is no result for one of the rows i want it to be 0 which is default value :
| 2 |
| 0 |
| 0 |
| 2 |
| 0 |
| 3 |
| 1 |
But right now i just get 4 rows because if there is no result my query doesn't return anything :
| 2 |
| 2 |
| 3 |
| 1 |
SQL fiddle : http://sqlfiddle.com/#!9/a07486/3
Please give it a try:
SELECT
COALESCE(YT.total,t.total) AS cnt
FROM
(SELECT 0 AS total) t
LEFT JOIN
(
SELECT
COUNT(id) AS total
FROM `panel_messages`
WHERE `sent_by` = 'root'
AND `send_date` IN ("1395-4-25","1395-4-24","1395-4-23","1395-4-22","1395-4-21","1395-4-20","1395-4-19")
GROUP BY `send_date`
ORDER BY `send_date` DESC
) YT
ON 1=1;
Note:
A dummy row has been created with value 0.
Later doing a LEFT JOIN between this dummy table and your query
And finally using COALESCE you can achieve the default count 0 if your main query doesn't return anything.
EDIT:
Query:
SELECT
COALESCE(YT.count,0) AS count
FROM
(
SELECT ADDDATE('1395-01-01', INTERVAL #i:=#i+1 DAY) AS DAY
FROM (
SELECT a.a
FROM (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) AS c
) a
JOIN (SELECT #i := -1) r1
) dateTable
LEFT JOIN
(
SELECT
send_date,
COUNT(id) AS count
FROM
`panel_messages`
WHERE
`sent_by` = 'root'
AND `send_date` IN (
"1395-4-25",
"1395-4-24",
"1395-4-23",
"1395-4-20"
)
GROUP BY
`send_date`
ORDER BY
`send_date` DESC
) AS YT
ON dateTable.DAY = YT.send_date
WHERE dateTable.DAY IN ('1395-04-25','1395-04-24','1395-04-23','1395-04-20');
In order to get zero count for the dates which don't exist you need to create a temporary table where all the dates (under a certain range) reside.
Then making a left join between the date field of this temporary table and send_date field of your table would do the job done almost.
Finally you need to use COALESCE to get 0 if the count is NULL.
WORKING DEMO
try this :
SELECT sent_by ,"1395-4-25" as `SEND DATE`,COUNT(*) FROM `panel_messages` WHERE `sent_by` = 'root' AND `send_date` = "1395-4-25"
union
SELECT sent_by ,"1395-4-24" as `SEND DATE`,COUNT(*) FROM `panel_messages` WHERE `sent_by` = 'root' AND `send_date` = "1395-4-24"
union
SELECT sent_by ,"1395-4-23" as `SEND DATE`,COUNT(*) FROM `panel_messages` WHERE `sent_by` = 'root' AND `send_date` = "1395-4-23"
ORDER BY `SEND DATE` DESC
in this case when date is not found the count(*) return 0; but in the first return null add the 4 select statement and it will return 7 rows now it work but it can be better if i found onother solution i'm going back here
onother answer what you are trying to do is impossible without the union :
but you can try some think else
create a temporary table that contain your date
create Table temporary (
send_date date
);
insert INTO temporay("1395-4-25"),("1395-4-24"),("1395-4-23"),("1395-4-22"),("1395-4-21"),("1395-4-20"),("1395-4-19")
than do select with rigth join between your table and this one now you will have record for the date that don't have send_by
panel_messages.sent_by | panel_messages.send_date | temporary.send_date
root "1395-4-25" "1395-4-25"
root "1395-4-25" "1395-4-25"
null null "1395-4-24"
null null "1395-4-23"
root "1395-4-19" "1395-4-19"
.
.
.
now you count how much message in every day all i did is create a result that can return what you need :
Try this select after you create the temporary table
SELECT temporary.send_date, count(sender_by)
from panel_messages RIGTH JOIN temporary ON (temporary.send_date = panel_messages.send_date)
where
panel_messages.sent_by like 'root'
group by temporary.send_date
ORDER BY send_date DESC;

Condition on join not working properly

I have two tables
customers (id, name....,created_at)
coupons (id, customer_id, ....,created_id)
I am trying to get count of all those coupons where created_at is 2015-12-29 and where customer's created_at is not 2015-12-29
select
count(*) as aggregate
from
`coupons`
where
(select count(*) from `customers`
where
`coupons`.`customer_id` = `customers`.`id`
and
DATE(`customers`.`created_at`) <> '2015-12-29') >= 1
and
DATE(`updated_at`) = '2015-12-29'
This query works fine, but when I add one more constraint to query client_id = 1, it doesnot work.
select
count(*) as aggregate
from
`coupons`
where
(select count(*) from `customers`
where
`coupons`.`customer_id` = `customers`.`id`
and
DATE(`customers`.`created_at`) <> '2015-12-29') >= 1
and
DATE(`updated_at`) = '2015-12-29'
and
`client_id` = 1
It should return non zero value, but it returns 0
Dummy data
customers
id, name, created_at
1 ehsan 2015-12-29 12:10:10
2 ehs 2015-12-28 12:10:10
coupons
id, customer_id, updated_at client_id
1 1 2015-12-29 12:10:10 1
2 2 2015-12-29 12:10:10 1
3 2 2015-12-29 12:10:10 1