SQL Server: Rows to Columns with Case - sql

I have a table with 2 columns:
CREATE TABLE Prop_Cl
(
Id int,
ClId int
);
INSERT INTO Prop_Cl
(Id, ClId)
VALUES
(1, 1111111),
(1, 1111112),
(1, 1111113),
(2, 2222221),
(3, 3333331),
(3, 3333332);
ID CLID
1 1111111
1 1111112
1 1111113
2 2222221
3 3333331
3 3333332
I'm trying to show this table in that way:
ID CLIENT 1 CLIENT 2 CLIENT 3 CLIENT 4
1 1111111 1111112 1111113 0
2 2222221 0 0 0
3 3333331 3333332 0 0
with this statement:
SELECT p.Id,
CASE WHEN (ROW_NUMBER() OVER(PARTITION BY p.Id ORDER BY p.Id)) = 1 THEN p.ClId ELSE 0 END AS 'Client 1',
CASE WHEN (ROW_NUMBER() OVER(PARTITION BY p.Id ORDER BY p.Id)) = 2 THEN p.ClId ELSE 0 END AS 'Client 2',
CASE WHEN (ROW_NUMBER() OVER(PARTITION BY p.Id ORDER BY p.Id)) = 3 THEN p.ClId ELSE 0 END AS 'Client 3',
CASE WHEN (ROW_NUMBER() OVER(PARTITION BY p.Id ORDER BY p.Id)) = 4 THEN p.ClId ELSE 0 END AS 'Client 4'
FROM Prop_Cl p
But I get this result:
ID CLIENT 1 CLIENT 2 CLIENT 3 CLIENT 4
1 1111111 0 0 0
1 0 1111112 0 0
1 0 0 1111113 0
2 2222221 0 0 0
3 3333331 0 0 0
3 0 3333332 0 0
I can't use PIVOT function because of my Sql Server realisation.
There are maximum 4 clients in each ID.
Any ideas?
SQL Fiddle

I would change the syntax slightly to use an aggregate function and a subquery similar to:
select id,
max(case when seq = 1 then ClId else 0 end) Client1,
max(case when seq = 2 then ClId else 0 end) Client2,
max(case when seq = 3 then ClId else 0 end) Client3,
max(case when seq = 4 then ClId else 0 end) Client4
from
(
select Id, ClId,
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Id) seq
from Prop_Cl
) s
group by id;
See SQL Fiddle with Demo

Related

Get custom product selection upon dynamic ID in SQL

I have below table structure and I would like to obtain the result in the following form:
First, this is my item table output:
orderID code action id level description Price solvedChoice
--------------------------------------------------------------------------
321 622 RECIPE 0 0 SPICM1 15.5 NULL
321 10 RECIPE 0 1 SPICKN 17 NULL
321 7091 RECIPE 0 1 RFRY 8.5 NULL
321 521 CHOICE 0 1 R-COKE 7.5 10000003
321 612 RECIPE 1 0 BIGTM1 20.5 NULL
321 13 RECIPE 1 1 BTASTY 21 NULL
321 7091 RECIPE 1 1 RFRY 8.5 NULL
321 522 CHOICE 1 1 R-FANT 7.5 10000003
321 608 RECIPE 2 0 ROYAL1 18.5 NULL
321 11 RECIPE 2 1 MCROYA 18 NULL
321 7091 RECIPE 2 1 RFRY 8.5 NULL
321 411 CHOICE 2 1 ARWA 7.5 10000003
321 612 RECIPE 3 0 BIGTM1 20.5 NULL
321 13 RECIPE 3 1 BTASTY 21 NULL
321 7091 RECIPE 3 1 RFRY 8.5 NULL
321 524 CHOICE 3 1 R-SPRT 7.5 10000003
I want to get what select under each meal, for example id = 0, represent one meal with their sub-level (components) and we can see the choice made was R-Coke while for id =1 , the choice made is R-FANT.
The output should be like this:
R-COKE R-FANT ARWA R-SPRT
--------------------------------------
SPICM1 1 0 0 0
BIGTM1 0 1 0 1
ROYAL1 0 0 1 0
This looks like two levels of aggregation to me:
select col1,
sum(r_coke) as r_coke,
sum(r_fant) as r_fant,
sum(arwa) as arwa,
sum(r_sprt) as r_sprt
from (select max(case when level = 0 then description end) as col1,
sum(case when description = 'R-COKE' then 1 else 0 end) as r_coke,
sum(case when description = 'R-FANT' then 1 else 0 end) as r_fant,
sum(case when description = 'ARWA' then 1 else 0 end) as arwa,
sum(case when description = 'R-SPRT' then 1 else 0 end) as r_sprt
from t
group by id
) x
group by col1;
Or, perhaps more simply, using window functions:
select col1,
sum(case when description = 'R-COKE' then 1 else 0 end) as r_coke,
sum(case when description = 'R-FANT' then 1 else 0 end) as r_fant,
sum(case when description = 'ARWA' then 1 else 0 end) as arwa,
sum(case when description = 'R-SPRT' then 1 else 0 end) as r_sprt
from (select t.*,
max(case when level = 0 then description end) over (partition by id) as col1
from t
) t
group by col1;
Just following input and output provided
select orderID, x, [R-COKE], [R-FANT], [ARWA], [R-SPRT]
from (
select orderID, id
, max(case when level = 0 then description end) x
, max(case when level = 1 and solvedChoice is not null then description end) y
from mytable
group by orderID, id
) t
pivot (count(id) for y in ([R-COKE], [R-FANT], [ARWA], [R-SPRT]) ) pvt;
You could join the table to itself. Something like this
drop TABLE if exists #MyItemTable;
go
CREATE TABLE #MyItemTable
(
orderID INT,
code int,
action char(6),
id int,
level int,
description varchar(10),
price money,
solvedChoice int
)
INSERT INTO #MyItemTable (orderID, code, action, id , level, description, Price, solvedChoice)
VALUES
(321, 622 ,'RECIPE',0,0,'SPICM1',15.5 ,NULL)
,(321, 10 ,'RECIPE',0,1,'SPICKN',17 ,NULL )
,(321, 7091,'RECIPE',0,1,'RFRY ',8.5 ,NULL )
,(321, 521 ,'CHOICE',0,1,'R-COKE',7.5 ,10000003)
,(321, 612 ,'RECIPE',1,0,'BIGTM1',20.5 ,NULL )
,(321, 13 ,'RECIPE',1,1,'BTASTY',21 ,NULL )
,(321, 7091,'RECIPE',1,1,'RFRY ',8.5 ,NULL )
,(321, 522 ,'CHOICE',1,1,'R-FANT',7.5 ,10000003)
,(321, 608 ,'RECIPE',2,0,'ROYAL1',18.5 ,NULL )
,(321, 11 ,'RECIPE',2,1,'MCROYA',18 ,NULL )
,(321, 7091,'RECIPE',2,1,'RFRY ',8.5 ,NULL )
,(321, 411 ,'CHOICE',2,1,'ARWA ',7.5 ,10000003)
,(321, 612 ,'RECIPE',3,0,'BIGTM1',20.5 ,NULL )
,(321, 13 ,'RECIPE',3,1,'BTASTY',21 ,NULL )
,(321, 7091,'RECIPE',3,1,'RFRY ',8.5 ,NULL )
,(321, 524 ,'CHOICE',3,1,'R-SPRT',7.5 ,10000003);
select i.[description],
sum(case when i2.[description] = 'R-COKE' then 1 else 0 end) as r_coke,
sum(case when i2.[description] = 'R-FANT' then 1 else 0 end) as r_fant,
sum(case when i2.[description] = 'ARWA' then 1 else 0 end) as arwa,
sum(case when i2.[description] = 'R-SPRT' then 1 else 0 end) as r_sprt
from #MyItemTable i
left join #MyItemTable i2 on i.id=i2.id
and i2.[action]='CHOICE'
where i.[level]=0
group by i.[description];
description r_coke r_fant arwa r_sprt
BIGTM1 0 1 0 1
ROYAL1 0 0 1 0
SPICM1 1 0 0 0
The aim is to get 1 result for each type of order, that is represented by level=0, id is the order identifier.
You need to first normalize the results by querying the orders and the CHOICE items separately, then you can correlate them with a join.
Once you have identified the spearate order and item records, then we can easily target them with aggregates, in this case a simple COUNT
COUNT works well in this context because it will exclude NULL values.
SELECT [order].description
, COUNT(DISTINCT [order].id) as [Orders]
, COUNT(CASE WHEN item.description = 'R-COKE' THEN 1 END) as [R-COKE]
, COUNT(CASE WHEN item.description = 'R-FANT' THEN 1 END) as [R-FANT]
, COUNT(CASE WHEN item.description = 'ARWA' THEN 1 END) as [ARWA]
, COUNT(CASE WHEN item.description = 'R-SPRT' THEN 1 END) as [R-SPRT]
FROM tblOrders [order]
INNER JOIN tblOrders item ON item.id = [order].id AND item.level = 1
WHERE [order].level = 0
GROUP BY [order].description
Try it out in this fiddle: http://sqlfiddle.com/#!18/81ec5/1
To further highlight the groupings, I have included a count of the separate orders, seeing we are counting the drinks as well.

How to Replace NULL Value with 0 (Zero)?

I've just got myself stuck with some SQL query and I'm quite new on this.
I'm using pivot in my query.
This is my SELECT query:
SELECT *
FROM
(SELECT lg.domainNameID AS [Domain ID], COUNT(lg.domainNameID) AS [Fix Count]
FROM tbl_ATT_Request r
INNER JOIN tbl_ATT_Login lg ON lg.workdayID = r.workdayID
WHERE r.requestCategoryID = 1
GROUP BY lg.domainNameID) slct
and this is the output:
Domain | Fix Count
-------+-----------
1 1
2 1
4 2
5 1
And this is my query with PIVOT.
SELECT *
FROM
(SELECT lg.domainNameID AS [Domain ID], COUNT(lg.domainNameID) AS [Fix Count]
FROM tbl_ATT_Request r
INNER JOIN tbl_ATT_Login lg ON lg.workdayID = r.workdayID
WHERE r.requestCategoryID = 1
GROUP BY lg.domainNameID) slct
PIVOT
(SUM(slct.[Fix Count])
FOR slct.[Domain ID] IN ([1],[2],[3],[4],[5])
) AS pvt
This is the output:
1 | 2 | 3 | 4 | 5
1 1 NULL 2 1
Now my problem is how can I replace the NULL values with 0.
Just use conditional aggregation:
SELECT SUM(CASE WHEN Domain_Id = 1 THEN Fix_Count ELSE 0 END) as d_1,
SUM(CASE WHEN Domain_Id = 2 THEN Fix_Count ELSE 0 END) as d_2,
SUM(CASE WHEN Domain_Id = 3 THEN Fix_Count ELSE 0 END) as d_3,
SUM(CASE WHEN Domain_Id = 4 THEN Fix_Count ELSE 0 END) as d_4,
SUM(CASE WHEN Domain_Id = 5 THEN Fix_Count ELSE 0 END) as d_5
FROM (SELECT lg.domainNameID AS Domain_ID, COUNT(*) AS Fix_Count
FROM tbl_ATT_Request r JOIN
tbl_ATT_Login lg
ON lg.workdayID = r.workdayID
WHERE r.requestCategoryID = 1
GROUP BY lg.domainNameID
) d

3 Query by column and create a new table

I have students, and these students have their meals, morning and evening. I want to print the number of meals each student eats in the morning and evening.
If the number of dishes that student eats in the morning is more than one, I want to print the number in the table and the ID of the food.
FoodType when 1(morning), and when 2(evening)
StudentId FoodId FoodType
3 1 1
3 2 1
3 3 1
3 4 2
4 3 1
4 1 2
4 2 2
4 4 2
5 4 2
5 1 1
6 1 1
6 2 1
6 3 2
6 4 2
Sample out;
StudentId MorningFoodCountOrId EveningFoodCountOrId
3 3 meals 4
4 3 3 meals
5 4 1
6 2 meals 2 meals
Use conditional aggregation. The logic that decides if we print the number of records or their value is not intuitive, but I would phrase it as follows:
select
studentId,
case when sum(case when foodtype = 1 then 1 else 0 end) = 1
then max(case when foodtype = 1 then foodId end)
else sum(case when foodtype = 1 then 1 else 0 end)
end MorningFoodCountOrId
case when sum(case when foodtype = 2 then 1 else 0 end) = 1
then max(case when foodtype = 2 then foodId end)
else sum(case when foodtype = 2 then 1 else 0 end)
end EveningFoodCountOrId
from mytable
group by studentId
You RDMBS should be able to optimize the query by not computing the conditional sums twice.
Note: you did not specify which RDMBS you are using. If this is MySQL, then it is possible to shorten the conditional sums a little, as follows:
select
studentId,
case when sum(foodtype = 1) = 1
then max(case when foodtype = 1 then foodId end)
else sum(foodtype = 1)
MorningFoodCountOrId
case when sum(foodtype = 2) = 1
then max(case when foodtype = 2 then foodId end)
else sum(foodtype = 2)
EveningFoodCountOrId
from mytable
group by studentId
Here's your query, select sum() and case.. will do this
select t1.studentid
, case when t1.m <= 1 then t2.FoodId else concat(t1.m, ' meals') end MorningFoodCountOrId
, case when t1.e <= 1 then t3.FoodId else concat(t1.e, ' meals') end EveningFoodCountOrId
from(
select studentid
, sum(case when FoodType = 1 then 1 else 0 end) as m
, sum(case when FoodType = 2 then 1 else 0 end) as e
from tableA
group by studentid) t1
left join tableA t2 on t2.studentId = t1.studentId and t1.m = 1 and t2.FoodType = 1
left join tableA t3 on t3.studentId = t1.studentId and t1.e = 1 and t3.FoodType = 2
order by t1.studentid
see dbfiddle
in postgresql
, case when t1.m <= 1 then t2.FoodId::text else concat(t1.m, ' meals') end MorningFoodCountOrId
, case when t1.e <= 1 then t3.FoodId::text else concat(t1.e, ' meals') end EveningFoodCountOrId

group by and select max with value null

I have a next problem with query
SELECT
T.DETALLE_BECA_ANIO anio,
T.DETALLE_BECA_MES mes,
T.DETALLE_BECA_NIVEL_EDU_ID edu_id,
T.DETALLE_BECA_TRAMO_ID tr_id,
MAX(
CASE
WHEN T.DETALLE_BECA_TIPO_BENE_ID IS NULL
THEN NVL(DETALLE_BECA_VALOR,0)
ELSE 0
END) mant ,
MAX(
CASE
WHEN T.DETALLE_BECA_TIPO_BENE_ID = 1
THEN NVL(DETALLE_BECA_VALOR,0)
ELSE 0
END) tras
FROM
(SELECT DETALLE_BECA_NIVEL_EDU_ID,
DETALLE_BECA_BECA_ID,
DETALLE_BECA_TIPO_BENE_ID,
DETALLE_BECA_VALOR,
DETALLE_BECA_MES,
DETALLE_BECA_REGION_ID,
DETALLE_BECA_PROVINCIA_ID,
DETALLE_BECA_ANIO,
DETALLE_BECA_TRAMO_ID,
DETALLE_BECA_COMUNA_ID
FROM TBL_DETALLE_BECAS
WHERE (DETALLE_BECA_TIPO_BENE_ID = 1
OR DETALLE_BECA_TIPO_BENE_ID IS NULL)
and DETALLE_BECA_BECA_ID = 1
and detalle_beca_mes = 3
) T
GROUP BY T.DETALLE_BECA_BECA_ID,
T.DETALLE_BECA_TRAMO_ID,
T.DETALLE_BECA_REGION_ID,
T.DETALLE_BECA_PROVINCIA_ID,
T.DETALLE_BECA_ANIO,
T.DETALLE_BECA_MES,
T.DETALLE_BECA_NIVEL_EDU_ID,
T.DETALLE_BECA_COMUNA_ID
ORDER BY T.DETALLE_BECA_BECA_ID,
T.DETALLE_BECA_MES,
T.DETALLE_BECA_NIVEL_EDU_ID
output:
"ANIO" "MES" "EDU_ID" "TR_ID" "MANT" "TRAS"
2017 3 2 0.62 0 NULL
2017 3 3 1.24 6 NULL
2017 3 NULL 1.0 NULL 1
I need that sum value where EDU_ID is null with value 2,3 in TR_ID and replace value null in "tras" with value from EDU is null
"ANIO" "MES" "EDU_ID" "TR_ID" "MANT" "TRAS"
2017 3 2 1.62 0 1
2017 3 3 2.24 6 1
I writed query with min(edu_id) or max(edu_id ) but could not solve my problem.
The other thing that occurred to me is to make a join with the same table
First, this makes more sense as your query:
SELECT T.DETALLE_BECA_ANIO as anio, T.DETALLE_BECA_MES as mes,
T.DETALLE_BECA_NIVEL_EDU_ID as edu_id, T.DETALLE_BECA_TRAMO_ID as tr_id,
MAX(CASE WHEN T.DETALLE_BECA_TIPO_BENE_ID IS NULL
THEN NVL(DETALLE_BECA_VALOR, 0)
ELSE 0
END) as mant ,
MAX(CASE WHEN T.DETALLE_BECA_TIPO_BENE_ID = 1
THEN NVL(DETALLE_BECA_VALOR,0)
ELSE 0
END) tras
FROM TBL_DETALLE_BECAS
WHERE (DETALLE_BECA_TIPO_BENE_ID = 1 OR DETALLE_BECA_TIPO_BENE_ID IS NULL) AND
DETALLE_BECA_BECA_ID = 1 AND
detalle_beca_mes = 3
GROUP BY T.DETALLE_BECA_ANIO, T.DETALLE_BECA_MES,
T.DETALLE_BECA_NIVEL_EDU_ID, T.DETALLE_BECA_TRAMO_ID
ORDER BY T.DETALLE_BECA_BECA_ID, T.DETALLE_BECA_MES, T.DETALLE_BECA_NIVEL_EDU_ID;
This eliminates the subquery (unnecessary) and only aggregates by the columns being returned. A proper query might fix your problem.
But, you seem to want to use NULL to be "all" for the other columns. If so, something like this will work:
WITH t as (
SELECT T.DETALLE_BECA_ANIO as anio, T.DETALLE_BECA_MES as mes,
T.DETALLE_BECA_NIVEL_EDU_ID as edu_id, T.DETALLE_BECA_TRAMO_ID as tr_id,
MAX(CASE WHEN T.DETALLE_BECA_TIPO_BENE_ID IS NULL
THEN NVL(DETALLE_BECA_VALOR, 0)
ELSE 0
END) as mant ,
MAX(CASE WHEN T.DETALLE_BECA_TIPO_BENE_ID = 1
THEN NVL(DETALLE_BECA_VALOR,0)
ELSE 0
END) tras
FROM TBL_DETALLE_BECAS
WHERE (DETALLE_BECA_TIPO_BENE_ID = 1 OR DETALLE_BECA_TIPO_BENE_ID IS NULL) AND
DETALLE_BECA_BECA_ID = 1 AND
detalle_beca_mes = 3
GROUP BY T.DETALLE_BECA_ANIO, T.DETALLE_BECA_MES,
T.DETALLE_BECA_NIVEL_EDU_ID, T.DETALLE_BECA_TRAMO_ID
)
SELECT t.ANIO, t.MES, t.EDU_ID,
COALESCE(t.TR_ID, 0) + COALESCE(tnull.TR_ID, 0) as TR_ID,
t.MANT,
COALESCE(t.TRAS, 0) + COALESCE(tnull.TRAS, 0) as TRAS
FROM t LEFT JOIN
(SELECT t.*
FROM t
WHERE t.edu_id IS NULL
) tnull
ON tnull.ANIO = t.ANIO AND tnull.MES = t.MES
WHERE t.edu_id IS NOT NULL
ORDER BY T.DETALLE_BECA_BECA_ID, T.DETALLE_BECA_MES, T.DETALLE_BECA_NIVEL_EDU_ID;

How do I return count and not count in a SQL query?

If I have a table
AgentID | IsNew | TeamID
1 N 1
2 Y 2
3 Y 2
4 N 2
5 Y 1
I want to return the following from a query:
Team | CountIsNew = N | CountIsNew = Y
1 1 1
2 1 2
Is there a way I can do this?
Using Oracle 10
SELECT team, SUM(DECODE(IsNew, 'N', 1, 0)), SUM(DECODE(IsNew, 'Y', 1, 0))
FROM mytable
GROUP BY
team
SELECT TeamId
, SUM(CASE WHEN IsNew = 'N' THEN 1 ELSE 0 END) AS CountIsNotNew
, SUM(CASE WHEN IsNew = 'Y' THEN 1 ELSE 0 END) AS CountIsNew
FROM Agent
GROUP BY TeamId
Yet another way - COUNT doesn't count NULLs (except for COUNT(*)):
SELECT TeamId,
COUNT(DECODE(IsNew,'N',1)) CountIsNotNew,
COUNT(DECODE(IsNew,'Y',1)) CountIsNew
FROM Agent
GROUP BY TeamId;
Or, if you prefer CASE:
SELECT TeamId,
COUNT(CASE IsNew WHEN 'N' THEN 1 END) CountIsNotNew,
COUNT(CASE IsNew WHEN 'Y' THEN 1 END) CountIsNew
FROM Agent
GROUP BY TeamId;
(note: the "1"s could be any literal value)