posts - 461, comments - 1497, trackbacks - 139

Select count(*), ExecuteScalar e NullReferenceException

In sintesi il problema è l'esecuzione con ExecuteScalar di una “select count(*) ...“.

Non so per quale strano destino non mi sia mai capitato prima ma questa banale operazione mi ha fatto perdere una mezzoretta per capire come mai la ExecuteScalar (su SQL Server 2000 SP3) lanciava una NullReferenceException.

Per prima cosa controllo sotto debugger che il command sia valido, che la connection sia corretta e aperta, che i parameters siano popolati correttamente .... insomma tutto senza problemi ma la NullReferenceException continua ad imperversare.

Da una select count(*) non mi aspettavo altro che un banale intero ma ecco che lanciando il comando nel query analyzer scopro che .... alcune volte la select count(*) ritorna un resultset vuoto!. Alcune volte non significa che il comportamento è randomico ma per esempio capita quando c'è la clausola “group by“.

Forse per alcuni è una banalità ma io mi aspettavo un più tradizionale “0” e cioè un resultset di una riga ed una colonna con un intero nell'unica riga.
Ovviamente ado.net non ne può nulla perchè se il resultset non c'è non può accadere nulla di diverso visto che la ExecuteScalar ha proprio come scopo quello di tornare il risultato della prima cella del resultset che quindi deve esistere.

Spero solo che esista un ottimo motivo nello standard SQL per cui certe volte questo possa accadere.

Print | posted on martedì 23 novembre 2004 21.01 | Filed Under [ ADO.NET [Italiano] ]

Feedback

Gravatar

# re: Select count(*), ExecuteScalar e NullReferenceException

Credo che sia una questione di insiemistica sulla quale si basa il modello relazionale più che di SQL (che è "semplicemente" il dialetto più famoso per interrogare db relazionali).

se c'è un group by il risultato non è scalare ma restituisce un insieme.

A memoria direi che in base al criterio di group by si genera una partizione dell'insieme (determinato da from e where) e per ogni partizione viene fatto il count (o qualsiasi altra funzione aggregata).
Da questa partizione l'elemento "insieme vuoto" è escluso.

andando a memoria... questo è solo un indizio
23/11/2004 22.14 | Luca Minudel
Gravatar

# re: Select count(*), ExecuteScalar e NullReferenceException

Ciao Luka,
grazie della risposta ma se è così, manca il filo della coerenza. Cioè se chiedo quanti elementi ci sono in un insieme vuoto, la risposta deve essere zero e non un resultset vuoto.
Mi sembra ancora impossibile che non mi sia capitato prima ... veramente incredibile...
23/11/2004 23.32 | Raffaele Rialdi
Gravatar

# re: Select count(*), ExecuteScalar e NullReferenceException

Raffaele, il punto è proprio che manca l'insieme.
Una
SELECT COUNT(*) FROM tabella
restituisce sempre un record.

Una
SELECT x, COUNT(*) FROM tabella GROUP BY x
restituisce da 0 a N righe, tante quanti sono i diversi valori di x nelle righe di tabella; se tabella è vuota... quanto dovrebbe valere x?

24/11/2004 1.26 | Marco Russo
Gravatar

# re: Select count(*), ExecuteScalar e NullReferenceException

Ciao Marco,
credo che nel caso che tu hai posto il resultset vuoto sia giustissimo.
Credo anche però che sia diverso se nella clausola select sia presente anche un'altro campo.
Parlo di diverso nel senso logico: se chiedo di contare gli elementi di un insieme, mi aspetto di ricevere comunque un numero, anche nel caso dell'insieme vuoto. Diverso è se voglio avere in una colonna del resultset *anche* il count.
Comunque come hai visto non ho certo ggridato al bug perchè capisco sia opinabile anche se non mi piace il comportamento attuale.
24/11/2004 13.00 | Raffaele Rialdi
Gravatar

# re: Select count(*), ExecuteScalar e NullReferenceException

Se stai usando il group by non stai chiedendo di contare gli elementi di un insieme ma di n insiemi con n che potrebbe essere zero.

Ecco un esempio.

Pensa a un insieme di elementi di tipo INT x CHAR - colonne IDUTENTE, CLASSE - :
{ (1, A), (2, B), (3, B), (4, A), (5, C), (6, A)}

il Select count(*) group by CLASSE farà una partizione dell'insieme in base a CLASSE ottenendo questi insiemi:
- { (1, A), (4, A), (6, A) }
- { (2, B), (3, B)}
- { (5, C) }
a questo punto per ogniuno di questi 3 insiemi calcola un Count(*) e quindi restituirà 3 righe:
- 3 = count(*) di { (1, A), (4, A), (6, A) }
- 2 = count(*) di { (2, B), (3, B)}
- 1 = count(*) di { (5, C) }

Se l'insieme iniziale fosse stato vuoto {} il Select count(*) group by CLASSE ottiene una partizione dell'insieme in base a CLASSE composta da *zero* insiemi e per "ogniuno" di questi *zero* insiemi calcola un Count(*) e quindi restituisce *zero* righe.

Ecco xKé è concettualmente sbagliato usare ExecuteScalar quando c'è un goup by ed il risultato che si ottiene usando ExecuteScalar quando c'è un goup by non è applicabile sia nel caso in cui la tabella sia vuota sia nel caso in cui più righe cadano su diversi group by.

Se hai la certezza (per la condizione di where che poni) che il group by ritornerà una sola riga allora l'ExecuteScalar è applicabile ma a questo punto puoi fare a meno di usare il group by e tutto funziona come desideri (zero righe => count a zero).

HTH


24/11/2004 21.03 | Luca Minudel
Gravatar

# re: Select count(*), ExecuteScalar e NullReferenceException

Ottimo sviluppo Luka, quello che mi sarei aspettato (come dicevo anche a Marco) è che quando il group by restituisce zero righe e l'unica colonna è il count(*) allora avrebbe avuto molto senso restituire una riga con zero.
Come invece hai mostrato tu, la coerenza logica c'è, ma è in sola ragione dell'insieme e non tiene conto del count(*) e questo dovrebbe secondo me cambiare tutto.
In sostanza (e forse l'ho già ripetuto troppe volte ;-)) se chiedo quante righe ci sono nell'insieme vuoto, mi aspetto di ricevere una risposta con zero e non il silenzio.
24/11/2004 22.16 | Raffaele Rialdi

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 5 and 7 and type the answer here:

Powered by: