views:

477

answers:

5

My stored procedure looks like:

WITH MYCTE(....)
AS 
(
...
)

UPDATE ... (using my CTE)



DELETE ( using my CTE)  <---  says the object, my CTE, doesn't exist

Can I only use it once?

+2  A: 

A CTE expression is only valid in its body. If you want to use it in other places, you should repeat the WITH clause too.

WITH MYCTE(....) AS ( ... ) 
UPDATE ... (using my CTE);  
-- a semicolon is necessary for statements followed by a CTE declaration

WITH MYCTE(....) AS ( ... )
DELETE ( using my CTE);
Mehrdad Afshari
but it is working in the UPDATE, just not in the DELETE, does that make sense?
mrblah
Of course. `UPDATE` is the body of your CTE.
Mehrdad Afshari
Isn't the body inside the () after the AS keyword. Update and DELETE are outside of the ().
mrblah
What you put in the `AS` clause is the declaration of the CTE. You actually use it in the statement that comes immediately after the closing parenthesis of `AS.
Mehrdad Afshari
depending on the complexity of the CTE and the number of rows returned, an OUTPUT clause may perform significantly better than duplicating the CTE for the DELETE command. WHY reselect all those rows again, just save the PKs the first time you have them. Also, the UPDATE might make the CTE impossible to repeat. See my answer for an example of the OUTPUT clause.
KM
KM: Yes. But that's a completely different story. My answer is doesn't really care about `DELETE`. It might be any other clause. The point is, you have to declare the CTE for the current statement. Of course, your answer is applicable if you want to delete exactly the rows you want to `SELECT`.
Mehrdad Afshari
+2  A: 

Yep, the WITH MYCTE clause is not creating a permanent object to use in multiple queries afterwards: it's only modifying the one query you're adding that clause to! If you need very different functonality, consider, instead, using views...

Alex Martelli
...or temp tables (# or @).
Philip Kelley
+3  A: 

In your example code, the CTE only persists for the UPDATE. If you need it to last longer, consider populating a #tempTable or @tableVariable with it, and then UPDATE and DELETE from those.

You may also augment your UPDATE to use an OUTPUT clause, like the following, so you can capture the affected rows. And use them in the DELETE, like here:

set nocount on
DECLARE @Table     table (PK int, col1 varchar(5))
DECLARE @SavedPks  table (PK int)

INSERT INTO @Table VALUES (1,'g')
INSERT INTO @Table VALUES (2,'g')
INSERT INTO @Table VALUES (3,'g')
INSERT INTO @Table VALUES (4,'g')
INSERT INTO @Table VALUES (5,'x')
INSERT INTO @Table VALUES (6,'x')
set nocount off

;WITH MYCTE
AS 
(
  SELECT PK, col1 FROM @Table
)
UPDATE MYCTE
    SET col1='xyz'
    OUTPUT INSERTED.PK
        INTO @SavedPks
    WHERE col1='g'

SELECT 'A',* FROM @Table

DELETE @Table
    WHERE PK IN (SELECT PK  from @SavedPks)

SELECT 'B',* FROM @Table

OUTPUT:

(4 row(s) affected)
     PK          col1
---- ----------- -----
A    1           xyz
A    2           xyz
A    3           xyz
A    4           xyz
A    5           x
A    6           x

(6 row(s) affected)

(4 row(s) affected)

     PK          col1
---- ----------- -----
B    5           x
B    6           x

(2 row(s) affected)
KM
Great answer. I've been using SQL Server for years and didn't know you could do OUTPUT. That will definitely come in handy in the future.
WesleyJohnson
+1  A: 

CTE don't create anything 'real'. They are merely a language element, a way to express a table expression that will be used, possible repeatedly, in a statement. When you say WITH cteFoo AS (select ... from table where ...) select ... from cteFoo where ... is just another way of saying select ... from (select ... from table where ....) as cteFoo where ....

CTE and derived tables are very similar, any query using derived tables can be rewriten as a CTE, and any non-recursive CTE can be rewritten as a query using derived tables. Personally, I much more preffer the CTE form as is more concise and easy to read.

CTEs allow for a table expression used multiple times to be declare only once:

  WITH cte AS (select ... from table where ...)
  select ... 
    from cte a join cte b on ...
  where ...

Compare this with the semantically similar derived table form:

select ... 
from (
   select ... from table where ...) as a
join (
   select ... from table where ...) as b
   on ...
where ...

The CTE is clearly more readable. But you must understand that the two forms are producing the same query. The CTE form might suggest that an intermediate result is created then the join is run on the intermediate result, but this is not true. The CTE form is compiled into exactly the same form as the derived table one, which makes clear the fact that the CTE's table expresion is run twice.

Remus Rusanu
A: 

I'm curious. Does anyone know why Microsoft say "...a CTE can be self-referencing and can be referenced multiple times in the same query." in MSDN at http://msdn.microsoft.com/en-us/library/ms190766.aspx.

If that were the case then this would work:-

WITH 
    CustomerTowns (CustomerID, CustomerName, Town)
AS
(
    SELECT 
        c.CustomerID,
        c.Name,
        a.Town
    FROM    
        dbo.tblCustomer c
    INNER JOIN
        dbo.tblAddress a
    ON
        c.DefaultAddressID = a.AddressID
)

-- Query the CTE rather than doing the whole query sever times.
SELECT * FROM CustomerTowns ct ORDER BY ct.CustomerName
SELECT * FROM CustomerTowns ct ORDER BY ct.Town -- Except you cannot reference a CTE more than once!
John Thompson