how to separate and sum 2 columns based on condition - sql

I'm doing a select statement and I have a column I would like to separate into 2 columns based on their type, and then get the sum of the amounts grouped by an ID
I want all the gold and platinum types in one column, and all the silver and bronze in a 2nd column, then summed and grouped by the ID so it looks like this :
I tried doing a union like this:
SELECT
ID,
SUM(Amount) AS "Gold/Platinum",
0 AS "Bronze/Silver"
FROM
table
WHERE
Type IN ('gold', 'platinum')
GROUP BY
ID
UNION ALL
SELECT
ID,
SUM(Amount) AS "Bronze/Silver",
0 AS "Gold/Platinum"
FROM
table
WHERE
Type IN ('bronze', 'silver')
GROUP BY
ID
The gold/platinum column will be correct, but I get nothing in the bronze/silver column

Use conditional aggregation:
select id,
sum(case when Type in ('gold', 'platinum') then amount else 0 end) as gold_platinum,
sum(case when Type in ('bronze', 'silver') then amount else 0 end) as bronze_silver
from t
group by id
order by id;

You can run this in SSMS:
DECLARE #data TABLE( [ID] INT, [Type] VARCHAR(10), [Amount] INT );
INSERT INTO #data ( [ID], [Type], [Amount] ) VALUES
( 1, 'gold', 100 )
, ( 1, 'gold', 50 )
, ( 1, 'bronze', 75 )
, ( 2, 'silver', 10 )
, ( 2, 'bronze', 20 )
, ( 3, 'gold', 35 )
, ( 4, 'silver', 20 )
, ( 4, 'platinum', 30 );
SELECT
[ID]
, SUM( CASE WHEN [Type] IN ( 'gold', 'platinum' ) THEN Amount ELSE 0 END ) AS [Gold/Platinum]
, SUM( CASE WHEN [Type] IN ( 'bronze', 'silver' ) THEN Amount ELSE 0 END ) AS [Bronze/Silver]
FROM #data
GROUP BY [ID]
ORDER BY [ID];
Returns
+----+---------------+---------------+
| ID | Gold/Platinum | Bronze/Silver |
+----+---------------+---------------+
| 1 | 150 | 75 |
| 2 | 0 | 30 |
| 3 | 35 | 0 |
| 4 | 30 | 20 |
+----+---------------+---------------+

Related

TSQL - Parent Child (1 to zero/many) Grouping/Aggregation

Code (Sample Data Staging):
DECLARE #Emp TABLE
(
[EId] INT IDENTITY(1, 1)
, [FN] NVARCHAR(50)
, [LN] NVARCHAR(50)
) ;
DECLARE #EmpPhCont TABLE
(
[EId] INT
, [PhType] VARCHAR(10)
, [PhNum] VARCHAR(16)
, [PhExt] VARCHAR(10)
, [IsMain] BIT
, [CreatedOn] DATETIME
) ;
INSERT INTO #Emp
VALUES
( N'Emp1', N'Emp1' )
, ( N'Emp2', N'Emp2' )
, ( N'Emp3', N'Emp3' )
, ( N'Emp4', N'Emp4' )
, ( N'Emp5', N'Emp5' )
, ( N'Emp6', N'Emp5' ) ;
INSERT INTO #EmpPhCont
VALUES
( 1, 'Home', '111111111', NULL, 0, '2020-01-01 00:00:01' )
, ( 1, 'Mobile', '222222222', NULL, 1, '2020-01-01 00:00:02' )
, ( 1, 'Work', '333333333', NULL, 0, '2020-01-01 00:00:03' )
, ( 2, 'Work', '444444444', '567', 1, '2020-01-01 00:00:04' )
, ( 2, 'Mobile', '555555555', NULL, 0, '2020-01-01 00:00:05' )
, ( 2, 'Mobile', '454545454', NULL, 0, '2020-01-01 00:00:06' )
, ( 3, 'Home', '777777777', NULL, 0, '2020-01-01 00:00:07' )
, ( 3, 'Mobile', '888888888', NULL, 1, '2020-01-01 00:00:08' )
, ( 3, 'Mobile', '12121212', NULL, 0, '2020-01-01 00:00:09' )
, ( 4, 'Work', '101010101', '111', 1, '2020-01-01 00:00:10' )
, ( 4, 'Work', '101010102', '232', 0, '2020-01-01 00:00:11' )
, ( 5, 'Work', '545454545', '456', 0, '2020-01-01 00:00:10' )
, ( 5, 'Work', '456456456', NULL, 1, '2020-01-01 00:00:11' ) ;
Description:
#Emp is the sample Employee table (Unique Employee records).
EId = Employee Id
FN = First Name
LN = Last Name
#EmpPhCont is the sample Employee Phone Contact table (Each Emp from #Emp table can have zero, one, or multiple phone numbers here - unique by Emp/Type).
PhType = Phone Type (home, mobile, work, and etc)
PhNum = Phone Number
PhExt = Phone Extension (mostly available for "Work" PhType)
IsMain = Is it main contact number. Each employee with a phone num will have exactly 1 record marked as IsMain.
CreatedOn = Date the record was created
Goal:
To output 1 record per employee with the following Columns
EId | HomeNum | MobileNum | WorkNum | WorkNumExt | MainPhType
Rules:
Return all EId for all records from #Emp, whether they have a #EmpPhCont record or not.
For each emp that has #EmpPhCont record avail, return the newest created PhNum and PhExt for the corresponding PhType, UNLESS an older record for the same Emp/PhType is marked as IsMain = 1 (For any emp, for whichever PhType, if IsMain = 1, always return that PhNum and PhExt value).
Expected Output:
EId HomeNum MobileNum WorkNum WorkNumExt MainPhType
1 111111111 222222222 333333333 NULL Mobile
2 NULL 454545454 444444444 567 Work
3 777777777 888888888 NULL NULL Mobile
4 NULL NULL 101010102 111 Work
5 NULL NULL 456456456 NULL Work
6 NULL NULL NULL NULL NULL
My unsuccessful try:
SELECT [EM].[EId]
, MAX ( IIF([PH].[PhType] = 'Home', [PH].[PhNum], NULL)) AS [HomePhNum]
, MAX ( IIF([PH].[PhType] = 'Mobile', [PH].[PhNum], NULL)) AS [MobilePhNum]
, MAX ( IIF([PH].[PhType] = 'Work', [PH].[PhNum], NULL)) AS [WorkPhNum]
FROM #Emp AS [EM]
LEFT JOIN #EmpPhCont AS [PH]
ON [EM].[EId] = [PH].[EId]
GROUP BY [EM].[EId] ;
Use ROW_NUMBER() window function inside a CTE to get the rows from #EmpPhCont that you want returned and join this CTE to #Emp:
with cte as (
select *,
row_number() over (partition by [EId], [PhType] order by [IsMain] desc, [CreatedOn] desc) rn
from #EmpPhCont
)
select e.[EId],
max(case when c.[PhType] = 'Home' then c.[PhNum] end) HomeNum,
max(case when c.[PhType] = 'Mobile' then c.[PhNum] end) MobileNum,
max(case when c.[PhType] = 'Work' then c.[PhNum] end) WorkNum,
max(case when c.[PhType] = 'Work' then c.[PhExt] end) WorkNumExt,
max(case when c.[IsMain] = 1 then c.[PhType] end) MainPhType
from #Emp e left join cte c
on c.[EId] = e.[EId] and c.rn = 1
group by e.[EId]
See the demo.
Results:
> EId | HomeNum | MobileNum | WorkNum | WorkNumExt | MainPhType
> --: | :-------- | :-------- | :-------- | :--------- | :---------
> 1 | 111111111 | 222222222 | 333333333 | null | Mobile
> 2 | null | 454545454 | 444444444 | 567 | Work
> 3 | 777777777 | 888888888 | null | null | Mobile
> 4 | null | null | 101010101 | 111 | Work
> 5 | null | null | 456456456 | null | Work
> 6 | null | null | null | null | null
I would implement that using APPLY:
SELECT EId, HomeNum, MobileNum, WorkNum, WorkNumExt
, COALESCE(HomeMain, MobileMain, WorkMain) AS MainPhType
FROM Emp e
OUTER APPLY (
SELECT TOP 1 c.[PhNum] AS HomeNum
, CASE WHEN c.[IsMain] = 1 THEN 'Home' END AS HomeMain
FROM EmpPhCont c
WHERE c.[EId] = e.[EId]
AND c.[PhType] = 'Home'
ORDER BY c.[IsMain] DESC, c.[CreatedOn] DESC
) home
OUTER APPLY (
SELECT TOP 1 c.[PhNum] AS MobileNum
, CASE WHEN c.[IsMain] = 1 THEN 'Mobile' END AS MobileMain
FROM EmpPhCont c
WHERE c.[EId] = e.[EId]
AND c.[PhType] = 'Mobile'
ORDER BY c.[IsMain] DESC, c.[CreatedOn] DESC
) mobile
OUTER APPLY (
SELECT TOP 1 c.[PhNum] AS WorkNum
, c.[PhExt] AS WorkNumExt
, CASE WHEN c.[IsMain] = 1 THEN 'Work' END AS WorkMain
FROM EmpPhCont c
WHERE c.[EId] = e.[EId]
AND c.[PhType] = 'Work'
ORDER BY c.[IsMain] DESC, c.[CreatedOn] DESC
) work
See SQL Fiddle for demo.
Output
EId | HomeNum | MobileNum | WorkNum | WorkNumExt | MainPhType
1 | 111111111 | 222222222 | 333333333 | (null) | Mobile
2 | (null) | 454545454 | 444444444 | 567 | Work
3 | 777777777 | 888888888 | (null) | (null) | Mobile
4 | (null) | (null) | 101010101 | 111 | Work
5 | (null) | (null) | 456456456 | (null) | Work
6 | (null) | (null) | (null) | (null) | (null)
Note: This solution will only be viable for large data sets if the EmpPhCont table has an index on [EId], [PhType], otherwise it'll be too slow.
row_number(), outer apply and aggregation:
select *
from #Emp as e
outer apply
(
select
MAX ( case when d.[PhType] = 'Home' then d.[PhNum] end) AS [HomePhNum]
, MAX ( case when d.[PhType] = 'Mobile' then d.[PhNum] end) AS [MobilePhNum]
, MAX ( case when d.[PhType] = 'Work' then d.[PhNum] end) AS [WorkPhNum]
, MAX ( case when d.[PhType] = 'Work' then d.[PhExt] end) AS [WorkNumExt]
, MAX ( case when IsMain = 1 then d.[PhType] end) AS MainPhType --work is max if both mob&work as set as main..
from
(
select *, row_number() over(partition by PhType order by IsMain DESC, CreatedOn DESC) as rownum
from #EmpPhCont as p
where p.EId = e.EId
) as d
where d.rownum = 1
) as ph;

pivot multi column and mutil row

this is my origin data:
ID New LikeNew Old Warehouse
1 10 100 20 LA
1 12 130 40 CA
2 90 200 10 LA
2 103 230 30 CA
i want to get the following format:
ID LA_new LA_likeNew LA_Old CA_New CA_LikeNew CA_Old
1 10 100 20 12 130 40
2 90 200 10 103 230 30
I can only pivot on each column but can not do on all 3 columns(New, LikeNew, Old) , so how can I do that?
You can accomplish this by using conditional logic to create your fields and grouping:
select id,
max(case when warehouse = 'LA' then new else null end) LA_New,
max(case when warehouse = 'LA' then likenew else null end) LA_likeNew,
max(case when warehouse = 'LA' then old else null end) LA_Old,
max(case when warehouse = 'CA' then new else null end) CA_New,
max(case when warehouse = 'CA' then likenew else null end) CA_likeNew,
max(case when warehouse = 'CA' then old else null end) CA_Old
from yourtable
group by id
Alternatively, you can use WITH Common Table Expression
DECLARE #T AS TABLE
(
id INT ,
New INT ,
likeNew INT ,
old INT ,
Warehouse VARCHAR(50)
)
INSERT INTO #T
( id, New, likeNew, old, Warehouse )
VALUES ( 1, 10, 100, 20, 'LA' ),
( 1, 12, 130, 40, 'CA' ),
( 2, 90, 200, 10, 'LA' ),
( 2, 103, 230, 30, 'CA' );
WITH cte
AS ( SELECT *
FROM #T
WHERE Warehouse = 'LA'
),
cte1
AS ( SELECT *
FROM #T
WHERE Warehouse = 'CA'
)
SELECT a.id ,
a.new [LA_New] ,
a.likeNew [LA_LikeNew] ,
a.Old [LA_Old] ,
b.new [CA_New] ,
b.likeNew [CA_LikeNew] ,
b.Old [CA_Old]
FROM cte A
JOIN cte1 B ON a.id = b.id
Result:
id LA_New LA_LikeNew LA_Old CA_New CA_LikeNew CA_Old
----------- ----------- ----------- ----------- ----------- ----------- -----------
1 10 100 20 12 130 40
2 90 200 10 103 230 30

SQL Server grouping rows

I have this data:
Id | Name | count | Group_number
------+-------+-------+--------------
1 | cdd | 50 | 0
2 | cdd | 15 | 0
3 | cdd | 0 | 0
4 | cdd | 25 | 0
5 | cdd | 11 | 0
I want a script that makes three or four groups on condition: Sum(count) for each group < 50
I want this output:
1 | cdd | 50 | 1
2 | cdd | 15 | 2
3 | cdd | 0 | 2
4 | cdd | 25 | 2
5 | cdd | 11 | 3
Assuming this has to be done for each name, you can use a recursive cte.
with rownums as (select t.*,row_number() over(partition by name order by id) as rnum from t)
,cte(rnum,id,name,cnt,runningsum,grp) as
(select rnum,id,name,cnt,cnt,1 from rownums where rnum=1
union all
select t.rnum,t.id,t.name,t.cnt
,case when c.runningsum+t.cnt > 50 then t.cnt else c.runningsum+t.cnt end
,case when c.runningsum+t.cnt > 50 then t.id else c.grp end
from cte c
join rownums t on t.rnum=c.rnum+1 and t.name=c.name
)
select id,cnt,name,dense_rank() over(partition by name order by grp) as grp
from cte
Sample Demo
Keep track of the running sum and reset it when it goes over 50. Also remember the id when the sum goes over 50. This can be used to assign group numbers.
For records where the count is less than 50 we can simply generate a grouping id by calculating a running total on the count and then divide this running total by 50. However, since some records may already have a count that is greater than or equal to 50 might generate an incorrect id. To solve this problem, we need to somehow force the generation of a new grouping id on the next record. This can be done by simply adjusting the count the next record by 50 if the current records count is 50 or greater.
The following example demonstrates how this can be done:
CREATE TABLE #Items
(
[Id] INT NOT NULL PRIMARY KEY
,[Name] VARCHAR(50) NOT NULL
,[Count] INT NOT NULL
)
INSERT INTO #Items
VALUES
(1, 'cdd', 50 ),
(2, 'cdd', 15 ),
(3, 'cdd', 0 ),
(4, 'cdd', 25 ),
(5, 'cdd', 11 );
;WITH CTE_ItemCountsAdjusted
AS
(
SELECT [Id]
,[Name]
,[Count]
,LAG([Count], 1, 0) OVER (PARTITION BY [Name] ORDER BY [Id]) AS PrevCount
,(
CASE
WHEN LAG([Count], 1, 0) OVER (PARTITION BY [Name] ORDER BY [Id]) >= 50 THEN [Count] + 50
ELSE [Count]
END
) AdjustedCount
FROM #Items
)
SELECT [Id]
,[Name]
,[Count]
,SUM([AdjustedCount]) OVER (PARTITION BY [Name] ORDER BY [Id] ROWS UNBOUNDED PRECEDING) / 50 AS [Group_number]
FROM CTE_ItemCountsAdjusted
ORDER BY [Id]
This method eliminates the need for recursive calls. Note if you need the group id to be strictly sequential (no gaps between group numbers) then you can make use of the DENSE_RANK() windowing function to achieve this as per following example:
INSERT INTO #Items
VALUES
(1, 'cdd', 50 ),
(2, 'cdd', 15 ),
(3, 'cdd', 0 ),
(4, 'cdd', 25 ),
(5, 'cdd', 11 ),
(6, 'cdd', 200 ),
(7, 'cdd', 10 );
;WITH CTE_ItemCountsAdjusted
AS
(
SELECT [Id]
,[Name]
,[Count]
,LAG([Count], 1, 0) OVER (PARTITION BY [Name] ORDER BY [Id]) AS PrevCount
,(
CASE
WHEN LAG([Count], 1, 0) OVER (PARTITION BY [Name] ORDER BY [Id]) >= 50 THEN [Count] + 50
ELSE [Count]
END
) AdjustedCount
FROM #Items
),CTE_ItemCountsWithGroupID
AS
(
SELECT [Id]
,[Name]
,[Count]
,SUM([AdjustedCount]) OVER (PARTITION BY [Name] ORDER BY [Id] ROWS UNBOUNDED PRECEDING) / 50 AS [Group_number]
FROM CTE_ItemCountsAdjusted
)
SELECT [Id]
,[Name]
,[Count]
,[Group_number]

Transpose SQL table results using Pivot

I am trying to transpose column results in my table into row results. Here is the query that generates the table results:
CREATE TABLE Zone
([Zone] varchar(9), [CompanyID] int, [SubCount] int);
CREATE TABLE Company
([UniqueIdentifier]int, [Name] varchar(50));
--Adding Values into the table
INSERT INTO Company
([UniqueIdentifier], [Name])
VALUES
( 1, 'CompanyA'),
( 2, 'CompanyB'),
( 3, 'CompanyC'),
( 4, 'CompanyD'),
( 5, 'CompanyE');
--Adding Values to the table
INSERT INTO Zone
([Zone], [CompanyID], [SubCount])
VALUES
( 'Zone1', 1, 100),
( 'Zone2', 1, 200),
( 'Zone3', 2, 1250),
( 'Zone4', 3, 1440),
( 'Zone5', 4, 1445),
( 'Zone6', 4, 3250),
( 'Zone7', 5, 4440);
--Getting TOTALS
SELECT
CASE WHEN GROUPING(dbo.Company.Name)=1 THEN 'Grand Total' else dbo.Company.Name end as Company,
SUM(dbo.Zone.SubCount) as Subs
FROM dbo.Company INNER JOIN dbo.Zone ON dbo.Company.UniqueIdentifier = dbo.Zone.CompanyID
WHERE (dbo.Zone.SubCount IS NOT NULL) AND (dbo.Zone.SubCount > 0)
Group by ROLLUP(dbo.Company.Name)
ORDER BY Subs DESC;
HERE ARE THE RESULTS OF THE QUERY:
Company | Subs
------------------------
1 Grand Total | 12125
2 CompanyD | 4695
3 CompanyE | 4440
4 CompanyC | 1440
5 CompanyB | 1250
6 CompanyA | 300
DESIRED RESULT WOULD LOOK LIKE:
Company |CompanyA|CompanyB|CompanyC|CompanyE|CompanyD|Grand Total
---------------------------------------------------------------------
Subs | 300 | 1250 | 1440 | 4440 | 4695 | 12125
ANY HELP IS GREATLY APPRECIATED!
All you need to do is use pivot function for converting these rows into columns
SELECT 'Subs' AS Company
,[CompanyA]
,[CompanyB]
,[CompanyC]
,[CompanyD]
,[CompanyE]
,[Grand Total]
FROM (
SELECT CASE
WHEN GROUPING(dbo.Company.NAME) = 1
THEN 'Grand Total'
ELSE dbo.Company.NAME
END AS Company
,SUM(dbo.Zone.SubCount) AS Subs
FROM dbo.Company
INNER JOIN dbo.Zone ON dbo.Company.UNIQUEIDENTIFIER = dbo.Zone.CompanyID
WHERE (dbo.Zone.SubCount IS NOT NULL)
AND (dbo.Zone.SubCount > 0)
GROUP BY ROLLUP(dbo.Company.NAME)
) a
pivot(max(subs) FOR Company IN (
[CompanyA]
,[CompanyB]
,[CompanyC]
,[CompanyD]
,[CompanyE]
,[Grand Total]
)) piv;

How to pivot rows to columns with known max number of columns

I have a table structured as such:
Pricing_Group
GroupID | QTY
TestGroup1 | 1
TestGroup1 | 2
TestGroup1 | 4
TestGroup1 | 8
TestGroup1 | 22
TestGroup2 | 2
TestGroup3 | 2
TestGroup3 | 5
What I'm looking for is a result like this:
Pricing_Group
GroupID | QTY1 | QTY2 | QTY3 | QTY4 | QTY5
TestGroup1 | 1 | 2 | 4 | 8 | 22
TestGroup2 | 2 | NULL | NULL | NULL | NULL
TestGroup3 | 2 | 5 | NULL | NULL | NULL
Note that there can only ever be a maximum of 5 different quantities for a given GroupID, there's just no knowing what those 5 quantities will be.
This seems like an application of PIVOT, but I can't quite wrap my head around the syntax that would be required for an application like this.
Thanks for taking the time to look into this!
Perfect case for pivot and you don't need a CTE:
Declare #T Table (GroupID varchar(10) not null,
QTY int)
Insert Into #T
Values ('TestGroup1', 1),
('TestGroup1', 2),
('TestGroup1', 4),
('TestGroup1', 8),
('TestGroup1', 22),
('TestGroup2', 2),
('TestGroup3', 2),
('TestGroup3', 5)
Select GroupID, [QTY1], [QTY2], [QTY3], [QTY4], [QTY5]
From (Select GroupID, QTY,
RowID = 'QTY' + Cast(ROW_NUMBER() Over (Partition By GroupID Order By QTY) as varchar)
from #T) As Pvt
Pivot (Min(QTY)
For RowID In ([QTY1], [QTY2], [QTY3], [QTY4], [QTY5])
) As Pvt2
You can pivot on a generated rank;
;with T as (
select
rank() over (partition by GroupID order by GroupID, QTY) as rank,
GroupID,
QTY
from
THE_TABLE
)
select
*
from
T
pivot (
max(QTY)
for rank IN ([1],[2],[3],[4],[5])
) pvt
>>
GroupID 1 2 3 4 5
----------------------------------------
TestGroup1 1 2 4 8 22
TestGroup2 2 NULL NULL NULL NULL
TestGroup3 2 5 NULL NULL NULL
You can also use case statement to perform the pivot:
declare #t table ( GroupID varchar(25), QTY int)
insert into #t
values ('TestGroup1', 1),
('TestGroup1', 2),
('TestGroup1', 4),
('TestGroup1', 8),
('TestGroup1', 22),
('TestGroup2', 2),
('TestGroup3', 2),
('TestGroup3', 5)
;with cte_Stage (r, GroupId, QTY)
as ( select row_number() over(partition by GroupId order by QTY ),
GroupId,
QTY
from #t
)
select GroupId,
[QTY1] = sum(case when r = 1 then QTY else null end),
[QTY2] = sum(case when r = 2 then QTY else null end),
[QTY3] = sum(case when r = 3 then QTY else null end),
[QTY4] = sum(case when r = 4 then QTY else null end),
[QTY5] = sum(case when r = 5 then QTY else null end),
[QTYX] = sum(case when r > 5 then QTY else null end)
from cte_Stage
group
by GroupId;