Pivot a table on two value columns - sql-server

I am trying to pivot a table on two columns (we did some normalization on our table but need to create a view for backwards compatibility). My new data structure is this:
CREATE TABLE #Products (
product VARCHAR(30),
market_year INT,
value INT,
description varchar(20));
INSERT INTO #Products VALUES('Corn', 2003, 100, 'bad');
INSERT INTO #Products VALUES('Beer', 2003, 200, 'not so bad');
INSERT INTO #Products VALUES('Beef', 2003, 150, 'good');
INSERT INTO #Products VALUES('Corn', 2004, 10, 'doo');
INSERT INTO #Products VALUES('Beer', 2004, 20, 'foo');
INSERT INTO #Products VALUES('Beef', 2004, 10, 'bar');
which looks Like:
SELECT * FROM #Products p;
product market_year value description
Corn 2003 100 bad
Beer 2003 200 not so bad
Beef 2003 150 good
Corn 2004 10 doo
Beer 2004 20 foo
Beef 2004 10 bar
The result I need should look like the following:
market_year |corn_value |corn_description | beer_value | beer_description |beef_value | beef_description
2003 | 100 |bad | 150 | NOT so bad |150 | good
2003 | 10 |doo | 15 | foo |15 | bar
I know I could do this with two separate statements with one pivot each on market_year and join them by market_year but that would require this table to be read twice. Is there a more elegant way to solve this issue?
(my table has approximately 7 million records split by 70 columns that need to be reverted to 140 columns)

You can try to use dynamic pivot
declare two var #CONTENT to represnt pivot sql query CASE WHEN with MAX
then use #sql to connecte your query sql and use execute execute this sql dynamically.
DECLARE #SQL VARCHAR(MAX),#CONTENT VARCHAR(MAX)
SELECT #CONTENT = STUFF((
SELECT ', '+'MAX(CASE WHEN product = '''+ product + ''' THEN [VALUE] END) AS '''+product+'_value'',
MAX(CASE WHEN product = '''+ product + ''' THEN [description] END) AS '''+product+'_value'''
FROM #Products
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'')
SELECT #SQL = 'SELECT market_year,' + #CONTENT + ' FROM #Products GROUP BY market_year';
execute(#SQL)
sqlfiddle

select *
from
(
select market_year,product, value
from #products p
) src
pivot
(
sum(value)
for product in (Beer, Com, Beef)
) piv;
try this it will help you to get some idea

Related

using regex in microsoft sql server management studio

I have a table in which I copy the data based on an condition and I insert it into the same table with a different ID.As follows:
SET IDENTITY_INSERT Table ON
INSERT INTO Table (ID,GroupID,Name,link,etc..)
SELECT
(SELECT MAX(ID) FROM Table) + ROW_NUMBER()OVER (ORDER BY ID),
10500,
Name,
link
FROM Table
WHERE GroupID =10400
SET IDENTITY_INSERT Table OFF
this gives me the following table
**ID | GroupID | Link**
3 | 10400 |/testsDatas/10400/Uploads
4 | 10500 |/testsDatas/10400/Uploads //this is a new entry that the above query will enter.
The question I have is when the above query copies a row how can I change /testsDatas/10400/ to /testsDatas/10500/?
so that it looks like the following
**ID | GroupID | Link**
3 | 10400 |/testsDatas/10400/Uploads
4 | 10500 |/testsDatas/10500/Uploads //desired output
there is mulitple rows of data,with more columns that I did not add.How do I achieve this?
Would using REPLACE work for you? A simple example:]
DECLARE #table TABLE ( ID INT, name VARCHAR(50), link VARCHAR(50) )
INSERT INTO #table
VALUES
( 3, '10400', '/testsDatas/10400/Uploads' )
INSERT INTO #table
SELECT
(
SELECT MAX(ID)
FROM #table
) + ROW_NUMBER() OVER (ORDER BY ID),
10500,
REPLACE( link, name, 10500 )
FROM #table
SELECT *
FROM #table
My results:

How to transform SQL Server row data to columns?

I queried data like this
member_no cover_version product_id product_name product_type
--------- ------------- ---------- -------------------- ------------
11421 7 4 Excellent More E
11421 7 15 Comprehensive Data D
But I want to shape this data like this:
member_no cover_version product_e_id product_e_name product_d_id product_d_name
--------- ------------- ------------ -------------------- ------------ --------------------
11421 7 4 Excellent More 15 Comprehensive Data
I am using SQL Server 2008. What should be the best approach to shape the data as I want?
Assuming you've only got product types D and E as stated, a simple self-join will get you what you're after.
If you want something more generic, please expand your question.
DROP TABLE IF EXISTS #Demo
SELECT
*
INTO
#Demo
FROM
(VALUES
(11421, 7, 4, 'Excellent More', 'E')
,(11421, 7, 15, 'Comprehensive Data', 'D')) A
(member_no, cover_version, product_id, product_name, product_type)
SELECT
D.member_no
,D.cover_version
,E.product_id product_e_id
,E.product_name product_e_name
,D.product_id product_d_id
,D.product_name product_d_name
FROM
#Demo D
JOIN #Demo E ON D.member_no = E.member_no
AND D.product_type = 'D'
AND E.product_type = 'E';
member_no cover_version product_e_id product_e_name product_d_id product_d_name
----------- ------------- ------------ ------------------ ------------ ------------------
11421 7 4 Excellent More 15 Comprehensive Data
If you want to do this dynamically, based on unknown product types, you can use this.
DECLARE #SQL NVARCHAR(MAX),
#Columns NVARCHAR(MAX),
#CaseExpressions NVARCHAR(MAX) = 'MAX(CASE WHEN product_type = ''<<productType>>'' THEN product_id END) AS [product_<<productType>>_id],
MAX(CASE WHEN product_type = ''<<productType>>'' THEN product_name END) AS [product_<<productType>>_name]'
-- build concatenated string with 2 columns for each product_type in your table.
-- group by product_type to get distinct results.
-- replaces <<productType>> with the actual product_type value
SELECT #Columns = COALESCE(#Columns + ',', '') + REPLACE(#CaseExpressions, '<<productType>>', product_type)
FROM myTable
GROUP BY product_type
-- build select query
SET #SQL = 'SELECT member_no, cover_version,' + #Columns + ' FROM myTable GROUP BY member_no, cover_version'
-- to see what the dynamic sql looks like
PRINT #SQL
-- to execute the dynamic sql and see result
EXEC (#SQL)

SQL Server Pivot Table with multiple column with dates

I have a PIVOT situation.
Source table columns:
Title Description Datetime RecordsCount
A California 2015-07-08 10:44:39.040 5
A California 2015-07-08 12:44:39.040 6
A California 2015-05-08 15:44:39.040 3
B Florida 2015-07-08 16:44:39.040 2
B Florida 2015-05-08 19:44:39.040 4
Now I need this pivoted as
2015-07-08 2015-05-08
Title Description
A California 11 3
B Florida 2 4
if we have two record counts on same dates (no matter of time) then sum them, else display in different column.
Trying to write something like this, but it throws errors.
Select * from #DataQualTest
PIVOT (SUM(RecordCount) FOR DateTime IN (Select Datetime from #DataQualTest) )
AS Pivot_Table
Please help me out with this.
Thanks
Not exactly the word for word solution but this should give you a direction.
create table #tmp
(
country varchar(max)
, date1 datetime
, record int
)
insert into #tmp values ('California', '2010-01-01', 2)
insert into #tmp values ('California', '2010-01-01', 5)
insert into #tmp values ('California', '2012-01-01', 1)
insert into #tmp values ('Florida', '2010-01-01', 3)
insert into #tmp values ('Florida', '2010-01-01', 5)
select * from #tmp
pivot (sum(record) for date1 in ([2010-01-01], [2012-01-01])) as avg
output
country 2010-01-01 2012-01-01
California 7 1
Florida 8 NULL
If you want to be more flexible, you need some pre-processing to get from full timestamps to days (in order for later on the PIVOT's grouping to actually have the anticipated effect):
CREATE VIEW DataQualTestView AS
SELECT
title
, description
, DATEFROMPARTS (DATEPART(yyyy, date_time),
DATEPART(mm, date_time),
DATEPART(dd, date_time)) AS day_from_date_time
, recordsCount
FROM DataQualTest
;
From there you could continue:
DECLARE #query AS NVARCHAR(MAX)
DECLARE #columns AS NVARCHAR(MAX)
SELECT #columns = ISNULL(#columns + ',' , '')
+ QUOTENAME(day_from_date_time)
FROM (SELECT DISTINCT
day_from_date_time
FROM DataQualTestView) AS TheDays
SET #query =
N'SELECT
title
, description
, ' + #columns + '
FROM DataQualTestView
PIVOT(SUM(recordsCount)
FOR day_from_date_time IN (' + #columns + ')) AS Pivoted'
EXEC SP_EXECUTESQL #query
GO
... and would get:
| title | description | 2015-05-08 | 2015-07-08 |
|-------|-------------|------------|------------|
| A | California | 3 | 11 |
| B | Florida | 4 | 2 |
See it in action: SQL Fiddle.
Please comment, if and as this requires adjustment / further detail.

Understanding PIVOT function in T-SQL

I am very new to SQL.
I have a table like this:
ID | TeamID | UserID | ElementID | PhaseID | Effort
-----------------------------------------------------
1 | 1 | 1 | 3 | 5 | 6.74
2 | 1 | 1 | 3 | 6 | 8.25
3 | 1 | 1 | 4 | 1 | 2.23
4 | 1 | 1 | 4 | 5 | 6.8
5 | 1 | 1 | 4 | 6 | 1.5
And I was told to get data like this
ElementID | PhaseID1 | PhaseID5 | PhaseID6
--------------------------------------------
3 | NULL | 6.74 | 8.25
4 | 2.23 | 6.8 | 1.5
I understand I need to use PIVOT function. But can't understand it clearly.
It would be great help if somebody can explain it in above case.(or any alternatives if any)
A PIVOT used to rotate the data from one column into multiple columns.
For your example here is a STATIC Pivot meaning you hard code the columns that you want to rotate:
create table temp
(
id int,
teamid int,
userid int,
elementid int,
phaseid int,
effort decimal(10, 5)
)
insert into temp values (1,1,1,3,5,6.74)
insert into temp values (2,1,1,3,6,8.25)
insert into temp values (3,1,1,4,1,2.23)
insert into temp values (4,1,1,4,5,6.8)
insert into temp values (5,1,1,4,6,1.5)
select elementid
, [1] as phaseid1
, [5] as phaseid5
, [6] as phaseid6
from
(
select elementid, phaseid, effort
from temp
) x
pivot
(
max(effort)
for phaseid in([1], [5], [6])
)p
Here is a SQL Demo with a working version.
This can also be done through a dynamic PIVOT where you create the list of columns dynamically and perform the PIVOT.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.phaseid)
FROM temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT elementid, ' + #cols + ' from
(
select elementid, phaseid, effort
from temp
) x
pivot
(
max(effort)
for phaseid in (' + #cols + ')
) p '
execute(#query)
The results for both:
ELEMENTID PHASEID1 PHASEID5 PHASEID6
3 Null 6.74 8.25
4 2.23 6.8 1.5
These are the very basic pivot example kindly go through that.
SQL SERVER – PIVOT and UNPIVOT Table Examples
Example from above link for the product table:
SELECT PRODUCT, FRED, KATE
FROM (
SELECT CUST, PRODUCT, QTY
FROM Product) up
PIVOT (SUM(QTY) FOR CUST IN (FRED, KATE)) AS pvt
ORDER BY PRODUCT
renders:
PRODUCT FRED KATE
--------------------
BEER 24 12
MILK 3 1
SODA NULL 6
VEG NULL 5
Similar examples can be found in the blog post Pivot tables in SQL Server. A simple sample
Ive something to add here which no one mentioned.
The pivot function works great when the source has 3 columns: One for the aggregate, one to spread as columns with for, and one as a pivot for row distribution. In the product example it's QTY, CUST, PRODUCT.
However, if you have more columns in the source it will break the results into multiple rows instead of one row per pivot based on unique values per additional column (as Group By would do in a simple query).
See this example, ive added a timestamp column to the source table:
Now see its impact:
SELECT CUST, MILK
FROM Product
-- FROM (SELECT CUST, Product, QTY FROM PRODUCT) p
PIVOT (
SUM(QTY) FOR PRODUCT IN (MILK)
) AS pvt
ORDER BY CUST
In order to fix this, you can either pull a subquery as a source as everyone has done above - with only 3 columns (this is not always going to work for your scenario, imagine if you need to put a where condition for the timestamp).
Second solution is to use a group by and do a sum of the pivoted column values again.
SELECT
CUST,
sum(MILK) t_MILK
FROM Product
PIVOT (
SUM(QTY) FOR PRODUCT IN (MILK)
) AS pvt
GROUP BY CUST
ORDER BY CUST
GO
A pivot is used to convert one of the columns in your data set from rows into columns (this is typically referred to as the spreading column). In the example you have given, this means converting the PhaseID rows into a set of columns, where there is one column for each distinct value that PhaseID can contain - 1, 5 and 6 in this case.
These pivoted values are grouped via the ElementID column in the example that you have given.
Typically you also then need to provide some form of aggregation that gives you the values referenced by the intersection of the spreading value (PhaseID) and the grouping value (ElementID). Although in the example given the aggregation that will be used is unclear, but involves the Effort column.
Once this pivoting is done, the grouping and spreading columns are used to find an aggregation value. Or in your case, ElementID and PhaseIDX lookup Effort.
Using the grouping, spreading, aggregation terminology you will typically see example syntax for a pivot as:
WITH PivotData AS
(
SELECT <grouping column>
, <spreading column>
, <aggregation column>
FROM <source table>
)
SELECT <grouping column>, <distinct spreading values>
FROM PivotData
PIVOT (<aggregation function>(<aggregation column>)
FOR <spreading column> IN <distinct spreading values>));
This gives a graphical explanation of how the grouping, spreading and aggregation columns convert from the source to pivoted tables if that helps further.
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>;
USE AdventureWorks2008R2 ;
GO
SELECT DaysToManufacture, AVG(StandardCost) AS AverageCost
FROM Production.Product
GROUP BY DaysToManufacture;
DaysToManufacture AverageCost
0 5.0885
1 223.88
2 359.1082
4 949.4105
-- 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;
Here is the result set.
Cost_Sorted_By_Production_Days 0 1 2 3 4
AverageCost 5.0885 223.88 359.1082 NULL 949.4105
To set Compatibility error
use this before using pivot function
ALTER DATABASE [dbname] SET COMPATIBILITY_LEVEL = 100
FOR XML PATH might not work on Microsoft Azure Synapse Serve. A possible alternative, following #Taryn dynamic generated cols approach, same results is obtained by using STRING_AGG.
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols = STRING_AGG(QUOTENAME(c.phaseid),', ')
/*OPTIONAL: within group (order by cast(t1.[FLOW_SP_SLPM] as INT) asc)*/
FROM (SELECT phaseid FROM temp
GROUP BY phaseid) c
set #query = 'SELECT elementid,' + #cols + ' from
(
select elementid,
phaseid,
effort
from temp
) x
PIVOT
(
max(effort)
for phaseid in (' + #cols + ')
) p '
execute(#query)

SQL Server Pivots: Displaying row values to column headers

I have a table (items) which is in the following format:
ITEMNO | WEEKNO | VALUE
A1234 | 1 | 805
A2345 | 2 | 14.50
A3547 | 2 | 1396.70
A2208 | 1 | 17.65
A4326 | 6 | 19.99
It's a table which shows the value of sales for items in a given week.
The results or what I want to display in a table format is the item number in a row followed by columns for each week containing the values, e.g.
ITEMNO | WK1 | WK2 | WK3 | WK4 | WK5 ...etc up to 52
A1234 | 805 | 345 | 234 | 12 | 10 ...etc up to 52
A2345 | 23 | 12 | 456 | 34 | 99 ...etc up to 52
A3456 | 234 | 123 | 34 | 25 | 190 ...etc up to 52
Although I've 52...so I've only data for up to week9 but that will increase with time.
So basically what it is I'm looking to display is the week number value as a column header.
Is this possible...although I'm tempted to just grab the data and display properly through code/(asp.net) but I was wondering if there was away to display it like this in SQL?
Does anyone know or think that that this might be the best way?
There are two ways of doing this with static SQL and dynamic SQL:
Static Pivot:
SELECT P.ItemNo, IsNull(P.[1], 0) as Wk1, IsNull(P.[2], 0) as Wk2
, IsNull(P.[3], 0) as Wk3, IsNull(P.[4], 0) as Wk4
, IsNull(P.[5], 0) as Wk5, IsNull(P.[6], 0) as Wk6
, IsNull(P.[7], 0) as Wk7, IsNull(P.[8], 0) as Wk8
, IsNull(P.[9], 0) as Wk9
FROM
(
SELECT ItemNo, WeekNo, [Value]
FROM dbo.Items
) I
PIVOT
(
SUM([Value])
FOR WeekNo IN ([1], [2], [3], [4], [5], [6], [7], [8], [9])
) as P
Dynamic Pivot:
DECLARE
#cols AS NVARCHAR(MAX),
#y AS INT,
#sql AS NVARCHAR(MAX);
-- Construct the column list for the IN clause
SET #cols = STUFF(
(SELECT N',' + QUOTENAME(w) AS [text()]
FROM (SELECT DISTINCT WeekNo AS W FROM dbo.Items) AS W
ORDER BY W
FOR XML PATH('')),
1, 1, N'');
-- Construct the full T-SQL statement
-- and execute dynamically
SET #sql = N'SELECT *
FROM (SELECT ItemNo, WeekNo, Value
FROM dbo.Items) AS I
PIVOT(SUM(Value) FOR WeekNo IN(' + #cols + N')) AS P;';
EXEC sp_executesql #sql;
GO
Maybe something like this:
Test data
CREATE TABLE #tbl
(
ITEMNO VARCHAR(100),
WEEKNO INT,
VALUE FLOAT
)
INSERT INTO #tbl
VALUES
('A1234',1,805),
('A2345',2,14.50),
('A3547',2,1396.70),
('A2208',1,17.65),
('A4326',6,19.99)
Week columns
DECLARE #cols VARCHAR(MAX)
;WITH Nbrs ( n ) AS (
SELECT 1 UNION ALL
SELECT 1 + n FROM Nbrs WHERE n < 52 )
SELECT #cols = COALESCE(#cols + ','+QUOTENAME('WK'+CAST(n AS VARCHAR(2))),
QUOTENAME('WK'+CAST(n AS VARCHAR(2))))
FROM
Nbrs
Just the included weeks
DECLARE #cols VARCHAR(MAX)
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY WEEKNO ORDER BY WEEKNO) AS RowNbr,
WEEKNO
FROM
#tbl
)
SELECT #cols = COALESCE(#cols + ','+QUOTENAME('WK'+CAST(WEEKNO AS VARCHAR(2))),
QUOTENAME('WK'+CAST(WEEKNO AS VARCHAR(2))))
FROM
CTE
WHERE
CTE.RowNbr=1
Dynamic pivot
DECLARE #query NVARCHAR(4000)=
N'SELECT
*
FROM
(
SELECT
tbl.ITEMNO,
''WK''+CAST(tbl.WEEKNO AS VARCHAR(2)) AS WEEKNO,
tbl.VALUE
FROM
#tbl as tbl
) AS p
PIVOT
(
SUM(VALUE)
FOR WEEKNO IN ('+#cols+')
) AS pvt'
EXECUTE(#query)
Drop the temp table
DROP TABLE #tbl
Use Pivot, although quite a bit of code..
If you create report in reporting services, can use matrix..
Follow the below walkthrogh which explains it clearly
http://www.tsqltutorials.com/pivot.php
You can use PIVOT if you want to do this in sql directly.
It can be more efficient to use the SQL Server to do this as opposed to the client depending upon the size of the data and the aggregation.

Resources