I'm currently trying to get the customers' retention rates using the following query:
set #DAY1 = 0;
set #DAY30 = 0;
set #DAY90 = 0;
SET #order_month = 1;
SET #order_year = 2020;
SELECT
#DAY1/COUNT(uid) AS D1,
#DAY30/COUNT(uid) AS D30,
#DAY90/COUNT(uid) AS D90
FROM
(
SELECT
uid,
case when DATEDIFF(date(lastConnection), date(accountCreation)) >= 1 then #DAY1:=#DAY1+1 END AS DAY1,
case when DATEDIFF(date(lastConnection), date(accountCreation)) >= 30 then #DAY30:=#DAY30+1 END AS DAY30,
case when DATEDIFF(date(lastConnection), date(accountCreation)) >= 90 then #DAY90:=#DAY90+1 END AS DAY90
from user
where month(accountCreation) = #order_month
and year(accountCreation) = #order_year
)
AS T1
This works as expected but what I would like to add is some sort of loop that would increment the #order_month variable by one (as well as the #order_year variable when we reach a new year). The increment would go as far as August 2022. I would like the result of each 'Select' to be displayed at once. Is that possible and if yes, how could I do that?
If I get your intention right, you just want to calculate the percentage of users whose account was created at least 1 day (30 days, 90 days) before their last login. And you want to calculate this for each year and month based on accountCreation, that is something like
SELECT year(accountCreation) year,
month(accountCreation) month,
sum(CASE WHEN DATEDIFF(date(lastConnection), date(accountCreation)) >= 1 THEN 1 END) / count(*) AS D1,
sum(CASE WHEN DATEDIFF(date(lastConnection), date(accountCreation)) >= 30 THEN 1 END) / count(*) AS D30,
sum(CASE WHEN DATEDIFF(date(lastConnection), date(accountCreation)) >= 90 THEN 1 END) / count(*) AS D90
FROM user
GROUP BY year, month;
In other words: there's no need for a loop.
I want to count the distinct number of fd_id over the time between today and yesterday, between today and 3 days ago, between today and 5 days ago, between today and 7 days ago, between today and 15 days ago, between today and 30 days ago.
My data table looks like the following:
user_id. fd_id. date
1. 123a. 20201010
1. 123a. 20201011
1. 124a. 20201011
...
and the desired result is of the following format:
user_id count_fd_id_1d count_fd_id_3d ... count_fd_id_30d
Specifically, I know I can do the following 6 times and join them together (some column bind method):
select user_id, count(distinct fd_id) as count_fd_id_1d
from table
where date <= today and date >= today-1 (#change this part for different dates)
select user_id, count(distinct fd_id) as count_fd_id_3d
from table
where date <= today and date >= today-3 (#change this part for different dates)
...
I am wondering how I may do this in one shot without running almost identical code for 6 times.
You can use conditional aggregation:
select user_id,
count(distinct case when date >= current_date - 1 day and date < current_date then fd_id end) as cnt_1d,
count(distinct case when date >= current_date - 3 day and date < current_date then fd_id end) as cnt_3d,
...
from mytable
goup by user_id
You can play around with the date expressions to set the ranges you want. The above works on entire days, and does not include the current day.
If the date column in the the table really does look like that (not in date/datetime format), I think you need to use STR_TO_DATE() to convert it to date format then uses DATEDIFF to check the date differences. Consider this example query:
SELECT user_id,
MAX(CASE WHEN ddiff=1 THEN cn END) AS count_fd_id_1d,
MAX(CASE WHEN ddiff=2 THEN cn END) AS count_fd_id_2d,
MAX(CASE WHEN ddiff=3 THEN cn END) AS count_fd_id_3d,
MAX(CASE WHEN ddiff=4 THEN cn END) AS count_fd_id_4d,
MAX(CASE WHEN ddiff=5 THEN cn END) AS count_fd_id_5d
FROM (SELECT user_id,
DATEDIFF(CURDATE(), STR_TO_DATE(DATE,'%Y%m%d')) ddiff,
COUNT(DISTINCT fd_id) cn
FROM mytable
GROUP BY user_id, ddiff) A
GROUP BY user_id;
At the moment, if you check date value simply by using direct subtraction, you'll get incorrect result. For example:
*your current date value - how many days:
'20201220' - 30 = '20201190' <-- this is not correct.
*if you convert the date value and using the same subtraction:
STR_TO_DATE('20201220','%Y%m%d') - 30 = '20201190' <-- still get incorrect.
*convert date value then uses INTERVAL for the date subtraction:
STR_TO_DATE('20201220','%Y%m%d') - INTERVAL 30 DAY = '2020-11-20'
OR
DATE_SUB(STR_TO_DATE('20201220','%Y%m%d'),INTERVAL 30 DAY) = '2020-11-20'
*IF your date column is storing standard date format value, then omit STR_TO_DATE
'2020-12-20' - INTERVAL 30 DAY = '2020-11-20'
OR
DATE_SUB('2020-12-20',INTERVAL 30 DAY) = '2020-11-20'
Check out more date manipulation in MySQL.
For the question, I made a fiddle with a bunch of testing.
SELECT det.partNum,
SUM(det.quantity) AS Demand1,
COUNT(det.partNum) AS Call1
FROM details det
JOIN invoice inv ON det.invoice_id = inv.id
WHERE inv.invoice_date
BETWEEN '2015-11-01 00:00:00'
AND '2015-11-31 23:59:59'
GROUP BY partNum
The above sql returns all part numbers, the total number sold (Demand), and the total number of transactions the parts were involved in (Call) for the current month.
What our vendor wants is this information for every part, but also grouped for each of the past 24 months. The csv they are requesting would look like the following (if only viewing the last 3 months):
Part# | Demand1 | Demand2 | Demand3 | Call1 | Call2 | Call3
123 | 0 | 2 | 0 | 0 | 1 | 0
345 | 6 | 3 | 4 | 1 | 2 | 3
Part# 123: 0 transactions this month (Call1) 0 quantity sold (Demand1)
1 transaction last month (Call2) 2 quantity sold (Demand2).
0 transactions two months ago (Call3) 0 quantity sold (Demand3).
Part# 345: 1 transaction this month (Call1) for qty sold of 6 (Demand1)
2 transactions last month (Call2) for qty sold of 3 (Demand2)
3 transactions two months ago (Call3) for qty sold of 4 (Demand3)
Realize that they want this extended out for the past 24 months. Demand1/Call1 are always the current month.
I used the WHERE/BETWEEN statement to show where the date is coming from and to demonstrate how I can get an accurate report of the parts for the current month.
What I can't figure out how to do is to fill Demand and Call for 24 months. And this is the format that the vendor expects the data to be in. This wasn't my choice. Any help in getting this working as expected would be greatly appreciated.
Thanks
EDIT
I removed the sql-server tag. Sorry about that. This is only MySQL.
Also, I'm adding my reply from below...
Looking into DATEDIFF, TIMESTAMPDIFF, and even PERIOD_DIFF. But none actually seem to return what I need. What needs to happen is the first demand column should search for the current month, day 1 (inclusive) through the next month, day 1 (exclusive). The next demand column should search the current month - one month, day 1 (inclusive) through next month - one month, day 1 (exclusive). And each subsequent column should search the same parameters, subtracting an additional month each column. I don't think that can be accomplished with precision simply using DATEDIFF.
I hope that makes sense.
And again, thanks for any help.
If I understood your problem correctly, you can do it like this:
SELECT
det.partNum,
SUM(case when inv.invoice_date >= dateadd(month, -3, #currMonth) and inv.invoice_date < dateadd(month, -2, #currMonth) then det.quantity else 0) AS Demand1,
SUM(case when inv.invoice_date >= dateadd(month, -2, #currMonth) and inv.invoice_date < dateadd(month, -1, #currMonth) then det.quantity else 0) AS Demand2,
...
FROM details det
JOIN invoice inv ON det.invoice_id = inv.id
WHERE
inv.invoice_date >= '2015-11-01 00:00:00' AND inv.invoice_date < '2015-12-01'
GROUP BY partNum
This uses a variable that has the start date of current month to make the SQL more simple. I also changed the where clause, you should really use >= + < with dates instead of between.
This might get you started with Pivot query.
;WITH cte AS
(
SELECT det.partNum,
SUM(det.quantity) AS DemandSum,
COUNT(det.partNum) AS CallCount,
DATEDIFF(MONTH,inv.invoice_date, GETDATE()) + 1 MonthDiff
FROM details det
JOIN invoice inv ON det.invoice_id = inv.id
GROUP BY det.partNum, DATEDIFF(MONTH,inv.invoice_date, GETDATE()) + 1
)
SELECT t.partNum,
[Demand1],[Demand2],[Demand3],[Demand4],[Demand5],[Demand6],[Demand7],[Demand8],[Demand9],[Demand10],[Demand11],[Demand12],
[Demand13],[Demand14],[Demand15],[Demand16],[Demand17],[Demand18],[Demand19],[Demand20],[Demand21],[Demand22],[Demand23],[Demand24],
[Call1],[Call2],[Call3],[Call4],[Call5],[Call6],[Call7],[Call8],[Call9],[Call10],[Call11],[Call12],
[Call13],[Call14],[Call15],[Call16],[Call17],[Call18],[Call19],[Call20],[Call21],[Call22],[Call23],[Call24]
FROM (SELECT DISTINCT partNum FROM cte) t
LEFT JOIN (
SELECT * FROM (
SELECT partNum, DemandSum, CONCAT('Demand',MonthDiff) ColName FROM cte
) c PIVOT (SUM(DemandSum) FOR ColName IN ([Demand1],[Demand2],[Demand3],[Demand4],[Demand5],[Demand6],[Demand7],[Demand8],[Demand9],[Demand10],[Demand11],[Demand12],
[Demand13],[Demand14],[Demand15],[Demand16],[Demand17],[Demand18],[Demand19],[Demand20],[Demand21],[Demand22],[Demand23],[Demand24])
) p
) ds ON ds.partNum = t.partNum
LEFT JOIN (
SELECT * FROM (
SELECT partNum, CallCount, CONCAT('Call',MonthDiff) ColName FROM cte
) c PIVOT (COUNT(CallCount) FOR ColName IN ([Call1],[Call2],[Call3],[Call4],[Call5],[Call6],[Call7],[Call8],[Call9],[Call10],[Call11],[Call12],
[Call13],[Call14],[Call15],[Call16],[Call17],[Call18],[Call19],[Call20],[Call21],[Call22],[Call23],[Call24])
) p
) cc ON cc.partNum = t.partNum
if that's too confusing, you can use the CASE method. I'd do it a little different than the other answer though..
SELECT
det.partNum,
SUM(case WHEN DATEDIFF(MONTH, inv.invoice_date, GETDATE()) = 0 then det.quantity else 0 end) AS Demand1,
SUM(case WHEN DATEDIFF(MONTH, inv.invoice_date, GETDATE()) = 1 then det.quantity else 0 end) AS Demand2,
SUM(case WHEN DATEDIFF(MONTH, inv.invoice_date, GETDATE()) = 2 then det.quantity else 0 end) AS Demand3,
COUNT(case WHEN DATEDIFF(MONTH, inv.invoice_date, GETDATE()) = 0 then det.partNum end) AS Call1,
COUNT(case WHEN DATEDIFF(MONTH, inv.invoice_date, GETDATE()) = 1 then det.partNum end) AS Call2,
COUNT(case WHEN DATEDIFF(MONTH, inv.invoice_date, GETDATE()) = 2 then det.partNum end) AS Call3
FROM
details det
JOIN invoice inv ON det.invoice_id = inv.id
GROUP BY
det.partNum
you can get the full script for all 24 months here.. SQL Fiddle
Maybe there is a simple fix but I can't seam to figure it out. I'll try my best to explain my situation.
I'm working on a MySQL query that will return results within date range (in column A), and for date range - 1 year (in column B). I need to group results by month day and not by year. So I would like to have something like this:
2014 2013
----------------
01-01 6 8
01-03 7 0
01-04 4 1
01-08 0 13
01-21 11 7
In my current query (below) I get results like this (because of ELSE in CASE):
2014 2013
----------------
01-01 0 8
01-03 7 0
01-04 0 1
01-08 0 13
01-21 0 7
QUERY:
SELECT
DATE_FORMAT(table.date, '%e.%c.') AS date,
(CASE WHEN DATE(table.date) BETWEEN '2014-01-01' AND '2014-02-01' THEN ROUND(SUM(table.field), 2) ELSE 0 END) AS field_2014,
(CASE WHEN DATE(table.date) BETWEEN '2013-01-01' AND '2013-02-01' THEN ROUND(SUM(table.field), 2) ELSE 0 END) AS field_2013
FROM table
WHERE
(DATE(table.date) BETWEEN '2014-05-01' AND '2014-06-01' OR DATE(table.date) BETWEEN '2013-05-01' AND '2013-06-01')
GROUP BY
DATE_FORMAT(table.date, '%c.%e.')
What should I put in ELSE and how can I achieve this functionality?
Thank you for your time
You need aggregation functions. I would recommend:
SELECT DATE_FORMAT(t.date, '%e.%c.') AS date,
SUM(CASE WHEN year(t.date) = 2014 THEN ROUND(SUM(t.field), 2) ELSE 0 END) AS field_2014,
SUM(CASE WHEN year(t.date) = 2013 THEN ROUND(SUM(t.field), 2) ELSE 0 END) AS field_2013
FROM table t
WHERE year(t.date) in (2013, 2014) and month(t.date) = 5
GROUP BY DATE_FORMAT(t.date, '%c.%e.');
I would also recommend using the format '%m-%d'. Having the month then the year means that order by will work on the column. Having all the dates be the same width ("05/01" rather than "5/1") better corresponds to your desired output.
I have a table of transactions for purchases. Each transaction has a timestamp and purchase amount (in USD).
I'm trying to create some stats from this. I'd like to extract a row for each year that contains the sum for each month in the year. (I'd like months with no transaction to sum to 0 - not omitted.)
I know I could just do a plain SELECT of everything and process it in PHP, but I was wondering if it was at all possible to make MySQL do the work and extract the data like I want it?
What I'd like to see is rows like:
Year, Total_Jan, Total_Feb, ... Total_Dec, Total_Year
I am able to get the total per year, but I can't work out how to get the total per month into the same row.
SELECT
YEAR(dt) as the_year,
SUM(mc_gross) AS sum_total
FROM
transactions
GROUP BY
the_year
SELECT
YEAR(dt) as the_year,
SUM(CASE WHEN MONTH(dt) = 1 THEN mc_gross ELSE 0 END) AS Total_Jan,
SUM(CASE WHEN MONTH(dt) = 2 THEN mc_gross ELSE 0 END) AS Total_Feb,
...
SUM(CASE WHEN MONTH(dt) = 12 THEN mc_gross ELSE 0 END) AS Total_Dec
FROM
transactions
GROUP BY
the_year;