In SQL Server how to Pivot for multiple columns - sql

This is my sample table, I want to pivot the category column and get the sales, stock and target as rows
I want the sample output in this form as shown in the below wherein the categories are in place of columns and columns in place of row

You gotta change the name of columns for next Pivot Statement.
Like
SELECT
*
FROM
(
SELECT
Branch,
Category,
Category+'1' As Category1,
Category+'2' As Category2,
Sales,
Stock,
Target
FROM TblPivot
) AS P
-- For Sales
PIVOT
(
SUM(Sales) FOR Category IN ([Panel], [AC], [Ref])
) AS pv1
-- For Stock
PIVOT
(
SUM(Stock) FOR Category1 IN ([Panel1], [AC1], [Ref1])
) AS pv2
-- For Target
PIVOT
(
SUM(Target) FOR Category2 IN ([Panel2], [AC2], [Ref2])
) AS pv3
GO
You are ready to go now....
You can use aggregate of pv3 to sum and group by the column you need.

Sample Table :
DECLARE #Table1 TABLE
(Branch varchar(9), Category varchar(9), Sales INT,Stock INT,Target INT)
;
INSERT INTO #Table1
(Branch, Category, Sales, Stock,Target)
VALUES
( 'mumbai', 'panel', 10,4,15),
( 'mumbai', 'AC', 11,7,14),
( 'mumbai', 'Ref', 7,2,10),
( 'Delhi', 'panel',20,4,17),
( 'Delhi', 'AC', 5,2,12),
( 'Delhi', 'Ref', 10,12,22)
;
IN SQL SERVER Script :
Select BRANCH,COL,[panel],[AC],[Ref] from (
select Branch,Category,COL,VAL from #Table1
CROSS APPLY (VALUES ('Sales',Sales),
('Stock',Stock),
('Target',Target))CS (COL,VAL))T
PIVOT (MAX(VAL) FOR Category IN ([panel],[AC],[Ref]))PVT
ORDER BY Branch DESC

Try below solution
-- Applying pivoting on multiple columns
SELECT
*
FROM
(
SELECT
Category,
Sales,
FROM TblPivot
) AS P
-- For Sales
PIVOT
(
SUM(Sales) FOR Category IN ([Panel], [AC], [Ref])
) AS pv1
union all
-- For Stock
SELECT
*
FROM
(
SELECT
Category,
Stock,
FROM TblPivot
) AS P
PIVOT
(
SUM(Stock) FOR Category IN ([Panel], [AC], [Ref])
) AS pv2
union all
-- For Target
SELECT
*
FROM
(
SELECT
Category,
Target,
FROM TblPivot
) AS P
PIVOT
(
SUM(Target) FOR Category IN ([Panel], [AC], [Ref])
) AS pv3
GO

Following should work,
select * FROM
(
SELECT
Branch,
Category,
Sales,
Stock,
Target
FROM Table1
) AS P
unpivot
(
[Value] FOR [OutPut] IN (sales,stock,[target])
)unpvt
pivot
(
max([Value]) for Category in (Panel,AC,Ref)
)pvt
order by Branch Desc

From Answer1 & Answer2
(this not OP's requirement)
DECLARE #Table1 TABLE(Branch varchar(9), Category varchar(9), Sales INT,Stock INT,Target INT);
INSERT INTO #Table1
(Branch, Category, Sales, Stock,Target)
VALUES
( 'mumbai', 'panel', 10,4,15),
( 'mumbai', 'AC', 11,7,14),
( 'mumbai', 'Ref', 7,2,10),
( 'Delhi', 'panel',20,4,17),
( 'Delhi', 'AC', 5,2,12),
( 'Delhi', 'Ref', 10,12,22);
SELECT
Branch,
SUM(Panel) As PanelSales,SUM([AC]) As ACSales,SUM([Ref]) As RefSales,
SUM(Panel1) As PanelStock,SUM([AC1]) As ACStock,SUM([Ref1]) As RefStock,
SUM(Panel2) As PanelTarget,SUM([AC2]) As ACTarget,SUM([Ref2]) As RefTarget
FROM
(
SELECT
Branch,
Category,
Category+'1' As Category1,
Category+'2' As Category2,
Sales,
Stock,
Target
FROM #Table1
) AS P
-- For Sales
PIVOT
(
SUM(Sales) FOR Category IN ([Panel], [AC], [Ref])
) AS pv1
-- For Stock
PIVOT
(
SUM(Stock) FOR Category1 IN ([Panel1], [AC1], [Ref1])
) AS pv2
-- For Target
PIVOT
(
SUM(Target) FOR Category2 IN ([Panel2], [AC2], [Ref2])
) AS pv3
Group BY Branch
GO

You could first UNPIVOT the data to make it a flat table and then PIVOT to turn the categories into columns:
SELECT *
FROM SampleTable
UNPIVOT (Amount FOR Output IN (Sales, Stock, Target)) upvt
PIVOT (SUM(Amount) FOR Category IN (Panel, AC, Ref)) pvt
ORDER BY Branch, Output;

Related

How to retrieve Count of all records and other records in single query

I have the following query
select name,trip_id from main order by name
I want to retrieve count of all the records and all the columns in the tables.
for ex if i have 200 rows in table i want to have the output as
select name,trip_id,count(*) from main
Is it possible in a single query?
Use analytic count:
select name, trip_id,
count(*) over() as cnt
from main
order by name
;
DECLARE
#Main TABLE
(
[name] VARCHAR(50),
trip_id INT
);
INSERT INTO #Main
(
[name],
trip_id
)
VALUES
(
'Jim', 1
),
(
'Ian', 2
),
(
'Susan', 2
);
-- Option 1
SELECT
[name],
trip_id,
COUNT(*) OVER () AS ct
FROM
#Main;
-- Option 2
SELECT
[name],
trip_id,
(
SELECT
COUNT(*)
FROM
#Main
)
ct
FROM
#Main;
-- Option 3
SELECT
[name],
trip_id,
v.ct
FROM
#Main
CROSS APPLY
(
SELECT
COUNT(*) FROM #Main
) v (ct);
-- Option 4
SELECT
t1.[name],
t1.trip_id,
t2.ct
FROM
#Main t1
JOIN
(
SELECT
COUNT(*) ct
FROM
#Main
) t2
ON 1 = 1;
-- Option 5
SELECT
t1.[name],
t1.trip_id,
t2.ct
FROM
#Main t1
CROSS JOIN
(
SELECT
COUNT(*) ct
FROM
#Main
) t2;

Compare two rows from same table

I want to compare values of two rows in one table,
The table is like follows.
I want to find record where -F sku greater than normal one
SKU ASIN Price
CT0144 B013VNZNYU 20.99
CT0144-F B013VNZNYU 17.64
Try like this,
DECLARE #table TABLE (
SKU VARCHAR(50)
,ASIN VARCHAR(50)
,Price FLOAT
)
INSERT INTO #table
VALUES (
'CT0144'
,'B013VNZNYU'
,'20.99'
)
,(
'CT0144-F'
,'B013VNZNYU'
,'17.64'
)
,(
'CT0144'
,'B013VNZNU'
,'10.99'
)
,(
'CT0144-F'
,'B013VNZNU'
,'18.64'
)
SELECT *
FROM #table
SELECT A.ASIN
,A.FPrice
,B.Normal
FROM (
SELECT ASIN
,MAX(PRICE) AS FPrice
FROM #table t1
WHERE SKU LIKE '%F'
GROUP BY ASIN
) A
INNER JOIN (
SELECT ASIN
,MAX(PRICE) AS Normal
FROM #table t1
WHERE SKU NOT LIKE '%F'
GROUP BY ASIN
) B ON A.ASIN = B.ASIN
WHERE A.FPrice > B.Normal
Try this
select t1.SKU,t1.ASIN,t1.Price from table as t1 inner join
(
select ASIN, max(price) as price from table group by ASIN
) as t2
on t1.ASIN=t2.ASIN and t1.price=t2.price
where t1.SKU like '%-F'

is there an easier way than doing UNION ALL?

I have a rather large query which select data from a certain date range.
I would like to group data based on year.
Right now I have:
;with mappingTable as
(
select b.CLIENT_ID,f.patient_id,f.received_date,f.SPECIMEN_ID
from F_ACCESSION_DAILY f
join
(
select f.client_id,a.patient_id
from
(
SELECT
max(received_date) AS received_date
, patient_id AS Patient_ID
FROM F_ACCESSION_DAILY
group by PATIENT_ID
) a
join F_ACCESSION_DAILY f
on f.PATIENT_ID=a.Patient_ID
and f.RECEIVED_DATE=a.received_date
) b
on b.Patient_ID=f.PATIENT_ID
where f.RECEIVED_DATE between '20100101' and '20110101' - as you can see this data is only for 2010
) ---there's much more to this query
as you can see this data is only for 2010
but i would like to do union all for all years: 2008, 2009, 2010, 2011, 2012...
how do i do this without rewriting the query 5 times and doing a union all between all of them?
here's the entire monster query:
;with mappingTable as
(
select b.CLIENT_ID,f.patient_id,f.received_date,f.SPECIMEN_ID
from F_ACCESSION_DAILY f
join
(
select f.client_id,a.patient_id
from
(
SELECT
max(received_date) AS received_date
, patient_id AS Patient_ID
FROM F_ACCESSION_DAILY
group by PATIENT_ID
) a
join F_ACCESSION_DAILY f
on f.PATIENT_ID=a.Patient_ID
and f.RECEIVED_DATE=a.received_date
) b
on b.Patient_ID=f.PATIENT_ID
where f.RECEIVED_DATE between '20100101' and '20110101'
)
,
counted as(
SELECT
PATIENT_ID,
client_id,
--case when COUNT(*)>=12 then 12 else COUNT(*) end TimesTested,
COUNT(*) as timestested,
case when count(*)>1 then
(datediff(day,MIN(received_date),max(received_date)))
/(COUNT(*)-1)
else
0
end as testfreq
FROM mappingTable
GROUP BY
client_id,
patient_id
),counted2 as (
SELECT
client_id,
TimesTested,
CAST(COUNT(*) AS varchar(30)) AS count,
CAST(AVG(testfreq) as varchar(30)) as TestFreq,
CAST(STDEV(TestFreq) as varchar(30)) Stdv
FROM counted
GROUP BY
client_id,
TimesTested
),
counted3 as
(
SELECT
patient_id,
client_id,
TimesTested,
CAST(COUNT(*) AS varchar(30)) AS count,
AVG(testfreq) as TestFreq
FROM counted
GROUP BY
client_id,
TimesTested,
PATIENT_ID
)
,
CountOver12 as
( select client_id,count(*) count
from counted
where timestested>12
group by CLIENT_ID)
,
TotalAvgTestFreq as (
select client_id, SUM(testfreq) / count(testfreq) TotalAvgTestFreq
from counted3
group by client_id
)
,
unpivoted AS (
SELECT
client_id,
ColumnName + CAST(TimesTested AS varchar(10)) AS ColumnName,
ColumnValue
FROM counted2
UNPIVOT (
ColumnValue FOR ColumnName IN (count, TestFreq,stdv)
) u
),
pivoted AS (
SELECT
client_id clientid,
count1, TestFreq1,stdv1,
count2, TestFreq2,stdv2,
count3, TestFreq3,stdv3,
count4, TestFreq4,stdv4,
count5, TestFreq5,stdv5,
count6, TestFreq6,stdv6,
count7, TestFreq7,stdv7,
count8, TestFreq8,stdv8,
count9, TestFreq9,stdv9,
count10, TestFreq10,stdv10,
count11, TestFreq11,stdv11,
count12, TestFreq12,stdv12
FROM unpivoted
PIVOT (
MAX(ColumnValue) FOR ColumnName IN (
count1,TestFreq1,stdv1,
count2,TestFreq2,stdv2,
count3,TestFreq3,stdv3,
count4,TestFreq4,stdv4,
count5,TestFreq5,stdv5,
count6,TestFreq6,stdv6,
count7,TestFreq7,stdv7,
count8, TestFreq8, stdv8,
count9, TestFreq9, stdv9,
count10, TestFreq10,stdv10,
count11, TestFreq11,stdv11,
count12, TestFreq12,stdv12
)
) p
)
,
PatientStats as
(
select
distinct
f.client_id
,datediff(MM,c.mlis_date_established,GETDATE()) MonthsCustomer
,count(distinct patient_id) TotalPatients
,count(distinct specimen_id) TotalSpecimens
from mappingTable f
left join D_CLIENT c
on f.CLIENT_ID=c.CLIENT_ID
group by f.client_id,c.mlis_date_established
)
,
Over12
as
(
select client_id,SUM(c) sumcount from
(
select CLIENT_ID,COUNT(*) c
from mappingTable
group by CLIENT_ID,PATIENT_ID
having count(*)>12
) a
group by client_id
),
GetMedian as(
select client_id, avg(timestested) median_testfreq
from
(
select client_id,
timestested,
rn=row_number() over (partition by CLIENT_ID
order by timestested),
c=count(timestested) over (partition by CLIENT_ID)
from counted3
where timestested>1
) g
where rn in (round(c/2,0),c/2+1)
group by client_id
)
, final as (
SELECT p.*,pivoted.*,c12.count [Count13+],Over12.sumcount SumofGreaterThan12,t.TotalAvgTestFreq,median.median_testfreq
FROM pivoted
left join PatientStats p
on p.CLIENT_ID=pivoted.CLIENTID
left join Over12
on over12.CLIENT_ID=p.CLIENT_ID
left join TotalAvgTestFreq t
on t.CLIENT_ID=p.CLIENT_ID
left join CountOver12 C12
on c12.CLIENT_ID=p.CLIENT_ID
left join GetMedian median
on median.CLIENT_ID=p.CLIENT_ID
where p.CLIENT_ID not in (select CLIENTid from SalesDWH..TestPractices)
)
/* Get the data into a temp table */
SELECT * INTO #TempTable
FROM final
/* Drop the cloumns that are not needed */
ALTER TABLE #TempTable
DROP COLUMN clientid
/* Get results and drop temp table */
SELECT * FROM #TempTable
DROP TABLE #TempTable
Create a table function:
CREATE FUNCTION fn_get_data_by_date_range
(
#start AS VARCHAR(8),
#end AS VARCHAR(8)
)
RETURNS TABLE
AS
RETURN
(
...your query...
WHERE f.RECEIVED_DATE BETWEEN #start AND #end
)
then you can UNION different call of that function:
SELECT * FROM fn_get_data_by_date_range('20100101','20110101')
UNION ALL
SELECT * FROM fn_get_data_by_date_range('20090101','20100101')
UNION ALL
....
UNION ALL
SELECT * FROM fn_get_data_by_date_range('20070101','20080101')

SQL Server 2008 Reporting: Sum of Max of group

I have a table like this in the report designer:
Category: 1 2 3 4 Total
Max Amount: x y z c ?
I need to get the total of Max Amount, but expressions will not let me take Sum(Max(amount)), and the add total is disabled for this cell.
The max amount row is an expression that takes the max of each category. The source data has repeated values, so I just take the max. For example:
Category Amount
1 4.6
1 4.6
1 4.6
2 5
3 4
Other columns in the table are different, but amount will be the same so I cannot only select distinct values.
Maybe something like this:
SELECT
SUM(t1.maxAmout)
FROM
(
SELECT
MAX(t.Amout) AS maxAmout,
t.Category
FROM
yourTable AS t
GROUP BY
t.Category
) AS t1
You can also do it like this. If you are using sql server 2005+:
SELECT
pvt.[1],
pvt.[2],
pvt.[3],
(
pvt.[1]+
pvt.[2]+
pvt.[3]
) AS Total
FROM
(
SELECT
t.Category,
t.Amout
FROM
yourTable AS t
) AS SourceTable
PIVOT
(
MAX(Amout)
FOR Category IN([1],[2],[3])
) AS pvt
EDIT
If you have a 1000 categories. Then a dynamic pivot would be the best solution. So like this:
Test data
CREATE TABLE #T
(
Category INT,
Amout FLOAT
)
INSERT INTO #T
VALUES
(1,4.6),
(1,4.6),
(1,4.6),
(2,5),
(3,4)
Unique column names
DECLARE #cols VARCHAR(MAX)
DECLARE #colsTotal VARCHAR(MAX)
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY t.Category ORDER BY t.Amout) AS RowNbr,
t.*
FROM
#T AS t
)
SELECT #cols = COALESCE(#cols + ','+QUOTENAME(Category),
QUOTENAME(Category)),
#colsTotal=COALESCE(#colsTotal + '+ISNULL('+QUOTENAME(Category)+',0)',
'ISNULL('+QUOTENAME(Category)+',0)')
FROM
CTE
WHERE
CTE.RowNbr=1
ORDER BY
Category
Dynamic pivot
DECLARE #query NVARCHAR(4000)=
N'SELECT
'+#cols+',
(
'+#colsTotal+'
) AS Total
FROM
(
SELECT
t.Category,
t.Amout
FROM
#T AS t
) AS SourceTable
PIVOT
(
MAX(Amout)
FOR Category IN('+#cols+')
) AS pvt'
EXECUTE(#query)
WITH
aggregate
AS
(
SELECT
category,
MAX(amount) AS max_amount
FROM
yourTable
GROUP BY
category
)
SELECT
MAX(CASE WHEN category = 1 THEN max_amount ELSE NULL END) AS [1],
MAX(CASE WHEN category = 2 THEN max_amount ELSE NULL END) AS [2],
MAX(CASE WHEN category = 3 THEN max_amount ELSE NULL END) AS [3],
MAX(CASE WHEN category = 4 THEN max_amount ELSE NULL END) AS [4],
SUM(max_amount) AS [total]
FROM
aggregate
The following returns a single value:
SELECT DISTINCT SUM(MAX(Amount)) OVER ()
FROM atable
GROUP BY Category
You could use this as a subquery calculating the value of the Total column.
Alternatively, TOP (1) could be used instead of DISTINCT (don't know why I couldn't think of it before):
SELECT TOP (1) SUM(MAX(Amount)) OVER ()
FROM atable
GROUP BY Category

Group By and Aggregate function

I want to write an efficient query which returns a list of fruits by type, the lowest price for the type of fruit and the name of the fruit. Right now, I have a query which return me the fruit type and the lowest price for that type (see below). But I am unable to get the name of the cheapest fruit.
Any idea how I can achieve that? Thanks.
CREATE TABLE Fruits (
[type] nvarchar(250),
[variety] nvarchar(250),
[price] money
)
GO
INSERT INTO Fruits VALUES ('Apple', 'Gala', 2.79)
INSERT INTO Fruits VALUES ('Apple', 'Fuji', 0.24)
INSERT INTO Fruits VALUES ('Apple', 'Limbertwig', 2.87)
INSERT INTO Fruits VALUES ('Orange', 'Valencia', 3.59)
INSERT INTO Fruits VALUES ('Pear', 'Bradford', 6.05)
SELECT type, MIN(price)
FROM Fruits
GROUP BY [type]
Use:
SELECT f.*
FROM FRUITS f
JOIN (SELECT t.type,
MIN(t.price) AS min_price
FROM FRUITS t
GROUP BY t.type) x ON x.type = f.type
AND x.min_price = f.price
I gather you're using SQL Server - if v2005 or newer, you could also use analytic/rank/windowing functions instead:
SELECT f.type, f.variety, f.price
FROM (SELECT t.type, t.variety, t.price,
ROW_NUMBER() OVER (PARTITION BY t.type ORDER BY t.price) AS rank
FROM FRUITS t) f
WHERE f.rank = 1
There are a number of ways to do this, one solution is below.
SELECT F2.type, f2.variety, f2.price
FROM
(
SELECT type, min(price) as price
FROM Fruits
GROUP BY [type]
) as MinData
INNER JOIN Fruits F2
ON (MinData.type = Type = F2.Type
AND MinData.price = F2.Price)
Keep in mind that if you have multiple items in a category with the same price at the minimum you will get multiple results.
There's a simple trick you can use for this sort of query if your table has a surrogate primary key. (Actually, you can do it without one, but it's more convoluted.)
The setup:
if object_id('tempdb..#Fruits') is not null drop table #Fruits
create table #Fruits (
[id] int identity(1,1) not null,
[type] nvarchar(250),
[variety] nvarchar(250),
[price] money
)
insert into #Fruits ([type], [variety], [price])
select 'Apple', 'Gala', 2.79 union all
select 'Apple', 'Fuji', 0.24 union all
select 'Apple', 'Limbertwig', 2.87 union all
select 'Orange', 'Valencia', 3.59 union all
select 'Pear', 'Bradford', 6.05
And now the SQL:
select * -- no stars in PROD!
from #Fruits a
where
a.id in (
select top 1 x.id
from #Fruits x
where x.[type] = a.[type]
order by x.price
)