Home > Enterprise >  How to reference System Versioned table in a sub query
How to reference System Versioned table in a sub query

Time:01-28

I have a system versioned temporal table of Contacts. I also have a table of events when contacts where used. I want to aggregate and get a count of the contacts that were available in a list when the event happened.

To do this I am using a sub-query. But when I tried to specify the respective event time SQL Server Management Studio won't compile the query.

How to I reference a System Versioned table in a subquery with a non-hardcoded timestamp?

DECLARE @CurrentTime DATETIMEOFFSET = GETUTCDATE()
DECLARE @LastSentTime DATETIMEOFFSET = DATEADD(day, -28, @CurrentTime)

SELECT
    c.ContactListID,
    ( SELECT COUNT( DISTINCT con.Email )
      FROM [schema].[Contact] FOR SYSTEM_TIME AS OF c.StartDate AS con
      WHERE c.ContactListID = con.ContactListID) as [ContactCount],
    c.StartDate
INTO #TEMP
FROM [schema].[event] c
WHERE StatusID = 3
AND StartDate > @LastSentTime
AND StartDate <= @CurrentTime
ORDER BY StartDate ASC

FOR SYSTEM_TIME AS OF c.StartDate AS con Doesn't work, citing incorrect syntax

But if I hardcode the date string

FOR SYSTEM_TIME AS OF '2022-01-03 23:00:00.00 00:00' AS con

it does

CodePudding user response:

Looking at the documentation, the argument for AS OF must either be a literal or a variable; it doesn't support using a value from a column in an outer query.

FROM clause plus JOIN, APPLY, PIVOT (T-SQL) - SQL Server | Microsoft Docs

<system_time> ::=  
{  
      AS OF <date_time>  
   |  FROM <start_date_time> TO <end_date_time>  
   |  BETWEEN <start_date_time> AND <end_date_time>  
   |  CONTAINED IN (<start_date_time> , <end_date_time>)   
   |  ALL  
}  
 
<date_time>::=  
    <date_time_literal> | @date_time_variable  
 
<start_date_time>::=  
    <date_time_literal> | @date_time_variable  
 
<end_date_time>::=  
    <date_time_literal> | @date_time_variable  

You'll need to use a scalar-valued function to count the history rows:

CREATE OR ALTER FUNCTION [schema].CountEmailContacts
(
    @ContactListID int,
    @AsOf datetime2(0)
)
Returns int
As
BEGIN
    Return (SELECT COUNT(DISTINCT Email)
    FROM [schema].[Contact]
    FOR SYSTEM_TIME AS OF @AsOf
    WHERE ContactListID = @ContactListID);
END;

Usage:

SELECT
    c.ContactListID,
    [schema].CountEmailContacts(c.ContactListID, c.StartDate) As [ContactCount],
    c.StartDate
INTO #TEMP
FROM [schema].[event] c
WHERE StatusID = 3
AND StartDate > @LastSentTime
AND StartDate <= @CurrentTime
ORDER BY StartDate ASC

Edit:
Or, as Stu pointed out, an inline table-valued function, which should have better performance:

CREATE OR ALTER FUNCTION [schema].CountEmailContacts
(
    @ContactListID int,
    @AsOf datetime2(0)
)
Returns Table
As
Return 
(
    SELECT COUNT(DISTINCT Email) As ContactCount
    FROM [schema].[Contact]
    FOR SYSTEM_TIME AS OF @AsOf
    WHERE ContactListID = @ContactListID
);

Usage:

SELECT
    c.ContactListID,
    con.[ContactCount],
    c.StartDate
INTO #TEMP
FROM [schema].[event] c
CROSS APPLY [schema].CountEmailContacts(c.ContactListID, c.StartDate) con
WHERE StatusID = 3
AND StartDate > @LastSentTime
AND StartDate <= @CurrentTime
ORDER BY StartDate ASC
  •  Tags:  
  • Related