I have a table
DAY 1
ID
amount
DATE
1
10
12-02-2020
2
15
12-02-2020
3
20
12-02-2020
4
25
12-02-2020
I did a sum of the amount on day one which turns out to be 70
Now next day I have few more rows where the amount is UPDATED an APPENDED
New tables looks like this
DAY 2
ID
amount
DATE
1
10
12-02-2020
2
20
13-02-2020
3
20
12-02-2020
4
25
12-02-2020
5
30
13-02-2020
6
35
14-02-2020
Now if you see the ID 2 has new updates amount which is 20 earlier 15
and it has new data from dates 13 and 14 on ID 5 and 6
Can I just run a query where it will only process the changed data and add it to the
previous sum
so like 30+35+5(as only 5 increased from the last value)
total = 70
Mainly to process changed data
This will very much depend on how the historical data will be provided.
This example requires additional Day column in the historical data table AND that you're using a MySQL version that supports LAG() (e.g. MySQL v8+ OR MariaDB 10.3+). Let's assume that it's possible for the historical data table to be like this:
ID
Amount
Date
Day
1
10
2020-02-12
1
2
15
2020-02-12
1
3
20
2020-02-12
1
4
25
2020-02-12
1
1
10
2020-02-12
2
2
20
2020-02-13
2
3
20
2020-02-12
2
4
25
2020-02-12
2
5
30
2020-02-13
2
6
35
2020-02-14
2
.. then maybe a query like this:
SELECT Day,
SUM(amount) AS Total,
SUM(amount)-LAG(SUM(amount)) OVER (ORDER BY Day) AS diff
FROM historical_data
GROUP BY Day
ORDER BY Day;
OR (in for MariaDB):
SELECT Day, Total,
Total-LAG(Total) OVER (ORDER BY Day) AS Diff
FROM
(SELECT Day,
SUM(amount) AS Total
FROM historical_data
GROUP BY Day) A;
This will return result like:
Day
Total
diff
1
70
2
140
70
I was following an example from this site on how to use LAG() to get the row data value above it an using them to subtract the SUM(amount) value for that day.
Here's a demo fiddle of the experiment.
I have a dbReadtable which I wanna sort by week date. My table looks as follow:
Id Date Number
1 2020-11-01 1
2 2020-11-03 3
3 2020-11-02 4
4 2020-11-01 2
5 2020-11-02 4
6 2020-11-03 3
7 2020-11-06 7
8 2020-11-05 4
9 2020-11-08 2
I want the output to look something like this:
Day Number
Sunday 5
Monday 8
Tuseday 6
Thursday 4
Friday 7
For this, I wanna use $wday in lubridate library.
My code looks as follow:
data <- dbReadTable(con, "observations")
How do I solve this?
Using data.table and lubridate and assuming Date is already a date (not a string).
library(lubridate)
library(data.table)
data <- dbReadTable(con, "observations")
data[, by=.(Day=wday(Date, label=TRUE, abbr=FALSE, locale="C")), .(Number=sum(Number))]
# Day Number
#1: Sunday 5
#2: Tuesday 6
#3: Monday 8
#4: Friday 7
#5: Thursday 4
I added locale to return english names for days (I'm french and get french names :-).
NEXT_DAY("01-SEP-95","FRIDAY") returns what is the date on next Friday, but in MySQL this function does not seem to be appear. What is the alternative?
I'm going to throw my hat in the ring with yet another approach:
Edit: I realize somewhat belatedly that the Oracle function in question takes a string as the second argument, and so this doesn't precisely fit the requirement. However MySQL has already kindly defined 0 - 6 as Monday - Sunday, and anyway I have moral objections to using a string as an argument for this type of thing. A string would either come from user input or yet another mapping in higher level code between numeric and string values. Why not pass an integer? :)
CREATE FUNCTION `fnDayOfWeekGetNext`(
p_date DATE,
p_weekday TINYINT(3)
) RETURNS date
BEGIN
RETURN DATE_ADD(p_date, INTERVAL p_weekday - WEEKDAY(p_date) + (ROUND(WEEKDAY(p_date) / (p_weekday + WEEKDAY(p_date) + 1)) * 7) DAY);
END
To break the portion down that determines the INTERVAL value:
The first part of the equation simply gets the offset between the weekday specified and the weekday of the date specified:
p_weekday - WEEKDAY(p_date)
This will return a positive number if p_weekday is greater than WEEKDAY(p_date) and vice versa. Zero will be returned if they're the same.
The ROUND() segment is used to determine whether the requested day of the week (p_weekday) has already occurred in the current week relative to the date (p_date) specified. So, by example...
ROUND(WEEKDAY('2019-01-25') / (6 + WEEKDAY('2019-01-25') + 1))
..returns 0, indicating that Sunday (6) has not occurred this week, as 2019-01-25 is a Friday. Likewise...
ROUND(WEEKDAY('2019-01-25') / (2 + WEEKDAY('2019-01-25') + 1))
...returns 1 because Wednesday (2) has already passed. Note that this will return 0 if p_weekday is the same as the weekday of p_date.
This value (either 1 or 0) is then multiplied by the constant 7 (the number of days in a week).
Hence if p_weekday has already occurred in the current week, it will add 7 to the offset p_weekday - WEEKDAY(p_date), because that offset would be a negative number and we want a date in the future.
If p_weekday has yet to occur in the current week, then we can just add the offset to the current date because the offset will be a positive number. Hence the section ROUND(...) * 7 is equal to zero and, in essence, ignored.
My desire for this approach was to simulate an IF() condition mathematically. This would be equally valid:
RETURN DATE_ADD(p_date, INTERVAL p_weekday - WEEKDAY(p_date) + IF(p_weekday - WEEKDAY(p_date) < 0, 7, 0) DAY);
And in the interest of objectivity, in running 1M iterations a few times of each function the IF-based version averaged about 4.2% faster than the ROUND-based version.
In MySQL, you can create user defined function
DELIMITER //
CREATE FUNCTION next_day(start_date DATETIME, weekday CHAR(20))
RETURNS DATETIME
BEGIN
DECLARE start DATETIME;
DECLARE i INT;
// Select the next date
SET start = ADDDATE(start_date, 1);
SET i = 1;
days: LOOP
-- Compare the day names
IF SUBSTR(DAYNAME(start), 1, 3) = SUBSTR(weekday, 1, 3) THEN
LEAVE days;
END IF;
// Select the next date
SET start = ADDDATE(start, 1);
SET i = i + 1;
-- Not valid weekday specified
IF i > 7 THEN
SET start = NULL;
LEAVE days;
END IF;
END LOOP days;
RETURN start;
END;
//
DELIMITER ;
and call it
SELECT NEXT_DAY("1995-09-01","FRIDAY")
Source : http://www.sqlines.com/mysql/how-to/next_day
Here's an answer to a different problem:
Find the date for Friday of the current week:-
SELECT STR_TO_DATE(CONCAT('2017',(SELECT DATE_FORMAT(CURDATE(),'%U')),' Friday'),'%Y %U %W') x;
+------------+
| x |
+------------+
| 2017-05-12 |
+------------+
The above hasn't been verified for robustness, but is provided 'as is' merely as food for thought.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE your_table ( value DATE );
INSERT INTO your_table ( value )
SELECT DATE '2017-05-08' FROM DUAL UNION ALL
SELECT DATE '2017-05-09' FROM DUAL UNION ALL
SELECT DATE '2017-05-10' FROM DUAL UNION ALL
SELECT DATE '2017-05-11' FROM DUAL UNION ALL
SELECT DATE '2017-05-12' FROM DUAL UNION ALL
SELECT DATE '2017-05-13' FROM DUAL UNION ALL
SELECT DATE '2017-05-14' FROM DUAL;
Query 1:
SELECT value,
ADDDATE(
value,
6 - DAYOFWEEK( value )
+ CASE WHEN DAYOFWEEK( value ) < 6 THEN 0 ELSE 7 END
) AS next_friday
FROM your_table
Results:
| value | next_friday |
|-----------------------|-----------------------|
| May, 08 2017 00:00:00 | May, 12 2017 00:00:00 |
| May, 09 2017 00:00:00 | May, 12 2017 00:00:00 |
| May, 10 2017 00:00:00 | May, 12 2017 00:00:00 |
| May, 11 2017 00:00:00 | May, 12 2017 00:00:00 |
| May, 12 2017 00:00:00 | May, 19 2017 00:00:00 |
| May, 13 2017 00:00:00 | May, 19 2017 00:00:00 |
| May, 14 2017 00:00:00 | May, 19 2017 00:00:00 |
Short Version:
Utilizing DAYOFWEEK() you can leverage the number values to create a closed formula that tells you how many days to add. You can then utilize DATE_ADD to help you shift the date. There may be a cleaner way, however you should be able to make a function out of this.
The following is an Excel formula, which you use to verify all 49 use cases: =MOD(MOD(Next-Input, 7)+7,7)
Some examples of how the excel calculations can be translated into SQL:
SELECT (((1-DAYOFWEEK('2018-08-15')) % 7)+7) % 7 AS DaysToAddToGetNextSundayFromWednesday
FROM dual; -- 4
SELECT DATE_ADD('2018-08-15', INTERVAL (((1-DAYOFWEEK('2018-08-15')) % 7)+7) % 7 DAY) AS NextSundayFromDate
FROM dual; -- 2018-08-19
If you plan to use the above often, you'll probably want to make a function or stored procedure.
Long Version:
I've run into this problem multiple times over the years. First time I created a giant case statement for all 49 cases. I found that this made the queries super big and not clean. I wanted a cleaner, simpler solution like a closed in formula. Below are details to to calculations I did to confirm the formula above works. I tested this with MySQL 5.7 if you are using MySQL 8.5 it looks like the function is built in ( Reference: http://www.sqlines.com/mysql/how-to/next_day ).
Note: Excel doesn't return negative results for modulus, where MySQL does. That is why we add 7 and do another modulus.
DAYOFWEEK()
Sun 1
Mon 2
Tues 3
Wed 4
Thur 5
Fri 6
Sat 7
Input Next Expected Days to Add Formula Result
1 1 0 0
1 2 1 1
1 3 2 2
1 4 3 3
1 5 4 4
1 6 5 5
1 7 6 6
2 1 6 6
2 2 0 0
2 3 1 1
2 4 2 2
2 5 3 3
2 6 4 4
2 7 5 5
3 1 5 5
3 2 6 6
3 3 0 0
3 4 1 1
3 5 2 2
3 6 3 3
3 7 4 4
4 1 4 4
4 2 5 5
4 3 6 6
4 4 0 0
4 5 1 1
4 6 2 2
4 7 3 3
5 1 3 3
5 2 4 4
5 3 5 5
5 4 6 6
5 5 0 0
5 6 1 1
5 7 2 2
6 1 2 2
6 2 3 3
6 3 4 4
6 4 5 5
6 5 6 6
6 6 0 0
6 7 1 1
7 1 1 1
7 2 2 2
7 3 3 3
7 4 4 4
7 5 5 5
7 6 6 6
7 7 0 0
I have a table with 4 schedule interval:
id time_int progr_comment A B C D
1 05:30:00 Good Morning 1 4 2 17
2 06:50:00 Have a nice day 1 4 2 17
3 17:00:00 Welcome Home 1 4 4 23
4 18:30:00 Good Evening 1 4 2 22
5 22:00:00 Good Night 1 4 2 20
For each interval I compare with NOW() and I will take the variables (A, B, C, D) from table based on time_int <= NOW() then my program create a query to mysql. The problem come after 0:00 hour. The query does not find anything until NOW() pass the first interval (ex. 5:30:00).
i have a database with workers, stations and session. A session describes at which time which worker has been on which station. I managed to build a query that gives me the duration of the overlap of each session.
SELECT
sA.station_id,
sA.worker_id AS worker1,
sB.worker_id AS worker2,
SEC_TO_TIME(
TIME_TO_SEC(LEAST(sA.end,sB.end)) - TIME_TO_SEC(GREATEST(sA.start,sB.start))
) AS overlap
FROM
`sessions` AS sA,
`sessions` AS sB
WHERE
sA.station_id = sb.station_id
AND
sA.station_id = 6
AND (
sA.start BETWEEN sB.start AND sB.end
OR
sA.end BETWEEN sB.start AND sB.end
)
With this query i get an result like this
station_id worker1 worker2 overlap
6 1 1 09:00:00
6 2 1 02:30:00
6 5 1 00:00:00
6 1 1 09:00:00
6 2 1 01:30:00
6 3 1 09:00:00
...
6 12 3 02:00:00
6 14 3 01:00:00
6 17 3 02:00:00
...
What i would like now is to sum up the overlap for every combination of worker1 and worker2 to get the overall overlap duration.
I tried different ways of using SUM() and GROUP BY but i never got the wanted result.
SELECT
...
SEC_TO_TIME(
**SUM**(TIME_TO_SEC(LEAST(sA.end,sB.end)) - TIME_TO_SEC(GREATEST(sA.start,sB.start)))
) AS overlap
...
#has as result
station_id worker1 worker2 overlap
6 1 1 838:59:59
#in combination with
GROUP BY
worker1
#i get
station_id worker1 worker2 overlap
6 1 1 532:30:00
6 2 1 -33:00:00
6 3 1 270:30:00
6 5 1 598:30:00
6 6 1 542:00:00
6 7 1 508:00:00
6 8 5 53:00:00
6 9 1 54:30:00
6 10 1 310:00:00
6 11 1 -108:00:00
6 12 1 593:30:00
6 14 1 97:30:00
6 15 1 -53:30:00
6 17 1 293:30:00
the last result is close but i am still missing a lot of combinations. I also dont understand why the combination 8 - 5 is displayed.
thanks for ur help (and time to read)
aaargh, sorry for my stupidity, the solution was fairly simple
....
SUM(((UNIX_TIMESTAMP(LEAST(sA.end,sB.end))-UNIX_TIMESTAMP(GREATEST(sA.start,sB.start)))/3600))
...
GROUP BY station_id, worker1, worker2
ORDER BY worker1, worker2
i switched to using timestamps and transforming it to hours by /3600 because my former used approach with TIME_TO_SEC and SEC_TO_TIME only used the TIME part of the DATETIME field and thereby produced some wrong numbers. With MySQL 5.5 i could use TO_SECONDS but unfortunately my server is still runing 5.1.