Show Multiple Result Tuples into One Tuple in Different Different Columns - mysql

Original Database Schema--
id username date Grade
----------------------------------------
1 Haryana 15/02/2001 A
2 Haryana 20/02/2008 B+
3 Punjab 15/05/2001 A
4 Haryana 25/02/2008 A
5 Punjab 25/02/2008 B+
Required Output With Selection Query--
username date Grade username date Grade username date Grade
-------------------------------------------------------------------------------------------
haryana 15/02/2001 A Haryana 20/02/2008 B+ Haryana 25/02/2008 A
Punjab 15/05/2001 A Punjab 25/02/2008 B+
Note->
Basically I want column name "username" to be used for Making Multiple rows of user "Haryana" to be into Single Tuple With Multiple Rows with Multiple Columns..

Relational database software is terrible at creating result sets (tables) in which the number or names of columns is dependent on the data in other tables. That's what you are asking for. It's difficult code to write and practically impossible to maintain. Even if you get it to work in SQL, it will be a miserable hackā„¢. The person who works on it after you will curse you.
This sort of thing, often called a pivot, is best done in database client software.
That being said, perhaps you can try the GROUP_CONCAT() aggregation function.
Here's a possible query.
SELECT GROUP_CONCAT(CONCAT_WS(' ', username,date, Grade) ORDER BY date SEPARATOR ' | ')
FROM table
GROUP BY username
It will give a result set like this:
Haryana 15/02/2001 A | Haryana 20/02/2008 B+ | Haryana 25/02/2008 A
Punjab 15/05/2001 A | Punjab 25/02/2008 B+
A small amount of string processing in php or any other language can transform this kind of result to present the illusion that it is in a table.

I'm not seriously advocating this as a solution (see Ollie's first sentence, or my comment above). So, just for fun...
SELECT username
, MAX(CASE WHEN rank = 1 THEN date END) date1
, MAX(CASE WHEN rank = 1 THEN grade END) grade1
, MAX(CASE WHEN rank = 2 THEN date END) date2
, MAX(CASE WHEN rank = 2 THEN grade END) grade2
, MAX(CASE WHEN rank = 3 THEN date END) date3
, MAX(CASE WHEN rank = 3 THEN grade END) grade3
FROM
(
SELECT x.*
, CASE WHEN #prev = username THEN #i:=#i+1 ELSE #i:=1 END rank
, #prev:=username
FROM my_table x
,(SELECT #prev:=null,#i:=0) vars
ORDER
BY username
, date
) a
GROUP
BY username;

Related

How Do I convert this table from row to column?

I have a table that contains id and country name, and I need to convert them so the id with more than 1 country will display in 1 row.I have been searching in this forum for over an hour and found nothing.
I tried if using the pivot function can help me to achieve the result i wanted, but I feel like using pivot does not work on my case here.
This is a mini version of the table I have. The number of distinct value in the field "country" will be over 100, so I can just say something like when county = '..' as this will be to repetitive.
enter code here
+----+--------+
| id | country|
+----+--------+
| 1 | US |
| 1 | UK |
| 2 | JP |
+----+--------+
Desired outcome I am looking for:
enter code here
+----+-----------+-----------+
| id | country_1 | country_2 |
+----+-----------+-----------+
| 1 | US | UK |
| 2 | JP | null |
+----+-----------+-----------+
I found this question which is similar but it is the opposite of what I am trying to achieve.
MySQL statement to pivot table without using pivot function or a union
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
update:
Thank you so much for all of your helps. I may not have used the queries of yours to solve my problem - as of the fact that the syntax is a bit diff running in snowflake. However, I got the insights I need from all of you.
here is my solution:
enter code here
select t1.id,
max(iff(t1.row_number = 1, t1.country ,null)) as country_1,
max(iff(t1.row_number = 2, t1.country ,null)) as country_2,
max(iff(t1.row_number = 3, t1.country, null)) as country_3
from
(
select id, country, row_number() over (partition by id order by id ) as
row_number
from table
) t1
group by t1.id
Whereas you could do it with "pivoting", what will happen when you have 3 countries? Or 4? Or 17?
May I suggest this:
SELECT id,
GROUP_CONCAT(country)
FROM tbl
GROUP BY id;
You will get something like:
1 US,UK
2 JP
use aggregation
select id, max(case when id=1 then country end ) as country_1,
max(case when id=2 then country end ) as country_2
from tbale group by id
As you comment on #Rick answer you have max 3 country for each id then you can use this
select
id,
(select country from test where test.id=t.id limit 0,1)as country_1,
(select country from test where test.id=t.id limit 1,1)as country_2,
(select country from test where test.id=t.id limit 2,1)as country_3
from test as t
group by id;
DEMO
You can try this following script with RowNumber generated per id. As you confirmed there are maximum 3 country per id, we can easily generate your desired result set by handling RowNumber 1,2 & 3
SELECT ID,
MAX(CASE WHEN RowNumber = 1 THEN country ELSE NULL END) Country_1,
MAX(CASE WHEN RowNumber = 2 THEN country ELSE NULL END) Country_2,
MAX(CASE WHEN RowNumber = 3 THEN country ELSE NULL END) Country_3
FROM
(
SELECT id,
country,
#row_num :=IF(#prev_value = concat_ws('',id),#row_num+1,1)AS RowNumber
,#prev_value := concat_ws('',id)
FROM tbale
ORDER BY id
)A
GROUP BY id
There's no "dynamic" PIVOT in SQL. You need to specify the list of columns when writing the query. Your options are:
If you know the number of columns in advance, then #ZaynulAbadinTuhin solution is the easier. It seems, however, this is not your case.
If you don't know the number of columns in advance and you want them all concatenated in a single column, then #Rick James solution is the best.
Otherwise, you can still use some kind of dynamic SQL in your app or in a stored procedure that will build the SQL query at runtime, based on the existing values of the table. But this solution would require much more programming. It's not a single/simple SQL query anymore. See Rick James's Pivoting in MySQL stored procedure.

Group By, SUM and COUNT

Looking for a solution to group and count. I have a working SQL which counts open records per value:
SELECT tech_query.assigned_to_group, COUNT(tech_query.assigned_to_group) AS qty
FROM tech_query
WHERE tech_query.status <> 9
AND tech_query.status <> 3
GROUP BY tech_query.assigned_to_group
ORDER BY tech_query.assigned_to_group
The outcome:
Name: | qty
-----------------------
ME-MS | 5
MU-TA-AAA | 4
MU-TA-BBB | 2
MU-TA-CCC | 3
Now I also like to combine the Name data which begins with MU%, sum the data of this value MU% and get an outcome as follows:
Name: | qty
-----------------------
ME-MS | 5
MU | 9
Curious if somebody can help :-)
You need contional for mu like. try this query:
SELECT (case when tech_query.assigned_to_group like "MU%" then "MU"
else tech_query.assigned_to_group end) as Name,
COUNT(tech_query.assigned_to_group) AS qty
FROM tech_query
WHERE tech_query.status <> 9
AND tech_query.status <> 3
GROUP BY name
ORDER BY name
Use a derived table to extract MU part when required:
SELECT name, COUNT(*) AS qty
FROM
(select case when tech_query.assigned_to_group like 'MU%' then 'MU'
else tech_query.assigned_to_group end as name
from tech_query
WHERE tech_query.status <> 9
AND tech_query.status <> 3) dt
GROUP BY name
ORDER BY name
Note: When reading the other answer (Balinti's), I realize MySQL perhaps wants double quotes for string literals, instead of ANSI SQL's single quotes? So maybe you need to replace 'MU%' then 'MU' with "MU%" then "MU"?

MySQL return min value but not null

I have a table where there are columns students and grade obtained(A-F). A student can appear for test more than once. Sometimes students register but do not appear for test so the grade is not entered but student record entry is made.
I want to get best grade of each student. When I do min(grade) if there is any record with null, null gets selected instead of 'A-F' which indicate proper results. I want to get min of grade if grade exists or null if there are no grades.
SELECT `name`,min(grade) FROM `scores` group by `name`
Id | Name | Grade
1 | 1 | B
2 | 1 |
3 | 1 | A
4 | 2 | C
5 | 2 | D
For name 1 it is fetching second record not the third one having 'A'.
As per the conversations in the comments, the easiest solution may be to convert your empty strings to null, and let the builtin min function do the heavy lifting:
ALTER TABLE scores MODIFY grade VARCHAR(1) NULL;
UPDATE scores
SET grade = null
WHERE grade = '';
SELECT name, MIN(grade)
FROM scores
GROUP BY name
If this is not possible, a dirty trick you could use is to have a case expression convert the empty string to a something you know will come after F:
SELECT name,
MIN(CASE grade WHEN '' THEN 'DID NOT PARTICIPATE' ELSE grade END)
FROM scores
GROUP BY name
And if you really need the empty string back, you can have another case expression around the min:
SELECT name, CASE best_grade WHEN 'HHH' THEN '' ELSE best_grade END
FROM (SELECT name,
MIN(CASE grade WHEN '' THEN 'HHH' ELSE grade END) AS
best_grade
FROM scores
GROUP BY name) t
Change your query slightly to -
SELECT `name`,min(grade) FROM `scores` WHERE grade <> "" group by `name`
If the name has a grade/s assigned to it then the lowest will be returned else the resultset will be null

Denormalizing result set

I'm trying to denormalize a result set so that I have one record per ID. This is a list of patients with multiple comorbidities. The data currently looks like this:
ID Disease
1 Asthma
1 Cancer
1 Anemia
2 Asthma
2 HBP
And I need it to look like this:
ID Disease1 Disease2 Disease3
1 Asthma Cancer Anemia
2 Asthma HBP <NULL or Blank>
I researched Pivot, but all of the examples I saw used aggregate functions which wouldn't apply.
I have added the row_number function and tried self joins like the following:
case when rownum = 1 then Disease else NULL end Disease1,
case when rownum = 2 then Disease else NULL end Disease2,
case when rownum = 3 then Disease else NULL end Disease3
However, this produces the following:
ID Disease1 Disease2 Disease3
1 Asthma NULL NULL
1 NULL Cancer NULL
1 NULL NULL Anemia
2 Asthma NULL NULL
2 NULL HBP NULL
Any suggestions would be greatly appreciated. I would really like to find a way to accomplish this without having a monstrous block of code (which is what I ended up with when trying to do it). Thanks!
You can use MAX to compact the rows:
select
id,
max(case when rownum = 1 then Disease end) Disease1,
max(case when rownum = 2 then Disease end) Disease2,
max(case when rownum = 3 then Disease end) Disease3
from (
select
id,
disease,
rownum = ROW_NUMBER() OVER (partition by id order by id)
from your_table
) sub
group by id
Sample SQL Fiddle

SQL - SELECT Merge rows if multiple results are found

I have an SQL table setup similar to this:
name | subject |
-------+----------+
Harry | Painting |
Sandra | Soccer |
Sandra | English |
How can I write a select statement that merges the rows if they have multiple subject, so it would output a result like this:
name | subject 1 | subject 2 |
-------+------------+------------+
Harry | Painting | |
Sandra | Soccer | English |
You shouldn't. The best approach is to join the tables so you have:
Harry, Painting
Sandra, Soccer
Sandra, English
And then process these rows in your scripting language (PHP etc) to turn it into the hierarchical data structure you desire
In your example above, what would happen when there's 3 subjects per person, 10, 100, etc.
SQL only really works with two dimensional data. For three dimensions you either need to pre/post process as i've suggested, or move to something like NoSQL mongoDB that deals with structured objects instead of table rows.
Since you mentioned that the maximum number of subjects is only 2, you can therefore, generate a sequential number for each name and used that to pivot the columns.
SELECT Name,
MAX(CASE WHEN rn = 1 THEN Subject END) Subject1,
MAX(CASE WHEN rn = 2 THEN Subject END) Subject2
FROM
(
SELECT A.name,
A.subject,
(
SELECT COUNT(*)
FROM tableName c
WHERE c.name = a.name AND
c.subject <= a.subject) AS rn
FROM TableName a
) aa
GROUP BY Name
SQLFiddle Demo
The above is an SQL way.
You need a PIVOT routine. Serach for this in the engine of your choice.
Some RDBMSs have this built in. There is an alternative using a CASE statement in the SELECT clause, for which there are many blog posts out there.
You can accomplish this in one statement using a combination of substring_index() and group_concat() like this (SQLfiddle)
SELECT DISTINCT s.name,
substring_index(p.subject_list, ',', 1) AS "subject_1",
IF(instr(p.subject_list, ','),
substring_index(p.subject_list, ',', -1),
NULL
) AS "subject_2"
FROM subjects s
JOIN (SELECT name, GROUP_CONCAT(subject) AS "subject_list"
FROM subjects
GROUP BY name
) p on p.name = s.name
;