Select SUM "separately" for the past 30 days - php

I have a MySQL table which is as follows
------------------------------------------------
| id | user_id | day | value |
| INT(11) | INT(11) | INT(8) | DECIMAL(5,2) |
------------------------------------------------
Day is (Ymd) number like this: 20191012
I want to grab the SUM(value) for Today and the past 29 days separately (30 days, based on day column)
I thought of a loop like this
for($i=0;$i<=30;$i++)
{
$query = "SELECT SUM(value) FROM table WHERE day=".date('Ymd', strtotime("-{$i} days"));
}
But how can I achieve this more efficiently?
Any help is appreciated.

You could use Group By to fetch it with a single MySQL-Query
SELECT day, SUM(value) FROM table GROUP BY day WHERE day >= ".date('Ymd', strtotime("-29 days"));
It should work because your day field is a number and will increase everyday so you can just compare it with gte
Expected output will be
---------------------------
| day | SUM(value) |
| INT(8) | |
---------------------------
| 20191012 | 15.25 |
---------------------------
| 20191011 | 29.13 |
---------------------------
| ... | ... |
---------------------------
J4I: If there are days without values in your dataset, the missing "day-row" is also missing in the output so it could be that there are less than 30 result-rows.

This query will work for any version of MySQL. It creates a temporary numbers table with the numbers from 0-29, then uses that to compute a list of days from today to 29 days earlier. This is then LEFT JOINed to the data table to compute the sum per day, with 0 sums where there is no data for a given day:
SELECT d.day, COALESCE(SUM(value), 0)
FROM (SELECT DATE_FORMAT(CURDATE() -INTERVAL n10.n * 10 + n.n DAY, '%Y%m%d') AS day
FROM (SELECT 0 AS n 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) n
CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2) n10) d
LEFT JOIN `table` t ON t.day = d.day
GROUP BY d.day
Demo on dbfiddle

Related

Joining a calendar table to a sales table to get total sales for every day in a specified range

I have a 'sales' table called phpbb_sold which records each 'sale' as a row.
I am able to use a WHERE clause with the uitemid field to select one particular item in the sales records, as seen below:
SELECT uitemid, locktime, migrated_sold FROM phpbb_sold WHERE uitemid=342;
+---------+------------+---------------+
| uitemid | locktime | migrated_sold |
+---------+------------+---------------+
| 342 | 1632523854 | 1 |
| 342 | 1634239244 | 1 |
| 342 | 1634240072 | 1 |
| 342 | 1636367271 | 1 |
+---------+------------+---------------+
uitemid = number that identifies this as a sale of X item. locktime = UNIX timestamp that shows the datetime that the item was sold. migrated_sold = the quantity of the item sold. So this is nice, I have a table that keeps a record of each sale as it happens.
What I want to achieve though, is a record of the total number of sales of this item type, for each day in a 6 month period spanning back from the current date, and including each day regardless of whether a sale was made or not. So the desired output of my query would be:
SELECT (the query I want goes here) and returns the following rows...;
+------------+------------+
| caldate | sold_total |
+------------+------------+
| 2021-09-23 | 2 |
| 2021-09-24 | 0 |
| 2021-09-25 | 1 |
| 2021-09-26 | 0 |
| 2021-09-27 | 0 |
| 2021-09-28 | 1 |
+------------+------------+
Note that each day is included as a row in the results, even where the sales total for that day is 0. I read that to do this, I would be required to create a calendar table with one column and all the days I want as rows, so I went ahead and did that:
SELECT caldate FROM phpbb_calendar;
+------------+
| caldate |
+------------+
| 2021-09-23 |
| 2021-09-24 |
| 2021-09-25 |
| 2021-09-26 |
| 2021-09-27 |
| 2021-09-28 |
+------------+
Now all that remains is for me to make the query. I need to somehow return all the rows from the phpbb_calendar table, joining the data from sum() (?) of the total migrated_sold for those days where exists, and a 0 where no sales took place.
I anticipated some issues with the UNIX timestamp, but it's okay because I am able to get caldate and locktime fields to be the same format by using from_unixtime(locktime, '%Y-%m-%d'), so both dates will be in the YYYY-MM-DD format for comparison.
Please could someone help me with this. I've gotten so close every time but it seems that everyone else's request is only slightly different from mine, so existing questions and answers have not been able to satisfy my requirements.
End goal is to use a JS chart library (AnyChart) to show a line graph of the number of sales of the item over time. But to get there, I first need to provide it with the query necessary for it to display that data.
Thanks
Update
Using this query:
SELECT c.caldate, u.uitemid, sum(v.migrated_sold) as total_sales
from phpbb_calendar c cross join
(select distinct uitemid from phpbb_sold) u left join
phpbb_sold v
on c.caldate = from_unixtime(v.locktime, '%Y-%m-%d') WHERE u.uitemid = 39 and c.caldate <= curdate() GROUP BY c.caldate ORDER BY c.caldate;
Returns:
But as you can see, it's just tallying up the total number of sales ever made or something - its clearly incrementing in a way I don't understand.
I don't want it to do that - I want it to count the number of total sales on each day individually. The results should look like this:
So that what is returned is basically a 'histogram' of sales, if any occurred, including 'empty' days where there were no sales (so these empty days must still be returned as rows).
SELECT c.caldate, u.uitemid, COALESCE(SUM(v.migrated_sold), 0) AS total_sales
FROM phpbb_calendar c
CROSS JOIN (SELECT DISTINCT uitemid FROM phpbb_sold WHERE uitemid = 37) u
LEFT JOIN phpbb_sold v
ON v.locktime BETWEEN UNIX_TIMESTAMP(TIMESTAMP(c.caldate)) AND UNIX_TIMESTAMP(TIMESTAMP(c.caldate, '23:59:59'))
AND u.uitemid = v.uitemid
WHERE c.caldate BETWEEN CURDATE() - INTERVAL 6 MONTH AND CURDATE()
GROUP BY c.caldate, u.uitemid
ORDER BY c.caldate;
N.B. I have changed your join to use the unix_timestamp as it should be more efficient and it can use any existing index on locktime
check this out:
select id, d, sum(s) from (
select U.id, d, 0 s from (
select adddate(current_date(),-rows.r) d from (
select (#row_number := #row_number + 1) r
from information_schema.columns,
(SELECT #row_number := 0) AS x
limit 200
) rows
) dates,
(SELECT distinct uitemid id FROM `phpbb_sold`) U
where d > adddate(current_date(), interval -6 month)
union
select uitemid, date(from_unixtime(locktime)),sum(migrated_sold)
from `phpbb_sold`
group by uitemid, date(from_unixtime(locktime))
) sales_union
group by id, d
order by id, d;
see dbfiddle
no need for calendar table

Select the rows with lowest value in specific column from those who match the other critarias

I have created an SQL table to save events data into it.
Each event can have multiple occurrences and when I filter them on site - I want to have the first matching occurrence of each event. each occurrence is saved in a different row, which contains a column for the general event_id and specific occ_id to each occurrence.
I need to get from the matching rows - only one row from each event_id, and it needs to be the one with the lowest occ_id value.
i.e.
gen_id | event_id | occ_id | month
------------------------------------
1 | 190 | 1 | 4
2 | 190 | 2 | 4
3 | 190 | 3 | 4
4 | 192 | 1 | 4
5 | 192 | 2 | 4
6 | 192 | 3 | 4
7 | 193 | 1 | 5
8 | 193 | 2 | 5
If I'm looking for events from month = 4, I need to get the events (gen_id): 1,4
and if I'm looking for month = 5 I need to get only event (gen_id): 7
My SQL query right now gets the matching events but with no occ_id filteration:
(it looks something like this right now)
SELECT
event_id,
event_title,
occ_id
FROM
table_name
WHERE month = 4
GROUP BY event_id
ORDER BY
event_id
DESC
I have tried to also use MIN / MAX but I guess it either not the right handler for this case or I'm using it wrong...
You want to filter. One method uses a correlated subquery in the WHERE clause:
select t.*
from table_name t
where t.occ_id = (select min(t2.occ_id)
from table_name t2
where t2.event_id = t.event_id
);
However, the lowest value always seems to be "1", so this might work as well:
select t.*
from table_name t
where t.month = 4 and
t.occ_id = 1;
To add month, you can add it to the outer query:
select t.*
from table_name t
where t.month = 4 and
t.occ_id = (select min(t2.occ_id)
from table_name t2
where t2.event_id = t.event_id and
t2.month = t.month
);
umm,
select t.event_id, t.occ_id, t.month, min(t.gen_id) from (
select event_id,month,min(occ_id) as min_occ_id from t where month=5 group by event_id, month
) t1 join t on t1.min_occ_id = t.occ_id and t1.event_id = t.event_id and t1.month = t.month
group by t.event_id, t.occ_id, t.month;
if columns event_id,occ_id,month make up an UNIQUE KEY, the SQL can be simplified.
select t.event_id, t.occ_id, t.month, t.gen_id from (
select event_id,month,min(occ_id) as min_occ_id from t where month=5 group by event_id, month
) t1 join t on t1.min_occ_id = t.occ_id and t1.event_id = t.event_id and t1.month = t.month

MySql - Get next date which satisfy a condition

I have a mysql table called 'helper_leaves' in which user have entered their leave dates for the next 2 months.
I would like to get 2 upcoming working dates for a particular user_id which are NOT in the 'helper_leaves' table and also should be next to the current date.
**id | user_id | leave_date**
1 | 1 | 2016-07-07
2 | 1 | 2016-07-09
3 | 1 | 2016-07-15
4 | 1 | 2016-08-03
I want write a query to get next 2 working dates of user_id = 1, from now.
Please note, this table is having only the leave dates. But I want to get the next 2 availability dates .
For the sample data given, I expected to get 2016-07-14 and 2016-07-16 as the next 2 working dates because today is 2016-07-13
Note: All the dates which are not in this table is considered as a working date.
Please help!!!!
Can you please try with below query?
In that, i have just generate next 60 dates and exclude leave_date from that with limit 2.
SELECT CURDATE() + INTERVAL a + b DAY dte
FROM
(SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
UNION SELECT 8 UNION SELECT 9 ) d,
(SELECT 0 b UNION SELECT 10 UNION SELECT 20
UNION SELECT 30 UNION SELECT 40) m
WHERE CURDATE() + INTERVAL a + b DAY < DATE_ADD(CURDATE(),INTERVAL 60 DAY) and dte not in ( select leave_date from helper_leaves where user_id = 1)
ORDER BY a + b limit 2
For this you can use interval
For Ex:
SELECT DATE_ADD(leave_date, INTERVAL 1 DAY) AS workingDay;

How to compute statistics with date intervals via MySQL

I want to compute some stats (with a MySQL backend) sorted by date and with dynamic intervals (week, month, year).
Here is a little example :
Mysql table :
tracker_click
| ID | SITE_ID | CREATED_AT |
| ---- |---------| --------------------|
| 153 | 2 | 2013-07-22 15:43:25 |
| 154 | 2 | 2013-07-25 16:45:46 |
| 2501 | 2 | 2013-09-15 17:45:48 |
I want to get the total click number by SITE_ID by week for the last month with one query
And the same thing by month for the last year.
An example of what I want by week for the last month is :
| click number | SITE_ID | BEGIN_DATE | END_DATE |
|----------------|---------|----------------------|---------------------|
| 25 | 2 | 2013-07-01 00:00:00 | 2013-07-08 00:00:00 |
| 19 | 2 | 2013-08-09 00:00:00 | 2013-08-16 00:00:00 |
| 53 | 2 | 2013-0717- 00:00:00 | 2013-08-24 00:00:00 |
I don’t know if there is a solution to get exaclty this array with only one query without any other processes.
Thank you
This should get you the counts for the last month (ie, last 4 weeks), including weeks where the count is 0 for each site id. If you have a table of sites to get the site id from it means the cross join to the sub query can be replaced with a simple cross join to a table.
This generates a range of numbers from 0 to 5 and subtracts that number of weeks from the current date, formats that to give the Sunday and Saturday of the resulting week and checks that the resuling week is a week between the current date and the current date minus 1 month (done this way rather that just subtracting 4 weeks to cope with variable length months).
SELECT Weeks.aWeek_start, Weeks.aWeek_end, all_site_id.site_id, COUNT(tracker_click.id)
FROM
(
SELECT STR_TO_DATE(DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U Sunday 00:00:00'), '%X%V %W %H:%i:%s') AS aWeek_start,
STR_TO_DATE(DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U Saturday 23:59:59'), '%X%V %W %H:%i:%s') AS aWeek_end
FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5)units
WHERE DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U') BETWEEN DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 MONTH), '%Y%U') AND DATE_FORMAT(NOW(), '%Y%U')
) Weeks
CROSS JOIN
(
SELECT DISTINCT site_id
FROM tracker_click
) AS all_site_id
LEFT OUTER JOIN tracker_click
ON tracker_click.CREATED_AT BETWEEN Weeks.aWeek_start AND Weeks.aWeek_end
AND tracker_click.site_id = all_site_id.site_id
GROUP BY Weeks.aWeek_start, Weeks.aWeek_end, all_site_id.site_id
A similar query could be done for months of the year
SELECT Months.aMonth_start, Months.aMonth_end, all_site_id.site_id, COUNT(tracker_click.id)
FROM
(
SELECT DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i MONTH), '%Y/%m/01 00:00:00') AS aMonth_start,
DATE_FORMAT(LAST_DAY(DATE_SUB(NOW(), INTERVAL units.i MONTH)), '%Y/%m/%d 23:59:59') AS aMonth_end
FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11)units
) Months
CROSS JOIN
(
SELECT DISTINCT site_id
FROM tracker_click
) AS all_site_id
LEFT OUTER JOIN tracker_click
ON tracker_click.CREATED_AT BETWEEN Months.aMonth_start AND Months.aMonth_end
AND tracker_click.site_id = all_site_id.site_id
GROUP BY Months.aMonth_start, Months.aMonth_end, all_site_id.site_id
SELECT count(*), site_id, extract(WEEK from created_at) as start_date,
date_add(extract(WEEK from created_at), 1, week) as end_date
FROM click_tracker
GROUP BY site_id, extract(WEEK from created_at)
and you can add where clause to filter results additionally.

How can make sort by votes "by date", "by year"..?

What I am trying to implement is similar to what we have on SO. I want to rank posts by upvotes in last day, last month etc. My schema makes up two tables,
post(id, post, posted_on..)
vote(post_id, vote_value, date)
I hope the schema is pretty self explanatory. The problem being, if I sort "by day" by making a inner join on posts and vote and having a where clause('votes.date >= DATE_SUB(CURDATE(), INTERVAL 1 DAY'), it does work as intended but fails to show the other posts. I mean the posts which haven't had vote in last day are completely ignored. What I want is that those posts be given low priority but do show up in the query.
While, I may think of using union operation but i was looking for another approach.
Update: Lets say, there are two posts, 1,2.
and votes table is like,
post_id vote_value date
1 1 2012-12-19
2 1 2012-12-10
If I query, as per my approach, then only the post - "1" will show up since I have put a date constraint but I want both to show up. Here is my query:
SELECT `id`, SUM(`votes`.`votes`) AS likes_t, `post`.* FROM `posts` JOIN `votes` ON (`id` = `votes`.`post_id`) WHERE `votes`.`date` >= DATE_SUB(CURDATE(), INTERVAL 2 DAY)
If you want to show all posts, but only count the recent votes, this should do it:
SELECT `id`,
SUM(IF(`votes`.`date` >= DATE_SUB(CURDATE(), INTERVAL 2 DAY, `votes`.`votes`, 0)) AS likes_t,
`post`.*
FROM `posts` JOIN `votes` ON (`id` = `votes`.`post_id`)
If I got it right:
SELECT *, IF(vote.date>=DATE_SUB(CURDATE(), INTERVAL 1 DAY), 1, 0) as rate FROM post INNER JOIN vote ON (post.id=vote.post_id) ORDER BY rate DESC;
+------+--------+---------+------+---------------------+------+
| id | post | post_id | vote | date | rate |
+------+--------+---------+------+---------------------+------+
| 1 | first | 1 | 1 | 2012-12-19 00:00:00 | 1 |
| 1 | first | 1 | 1 | 2012-12-13 00:00:00 | 0 |
| 2 | second | 2 | 1 | 2012-12-10 00:00:00 | 0 |
+------+--------+---------+------+---------------------+------+

Categories