Simplified the following question I got from a coding challenge...
I have a table grades like:
year sex person mark
2000 M Mark 70
2010 F Alyssa 23
2020 M Robert 54
I want to select the people per year for both sexes that have the highest marks.
My Attempt:
SELECT
year,
MAX(CASE
WHEN sex = ‘F’ THEN person
ELSE ‘’ END) AS person_f,
MAX(CASE
WHEN sex = ‘M’ THEN person
ELSE ‘’ END) AS person_m
FROM (
SELECT
year,
sex,
person,
**mark
FROM grades
WHERE mark IN (
SELECT MAX(mark) AS mark
FROM grades
GROUP BY year, sex)
**) AS t
WHERE x = 1
GROUP BY 1
ORDER BY 1
I modified everything within the ** ** but the rest of the code was pre-populated. The code seemed right to me, but somehow only passed 2/4 test cases, and there were no tiebreaker records.
Also, I omitted the WHERE x = 1 line, but the correct solution apparently needs that. (yes, x isn't a column in any table)
Is there a more elegant/efficient way to solve this?
Can't seem to figure it out, and it's really bugging me.
First you need to use single quotes for Strings
The Problem of your query, is the subquery for your marks, you select a bunch of highest marks without associating them to the year, and gender
MySql allows you to IN clause with multiple columns.
SELECT
year,
MAX(CASE
WHEN sex = 'F' THEN person
ELSE '' END) AS person_f,
MAX(CASE
WHEN sex = 'M' THEN person
ELSE '' END) AS person_m
FROM (
SELECT
year,
sex,
person,
mark
FROM grades
WHERE (year,sex,mark) IN (
SELECT year, sex,MAX(mark) AS mark
FROM grades
GROUP BY year, sex)
) AS t
GROUP BY 1
ORDER BY 1
| year | person\_f | person\_m |
|-----:|:----------|:----------|
| 2000 | | Mark |
| 2010 | Alyssa | |
| 2020 | | Robert |
fiddle
I believe this approach incorporates the WHERE x = 1 clause as well.
SELECT
year,
MAX(CASE
WHEN sex = 'F' THEN person
ELSE '' END) AS person_f,
MAX(CASE
WHEN sex = 'M' THEN person
ELSE '' END) AS person_m
FROM (
SELECT
year,
sex,
person,
RANK() OVER (PARTITION BY year, sex ORDER BY mark DESC) AS x
FROM grades)
WHERE x = 1
GROUP BY 1
ORDER BY 1
You can use rank. I added info to the table so you could see what happens in different scenarios including a tie.
select year
,sex
,person
,mark
from (
select *
,rank() over(partition by year, sex order by mark desc) as rnk
from t
) t
where rnk = 1
order by year, sex
year
sex
person
mark
2000
F
Alyssa
23
2000
M
Mark
70
2000
M
Danny
70
2010
F
Alma
100
2010
M
Dudu
47
2020
F
Noga
98
2020
M
Moshe
56
Fiddle
so i have this table:
DATE
NAME
REFERENCE
ITEM_NUMBER
TOTAL
03/06/2021
XYC
SAFGASZXFEW
Z1-100-0006
102
03/06/2021
XYC
SAFGASZXFEW
Z1-100-0002
200
03/06/2021
XYC
SAFGASZXFEW
A2-50-0003
329
03/06/2021
XYC
SAFGASZXFEW
A2-50-0007
431
03/07/2021
ZBA
DSHDRFBVDRF
RV-100-0001
653
03/07/2021
ZBA
DSHDRFBVDRF
RV-100-0004
222
03/07/2021
ZBA
DSHDRFBVDRF
A2-50-0002
643
and i tried to transpose the item_number table with this query:
SUM(CASE WHEN ITEM_NUMBER like '%-100-%' THEN CAST(TOTAL AS INT)END) 100_1ST,
SUM(CASE WHEN ITEM_NUMBER like '%-100-%' AND (**ITEM_NUMBER<>ITEM_NUMBER**) THEN CAST(TOTAL AS INT)END) 100_2ND,
SUM(CASE WHEN ITEM_NUMBER like '%-50-%' THEN CAST(TOTAL AS INT)END) 50_1ST,
SUM(CASE WHEN ITEM_NUMBER like '%-50-%' AND (**ITEM_NUMBER<>ITEM_NUMBER**) THEN CAST(TOTAL AS INT)END) 50_2ND,
And i get the result:
DATE
NAME
REFERENCE
100_1ST
100_2ND
50_1ST
50_2ND
03/06/2021
XYC
SAFGASZXFEW
202
NULL
760
NULL
03/07/2021
ZBA
DSHDRFBVDRF
875
NULL
643
NULL
And the result i expected is like this:
DATE
NAME
REFERENCE
100_1ST
100_2ND
50_1ST
50_2ND
03/06/2021
XYC
SAFGASZXFEW
100
200
329
431
03/07/2021
ZBA
DSHDRFBVDRF
653
222
643
NULL
I know that my query is wrong on the conditional statement on 100_2ND and 50_2ND, i still don't know on how to compare between 2 values within one column for example (Z1-100-0006 & Z1-100-0002) which are has the same '%-100-%' attribute but only distinguished by their last digit number, so i can put the TOTAL values into different columns. Could anyone help me?
I think you want row_number(). It is unclear how you are determining the first and second item, but something like this:
SELECT date,
SUM(CASE WHEN ITEM_NUMBER LIKE '%-100-%' AND seqnum = 1 THEN TOTAL END) as 100_1ST,
SUM(CASE WHEN ITEM_NUMBER LIKE '%-100-%' AND seqnum = 2 THEN TOTAL END) as 100_2ND,
SUM(CASE WHEN ITEM_NUMBER LIKE '%-50-%' AND seqnum = 1 THEN TOTAL END) as 50_1ST,
SUM(CASE WHEN ITEM_NUMBER LIKE '%-50-%' AND seqnum = 2 THEN TOTAL END) as 50_2ND
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY SUBSTRING_INDEX(SUBSTRING_INDEX(item_number, '-', 2), '-', -1)
ORDER BY item_number
) as seqnum
FROM t
) t
GROUP BY date;
I have this SQL query with substring_index and group_concat but the results I get does not give the right location of the values because the values does not exist.
I need to add a null or zero value in order to have the right location of the values in the sql result.
In the table there are three lid (1, 2, 3). The lid should be the basis count of the P's (P1, P2, P3) for the substring_index.
This is the table:
lid class_id class total
----- ------- ----- -----
1 73 Leader 10000
1 77 Consultant 8000
1 83 Coordinator 6000
2 73 Leader 20000
2 76 Staff 8000
2 77 Consultant 10000
3 73 Leader 30000
3 78 Team Leader 8000
This is the SQL query I used to group_concat for the totals and substring_index to separate the grouped values with their each column (P1, P2, P3)
SELECT *, GROUP_CONCAT(lid) as lids, GROUP_CONCAT(pyear) as pyears,
COUNT(DISTINCT lib_id) AS total_count,
CASE WHEN COUNT(*)>=1 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(if(total is null,0,total) ORDER BY lid ASC SEPARATOR ' '),' ',1),' ',-1) END AS P1,
CASE WHEN COUNT(*)>=2 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(if(total is null,0,total) ORDER BY lid ASC SEPARATOR ' '),' ',2),' ',-1) END AS P2,
CASE WHEN COUNT(*)>=3 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(if(total is null,0,total) ORDER BY lid ASC SEPARATOR ' '),' ',3),' ',-1) END AS P3
FROM (
SELECT * FROM view_items WHERE lid='1'
UNION
SELECT * FROM view_items WHERE lid='2'
UNION
SELECT * FROM view_items WHERE lid='3'
) AS AZ GROUP BY class_id
This is the result of the above query:
class id class lids P1 P2 P3
--------- ----- ----- ---- ---- ----
73 Leader 1,2,3 10000 20000 30000
76 Staff 2 8000
77 Consultant 1,2 8000 10000
78 Team Leader 3 8000
83 Coordinator 1 6000
The lids should always have three count even though the record does not exists, a zero or null value should be added. How to do the adding of null value?
This is the expected result I need.
class id class lids P1 P2 P3
--------- ----- ----- ---- ---- ----
73 Leader 1,2,3 10000 20000 30000
76 Staff 0,2,0 0 8000 0
77 Consultant 1,2,0 8000 10000 0
78 Team Leader 0,0,3 0 0 8000
83 Coordinator 1,0,0 6000 0 0
To get 0 values where no lid is present in the table, you need to generate a list of all lid values for all class_id values, which you can do with a CROSS JOIN of two SELECT DISTINCT queries (one for lid, one for class_id). This can then be LEFT JOINed to the table, to get the required total value for each P group using conditional aggregation:
SELECT c.class_id,
MAX(v.class),
GROUP_CONCAT(COALESCE(v.lid, 0) ORDER BY l.lid) AS lids,
MAX(CASE WHEN v.lid=1 THEN total ELSE 0 END) AS P1,
MAX(CASE WHEN v.lid=2 THEN total ELSE 0 END) AS P2,
MAX(CASE WHEN v.lid=3 THEN total ELSE 0 END) AS P3
FROM (SELECT DISTINCT lid FROM view_items) l
CROSS JOIN (SELECT DISTINCT class_id FROM view_items) c
LEFT JOIN view_items v ON v.lid = l.lid AND v.class_id = c.class_id
GROUP BY c.class_id
Output:
class_id class lids P1 P2 P3
73 Leader 1,2,3 10000 20000 30000
76 Staff 0,2,0 0 8000 0
77 Consultant 1,2,0 8000 10000 0
78 Team Leader 0,0,3 0 0 8000
83 Coordinator 1,0,0 6000 0 0
Demo on dbfiddle
Use else 0 in case expression
SELECT *, GROUP_CONCAT(lid) as lids, GROUP_CONCAT(pyear) as pyears,
COUNT(DISTINCT lib_id) AS total_count,
CASE WHEN COUNT(*)>=1 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(if(total is null,0,total) ORDER BY lid ASC SEPARATOR ' '),' ',1),' ',-1) else 0 END AS P1,
CASE WHEN COUNT(*)>=2 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(if(total is null,0,total) ORDER BY lid ASC SEPARATOR ' '),' ',2),' ',-1) else 0 END AS P2,
CASE WHEN COUNT(*)>=3 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(if(total is null,0,total) ORDER BY lid ASC SEPARATOR ' '),' ',3),' ',-1) else 0 END AS P3
FROM (
SELECT * FROM view_items WHERE lid='1'
UNION
SELECT * FROM view_items WHERE lid='2'
UNION
SELECT * FROM view_items WHERE lid='3'
) AS AZ GROUP BY class_id
I think the problem is, that you say count >= 1, but count >= 3 is also count >=1 so your code never reaches this line. You have to say,
CASE WHEN COUNT() >=1 AND COUNT () < 2...
CASE WHEN COUNT() >=2 AND COUNT () < 3...
I don't think GROUP_CONCAT() is the right approach for what you want. Try this:
SELECT vi.class_id, vi.class,
COUNT(DISTINCT vi.lib_id) AS total_count,
CONCAT_WS(',',
MAX(CASE WHEN vi.lid = 1 THEN 1 ELSE 0 END),
MAX(CASE WHEN vi.lid = 2 THEN 1 ELSE 0 END),
MAX(CASE WHEN vi.lid = 3 THEN 1 ELSE 0 END)
) as lids,
SUM(CASE WHEN vi.lid = 1 THEN vi.total ELSE 0 END) as total_1,
SUM(CASE WHEN vi.lid = 2 THEN vi.total ELSE 0 END) as total_2,
SUM(CASE WHEN vi.lid = 3 THEN vi.total ELSE 0 END) as total_3
FROM view_items vi
WHERE vi.lid IN (1, 2, 3)
GROUP BY vi.class_id;
Notes:
Your subquery and UNION are not needed. In MySQL, these can actually hurt performance.
I assume that lid is a number, so I've removed the single quotes.
You can use conditional aggregation for each of the totals that you want. Parsing GROUP_CONCAT() is not the right way to do this.
Your question is about the lids. The CONCAT_WS() does what you want -- concatenating either the value (if it appears) or zero if it does not.
I need to create a select statement on a table like this:
id rank name city
34 0 adm TO
44 0 sas BA
44 1 wqe BS
92 0 adm TO
92 1 ter BO
92 2 ter BO
92 3 ter RM
what I want to select is to count the number of rows where rank is > 0 but only for ids that have more than one rank value. For those id that just have one record, thus rank is 0, then I want to pick that record. If I use:
SELECT id, count(rank) as t
FROM mytable
WHERE rank > 0
GROUP BY id
then, for instance, I omit id=34.
My optimal results set would be:
id t
34 1
44 1
92 3
Any hint on how to accomplish this task?
You can use something like the following:
SELECT id,
CASE
WHEN cntNonZero = 0 THEN cntZero
ELSE cntNonZero
END AS t
FROM (
SELECT id,
COUNT(CASE WHEN rank > 0 THEN 1 END) AS cntNonZero,
COUNT(CASE WHEN rank = 0 THEN 1 END) AS cntZero
FROM mytable
GROUP BY id ) s
Demo here
In the below query I'm getting some odd results -- specifically, I'm getting the same counts for both categories (current year and all time) and I can't figure out why that would be. Here's my query:
SELECT eventlist.isid, eventlist.name, COUNT(currentyearcount.arid) AS currentyearcount, COUNT(allreports.arid) AS allreports
FROM eventlist
LEFT JOIN artist_reports currentyearcount ON eventlist.isid = currentyearcount.CompanyID AND currentyearcount.event_year = 2015
JOIN artist_reports allreports ON eventlist.isid = allreports.CompanyID
GROUP BY eventlist.isid
I'm getting results like this:
isid name currentyearcount allreports
1234 Name1 33 33
5678 Name2 105 105
9012 Name3 0 63
I'm not sure why Name3 would show 0 for currentyearcount when really they should all be showing 0 as there aren't any reports for the current year yet.
UPDATE: Here's the correct query thanks to Aleksandar's comments:
SELECT eventlist.isid, eventlist.name, SUM(CASE WHEN artist_reports.arid IS NOT NULL AND artist_reports.event_year = 2015 THEN 1 ELSE 0 END) AS currentyearcount, SUM(CASE WHEN artist_reports.arid IS NOT NULL THEN 1 ELSE 0 END) AS totalcount
FROM eventlist
LEFT JOIN artist_reports ON eventlist.isid = artist_reports.CompanyID
GROUP BY eventlist.isid