I'm trying to create a query that will group items by description, and then count that item for the past 3 months, with each month having its own column. Ultimately I'm trying to achieve something like the following:
+-------------+------------+------------+--------------------+
| Description | This Month | Last Month | The Previous Month |
+-------------+------------+------------+--------------------+
| Item_1 | 4 | 3 | 5 |
| Item_2 | 3 | 0 | 2 |
| Item_3 | 1 | 3 | 5 |
+-------------+------------+------------+--------------------+
SELECT
"Description",
SUM(
CASE
WHEN "Date" > NOW() THEN 0
WHEN "Date" > NOW() - INTERVAL 1 MONTH THEN 1
ELSE 0
END CASE
) AS "This month",
SUM(
CASE
WHEN "Date" > NOW() - INTERVAL 1 MONTH THEN 0
WHEN "Date" > NOW() - INTERVAL 2 MONTH THEN 1
ELSE 0
END CASE
) AS "Last month",
SUM(
CASE
WHEN "Date" > NOW() - INTERVAL 2 MONTH THEN 0
WHEN "Date" > NOW() - INTERVAL 3 MONTH THEN 1
ELSE 0
END CASE
) AS "The previous month"
FROM Items
WHERE "Date" > NOW() - INTERVAL 3 MONTH
GROUP BY "Description"
Related
I have a table like so
+---------+
| weekday |
+---------+
| 2 |
| 5 |
| 3 |
+---------+
Now I want to have a resultset in which I see the dates of this and the upcoming week like so:
+------------+---------+
| date | weekday |
+------------+---------+
| 2019-12-18 | 2 |
| 2019-12-21 | 5 |
| 2019-12-19 | 3 |
| 2019-12-25 | 2 |
| 2019-12-28 | 5 |
| 2019-12-26 | 3 |
+------------+---------+
So far I have this query
SELECT
CURDATE() + INTERVAL w.weekday - WEEKDAY(CURDATE()) DAY AS thisWeek,
CURDATE() + INTERVAL w.weekday + 7 - WEEKDAY(CURDATE()) DAY AS nextWeek,
dw.weekday
FROM
weekdays AS w
Which gives me this result
+------------+------------+---------+
| thisWeek | nextWeek | weekday |
+------------+------------+---------+
| 2019-12-18 | 2019-12-25 | 2 |
| 2019-12-21 | 2019-12-28 | 5 |
| 2019-12-19 | 2019-12-26 | 3 |
+------------+------------+---------+
How would I have to need to proceed to get the former resultset?
Use UNION ALL.
SELECT
CURDATE() + INTERVAL w.weekday - WEEKDAY(CURDATE()) DAY AS date
dw.weekday
FROM
weekdays AS w
UNION ALL
SELECT
CURDATE() + INTERVAL w.weekday + 7 - WEEKDAY(CURDATE()) DAY AS date
dw.weekday
FROM
weekdays AS w;
You could try using UNION
select thisWeek date, weekday
from (
SELECT
CURDATE() + INTERVAL w.weekday - WEEKDAY(CURDATE()) DAY AS thisWeek,
CURDATE() + INTERVAL w.weekday + 7 - WEEKDAY(CURDATE()) DAY AS nextWeek,
dw.weekday
FROM weekdays AS w
) t1
union all
select nextWeek date, weekday
from (
SELECT
CURDATE() + INTERVAL w.weekday - WEEKDAY(CURDATE()) DAY AS thisWeek,
CURDATE() + INTERVAL w.weekday + 7 - WEEKDAY(CURDATE()) DAY AS nextWeek,
dw.weekday
FROM weekdays AS w
) t2
or for avoid subquery you could create a view
create view my_view as
SELECT
CURDATE() + INTERVAL w.weekday - WEEKDAY(CURDATE()) DAY AS thisWeek,
CURDATE() + INTERVAL w.weekday + 7 - WEEKDAY(CURDATE()) DAY AS nextWeek,
dw.weekday
FROM weekdays
then the query is
select thisWeek date, weekday
from my_view
union all
select nextWeek date, weekday
from my_view
I have a MySQL query for example:
select sum(qty) as qty
from myitem
where filedatetime >='2019-08-01 06:30:00'
and filedatetime < '2019-09-01 18:30:00'
My table only have qty and filedatetime columns.
Based on this query is it possible to query out the following conditions:
if filedatetime is filedatetime >= '2019-08-01 06:30:00' and filedatetime < '2019-08-01 18:30:00' then
assign value 'Day' to column shift
assign value '2019-08-01' to column shiftDate
if filedatetime >= '2019-08-01 18:30:00' and filedatetime < '2019-08-02 06:30:00' then
assign value 'Night' to column shift
assign value '2019-08-01' to column shiftDate
So this how the result will be look like:
shiftDate | Shift | QTY |
2019-08-01 | Day | 10 |
2019-08-01 | Night | 12 |
2019-08-02 | Day | 11 |
2019-08-02 | Night | 15 |
2019-08-11 | Day | 11 |
2019-08-11 | Night | 18 |
2019-08-12 | Day | 13 |
2019-08-12 | Night | 19 |
You can use Time() function to extract time (hh:mm:ss) portion out of a datetime value. Once we have determined that, we can use CASE .. WHEN conditional expression, to check whether "Day" or "Night" shift based on your defined conditions. Also, we can use Date() function to extract the date (YYYY-mm-dd) portion and add/subtract 1 day accordingly.
We can then GROUP BY on these calculated expressions, and calculate sum of the qty; no need of using a subquery for further aggregation:
SELECT
-- Determining day of the shift
CASE
-- When below 06:30:00 then previous day's Night shift
WHEN TIME(filedatetime) < '06:30:00'
THEN DATE(filedatetime) - INTERVAL 1 DAY
-- Else When >= 06:30:00 then current day's shift
ELSE DATE(filedatetime)
END AS shiftDate,
-- Determining Night / Day
CASE
-- When between 06:30:00 (inclusive) and 18:30:00 then Day
WHEN TIME(filedatetime) >= '06:30:00' AND TIME(filedatetime) < '18:30:00'
THEN 'Day'
ELSE 'Night'
END AS shift,
-- Do aggregation
SUM(qty) AS qty
FROM myitem
WHERE filedatetime >='2019-08-01 06:30:00'
AND filedatetime < '2019-09-01 18:30:00'
GROUP BY shiftDate, shift
You should be able to use time functionality directly:
select mi.*,
(case when cast(filedatetime as time) >= '06:30:00' and
cast(filedatetime as time) < '18:30:00'
then 'day' else 'night'
end) as shift
from myitem mi;
In MySQL, you can also use time(filedatetime) function instead of cast().
I have this kind of table with time based data:
| entity_id | ttime | value |
-------------------------------------------
| 1 | 2014-11-01 00:00:04 | 553 |
| 1 | ... | 600 |
| 2 | ... | 234 |
I want to get the average of the value grouped by week and entity_id. But I would like also the starting day of the week to appear in the results. Additional complexity is that the week starts on wednesday.
I can group by YEAR(ttime + INTERVAL 3 DAY), WEEK(ttime + INTERVAL 3 DAY) but is it possible to print the first day of the group (wednesday) in the results?
Thanks
maybe something like this:
SELECT
`entity_id`,
DATE_SUB(ttime, INTERVAL WEEKDAY(ttime)-2 DAY),
SUM(`value`)
FROM `table`
GROUP BY `entity_id`, YEARWEEK(ttime + INTERVAL 4 DAY)
SqlFiddle
I found this solution:
SELECT
str_to_date(CONCAT(YEAR(ttime + INTERVAL -3 DAY),
WEEK(ttime + INTERVAL -3 DAY), 'Wednesday'), '%X%V %W') as WeekCommencing,
entity_id, AVG(value),
FROM `table`
GROUP BY WeekCommencing, entity_id
Is there a way in mysql to group by month, but with custom starting dates.
Say I want to count logins in a monthly basis, but with the condition that the month starts when a user register.
So for example user A registered on January 30th and user B on January 15th
I should group the logins as follow:
* User A: January 30th - February 28th, March 1st - March 30th, March 31 - April 30 and so on and so forth
* User B: January 15th - February 14th, February 15th - March 14th and so on and so forth
I guess I need to use something like DATE_ADD('2013-01-30', INTERVAL 1 MONTH); but I can not seem to find a way to make the grouping.
UPDATE
#GarethD: You are right that was a typo
In general the month should start at the same day of the next month or the last day of the next month in case that the first is not possible, so if you registered in day 31, the month period would start in day 30 for months that does not have 31 days and the last day of February either 28 or 29
Example:
Given that
id 1 registered on 2012-12-16
id 2 registered on 2013-01-29
and the following table
+----+------------+
| id | date |
+----+------------+
| 1 | 2013-01-15 |
| 1 | 2013-01-16 |
| 1 | 2013-01-17 |
| 1 | 2013-01-17 |
| 2 | 2013-03-20 |
| 2 | 2013-03-21 |
| 2 | 2013-03-28 |
| 2 | 2013-03-29 |
| 2 | 2013-03-30 |
+----+------------+
the results should be
+----+----------------------------+-------+
| id | range | count |
+----+----------------------------+-------+
| 1 | 2012-12-16, 2013-01-15 | 1 |
| 1 | 2013-01-16, 2013-02-15 | 3 |
| 2 | 2013-02-2[8|9], 2013-03-28 | 3 |
| 2 | 2013-03-29, 2013-04-28 | 2 |
+----+----------------------------+-------+
I hope the intent is clearer now.
For the following I am assuming you already have a numbers table, If you don't have a numbers table, then I'd recommend you make one then, but if you don't want to then you can create a number list on the fly
You can get a list of all boundaries by cross joining your userID and registered dates with your numbers table:
SELECT u.ID,
DATE_ADD(RegisteredDate, INTERVAL n.Number MONTH) PeriodStart,
DATE_ADD(RegisteredDate, INTERVAL n.Number + 1 MONTH) PeriodEnd
FROM User u
CROSS JOIN Numbers n;
This gives a table like:
ID PERIODSTART PERIODEND
1 2012-12-16 2012-12-16
2 2013-01-29 2013-01-29
1 2013-01-16 2013-01-16
2 2013-02-28 2013-02-28
Example on SQL Fiddle
You then need to join this to your main table, and do the count:
SELECT u.ID,
u.PeriodStart,
DATE_ADD(PeriodEnd, INTERVAL -1 DAY) PeriodEnd,
COUNT(*) AS `COUNT`
FROM ( SELECT u.ID,
DATE_ADD(RegisteredDate, INTERVAL n.Number MONTH) PeriodStart,
DATE_ADD(RegisteredDate, INTERVAL n.Number + 1 MONTH) PeriodEnd
FROM User u
CROSS JOIN Numbers n
) u
INNER JOIN T
ON T.ID = u.ID
AND T.Date >= u.PeriodStart
AND T.Date < PeriodEnd
GROUP BY u.ID, u.PeriodStart, u.PeriodEnd;
Giving a final result of:
ID PERIODSTART PERIODEND COUNT
1 2012-12-16 2013-01-15 1
1 2013-01-16 2013-02-15 3
2 2013-02-28 2013-03-28 3
2 2013-03-29 2013-04-28 2
Full Example on SQL-Fiddle
You can obviously concatenate your period start and end dates to make a 'range' string, but this is probably best handled in your application layer.
EDIT
This can be achieved with no subqueries which is likely to perform better:
SELECT u.ID,
DATE_ADD(u.RegisteredDate, INTERVAL n.Number MONTH) PeriodStart,
DATE_ADD(DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH), INTERVAL -1 DAY) PeriodEnd,
COUNT(*) AS `COUNT`
FROM User u
CROSS JOIN Numbers n
INNER JOIN T
ON T.ID = u.ID
AND T.Date >= DATE_ADD(u.RegisteredDate, INTERVAL n.Number MONTH)
AND T.Date < DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH)
GROUP BY u.ID, u.RegisteredDate, n.Number;
Example with no subquery on SQL-Fiddle
EDIT 2
This will get you all periods for all users up until the current period (i.e. where today falls within the date range)
SELECT u.ID,
DATE_ADD(u.RegisteredDate, INTERVAL n.Number MONTH) PeriodStart,
DATE_ADD(DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH), INTERVAL -1 DAY) PeriodEnd,
COUNT(T.ID) AS `COUNT`
FROM User u
CROSS JOIN Numbers n
LEFT JOIN T
ON T.ID = u.ID
AND T.Date >= DATE_ADD(u.RegisteredDate, INTERVAL n.Number MONTH)
AND T.Date < DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH)
WHERE DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH) <= CURRENT_TIMESTAMP
GROUP BY u.ID, u.RegisteredDate, n.Number;
Example on SQL Fiddle
i'm trying to do age analysis on some data and need to do a conditional summation i.e my table is :
ID | Date | Amount |
===+=========+========+
1 | 1/1/10 | 100 |
2 | 1/2/10 | 100 |
3 | 1/5/10 | 100 |
4 | 15/5/10 | 100 |
5 | 20/5/10 | 100 |
Say the date today is 1/6/10 I'd like to sum the amount depending on their age as used in age analysis. i.e i'd like this out
Age | Total
===========+======
<30 days | 300
30-60 days | 0
60-90 days | 0
90 days+ | 200
Essentially its a conditional summation so I want to sum all the values (<30 days, then 30-60 days, then 60-90 days, then 90days+)
You can do:
select case
when datediff(now(), date) >= 90 then '90 days+'
when datediff(now(), date) >= 60 and datediff(now(), date) < 90 then '60-90 days'
when datediff(now(), date) >= 30 and datediff(now(), date) < 60 then '30-60 days'
else '< 30 days'
end case f,
sum(amount)
from your_table
group by f;