Overview of PIVOT
For anyone who has worked with the SolarWinds Query Language (SWQL) for any amount of time knows that it mimics traditional SQL in many ways. But there are some limitations that aren't put on traditional SQL. Specifically, I'm looking at a few functions that don't exist in SWQL (at least of the time of this writing). To say that SWQL and SQL are related it true; but they are second or third cousins.
One such omission is the ability to pivot returned data. If you are unfamiliar with the term, it's explained very well on Microsoft's site, but I've always thought of it like the Paste as Transpose that's in Excel.
Basically, it takes data that's like this:
Alpha | Beta |
Apple | Banana |
Alfalfa | Bermuda |
Astronaut | Bakers |
and converts it to this:
Alpha | Apple | Alfalfa | Astronaut |
Beta | Banana | Bermuda | Baker |
Is this a gross over simplication? You better believe it, but it's how my brain interprets this data.
Poor Man's SWQL PIVOT
Another limitation on SWQL is that it doesn't support subqueries. But there are ways to link tables together because SWQL does support JOINs.
I've written about why I dislike using JOINs in the past because SWQL has native functionality that renders most of them unecessary. If it's at all possible, you should leverage Navigation Properties. They work better than JOINS for 90% of your queries.
To better illustrate how this works in practicality, we need two tables that are related by some type of common element. Since this procedure works for (at least) SAM Script components that return multiple statistics and Universal Device Pollers, I'm going to propose two different entities (tables) for this discussion.
The Example Entities
Orion.EntityTable
SELECT ElementID , Caption , IPAddress FROM Orion.EntityTable
ElementID | Caption | IPAddress |
---|---|---|
1111 | WESCRUSHER | 10.17.0.1 |
1112 | Skywalker | 192.168.199.11 |
Orion.EntityMultiLookupTable
SELECT ElementID , ElementName , ElementValue FROM Orion.EntityMultiLookupTable
ElementID | ElementName | ElementValue |
---|---|---|
1111 | AccountStat | 1 |
1111 | NumDrives | 6 |
1111 | HasDvdRom | 0 |
1112 | AccountStat | 0 |
1112 | NumDrives | 2 |
1112 | HasDvdRom | 1 |
Our Desired Result Set
Caption | IPAddress | AccountStat | NumDrives | HasDvdRom |
---|---|---|---|---|
WESCRUSHER | 10.17.0.1 | 1 | 6 | 0 |
Skywalker | 192.168.199.11 | 0 | 2 | 1 |
Step-by-Step
- Build the initial query getting what you need from the first entity
-- Step 1: Select what elements you can traditionally SELECT [ET].Caption , [ET].IPAddress FROM Orion.EntityTable AS [ET]
- Use a
LEFT JOIN
to connect that entity to the multiple element entity and add an additional filter for the element you want to retrieve
-- Step 2: Adding the LEFT JOIN SELECT [ET].Caption , [ET].IPAddress FROM [Orion.EntityTable] AS [ET] LEFT JOIN [Orion.EntityMultiLookupTable] AS [EMLT] ON [ET].ElementID = [EMLT].ElementID AND [EMLT].ElementName = 'AccountStat'
- And add
[EMLT].ElementValue AS [AccountStat]
to your list of properties to return
-- Step 3: Add your newly connected element SELECT [ET].Caption , [ET].IPAddress , [EMLT].ElementValue AS [AccountStat] <-- new FROM [Orion.EntityTable] AS [ET] LEFT JOIN [Orion.EntityMultiLookupTable] AS [EMLT] ON [ET].ElementID = [EMLT].ElementID AND [EMLT].ElementName = 'AccountStat'
- Since we want multiple elements from that target table, we just repeat with new aliases and element filters
-- Step 4: Bring in additional information via joins and properties SELECT [ET].Caption , [ET].IPAddress , [EMLT].ElementValue AS [AccountStat] , [EMLT2].ElementValue AS [NumDrives] , [EMLT3].ElementValue AS [HasDvdRom] FROM [Orion.EntityTable] AS [ET] LEFT JOIN [Orion.EntityMultiLookupTable] AS [EMLT] ON [ET].ElementID = [EMLT].ElementID AND [EMLT].ElementName = 'AccountStat' LEFT JOIN [Orion.EntityMultiLookupTable] AS [EMLT2] ON [ET].ElementID = [EMLT2].ElementID AND [EMLT2].ElementName = 'NumDrives' LEFT JOIN [Orion.EntityMultiLookupTable] AS [EMLT3] ON [ET].ElementID = [EMLT3].ElementID AND [EMLT3].ElementName = 'HasDvdRom'
- Keep repeating step 4 until you have all the information in your result set you need.
- Use better names for the joined entity aliases - they can get numerous and hard to remember what's what
-- Step 6: Think on it and use better names for your joined entities aliases SELECT [ET].Caption , [ET].IPAddress , [EMLT_AccountStat].ElementValue AS [AccountStat] , [EMLT_NumDrives].ElementValue AS [NumDrives] , [EMLT_HasDvdRom].ElementValue AS [HasDvdRom] FROM [Orion.EntityTable] AS [ET] LEFT JOIN [Orion.EntityMultiLookupTable] AS [EMLT_AccountStat] ON [ET].ElementID = [EMLT_AccountStat].ElementID AND [EMLT_AccountStat].ElementName = 'AccountStat' LEFT JOIN [Orion.EntityMultiLookupTable] AS [EMLT_NumDrives] ON [ET].ElementID = [EMLT_NumDrives].ElementID AND [EMLT_NumDrives].ElementName = 'NumDrives' LEFT JOIN [Orion.EntityMultiLookupTable] AS [EMLT_HasDvdRom] ON [ET].ElementID = [EMLT_HasDvdRom].ElementID AND [EMLT_HasDvdRom].ElementName = 'HasDvdRom'
Summary
In summation, the lack of the PIVOT function in the SolarWinds Query Language can be an annoyance then you're trying to represent data in a way that's appealing for you and for the consumers of your Observability solutions. However, it's rarely needed because SWQL has Navigation Properties to help you do most of the heavy lifting.
Tl;DR: I didn't want to have to keep looking for my notes on how to do this, so I decided to publish this discussion and add it to my THWACK bookmarks for easy reference.