Turning "Set Statistics IO" for stored procedure doesn't show all tables used in stored procedure - why? I'm reviewing a stored procedure that's been a performance problem. And I just realized that some of the tables which are used in the stored procedure don't even show up AT ALL in the results from Set Statistics I/O?
Is this normal, or a bug? It's a little disturbing because I use the results of Set Statistics I/O a lot, when figuring out what to index.
The tables that don't show up in the output of Set Statistics I/O are in a subselect on the main query, like the ActivityFeedCache table below:
SELECT *
FROM (
    SELECT AF.ActivityFeedID
        ,A.ActivityType AS ActivityType
        ,A.IconClass
        ,AF.SubscriptionID
        ,AF.ObjectID
        ,AF.ObjectType
        ,(
            SELECT L.Locale
                ,AFC.LocalizedMessage
            FROM ActivityFeedCache AFC
            JOIN Locales L
                ON AFC.LocaleID = L.LocaleID
            WHERE ActivityFeedID = AF.ActivityFeedID
            FOR JSON PATH
            ) AS MessageJSON
    FROM ActivityFeed AF
    ETC 
    ETC    
    ) AS Results    

This is SQL Server 2017 (RTM-CU13).
EDIT:
I've narrowed down to a small query that shows a missing table in the Statistics IO output. The Locales table is missing in Statistics I/O, but shows up in the execution plan. Here it is:
-- Locales table doesn't show in statistics i/o output
SELECT AF.ActivityFeedID
        ,(
            SELECT L.Locale
                ,AFC.LocalizedTeamMessage AS LocalizedMessage
            FROM ActivityFeedTeamCache AFC
            JOIN Locales L
                ON AFC.LocaleID = L.LocaleID
            WHERE ActivityFeedID = AF.ActivityFeedID
            FOR JSON PATH
            ) AS TeamMessageJSON
    FROM ActivityFeed AF
Where AF.ActivityFeedID = (select top 1 ActivityFeedID from ActivityFeed)


Here's the Statistics I/O output:

(1 row affected)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ActivityFeedTeamCache'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ActivityFeed'. Scan count 1, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

And here's a small chunk of the actual execution plan XML, just so you can see the Locales table is actually being used:
  &lt;IndexScan Ordered=&quot;true&quot; ScanDirection=&quot;FORWARD&quot; ForcedIndex=&quot;false&quot; ForceSeek=&quot;false&quot; ForceScan=&quot;false&quot; NoExpandHint=&quot;false&quot; Storage=&quot;RowStore&quot;&gt;
    &lt;DefinedValues&gt;
      &lt;DefinedValue&gt;
        &lt;ColumnReference Database=&quot;[Database1]&quot; Schema=&quot;[dbo]&quot; Table=&quot;[Locales]&quot; Alias=&quot;[L]&quot; Column=&quot;Locale&quot; /&gt;
      &lt;/DefinedValue&gt;
    &lt;/DefinedValues&gt;
    &lt;Object Database=&quot;[Database1]&quot; Schema=&quot;[dbo]&quot; Table=&quot;[Locales]&quot; Index=&quot;[PK_Locales]&quot; Alias=&quot;[L]&quot; IndexKind=&quot;Clustered&quot; Storage=&quot;RowStore&quot; /&gt;
    &lt;SeekPredicates&gt;
      &lt;SeekPredicateNew&gt;
        &lt;SeekKeys&gt;
          &lt;Prefix ScanType=&quot;EQ&quot;&gt;
            &lt;RangeColumns&gt;
              &lt;ColumnReference Database=&quot;[Database1]&quot; Schema=&quot;[dbo]&quot; Table=&quot;[Locales]&quot; Alias=&quot;[L]&quot; Column=&quot;LocaleID&quot; /&gt;
            &lt;/RangeColumns&gt;
            &lt;RangeExpressions&gt;
              &lt;ScalarOperator ScalarString=&quot;[Database1].[dbo].[ActivityFeedTeamCache].[LocaleID] as [AFC].[LocaleID]&quot;&gt;
There is a scenario where an estimated execution plan can contain an operator that doesn't get used at runtime, for example when a predicate renders the operator a no-op.
It's tricky to spot because there aren't many clues it's happened, other than the operator's actual execution count is 0. I helped drive a change in SentryOne Plan Explorer that makes it more obvious - the operator icon gets dimmed. You can read about this change in this blog post under the heading, &quot;Apply opacity for operators with 0 executions + actual rows.&quot;
If a table is only touched by a single operator in the execution plan, and that operator gets eliminated at runtime (meaning the table does not get touched), that table will not show up in Statistics IO.