Le istruzioni PRINT e RAISERROR in Transact-Sql

Quello che presento qui è il classico tip che potrebbe tornare utile una volta nella vita, magari due, più probabilmente mai, ma il giorno che fosse necessario, trovare le informazioni potrebbe rilevarsi un’impresa: la PRINT non è altro che una scorciatoia per la RAISERROR con la severity impostata a 0 (ZERO).

Devo ammettere che il mio interesse al riguardo è nato scrivendo uno script ad hoc che più schifoso non avrebbe potuto essere: dovevo verificare se e quando un determinato record venisse aggiornato da una procedura automatica.

La prima tentazione era di scrivere la SELECT brutale in Management Studio e brutalmente lanciarla mettendo un bel mattone sul tasto F5 fino a vedere il campo aggiornato con il nuovo valore. Solo che non era certo che la procedura aggiornasse effettivamente il campo; in effetti poi risultò che non lo faceva, ma questa è un’altra storia!

E comunque la procedura veniva lanciata ogni tot minuti: a parte l’indisponibilità di un mattone, continuare a mandare in esecuzione la query 4 volte al secondo non mi è parso il massimo della vita, oltre che dell’educazione nei confronti del server stesso e quindi, di riflesso, degli altri utenti (ma di questi ultimi a noi che c’importa, giusto? Giusto!).

L’uovo di Colombo (il marinaio, non il tenente!) è stato fare un bel ciclo WHILE al cui interno piazzare una semplice WAITFOR DELAY che ferma lo script per 30 secondi per poi rieseguire la query… e così via finchè il campo non viene aggiornato; sempre all’interno del ciclo una semplice PRINT giusto per vedere il valore del record.

DECLARE @state nvarchar(2);
DECLARE @counter int;
DECLARE @msg nvarchar(max);

SET @state = '';
SET @counter = 0;

WHILE @state != 'E'
BEGIN
  SELECT @state = [Stato]
  FROM [dbo].[Ordini]
  WHERE [Ordine] = 123456;

  SET @counter = @counter + 1;
  SET @msg = Convert(varchar(8), @counter ) + ' - @state = T';

  PRINT @msg;

  WAITFOR DELAY '00:00:30';
END

Geniale, nevvero? Se solo funzionasse… Oh, non è che vada in errore: il problema è che la PRINT non… PRINTA!

Cercando in giro, viene spiegato che la PRINT non produce effetti visibili fino al termine dell’esecuzione dello script o, per essere più precisi, fino al termine dell’esecuzione dal batch ): e infatti, il consiglio più gettonato è quello di usare la parola chiave GO per “chiudere” prima il batch e forzare la stampa.

Peccato che la GO abbia uno sgradevole effetto collaterale sulle variabili che abbiamo definito: le termina! Per tacere del fatto che possiamo tranquillamennte scordarci di usarla dentro una stored procedure o una user function.

Come già altre volte in passato, la dritta giusta arriva da Erland Sommarskog, uno che il titolo di Sql Server MVP non se l’è certo guadagnato coi punti del supermercato. E’ lui infatti che spiega che i messaggi sono bufferizati (scusate l’orribile traduzione) e suggerisce che, dal momento che la PRINT non è altro che uno shortcut per la RAISERROR con la severity impostata a 0 (ZERO), possiamo usare quest’ultima con l’opzione WITH NOWAIT e goderci il risultato:

DECLARE @state nvarchar(2);
DECLARE @counter int;
DECLARE @msg nvarchar(max);

SET @state = '';
SET @counter = 0;

WHILE @state != 'E'
BEGIN
  SELECT @state = [Stato]
  FROM [dbo].[Ordini]
  WHERE [Ordine] = 123456;

  SET @counter = @counter + 1;
  SET @msg = Convert(varchar(8), @counter ) + ' - @state = T';

  RAISERROR( @msg , 0, 1) WITH NOWAIT;

  WAITFOR DELAY '00:00:30';
END

Ohhhh… adesso sì che ci siamo!!!

Tanto per cambiare (e non vivere una vita troppo tranquilla), ci sono però alcune considerazioni da tenere presente.

Innanzitutto, quando si invoca la RAISERROR .... WITH NOWAIT, in realtà viene passato al client tutto ciò che era stato precedentemente messo nel buffer: ad esempio se avessi messo una PRINT 'Ho atteso 30 secondi' subito prima della WAITFOR DELAY '00:00:30', il messaggio sarebbe apparso soltanto appena prima dell’invocazione della RAISERROR, quindi in realtà dopo i famosi 30 secondi (più il tempo eventualmente richiesto dalla query).

Quando si usa un blocco TRY...CATCH solo gli errori che hanno severity > 10 portano l’esecuzione del codice all’interno del blocco CATCH. Gli errori con severity 0 e 10 sono in realtà puramente informativi e nemmeno impostano la variabile di sistema @@error; gli errori con severity 19 o superiore possono essere innalzati solo da utenti con permessi di amministratore e comunque con l’opzione WITH LOG; inoltre gli errori con severity maggiore o uguale a 20 terminano la connessione.

L’ultima cosa da considerare è che esiste[va?] un bug nel motore di Sql Server per il quale vengono bufferizzati i messaggi per le procedure chiamate tramite RPC (Remote Procedure Call), come è la norma per la maggior parte delle applicazioni – con l’ovvia esclusione di Query Analyzer o Management Studio, che evidentemente godono di un canale privilegiato. In realtà l’informazione sul bug si riferiva a Sql Server 2000, ma non ho potuto verificare se con Sql Server 2005 (o successivi) il baco sia stato risolto: perdonatemi ma l’idea di scrivere un’applicazione solo per eseguire la query ad hoc non mi è passata nemmeno per l’anticamera del cervello.

Vedi anche

PRINT within WHILE loop is not being displayed – MSDN – Erland Sommarskog

PRINT vs RAISERROR (by Jim McLeod)

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

*