I have a dataset like this as shown below, for example row 1, 0 purchase is made for the item priced at $3, 3 purchases are made for item priced at $30. I would need to write a query to provide the summary of how many passes purchased, by categorising the prices into price range of "0-10", "11-20", "21-30", "31-40". I assume case when should be used but I am unsure of how. Please help.
+-----------------+-----------------+--------------------------+--------------------------+
| price_category1 | price_category2 | purchase_count_category1 | purchase_count_category2 |
+-----------------+-----------------+--------------------------+--------------------------+
| 3 | 30 | 0 | 3 |
| 20 | 6 | 1 | 4 |
| 25 | 11 | 4 | 0 |
| 17 | 12 | 0 | 1 |
+-----------------+-----------------+--------------------------+--------------------------+
+------+-------+-------+-------+
| 0-10 | 11-20 | 21-30 | 31-40 |
+------+-------+-------+-------+
| 4 | 2 | 7 | 0 |
+------+-------+-------+-------+
You can try below.
Working Solution
WITH MAIN
AS (SELECT 3 AS price_category1,
30 AS price_category2,
0 purchase_count_category1,
3 AS purchase_count_category2
FROM DUAL
UNION ALL
SELECT 20 AS price_category1,
6 AS price_category2,
1 purchase_count_category1,
4 AS purchase_count_category2
FROM DUAL
UNION ALL
SELECT 25 AS price_category1,
11 AS price_category2,
4 purchase_count_category1,
0 AS purchase_count_category2
FROM DUAL
UNION ALL
SELECT 17 AS price_category1,
12 AS price_category2,
0 purchase_count_category1,
1 AS purchase_count_category2
FROM DUAL),
M2
AS (SELECT price_category1 CAT, purchase_count_category1 CNT FROM MAIN
UNION
SELECT price_category2, purchase_count_category2 FROM MAIN)
SELECT CASE
WHEN CAT >= 0 AND CAT < 11 THEN '0-10'
WHEN CAT >= 11 AND CAT < 21 THEN '11-20'
WHEN CAT >= 21 AND CAT < 31 THEN '21-30'
END
CAT,
SUM (CNT) SUMM
FROM M2
GROUP BY CASE
WHEN CAT >= 0 AND CAT < 11 THEN '0-10'
WHEN CAT >= 11 AND CAT < 21 THEN '11-20'
WHEN CAT >= 21 AND CAT < 31 THEN '21-30'
END
SELECT SUM(CASE WHEN price_category1 BETWEEN 0 AND 10
THEN purchase_count_category1
END) + SUM(CASE WHEN price_category2 BETWEEN 0 AND 10
THEN purchase_count_category2
END) AS `0-10`,
SUM(CASE WHEN price_category1 BETWEEN 11 AND 20
THEN purchase_count_category1
END) + SUM(CASE WHEN price_category2 BETWEEN 11 AND 20
THEN purchase_count_category2
END) AS `11-20`,
SUM(CASE WHEN price_category1 BETWEEN 21 AND 30
THEN purchase_count_category1
END) + SUM(CASE WHEN price_category2 BETWEEN 21 AND 30
THEN purchase_count_category2
END) AS `21-30`
FROM source_table
or
SELECT SUM(CASE WHEN cat BETWEEN 0 AND 10
THEN cnt
END ) AS `0-10`,
SUM(CASE WHEN cat BETWEEN 11 AND 20
THEN cnt
END ) AS `11-20`,
SUM(CASE WHEN cat BETWEEN 21 AND 30
THEN cnt
END ) AS `21-30`
FROM ( SELECT price_category1 cat, purchase_count_category1 cnt
FROM source_table
UNION ALL
SELECT price_category2, purchase_count_category2
FROM source_table ) src
PS. This solution gives "horizontal" output - one row with all needed statistic. If you need "vertical" output then use the solution provided by ismetguzelgun.
Pay attention - my solution is not extendable (if you need to alter the ranges amount or borders you must alter the query text) whereas alternative solution can be extended easily after converting hardcoded ranges borders to according CTE or (the best) additional criteria table.
Related
Table:
id price is_active
========================
1 20.99 0
2 10.99 1
3 30.99 0
4 15.99 1
5 35.99 1
I am trying to select COUNT of all rows that has is_active equals to 1, so I've used this simple query:
SELECT COUNT(*) FROM table WHERE is_active=1
But what if I want to also know how many rows have price:
less than 15
between 15 and 30
more than 30
I can write this:
SELECT COUNT(*) FROM table WHERE is_active=1 AND price < 15
SELECT COUNT(*) FROM table WHERE is_active=1 AND price > 15 AND price < 30
SELECT COUNT(*) FROM table WHERE is_active=1 AND price > 30
But can I do it in one simple query that returns something like this?
"Less than 15" | "Between 15 and 30" | "More than 30"
1 | 2 | 2
With conditional aggregation:
SELECT
SUM(price < 15) `Less than 15`,
SUM(price >= 15 AND price <= 30) `Between 15 and 30`,
SUM(price > 30) `More than 30`
FROM `table`
WHERE is_active=1
In MySql a boolean expression like price < 15 is evaluated as 0 for false or 1 for true.
See the demo.
Results:
| Less than 15 | Between 15 and 30 | More than 30 |
| ------------ | ----------------- | ------------ |
| 1 | 1 | 1 |
Because they are even intervals you could
SELECT
IF(price > 30, 30, price) div 15 as g,
count(*) as c
FROM `table`
WHERE is_active=1
GROUP BY g
Note this leaves 30 in the 'more than 30' category
I have this table: "sales"
+-------------+---------+
| date | total |
+-------------+---------+
| 2018-12-04 | 269.10 |
| 2018-12-05 | 29.00 |
| 2018-12-06 | 107.10 |
| 2018-12-06 | 34.00 |
| 2018-12-08 | 69.50 |
| 2018-12-08 | 223.00 |
| 2018-12-08 | 68.00 |
| 2018-12-09 | 99.00 |
| 2018-12-10 | 59.50 |
| ... | ... |
+-------------+---------+
I'm trying this query
SELECT DAY(date) AS Days,
SUM(CASE WHEN MONTH(date) = 12 THEN total ELSE NULL END) AS December
FROM sales WHERE YEAR(date) = 2018 GROUP BY date
And I get
+-------+----------+
| Days | December |
+-------+----------+
| 4 | 269.10 |
| 5 | 29.00 |
| 6 | 141.10 |
| 8 | 360.50 |
| 9 | 99.00 |
| 10 | 59.50 |
| ... | ... |
+-------+----------+
But I want consecutive days like this:
+-------+----------+
| Days | December |
+-------+----------+
| 1 | NULL |
| 2 | NULL |
| 3 | NULL |
| 4 | 269.10 |
| 5 | 29.00 |
| 6 | 141.10 |
| 7 | NULL |
| 8 | 360.50 |
| 9 | 99.00 |
| 10 | 59.50 |
| ... | ... |
| 31 | 123.00 |
+-------+----------+
Can you help me plss..
PS: I have several months and years in "date" column from "sales" table.
This recursive CTE generates a list of dates corresponding to the month and year specified in the doi CTE, and then LEFT JOINs that to the sales table to get the sales for that month. It will work for any month/year, just change the values in the doi CTE, and the title of the SUM column (currently December) to suit.
WITH RECURSIVE doi AS (
SELECT 12 AS month,
2018 AS year
),
cte AS (
SELECT DATE(CONCAT_WS('-', year, month, 1)) AS date
FROM doi
UNION ALL
SELECT date + INTERVAL 1 DAY
FROM cte
WHERE date < LAST_DAY(date)
)
SELECT DAY(cte.date) AS Days,
ROUND(SUM(s.total),2) AS December
FROM cte
LEFT JOIN sales s ON s.date = cte.date
GROUP BY cte.date
ORDER BY cte.date
Output is too long to show here but can be seen at this demo on dbfiddle
Update
To expand this query to cover an entire year requires changing the approach slightly in terms of generating an entire year's worth of dates, and then using conditional aggregation to get the sums for each day of each month:
WITH RECURSIVE doi AS (
SELECT 2018 AS year
),
cte AS (
SELECT DATE(CONCAT_WS('-', year, 1, 1)) AS date
FROM doi
UNION ALL
SELECT date + INTERVAL 1 DAY
FROM cte
CROSS JOIN doi
WHERE date < DATE(CONCAT_WS('-', doi.year, 12, 31))
)
SELECT DAY(cte.date) AS Days,
ROUND(SUM(CASE WHEN MONTH(s.date) = 1 THEN s.total END),2) AS January,
ROUND(SUM(CASE WHEN MONTH(s.date) = 2 THEN s.total END),2) AS February,
ROUND(SUM(CASE WHEN MONTH(s.date) = 3 THEN s.total END),2) AS March,
ROUND(SUM(CASE WHEN MONTH(s.date) = 4 THEN s.total END),2) AS April,
ROUND(SUM(CASE WHEN MONTH(s.date) = 5 THEN s.total END),2) AS May,
ROUND(SUM(CASE WHEN MONTH(s.date) = 6 THEN s.total END),2) AS June,
ROUND(SUM(CASE WHEN MONTH(s.date) = 7 THEN s.total END),2) AS July,
ROUND(SUM(CASE WHEN MONTH(s.date) = 8 THEN s.total END),2) AS August,
ROUND(SUM(CASE WHEN MONTH(s.date) = 9 THEN s.total END),2) AS September,
ROUND(SUM(CASE WHEN MONTH(s.date) = 10 THEN s.total END),2) AS October,
ROUND(SUM(CASE WHEN MONTH(s.date) = 11 THEN s.total END),2) AS November,
ROUND(SUM(CASE WHEN MONTH(s.date) = 12 THEN s.total END),2) AS December
FROM cte
LEFT JOIN sales s ON s.date = cte.date
GROUP BY DAY(cte.date)
ORDER BY DAY(cte.date)
Demo on dbfiddle
generate your months using union and do right join
select t1.d as Days
, sum(iif(month(date) = 12, total, null) as December
from sales
right join (select 1 as d
union select 2 union select 3 union select 4 union select 5 union select 6
union select 7 union select 8 union select 9 union select 10 union select 11
.... ) as t1 on t1.d = day(date)
where year(date) = 2012
group by date
if you are using mysql v8.0, you can use recursive queries.
with recursive cte as(
select 1 as d
union all
select d + 1 from cte where d < day(last_day('2019-12-01'))
)
select coalesce(day(s.date), t1.d) as Days
, sum(iif(month(s.date) = 12, total, null) as December
from sales s
right join cte as t1 on t1.d = day(s.date)
where year(date) = 2012
group by coalesce(day(s.date), t1.d)
I have a query for getting data from a certain table by date range and grouping by week. My CROSS JOIN intends to fill in a default value for each week where there are no results for the date range.
I can then execute this query.
SELECT
SUM(invoice.amount) AS "invoice.amount",
CONCAT(DATE_FORMAT(invoice.updated_at, '%b %d'), ' - ', DATE_FORMAT(DATE_ADD(invoice.updated_at, INTERVAL 7 DAY), '%b %d')) AS "invoice.updated_at"
FROM invoice
CROSS JOIN (
SELECT selected_date
FROM (
SELECT ADDDATE('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) selected_date
FROM
(SELECT 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(SELECT 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(SELECT 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(SELECT 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(SELECT 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4
) v
WHERE selected_date BETWEEN '2018-01-01' AND '2018-01-31'
GROUP BY selected_date, YEAR(selected_date), WEEK(selected_date)
) calendar
WHERE invoice.updated_at >= '2018-01-01'
AND invoice.updated_at <= '2018-01-31'
AND invoice.status = "PAID"
GROUP BY calendar.selected_date, invoice.id, invoice.amount, YEAR(invoice.updated_at), WEEK(invoice.updated_at)
Assume I have these records in the database:
+----+------------+------------+------------+
| id | amount | status | updated_at |
+----+------------+------------+------------+
| 1 | 1000 | PAID | 2018-01-01 |
| 2 | 2000 | PAID | 2018-01-01 |
| 3 | 100 | PAID | 2018-01-07 |
| 4 | 50 | PAID | 2018-01-11 |
+----+------------+------------+------------+
I expect to see these results, one record for every week of January:
+--------+-------------------+
| amount | updated_at |
+--------+-------------------+
| 3100 | Jan 1 - Jan 7 |
| 50 | Jan 8 - Jan 15 |
| 0 | Jan 16 - Jan 22 |
| 0 | Jan 23 - Jan 30 |
| 0 | Jan 31 - Jan 31 |
+--------+-------------------+
However, I get like 50 of these random duplicated results, the least of which contains the joined filler weeks since there are no 0 amounts:
+--------+----------------+
| amount | updated_at |
+--------+----------------+
| 1000 | Jan 1 - Jan 7 |
| 2000 | Jan 1 - Jan 7 |
| 100 | Jan 1 - Jan 7 |
| 50 | Jan 8 - Jan 15 |
| 1000 | Jan 1 - Jan 7 |
| 1000 | Jan 1 - Jan 7 |
| 2000 | Jan 1 - Jan 7 |
| 2000 | Jan 1 - Jan 7 |
| 100 | Jan 1 - Jan 7 |
| 50 | Jan 8 - Jan 15 |
| 100 | Jan 1 - Jan 7 |
| 50 | Jan 8 - Jan 15 |
| ... | ... |
| ... | ... |
| ... | ... |
+--------+----------------+
What gives?
GROUP BY calendar.selected_date, invoice.id, invoice.amount,
You have too many columns specified in the group by, particularly invoice.amount
Instead, try with:
GROUP BY
CONCAT(DATE_FORMAT(invoice.updated_at, '%b %d'), ' - ', DATE_FORMAT(DATE_ADD(invoice.updated_at, INTERVAL 7 DAY), '%b %d'))
I cannot be sure, but I think your date range needs adjustment as well, the following will guarantee you get everything for January 2018:
WHERE invoice.updated_at >= '2018-01-01'
AND invoice.updated_at < '2018-02-01'
My source table (wplott_wpkl_winner) contains the field "lottery_number" that carries 1 to 6 digit numbers and the corresponding "draw_date".
lottery_number | draw_date
==================================
0024 | 2018-11-10
4456 | 2018-11-10
3895 | 2018-11-10
4557 | 2018-11-10
4225 | 2018-11-10
2896 | 2018-11-10
3354 | 2018-11-10
1895 | 2018-11-10
78466 | 2018-11-10
998556 | 2018-11-10
My current MYSQL query is as below (I am trying to group the data into ranges)
select
count(case when wplott_wpkl_winner.lottery_number between 0 and 999 then 1 end) `0-999`,
count(case when wplott_wpkl_winner.lottery_number between 1000 and 1999 then 1 end) `1000-1999`,
count(case when wplott_wpkl_winner.lottery_number between 2000 and 2999 then 1 end) `2000-2999`,
count(case when wplott_wpkl_winner.lottery_number between 3000 and 3999 then 1 end) `3000-3999`,
count(case when wplott_wpkl_winner.lottery_number between 4000 and 4999 then 1 end) `4000-4999`,
count(case when wplott_wpkl_winner.lottery_number between 5000 and 5999 then 1 end) `5000-5999`,
count(case when wplott_wpkl_winner.lottery_number between 6000 and 6999 then 1 end) `6000-6999`,
count(case when wplott_wpkl_winner.lottery_number between 7000 and 7999 then 1 end) `7000-7999`,
count(case when wplott_wpkl_winner.lottery_number between 8000 and 8999 then 1 end) `8000-8999`,
count(case when wplott_wpkl_winner.lottery_number between 9000 and 9999 then 1 end) `9000-9999`
from wplott_wpkl_winner
where CHAR_LENGTH(wplott_wpkl_winner.lottery_number) = 4 AND wplott_wpkl_winner.draw_date > '2013-06-30'
It provides the below output
0-999 | 1000-1999 | 2000-2999 | 3000-3999 | 4000- 4999 .... etc
=====================================================================
1 | 1 | 1 | 2 | 3
However, I would like to get the output in the below format.
Range | Count
=======================
0-999 | 1
1000-1999 | 1
2000-2999 | 1
3000-3999 | 2
4000-4999 | 3
.
.
.
Any help is highly appreciated. I did search in SO for a similar answer but none of the answers helped my particular case.
Thanks in advance!
One approach uses a series of unions:
SELECT
`range`,
count
FROM
(
SELECT 1 AS pos, '0-999' AS `range`, COUNT(*) AS count
FROM wplott_wpkl_winner
WHERE draw_date > '2013-06-30' AND lottery_number BETWEEN 0 AND 999
UNION ALL
SELECT 2, '1000-1999', COUNT(*)
FROM wplott_wpkl_winner
WHERE draw_date > '2013-06-30' AND lottery_number BETWEEN 1000 AND 1999
UNION ALL
... -- fill in remaining ranges here
) t
ORDER BY pos;
Note that I introduce a computed column pos so that we may maintain the desired ordering of the ranges in the final output. Also, I removed the check on the CHAR_LENGTH of the lottery_number, since the conditional sums already handle this logic.
So here's what my data table looks like:
TeamNum Round Points1 Points2
1 1 5 21
2 1 10 20
3 1 9 29
1 2 6 22
2 2 11 21
3 2 10 30
1 3 80 50
I also have a second table with this:
TeamNum TeamName
1 goteam1
2 goteam2
3 goteam4-1
I want SQL to take it and turn it into this:
Team Round1 Round2 Round3 TeamName
1 (points1+points2 of round1) (same but for r2) (same but for r3) goteam1
2 (points1+points2 of round1) (same but for r2) (same but for r3) goteam2
3 (points1+points2 of round1) (same but for r2) (same but for r3) goteam4-1
And a sample output from the tables above would be:
Team Round1 Round2 Round3 TeamName
1 26 28 130 goteam1
2 30 32 0 goteam2
3 38 40 0 goteam4-1
The actual data has a bunch of "points1" and "points2" columns, but there are only 3 rounds.
I am very new to SQL and this is all I have right now:
select
`data`.`round`,
`data`.`teamNumber`,
sum(`Points1`) + sum(`Points2`) as score
from `data` join `teams` ON `teams`.`teamNumber` = `data`.`teamNumber`
group by `data`.`teamNumber` , `round`
order by `data`.`teamNumber`, `data`.`round`
But it doesn't return anything at all. If I remove the join statement, it shows everything like I want, but doesn't consolidate Round1, 2, and 3 as columns, they are each separate rows. Can you guys help me out? Thanks!
Use conditional aggregation
SELECT t.teamnumber, t.teamname,
SUM(CASE WHEN d.round = 1 THEN d.points1 + d.points2 ELSE 0 END) round1,
SUM(CASE WHEN d.round = 2 THEN d.points1 + d.points2 ELSE 0 END) round2,
SUM(CASE WHEN d.round = 3 THEN d.points1 + d.points2 ELSE 0 END) round3
FROM data d JOIN teams t
ON d.teamnumber = t.teamnumber
GROUP BY t.teamnumber, t.teamname
Output:
| TEAMNUMBER | TEAMNAME | ROUND1 | ROUND2 | ROUND3 |
|------------|-----------|--------|--------|--------|
| 1 | goteam1 | 26 | 28 | 130 |
| 2 | goteam2 | 30 | 32 | 0 |
| 3 | goteam4-1 | 38 | 40 | 0 |
Here is SQLFiddle demo
No need to aggregate:
SELECT
t.teamnumber,
COALESCE(r1.points1 + r1.points2, 0) AS round1,
COALESCE(r2.points1 + r2.points2, 0) AS round2,
COALESCE(r3.points1 + r3.points2, 0) AS round3,
t.teamname
FROM teams t
LEFT JOIN data r1 ON r1.teamnumber = t.teamnumber AND r1.round = 1
LEFT JOIN data r2 ON r2.teamnumber = t.teamnumber AND r2.round = 2
LEFT JOIN data r3 ON r3.teamnumber = t.teamnumber AND r3.round = 3
Something like this:
select teams.teamNumber,
SUM(CASE WHEN Round=1 THEN `Points1`+`Points2` ELSE 0 END)as Round1,
SUM(CASE WHEN Round=2 THEN `Points1`+`Points2` ELSE 0 END)as Round2,
SUM(CASE WHEN Round=3 THEN `Points1`+`Points2` ELSE 0 END)as Round3,
teams.teamName
from `data` join `teams` ON `teams`.`teamNumber` = `data`.`teamNumber`
group by teamnumber , teamname
order by `data`.`teamNumber`, `data`.`round`