SQL Pivot not quite right - sql

Maybe I am just not awake enough...I know I have done something like this in the past and when I look at the other answers here I think I am doing the same thing but I am not getting the expected results.
I have this query:
SELECT
tPM.mast_rel as Mast_Rel
, row_Number() over(Partition by tPM.Mast_rel Order by tPM.Mast_rel) as CategoryCount
, S.[RC_TRANS] as [Category]
, SUM(P.[VAL]) as [Value]
FROM #caselist AS tPM
INNER JOIN [TIBURON].[PARSProperty] AS P ON tPM.[MAST_REL] = P.[MAST_REL]
--S.RC_KEY equals combination of P.CAT-P.ART when P.CAT ='Y' otherwise just P.CAT = RC_KEY
LEFT JOIN [TIBURON].[SSCTAB] AS S ON (CASE
WHEN P.[CAT] = 'Y' THEN P.[CAT] + '-' + P.[ART]
ELSE P.[CAT]
END) = S.[RC_KEY] AND S.[RC_TYPE] = 'CP'
WHERE P.[P_INVL] != 'EVD' and S.[RC_TRANS] is not null
GROUP BY tPM.mast_rel, S.[RC_TRANS]
which gives me these results:
I want to pivot them so I get a single Mast_Rel with three columns of the categories
select Mast_Rel,[1], [2], [3]
from
(
SELECT
tPM.mast_rel as Mast_Rel
, row_Number() over(Partition by tPM.Mast_rel Order by tPM.Mast_rel) as CategoryCount
, S.[RC_TRANS] as [Category]
, SUM(P.[VAL]) as [Value]
FROM #caselist AS tPM
INNER JOIN [TIBURON].[PARSProperty] AS P ON tPM.[MAST_REL] = P.[MAST_REL]
--S.RC_KEY equals combination of P.CAT-P.ART when P.CAT ='Y' otherwise just P.CAT = RC_KEY
LEFT JOIN [TIBURON].[SSCTAB] AS S ON (CASE
WHEN P.[CAT] = 'Y' THEN P.[CAT] + '-' + P.[ART]
ELSE P.[CAT]
END) = S.[RC_KEY] AND S.[RC_TYPE] = 'CP'
WHERE P.[P_INVL] != 'EVD' and S.[RC_TRANS] is not null
GROUP BY tPM.mast_rel, S.[RC_TRANS]
)
src
pivot
(
max(Category) for CategoryCount in ([1], [2], [3])
) piv
order by 1;
but instead of getting a single row, I am getting each one on its own row:
Additionally, I need to have a "total" for the Value column also on the pivot. So ultimately I would like a single record that shows:
Can anyone help me tweak my query to get the results I need?
Thank you!
Edit:
Here is a script that will create the data and the current results:
declare #results table (Mast_Rel varchar(100), CategoryCount varchar(10), Category varchar(100) , [Value] varchar(100))
insert into #results (Mast_rel, CategoryCount, Category, [Value])
values
('1602030055590P2404','1','Money','80.00'),
('1602051033480P3481','1','Miscellaneous/other (None of the above)','1000.00'),
('1602051033480P3481','2','Personal accessories (incl serial jewelry)','5000.00'),
('1602051033480P3481','3','Radio, TV, and sound entertainment devices',''),
('1602070005106P2804','1','Miscellaneous/other (None of the above)',''),
('1602080020374P3352','1','Money','128.09'),
('1602080020374P3352','2','Radio, TV, and sound entertainment devices',''),
('1602132349110P5208','1','Money','160.00'),
('1602132349110P5208','2','Radio, TV, and sound entertainment devices',''),
('1602171004296P3848','1','Consumable Goods','21.73'),
('1602201425504P2876','1','Radio, TV, and sound entertainment devices',''),
('16022115223610P3282','1','Consumable Goods','60.00'),
('16022115223610P3282','2','Money','300.00'),
('16022115223610P3282','3','Narcotic Equipment/Paraphernalia','10.00'),
('1602250140284P2804','1','Money','165.00'),
('1602250140284P2804','2','Radio, TV, and sound entertainment devices',''),
('16022916203812P2702','1','Guns/Firearms',''),
('16022916203812P2702','2','Radio, TV, and sound entertainment devices','')
select Mast_Rel,[1], [2], [3]
from
(
SELECT
* from #results
)
src
pivot
(
max(Category) for CategoryCount in ([1], [2], [3])
) piv
order by 1;

You should be able to alter your original query to get the result you want. The problem lies with the GROUP BY and the SUM() in it. Since you are grouping by S.[RC_TRANS] for the SUM() you are returning multiple rows which is altering the final result of your PIVOT.
You could remove the GROUP BY in the inner subquery and use SUM() OVER() instead. Changing your original query to the following should get you the result you want:
select Mast_Rel,[1], [2], [3], [Value]
from
(
SELECT
tPM.mast_rel as Mast_Rel
, row_Number() over(Partition by tPM.Mast_rel Order by tPM.Mast_rel) as CategoryCount
, S.[RC_TRANS] as [Category]
-- change the following line
, SUM(P.[VAL]) OVER(PARTITION BY tPM.Mast_rel) as [Value]
FROM #caselist AS tPM
INNER JOIN [TIBURON].[PARSProperty] AS P ON tPM.[MAST_REL] = P.[MAST_REL]
--S.RC_KEY equals combination of P.CAT-P.ART when P.CAT ='Y' otherwise just P.CAT = RC_KEY
LEFT JOIN [TIBURON].[SSCTAB] AS S ON (CASE
WHEN P.[CAT] = 'Y' THEN P.[CAT] + '-' + P.[ART]
ELSE P.[CAT]
END) = S.[RC_KEY] AND S.[RC_TYPE] = 'CP'
WHERE P.[P_INVL] != 'EVD' and S.[RC_TRANS] is not null
)
src
pivot
(
max(Category) for CategoryCount in ([1], [2], [3])
) piv
order by 1;
By changing from SUM(P.[VAL]) with a GROUP BY to SUM(P.[VAL]) OVER(PARTITION BY tPM.Mast_rel) you're getting the total sum for each tPM.Mast_rel which is what you're trying to return in the final result set. The SUM(P.[VAL]) OVER should calculate the same value for each row in the Mast_Rel which then will not create multiple rows in the final result set.

I would do conditional aggregation :
select mast_rel,
max(case when categorycount = 1 then category end),
max(case when categorycount = 2 then category end),
max(case when categorycount = 3 then category end),
sum(value)
from #results r
group by mast_rel;

Not sure as I cannot test it directly,.. but what happens when you add a max(Mast_Rel) to the pivot-clause?
pivot (
max(Mast_Rel), max(Category) for CategoryCount in ([1], [2], [3])
) piv

You can wrap it in another GROUP BY
SELECT Mast_Rel, MAX([1]) AS [1], MAX([2]) AS [2], MAX([3]) AS [3]
FROM
(
SELECT *
FROM #results
) src
PIVOT
(
MAX(Category) FOR CategoryCount IN ([1], [2], [3])
) piv
GROUP BY Mast_Rel
ORDER BY Mast_Rel;

Related

SQL Query-Transform the below table to required format

Need help on below questions.Please provide some pointers/suggestions.
Transform the below table to required format shown below:
I created the table at below link:
https://rextester.com/EEFTL51608
Note that i have changed the value to integer. If the data type in your original table is not numeric, you will required another step to convert to numeric so that it is able to sum up in the final result
Create table ProdTable
(
Product nvarchar(50),
[2012] int,
[2013] int ,
[2014] int ,
[2015] int ,
)
GO
Insert into ProdTable values ('Cars','100','125','200','175');
Insert into ProdTable values ('shirts','125','75','100','155');
Insert into ProdTable values ('Cars','75','115','100','255');
Insert into ProdTable values ('Pens','140','100','105','185');
Insert into ProdTable values ('Flowers','155','120','145','165');
select * from ProdTable
select *
from ProdTable
unpivot
(
value
for year in ([2012], [2013], [2014], [2015])
) u
pivot
(
sum(value)
for Product in ([Cars], [shirts], [Pens], [Flowers])
) p
You need unpivot & pivot so i would do :
select year, max(case when t.product = 'Cars' then val end) as Cars,
max(case when t.product = 'shirts' then val end) as shirts,
max(case when t.product = 'Pens' then val end) as Pens,
max(case when t.product = 'Flowers' then val end) as Flowers
from table t cross apply
( values ('2012', [2012]), ('2013', [2013]), ('2014', [2014]), ('2015', [2015])
) tt(year, val)
group by year;

How to pivot multiple columns without aggregation

I use SqlServer and i have to admit that i'm not realy good with it ...
This might be and easy question for the advanced users (I hope)
I have two tables which look like this
First table (ID isn't the primary key)
ID IdCust Ref
1 300 123
1 300 124
2 302 345
And the second (ID isn't the primary key)
ID Ref Code Price
1 123 A 10
1 123 Y 15
2 124 A 14
3 345 C 18
In the second table, the column "Ref" is the foreign key of "Ref" in the first table
I'm trying to produce the following output:
[EDIT]
The column "Stock", "Code" and "Price" can have x values, so I don't know it, in advance...
I tried so many things like "PIVOT" but it didn't give me the right result, so i hope someone can solve my problem ...
Use row_number() function and do the conditional aggregation :
select id, IdCust, Ref,
max(case when Seq = 1 then stock end) as [Stock A], -- second table *id*
max(case when Seq = 1 then code end) as [Code 1],
max(case when Seq = 1 then price end) as [Price1],
max(case when Seq = 2 then stock end) as [Stock B], -- second table *id*
max(case when Seq = 2 then code end) as [Code 2],
max(case when Seq = 2 then price end) as [Price2]
from (select f.*, s.Id Stock, s.Code, s.Price,
row_number() over (partition by f.Ref order by s.id) as Seq
from first f
inner join second s on s.Ref = f.Ref
) t
group by id, IdCust, Ref;
However, this would go with known values else you would need go with dynamic solution for that.
#YogeshSharma's provided an excellent answer.
Here's the same done using Pivot; SQL Fiddle Demo.
Functionally there's no difference between the two answers. However, Yogesh's solution's simpler to understand, and performs better; so personally I'd opt for that... I included this answer only because you mention PIVOT in the question:
select ft.Id
, ft.IdCust
, ft.Ref
, x.Stock1
, x.Code1
, x.Price1
, x.Stock2
, x.Code2
, x.Price2
from FirstTable ft
left outer join (
select Ref
, max([Stock1]) Stock1
, max([Stock2]) Stock2
, max([Code1]) Code1
, max([Code2]) Code2
, max([Price1]) Price1
, max([Price2]) Price2
from
(
select Ref
, Id Stock
, Code
, Price
, ('Stock' + cast(Row_Number() over (partition by Ref order by Id, Code) as nvarchar)) StockLineNo
, ('Code' + cast(Row_Number() over (partition by Ref order by Id, Code) as nvarchar)) CodeLineNo
, ('Price' + cast(Row_Number() over (partition by Ref order by Id, Code) as nvarchar)) PriceLineNo
from SecondTable
) st
pivot (max(Stock) for StockLineNo in ([Stock1],[Stock2])) pvtStock
pivot (max(Code) for CodeLineNo in ([Code1],[Code2])) pvtCode
pivot (max(Price) for PriceLineNo in ([Price1],[Price2])) pvtPrice
Group by Ref
) x
on x.Ref = ft.Ref
order by ft.Ref
Like Yogesh's solution, this will only handle as many columns as you specify; it won't dynamically alter the number of columns to match the data. For that you'd need to do dynamic SQL. However; if you need to do that, it's more likely you're attempting to solve the problem in the wrong way... so consider your design / determine if you really need additional columns per result rather than additional rows / some alternate approach...
Here's a Dynamic SQL implementation based on #YogeshSharma's answer: DBFiddle
declare #sql nvarchar(max) = 'select id, IdCust, Ref'
select #sql = #sql + '
,max(case when Seq = 1 then stock end) as [Stock' + rowNumVarchar + ']
,max(case when Seq = 1 then code end) as [Code' + rowNumVarchar + ']
,max(case when Seq = 1 then price end) as [Price' + rowNumVarchar + ']
'
from
(
select distinct cast(row_number() over (partition by ref order by ref) as nvarchar) rowNumVarchar
from second s
) z
set #sql = #sql + '
from (select f.*, s.Id Stock, s.Code, s.Price,
row_number() over (partition by f.Ref order by s.id) as Seq
from first f
inner join second s on s.Ref = f.Ref
) t
group by id, IdCust, Ref;
'
print #sql --see what the SQL produced is
exec (#sql)
(Here's a SQL Fiddle link for this one; but it's not working despite the SQL being valid

Pivot outputs two values

create table #Contact(
LoanNumber int,
ContactType varchar(10),
CompanyName varchar(10),
CompanyPhone varchar(10),
CONSTRAINT PK PRIMARY KEY (LoanNumber,ContactType)
)
Insert into #Contact
values (1,'Appriaser','Yige King','11' ),
(1,'AssetOwner','gqqnbig','22' )
This is my table. ContactTypes are only Appriaser and AssetOwner.
Can I get a table like
LoanNumber AppraiserCompanyName AppraiserCompanyPhone AssertOwnerCompanyName AssertOwnerCompanyPhone
----------------------------------------------------------------------------------------------------
6103339 YigeKing 11 gqqnbig 22
I managed to write this
select LoanNumber,
CompanyNamePT.Appriaser as AppriaserCompanyName, CompanyNamePT.AssetOwner as AssetOwnerCompanyName
--CompanyPhonePT.Appriaser as AppriaserCompanyPhone, CompanyPhonePT.AssetOwner as AssetOwnerCompanyPhone
from (
select #contact.LoanNumber, #contact.ContactType, #contact.CompanyName
from #contact
) as c
pivot ( max(c.CompanyName) for c.ContactType in (Appriaser,AssetOwner)) as CompanyNamePT
--pivot ( max(c.CompanyPhone) for c.ContactType in ([Appriaser],[AssetOwner])) as CompanyPhonePT
It outputs company names, but if I uncomment the two lines to get phone number, it throws syntax error.
How can I make it work? Ideally I want to use pivot because I'm learnig it.
For the desired PIVOT
Select *
From (
Select C.LoanNumber
,B.*
From #Contact C
Cross Apply ( values (IIF(ContactType='Appriaser' ,'AppraiserCompanyName' , 'AssetOwnerCompanyName') ,C.CompanyName)
,(IIF(ContactType='Appriaser' , 'AppraiserCompanyPhone', 'AssetOwnerCompanyPhone'),C.CompanyPhone)
) B (Item,Value)
) A
pivot ( max(A.Value) for A.Item in ([AppraiserCompanyName],[AppraiserCompanyPhone],[AssetOwnerCompanyName],[AssetOwnerCompanyPhone]) ) P
But a Conditional Aggregation would do as well
Select C.LoanNumber
,AppraiserCompanyName = max(case when ContactType='Appriaser' then C.CompanyName end)
,AppraiserCompanyPhone = max(case when ContactType='Appriaser' then C.CompanyPhone end)
,AssetOwnerCompanyName = max(case when ContactType='AssetOwner' then C.CompanyName end)
,AssetOwnerCompanyPhone = max(case when ContactType='AssetOwner' then C.CompanyPhone end)
From #Contact C
Group By C.LoanNumber
Both Would Return
If it Helps with the Visualization, the sub-query with the Cross Apply Generates

Remove Duplicates while Merging values

How can I remove duplicates and merge Account Types?
I have a call log that reports duplicate phones based on Account Type.
For example:
Telephone | Account Type
304-555-6666 | R
304-555-6666 | C
I know how to remove duplicate Telephones using RANK\MAXCOUNT
But before removing duplicates I need to reset the Account Type to “B” is the duplicates have multiple account types.
In the example the surviving duplicate would be:
Telephone | Account Type
304-555-6666 | B
Warning, it is not guaranteed that duplicate phones have multiple Account Types.
Example:
Telephone | Account Type
999-888-6666 | R
999-888-6666 | R
Therefore the surviving duplicate should be:
Telephone | Account Type
999-888-6666 | R
How can I remove duplicates and reset the account type at the same time?
--
-- Remove Duplicate Recordings
--
SELECT * FROM (
SELECT i.dateofcall ,
i.recordingfile ,
i.telephone ,
s.accounttype ,
ROW_NUMBER() OVER (PARTITION BY i.telephone ORDER BY i.dateofcall DESC) AS 'RANK' ,
COUNT(i.telephone) OVER (PARTITION BY i.telephone) AS 'MAXCOUNT'
FROM #myactions i
LEFT JOIN #myphone s ON s.interactionID = i.Interactionid
) x
WHERE [RANK] = [MAXCOUNT]
SELECT * FROM (
SELECT i.dateofcall ,
i.recordingfile ,
i.telephone ,
s.accounttype ,
ROW_NUMBER() OVER (PARTITION BY i.telephone ORDER BY i.dateofcall DESC) AS 'RANK' ,
COUNT(i.telephone) OVER (PARTITION BY i.telephone) AS 'MAXCOUNT',
DENSE_RANK() OVER ( PARTITION BY i.telephone ORDER BY s.accounttype DESC ) AS 'ContPhone'
FROM #myactions i
LEFT JOIN #myphone s ON s.interactionID = i.Interactionid
) x
WHERE [RANK] = [MAXCOUNT]
Try this?
select
x.dateofcall
, x.recordingfile
, x.telephone
, case when count(*) > 2 then 'B' else max(x.accounttype) end accounttype
(
select
i.dateofcall
, i.recordingfile
, i.telephone
, s.accounttype
from
#myactions i
LEFT JOIN #myphone s ON s.interactionID = i.Interactionid
group by
i.dateofcall
, i.recordingfile
, i.telephone
, s.accounttype
) x
group by
x.dateofcall
, x.recordingfile
, x.telephone
Basically you need to put your business check in a case statement outside.
EDIT: I've also added the logic for B, R and C. Also done a sql fiddle- link to fiddle -http://sqlfiddle.com/#!6/b5ef5/7
SELECT
x.dateofcall,
x.recordingfile,
x.telephone,
COALESCE(
CASE WHEN x.maxcount>1 AND value>x.maxcount AND value<(2*x.maxcount) THEN 'B' ELSE NULL END,
CASE WHEN x.maxcount>1 AND value= (2*x.maxcount) THEN 'C' ELSE NULL END,
CASE WHEN x.maxcount>1 AND value= x.maxcount THEN 'R' ELSE NULL END,
x.accounttype ) as accounttype,
x.rank,
x.maxcount
FROM (
SELECT i.dateofcall ,
i.recordingfile ,
i.telephone ,
s.accounttype ,
ROW_NUMBER() OVER (PARTITION BY i.telephone ORDER BY i.dateofcall DESC) AS 'RANK' ,
COUNT(i.telephone) OVER (PARTITION BY i.telephone) AS 'MAXCOUNT',
SUM(CASE WHEN s.accounttype LIKE 'R' THEN 1 ELSE 2 END) OVER (PARTITION BY i.telephone) as Value
FROM
myactions i LEFT JOIN myphone s
ON s.interactionID = i.Interactionid
) x
WHERE [RANK] = [MAXCOUNT]

sql turn dynamic rows into columns

Subject: Issues and their tasks.
Environment: SQL Server 2008 or above
Database tables: Issues, Tasks, and IssuesTasks
Let's say I have a single input screen that deals with a single issue and their associated tasks.
We're dealing with Issue1 and there are 7 tasks listed to check off.
The user checks 3 of the 7 tasks as completed and saves to database.
Is it possible to write a SQL that shows Issue1 with the 7 tasks on the same row? (Keep in mind only 3 were checked, so the others should be null).
Also note, there are only 3 tasks in the IssuesTasks join table representing what the user checked.
Use SQL server build in PIVOT function:
SELECT <non-pivoted column>,
[first pivoted column] AS <column name>,
[second pivoted column] AS <column name>,
...
[last pivoted column] AS <column name>
FROM
(<SELECT query that produces the data>)
AS <alias for the source query>
PIVOT
(
<aggregation function>(<column being aggregated>)
FOR
[<column that contains the values that will become column headers>]
IN ( [first pivoted column], [second pivoted column],
... [last pivoted column])
) AS <alias for the pivot table>
<optional ORDER BY clause>;
You can use the PIVOT and UNPIVOT relational operators to change a table-valued expression into another table. PIVOT rotates a table-valued expression by turning the unique values from one column in the expression into multiple columns in the output, and performs aggregations where they are required on any remaining column values that are wanted in the final output. UNPIVOT performs the opposite operation to PIVOT by rotating columns of a table-valued expression into column values.
Simple AdventureWorks example:
-- Pivot table with one row and five columns
SELECT 'AverageCost' AS Cost_Sorted_By_Production_Days,
[0], [1], [2], [3], [4]
FROM
(SELECT DaysToManufacture, StandardCost
FROM Production.Product) AS SourceTable
PIVOT
(
AVG(StandardCost)
FOR DaysToManufacture IN ([0], [1], [2], [3], [4])
) AS PivotTable;
More complex example:
USE AdventureWorks2008R2;
GO
SELECT VendorID, [250] AS Emp1, [251] AS Emp2, [256] AS Emp3, [257] AS Emp4, [260] AS Emp5
FROM
(SELECT PurchaseOrderID, EmployeeID, VendorID
FROM Purchasing.PurchaseOrderHeader) p
PIVOT
(
COUNT (PurchaseOrderID)
FOR EmployeeID IN
( [250], [251], [256], [257], [260] )
) AS pvt
ORDER BY pvt.VendorID;
For more information see here
While you've not provided schema for your tables, it's not easy to write you exact query, but take a look at this
select
I.Id,
I.Name,
max(case when IT.TaskId = 1 then T.Name end) as Task1,
max(case when IT.TaskId = 2 then T.Name end) as Task2,
max(case when IT.TaskId = 3 then T.Name end) as Task3,
max(case when IT.TaskId = 4 then T.Name end) as Task4,
max(case when IT.TaskId = 5 then T.Name end) as Task5,
max(case when IT.TaskId = 6 then T.Name end) as Task6,
max(case when IT.TaskId = 7 then T.Name end) as Task7
from Issues as I
left outer join IssuesTasks as IT on IT.IssueId = I.Id
left outer join Tasks as T on T.Id = IT.TaskID
group by I.Id, I.Name;
See SQL FIDDLE example
Please excuse me if this isn't kosher; I'm still getting acclimated with the rules around here (long time reader of stackoverflow, first day posting). I actually just wrote an article on this on my new blog, and I sincerely think it would help. Basically, you can dynamically build your pivoted column values and pass them in to a dynamically-built PIVOT query like this:
IF (OBJECT_ID('tempdb..#TEMP') is not null) DROP TABLE #TEMP
DECLARE #cols NVARCHAR(2000)
SELECT DISTINCT DATE
INTO #TEMP
FROM T_EMPLOYEE_PRODUCTIVITY
SELECT #cols = ISNULL(#cols + ',', '') + '[' + CONVERT(NVARCHAR, DATE) + ']'
FROM #TEMP
ORDER BY DATE
SELECT #cols
DECLARE #query NVARCHAR(4000)
SET #query = 'SELECT EMPLOYEE_NAME, ' + #cols +
'FROM
(
SELECT EMPLOYEE_NAME, DATE, UNITS
FROM T_EMPLOYEE_PRODUCTIVITY
) AS SourceTable
PIVOT
(
SUM(UNITS)
FOR DATE IN ('+ #cols + ')
) AS PivotTable
ORDER BY EMPLOYEE_NAME'
SELECT #query
EXECUTE(#query)
If you need a more detailed explanation with sample data, check it out here: http://thrillhouseblog.blogspot.com/2013/08/dynamic-pivot-query-in-tsql-microsoft.html
I hope this helps!