• Ok, I've got this approach... 

    In my ETL, for new rows, the recnum will be null, as it's not in the source table; for existing rows, the recnum will be populated by the target table.  I'm doing a FULL OUTER JOIN to detect new/existing/deleted.

    As long as my ETL code "does the right thing", this should work.  I do think the trigger is more bulletproof though. 

    Does a nonclustered columnstore index have the same performance as a clustered columnstore index, it just takes up more space?

    This approach requires two inserts, one for new rows (no ID), one for existing rows (includes ID).

    Thoughts on this approach?

    USE [tempdb]
    GO

    DROP TABLE IF EXISTS #t1
    GO

    DROP SEQUENCE IF EXISTS recnum
    CREATE SEQUENCE recnum START WITH 1 INCREMENT BY 1
    GO

    CREATE TABLE #t1 (
      ID BIGINT DEFAULT NEXT VALUE FOR recnum NOT NULL,
      name VARCHAR(100)
    )
    GO

    -- new rows
    INSERT INTO [#t1] ([name])
    SELECT name
    FROM sys.databases

    SELECT * FROM [#t1]

    -- existing rows
    INSERT INTO [#t1]
    SELECT *
    FROM [#t1]
    ORDER BY [name]

    SELECT * FROM [#t1] ORDER BY name

    -- mixed
    DROP TABLE IF EXISTS #t2
    CREATE TABLE #t2 (ID BIGINT, name VARCHAR(100))

    INSERT INTO [#t2]
    VALUES (NULL,'FOO'),
       (1,'master'),
       (NULL,'BAR')

    INSERT INTO #t1 (name)
    SELECT name
    FROM #t2
    WHERE ID IS NULL

    INSERT INTO #t1 (ID, name)
    SELECT ID, name
    FROM #t2
    WHERE ID IS NOT NULL

    SELECT * FROM [#t1] ORDER BY name