This discussion has been locked. The information referenced herein may be inaccurate due to age, software updates, or external references.
You can no longer post new replies to this discussion. If you have a similar question you can start a new discussion in this forum.

Decreasing JOINs - the Power of Navigation Properties

Because many people (myself included) started to learn the syntax of SWQL Queries based on (sometimes limited) knowledge of SQL queries, I wanted to share with everyone the best way to avoid JOIN clauses.

The examples herein will be show in SWQL Studio, so if you don't already have SWQL Studio installed, you should do so if you'd like to follow along.  Some of my sample data from below is completely made up, but you should see something similar in your environment.

If you are not familiar with the traditional T-SQL JOIN clauses, there's multiple excellent articles on Microsoft's and other sites (Official Doc: Joins (SQL Server) - SQL Server | Microsoft Docs).  My non-technical description of a JOIN is "a way to connect one table to another through a shared element."

I will use terms like field and record and for the sake of clarity this is what I mean:

Fields vs. Records

Sample Tables

NodeID Caption ObjectSubType DetailsURL
27 EASTEXMBX01v WMI /Orion/NetPerfMon/NodeDetails.aspx?NetObject=N:27
42 EAST-2945-WAN SNMP /Orion/NetPerfMon/NodeDetails.aspx?NetObject=N:42
914 WESTAZLINUX01v Agent /Orion/NetPerfMon/NodeDetails.aspx?NetObject=N:914

Table 1: Selection of elements from Orion.Nodes

SELECT NodeID, Caption, ObjectSubType, DetailsURL FROM Orion.Nodes

NodeID InterfaceID InterfaceName InterfaceAlias TypeDescription PercentUtil DetailsURL
27 97 Ethernet MAPI Ethernet 67.6 /Orion/Interfaces/InterfaceDetails.aspx?NetObject=I:97
27 98 Ethernet Replication Ethernet 82.7 /Orion/Interfaces/InterfaceDetails.aspx?NetObject=I:98
42 168 Lo0 Loopback 0.5 /Orion/Interfaces/InterfaceDetails.aspx?NetObject=I:168
42 169 GigabitEthernet0/0 Inside Ethernet 42.4 /Orion/Interfaces/InterfaceDetails.aspx?NetObject=I:169
42 170 GigabitEthernet0/1 Outside Ethernet 69.9 /Orion/Interfaces/InterfaceDetails.aspx?NetObject=I:170
914 9147 eth0 Ethernet 18.7 /Orion/Interfaces/InterfaceDetails.aspx?NetObject=I:9147

Table 2: Selection of elements from Orion.NPM.Interfaces

SELECT NodeID, InterfaceID, InterfaceName, InterfaceAlas, TypeDescription, PercentUtil, DetailsURL FROM Orion.NPM.Interfaces

Moving the Queries to a more "readable" format

Converting the queries to something more readable helps me organize the fields and make the logical connection.  These are the same queries from above, but refactored.  For SQL and SWQL anything on a line after a double-dash is considered a comment.

-- Data from Orion.Nodes
-- Read the Comments in number order to understand the logic
--
SELECT [Nodes].NodeID          -- 2. Prepend the fields with the table alias.
     , [Nodes].Caption         -- 3. The same applies for all fields from this table.
     , [Nodes].ObjectSubType   -- 4. and again
     , [Nodes].DetailsURL      -- 5. you get the picture
FROM Orion.Nodes AS [Nodes]    -- 1. Choose an easy to comprehend alias for the table

-- Data from Orion.NPM.Interfaces
-- Read the comments in number order to understand the logic
--
SELECT [Interfaces].NodeID                -- 2. Prepend the fields with the table alias.
     , [Interfaces].InterfaceID           -- 3. The same applies for all fields from this table.
     , [Interfaces].InterfaceName         -- 4. and again
     , [Interfaces].InterfaceAlias        -- 5. lather
     , [Interfaces].TypeDescription       -- 6. rinse
     , [Interfaces].PercentUtil           -- 7. repeat
     , [Interfaces].DetailsURL            -- 8. you see where I'm going, right?
FROM Orion.NPM.Interfaces AS [Interfaces] -- 1. Choose an easy to comprehend alias for the table

Joining the Tables (Traditional)

Using the JOIN clause (which is 100% supported in SWQL), you can join these two tables on a common element (NodeID).

--
-- Read the Comments in number order to understand the logic
--
SELECT [Nodes].NodeID                           -- 3. Field from the original table
     , [Nodes].Caption                          -- 4. Another from the Nodes table
     , [Nodes].ObjectSubType                    -- 5. Yep, this one is also from Nodes
     , [Nodes].DetailsURL                       -- 6. Nodes as well
     , [Interfaces].NodeID                      -- 7. This is from our joined Interfaces table
     , [Interfaces].InterfaceID                 -- 8. Also from the Interfaces table
     , [Interfaces].InterfaceName               -- 9. More from Interfaces
     , [Interfaces].InterfaceAlias              -- 10. You can see why the alias...
     , [Interfaces].TypeDescription             -- 11. ... makes it easy to understand ...
     , [Interfaces].PercentUtil                 -- 12. ... the source of the field in question.
     , [Interfaces].DetailsURL                  -- 13. I ran out of clever things to say
FROM Orion.Nodes AS [Nodes]                     -- 1. Our original table
INNER JOIN Orion.NPM.Interfaces AS [Interfaces] -- 2. Our second table
  ON [Nodes].NodeID = [Interfaces].NodeID

Now each record will show all of the requested fields from both tables.

Notice the second reference to NodeID (the fifth field in the above record)?  Since our JOIN guarantees that the field NodeID and NodeID1 are equal, we could remove one of them.

But there's also a second reference to DetailsURL.  In this case, the Node's details link and that for the interface are different.  We should probably come up with a more logical way to present this.

--
-- Read the Comments in number order to understand the logic
--
SELECT [Nodes].NodeID
     , [Nodes].Caption
     , [Nodes].ObjectSubType
     , [Nodes].DetailsURL AS [NodeDetailsUrl]           -- 2. Provide a field alias to the field name
     -- , [Interfaces].NodeID                           -- 1. (commented out the duplicate NodeID because the JOIN already guarantees these match
     , [Interfaces].InterfaceID
     , [Interfaces].InterfaceName
     , [Interfaces].InterfaceAlias
     , [Interfaces].TypeDescription
     , [Interfaces].PercentUtil
     , [Interfaces].DetailsURL AS [InterfaceDetailsUrl] -- 3. Provide a different field alias for this other one
FROM Orion.Nodes AS [Nodes]
INNER JOIN Orion.NPM.Interfaces AS [Interfaces]
     ON [Nodes].NodeID = [Interfaces].NodeID

Neat, right?  Now we can join two tables on a matching record and show fields from either table in a single record.

But what about 3 tables?  What about 4, 5, 6, 11?  It can be done with multiple JOIN operations between the tables, but you can see that things could get complex, right?

The Easier Way - Navigation Properties

The incredibly smart people at SolarWinds Orion Architecture decided that although JOINs do work, you need to have an intrinsic knowledge of the connected tables before you can be successful.

Each table (technically called an "entity") in the SolarWinds Orion API can have up to 5 different types of elements.

Available Resources in Orion Entities

  • Primary Key - this is the unique identifier(s) for this table
  • Fields - the things you can query
  • Inherited Fields - additional things you can query (but aren't the default)
  • Navigation Properties - connected entities <-- this is the focus of the discussion today
  • Verbs - actions you can take on the elements in the entity (records in the table)

What is a Navigation Property?

The Official Definition: a property on one entity type whose value is a set of zero or more other entities of a particular type

The "KMSigma" Definition: a way to skip doing JOINs.

I'm going to zoom in on one specific navigation property under Orion.Nodes.

Looks familiar right?  Remember that this is a child of the Orion.Nodes entity, not of Orion.NPM.Interfaces.

Here's what it's saying in plain language: You can query for fields from Orion.Nodes and then connect to Orion.NPM.Interfaces via the Interfaces navigation property.

Taking our above examples, we can connect Orion.Nodes with Orion.NPM.Interfaces via the navigation property.  Starting simple, let's just include the interfaceID.

--
-- Read the Comments in number order to understand the logic
--
SELECT [Nodes].NodeID
     , [Nodes].Caption
     , [Nodes].ObjectSubType
     , [Nodes].DetailsURL
     , [Nodes].Interfaces.InterfaceID
--        ^        ^           ^
--        |        |           |
--        +- 1. Starting with Orion.Nodes
--                 |           |
--                 +- 2. Connect to Orion.NPM.Interfaces via the Navigation Property
--                             |
--                             + 3. Pull this field from Orion.NPM.Interfaces
FROM Orion.Nodes AS [Nodes] -- 1. Having a table alias here is practically mandatory to keep things stright in your head - get in the habit.

Huzzah!  We've connected Orion.Nodes to Orion.NPM.Interfaces without using a JOIN clause.

If we wanted to add the other fields, it's the same as following the formula.  Thankfully, SWQL Studio has autocomplete, so it's easy.

SWQL Studio Support Navigation Property Autocompletion

And the results are pretty much what you would expect.

But all of this was done WITH NO JOIN CLAUSES.

You can even continue further.  If you look at the Orion.NPM.Interfaces entity, it also has navigation properties.

--
-- Read the Comments in number order to understand the logic
--
SELECT [Nodes].NodeID
     , [Nodes].Caption
     , [Nodes].ObjectSubType
     , [Nodes].DetailsURL
     , [Nodes].Interfaces.InterfaceID
     , [Nodes].Interfaces.InterfaceName
     , [Nodes].Interfaces.InterfaceAlias
     , [Nodes].Interfaces.TypeDescription
     , [Nodes].Interfaces.PercentUtil
     , [Nodes].Interfaces.DetailsUrl
--     * Show two table hops *
     , [Nodes].Interfaces.ForecastCapacity.DaysToCapacityAvg
--        ^        ^           ^                 ^
--        |        |           |                 |
--        +- 1. Starting with Orion.Nodes        |
--                 |           |                 |
--                 +- 2. Connect to Orion.NPM.Interfaces via the Navigation Property
--                             |                 |
--                             + 3. Connect to Orion.NPM.InterfacesForecastCapacity via the Navigation Property on Orion.NPM.Interfaces
--                                               |
--                                               +- 4. Pull DaysToCapacity field from Orion.NPM.InterfacesForecastCapacity
FROM Orion.Nodes AS [Nodes] -- 1. Having a table alias here is practically mandatory to keep things stright in your head - get in the habit.

Summary

Navigation Properties are one of the most underappreciated features of the SolarWinds Orion API.  The API already knows how the various data types are connected, so use what's already been built for you.  Go forth and explore!