Transpose the results of a MySQL query that outputs ranges - mysql

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.

Related

MySQL which query statement should be used?

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.

MySQL Grouping or converting multiple records into 1 single record [duplicate]

This question already has answers here:
MYSQL Sum Query with IF Condition
(2 answers)
Closed 2 years ago.
I have a MySQL Table as for example
Date | Branch | shift_time | Total Order | Avg Price
20-06-08 | A | morning | 4 | 5.6
20-06-08 | A | night | 3 | 3.4
20-06-08 | B | morning | 2 | 2.7
20-06-08 | B | night | 6 | 5.9
20-06-09 | A | morning | 9 | 8.9
20-06-09 | A | night | 4 | 6.9
The column shift_time is an enum and will be constant
We need to convert that into single record per each date by branch
Date | Branch | morning_total_order | morning_price | night_total_order | night_avg_price
20-06-08 | A | 4 | 5.6 | 3 | 3.4
20-06-08 | B | 2 | 2.7 | 6 | 5.9
20-06-09 | A | 9 | 8.9 | 4 | 6.9
I tried using GROUP_CONCAT but that query merges the shift time with data. We want that on the column header.
Maybe we need to use CASE WHEN. But I am not sure about that.
You can use conditional aggregation to generate the results you want:
SELECT Date, Branch,
SUM(CASE WHEN shift_time = 'morning' THEN `Total Order` ELSE 0 END) AS morning_total_order,
SUM(CASE WHEN shift_time = 'morning' THEN `Avg Price` * `Total Order` ELSE 0 END) /
SUM(CASE WHEN shift_time = 'morning' THEN `Total Order` ELSE 0 END) AS morning_avg_price,
SUM(CASE WHEN shift_time = 'night' THEN `Total Order` ELSE 0 END) AS night_total_order,
SUM(CASE WHEN shift_time = 'night' THEN `Avg Price` * `Total Order` ELSE 0 END) /
SUM(CASE WHEN shift_time = 'night' THEN `Total Order` ELSE 0 END) AS night_avg_price
FROM shifts
GROUP BY Date, Branch
Output:
Date Branch morning_total_order morning_avg_price night_total_order night_avg_price
20-06-08 A 4 5.6 3 3.4
20-06-08 B 2 2.7 6 5.9
20-06-09 A 9 8.9 4 6.9
Demo on SQLFiddle

Count whether a value exist from a range of keys

Return a count when response_val=4 is found between response_key 95 to 98 corresponding to each uhid and group by month
|response_key | response_val | month | uhid |
|---------------------------------------------|
| 95 | 4 | 2019-09| 14569|
| 96 | 4 | 2019-09| 14569|
| 97 | 5 | 2019-09| 14569|
| 98 | 5 | 2019-09| 14569|
| 95 | 5 | 2019-09| 13256|
| 96 | 5 | 2019-09| 13256|
| 97 | 5 | 2019-09| 13256|
| 98 | 5 | 2019-09| 13256|
| 95 | 5 | 2019-09| 25689|
| 96 | 5 | 2019-09| 25689|
| 97 | 5 | 2019-09| 25689|
| 98 | 5 | 2019-09| 25689|
|---------------------------------------------|
I tried sql query as
SELECT month,
COUNT(CASE
WHEN `response_key` IN (95,96,97,98) and `response_val`='5'
THEN 1 ELSE NULL END) AS 'yes',
COUNT(CASE
WHEN `response_key` IN (81,82,83,84,85,86,87) and `response_val`='4'
THEN 1 ELSE NULL END) AS 'no'
FROM audit WHERE group by month
but it return the count when all the response_key have value 4.
I expect result as
| count | month |
-----------------
| 1 |2019-09|
Maybe the following will get you what you want:
SELECT `month`,
COUNT(CASE response_val WHEN 4 THEN 1 END) AS `yes`,
COUNT(CASE response_val WHEN 5 THEN 1 END) AS `no`
FROM audit
WHERE response_key between 95 AND 98
GROUP BY `month`
I simplified your case statements and move the response_key condition into the where-clause (the ELSE part in a CASE statement is only necessary if you want to return a value other than null).
You can find a working demo here: https://rextester.com/PCN24609
The resulting counts from this query are:
month yes no
2019-09 1 11
Well, you can swap the order of your output columns as you like, and if you are only interested in the response_val=4 counts do it like here:
SELECT COUNT(CASE response_val WHEN 4 THEN 1 END) AS `count`,
`month`
FROM audit
WHERE response_key between 95 AND 98
GROUP BY `month`
demo: https://rextester.com/JEV40175
result:
| count | month |
-------------------
| 1 | 2019-09 |
Edit:
"Return a count when response_val=4 is found between response_key 95
to 98 corresponding to each uhid and group by month"
This would imply that you simply group over uhid too, like shown here:
SELECT COUNT(CASE response_val WHEN 4 THEN 1 END) AS `count`,
`month`,uhid
FROM audit
WHERE response_key between 95 AND 98
GROUP BY `month`,uhid;
-- results:
count month uhid
0 2019-09 13256
2 2019-09 14569
1 2019-09 25689
Or, if you want all uhid-counts in one row per month:
SELECT `month`,
COUNT(CASE uhid WHEN 14569 THEN 1 END) AS `cnt_14569`,
COUNT(CASE uhid WHEN 13256 THEN 1 END) AS `cnt_13256`,
COUNT(CASE uhid WHEN 25689 THEN 1 END) AS `cnt_25689`
FROM audit
WHERE response_key between 95 AND 98 AND response_val=4
GROUP BY `month`,uhid
-- results:
month cnt_14569 cnt_13256 cnt_25689
2019-09 2 0 0
2019-09 0 0 1
demo: https://rextester.com/WXQ73318
2. Edit: Following your latest comment, the following might fulfil your requirements
("list the number of distinct users with response_val=4 for each month"):
SELECT COUNT(`count`) usrcnt, `month` FROM (
SELECT COUNT(CASE response_val WHEN 4 THEN 1 END) AS `count`,`month`,uhid
FROM audit WHERE response_key between 95 AND 98
GROUP BY `month`,uhid
) tbl WHERE `count`>0 GROUP BY `month`;
-- or, even shorter:
SELECT COUNT(DISTINCT CASE response_val WHEN 4 THEN uhid END) AS `count`,`month`
FROM audit WHERE response_key between 95 AND 98
GROUP BY `month`;
-- same result from both queries:
usrcnt month
2 2019-09
demo: https://rextester.com/UGN73913
The latest queries are based on these table values:
response_key response_val month uhid
95 4 2019-09 14569
96 4 2019-09 14569
97 5 2019-09 14569
98 5 2019-09 14569
95 5 2019-09 13256
96 5 2019-09 13256
97 5 2019-09 13256
98 5 2019-09 13256
95 5 2019-09 25689
96 4 2019-09 25689
97 5 2019-09 25689
98 5 2019-09 25689

Select daily amount of specific values from same column [duplicate]

This question already has answers here:
need to return two sets of data with two different where clauses
(2 answers)
Closed 8 years ago.
I'm building a feedback tool, and I have a feedback table that the following structure:
ID | Satisfaction | Timestamp
--------------------------------------------
1 0 2014-01-01 00:00:00
2 5 2014-01-01 00:00:00
3 10 2014-01-02 00:00:00
4 5 2014-01-02 00:00:00
5 10 2014-01-03 00:00:00
6 0 2014-01-03 00:00:00
7 10 2014-01-03 00:00:00
8 5 2014-01-04 00:00:00
9 5 2014-01-04 00:00:00
How can I get a daily count of the number of each "satisfaction" value?
For example:
Date | 0's | 5's | 10's
--------------------------------------
2014-01-01 | 1 | 1 | 0
2014-01-02 | 0 | 1 | 1
2014-01-03 | 1 | 0 | 2
2014-01-04 | 0 | 2 | 0
I'd imagine it involves a GROUP BY timestamp, but I'm not sure how to select
The simplest way to pivot this data in MySQL:
select date(timestamp),
sum(satisfaction = 0) as zeroes,
sum(satisfaction = 5) as fives,
sum(satisfaction = 10) as tens
from feedback
group by date(timestamp);
Solved! I was able to grab the counts of the individual values using a combination of sum() and case statements.
SELECT
DATE(timestamp),
IFNULL(sum(case when satisfaction = 0 then 1 end), 0) as 'unhappy',
IFNULL(sum(case when satisfaction = 5 then 1 end), 0) as 'neutral',
IFNULL(sum(case when satisfaction = 10 then 1 end), 0) as 'happy'
FROM feedback
GROUP BY DATE(timestamp)

SQL consolidate data and turn them into columns

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`