Multiple SELECTs in one query using same WITH

Viewed 379

This query filters some IDs from a master table, and returns all data abpout that IDs from two other tables as two record sets:

WITH filteredIds (fid) AS 
(
    SELECT id 
    FROM MasterTable 
    WHERE <somecondition>
)
SELECT * 
FROM Table1 
RIGHT JOIN filteredIds ON (Table1.id = filteredIds.fid);

WITH filteredIds (fid) AS 
(
    SELECT id 
    FROM MasterTable 
    WHERE <somecondition>
)
SELECT * 
FROM Table2 
RIGHT JOIN filteredIds ON (Table2.id = filteredIds.fid);

This works so far, but it would be great to have just one WITH clause, since the condition is always the same. Further more, the condition is often written manually, since this is used to collect some diagnostics data.

But this

WITH filteredIds (fid) AS 
(
    SELECT id 
    FROM MasterTable 
    WHERE <somecondition>
)
SELECT * 
FROM Table1 
RIGHT JOIN filteredIds ON (Table1.id = filteredIds.fid);

SELECT * 
FROM Table2 
RIGHT JOIN filteredIds ON (Table2.id = filteredIds.fid);

does not work, SQL Server claims it does not know object filteredIds in the last query.

Have I overseen something, or doesn't it work that way? I guess the alternative would be a temporary table.

2 Answers

Once a CTE has been consumed in a query, it cannot be reused in another query. There is no direct workaround as far as I know. The closest thing perhaps to want you want would be to create a bona fide temporary table:

CREATE TABLE #temp (fid int);
INSERT INTO #temp (fid)
SELECT id FROM MasterTable WHERE <somecondition>;

Then, you may reuse the temporary table directly:

SELECT * FROM Table1 a RIGHT JOIN #temp b ON a.id = b.fid;
SELECT * FROM Table2 a RIGHT JOIN #temp b ON a.id = b.fid;

You can't do what you want with CTE. CTE doesn't exist outside the query where it was defined.

I think that the closest thing to what you want is called a "view".

CREATE VIEW filteredIds
AS
SELECT id AS fid
FROM MasterTable 
WHERE <somecondition>
;

And now you can use that view in many other queries.

SELECT * 
FROM Table1 
RIGHT JOIN filteredIds ON (Table1.id = filteredIds.fid);

SELECT * 
FROM Table2 
RIGHT JOIN filteredIds ON (Table2.id = filteredIds.fid);

Furthermore, in SQL Server it will work exactly like a CTE in a sense that the list of filtered IDs is not materialized and the engine will optimize the whole query that references the view. It will inline the view definition into the outer / main query as if you wrote everything in one big query. Just like it would do with CTE.

With a temporary table you explicitly write this temporary result to disk and read it back.

In some cases it may be better, in some not.

Related