Attempting to count total number of sales per month what table shows what day EACH sale happened.
My code is as follows:
WITH sales_months AS (
SELECT
'2018-01-01' AS first_day,
'2018-01-31' AS last_day
UNION
SELECT
'2018-02-01' AS first_day,
'2018-02-28' AS last_day
UNION
SELECT
'2018-03-01' AS first_day,
'2018-03-31' AS last_day
UNION
SELECT
'2018-04-01' AS first_day,
'2018-04-31' AS last_day
UNION
SELECT
'2018-05-01' AS first_day,
'2018-05-31' AS last_day
UNION
SELECT
'2018-06-01' AS first_day,
'2018-06-31' AS last_day
UNION
SELECT
'2018-07-01' AS first_day,
'2018-07-31' AS last_day
UNION
SELECT
'2018-08-01' AS first_day,
'2018-08-31' AS last_day
UNION
SELECT
'2018-09-01' AS first_day,
'2018-09-31' AS last_day
UNION
SELECT
'2018-10-01' AS first_day,
'2018-10-31' AS last_day
UNION
SELECT
'2018-11-01' AS first_day,
'2018-11-31' AS last_day
UNION
SELECT
'2018-12-01' AS first_day,
'2018-12-31' AS last_day
)
cross_join AS (
SELECT *
FROM sales_2018
CROSS JOIN sales_months
),
purchase AS (
SELECT GlobalCustomerID, first_day AS sales_month,
CASE
WHEN (SoldDate BETWEEN first_day AND last_day)
THEN 1
ELSE 0
END AS purchased
FROM cross_join
),
purchase_total AS (
SELECT
month,
sum(purchased)
FROM purchase
GROUP BY month;
It returns an error
Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'cross_join AS ( SELECT * FROM sales_2018 CROSS JOIN sales_months ), purchase AS ' at line 50
I believe it may be a problem with how I'm formatting my dates?
Any help is appreciated.
Could you share more of what your table structure looks like? If you want the sum of sales by month, you really shouldn't need to identify the first/last of each month (especially not manually). Something like this would be more straight forward, but I'm not sure of your schema so it may require tweaking.
SELECT
YEAR(SoldDate)
, MONTH(SoldDate)
, COUNT(GlobalCustomerID)
FROM sales_2018
GROUP BY YEAR(SoldDate), MONTH(SoldDate)
Related
I am trying to find number of users every month.
This is my SQL which I learn from another question.
The part for creating number of month is easy to understand but it is long. I am wondering is there a neater way to write the same SQL. Thanks.
SELECT
meses.MONTH,
COUNT(Users.user_ID) AS num_of_user
FROM
(
SELECT
1 AS MONTH
UNION
SELECT
2 AS MONTH
UNION
SELECT
3 AS MONTH
UNION
SELECT
4 AS MONTH
UNION
SELECT
5 AS MONTH
UNION
SELECT
6 AS MONTH
UNION
SELECT
7 AS MONTH
UNION
SELECT
8 AS MONTH
UNION
SELECT
9 AS MONTH
UNION
SELECT
10 AS MONTH
UNION
SELECT
11 AS MONTH
UNION
SELECT
12 AS MONTH
) AS meses
LEFT JOIN
Users
ON
meses.month = MONTH(Users.joint_date) AND YEAR(Users.joint_date) = '2000'
GROUP BY
meses.MONTN
In MySQL 8.0, you can use a recursive query to generate the series.
I would also recommend filtering against literal dates rather than applying date function on the column being filtered: this is much more efficient, and can take advantage of an index on users(joint_date).
with dates as (
select '2020-01-01' dt
union all select dt + interval 1 month from dates where dt + interval 1 month < '2021-01-01'
)
select d.dt, count(u.user_id) as num_of_users
from dates d
left join users u
on u.joint_date >= d.dt
and u.joint_date < d.dt + interval 1 month
group by d.dt
In earlier versions, you do need to enumerate the dates, using union. However I would still recommend the literal date technique. That would look like:
select '2020-01-01' + interval n.n month as dt, count(u.user_id) as num_of_users
from (select 0 n union all select 2 ... union all select 11) n
left join users u
on u.joint_date >= '2020-01-01' + interval n.n month
and u.joint_date < '2020-01-01' + interval (n.n + 1) month
group by n.n
The following query returns the visitors and pageviews of last 7 days. However, if there are no results (let's say it is a fresh account), nothing is returned.
How to edit this in order to return 0 in days that there are no entries?
SELECT Date(timestamp) AS day,
Count(DISTINCT hash) AS visitors,
Count(*) AS pageviews
FROM behaviour
WHERE company_id = 1
AND timestamp >= Subdate(Curdate(), 7)
GROUP BY day
Assuming that you always have at least one record in the table for each of the last 7 days (regardless of the company_id), then you can use conditional aggregation as follows:
select
date(timestamp) as day,
count(distinct case when company_id = 1 then hash end) as visitors,
sum(company_id = 1) as pageviews
from behaviour
where timestamp >= curdate() - interval 7 day
group by day
Note that I changed you query to use standard date arithmetics, which I find easier to understand that date functions.
Otherwise, you would need to move the condition on the date from the where clause to the aggregate functions:
select
date(timestamp) as day,
count(distinct case when timestamp >= curdate() - interval 7 day and company_id = 1 then hash end) as visitors,
sum(timestamp >= curdate() - interval 7 day and company_id = 1) as pageviews
from behaviour
group by day
If your table is big, this can be expensive so I would not recommend that.
Alternatively, you can generate a derived table of dates and left join it with your original query:
select
curdate - interval x.n day day,
count(distinct b.hash) visitors,
count(b.hash) page_views
from (
select 1 n union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7
) x
left join behavior b
on b.company_id = 1
and b.timestamp >= curdate() - interval x.n day
and b.timestamp < curdate() - interval (x.n - 1) day
group by x.n
Use a query that returns all the dates from today minus 7 days to today and left join the table behaviour:
SELECT t.timestamp AS day,
Count(DISTINCT b.hash) AS visitors,
Count(b.timestamp) AS pageviews
FROM (
SELECT Subdate(Curdate(), 7) timestamp UNION ALL SELECT Subdate(Curdate(), 6) UNION ALL
SELECT Subdate(Curdate(), 5) UNION ALL SELECT Subdate(Curdate(), 4) UNION ALL SELECT Subdate(Curdate(), 3) UNION ALL
SELECT Subdate(Curdate(), 2) UNION ALL SELECT Subdate(Curdate(), 1) UNION ALL SELECT Curdate()
) t LEFT JOIN behaviour b
ON Date(b.timestamp) = t.timestamp AND b.company_id = 1
GROUP BY day
Use IFNULL:
IFNULL(expr1, 0)
From the documentation:
If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns expr2. IFNULL() returns >a numeric or string value, depending on the context in which it is used.
You can use next trick:
First, get query that return 1 dummy row: SELECT 1;
Next use LEFT JOIN to connect summary row(s) without condition. This join will return values in case data exists on NULL values in other case.
Last select from joined queries onle what we need and convert NULL's to ZERO's
using IFNULL dunction.
SELECT
IFNULL(b.day,0) AS DAY,
IFNULL(b.visitors,0) AS visitors,
IFNULL(b.pageviews,0) AS pageviews
FROM (
SELECT 1
) a
LEFT JOIN (
SELECT DATE(TIMESTAMP) AS DAY,
COUNT(DISTINCT HASH) AS visitors,
COUNT(*) AS pageviews
FROM behaviour
WHERE company_id = 1
AND TIMESTAMP >= SUBDATE(CURDATE(), 7)
GROUP BY DAY
) b ON 1 = 1;
I'm doing this select statement:
SELECT * FROM (
SELECT COUNT(t.text) as count, COUNT(DISTINCT(t.from_user_id)) as usercount, DATE_FORMAT(t.created_at,'%Y-%m-%d %H:00') datepart
FROM TABLE1 t WHERE t.created_at >= '2015-08-12 00:00:00' AND t.created_at <= '2015-08-13 18:30:00' AND t.eliminar IS NULL
GROUP BY datepart) as t
UNION ALL
SELECT * FROM (
SELECT COUNT(b.id) as count, COUNT(DISTINCT(b.from_user_id)) as usercount, DATE_FORMAT(b.created_at,'%Y-%m-%d %H:00') datepart
FROM TABLE2 b WHERE b.created_at >= '2015-08-12 00:00:00' AND b.created_at <= '2015-08-13 18:30:00' AND b.eliminar IS NULL
GROUP BY datepart) as x GROUP BY datepart
this select gets this:
I'm trying to view with datepart grouped but I can't, any idea what I'm doing wrong?
TABLE2 only have (id,from_user_id,eliminar) and all are NULL except created_at, in this row I have entire 2015 year by day and hour, same format as TABLE1
SOLVED:
SELECT DISTINCT * FROM (
SELECT COUNT(t.text) as count, COUNT(DISTINCT(t.from_user_id)) as usercount, DATE_FORMAT(t.created_at,'%Y-%m-%d %H:00') datepart
FROM TABLE1 t WHERE t.created_at >= '2015-08-12 00:00:00' AND t.created_at <= '2015-08-13 18:30:00' AND t.eliminar IS NULL
GROUP BY datepart
UNION ALL
SELECT COUNT(t.id) as count, COUNT(DISTINCT(t.from_user_id)) as usercount, DATE_FORMAT(t.created_at,'%Y-%m-%d %H:00') datepart
FROM TABLE2 t WHERE t.created_at >= '2015-08-12 00:00:00' AND t.created_at <= '2015-08-13 18:30:00' AND t.eliminar IS NULL
GROUP BY datepart) as x GROUP BY datepart ORDER BY datepart
I have the table with following fields
Createdon(datetime)
Amount(double)
I need to find the sum of amounts for next 24 hours of the given date. If there are no results then the sum should be zero.
e.g
duration sum
0000-0001 25.43
0001-0002 36.85
0002-0003 0
.
.
.
.
0022-0023 38.56
Can you please help me creating a query to find the required solution
The key to your query is the ability to take any datetime value and truncate it to the nearest preceding hour. You can do that with this expression:
DATE_FORMAT(Createdon, '%Y-%m-%d %H:00')
Given, for example, 2015-04-21 14:22:05, this gives back 2015-04-21 14:00:00.
Then you use that in GROUP BY
SELECT DATE_FORMAT(Createdon, '%Y-%m-%d %H:00') Createdhour,
SUM(Amount) sum
FROM theTable
GROUP BY DATE_FORMAT(Createdon, '%Y-%m-%d %H:00')
ORDER BY DATE_FORMAT(Createdon, '%Y-%m-%d %H:00')
Finally, I think you wanted one day's worth of results. You need to add a WHERE clause to get that. The one shown here will take yesterday's results -- that is, all results from [midnight yesterday -- midnight today).
SELECT DATE_FORMAT(Createdon, '%Y-%m-%d %H:00') Createdhour,
SUM(Amount) sum
FROM theTable
WHERE CreatedOn >= DATE(NOW()) - INTERVAL 1 DAY
AND CreatedOn < DATE(NOW())
GROUP BY DATE_FORMAT(Createdon, '%Y-%m-%d %H:00')
ORDER BY DATE_FORMAT(Createdon, '%Y-%m-%d %H:00')
This is explained in greater detail at http://www.plumislandmedia.net/mysql/sql-reporting-time-intervals/
To include all hours of the day, you will need an independent source of distinct DATETIME items.
Here's a query that will do such a thing.
SELECT mintime + INTERVAL seq.seq HOUR AS CreatedHour
FROM (
SELECT DATE(NOW()) - INTERVAL 1 DAY AS mintime,
DATE(NOW()) AS maxtime
) AS minmax
JOIN seq_0_to_23 AS seq
ON seq.seq < TIMESTAMPDIFF(HOUR,mintime,maxtime)
You then need to use LEFT JOIN to pick up your data.
SELECT a.Createdhour,
SUM(Amount) sum
FROM (
SELECT mintime + INTERVAL seq.seq HOUR AS CreatedHour
FROM (
SELECT DATE(NOW()) - INTERVAL 1 DAY AS mintime,
DATE(NOW()) AS maxtime
) AS minmax
JOIN seq_0_to_23 AS seq
ON seq.seq < TIMESTAMPDIFF(HOUR,mintime,maxtime)
) a
LEFT JOIN theTable t
ON a.CreatedHour = DATE_FORMAT(t.Createdon, '%Y-%m-%d %H:00')
GROUP BY DATE_FORMAT(t.Createdon, '%Y-%m-%d %H:00')
ORDER BY DATE_FORMAT(t.Createdon, '%Y-%m-%d %H:00')
Finally, you need to somehow get that table seq_0_to_23. If you're running MariaDB, it's built in. If not...
CREATE TABLE seq_0_to_23 AS
SELECT 0 AS seq
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
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18
UNION ALL SELECT 19 UNION ALL SELECT 20 UNION ALL SELECT 21
UNION ALL SELECT 22 UNION ALL SELECT 23
This is written up in more general form at http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/
I wrote a query to get month-wise record in user table as follows
SELECT COUNT( `userID` ) AS total, DATE_FORMAT( `userRegistredDate` , '%b' ) AS
MONTH , YEAR( `userRegistredDate` ) AS year
FROM `users`
GROUP BY DATE_FORMAT( FROM_UNIXTIME( `userRegistredDate` , '%b' ) )
Output:
total MONTH year
---------------------------
3 May 2013
2 Jul 2013
--------------------------
Expected Output:
total MONTH year
---------------------------
0 Jan 2013
0 Feb 2013
0 Mar 2013
0 Apr 2013
3 May 2013
0 Jun 2013
2 Jul 2013
--------------------------
I need to show the record even if data not exist. How to do this?
I won't say much about efficiency as I have not tested it against other methods but without having a temp table this seem a fair way to go.
SELECT COUNT(u.userID) AS total, m.month
FROM (
SELECT 'Jan' AS MONTH
UNION SELECT 'Feb' AS MONTH
UNION SELECT 'Mar' AS MONTH
UNION SELECT 'Apr' AS MONTH
UNION SELECT 'May' AS MONTH
UNION SELECT 'Jun' AS MONTH
UNION SELECT 'Jul' AS MONTH
UNION SELECT 'Aug' AS MONTH
UNION SELECT 'Sep' AS MONTH
UNION SELECT 'Oct' AS MONTH
UNION SELECT 'Nov' AS MONTH
UNION SELECT 'Dec' AS MONTH
) AS m
LEFT JOIN users u
ON MONTH(STR_TO_DATE(CONCAT(m.month, ' 2013'),'%M %Y')) = MONTH(u.userRegistredDate)
AND YEAR(u.userRegistredDate) = '2013'
GROUP BY m.month
ORDER BY 1+1;
If you make the union based on a date format you can even reduce the work and load on the query.
SELECT COUNT(u.userID) AS total, DATE_FORMAT(merge_date,'%b') AS month, YEAR(m.merge_date) AS year
FROM (
SELECT '2013-01-01' AS merge_date
UNION SELECT '2013-02-01' AS merge_date
UNION SELECT '2013-03-01' AS merge_date
UNION SELECT '2013-04-01' AS merge_date
UNION SELECT '2013-05-01' AS merge_date
UNION SELECT '2013-06-01' AS merge_date
UNION SELECT '2013-07-01' AS merge_date
UNION SELECT '2013-08-01' AS merge_date
UNION SELECT '2013-09-01' AS merge_date
UNION SELECT '2013-10-01' AS merge_date
UNION SELECT '2013-11-01' AS merge_date
UNION SELECT '2013-12-01' AS merge_date
) AS m
LEFT JOIN users u
ON MONTH(m.merge_date) = MONTH(u.userRegistredDate)
AND YEAR(m.merge_date) = YEAR(u.userRegistredDate)
GROUP BY m.merge_date
ORDER BY 1+1;
Live DEMO of both queries.
You may need a table to hold every "month" record. A temp table can do the trick:
drop table if extists temp_months;
create temporary table temp_months
month date,
index idx_date(month);
insert into temp_months
values ('2013-01-31'), ('2013-02-28'), ...
And now, you can left join your data with this newly created temp table:
SELECT
COUNT( `userID` ) AS total,
DATE_FORMAT( m.month , '%b' ) AS
MONTH ,
YEAR( m.month ) AS year
FROM
months as m
left join `users` as u on m.month = last_day(FROM_UNIXTIME(`userRegistredDate`, '%b' )
GROUP BY
last_day(m.month);
Notice that you can put the temp table creation (and fill) in a stored procedure.
I use last_day for simplicity, but you are free to use any date in the month that you like, if you join it correctly.
Hope this helps