Group by, converting rows to columns with no aggregate - mysql

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;

Related

Merge multiple rows in single row in mysql view

I have a mysql view on which 1 column is repeated on 4 rows but the other column has different values. I want to merge all 4 rows into 1 by giving columns custom names.
Example:
I have Long_Desc repeated in 4 rows with different Nutr_Val and Nutr_No. I want to combine all 4 rows so the table will look like
NDB_No | Nutr_No | Nutr_Val | Long_Desc | PROCNT | FAT | CHODCDF | ENERC_KCAL
So I have Nutr_Val adjusted in those last 4 columns.
This could be solved with conditional aggregation :
SELECT
t.nbd_no,
t.nutr_no,
t.nutr_val,
t.long_desc,
MAX(CASE WHEN t.tagname = 'PROCNT' THEN t.fdgrp_cd END) PROCNT,
MAX(CASE WHEN t.tagname = 'FAT' THEN t.fdgrp_cd END) FAT,
MAX(CASE WHEN t.tagname = 'CHODCDF' THEN t.fdgrp_cd END) CHODCDF,
MAX(CASE WHEN t.tagname = 'ENERC_KCAL' THEN t.fdgrp_cd END) ENERC_KCAL
FROM mytable t
GROUP BY
t.nbd_no,
t.nutr_no,
t.nutr_val,
t.long_desc

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

MYSQL Transform Data

First of all, sorry for my bad English :)
I have following example table data:
id name state amount
1 name1 open 2
1 name1 open 3
1 name1 closed 4
2 name2 open 5
2 name2 closed 8
2 name2 closed 4
I want to aggregate amount data for each state (open or closed) and name (name1 or name2)
something like this:
id name open(sum amount) closed(sum amount)
1 name1 5(2+3) 4
2 name2 5 12(8+4)
Can somebody help me?
Apart from the already chosen answer another way of doing this is :
SELECT
id
,name
,SUM( IF( state = 'open' ,amount ,0 ) ) open_total
,SUM( IF( state = 'closed' ,amount ,0 ) ) closed_total
FROM
`data`
WHERE
1
GROUP BY
name
SELECT id, name,
SUM(CASE WHEN state="open" THEN amount else 0 END) AS `open`,
SUM(CASE WHEN state="closed" THEN amount else 0 END) AS `closed`
FROM <yourtablename>
GROUP BY id;
Mind the special quotes in the derived column names, they cannot be replaced by a different type of quotes.

Joining Tables and Pivoting Data with MySQL

Objective 1:
Your sales data is stored in the Purchases table.
Your sales staff wants to see the sales data in a pivoted form, broken down by quarter.
If your Purchases table doesn't have sales data, create some. Be sure the data spans four quarters.
Next, write a query to pivot the data as follows:
Album Q1 Q2 Q3 Q4
OK Computer 2 5 3 7
Sea Change 8 6 2 1
Do not create a separate table or a view. Do not alter any tables.
Save your query as dba1lesson10project1.sql and hand in the project.
This is What I need to do. But, the table it wants me to work with looks like this. And it states in the assignment I cannot alter it at all.
CustomerID DateOfPurchase SongID
1 2007-03-31 3
3 2007-06-30 4
4 2007-09-30 4
5 2007-12-31 5
I know I need to join three tables together so I can group by the title. Which are my Songs, Albums, and Purchases tables.
SELECT Albums.Title FROM Albums
LEFT JOIN Songs
INNER JOIN Purchases
ON Songs.SongID = Purchases.SongID
ON Albums.Title = Purchases.SongID,
SELECT Title,
SUM(CASE WHEN QUARTER(DateOfPurchase) = 1 THEN 1 ELSE 0 END) AS 'Q1',
SUM(CASE WHEN QUARTER(DateOfPurchase) = 2 THEN 1 ELSE 0 END) AS 'Q2',
SUM(CASE WHEN QUARTER(DateOfPurchase) = 3 THEN 1 ELSE 0 END) AS 'Q3',
SUM(CASE WHEN QUARTER(DateOfPurchase) = 4 THEN 1 ELSE 0 END) AS 'Q4'
From Purchases
GROUP BY Title;
I'm kind of at a loss here when it comes to Joining three separate tables then pivoting the data
I've tried the code above in multiple other variants which has failed me past the table joining portion.
Any help would be much appreciated.
My suggestion before attempting to PIVOT the data would be to first, write the query to return the columns that you need, this will involve joining your tables. You didn't provide your table definitions so I am making a few guesses on the structure. If your tables are structured similar to the following:
CREATE TABLE Purchases
(`CustomerID` int, `DateOfPurchase` datetime, `SongID` int)
;
CREATE TABLE Albums
(`AlbumId` int, `Title` varchar(11))
;
CREATE TABLE Songs
(`SongID` int, `AlbumID` int)
;
Then you would SELECT from the tables using a JOIN similar to this code:
select a.title,
p.DateOfPurchase
from albums a
inner join songs s
on a.albumid = s.albumId
inner join purchases p
on s.songid = p.songid
This query will return to you the album Title as well as the DateOfPurchase which you need to pivot the data. Once you have the JOINS worked out, then you can replace the p.DateOfPurchase with your aggregate function and CASE expression and add the GROUP BY clause to get the final result:
select a.title,
SUM(CASE WHEN Quarter(p.DateOfPurchase) = 1 THEN 1 ElSE 0 END) AS Q1,
SUM(CASE WHEN Quarter(p.DateOfPurchase) = 2 THEN 1 ELSE 0 END) AS Q2,
SUM(CASE WHEN Quarter(p.DateOfPurchase) = 3 THEN 1 ELSE 0 END) AS Q3,
SUM(CASE WHEN Quarter(p.DateOfPurchase) = 4 THEN 1 ELSE 0 END) AS Q4
from albums a
inner join songs s
on a.albumid = s.albumId
inner join purchases p
on s.songid = p.songid
group by a.title;
See SQL Fiddle with Demo. This gives a result:
| TITLE | Q1 | Q2 | Q3 | Q4 |
| OK Computer | 1 | 0 | 0 | 0 |
| Sea Change | 0 | 1 | 1 | 0 |

Inserting columns with a case statement

I have an issue where I would like to look for statements in a single column and each time I find them insert a new column into the mix. Im using a mysql database.
For example say I have data like this
Class Sub
-----------------
1 math
1 tech
2 math
2 english
3 math
I would like the data to be output like this:
Class math tech english
---------------------------
1 Y Y N
2 Y N Y
3 Y N N
I am trying to use CASE statements to find the values in the column, but the problem is that it will only return one result in the column for the value it finds, and i end up getting the same class repeated with a case statement for each column. Combining the case statements wont work as that still gives me a single column.
You need to wrap the CASE expression in an aggregate and add a GROUP BY. In this case MAX will work as alphabetically Y comes after N
(SQL Fiddle Demo)
SELECT class,
MAX(CASE
WHEN ( community_id = 'Math' ) THEN 'Y'
ELSE 'N'
END) AS Math,
MAX (CASE
WHEN ( community_id = 'tech' ) THEN 'Y'
ELSE 'N'
END) AS tech,
MAX (CASE
WHEN ( community_id = 'english' ) THEN 'Y'
ELSE 'N'
END) AS english
FROM x_class_community
GROUP BY class
You need to be using PIVOT functionality. You haven't specified your DBMS, however here is a link for SQL Server: Using PIVOT and UNPIVOT
If you are using SQL Server then you can use the PIVOT function:
select class,
isnull(math, 'N') math,
isnull(tech, 'N') tech,
isnull(english, 'N') english
from
(
SELECT class, community_id, 'Y' as flag
FROM x_class_community
) src
pivot
(
max(flag)
for community_id in (math, tech, english)
) piv
See SQL Fiddle with Demo
The result is:
| CLASS | MATH | TECH | ENGLISH |
---------------------------------
| 1 | Y | Y | N |
| 2 | Y | N | Y |
| 3 | Y | N | N |