Combine multiple MySQL rows into one row - mysql

Here's what I am currently getting from my query:
row_no
prospect
hot
contract
1
null
Joe
null
1
John
null
null
1
null
null
Sam
I Am trying to get:
row_no
prospect
hot
contract
1
John
Joe
Sam
I cannot use concate because I still need separate columns
Here's what I have so far...
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=e7aab240b40e40955bac44f616d7f3d7
Please note: Ultimately, there will be 7 columns, but a varying amount of rows (ie. the prospect column may have 20 records, where the contract column may only have four or five.

You can do it with conditional aggregation:
WITH CTE_tbl AS
(
SELECT lead_type,firstname, last_date,
ROW_NUMBER() OVER(PARTITION BY lead_type order by lead_type Desc) as row_no
FROM leads
)
SELECT row_no,
MAX(case when lead_type = 'prospect' then firstname end) prospect,
MAX(case when lead_type = 'hot' then firstname end) hot,
MAX(case when lead_type = 'contract' then firstname end) contract
FROM CTE_tbl
GROUP BY row_no
ORDER BY row_no
See the demo.
Results:
row_no
prospect
hot
contract
1
John
Joe
Sam
2
Mario
Autumn
null
3
Frank
null
null
4
Mary
null
null
5
Steve
null
null

Related

Show Multiple Result Tuples into One Tuple in Different Different Columns

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;

Group by, converting rows to columns with no aggregate

I need to group the table during my query, and up until know I was doing that after querying - with the code as my group is quite complicated. But with the new data it appears to take minutes, and I'm thinking is there better way.
My current query results in this:
FKId | Name | A | B | C
1 Alpha 2 3 2
1 Beta 2 5 7
2 Alpha 8 1 10
2 Beta 7 -5 6
2 Gamma 1 2 3
And I convert it to this:
FKId | Alpha[A] | Alpha[B] | Alpha[C] | Beta[A] | Beta[B] | Beta[C] | Gamma[A] | Gamma[B] | Gamma[C]
1 2 3 2 2 5 7
2 8 1 10 7 -5 6 1 2 3
Is it possible to do with SQL? (and I assume it should be much faster than if I do this with code)
The names can be anything
I have very big number of colums A, B, C (like 20 - 30). The number of result columns can easily go to thousands as average project has about 100 names.
I have like 10-20 columns that I should group by, but doing a single group by FKId is fine - these columns are the same.
We use different SQL DBs, so I cannot use specific functions like PIVOT. I know that we used MySQL, MsSQL and SQLite a lot
We use NHibernate if it makes any difference.
I would also honor the solution done for MySQL if specific functions are used. We use it in 80% and it will already greatly improve the average performance if I could do that at least for MySQL.
Basically, you want to transpose the data. Here is what you can try. It should work across all databases but you need to know the columns A, B, C, etc beforehand:
create table my_table (
fkid integer,
name varchar(10),
a integer,
b integer,
c integer
);
insert into my_table values(1,'alpha',2,3,2)
,(1,'beta',2,5,7)
,(2,'alpha',8,1,10)
,(2,'beta',7,-5,6)
,(2,'gamma',1,2,3);
select fkid
, max(case when name = 'alpha' then a else null end) as alphaa
, max(case when name = 'alpha' then b else null end) as alphab
, max(case when name = 'alpha' then c else null end) as alphac
, max(case when name = 'beta' then a else null end) as betaa
, max(case when name = 'beta' then b else null end) as betab
, max(case when name = 'beta' then c else null end) as betac
, max(case when name = 'gamma' then a else null end) as gammaa
, max(case when name = 'gamma' then b else null end) as gammab
, max(case when name = 'gamma' then c else null end) as gammac
from my_table
group by fkid;

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 join and sub query in sql

I have a table employee having columns id (primary key), *employee_name* and another table called employee_works with columns *employee_id* (foreign key referencing employee.id), *start_date* (datetime), *finish_date* (datetime).
Here are some datas for employee table:
**id** **employee_name**
1 employee A
2 employee B
3 employee C
4 employee D
5 employee E
6 employee F
7 employee G
employee_works table:
1 2010-01-01 00:00:00 NULL
2 2010-01-01 00:00:00 2010-01-10 10:00:00"
2 2010-01-13 00:00:00 2010-01-15 10:00:00"
2 2010-01-31 00:00:00 NULL
4 2010-02-18 00:00:00 2011-01-31 00:00:00"
6 2010-02-18 00:00:00 NULL
NULL value means the employee still works.
I need to get a single query showing the list of persons in employee, if they worked with us, who still works in our company, who left and if possible, for how long they worked with us.
Example:
id employee_name status
1 Employee A Still with us
3 Employee C Never worked
4 Employee D Left
My attempt:
SELECT emp.id,emp.name,
CASE
WHEN occ.finish_date is NULL and occ.start_date is NOT NULL THEN 'Still working'
WHEN occ.finish_date is NULL and occ.start_date is NULL THEN 'Never Worked'
WHEN occ.finish_date is NOT NULL and occ.start_date is NOT NULL THEN 'Left'
END
AS status
FROM employee AS emp
LEFT JOIN employee_works AS occ ON emp.id=occ.employee_id
GROUP BY emp.id, occ.finish_date
I also want to get the total no of days the employees have worked in another column?
The problem is that you have a group by but no aggregations for the definition of status. Mysql does not give you a syntax error. Instead, it gives you a random status:
Try something like this instead:
select id, name,
(CASE WHEN statusint = 3
THEN 'Still working'
WHEN statusint = 1 or statusint is null
THEN 'Never Worked'
WHEN statusint = 2
THEN 'Left'
END) AS status,
days_worked
from (SELECT emp.id, emp.name,
max(CASE WHEN occ.departure_date is NULL and occ.start_date is NOT NULL
THEN 3
WHEN occ.departure_date is NULL and occ.start_date is NULL
THEN 1
WHEN occ.departure_date is NOT NULL and occ.start_date is NOT NULL
THEN 2
END) AS statusint,
sum(datediff(coalesce(departure_date, curdate()), occ.start_date
) as days_worked
FROM employee emp LEFT JOIN
employee_works occ
ON emp.id=occ.employee_id
GROUP BY emp.id, emp.name
) eg
This "feature" of mysql is called hidden columns. Folks who write mysql (and many who use it) think this is a great feature. Many people who use other databases just scratch their heads and wonder why any database would act so strangely.
By the way, you should check if someone who is employeed multiple times gets assigned a new id. If so, your query might need more advanced name matching methods.
Try to simplify your condition.
SELECT a.*,
CASE
WHEN b.employeeID IS NULL THEN 'NEVER WORKED'
WHEN b.finish_date IS NULL THEN 'STILL WORKING'
WHEN DATE(b.finish_date) < CURDATE() THEN 'LEFT'
END as `Status`
FROM employee a
LEFT JOIN employee_works b
on a.id = b.employeeID