4

I am no database admin nor is my SQL-foo any good (yet), but have a few tasks to get done in MSSQL, and one is a rather time intensive procedure where I fetch from a CURSOR and WHILE @@FETCH_STATUS = 0, I have multiple SELECTs and an UPDATE on giant tables. (The procedure works, I've checked with a colleague)

Now in a script or a program I'd have a simple print @current_cursor or print a counter in every WHILE just to have some feedback.

How could I do something like this in MSSQL? (Working on Microsoft SQL Server Enterprise Edition v9.00.3042.00, i.e. SQL Server 2005 Service Pack 2)

Could I do a print @current_cursor, or would that be a no-go in my case?

edit

I've tested the RAISEERROR with the following statements:

DECLARE @cur_id int; 
SET @cur_id = 0;

DECLARE Cur CURSOR FOR
    SELECT ID FROM TableA
    WHERE ID > 100  ORDER BY ID DESC

OPEN Cur
FETCH NEXT FROM Cur INTO @cur_id;

WHILE @@FETCH_STATUS = 0
BEGIN
    RAISERROR(@cur_id, 0, 0) WITH NOWAIT
    FETCH NEXT FROM Cur INTO @cur_id;
END

But this leads to some time of waiting without output and then an endless wall of (I assume one for every RAISERROR):

Msg 18054, Level 16, State 1, Line 17
Error 983700, severity 0, state 0 was raised, but no message with that error number was found in sys.messages. If error is larger than 50000, make sure the user-defined message is added using sp_addmessage.
  • 1
    For your error, you can't pass an int in to the message - it thinks you're passing in a message id. Try making @MsgStr varchar(100) = CAST(@Cur_id as varchar(100)) instead. – JNK Nov 13 '14 at 14:32
  • sweet, thank you! I'm going to assume that the I still get all of them at once, because the SELECT is the time intensive thing here and in the while I just print the ID. Thanks man! – Daedalus Mythos Nov 13 '14 at 14:37
  • 2
    Regarding your cursor, please read this. – Aaron Bertrand Nov 13 '14 at 15:52
  • 2
    @AaronBertrand That's my 'learn one new thing' for today. Great article. – Mark Sinkinson Nov 13 '14 at 16:40
10

In SQL Server (and I think this should work in the ancient version you say you're using), the normal method for this is to use RAISERROR. PRINT won't display any output until the process finishes which is not very useful. I use this technique a lot in batch scripts for say updates and deletes where I want to keep the batch size small.

In the example below, I set a @Batchsize variable and my loop just works on that many rows at a time, then I output that with a RAISERROR. It's important to use a severity of 0, which means nothing will treat it as an error, just as an informational message.

DECLARE @MsgStr varchar(200) = 'Inserted ' + CAST(@BatchSize AS varchar(10)) + ' rows...'

RAISERROR(@MsgStr, 0, 0) WITH NOWAIT
  • thank you very much for the suggestion. I've tested this solution and added the test an my issue with it in OP. I really appreciate the help..! – Daedalus Mythos Nov 13 '14 at 14:25
  • 2
    JNK and @DaedalusMythos : I am 99.99% certain that the statement regarding PRINT not displaying until the process is over is incorrect. It does get held up more than RAISERROR() WITH NOWAIT as there is no WITH NOWAIT option, but it does not specifically wait for the process to finish. But yes, RAISERROR WITH NOWAIT is better. And since you are using RAISERROR, why go through the trouble of declaring a variable and casting? Why not just use: RAISERROR('Inserted %d rows; ID = %d ...', 0, 0, @BatchSize, @cur_id) ? – Solomon Rutzky Nov 14 '14 at 5:23
4

You can also do:

RAISERROR('%d', 0, 0, @cur_id) WITH NOWAIT;

However depending on how many of these you have you can still overrun the buffer provided by Management Studio, and still end up in scenarios where the output isn't printing immediately.

4

I think maybe the intention of what is being asked for here is getting lost by the distraction of trying to address the literal question of how to get the RAISERROR to work. @wBob asked a great question: "why are you watching it?" The intention here is to have insight into where the process is, how far it has gotten, etc. That doesn't require PRINT or RAISERROR.

A better approach would be to create a status table to keep track of this process (although, to be fair, there is merit in @wBob's suggestion to refactor this process to be more set-based, but we don't know the situation and refactoring this might be unfeasible for one or more reasons). So, think about whether you want just the current ID (i.e. just a status) or a running trail of what has happened so far (i.e. a log / history). Assuming you might want a log/history table (it would be the most similar to having the individual messages printing out), create something similar to:

ProcessTime DATETIME NOT NULL PRIMARY KEY DEFAULT (GETDATE()),
RowsInserted INT NOT NULL,
CurrentID INT NOT NULL

Then, just after the BEGIN of the WHILE loop, do the insert:

INSERT INTO ProcessLog (RowsInserted, CurrentID) VALUES (@BatchSize, @cur_id);

Make sure that IF you are grouping the statements inside the loop in a transaction, that this INSERT happens before the BEGIN TRAN.

Now:

  • You don't have to watch it :).
  • You don't have to worry about the buffer in SSMS getting filled up and hence not report messages immediately (something that @Aaron mentioned and something that I have experienced quite a few times).
  • The status is available to anyone able to do a SELECT on the table, rather than only to someone looking at your screen.
  • You (or someone else) can monitor it via actual monitoring software.
  • You don't lose that history when your computer automatically reboots because there were Windows Updates to apply and IT set up a policy to force the reboots and hence it is no longer on your screen in the morning (any bets on whether or not I used to work at a place with such a policy? ;-).
1

Honestly why are you watching it? If the proc really takes that long, schedule it to run overnight using a SQL Agent job so it's done in the morning when you come in.

Longer term, consider refactoring the proc to use set-based logic as this will almost certainly be faster. At least when you have existing code you have something to test against, eg identify the top bottleneck, make a change, do I get the same result? Ok, make another change ...

Use time saved not waiting for procs to finish to read some of the links provided to you and/or improve your SQL skills. Consider whether refactoring to use SSIS might help as it can do things in parallel for example.

If you need further help with your code, post questions here, supplying DDL and sample data, following the guidelines and I'm sure someone will be able to help you.

Finally, you are on an old version of SQL Server and an out-of-support service pack. Consider bringing the service pack up-to-date and/or upgrading to SQL Server 2014 which came out in April this year.

Good luck! : )

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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