Question

I have a CTE in SQL Server with a WHERE EXISTS clause which I'm trying out instead of using joins.

WITH Work_Info AS
(
  SELECT 
      a.fName,
      a.lName, 
      a.workID,
      a.carID
  FROM tbl_info a
  WHERE EXISTS (SELECT b.workID
                FROM tbl_work b
                WHERE a.workID = b.workID)
    AND EXISTS (SELECT c.carID, dbo.age_function(a.startDate, c.DOB) AS AGE
                FROM tbl_car c
                WHERE a.carID = c.carID
)

I'm trying to use/reference the column aliased as AGE in statements following the CTE, for example:

update tble_info a
SET adult =
   CASE WHEN AGE < 18 THEN 'NO'
Was it helpful?

Solution

I think you are misunderstanding how EXISTS works. You cannot return values from the EXISTS subquery. You would need to use JOINs:

You can do your update as follows:

WITH Work_Info AS
(   SELECT  a.fName,
            a.lName, 
            a.workID,
            a.carID
            Age = dbo.age_function(a.startDate, c.DOB)
    FROM    tbl_info a
            INNER JOIN tbl_car c
                ON c.CarID = a.CarID
  WHERE     EXISTS
            (   SELECT  1
                FROM    tbl_work b
                WHERE   a.workID = b.workID
            )
)
UPDATE  Work_Info
SET     adult = CASE WHEN AGE < 18 THEN 'NO' ELSE 'YES' END;

However, I recommend not using this method (it may not even work as an updateable CTE, I haven't tested). I would instead use MERGE (I have had to assume the existance of an ID column as the PK in tbl_info):

WITH Work_Info AS
(   SELECT  a.ID,
            Age = dbo.age_function(a.startDate, c.DOB)
    FROM    tbl_info a
            INNER JOIN tbl_car c
                ON c.CarID = a.CarID
  WHERE     EXISTS
            (   SELECT  1
                FROM    tbl_work b
                WHERE   a.workID = b.workID
            )
)
MERGE tbl_Info a
USING Work_Info w
    ON a.ID = w.ID
WHEN MATCHED THEN UPDATE
    SET adult = CASE WHEN w,Age < 18 THEN 'NO' ELSE 'YES' END;

The reasons I prefer (and suggest) to use MERGE even though you are not really merging you are only updating are discussed on sqlblog.com


ADDENDUM

To elaborate further on the main reason I prefer MERGE over just using UPDATE is that it safeguards against non deterministic updates. With the following sample schema:

CREATE TABLE T1 (ID INT, Adult CHAR(3));
CREATE TABLE T2 (ID INT, Age INT);

INSERT T1 (ID) VALUES (1);
INSERT T2 (ID, Age) VALUES (1, 5), (1, 20);

We can see that a single ID in T1 has two rows in T2, one where the age is over 18 and one where it is under, so if we run this update:

WITH CTE AS
(   SELECT  T1.ID, T1.Adult, T2.Age
    FROM    T1
            INNER JOIN T2
                ON T1.ID = T2.ID
)
UPDATE  CTE
SET     Adult = CASE WHEN Age < 18 THEN 'NO' ELSE 'YES' END;

There will be no error, and the T1 will be updated, but we don't know whether it would be set to NO or YES, since we don't know which reford of T2 will be the last encountered and "stick" in T1, when I ran the above Adult was set to NO, however when I change the insert to:

INSERT T2 (ID, Age) VALUES (1, 20), (1, 5); -- order changed

T1.Adult is set to YES. When I run using MERGE:

WITH CTE AS
(   SELECT  T1.ID, T1.Adult, T2.Age
    FROM    T1
            INNER JOIN T2
                ON T1.ID = T2.ID
)
MERGE T1
USING CTE
    ON CTE.ID = #T1.ID
WHEN MATCHED THEN UPDATE 
    SET Adult = CASE WHEN Age < 18 THEN 'NO' ELSE 'YES' END;

I get the following error:

The MERGE statement attempted to UPDATE or DELETE the same row more than once. This happens when a target row matches more than one source row. A MERGE statement cannot UPDATE/DELETE the same row of the target table multiple times. Refine the ON clause to ensure a target row matches at most one source row, or use the GROUP BY clause to group the source rows.

Prompting me to review my query, and ensure that it is deterministic.

There are other reasons mentioned in the linked article, such as ANSI compliance (not a huge concern to me), and also updating a view using UPDATE .. FROM with an INSTEAD OF TRIGGER on the view will fail, but won't with MERGE, this is again something I have not yet lost any sleep over.

Don't get me wrong, I still use UPDATE .. FROM on occasion, but only really for large one off updates when I know for sure duplicates are not an issue as it does offer a better plan with lower IO.

OTHER TIPS

Your problem is you're not exposing AGE as a column in your CTE. It's only used in your WHERE clause so it's just acting as part of your larger EXISTS filter.

Basically, you're going to have to join tbl_car to tbl_info if you want to be able to run your age function and return the value. You're already doing that anyway in your inner WHERE clause in your EXISTS statement, so it won't be a performance hit to actually join on a.carID = c.carID and add the AGE column to your CTE definition.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top