SQLServerCentral is supported by Red Gate Software Ltd.
 
Log in  ::  Register  ::  Not logged in
Search:  
 
 
        
Home       Members    Calendar    Who's On


«««12345

Trying to UnPivot without using multiple selects unioned together Expand / Collapse
Author
Message
Posted Monday, November 02, 2009 6:10 PM
Ten Centuries

Ten CenturiesTen CenturiesTen CenturiesTen CenturiesTen CenturiesTen CenturiesTen CenturiesTen Centuries

Group: General Forum Members
Last Login: Yesterday @ 1:27 PM
Points: 1,278, Visits: 1,522
Paul White (11/1/2009)
Although the 2008 method is undoubtedly impressive, I'm very aware of the fact that it is a relatively small tweak to the original (awesome) solution from Peter. He should get the credit for the method really.

You know, it is this attitude that I see so much from the "regulars" here, and it's one of the reasons I love this site. But Paul, none of the others participating in this thread saw this... YOU did. YOU realized that this little tweak could be done, and that it would make a huge difference. So Paul, it might have been a "minor" tweak to an already good solution, but YOU made it, and ended up with the best plan yet (so far...). So, take credit where credit is due.

...but I thought I should add the above comments to ensure we are fair in our assessments.

and these are some excellent comments. My question is... how have you figured out all these different options? They aren't used very much...


Wayne
For better assistance in answering your questions, click here
For performance problems, please read this.
Post #812681
Posted Tuesday, November 03, 2009 12:43 PM
SSChasing Mays

SSChasing MaysSSChasing MaysSSChasing MaysSSChasing MaysSSChasing MaysSSChasing MaysSSChasing MaysSSChasing Mays

Group: General Forum Members
Last Login: Yesterday @ 11:17 PM
Points: 612, Visits: 1,433
Paul White (11/1/2009)

3. Peter's method also suffers at the hands of the optimizer for large sets. The lazy spool on the #Update table is a bit silly - we can fix it by adding OPTION (FORCE ORDER) to the query text, or by creating a (very reasonable) PRIMARY KEY on the RowID column of the #Update table.

Finally got the time to look at the test results. Great job Wayne, also very useful comments Paul. I'm learning all the time. About the lazy spool this. In my original cross join solution I used
cross join
(
select 1 column_no union all select 2 union all select 3
) n

which was changed into
      CROSS JOIN (select TOP 30 column_no = row_number() over (order by object_id) from sys.objects) n


for the 30 column problem, which looks like the problem here. The Constant Scan is abandonned and the order of both table scans is reversed.
I suggest using the original code expanded to 30 constants i.e.
       CROSS JOIN (
select 1 column_no union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9 union all
select 10 union all
select 11 union all
select 12 union all
select 13 union all
select 14 union all
select 15 union all
select 16 union all
select 17 union all
select 18 union all
select 19 union all
select 20 union all
select 21 union all
select 22 union all
select 23 union all
select 24 union all
select 25 union all
select 26 union all
select 27 union all
select 28 union all
select 29 union all
select 30
) n

This will again result in a Constant Scan in the inner loop. It takes some coding but it's consistent and no need for query hints.

Peter
Post #813156
Posted Thursday, November 05, 2009 12:42 AM


SSCrazy

SSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazy

Group: General Forum Members
Last Login: Today @ 4:15 AM
Points: 2,069, Visits: 2,040
WayneS (11/2/2009)
You know, it is this attitude that I see so much from the "regulars" here, and it's one of the reasons I love this site. But Paul, none of the others participating in this thread saw this... YOU did. YOU realized that this little tweak could be done, and that it would make a huge difference. So Paul, it might have been a "minor" tweak to an already good solution, but YOU made it, and ended up with the best plan yet (so far...). So, take credit where credit is due.

Ok, ok - appropriate credit taken
Just make sure you take credit for the test rigs, testing, and publishing of the results!

WayneS (11/2/2009)
My question is... how have you figured out all these different options? They aren't used very much...

Just past experience with the optimizer! Once you put yourself in its place, and try to reason out why it made the choices it made, it becomes relatively easy to add appropriate hints to lead it in the right direction - as sometimes is necessary. The optimizer generally makes good choices given the information it has available. Sometimes though, the DBA knows something about the data that can only be passed on to the optimizer through the use of hints, extra statistics, or a constraint (like the PK I used, for example). More on this later.



The quality of the answers is directly proportional to the quality of the question.
Post #814052
Posted Thursday, November 05, 2009 3:34 AM


SSCrazy

SSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazy

Group: General Forum Members
Last Login: Today @ 4:15 AM
Points: 2,069, Visits: 2,040
Here is the 2005-syntax equivalent of the 2008 code I posted (it produces an identical plan on 2005 to the 2008 version):

SELECT  U.row_id, CA.column_nm, CA.old, CA.new
FROM #Update U
CROSS
APPLY (
SELECT 'column01', CONVERT(SQL_VARIANT, U.column01_old), CONVERT(SQL_VARIANT, U.column01_new) UNION ALL
SELECT 'column02', U.column02_old, U.column02_new UNION ALL
SELECT 'column03', U.column03_old, U.column03_new UNION ALL
SELECT 'column04', U.column04_old, U.column04_new UNION ALL
SELECT 'column05', U.column05_old, U.column05_new UNION ALL
SELECT 'column06', U.column06_old, U.column06_new UNION ALL
SELECT 'column07', U.column07_old, U.column07_new UNION ALL
SELECT 'column08', U.column08_old, U.column08_new UNION ALL
SELECT 'column09', U.column09_old, U.column09_new UNION ALL
SELECT 'column10', U.column10_old, U.column10_new UNION ALL
SELECT 'column11', U.column11_old, U.column11_new UNION ALL
SELECT 'column12', U.column12_old, U.column12_new UNION ALL
SELECT 'column13', U.column13_old, U.column13_new UNION ALL
SELECT 'column14', U.column14_old, U.column14_new UNION ALL
SELECT 'column15', U.column15_old, U.column15_new UNION ALL
SELECT 'column16', U.column16_old, U.column16_new UNION ALL
SELECT 'column17', U.column17_old, U.column17_new UNION ALL
SELECT 'column18', U.column18_old, U.column18_new UNION ALL
SELECT 'column19', U.column19_old, U.column19_new UNION ALL
SELECT 'column20', U.column20_old, U.column20_new UNION ALL
SELECT 'column21', U.column21_old, U.column21_new UNION ALL
SELECT 'column22', U.column22_old, U.column22_new UNION ALL
SELECT 'column23', U.column23_old, U.column23_new UNION ALL
SELECT 'column24', U.column24_old, U.column24_new UNION ALL
SELECT 'column25', U.column25_old, U.column25_new UNION ALL
SELECT 'column26', U.column26_old, U.column26_new UNION ALL
SELECT 'column27', U.column27_old, U.column27_new UNION ALL
SELECT 'column28', U.column28_old, U.column28_new UNION ALL
SELECT 'column29', U.column29_old, U.column29_new UNION ALL
SELECT 'column30', U.column30_old, U.column30_new
) CA (column_nm, old, new)
ORDER BY U.row_id, CA.column_nm;




The quality of the answers is directly proportional to the quality of the question.
Post #814095
Posted Thursday, November 05, 2009 3:57 AM


SSCrazy

SSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazy

Group: General Forum Members
Last Login: Today @ 4:15 AM
Points: 2,069, Visits: 2,040
Finally, here's my script for what I believe to be the absolute fastest possible way to populate the table and return the desired results:

The key point is that all the sorts are eliminated...

USE tempdb;
GO
-- Drop the temporary table if it exists
IF OBJECT_ID(N'tempdb..#Update', N'U') IS NOT NULL DROP TABLE #Update;
GO
-- Create the temporary table, complete with primary key
CREATE TABLE #Update
(
row_id BIGINT NOT NULL PRIMARY KEY CLUSTERED,
column01_old DATETIME NOT NULL,
column01_new DATETIME NOT NULL,
column02_old BIT NOT NULL,
column02_new BIT NOT NULL,
column03_old DECIMAL(10,0) NOT NULL,
column03_new DECIMAL(10,0) NOT NULL,
column04_old DATETIME NOT NULL,
column04_new DATETIME NOT NULL,
column05_old BIT NOT NULL,
column05_new BIT NOT NULL,
column06_old DECIMAL(10,0) NOT NULL,
column06_new DECIMAL(10,0) NOT NULL,
column07_old DATETIME NOT NULL,
column07_new DATETIME NOT NULL,
column08_old BIT NOT NULL,
column08_new BIT NOT NULL,
column09_old DECIMAL(10,0) NOT NULL,
column09_new DECIMAL(10,0) NOT NULL,
column10_old DATETIME NOT NULL,
column10_new DATETIME NOT NULL,
column11_old DATETIME NOT NULL,
column11_new DATETIME NOT NULL,
column12_old BIT NOT NULL,
column12_new BIT NOT NULL,
column13_old DECIMAL(10,0) NOT NULL,
column13_new DECIMAL(10,0) NOT NULL,
column14_old DATETIME NOT NULL,
column14_new DATETIME NOT NULL,
column15_old BIT NOT NULL,
column15_new BIT NOT NULL,
column16_old DECIMAL(10,0) NOT NULL,
column16_new DECIMAL(10,0) NOT NULL,
column17_old DATETIME NOT NULL,
column17_new DATETIME NOT NULL,
column18_old BIT NOT NULL,
column18_new BIT NOT NULL,
column19_old DECIMAL(10,0) NOT NULL,
column19_new DECIMAL(10,0) NOT NULL,
column20_old DATETIME NOT NULL,
column20_new DATETIME NOT NULL,
column21_old DATETIME NOT NULL,
column21_new DATETIME NOT NULL,
column22_old BIT NOT NULL,
column22_new BIT NOT NULL,
column23_old DECIMAL(10,0) NOT NULL,
column23_new DECIMAL(10,0) NOT NULL,
column24_old DATETIME NOT NULL,
column24_new DATETIME NOT NULL,
column25_old BIT NOT NULL,
column25_new BIT NOT NULL,
column26_old DECIMAL(10,0) NOT NULL,
column26_new DECIMAL(10,0) NOT NULL,
column27_old DATETIME NOT NULL,
column27_new DATETIME NOT NULL,
column28_old BIT NOT NULL,
column28_new BIT NOT NULL,
column29_old DECIMAL(10,0) NOT NULL,
column29_new DECIMAL(10,0) NOT NULL,
column30_old DATETIME NOT NULL,
column30_new DATETIME NOT NULL,
);
GO
-- Generate test rows (5,000 in this example)
-- There is *no sort* in the plan!
;WITH CTE AS
(
SELECT TOP (5000)
n = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
master.sys.allocation_units A1,
master.sys.allocation_units A2,
master.sys.allocation_units A3
ORDER BY
n ASC
)
INSERT #Update
SELECT CTE.N,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'),
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'),
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01'), 0, 1, CTE.N + 0.1, CTE.N + 0.3,
DATEADD(MINUTE, CTE.N, '2000-01-01'), DATEADD(HOUR, CTE.N, '2000-01-01')
FROM CTE
ORDER BY n ASC;
GO
-- The final query (again no sort!)
SELECT U.row_id, CA.column_nm, CA.old, CA.new
FROM #Update U WITH (TABLOCK)
CROSS
APPLY (
SELECT 'column01', CONVERT(SQL_VARIANT, U.column01_old), CONVERT(SQL_VARIANT, U.column01_new) UNION ALL
SELECT 'column02', U.column02_old, U.column02_new UNION ALL
SELECT 'column03', U.column03_old, U.column03_new UNION ALL
SELECT 'column04', U.column04_old, U.column04_new UNION ALL
SELECT 'column05', U.column05_old, U.column05_new UNION ALL
SELECT 'column06', U.column06_old, U.column06_new UNION ALL
SELECT 'column07', U.column07_old, U.column07_new UNION ALL
SELECT 'column08', U.column08_old, U.column08_new UNION ALL
SELECT 'column09', U.column09_old, U.column09_new UNION ALL
SELECT 'column10', U.column10_old, U.column10_new UNION ALL
SELECT 'column11', U.column11_old, U.column11_new UNION ALL
SELECT 'column12', U.column12_old, U.column12_new UNION ALL
SELECT 'column13', U.column13_old, U.column13_new UNION ALL
SELECT 'column14', U.column14_old, U.column14_new UNION ALL
SELECT 'column15', U.column15_old, U.column15_new UNION ALL
SELECT 'column16', U.column16_old, U.column16_new UNION ALL
SELECT 'column17', U.column17_old, U.column17_new UNION ALL
SELECT 'column18', U.column18_old, U.column18_new UNION ALL
SELECT 'column19', U.column19_old, U.column19_new UNION ALL
SELECT 'column20', U.column20_old, U.column20_new UNION ALL
SELECT 'column21', U.column21_old, U.column21_new UNION ALL
SELECT 'column22', U.column22_old, U.column22_new UNION ALL
SELECT 'column23', U.column23_old, U.column23_new UNION ALL
SELECT 'column24', U.column24_old, U.column24_new UNION ALL
SELECT 'column25', U.column25_old, U.column25_new UNION ALL
SELECT 'column26', U.column26_old, U.column26_new UNION ALL
SELECT 'column27', U.column27_old, U.column27_new UNION ALL
SELECT 'column28', U.column28_old, U.column28_new UNION ALL
SELECT 'column29', U.column29_old, U.column29_new UNION ALL
SELECT 'column30', U.column30_old, U.column30_new
) CA (column_nm, old, new)
ORDER BY U.row_id
OPTION (MAXDOP 1);




I have coded this example for 5K rows (my laptop is no match for Wayne's machine!) but nothing changes with 1M rows...or more.

Paul



The quality of the answers is directly proportional to the quality of the question.


  Post Attachments 
No Sort Query.jpg (51 views, 31.84 KB)
No Sort Population.jpg (34 views, 19.93 KB)
Post #814105
Posted Friday, November 06, 2009 6:31 PM


SSCrazy

SSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazy

Group: General Forum Members
Last Login: Yesterday @ 4:58 PM
Points: 2,279, Visits: 2,832
Wow... I wander off and enjoy the PASS Summit and come back to find this !? Why don't one of you guys take this opportunity to write a "Beyond Unpivot" article?

__________________________________________________

Against stupidity the gods themselves contend in vain. -- Friedrich Schiller
Whenever possible, I prefer to eat the rude. -- Hannibal Lecter
Post #815266
Posted Friday, November 06, 2009 9:24 PM


SSCrazy

SSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazy

Group: General Forum Members
Last Login: Today @ 4:15 AM
Points: 2,069, Visits: 2,040
Hey Bob,

I think we pretty much did already

Glad you enjoyed PASS.

Paul



The quality of the answers is directly proportional to the quality of the question.
Post #815298
Posted Friday, November 06, 2009 11:16 PM


SSCrazy

SSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazySSCrazy

Group: General Forum Members
Last Login: Yesterday @ 4:58 PM
Points: 2,279, Visits: 2,832
I'll say you did. The end result is sweet, too.

__________________________________________________

Against stupidity the gods themselves contend in vain. -- Friedrich Schiller
Whenever possible, I prefer to eat the rude. -- Hannibal Lecter
Post #815331
« Prev Topic | Next Topic »

«««12345

Permissions Expand / Collapse