sqlsql-servert-sqlunpivotunion-all

Can a query that uses the same filtered table for both unpivoting and joining be written without repetition?


Consider the following query:

create function unpivoter(@TableID int)
returns table
as
return
(
    select a, b
    from
    (
        select foo1, foo2, foo3, foo4
        from table1
        where table1.id = @TableID
    ) tab_in
    unpivot
       (a for b in (foo1, foo2, foo3, foo4)) tab_out

    union all

        select t3.a, t3.b,
        from table1 t1
        join t2 on t1.id = t2.id
        join t3 on t3.id = t2.id
        where t1.id = @TableID

    union all
    
        select t4.a, t4.b,
        from table1 t1
        join t4 on t4.id = t4.id
        where t1.id = @TableID
)

this is obviously highly repetitive. Two things stand out

  1. table1 is given the same filter thrice. I'd replace it with a CTE, but the select list is different between the first use the rest, so this could have a big performance hit depending on the indexing situation.
  2. The union alls all look very similar. The fact that I could have also written the unpivot as a set of three union alls suggests that the two union alls that are currently within the query should somehow be merged in to the unpivot.

Is there anything in T-SQL that can tackle these two problems?


Solution

  • Not entirely, however you can avoid looking up table1 multiple times by putting the UNION ALL in an APPLY.

    Use a (VALUES constructor instead of UNPIVOT, it is far more flexible.

    CREATE OR ALTER FUNCTION dbo.unpivoter (@TableID int)
    RETURNS TABLE
    AS RETURN
    
    SELECT
      u.a,
      u.b
    FROM table1 t1
    
    CROSS APPLY (
        SELECT
          tab_out.a,
          tab_out.b
        FROM (VALUES
            ('foo1', t1.foo1),
            ('foo2', t1.foo2),
            ('foo3', t1.foo3),
            ('foo4', t1.foo4)
        ) tab_out(a, b)
    
        UNION ALL
    
        SELECT
          t3.a,
          t3.b,
        FROM t2
        JOIN t3 ON t3.id = t2.id
        WHERE t2.id = t1.id
    
        UNION ALL
    
        SELECT
          t4.a,
          t4.b,
        FROM t4
        WHERE t4.id = t1.id
    ) u
    WHERE t1.id = @TableID;
    

    You could also replace the VALUES with a few UNION ALL, bu it's more verbose.

    CROSS APPLY (
        SELECT
          'foo1' AS a,
          t1.foo1 AS b
    
        UNION ALL
    
        SELECT
          'foo2' AS a,
          t1.foo2 AS b
    
        UNION ALL
    
        SELECT
          'foo3' AS a,
          t1.foo3 AS b
    
        UNION ALL
    
        SELECT
          'foo4' AS a,
          t1.foo4 AS b
    
        UNION ALL