I have a query like this
SELECT count(Distinct name) as total, DATE_FORMAT(date_added, '%M %d, %Y') AS date_added
FROM `submitted_changes`
WHERE date_added >= NOW() - INTERVAL 1 WEEK
GROUP BY DATE(date_added)
It works great and returns rows that have a nicely formatted date and a total. Basically, this represents the number of submissions per day.
The problem I have is dealing with days with 0 submissions. I don't want to skip these days, but rather have the date shown and 0 for the total. Is there a way to ensure that when I do the query above (which only includes dates from the past week [7 days]) that I always get 7 rows back?
To do this, you need an existence of a record per each date. Since this is not the case in your submitted_changes table - I'll suggest to create a date table (if you don't have it already).
Note - for the shortest version, check the last edit at the bottom:
Here is an example with a temporary table. First run:
CREATE TEMPORARY TABLE IF NOT EXISTS dates AS
SELECT DATE(curdate()-num) as date_col
FROM
(
SELECT 0 as num
UNION
SELECT 1
UNION
SELECT 2
UNION
SELECT 3
UNION
SELECT 4
UNION
SELECT 5
UNION
SELECT 6) sub
This will create a table with 7 relevant dates.
Now left join it with your data:
SELECT
count(Distinct name) as total,
DATE_FORMAT(date_col, '%M %d, %Y') AS date_added
FROM dates LEFT JOIN submitted_changes
on (dates.date_col = DATE(submitted_changes.date_added))
GROUP BY date_col
You can also run it as a one-shot query (with no create statement):
SELECT
count(Distinct name) as total,
DATE_FORMAT(date_col, '%M %d, %Y') AS date_added
FROM
(SELECT DATE(curdate()-num) as date_col
FROM
(
SELECT 0 as num
UNION
SELECT 1
UNION
SELECT 2
UNION
SELECT 3
UNION
SELECT 4
UNION
SELECT 5
UNION
SELECT 6) sub) dates
LEFT JOIN submitted_changes
on (dates.date_col = DATE(submitted_changes.date_added))
GROUP BY date_col
Another approach is a permanent dim_date. Here is a sample code for static table (with more extra fields):
CREATE TABLE dim_date (
id int(11) NOT NULL AUTO_INCREMENT,
date date,
day int(11),
month int(11),
year int(11),
day_name varchar(45),
PRIMARY KEY (id),
INDEX date_index (date)
)
and then populate it:
SET #currdate := "2015-01-01";
SET #enddate := "2025-01-01";
delimiter $$
DROP PROCEDURE IF EXISTS BuildDate$$
CREATE PROCEDURE BuildDate()
BEGIN
WHILE #currdate < #enddate DO
INSERT INTO dim_date (date, day, month, year, day_name)
VALUES (
#currdate, DAY(#currdate), MONTH(#currdate),
YEAR(#currdate), DAYNAME(#currdate)
);
SET #currdate := DATE_ADD(#currdate, INTERVAL 1 DAY);
END WHILE;
END$$
CALL BuildDate();
Then you can finally run your query with a left join:
SELECT
count(Distinct name) as total,
DATE_FORMAT(date, '%M %d, %Y') AS date_added
FROM dim_date LEFT JOIN submitted_changes
on (dim_date.date = DATE(submitted_changes.date_added))
WHERE date >= NOW() - INTERVAL 1 WEEK
GROUP BY date
This would return a line per each date, even if there are no records in submitted_changes for them.
Edit: another one-shot super short version inspired by this post:
SELECT
count(Distinct name) as total,
DATE_FORMAT(date, '%M %d, %Y') AS date_added
(SELECT date(curdate()-id%7) as date
FROM submitted_changes
GROUP BY num) dates LEFT JOIN submitted_changes
on (date.dates = DATE(submitted_changes.date_added))
GROUP BY date
Related
I have a database with colums I am working on. What I am looking for is the date associated with the row where the SUM(#) reaches 6 in a query. The query I have now will give the date when the number in the colum is six but not the sum of the previous rows. example below
Date number
---- ------
6mar16 1
8mar16 4
10mar16 6
12mar16 2
I would like to get a query to get the 10mar16 date because on that date the number is now greater than 6. Earlier dates wont total up to six.
Here is an example of a query i have been working on:
SELECT max(date) FROM `numbers` WHERE `number` > 60
You could use this query, which tracks the accumulated sum and then returns the first one that meets the condition:
select date
from (select * from mytable order by date) as base,
(select #sum := 0) init
where (#sum := #sum + number) >= 6
limit 1
SQL Fiddle
Most databases support ANSI standard window functions. In this case, cumulative sum is your friend:
select t.*
from (select t.*, sum(number) over (order by date) as sumnumber
from t
) t
where sumnumber >= 10
order by sumnumber
fetch first 1 row only;
In MySQL, you need variables:
select t.*
from (select t.*, (#sumn := #sumn + number) as sumnumber
from t cross join (select #sumn) params
order by date
) t
where sumnumber >= 10
order by sumnumber
fetch first 1 row only;
Awesome!!!! It seems to be working great. Here is the code that I used.
SELECT date, id, crewname
FROM (select * FROM flightrecord WHERE `crewname` = 'brayn'
ORDER BY dutyTimeArrive DESC) as base,
(select #sum := 0) init
WHERE (#sum := #sum + tankDropCount) >= 6
limit 1
I ran into one problem that I can not solve for 3 days already.
The problem is current, there is a database of two columns of date ("yyyy-mm") and the phone number is unique non-repeating. I want to find out how many new customers each month. Well, the formula is roughly current (the new client is the first month + the new client is the next month ....). If the customer meets the first month he is considered a new customer, then it is on the other month, he is missing a new customer.
try Something like this:
select year(f2.DateColumn) YearCol, month(f2.DateColumn) MonthCol, count(f2.PhoneNumber) NbNewPhone
from (
Select f1.*, row_number() over(partition by f1.PhoneNumber order by f1.DateColumn) rang
from yourTable f1
) f2
where f2.rang=1
group by year(f2.DateColumn), month(f2.DateColumn)
order by 1, 2
or if you column is not a date :
select f2.DateColumn, count(f2.PhoneNumber) NbNewPhone
from (
Select f1.*, row_number() over(partition by f1.PhoneNumber order by f1.DateColumn) rang
from yourTable f1
) f2
where f2.rang=1
group by f2.DateColumn
order by 1, 2
As per your example just below count is sufficient:
Select DateColumn, Count(PhoneNumber) from yourTable
Group by DateColumn
If Datecolumn is real date as yyyy-mm-dd with date datatype you can query as below:
Select year(datecolumn), month(datecolumn), count(PhoneNumber) from yourTable
group by year(datecolumn), month(datecolumn)
SELECT `Date`, SUM(Clicks) AS Clicks, DAY(LAST_DAY(NOW())) AS Monthdays
FROM myTbl
WHERE ( DATE BETWEEN DATE_FORMAT(NOW(), '%Y-%m-01') AND NOW() )
AND MoverID = 123 GROUP BY `Date` ASC
I don't want use PROCEDURE like this: http://stackoverflow.com/questions/7252460/mysql-group-by-and-fill-empty-rows
I don't want to create a whole day containing table like this: http://stackoverflow.com/questions/5988179/mysql-group-by-date-how-to-return-results-when-no-rows
I can use SELECT ##global.time_zone, ##session.time_zone; in PHP and after the MySQL query result is out, I make PHP to the same timezone as MySQL, and fill the date by PHP. But is it a way I can just do it in MySQL way?
You can use a trick to generate virtual table having all the dates you need with another table (replace aux with any table in your DB with 31 recored at least):
SELECT CONVERT(#d := DATE_ADD(#d, INTERVAL 1 DAY), DATE) AS `d`
FROM
`aux`,
(SELECT #d := DATE_SUB(CONVERT(DATE_FORMAT(NOW(), '%Y-%m-01'), DATETIME), INTERVAL 1 DAY)) `x`
WHERE
#d < DATE_SUB(NOW(), INTERVAL 1 DAY)
LIMIT
31
And then join you table on it:
SELECT
`aux`.`d` as `Date`,
SUM(IFNULL(`Clicks`, 0))AS `Clicks`,
DAY(LAST_DAY(NOW())) AS `Monthdays`
FROM (
SELECT CONVERT(#d := DATE_ADD(#d, INTERVAL 1 DAY), DATE) AS `d`
FROM
`aux`,
(SELECT #d := DATE_SUB(CONVERT(DATE_FORMAT(NOW(), '%Y-%m-01'), DATETIME), INTERVAL 1 DAY)) `x`
WHERE
#d < DATE_SUB(NOW(), INTERVAL 1 DAY)
LIMIT
31
) aux
LEFT JOIN
myTbl
ON `Date` = `aux`.`d`
GROUP BY `aux`.`d`
I have the below table:
studentid VARCHAR(12)
latetime DATETIME
attendance CHAR(1)
latetime only have weekdays.
Some of the days the students will have "Parents letter" indicated by V for attendance column.
I need to group these attendance column V by consecutive week days.
Then count these occurrences.
Each group of consecutive days are counted as 1 letter.
My SQLFIDDLE: http://sqlfiddle.com/#!2/55d5b/1
This SQLFIDDLE sample data should return
STUDENTID LETTERCOUNT
a1111 3
b2222 2
a1111 - 3 counts
-----
1. 2014-01-02
2. 2014-01-27
2. 2014-01-29 and 2014-01-30
b2222 - 2 counts
-----
1. 2014-01-02 and 2014-01-03
2. 2014-01-24, 2014-01-27 and 2014-01-28
I tried various methods from the below SO without any proper result yet:
How to GROUP BY consecutive data (date in this case)
MySQL: group by consecutive days and count groups
I can do this programatically in PHP by looping through the results and manually checking for each record + its next date. But i was trying to acheive the same with SQL.
Any help / direction towards finding a solution will be much appreciated.
This is derived from one of the answers in MySQL: group by consecutive days and count groups. I added the WITH ROLLUP option to get the letter count into the same query, and used GROUP_CONCAT to show all the dates. I made the INTERVAL conditional on the weekday, to skip over weekends; holidays aren't taken into account, though.
In my version of the fiddle I changed the latetime column to date, so I could remove all the DATE() functions from the SQL.
SELECT studentid, IFNULL(dates, '') dates, IF(dates IS NULL, lettercount, '') lettercount
FROM (
SELECT studentid, dates, COUNT(*) lettercount
FROM (
SELECT v.studentid,
GROUP_CONCAT(latetime ORDER BY latetime SEPARATOR ', ') dates
FROM
(SELECT studentid, latetime,
#start_date := IF(#last_student IS NULL OR #last_student <> studentid,
1,
IF(#last_latetime IS NULL
OR (latetime - INTERVAL IF(WEEKDAY(latetime) = 0, 3, 1) DAY) > #last_latetime, latetime, #start_date)) AS start_date,
#last_latetime := latetime,
#last_student := studentid
FROM
studentattendance, (SELECT #start_date := NULL, #last_latetime := NULL, #last_student := NULL) vars
WHERE attendance = 'V'
ORDER BY
studentid, latetime
) v
GROUP BY
v.studentid, start_date) x
GROUP BY studentid, dates WITH ROLLUP) y
WHERE studentid IS NOT NULL
ORDER BY studentid, dates
http://sqlfiddle.com/#!2/6c944/12
i have the following statement:
SELECT
count(rs.rsc_id) as counter
FROM shots as rs
where rsc_rs_id = 345354
AND YEAR(rs.timestamp) = YEAR(DATE_SUB(CURDATE(), INTERVAL 6 MONTH))
GROUP BY DATE_FORMAT(rs.timestamp,'%Y%m')
rs.timestamp is a unix timestamp
Output would be like for each row / month a numeric like '28'
It Works fine, but if i have inconsistent data, like only for the past three month (not for all six month), i get no return from my Database. I would like to have every time there is not data for this month, 0 returned...
any suggestion?
i thought about some case statements, but this seems not so good...
thanks!!
For only 6 months, a date table seems unnecessary, although this looks complicated (it really isn't!)
SELECT DATE_FORMAT(N.PivotDate,'%Y%m'), count(rs.rsc_id) as counter
FROM (
select ADDDATE(CURDATE(), INTERVAL N MONTH) PivotDate
FROM (
select 0 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) N) N
LEFT JOIN shots as rs
ON rsc_rs_id = 345354
AND DATE_FORMAT(N.PivotDate,'%Y%m')=DATE_FORMAT(FROM_UNIXTIME(rs.timestamp),'%Y%m')
GROUP BY DATE_FORMAT(N.PivotDate,'%Y%m')
In such cases it's common to use a table of dates with all dates (e.g. from 1/1/1970 to 31/12/2999) and LEFT JOIN your data to that table.
See an example in the answer here: mysql joins tables creating missing dates
If you create a dates table you can use:
SELECT
DATE_FORMAT(d.date,'%Y%m') AS `month`, count(rs.rsc_id) AS `counter`
FROM dates d
LEFT JOIN shots as rs
ON d.date = FROM_UNIXTIME(rs.timestamp)
AND rs.rsc_rs_id = 345354
WHERE d.date > DATE_SUB(CURDATE(), INTERVAL 5 MONTH)
AND d.date < CURDATE()
GROUP BY DATE_FORMAT(d.date,'%Y%m');