Tutorials
Practical T-SQL
Practical T-SQL
  • Practical T-SQL Pocket Guide For Beginners
  • Preface
    • Section I. A Note From The Author
    • Section II. Tutorial Overview
    • Section III. Running The Examples
    • Section IV. How To Report An Issue
    • Section V. Join The MSU Community On Discord
    • Section VI. Supplementary Material
  • Language Basics
    • Lesson 1. Obligatory Hello World
    • Lesson 2. Code Comments With T-SQL
    • Lesson 3. Basic Syntax
    • Lesson 4. Your First Query
    • Lesson 5. Filtering Data
    • Lesson 6. Sorting Data
    • Lesson 7. Complex Data Filtering
    • Lesson 8. Aliases
    • Lesson 9. String Functions
    • Lesson 10. Creating New Columns From Existing Data (Calculated Fields)
    • Lesson 11. Displaying Data Based On Conditions (Case Statement)
    • Lesson 12. Aggregate Functions
    • Lesson 13. Grouping And Summarizing Data
    • Lesson 14. Querying More Than One Table
    • Lesson 15. Combining Queries
    • Lesson 16. Subqueries
    • Lesson 17. Creating Data
    • Lesson 18. Updating Data
    • Lesson 19. Deleting Data
    • Lesson 20. Common Table Expressions (CTEs)
    • Lesson 21. Derived Tables
    • Lesson 22. Putting It All Together
  • Advanced Topics
    • Lesson 23. Selecting Unique Values
    • Lesson 24. Updating Data With A Join
    • Lesson 25. Data Types
    • Lesson 26. Casting Data Types
    • Lesson 27. Creating Tables
    • Lesson 28. Altering Tables
    • Lesson 29. Dropping Tables
    • Lesson 30. Variables
    • Lesson 31. Controlling Flow
    • Lesson 32. Looping
    • Lesson 33. Error Processing
    • Lesson 34. Temporary Tables
    • Lesson 35. Views
    • Lesson 36. Indexed Views
    • Lesson 37. User Defined Functions
    • Lesson 38. Stored Procedures
    • Lesson 39. BULK INSERT
    • Lesson 40. Loading Tables With MERGE
    • Lesson 41. Partitioning A Dataset
    • Lesson 42. Pivoting Data
    • Lesson 43. Dynamic SQL
    • Lesson 44. Cursors
  • Solutions To Real World Problems
    • Lesson 45. Listing All Tables In A SQL Server Database
    • Lesson 46. Listing All Columns In A SQL Server Database
    • Lesson 47. Pull Records From A Table At Random
    • Lesson 48. A Better Alternative To WITH (NOLOCK)
    • Lesson 49. Boost Performance When Calling A Stored Proc From SSIS
    • Lesson 50. Setting Up Queries For Ablation Testing
    • Lesson 51. Reduce Code And Save Time With Default Column Values
    • Lesson 52. Finding Duplicate Records In A Table
    • Lesson 53. Why You Cannot Have More Than One Clustered Index On A Table
    • Lesson 54. Converting Dates To YYYYMMDD
    • Lesson 55. Sending Notification Emails With T-SQL Without Using Hardcoded Email Addresses
    • Lesson 56. Troubleshooting Long Running Queries
    • Lesson 57. Loading Large CSVs Into Data Warehouse Staging Tables
    • Lesson 58. The Only Bloody Good Reason To Use Cursors
    • Lesson 59. Loading A Type II Slowly Changing Dimension With SQL Merge
    • Lesson 60. A Clearer Explanation Of The Parameters Of The Numeric Data Type
    • Lesson 61. Why You Cannot Join On Null Values
    • Lesson 62. A Deep Dive On How The Where Clause Functions
    • Lesson 63. Using HASHBYTES() To Compare Character Strings
    • Lesson 64. Using Pipe To Hash Multiple Columns For Matching
    • Lesson 65. Why People That Indent Code Drive Me Nuts
    • Lesson 66. How To Rapidly Stand Up A Data Warehouse From Scratch
    • Lesson 67. How To Pivot Data With T-SQL When Columns Are Not Predefined
    • Lesson 68. Prepopulating A Junk Dimension
Powered by GitBook
On this page
  1. Solutions To Real World Problems

Lesson 67. How To Pivot Data With T-SQL When Columns Are Not Predefined

The PIVOT function is great. Until you find out that the function demands to know the column names prior to running the query.

WACK! That's wack.

When does that even happen in the real world?! Never. That's not how this works. That's not how any of this works.

Fortunately, there is a solution that utilizes some dynamic SQL and the deep magic.

So, picture this exchange.

Accountant: Hey Bob, I need some data.

Me: Yo dawg! I'm your hook up! I'm about to HOOK you UP! What you want what you need dawg?

Accountant: I need to see the sales of all salespeople by month from the beginning of time.

Me: (sucks teeth) Man dawg that ain't nothing! I got you fam!

Accountant: Ok but I need to see it with people in rows and months as column headers.

Me: .....For real dawg?!

Unfortunately, this story is only partially made up. I don't really talk like this, but the request for the shape of his data totally happened.

But hey. This is business analytics. We're ALL about some customer service. So, we gives the people what they wants!

Some of this code is magic that I can't explain and that’s fine. I can use it, that’s what matters. I just keep this query handy and alter it every time I need to do this. Most of it is straight forward SQL. It’s the end that gets weird. We’re going to take all the possible columns and STUFF them into a variable using XML PATH. Then we are going to build our dynamic statement. Most of the code there should be familiar from the standard PIVOT example. The only difference is the columns variable is providing the columns instead of a hard coded value.

Examples

How To Use This Example

Create a SQL statement with columns in the form of y, x, z where:

y = the values that you want to see in rows x = the values that you want to see in columns z = the value that you want to see at the intersection of x and y coordinates

Alter the temp table schema to fit the column name and data type of your SQL statement.

Alter the rest of the query so column and table names are correct.

Alter the rest of the query to suit your use case.

In [ ]:

USE AdventureWorks2016

DECLARE @columns AS NVARCHAR(MAX)
DECLARE @sql  AS NVARCHAR(MAX)

DROP TABLE IF EXISTS #SalesReport

--We turn month of sale into an string value so we can add a leading zero later.
CREATE TABLE #SalesReport (SalesPerson NVARCHAR(50), MonthOfSale NVARCHAR(2), TotalSales MONEY)
;


INSERT INTO #SalesReport (SalesPerson, MonthOfSale, TotalSales)
SELECT
CONCAT(p.FirstName, ' ',p.LastName) AS SalesPerson,
DATEPART(month,soh.OrderDate) AS MonthOfSale,
SUM(sod.LineTotal) AS TotalSales
FROM Person.Person p
JOIN Sales.SalesPerson sp
ON p.BusinessEntityID = sp.BusinessEntityID
JOIN Sales.SalesOrderHeader soh
ON sp.BusinessEntityID = soh.SalesPersonID
JOIN Sales.SalesOrderDetail sod
ON soh.SalesOrderID = sod.SalesOrderDetailID
GROUP BY p.LastName, p.FirstName, DATEPART(month,soh.OrderDate)
ORDER BY p.LastName, p.FirstName, DATEPART(month,soh.OrderDate)
;

--Add the leading zero so the output goes from Jan to December.
UPDATE #SalesReport
SET MonthOfSale =
CASE
WHEN LEN(MonthOfSale) = 1 THEN '0'+ MonthOfSale
ELSE MonthOfSale
END


SELECT @columns = STUFF(
(
SELECT DISTINCT ',' + QUOTENAME(MonthOfSale) 
FROM #SalesReport
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,''
)

SET @sql = 'SELECT SalesPerson, ' + @columns + ' FROM
(
SELECT SalesPerson, MonthOfSale, TotalSales
FROM #SalesReport
) x
PIVOT 
(
MIN(TotalSales)
FOR MonthOfSale IN (' + @columns + ')
) y '

execute(@sql)

DROP TABLE #SalesReport
PreviousLesson 66. How To Rapidly Stand Up A Data Warehouse From ScratchNextLesson 68. Prepopulating A Junk Dimension

Last updated 3 years ago