MySQL Date Range Multi Column Count and Group By Select Statement - mysql

I have a query I have been working on for a while but I cannot seem to get it down. The other answers on here work well for counting an amount with a certain date range then grouping by the date to get the count. However I need to have two columns counted and grouped by date.
For example here is the query I have tried to get to work:
(SELECT COUNT(*) arrived, DATE(arrived) date, 'arrived' AS source
FROM products
WHERE arrived BETWEEN '2016-01-01' AND '2016-01-31'
GROUP BY DATE(date)
ORDER BY date ASC)
UNION ALL
(SELECT COUNT(*) released, DATE(released) date, 'released' AS source
FROM products
WHERE released BETWEEN '2016-01-01' AND '2016-01-31'
GROUP BY DATE(date)
ORDER BY date ASC)
However this returns the following:
arrived date source
3 2016-01-12 arrived
2 2016-01-28 arrived
1 2016-01-29 arrived
1 2016-01-05 released
What I am requiring is something like this:
date arrived released
2016-01-05 0 1
2016-01-12 3 0
2016-01-28 2 0
2016-01-29 1 0
Any suggestions? Thank you.

You can apply conditional aggregation to a derived table obtained by a UNION ALL operation for 'arrived' and 'released' dates:
SELECT `date`,
COUNT(CASE WHEN type = 'arrived' THEN 1 END) AS arrived,
COUNT(CASE WHEN type = 'released' THEN 1 END) AS released
FROM (
SELECT arrived AS `date`, 'arrived' as type
FROM products
WHERE arrived BETWEEN '2016-01-01' AND '2016-01-31'
UNION ALL
SELECT released AS `date`, 'released' as type
FROM products
WHERE released BETWEEN '2016-01-01' AND '2016-01-31') AS t
GROUP BY `date`
Demo here

Related

Count differently an event if the same event from the same user happened in the past

I have a table listing subscription events. When there is a "NEW" event added to the table, it means either a new subscription from a brand new customer OR the renewal of a monthly subscription from an existing customer.
I want to be able to be able to summarize the data by month and split it depending on whether that is a new customer or just a renewal.
I am looking for a formula that says "if the user_ID is unknown and the event is "NEW", then count +1 in the "new customer" column, otherwise +1 in the "renewal" column
SOURCE TABLE
User_id
Event
Date
2
NEW
26/9/2021
2
NEW
26/8/2021
1
NEW
15/8/2021
DESIRED OUTPUT
Sept 20: 1 renewal; 0 new subscriptions
Aug 20: 2 new subscriptions
You may use a window function MIN to determine the earliest subscription date for each user and compare that to determine whether they are a new user or not. You may then aggregate/sum on this to determine the number of new subscriptions or renewals per year and month.
SELECT
YEAR(`Date`) as `year`,
MONTH(`Date`) as `month`,
SUM(is_new=true) as `new subscriptions`,
SUM(is_new=false) as `renewals`
FROM (
SELECT
*,
`Date`=MIN(`Date`) OVER (PARTITION BY `User_id`) as is_new
FROM
events
WHERE
`Event`='NEW'
) e
GROUP BY
YEAR(`Date`),
MONTH(`Date`)
ORDER BY
YEAR(`Date`),
MONTH(`Date`);
year
month
new subscriptions
renewals
2021
8
2
0
2021
9
0
1
or if you are using a mysql version which does not support window functions you may perform a left join on a subquery that finds the earliest subscription date. Using the same logic, the we can determine and count the number of new and renewed subscriptions.
SELECT
YEAR(`Date`) as `year`,
MONTH(`Date`) as `month`,
SUM(new_sub.min_date IS NOT NULL) as `new subscriptions`,
SUM(new_sub.min_date IS NULL) as `renewals`
FROM
events e
LEFT JOIN (
SELECT
`User_id`,
MIN(`Date`) as min_date
FROM
events
WHERE
`Event`='NEW'
GROUP BY
`User_id`
) as new_sub ON e.`User_id`=new_sub.`User_id` AND
e.`Date`=new_sub.min_date
GROUP BY
YEAR(`Date`),
MONTH(`Date`)
ORDER BY
YEAR(`Date`),
MONTH(`Date`)
year
month
new subscriptions
renewals
2021
8
2
0
2021
9
0
1
View working demo on DB Fiddle
Let me know if this works for you.
You can use ROW_NUMBER() to identify if a row is the first one for each client.
For example you can do:
select
year(date) as y,
month(date) as m,
sum(case when rn = 1 then 1 else 0 end) as new_subscriptions,
sum(case when rn <> 1 then 1 else 0 end) as renewals
from (
select *, row_number() over(partition by user_id order by date) as rn
from t
where event = 'NEW'
) x
group by y, m
order by y, m
Result:
y m new_subscriptions renewals
----- -- ------------------ --------
2021 8 2 0
2021 9 0 1
See running example at DB Fiddle.

A query for getting results separated by a date gap

ID
TIMESTAMP
1
2020-01-01 12:00:00
2
2020-02-01 12:00:00
3
2020-05-01 12:00:00
4
2020-06-01 12:00:00
5
2020-07-01 12:00:00
I am looking for a way to get records in a MySQL database that are within a certain range of each other. In the above example, notice that there is a month between the first two records, then a three month gap, before we see another three records with a month between.
What is a way to group these into two result sets, so I will get Ids 1, 2 and 3, 4, 5 A solution using days would be probably work the best as thats easier to modify.
You can use lag() and then logic to see where a gap is big enough to start a new set of records. A cumulative sum gives you the groups you want:
select t.*,
sum(case when prev_timestamp >= timestamp - interval 1 month then 0 else 1 end) over (order by timestamp) as grouping
from (select t.*,
lag(timestamp) over (order by timestamp) as prev_timestamp
from t
) t;
If you want to summarize this with a start and end date:
select min(timestamp), max(timestamp)
from (select t.*,
sum(case when prev_timestamp >= timestamp - interval 1 month then 0 else 1 end) over (order by timestamp) as grouping
from (select t.*,
lag(timestamp) over (order by timestamp) as prev_timestamp
from t
) t
) t
group by grouping;
For example, the following query:
select group_concat(ID)
from (
select w1.ID,w1.TS,w2.ID flag
from work1 w1 left outer join work1 w2
on timestampdiff(month,w2.TS,w1.TS)=1
order by w1.ID
) w
group by
case when flag is null then #str:=ID else #str end
See db fiddle

MySQL SELECT SUM CASE with GROUP BY or DISTINCT

I'm trying to count unique user ids in a log table by month. So far I came up with the following query:
SELECT
COUNT(CASE WHEN log_date LIKE '2020-01%' THEN 1 END) AS januari
FROM user_log;
This query returns the total of all rows of the user_log in januari. However I would like to know how many unique users have logged in in Januari. So I need something like:
SELECT
COUNT(**DISTINCT user_id** CASE WHEN log_date LIKE '2020-01%' THEN 1 END) AS januari
FROM user_log;
I also tried GROUP BY, but so far no luck. Does anyone have a suggestion?
Consider:
SELECT COUNT(DISTINCT CASE WHEN log_date >= '2020-01-01' AND log_date < '2020-02-01' THEN userid END) AS januari
FROM user_log;
I changed the filtering logic to use half-open intervals rather than string matching: it is more efficient.
Note that, if you just that result for January, it is sufficient to use a WHERE clause:
SELECT COUNT(DISTINCT userid) januari
FROM user_log
WHERE log_date >= '2020-01-01' AND log_date < '2020-02-01'

how to get pending months from due entries using mysql

I have the entries of monthly dues like,
Table name : month_dues,
Columns:
customer_id,
due_date,
due_amount
These table have lot of due entries.
customer_id due_date due_amount
--------------------------------------
1 2018-12-01 100
1 2019-01-01 100
1 2019-02-01 100
1 2019-10-01 100
so, how to select pending due months from these record?
in my table customer 1 not paid dues for these months,
2019-03,2019-04,2019-05,2019-06,2019-07,2019-08, 2019-09
customer pay the due every month so
For select paid dues from table using,
SELECT customer_id, due_date, due_amount FROM month_dues where customer_id='1' where due_date>='2019-01-01' and due_date<='2019-10-18';
How to get pending due month and year using this table?
Which means, find month and year not in this record.
IF you are searching between the two dates
YOu can use this
select * from month_dues
where due_date between '2012-03-11 00:00:00' and '2012-05-11
23:59:00' && customer_id='1'
order by month_dues desc;
If you want to search the date(due_date) lower than today
SELECT * FROM month_dues WHERE due_date < CURDATE();
And you are refeering to the record that is not belong to the query you can find some NOT IN query
Like
`SELECT * FROM month_dues NOT IN ( select * from month_dues
where due_date between '2012-03-11 00:00:00' and '2012-05-11
23:59:00' && customer_id='1'
order by month_dues desc;
)`
So basically you need to validate your table against some kind of calendar, here is a simple solution that only works for the current year, maybe you can use it as a start or someone else could improve it
SELECT m.MONTH
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 m
WHERE m.MONTH NOT IN (SELECT MONTH(due_date)
FROM due_months
WHERE customer_id = 1
AND YEAR(due_date) = YEAR(CURDATE()))
AND m.MONTH < MONTH(CURDATE()) -- needs to be improved as well

SQL Combining Multiple SELECT Statements

I am trying to build an SQLite query that will collect statistics from a single table.
The table holds a log, of sorts, with several entries per day. I need to get a separate row for each day within the search parameters and then compile the totals of rows within those dates with certain boolean values.
Here is the query I have so far:
SELECT DATE(DateTime) AS SearchDate,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Total,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE NoMarket = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS NoMarkets,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE Complaint = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Complaints,
(SELECT COUNT() AS Total
FROM CallRecords
WHERE Voicemail = 1
AND DATE(DateTime)
BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY DATE(DateTime)
ORDER BY Total DESC) AS Voicemails
FROM CallRecords
WHERE DATE(DateTime) BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY SearchDate
And the output:
8/28/2017 175 27 11
8/29/2017 175 27 11
8/30/2017 175 27 11
8/31/2017 175 27 11
9/1/2017 175 27 11
As you can see, it is properly getting each individual date, but the totals for the columns is incorrect.
Obviously, I am missing something in my query, but I am not sure where. Is there a better way to perform this query?
EDIT: I have looked into several of the other questions with near-identical titles here, but I have not found anything similar to what I'm looking for. Most seem much more complicated than what I'm trying to accomplish.
It looks like you have a mess of columns in your CallRecords table with names like Complaint and Voicemail, each of which classifies a call.
It looks like those columns have the value 1 when relevant.
So this query should probably help you.
SELECT DATE(DateTime) AS SearchDate,
COUNT(*) AS Total,
SUM(NoMarket = 1) AS NoMarkets,
SUM(Complaint = 1) AS Complaints,
SUM(Voicemail = 1) AS Voicemails
FROM CallRecords
WHERE DateTime >= '2017-08-27'
AND DateTime < '2017-09-02' + INTERVAL 1 DAY
GROUP BY DATE(DateTime)
Why does this work? Because in MySQL a Boolean expression like Voicemail = 1 has the value 1 when it's true and 0 when it's false. You can sum those values up quite nicely.
Why is it faster than what you have? Because DATE(DateTime) BETWEEN this AND that can't exploit an index on DateTime.
Why is it correct for the end of your date range? Because DateTime < '2017-09-02' + INTERVAL 1 DAY pulls in all the records up until, but not including, midnight, on the day after your date range.
If you're using Sqlite, you need AND DateTime < date('2017-09-02', '+1 day'). The + INTERVAL 1 DAY stuff is slightly different there.
you can doing like this , although i wrote in SQL server
SELECT DATE(DateTime) AS SearchDate,
COUNT() AS TOTAL,
SUM(CASE WHEN NoMarket = 1 THEN 1 ELSE 0 END) AS NoMarkets,
SUM(CASE WHEN Complaint = 1 THEN 1 ELSE 0 END) AS Complaints,
SUM(CASE WHEN Voicemail = 1 THEN 1 ELSE 0 END) AS Voicemails
FROM CallRecords
WHERE DATE(DateTime) BETWEEN '2017-08-27' AND '2017-09-02'
GROUP BY SearchDate
SELECT DATE(DateTime) AS SearchDate, Total, NoMarkets, Complaints, Voicemails FROM
(SELECT COUNT() AS Total FROM CallRecords) CR
JOIN
(SELECT COUNT() AS NoMarkets FROM CallRecords WHERE NoMarket = 1) NM
ON CR.DateTime = NM.DateTime
JOIN
(SELECT COUNT() AS Complaints FROM CallRecords WHERE Complaint = 1) C
ON NM.DateTime = C.DateTime
JOIN
(SELECT COUNT() AS Voicemails FROM CallRecords WHERE Voicemail = 1) VM
ON C.DateTime = VM.DateTime
JOIN CallRecords CLR ON VM.DateTime=CLR.DateTime WHERE DATE(CLR.DateTime) >= '2017-08-27' AND DATE(CLR.DateTime) <= '2017-09-02'GROUP BY SearchDate;
This may Output correctly.