Unpivot Multiple Columns in into Rows - sql

I did some searching and didn't come up with a good answer. I am trying to create a table from data and then Unpivot it in SQL Server, but can't get the columns and rows required.
IF OBJECT_ID('tempdb..##EXPECT_PERCENTS') IS NOT NULL
DROP TABLE ##EXPECT_PERCENTS
CREATE TABLE ##EXPECT_PERCENTS (
PAYOR_ID VARCHAR(40)
,TEST_TYPE VARCHAR(40)
,[2011-08-31] INT
,[2011-09-30] INT
)
GO
INSERT INTO ##EXPECT_PERCENTS VALUES('UHC','UDT','1','2');
select * from ##EXPECT_PERCENTS
SELECT PAYOR_ID, TEST_TYPE, EXPECT
FROM ##EXPECT_PERCENTS
UNPIVOT
(
EXPECT FOR EXPECTS IN ([2011-08-31],[2011-09-30])
) AS u
I am trying to unpivot the dates as well so that there is another field called "Date" with the two dates representing each number. The first line should be:
UHC UDT 1 2011-08-31

Needing to unpivot multiple columns can often be easier without the unpivot command. One option is to use CROSS APPLY:
select payor_id, test_type, expect, expectdate
from expect_percents
cross apply
(
select [2011-08-31],'2011-08-31' union all
select [2011-09-30],'2011-09-30'
) c (expect, expectdate);
SQL Fiddle Demo
Another option is to use UNION ALL:
select payor_id, test_type, [2011-08-31] expect, '2011-08-31' date
from expect_percents
union all
select payor_id, test_type, [2011-09-30] expect, '2011-09-30' date
from expect_percents;
More Fiddle

Related

How to transform the table columns to vertical data in Sql server tables?

I would like to transform one Sql server table into another.
Original table
Period Date Portfolio Benchmark
Pre0Month 12/31/2014 -0.0001 -0.0025
Pre1Month 11/31/2014 0.0122 0.0269
Pre2Month 10/31/2014 0.0176 0.0244
After transformation
Returns Pre0Month Pre1Month Pre2Month
Portfolio -0.0001 0.0122 0.0176
Benchmark -0.0025 0.0269 0.0244
Considering the name of the table to be MyTable, you can pivot it the following way:
SELECT * FROM
(
SELECT Period, [Returns], value
FROM MyTable
CROSS APPLY
(
SELECT 'Portofolio', CAST(Portofolio as varchar(10))
UNION ALL
SELECT 'Benchmark', CAST(Benchmark as varchar(10))
) c([Returns], value)
) d
PIVOT
(
MAX(value)
FOR Period IN (Pre0Month, Pre1Month, Pre2Month)
) piv;
This requires a combination of PIVOT and UNPIVOT:
DECLARE #t TABLE(period VARCHAR(32),[date] DATETIME, portfolio DECIMAL(28,4), benchmark DECIMAL(28,4));
INSERT INTO #t(period,[date],portfolio,benchmark)VALUES('Pre0Month','2014-12-31',-0.0001,-0.0025);
INSERT INTO #t(period,[date],portfolio,benchmark)VALUES('Pre1Month','2014-11-30',0.0122,0.0269);
INSERT INTO #t(period,[date],portfolio,benchmark)VALUES('Pre2Month','2014-10-31',0.0176,0.0244);
SELECT
*
FROM
(
SELECT
*
FROM
(
SELECT
period,
portfolio,
benchmark
FROM
#t
) AS t
UNPIVOT(
value
FOR Returns IN (portfolio,benchmark)
) AS up
) AS t
PIVOT(
MAX(value)
FOR period IN ([Pre0Month],[Pre1Month],[Pre2Month])
) AS p;
Result is the following:
Returns Pre0Month Pre1Month Pre2Month
benchmark -0.0025 0.0269 0.0244
portfolio -0.0001 0.0122 0.0176
Because you are using SQL-Server you can use the pivot command to do what you want. check it out here: https://technet.microsoft.com/en-us/library/ms177410%28v=sql.105%29.aspx
You can use the DateDiff function to separate the dates out by month easily too. https://msdn.microsoft.com/en-us/library/ms189794.aspx

Unpivoting multiple columns

I have a table in SQL Server 2014 called anotes with the following data
and I want to add this data into another table named final as
ID Notes NoteDate
With text1, text2, text3, text4 going into the Notes column in the final table and Notedate1,notedate2,notedate3,notedate4 going into Notedate column.
I tried unpivoting the data with notes first as:
select createdid, temp
from (select createdid,text1,text2,text3,text4 from anotes) p
unpivot
(temp for note in(text1,text2,text3,text4)) as unpvt
order by createdid
Which gave me proper results:
and then for the dates part I used another unpivot query:
select createdid,temp2
from (select createdid,notedate1,notedate2,notedate3,notedate4 from anotes) p
unpivot (temp2 for notedate in(notedate1,notedate2,notedate3,notedate4)) as unpvt2
which also gives me proper results:
Now I want to add this data into my final table.
and I tried the following query and it results into a cross join :(
select a.createdid, a.temp, b.temp2
from (select createdid, temp
from (select createdid,text1,text2,text3,text4 from anotes) p
unpivot
(temp for note in(text1,text2,text3,text4)) as unpvt) a inner join (select createdid,temp2
from (select createdid,notedate1,notedate2,notedate3,notedate4 from anotes) p
unpivot (temp2 for notedate in(notedate1,notedate2,notedate3,notedate4)) as unpvt) b on a.createdid=b.createdid
The output is as follows:
Is there any way where I can unpivot both the columns at the same time?
Or use two select queries to add that data into my final table?
Thanks in advance!
I would say the most concise, and probably most efficient way to unpivot multiple columns is to use CROSS APPLY along with a table valued constructor:
SELECT t.CreatedID, upvt.Text, upvt.NoteDate
FROM anotes t
CROSS APPLY
(VALUES
(Text1, NoteDate1),
(Text2, NoteDate2),
(Text3, NoteDate3),
(Text4, NoteDate4),
(Text5, NoteDate5),
(Text6, NoteDate6),
(Text7, NoteDate7)
) upvt (Text, NoteDate);
Simplified Example on SQL Fiddle
ADDENDUM
I find the concept quite a hard one to explain, but I'll try. A table valued constuctor is simply a way of defining a table on the fly, so
SELECT *
FROM (VALUES (1, 1), (2, 2)) t (a, b);
Will Create a table with Alias t with data:
a b
------
1 1
2 2
So when you use it inside the APPLY you have access to all the outer columns, so it is just a matter of defining your constructed tables with the correct pairs of values (i.e. text1 with date1).
Used the link above mentioned by #AHiggins
Following is my final query!
select createdid,temp,temp2
from (select createdid,text1,text2,text3,text4,text5,text6,text7,notedate1,notedate2,notedate3,notedate4,notedate5,notedate6,notedate7 from anotes) main
unpivot
(temp for notes in(text1,text2,text3,text4,text5,text6,text7)) notes
unpivot (temp2 for notedate in(notedate1,notedate2,notedate3,notedate4,notedate5,notedate6,notedate7)) Dates
where RIGHT(notes,1)=RIGHT(notedate,1)
Treat each query as a table and join them together based on the createdid and the fieldid (the numeric part of the field name).
select x.createdid, x.textValue, y.dateValue
from
(
select createdid, substring(note, 5, len(note)) fieldId, textValue
from (select createdid,text1,text2,text3,text4 from anotes) p
unpivot
(textValue for note in(text1,text2,text3,text4)) as unpvt
)x
join
(
select createdid, substring(notedate, 9, len(notedate)) fieldId, dateValue
from (select createdid,notedate1,notedate2,notedate3,notedate4 from anotes) p
unpivot (dateValue for notedate in(notedate1,notedate2,notedate3,notedate4)) as unpvt2
) y on x.fieldId = y.fieldId and x.createdid = y.createdid
order by x.createdid, x.fieldId
The other answer given won't work if you have too many columns and the rightmost number of the field name is duplicated (e.g. text1 and text11).

Converting a pivot table to a flat table in SQL

I would like to transform a pivot table into a flat table, but in the following fashion: consider the simple example of this table:
As you can see, for each item - Address or Income -, we have a column for old values, and a column for new (updated values). I would like to convert the table to a "flat" table, looking like:
Is there an easy way of doing that?
Thank you for your help!
In order to get the result, you will need to UNPIVOT the data. When you unpivot you convert the multiple columns into multiple rows, in doing so the datatypes of the data must be the same.
I would use CROSS APPLY to unpivot the columns in pairs:
select t.employee_id,
t.employee_name,
c.data,
c.old,
c.new
from yourtable t
cross apply
(
values
('Address', Address_Old, Address_new),
('Income', cast(income_old as varchar(15)), cast(income_new as varchar(15)))
) c (data, old, new);
See SQL Fiddle with demo. As you can see this uses a cast on the income columns because I am guessing it is a different datatype from the address. Since the final result will have these values in the same column the data must be of the same type.
This can also be written using CROSS APPLY with UNION ALL:
select t.employee_id,
t.employee_name,
c.data,
c.old,
c.new
from yourtable t
cross apply
(
select 'Address', Address_Old, Address_new union all
select 'Income', cast(income_old as varchar(15)), cast(income_new as varchar(15))
) c (data, old, new)
See Demo
select employee_id,employee_name,data,old,new
from (
select employee_id,employee_name,adress_old as old,adress_new as new,'ADRESS' as data
from employe
union
select employee_id,employee_name,income_old,income_new,'INCOME'
from employe
) data
order by employee_id,data
see this fiddle demo : http://sqlfiddle.com/#!2/64344/7/0

Is it possible to group all the related elements using a SQL statement

I want a SQL statement which can run on all the platforms and give the result in tree structure (not a real tree structure though)format.i.e all the related columns appears together. is it possible to achieve the following format using a sql. I have a simple table with three columns(GROUP_STEP,PREDECESSOR,COLUMNNUM). expected output
Platform supported : Oracle, SQL Server, DB2 and Sybase. I am looking for a SELECT statement from the table having following data in different format.
After #diaho suggestion , following is the output
select groupnum,predecessor ,count(groupnum) as columnum group by groupnum,predecessor
if you give more details on table schema answer may change !!!
Based on your table, I'm assuming that you only have the Group_Step and Predecessor columns in your raw table and that the ColumnNum column represents the total number of levels for a leaf in your tree. For example, since PC_Wrap1 has the Predecessor BENEFITS which has the Predecessor COPY_BUDG it has a total of 3 'levels'. If that is the case, you need a recursive query to calculate the ColumnNum value. I can't speak to all platforms, but for SQL Server, you can use a CTE:
EDIT: Removed 'dreaded non-standard square brackets' per a_horse_with_no_name's suggestion :)
-- Setup table
CREATE TABLE #Temp
(
Group_Step VARCHAR(100),
Predecessor VARCHAR(100)
)
-- Setup dummy data
INSERT INTO #Temp
(
Group_Step,
Predecessor
)
SELECT 'ACT_BD_ACT', '' UNION
SELECT 'COPY_BUDG', '' UNION
SELECT 'COPY_BUDG2', '' UNION
SELECT 'BENEFITS', 'COPY_BUDG' UNION
SELECT 'BENEFITS', 'COPY_BUDG2' UNION
SELECT 'PC_WRAP1', 'BENEFITS' UNION
SELECT 'PC_WRAP2', 'BENEFITS' UNION
SELECT 'ALLC1', '' UNION
SELECT 'ALLC2', '' UNION
SELECT 'ALLC3', 'ALLC2' UNION
SELECT 'TCP1', 'ALLC3' UNION
SELECT 'TCP1', 'ALLC4' UNION
SELECT 'COPY_BUDG3', '' UNION
SELECT 'COPY_BUDG4', '';
-- Actual solution starts here:
WITH Result
(
Group_Step,
Predecessor,
ColumnNum
)
AS
(
-- Anchor member definition
SELECT
Group_Step,
Predecessor AS Predecessor,
1 AS ColumnNum
FROM
#Temp
WHERE
Predecessor = ''
UNION ALL
-- Recursive member definition
SELECT
t.Group_Step,
t.Predecessor,
ColumnNum + 1 AS ColumnNum
FROM
#Temp AS t
JOIN
Result AS r
ON
t.Predecessor = r.Group_Step
)
-- Statement that executes the CTE
SELECT DISTINCT
Group_Step,
Predecessor,
ColumnNum
FROM
Result
-- EDIT #2: Adding ORDER BY per Op's comment
ORDER BY
ColumnNum
DROP TABLE #Temp

SQL Partition Elimination

I am currently testing a partitioning configuration, using actual execution plan to identify RunTimePartitionSummary/PartitionsAccessed info.
When a query is run with a literal against the partitioning column the partition elimination works fine (using = and <=). However if the query is joined to a lookup table, with the partitioning column <= to a column in the lookup table and restricting the lookup table with another criteria (so that only one row is returned, the same as if it was a literal) elimination does not occur.
This only seems to happen if the join criteria is <= rather than =, even though the result is the same. Reversing the logic and using between does not work either, nor does using a cross applied function.
Edit: (Repro Steps)
OK here you go!
--Create sample function
CREATE PARTITION FUNCTION pf_Test(date) AS RANGE RIGHT FOR VALUES ('20110101','20110102','20110103','20110104','20110105')
--Create sample scheme
CREATE PARTITION SCHEME ps_Test AS PARTITION pf_Test ALL TO ([PRIMARY])
--Create sample table
CREATE TABLE t_Test
(
RowID int identity(1,1)
,StartDate date NOT NULL
,EndDate date NULL
,Data varchar(50) NULL
)
ON ps_Test(StartDate)
--Insert some sample data
INSERT INTO t_Test(StartDate,EndDate,Data)
VALUES
('20110101','20110102','A')
,('20110103','20110104','A')
,('20110105',NULL,'A')
,('20110101',NULL,'B')
,('20110102','20110104','C')
,('20110105',NULL,'C')
,('20110104',NULL,'D')
--Check partition allocation
SELECT *,$PARTITION.pf_Test(StartDate) AS PartitionNumber FROM t_Test
--Run simple test (inlcude actual execution plan)
SELECT
*
,$PARTITION.pf_Test(StartDate)
FROM t_Test
WHERE StartDate <= '20110103' AND ISNULL(EndDate,getdate()) >= '20110103'
--<PartitionRange Start="1" End="4" />
--Run test with join to a lookup (with CTE for simplicity, but doesnt work with table either)
WITH testCTE AS
(
SELECT convert(date,'20110101') AS CalendarDate,'A' AS SomethingInteresting
UNION ALL
SELECT convert(date,'20110102') AS CalendarDate,'B' AS SomethingInteresting
UNION ALL
SELECT convert(date,'20110103') AS CalendarDate,'C' AS SomethingInteresting
UNION ALL
SELECT convert(date,'20110104') AS CalendarDate,'D' AS SomethingInteresting
UNION ALL
SELECT convert(date,'20110105') AS CalendarDate,'E' AS SomethingInteresting
UNION ALL
SELECT convert(date,'20110106') AS CalendarDate,'F' AS SomethingInteresting
UNION ALL
SELECT convert(date,'20110107') AS CalendarDate,'G' AS SomethingInteresting
UNION ALL
SELECT convert(date,'20110108') AS CalendarDate,'H' AS SomethingInteresting
UNION ALL
SELECT convert(date,'20110109') AS CalendarDate,'I' AS SomethingInteresting
)
SELECT
C.CalendarDate
,T.*
,$PARTITION.pf_Test(StartDate)
FROM t_Test T
INNER JOIN testCTE C
ON T.StartDate <= C.CalendarDate AND ISNULL(T.EndDate,getdate()) >= C.CalendarDate
WHERE C.SomethingInteresting = 'C' --<PartitionRange Start="1" End="6" />
--So all 6 partitions are scanned despite only 2,3,4 being required, as per the simple select.
--edited to make resultant ranges identical to ensure fair test
It makes sense for the query to scan all the partitions.
All partitions are involved in the predicate T.StartDate <= C.CalendarDate, because the query planner can't possibly know which values C.CalendarDate might take.