How can I join 2 tables by their dates in SQL? - mysql

I have 2 tables
cumulative_money_in:
Cumulative_amount | date
500 | Apr 12
570 | Apr 14
620 | Apr 16
cumulative_money_out:
Cumulative_amount | date
100 | Apr 13
170 | Apr 15
200 | Apr 17
I want my result in this example to be:
Remaining_balance | date
500 | Apr 12
400 | Apr 13
470 | Apr 14
400 | Apr 15
450 | Apr 16
420 | Apr 17
i.e. I want to have a timeline of how the remaining balance (money_in-money_out) changes
What I have so far is this:
SELECT date, (COALESCE(money_in.cumulative_amount,0) - COALESCE(money_out.cumulative_amount,0))
FROM money_in
FULL OUTER JOIN money_out
ON money_in.date <= money_out.date
Which clearly is not what I want to have

You can union all your tables, order by date. and then calculate remaining money for each interval with a left join:
with money_start as (
select *, 1 flag from money_in
union all
select *, -1 flag from money_out
),
money as (
select * from money_start order by date
)
select case when m1.flag = 1
then m1.amount - coalesce(m2.amount, 0)
else m2.amount - m1.amount end remaining,
m1.date
from money m1
left join money m2 on m2.date = date_sub(m1.date, interval 1 day)
order by m1.date;
Output:
remaining
date
500
2021-04-12
400
2021-04-13
470
2021-04-14
400
2021-04-15
450
2021-04-16
420
2021-04-17

Related

Getting the list of all months and the number of products ordered for the month, and should have '0'for the month with no orders - mysql

I have a set of data that looks like this
Cust Id order date Ordered Product
1 Jan 2 1
1 Jan 5 2
1 March 14 1
1 September 9 1
1 December 12 2
2 Jan 5 1
2 Feb 13 2
3 March 12 2
3 April 5 3
3 June 10 2
and my output should look like this
Cust Id order Date Order product
1 Jan 31 3
1 feb 29 0
1 Mar31 1
1 Apr 30 0
1 May 31 0
1 June 30 0
1 July 31 0
1 Aug 31 0
1 Sept 30 1
1 oct 31 0
1 Nov 30 0
1 Dec 31 2
and I have got this far
1 January 31 3
1 March 31 1
1 September 30 1
1 December 31 2
and my code is
select customer_id,
date_format(last_day(order_date), '%M %d') as new_months,
sum(products_ordered) as total
from amazon_test
where customer_id =1
group by new_months, customer_id;
I currently stuck at the part where I need to have all the months and '0' as the output since no orders were made.
If you are running MySQL 8.0, one option is to use a recursive query to generate the months, and then bring the table with a left join:
with recursive months as (
select customer_id, date_format(min(order_date), '%Y-%m-01') order_date, max(order_date) max_order_date
from amazon_test
group by customer_id
union all
select customer_id, order_date + interval 1 month, max_order_date
from months
where order_date + interval 1 month < max_order_date
)
select
m.customer_id,
date_format(last_day(m.order_date), '%M %d') new_months,
coalesce(sum(t.products_ordered), 0) ordered_products
from months m
left join amazon_test t
on t.customer_id = m.customer_id
and t.order_date >= m.order_date
and t.order_date < m.order_date + interval 1 month
where m.customer_id = 1
group by m.customer_id, m.order_date
order by m.customer_id, m.order_date
I phrased the query so it actually operates on all customer_ids - if you remove the where clause in the outer query, you do get the results for all customers. If you really want the results for only one customer, you can optimize the query by pushing the where filter to the anchor of the recusive query.
Demo on DB Fiddle:
customer_id | new_months | ordered_products
----------: | :----------- | ---------------:
1 | January 31 | 3
1 | February 29 | 0
1 | March 31 | 1
1 | April 30 | 0
1 | May 31 | 0
1 | June 30 | 0
1 | July 31 | 0
1 | August 31 | 0
1 | September 30 | 1
1 | October 31 | 0
1 | November 30 | 0
1 | December 31 | 2

Get record count of each day of last week. if no record found in particular date then i want 0 as record

My sale Table
ID amount created_at
48 10 2018-10-15 10:57:24
49 20 2018-10-16 10:58:14
50 25 2018-10-22 14:07:31
51 24 2018-10-24 12:13:15
52 36 2018-10-24 12:13:21
53 40 2018-10-30 09:46:37
54 40 2018-10-28 09:46:37
55 40 2018-11-1 09:46:37
56 40 2018-11-2 09:46:37
57 40 2018-11-2 09:46:37
58 40 2018-11-2 09:46:37
59 40 2018-11-2 09:46:37
60 40 2018-11-2 09:46:37
My qyery
SELECT Date(created_at),
Count(*)
FROM sale
WHERE Date(created_at) BETWEEN ( Now() - INTERVAL 7 day ) AND Now()
GROUP BY Date(created_at)
My result
date(created_at) count
2018-10-28 12:13:15 1
2018-10-1 09:46:37 1
2018-10-2 09:46:37 5
Suppose Week Start from 2018-10-28 and i need result like below if there is no record of particular day then it will 0.
day count
mon 1
tue 0
wed 0
thu 1
fri 5
sat 0
sun 0
Firstly, you can create a Master Table representing all the Abbreviated Weekday name(s).
Now, we can use Date_format() function with %a specifier to get the abbreviated weekday name for a particular created_at date. We can use this as our Left Join condition.
Left Join allows us to consider all the Weekdays, even if there is no matching created_at for a particular day.
Count(*) will not work here, as it counts all the rows in a group. However, we don't want to count the rows where there is no matching rows. So, we use Count(created_at), as it will ignore the null values.
Finally, query is made sargable by removing Date() function usage in the Where clause.
You will need to use a master table for week days.
Query:
SELECT week_table.day_order,
week_table.wkday AS `day`,
Count(created_at) AS `count`
FROM
(
SELECT 'Mon' AS wkday, 1 AS day_order UNION ALL
SELECT 'Tue', 2 UNION ALL
SELECT 'Wed', 3 UNION ALL
SELECT 'Thu', 4 UNION ALL
SELECT 'Fri', 5 UNION ALL
SELECT 'Sat', 6 UNION ALL
SELECT 'Sun', 7
) AS week_table
LEFT JOIN sale AS s
ON DATE_FORMAT(created_at, '%a') = week_table.wkday AND
created_at >= ( CURDATE() - INTERVAL 7 day ) AND
created_at < ( CURDATE() + INTERVAL 1 day )
GROUP BY week_table.day_order, week_table.wkday
ORDER BY week_table.day_order;
Result
| day_order | day | count |
| --------- | --- | ----- |
| 1 | Mon | 0 |
| 2 | Tue | 1 |
| 3 | Wed | 0 |
| 4 | Thu | 1 |
| 5 | Fri | 5 |
| 6 | Sat | 0 |
| 7 | Sun | 1 |
View on DB Fiddle

how to to get a record from mysql 20 consecutive days?

I have a table XX. i need to get the records which are 20 days consecutive gap .below is my table look
ID ISmen Date
1 0 2013-05-2
2 0 2013-05-2
3 0 2014-04-2
4 1 2014-05-2
5 1 2014-05-2
6 0 2014-05-2
7 0 2014-05-2
8 0 2014-05-2
9 1 2014-05-25
10 1 2014-05-25
11 0 2014-05-26
12 1 2014-05-27
13 0 2014-05-28
From the above table i need to get the records which are ismen is 1 and the next record ismen is also 1 (i.e 4,5 and 9,10 but not 12).and one more thing 4,5 and 9,10 should have 20 days gap
i am getting the records which are 4,5 and 9,10 ..but i can't able to check date difference between the records .i know we can achieve in the loop but i am trying to get in MySQL is it possible or not.I try below query.thanks in advance for help
SELECT *
FROM XX t1,
XX t2
WHERE (t1.ID=t2.ID+1
OR t1.ID=t2.ID-1)
AND t1.Ismen=1
AND t2.Ismen=1
There is a 23 day gap between |4|5| to |9|10| but ignoring the sample data precision, this result:
| ISMEN | T1ID | T2ID | T1DATE | T2DATE |
|-------|------|------|----------------------------|----------------------------|
| 1 | 4 | 9 | May, 02 2014 00:00:00+0000 | May, 25 2014 00:00:00+0000 |
| 1 | 5 | 9 | May, 02 2014 00:00:00+0000 | May, 25 2014 00:00:00+0000 |
| 1 | 4 | 10 | May, 02 2014 00:00:00+0000 | May, 25 2014 00:00:00+0000 |
| 1 | 5 | 10 | May, 02 2014 00:00:00+0000 | May, 25 2014 00:00:00+0000 |
was produced by this query:
select
t1.ismen
, t1.id as t1id
, t2.id as t2id
, t1.`date` as t1date
, t2.`date` as t2date
from table1 as t1
inner join table1 as t2 on t1.ismen = t2.ismen
and t1.`date` + INTERVAL 23 DAY = t2.`date`
The wanted gap between records can be defined in the join conditions (change to 20 or whatever). But do note there is nothing to stop 4 relating to 9 and 10 or 5 to 9 & 10 so you get 4 records in total.
see: http://sqlfiddle.com/#!9/8d941/1
You could reduce that result by some means (e.g. using row_number() but I don't know if that is required.

sql for the one entry in each group that meets a condition

I have a query
SELECT ckt, setpt, clock FROM progs
WHERE feed = "80302" AND day=4 AND clock<"12:15:00"
ORDER BY ckt, clock DESC
That gets me this:
ckt setpt clock
0 69 06:06:00
0 62 00:30:00
1 57 10:30:00
1 67 04:30:00
1 57 01:30:00
2 69 11:00:00
2 62 00:30:00
How could I modify this query to give me the MAX(clock) for each CKT
In total:
Each cktand day has a number of setpt and clock entries. I am looking for the (1 or none) clock record < some_time for each ckt and day. ie
0 69 06:06:00
1 67 10:30:00
2 69 11:00:00
in mysql.
Something like this?
SELECT ckt, MAX(setpt), MAX(clock)
FROM progs
WHERE feed = "80302" AND day=4 AND clock<"12:15:00"
GROUP BY ckt
ORDER BY ckt, clock DESC
Result
| CKT | MAX(SETPT) | MAX(CLOCK) |
-----------------------------------------------------
| 0 | 69 | January, 01 1970 06:06:00+0000 |
| 1 | 67 | January, 01 1970 10:30:00+0000 |
| 2 | 69 | January, 01 1970 11:00:00+0000 |
See the demo
Sounds like you need to JOIN the table to itself, joining on the MAX(clock):
SELECT p.ckt, p.setpt, p.clock
FROM progs p
JOIN (
SELECT MAX(clock) maxClock, ckt
FROM progs
WHERE feed = "80302"
AND day=4
AND clock<"12:15:00"
GROUP BY ckt
) p2 on p.ckt = p2.ckt AND p.clock = p2.maxclock
ORDER BY p.ckt, p.clock DESC
SQL Fiddle Demo (borrowed from other post)
Producing:
0 69 January, 01 1970 06:06:00+0000
1 57 January, 01 1970 10:30:00+0000
2 69 January, 01 1970 11:00:00+0000
Please note row with id 1 is different than your desired results, but match your question's desired results...

Select highest 3 scores in each day for every user

I have a MYSQL table like this:
id | userid | score | datestamp |
-----------------------------------------------------
1 | 1 | 5 | 2012-12-06 03:55:16
2 | 2 | 0,5 | 2012-12-06 04:25:21
3 | 1 | 7 | 2012-12-06 04:35:33
4 | 3 | 12 | 2012-12-06 04:55:45
5 | 2 | 22 | 2012-12-06 05:25:11
6 | 1 | 16,5 | 2012-12-06 05:55:21
7 | 1 | 19 | 2012-12-06 13:55:16
8 | 2 | 8,5 | 2012-12-07 06:27:16
9 | 2 | 7,5 | 2012-12-07 08:33:16
10 | 1 | 10 | 2012-12-07 09:25:19
11 | 1 | 6,5 | 2012-12-07 13:33:16
12 | 3 | 6 | 2012-12-07 15:45:44
13 | 2 | 4 | 2012-12-07 16:05:16
14 | 2 | 34 | 2012-12-07 18:33:55
15 | 2 | 22 | 2012-12-07 18:42:11
I would like to display user scores like this:
if a user on a certain day has more than 3 scores it would get only highest 3, repeat that for every day for this user and then add all days together. I want to display this sum for every user.
EDIT:
So in the example above for user 1 on 06.12. I would add top 3 scores together and ignore 4th score, then add to that number top 3 from the next day and so on. I need that number for every user.
EDIT 2:
Expected output is:
userid | score
--------------------
1 | 59 //19 + 16.5 + 7 (06.12.) + 10 + 6.5 (07.12.)
2 | 87 //22 + 0.5 (06.12.) + 34 + 22 + 8.5 (07.12.)
3 | 18 //12 (06.12.) + 6 (07.12.)
I hope this is more clear :)
I would really appreciate the help because I am stuck.
Please take a look at the following code, if your answer to my comment is yes :) Since your data all in 2012, and month of november, I took day.
SQLFIDDLE sample
Query:
select y.id, y.userid, y.score, y.datestamp
from (select id, userid, score, datestamp
from scores
group by day(datestamp)) as y
where (select count(*)
from (select id, userid, score, datestamp
from scores group by day(datestamp)) as x
where y.score >= x.score
and y.userid = x.userid
) =1 -- Top 3rd, 2nd, 1st
order by y.score desc
;
Results:
ID USERID SCORE DATESTAMP
8 2 8.5 December, 07 2012 00:00:00+0000
20 3 6 December, 08 2012 00:00:00+0000
1 1 5 December, 06 2012 00:00:00+0000
Based on your latter updates to question.
If you need some per user by year/month/day and then find highest, you may simply add aggregation function like sum to the above query. I am reapeating myself, since your sample data is for just one year, there's no point group by year or month. That's why I took day.
select y.id, y.userid, y.score, y.datestamp
from (select id, userid, sum(score) as score,
datestamp
from scores
group by userid, day(datestamp)) as y
where (select count(*)
from (select id, userid, sum(score) as score
, datestamp
from scores
group by userid, day(datestamp)) as x
where y.score >= x.score
and y.userid = x.userid
) =1 -- Top 3rd, 2nd, 1st
order by y.score desc
;
Results based on sum:
ID USERID SCORE DATESTAMP
1 1 47.5 December, 06 2012 00:00:00+0000
8 2 16 December, 07 2012 00:00:00+0000
20 3 6 December, 08 2012 00:00:00+0000
UPDATED WITH NEW SOURCE DATA SAMPLE
Simon, please take a look at my own sample. As your data was changing, I used mine.
Here is the reference. I have used pure ansi style without any over partition or dense_rank.
Also note the data I used are getting top 2 not top 3 scores. You can change is accordingly.
Guess what, the answer is 10 times simpler than the first impression your first data gave....
SQLFIDDLE
Query to 1:
-- for top 2 sum by user by each day
SELECT userid, sum(Score), datestamp
FROM scores t1
where 2 >=
(SELECT count(*)
from scores t2
where t1.score <= t2.score
and t1.userid = t2.userid
and day(t1.datestamp) = day(t2.datestamp)
order by t2.score desc)
group by userid, datestamp
;
Results for query 1:
USERID SUM(SCORE) DATESTAMP
1 70 December, 06 2012 00:00:00+0000
1 30 December, 07 2012 00:00:00+0000
2 22 December, 06 2012 00:00:00+0000
2 25 December, 07 2012 00:00:00+0000
3 30 December, 06 2012 00:00:00+0000
3 30 December, 07 2012 00:00:00+0000
Final Query:
-- for all two days top 2 sum by user
SELECT userid, sum(Score)
FROM scores t1
where 2 >=
(SELECT count(*)
from scores t2
where t1.score <= t2.score
and t1.userid = t2.userid
and day(t1.datestamp) = day(t2.datestamp)
order by t2.score desc)
group by userid
;
Final Results:
USERID SUM(SCORE)
1 100
2 47
3 60
Here goes a snapshot of direct calculations of data I used.
SELECT
*
FROM
table1
LEFT JOIN
(SELECT * FROM table1 ORDER BY score LIMIT 3) as lr on DATE(lr.datestamp) = DATE(table1.datastamp)
GROUP BY
datestamp