Mysql sum multiple column values with date condition - mysql

I have a client table with below columns which have data of every day purchase of every client month wise.
ID|MONTH|DAY1|DAY2|DAY3|DAY4|..........|DAY31
1 | 4 | 10 | 20 | 0 | 15 |..........|10
2 | 4 | 20 | 30 | 23 | 7 |..........| 5
1 | 5 | 5 | 10 | 20 | 4 |..........| 20
1 | 6 | 12 | 0 | 10 | 5 |..........| 10
2 | 6 | 10 | 10 | 5 | 10 |..........| 5
Now i want to find the total qty purchased by every client between 15/4/2015 to 15/6/2015.
I am new to mysql, so have no idea how to move forward.
Thanks in advance

It's not a good idea to use a column for every day of the month to store a value. But sometimes we are stuck on bad data formats, here's how you can get the count you need:
SELECT ID, SUM(qty)
FROM (
SELECT
ID,
MAKEDATE(2015,1) + INTERVAL (month-1) MONTH AS dt,
DAY1 AS qry
FROM yourtable
UNION ALL
SELECT
ID,
MAKEDATE(2015,1) + INTERVAL (month-1) MONTH AS dt + INTERVAL 1 DAY,
DAY2 AS qry
UNION ALL
SELECT
ID,
MAKEDATE(2015,1) + INTERVAL (month-1) MONTH AS dt + INTERVAL 2 DAY,
DAY3 AS qry
FROM yourtable
UNION ALL
...
until day 31
...
) s
WHERE
dt>='2015-04-15' AND dt<='2015-06-15'
GROUP BY ID
I'm using a subquery to normalize the data structure, then I'm doing the counts on the outer query, a simple where clause and group by will give the results that you need.

Related

MySQL Query to get only one entry per interval from database

I have a table with this structure and some sample values:
ID | created | value | person
1 | 1 | 5 | 1
2 | 2 | 2 | 2
3 | 3 | 3 | 3
4 | 4 | 5 | 1
5 | 5 | 1 | 2
6 | 6 | 32 | 3
7 | 7 | 9 | 1
8 | 8 | 34 | 2
10 | 9 | 25 | 3
11 | 11 | 53 | 1
12 | 12 | 52 | 2
13 | 13 | 15 | 3
... etc
The created column will have timestamps. I.e. A number like "1555073978". I just made it incremental to demonstrate that the timestamps will rarely be the same.
So values are stored per person with creation times. Values are added every minute. After a week, this table is quite big. So when I do a query to draw a graph, PHP run's out of memory because the dataset is so huge.
So what I am looking for, is an easy way to do a query on a table like this, so that I get values in smaller intervals.
How would I query this table, so that I get:
- only one value per person per interval
- where interval should be 15 mins, 30 mins, 60 mins etc (i.e. a parameter in the query)
I've started with an approach but don't want to spend too much time, in case i am missing a much easier way. My way involves converting the timestamp to YEAR-MONTH-DAY-HOUR, but this will only work for hourly. I am also struggling to make sure that the query returns the MOST RECENT entry PER PERSON for that hour.
Any help would be greatly appreciated.
assuming your created column is a timestamp and you want the max value for person every each 15 minutes you could try
select person, max(value)
from my_table
group by person, FLOOR(UNIX_TIMESTAMP(created )/(15 * 60))
but if you dont need unix_timestamp
then
group by person, FLOOR(created /(15 * 60))
If you want the most recent values for person and interval then you could use
select * from my_table m
inner join (
select person, max(created) max_created
from my_table
group by person, FLOOR(UNIX_TIMESTAMP(created )/(15 * 60))
) t on t.person = m.person and t.max_created = m.created

How to get the number of days in period?

Storage table
| id| product_id | date_add | date_remove
------------------------------------------------------------------
| 1 | 10 |2018-04-02 08:28:43 | 2018-04-03 07:21:08
| 2 | 10 |2018-04-05 08:28:43 | 2018-04-06 08:28:50
| 3 | 10 |2018-04-01 08:28:43 | 2018-04-05 08:28:50
| 4 | 12 |2018-04-01 08:28:43 | 2018-04-03 07:21:08
| 5 | 12 |2018-04-04 08:28:43 | 2018-04-04 10:28:43
| 6 | 13 |2018-03-01 08:28:43 | 2018-03-01 10:28:43
how to find ?
how many days product was in the storage in period 2018-04-01 to 2018-04-05?
find result
| product_id | days
| 10 | 5
| 12 | 3
try
SELECT product_id, SUM(DATEDIFF(date_remove, date_add)) as days
FROM storage
where date_remove BETWEEN '2018-04-01 00:00:00'
AND '2018-04-05 23:59:59'
AND date_add BETWEEN '2018-04-01 00:00:00'
AND '2018-04-05 23:59:59'
GROUP BY product_id
but result wrong because 'SUM' sums all days
get result
| product_id | days
| 10 | 7
correct result
| product_id | days
| 10 | 5
upd
http://rextester.com/QFS96125
result 9,646805555556 but probably maximum 5 days and product_id 13 correct 0,436608796296 but result 0,87
First you want to look at all date ranges that are within or overlap with the range 2018-04-01 to 2018-04-05.
where date_add < date '2018-04-06' and date_remove >= date '2018-04-01'
Then, with the ranges found, you want to consider only their days in the range 2018-04-01 to 2018-04-05.
greatest(date_add, date '2018-04-01')
least(date_remove, date '2018-04-06')
Then you want to count days. Here you need a rule. Do you want to look at single ranges and only take their full days which you add up then? Or do you want to consider day fractions, add all up and see how many full days result? For the latter you could get durations in seconds and add these up:
select
product_id,
sum(timestampdiff(second, greatest(date_add, date '2018-04-01'),
least(date_remove, date '2018-04-06'))
) / 60 / 60 / 24 as days
from storage
where date_add < date '2018-04-06' and date_remove >= date '2018-04-01'
group by product_id
order by product_id;
This gets you
product_id | days
-----------+---------------
10 | 5,599872685185
12 | 2,036400462963
Feel free to use FLOOR, CEIL or ROUND on the resulting days.
Rextester demo: http://rextester.com/XTVU47656
To obtain such result, try
SELECT SUM(DATEDIFF(date_remove, date_add)) as days
FROM table
GROUP BY product_id
Keep in mind that this will sum all the days for the same product_id. To get the result for each id, use:
SELECT id, product_id, DATEDIFF(date_remove, date_add) as days
FROM table

UPDATE + SET + WHERE - Dynamic minimum value

This is a follow-up to:
Dynamic minimum value for specfic range (mysql)
I do have the query to fetch the third column (lowest of the last 3 days) "Low_3_days" via SELECT command:
-----------------------------------------
| Date | Unit_ | Lowest_in_last_|
| | price | 3_days |
|----------------------------------------
| 2015-01-01 | 15 | 15 |
| 2015-01-02 | 17 | 15 |
| 2015-01-03 | 21 | 15 |
| 2015-01-04 | 18 | 17 |
| 2015-01-05 | 12 | 12 |
| 2015-01-06 | 14 | 12 |
| 2015-01-07 | 16 | 12 |
|----------------------------------------
select S.Date,Unit_price,
(select S.Date, Unit_price,
(SELECT min(s2.Unit_Price)
FROM table s2
WHERE s2.DATE BETWEEN s.DATE - interval 3 day and
s.DATE - interval 1 day
) as min_price_3_days
FROM table S;
My new challenge is - what is the best way to use UPDATE-SET-WHERE so I could add the ("Lowest_in_last_3_days") values to a new column in a table (instead of having temporary results displayed to me via SELECT).
By following the UPDATE-SET-WHERE syntax, the query would be:
UPDATE table
SET min_price_3_days =
(select S.Date, Unit_price,
(SELECT min(s2.Unit_Price)
FROM table s2
WHERE s2.DATE BETWEEN s.DATE - interval 3 day and
s.DATE - interval 1 day
) as min_price_3_days
but I have difficulties constructing the correct query.
What would be the correct approach to this? I do recognize this one is a tough one to solve.
Your UPDATE should look like:
update table set low_3_days=
(SELECT min(Unit_Price)
FROM (select unit_price, date as date2 from table) as s2
WHERE s2.date2 BETWEEN date - interval 3 day and date - interval 1 day
);
You can check it in SQLFiddle
In Fiddle I used different names for table and column. I prefer not to use SQL keywords as names

SQL "First relevant day"

I have a table with opening_hours for restaurants:
SELECT * FROM opening_hours;
+----+---------------+------------+----------+-----+
| id | restaurant_id | start_time | end_time | day |
+----+---------------+------------+----------+-----+
| 1 | 1 | 12:00:00 | 18:00:00 | 1 |
| 2 | 1 | 09:00:00 | 19:00:00 | 4 |
| 3 | 2 | 09:00:00 | 16:00:00 | 4 |
| 4 | 2 | 09:00:00 | 16:00:00 | 5 |
| 5 | 3 | 09:00:00 | 16:00:00 | 4 |
| 6 | 3 | 09:00:00 | 16:00:00 | 5 |
| 7 | 3 | 09:00:00 | 16:00:00 | 1 |
| 8 | 3 | 09:00:00 | 16:00:00 | 6 |
+----+---------------+------------+----------+-----+
http://www.sqlfiddle.com/#!2/eaea09/1
Now I want to fetch the "closest" next day or same day for every restaurant to the current day. For example when the current day is 1 the result would be:
restaurant_id: 1 day: 1
restaurant_id: 2 day: 4
restaurant_id: 3 day: 1
In the case of day 1 I could do this:
SELECT day FROM opening_hours WHERE day >= 1 GROUP BY restaurant_id LIMIT 1
But if today would be 6 that would not work. I would need the query to go get the maximum number of days (7) and if that could not be found it should start trying from 1 again. So the result for day 6 would be in this case:
restaurant_id: 1 day: 1
restaurant_id: 2 day: 4
restaurant_id: 3 day: 6
How could I achieve this with a query?
I'd think it could be something like this in pseudo SQL:
SELECT `day` FROM opening_hours WHERE `day` >= 'today' IF NOT FOUND WHERE `day` >= 1 GROUP BY `restaurant_id` LIMIT 1
edit:
I could run 2 queries, and determine if a match for a restaurant was found in the first. If not, run a second query. But there must be a better way.
Tested, this one works :) you can keep it simple.
Query:
SELECT * FROM (
SELECT
oh.*
FROM
opening_hours oh
ORDER BY restaurant_id,
`day` + IF(`day` < $current_day, 7, 0)
) sq
GROUP BY restaurant_id;
Explanation:
Note though, that this is a bit hacky. To select a column that is not used in the group by and has no aggregate function applied to it, usually isn't allowed, because theoretically it could give you a random row of each group. That's why it's not allowed in most database systems. MySQL is actually the only one I know of, that allows this (if not set otherwise via sql-mode). Like I said, in theory. Practically it's a bit different and if you do an order by in the subquery, MySQL will always give you the minimum or maximum value (depending on the sort order).
Tests:
Desired result with current day = 1:
root#VM:playground > SELECT * FROM (
-> SELECT
-> oh.*
-> FROM
-> opening_hours oh
-> ORDER BY restaurant_id,
-> `day` + IF(`day` < 1, 7, 0)
-> ) sq
-> GROUP BY restaurant_id;
+----+---------------+------------+----------+-----+
| id | restaurant_id | start_time | end_time | day |
+----+---------------+------------+----------+-----+
| 1 | 1 | 12:00:00 | 18:00:00 | 1 |
| 3 | 2 | 09:00:00 | 16:00:00 | 4 |
| 7 | 3 | 09:00:00 | 16:00:00 | 1 |
+----+---------------+------------+----------+-----+
3 rows in set (0.00 sec)
Desired result with current day = 6:
root#VM:playground > SELECT * FROM (
-> SELECT
-> oh.*
-> FROM
-> opening_hours oh
-> ORDER BY restaurant_id,
-> `day` + IF(`day` < 6, 7, 0)
-> ) sq
-> GROUP BY restaurant_id;
+----+---------------+------------+----------+-----+
| id | restaurant_id | start_time | end_time | day |
+----+---------------+------------+----------+-----+
| 1 | 1 | 12:00:00 | 18:00:00 | 1 |
| 3 | 2 | 09:00:00 | 16:00:00 | 4 |
| 8 | 3 | 09:00:00 | 16:00:00 | 6 |
+----+---------------+------------+----------+-----+
3 rows in set (0.00 sec)
This is a tricky one.
Best I can come up with is this, which seems to work but I might be missing some edge cases.
SELECT sub0.restaurant_id, MIN(sub1.day)
FROM
(
SELECT restaurant_id, MIN( LEAST(ABS(DAYOFWEEK(CURDATE()) - day), ABS(DAYOFWEEK(CURDATE()) - (day + 7)), ABS(DAYOFWEEK(CURDATE()) - (day - 7)))) AS difference
FROM opening_hours
GROUP BY restaurant_id
) sub0
INNER JOIN
(
SELECT restaurant_id, day, LEAST(ABS(DAYOFWEEK(CURDATE()) - day), ABS(DAYOFWEEK(CURDATE()) - (day + 7)), ABS(DAYOFWEEK(CURDATE()) - (day - 7))) AS difference
FROM opening_hours
) sub1
ON sub0.restaurant_id = sub1.restaurant_id
AND sub0.difference = sub1.difference
GROUP BY sub0.restaurant_id
The first sub query is getting the absolute difference between todays day and each restaurant day. It is using the day, the day plus 7 and the day minus 7 to compare with, using ABS to just get the difference in days and using LEAST to get the lowest of those differences. This way if the current day is 1 and there is a restaurant day of 6 it is comparing 1 + 7 with 6, 1 - 7 with 6 and 1 with 6 and getting the least of those (in this case that would be 1 + 7).
The 2nd sub query just gets that difference and the day of the week for each possible restaurant / day, and this is joined to the first sub query.
The outer query uses MIN just to pick a single day when 2 are just as close.
Something like that should do it:
SELECT oh1.*
-- set the start day
FROM (SELECT #start := 1) AS start,
-- calculate difference in days
(SELECT *, (CASE WHEN day-#start >= 0 THEN day-#start ELSE day-#start+7 END) AS diff
FROM opening_hours) AS oh1
-- find minimum difference
JOIN (SELECT restaurant_id, MIN(CASE WHEN day-#start >= 0 THEN day-#start ELSE day-#start+7 END) AS min_diff
FROM opening_hours
GROUP BY restaurant_id) AS oh2
ON oh1.restaurant_id = oh2.restaurant_id AND
oh1.diff = oh2.min_diff
Replace #start := 1 with your starting day or a call to DAYOFWEEK(CURDATE()), depending on how you want to do it!
First step is to simply add 7 to the result of your day difference calculation when the day is less than the day you are searching for:
SET #Day = 6;
SELECT ID, Restaurant_id, Day,
CASE WHEN Day < #Day THEN 7 ELSE 0 END + Day - #Day AS DaysFromNow
FROM opening_hours;
This will give:
ID RESTAURANT_ID DAY DAYSFROMNOW
1 1 1 2
2 1 4 5
3 2 4 5
4 2 5 6
5 3 4 5
6 3 5 6
7 3 1 2
8 3 6 0
Then to get the next relavant day you need to get the minimum DaysFromNow for each restaurant, then join back to your main table:
SET #Day = 6;
SELECT o.*
FROM opening_hours AS o
INNER JOIN
( SELECT Restaurant_id,
MIN(CASE WHEN Day < #Day THEN 7 ELSE 0 END + Day - #Day) AS DaysFromNow
FROM opening_hours
GROUP BY Restaurant_id
) AS mo
ON mo.Restaurant_id = o.Restaurant_id
AND mo.DaysFromNow = (CASE WHEN Day < #Day THEN 7 ELSE 0 END + Day - #Day);
Example on SQL Fiddle

sql query with current day comparison

I have a mysql table with a due_date field which is simply an integer value.
dealID | due_day
1 | 15
2 | 25
3 | 10
4 | 9
5 | 31
6 | 20
I would like to query this table to only display the data that would be 14 days before the due_day. For example, today is 01/05/13, if I query this table it should only show me dealID 1, 3 and 9. How should I go about this condition?
You can do that simply using DATE_SUB to substract the number of days from current date and then DAYOFMONTH to get the day.
You can create the query using the mentioned functions.
So based on user1951544's answer this is what I came up with.
SELECT due_day
FROM deals
WHERE (due_day - DAYOFMONTH( NOW( ) ) ) <=14
Query:
SQLFIDDLEExample
SELECT
dealID,
due_day
FROM Table1
WHERE due_day < 14 + DAYOFMONTH( NOW( ) )
Result:
| DEALID | DUE_DAY |
--------------------
| 1 | 15 |
| 3 | 10 |
| 4 | 9 |