Trouble using GO or other batch command.

  • Hello all. SQL Server 2008 T-SQL question but not necessarily about new features.

    I am stumped. I am trying to run some T-SQL to fix some problems in one of our DBs. I just want to drop code into Management Studio and let it rip.

    I am aware of the compiled nature of T-SQL and its need to have GO as a batch separator. However, I can’t make it work at all! I have already read many posts, including these:

    http://stackoverflow.com/questions/6376866/tsql-how-to-use-go-inside-of-a-begin-end-block

    http://stackoverflow.com/questions/4855537/sql-server-2000-alter-table-insert-into-errors/4855582#4855582

    http://stackoverflow.com/questions/2668529/t-sql-go-statement

    But nothing helps. I have a section of code that loops a temp table like so:

    WHILE (SELECT COUNT(*) FROM #temp WHERE Processed = 0) > 0

    BEGIN

    END

    Inside that loop, once a row has been processed it has its Processed column tripped to 1, like so:

    UPDATE #temp

    SET Processed = 1

    WHERE

    Foo = @Foo

    AND (Bar = @bar);

    But the UPDATE code is never actually processed by SQL server and this creates an infinite loop. If I cancel execution I can see that the very first row has had its Processed value tripped to 1, but none of the other rows ever gets tripped. My assumption is that without a GO statement in there somewhere, the value is never being written to the table – until I cancel the execution and that acts as my soft GO.

    So, armed with the articles above, I attempt to add a GO into the loop. Here’s a bit more detail:

    <DECLARE variables.>

    WHILE (SELECT COUNT(*) FROM #temp WHERE Processed = 0) > 0

    BEGIN

    <Reset variables to defaults>

    <SELECT variable values from temp #temp table>

    <Use variables to do stuff – not important here yet>

    UPDATE #temp

    SET Processed = 1

    WHERE

    Foo = @Foo

    AND (Bar = @bar);

    GO

    END

    The GO causes syntax errors all over the place. Using a ; terminator makes no difference. The posts above seem to say you SHOULD be able to do this.

    I have tried placing the GO in every logical place I can think of, but no good.

    Can anyone see an issue here? How can I force the temp table to accept the value in its Processed column so I can move to the next record? If I weren’t bald I’d be tearing my hair out.

    Kurt

  • The key behind your trouble is the misunderstanding of the concept of a batch separator A batch separator is not a T-SQL concept, it is a data client concept. In other words, Management Studio is the only thing that cares about the GO. The GO is never actually submitted to SQL Server, it just tells Management Studio how to break up the code in the window as batches to send to to SQL Server.

    Consider this proper T-SQL batch:

    --<DECLARE variables.>

    WHILE (

    SELECT COUNT(*)

    FROM #temp

    WHERE Processed = 0

    ) > 0

    BEGIN

    --<Reset variables to defaults>

    --<SELECT variable values from temp #temp table>

    --<Use variables to do stuff – not important here yet>

    UPDATE #temp

    SET Processed = 1

    WHERE Foo = @Foo

    AND (Bar = @bar);

    END

    Now consider if I were to submit two separate batches to SQL Server that looked like this:

    Batch 1:

    --<DECLARE variables.>

    WHILE (

    SELECT COUNT(*)

    FROM #temp

    WHERE Processed = 0

    ) > 0

    BEGIN

    --<Reset variables to defaults>

    --<SELECT variable values from temp #temp table>

    --<Use variables to do stuff – not important here yet>

    UPDATE #temp

    SET Processed = 1

    WHERE Foo = @Foo

    AND (Bar = @bar);

    Batch 2:

    END

    This is what SQL Server sees when you add the GO to the query window and you execute all code in Management Studio...and is why it throws an error since the WHILE loop has not END.

    There are no special teachers of virtue, because virtue is taught by the whole community.
    --Plato

  • Thanks SS:

    That makes sense. But what can be done about the loop issue, then? It seems to me that the call to:

    UPDATE #temp SET Processed = 1 WHERE Foo = @Fool AND (Bar = @bar); doesn't get processed until the script is terminated. I believe this to be the case because it sets up an infinite loop and only after I manually terminate the script and dump #temp can I see that the first record has had its 'Processed' value set to 1 but none of the others do.

    Do I need to encase that UPDATE in a transaction so each pass through #temp is explicitly committed?

    Thanks.

    Kurt

    PS - I should add that debugging also showed that each pass through the loop was processing the exact same variables. It was all data from the first row.

  • Hard to say, you have only given us part of the picture. We need the whole picture. DDL (CREATE TABLE statement), sample data (INSERT INTO statements), expected results, and your entire piece of code so we know what you are doing.

  • kpwimberger (4/6/2012)


    Thanks SS:

    That makes sense. But what can be done about the loop issue, then? It seems to me that the call to:

    UPDATE #temp SET Processed = 1 WHERE Foo = @Fool AND (Bar = @bar); doesn't get processed until the script is terminated.

    Not the case unless you turned implicit transactions on (i.e. auto-commit off) or were within an explicit transaction that you did not explicitly commit. By default, unlike in Oracle where you have to provide an explicit COMMIT to commit changes, SQL Server will commit your update statements as they are issued.

    I believe this to be the case because it sets up an infinite loop and only after I manually terminate the script and dump #temp can I see that the first record has had its 'Processed' value set to 1 but none of the others do.

    This may be how it appears, and some are misled thinking there is something wrong with SQL Server, but at the end of the day it usually boils down to a problem in the code.

    ------

    Backing up for a minute, may I ask why you think you need a loop to do this processing? It is likely that you do not need it at all, and could do all your processing in a set-based way with a carefully crafted UPDATE statement.

    There are no special teachers of virtue, because virtue is taught by the whole community.
    --Plato

  • We must have been typing at the same time...ditto to everything Lynn said. In order to help further we need to see DDL (i.e. CREATE TABLE statements), DML to create sample data (i.e. INSERT statements) and code that illustrates what you're attempting to do.

    There are no special teachers of virtue, because virtue is taught by the whole community.
    --Plato

  • Lynn and SS:

    Right now there isn't actually a whole lot I am actually doing. Given that this hasn't been working I am trying to get to the root of the issue so I have the code pared to do, essentially, this:

    IF @GO = 1

    BEGIN

    SELECT * INTO #temp FROM dbo.DateL2;

    UPDATE #temp SET Processed = 0;

    END

    WHILE (SELECT COUNT(*) FROM #temp WHERE Processed = 0) > 0

    BEGIN

    /*-------------------------------------

    Clear the variables.

    --------------------------------------*/

    SET @CollectionPrefix = NULL;

    SET @Box = NULL;

    SET @Section = NULL;

    SET @Row = NULL;

    SET @Acc = NULL;

    SET @AccId1 = NULL;

    SET @AccId2 = NULL;

    SET @Locator1 = NULL;

    SET @Locator2 = NULL;

    SET @Date1 = NULL;

    SET @Date2 = NULL;

    /*-------------------------------------

    Pull variable set for this row of

    #temp.

    --------------------------------------*/

    IF @GO = 1

    BEGIN

    SELECT TOP 1

    @CollectionPrefix = CollectionPrefix

    , @Acc = DisplayAcc

    , @Box = Box

    , @Section = Section

    , @Row = Row

    , @Date1 = LDate1

    , @Date2 = LDate2

    FROM

    dbo.DateL2

    WHERE

    Processed = 0;

    SET @OUT = @OUT + 'Pulled Box: ' +COALESCE(CAST(@Box AS VarChar), 'NULL')

    + ', Section: ' + COALESCE(CAST(@Section AS VarChar), 'NULL')

    + ', Row: ' + COALESCE(@Row, 'NULL')

    + ', Acc: ' + COALESCE(@Acc, 'NULL')

    + ', Date1: ' + COALESCE(CONVERT(VarChar(10), @Date1, 101), 'NULL')

    + '.' + CHAR(13) + CHAR(13);

    END

    /*-------------------------------------

    Set current record as processed.

    --------------------------------------*/

    IF @GO = 1

    BEGIN

    UPDATE #temp

    SET Processed = 1

    WHERE

    CollectionPrefix = @CollectionPrefix

    AND (Box = @Box)

    AND (Section = @Section)

    AND (DisplayAcc = @Acc)

    AND (LDate1 = @Date1);

    END

    END

    This is somewhat 'proof-of-concept' until I can see what's not working. The odd thing is that I have used this approcah before with success but can't see the difference. Well, that's not completely true: I have always used a real table rather than a temp. Could that be the issue? I'll have to explore that.

    Anyway, I have no doubt that a well-crafted set-based solution might be slicker, but this was sup[posed to be a simple, down-n-dirty (HA!) patch and so I felt fine with a basic loop. And I guess I'm a bit of a code neandetal as I think better in loops than in sets.

    Kurt

  • Can you post the DDL for DateL2?

    This part of your update statement will not tolerate NULLs:

    WHERE CollectionPrefix = @CollectionPrefix

    AND (Box = @Box)

    AND (Section = @Section)

    AND (DisplayAcc = @Acc)

    AND (LDate1 = @Date1);

    So, if any of those columns are NULL in DateL2, then the UPDATE will fail to update any rows because nothing can be equal to NULL. In this scenario you'll continue looping over that same row, always selecting it because Processed stays at 0.

    There are no special teachers of virtue, because virtue is taught by the whole community.
    --Plato

  • I think I can tell you what's wrong....

    The table you're pulling the TOP (1) columns from is a different table to the one you're updating. So yes, it'll just be fetching the same row again and again and again.

    kpwimberger (4/6/2012)


    ------------ code removed for clarity

    SELECT TOP 1

    @CollectionPrefix = CollectionPrefix

    , @Acc = DisplayAcc

    , @Box = Box

    , @Section = Section

    , @Row = Row

    , @Date1 = LDate1

    , @Date2 = LDate2

    FROM

    dbo.DateL2 ----- SELECT from DateL2

    WHERE

    Processed = 0;

    ------------ code removed for clarity

    UPDATE #temp ------- but update #temp

    SET Processed = 1

    WHERE

    CollectionPrefix = @CollectionPrefix

    AND (Box = @Box)

    AND (Section = @Section)

    AND (DisplayAcc = @Acc)

    AND (LDate1 = @Date1);

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • Apologies opc, I have been referring to you as SS, as in your forum title.

    Anyway, here’s the scoop. Client supplies me with a spreadheet and that gets imported into SS using a SSIS package that creates this table:

    CREATE TABLE [dbo].[DateL2](

    [CollectionPrefix] [nvarchar](2) NULL,

    [Box] [int] NULL,

    [Section] [tinyint] NULL,

    [Row] [varchar](1) NULL,

    [DisplayAcc] [varchar](50) NULL,

    [lDate1] [datetime] NULL,

    [qtyDate1] [tinyint] NULL,

    [lDate2] [datetime] NULL,

    [qtyDate2] [tinyint] NULL,

    [Processed] [bit] NULL,

    [AccId1] [int] NULL,

    [AccId2] [int] NULL,

    [Locator1] [int] NULL,

    [Locator2] [int] NULL

    ) ON [PRIMARY]

    Next, this DDL is run in a discreet query window:

    /*-------------------------------------

    Locals.

    --------------------------------------*/

    DECLARE

    @CONTEXT varChar(4)

    , @OUT varChar(Max)

    , @GO bit

    , @HaveRow Bit;

    /*-------------------------------------

    Setup local vars.

    --------------------------------------*/

    SET @CONTEXT = 'DEV';

    --'PROD';

    SET @OUT = '';

    /*-------------------------------------

    Test context.

    --------------------------------------*/

    IF @CONTEXT = 'PROD'

    BEGIN

    IF @@SERVERNAME = 'SQP-aaaa'

    SET @GO = 1;

    ELSE

    SET @GO = 0;

    END

    IF @CONTEXT = 'DEV'

    BEGIN

    IF @@SERVERNAME = 'SQD-bbbb'

    SET @GO = 1;

    ELSE

    SET @GO = 0;

    END

    USE BLocations;

    /*----------------------------------------------

    Add some missing columns to dbo.dateL2

    and populate Processed column.

    -----------------------------------------------*/

    IF @GO = 1

    BEGIN

    IF COL_LENGTH('dbo.DateL2', 'Processed') IS NULL

    BEGIN

    ALTER TABLE dbo.DateL2 ADD Processed bit NULL;

    SET @OUT = @OUT + 'Created col Processed in dbo.DateL2.' + CHAR(13);

    END

    IF COL_LENGTH('dbo.DateL2', 'AccId1') IS NULL

    BEGIN

    ALTER TABLE dbo.DateL2 ADD AccId1 Int NULL;

    SET @OUT = @OUT + 'Created col AccId1 in dbo.DateL2.' + CHAR(13);

    END

    IF COL_LENGTH('dbo.DateL2', 'AccId2') IS NULL

    BEGIN

    ALTER TABLE dbo.DateL2 ADD AccId2 Int NULL;

    SET @OUT = @OUT + 'Created col AccId2 in dbo.DateL2.' + CHAR(13);

    END

    IF COL_LENGTH('dbo.DateL2', 'Locator1') IS NULL

    BEGIN

    ALTER TABLE dbo.DateL2 ADD Locator1 Int NULL;

    SET @OUT = @OUT + 'Created col Locator1 in dbo.DateL2.' + CHAR(13);

    END

    IF COL_LENGTH('dbo.DateL2', 'Locator2') IS NULL

    BEGIN

    ALTER TABLE dbo.DateL2 ADD Locator2 Int NULL;

    SET @OUT = @OUT + 'Created col Locator2 in dbo.DateL2.' + CHAR(13);

    END

    UPDATE dbo.DateL2 SET Processed = 0;

    /*---------------

    Success?

    ----------------*/

    IF (@@ERROR <> 0) OR (@@ROWCOUNT = 0)

    BEGIN

    SET @GO = 0;

    SET @OUT = @OUT + 'ERROR: Failed to set Processed = 1.' + CHAR(13);

    END

    ELSE

    SET @OUT = @OUT + 'Success: Set Processed = 1 for current record.' + CHAR(13);

    END

    END

    PRINT @OUT;

    Then we move on to the last script I posted. I know that NULLS would crash that code but they will not be an issue.

  • No worries on the name, happens all the time.

    Did you see Gail's post? I think she is onto something 😉

    There are no special teachers of virtue, because virtue is taught by the whole community.
    --Plato

  • !!!!!!!!!!!!!!! AARRRGGHHHHHHHHHHHH !!!!!!!!!!!!!!

    Gila Monster, Gail, SPOT ON!

    THAT was the issue. Oh god, I feel stupid. I have been staring at this code for HOURS and never caught that. THANK YOU, you saved my weekend. Thanks to you opc and Lynn as well for your time and effort.

    Kurt

  • Gail:

    I am currently doing a 'gratitude project' on facebook. Just thought you should know YOU are today's gratitude!

    "Today's gratitude: database consultant Gail Shaw of Johannesburg, South Africa, for her wonderful spotting of a simple and (at least to me) invisible mistake I made in code. She saved my weekend."

    Namaste!

    Kurt

  • You're welcome.

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass

Viewing 14 posts - 1 through 13 (of 13 total)

You must be logged in to reply to this topic. Login to reply