1

I have an IF EXISTS 'upsert' running fine by itself in it’s own stored proc. But when I try and use the same statement referencing a CTE, it doesn't recognize the CTE. I see in related post that I'm not allowed to use the CTE as the subquery. I'm curious why is that, and how else could I accomplish this?

Working stored procedure using IF EXISTS:

ALTER Procedure [dbo].[sproc_receive]
    @StockCode VARCHAR(50), 
    @Qty DECIMAL(18,6)
AS

--source: https://weblogs.sqlteam.com/dang/2007/10/28/conditional-insertupdate-race-condition/

SET NOCOUNT, XACT_ABORT ON

BEGIN TRAN

IF EXISTS(SELECT * FROM tblReceivedQty WITH (UPDLOCK, HOLDLOCK) WHERE StockCode = @StockCode)
    BEGIN
          UPDATE tblReceivedQty
          SET ReceivedQty = ReceivedQty + @Qty
          WHERE StockCode = @StockCode
    END
ELSE
    BEGIN
          INSERT INTO tblReceivedQty (StockCode, ReceivedQty)
          VALUES (@StockCode, @Qty)
    END
COMMIT

RETURN @@ERROR
GO

And here is my attempt to repurpose the IF EXISTS in another stored proc which takes a json string as input.

USE [<databasename>]
GO

/****** Object:  StoredProcedure [dbo].[sproc_PutAway]    Script Date: 6/13/2022 4:14:02 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

ALTER Procedure [dbo].[sproc_PutAway]
(@json NVARCHAR(MAX) = '')
AS
BEGIN

-- Create CTE from JSON input
WITH json_received(StockCode, Qty)
AS
(
SELECT StockCode, Qty
    FROM OPENJSON(@json)
    WITH (
        StockCode VARCHAR(30) '$.StockCode',
        Qty DECIMAL(18,6) '$.Qty'
        )
)

SET NOCOUNT, XACT_ABORT ON

BEGIN TRAN

IF EXISTS(SELECT * FROM tblReceivedQty WITH (UPDLOCK, HOLDLOCK) WHERE tblReceivedQty.StockCode = json_received.StockCode)
    BEGIN
        UPDATE tblReceivedQty
        SET tblReceivedQty.ReceivedQty = tblReceivedQty.ReceivedQty - (
            SELECT Sum(Qty)
            FROM json_received
            WHERE tblReceivedQty.StockCode = json_received.StockCode
            GROUP BY json_received.StockCode
            )
    END
ELSE
    BEGIN
        INSERT INTO tblReceivedQty (StockCode, ReceivedQty)
        VALUES (json_received.StockCode, (-1 * json_received.Qty))
    END

COMMIT

RETURN @@ERROR
GO

This gives me a syntax error after the CTE, and a 'multipart identifer could not be bound' on all references to the CTE.

Appreciate any hints!

9
  • Open your BEGIN TRAN to include the CTE.
    – Mike Petri
    Commented Jun 17, 2022 at 17:55
  • 2
    CTEs are a construct that can be used in one specific statement, not part of the entire procedure. Your CTE should immediately precede the statement that uses it. If you want to "run once & reuse the results multiple times in the procedure" you might want to consider a temp table.
    – AMtwo
    Commented Jun 17, 2022 at 18:03
  • Is there a reason that you are not using MERGE? Commented Jun 17, 2022 at 18:15
  • 1
    @Lennart MERGE has a number of known bugs and issues so probably it's better off they aren't.
    – J.D.
    Commented Jun 17, 2022 at 19:52
  • 1
    @J.D., thanks. Was not aware the SQL server had these issues. I'll keep it in mind for the future Commented Jun 18, 2022 at 11:12

1 Answer 1

1

Why not try inserting values into a table variable instead of a CTE? CTEs are only really helpful when navigating a recursive dataset.

BEGIN TRAN;

DECLARE @JSONTable TABLE
(
    StockCode VARCHAR(30)
    ,Qty      DECIMAL(18, 6)
);

INSERT INTO @JSONTable
(
    StockCode
    ,Qty
)
SELECT  StockCode
        ,SUM(Qty) AS Qty
FROM
        OPENJSON(@json)
        WITH (
                 StockCode VARCHAR(30) '$.StockCode'
                 ,Qty DECIMAL(18, 6) '$.Qty'
             )
GROUP BY
        StockCode;

/* Update existing values */
BEGIN
    UPDATE  ur
    SET     ReceivedQty = ReceivedQty - jt.Qty
    FROM    UserReceivedQty AS ur
            INNER JOIN @JSONTable AS jt ON jt.StockCode = ur.StockCode;
END;

/* Insert new values */
IF @@ROWCOUNT = 0
BEGIN
    INSERT INTO UserReceivedQty
    (
        StockCode
        ,ReceivedQty
    )
    SELECT  jt.StockCode
            ,jt.Qty
    FROM    @JSONTable AS jt;
END;

COMMIT TRAN;

RETURN @@ERROR;
2
  • Thanks SO much for that snippet! I had a feeling I was going to need to utilize a temp table instead of CTE. Very much appreciated.
    – hap76
    Commented Jun 24, 2022 at 18:33
  • Hey Mike, quick question on this: I'm trying to track down why the INSERT block fails sometimes, while the UPDATE succeeds. My suspicion is the script runs while an hourly database backup is running. The backup takes 5 minutes. Is it possible that if the script runs during this period, that the UPDATE block would succeed, but the INSERT would fail?
    – hap76
    Commented Oct 6, 2022 at 16:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.