Archive

Archive for the ‘Database’ Category

Transact-SQL: NOT IN in a nested select

September 12th, 2010 No comments

Occorre fare attenzione alla clausola NOT IN, quando l’insieme dei dati in cui viene eseguito il controllo è una select a sua volta. Se la SELECT interna contiene valori null non viene restituito nessun record.
Qualche query su due tabelle di esempio:

CREATE TABLE TABELLA1 (id INT, descr VARCHAR(11))
INSERT INTO tabella1 VALUES (1, 'pippo')
INSERT INTO tabella1 VALUES (1, 'pluto')
INSERT INTO tabella1 VALUES (1, 'paperino')
ID DESCR
1 pippo
1 pluto
1 paperino
CREATE TABLE TABELLA2 (id INT, descr VARCHAR(11))
INSERT INTO tabella2 VALUES (2, 'pippo')
INSERT INTO tabella2 VALUES (2, 'pluto')
ID DESCR
2 pippo
2 pluto

Se voglio prendere tutti i record di TABELLA1 che non hanno una descrizione in TABELLA2 posso scrivere così:

SELECT *
  FROM tabella1 t1
 WHERE t1.descr NOT IN (SELECT t2.descr FROM tabella2 t2)

E ottengo il risultato:

ID DESCR
1 paperino

che è coerente con la query. Adesso inserisco una riga con valore null nel campo DESCR in TABELLA2;

INSERT INTO tabella2 VALUES (2,NULL)

Ora la stessa query restituisce un recordset vuoto, sbagliando.

Per continuare ad usare il NOT IN occorre inserire la condizione di IS NOT NULL alla select interna:

SELECT *
  FROM tabella1 t1
 WHERE t1.descr NOT IN (SELECT t2.descr FROM tabella2 t2 WHERE t2.descr IS NOT NULL)

Ora il recordset è di nuovo coerente con la query.

Una alternativa al NOT IN è il JOIN diretto fra le tabelle:

SELECT t1.* 
  FROM tabella1 t1 LEFT JOIN tabella2 t2 ON (t1.descr = t2.descr)
 WHERE t2.descr IS NULL

oppure utilizzando la clausola EXCEPT:

SELECT descr FROM tabella1 EXCEPT SELECT descr FROM tabella2

Il problema, ad onore del vero, non è solo di SQLServer, ma è condiviso anche da altri DBMS.

Categories: Database, SQL Server Tags: ,

Trasact-SQL: utilizzare una variabile nella clausola SELECT TOP

June 30th, 2010 1 comment

Mi sono imbattuto in questa problematica: utilizzare una SELECT TOP all’interno di un ciclo, in cui il numero di record da utilizzare è assegnato ad una variable.
Questo è l’esempio che non funziona:

DECLARE @num INT
 
DECLARE c_curs CURSOR FOR
   [...]
OPEN c_curs
 
FETCH NEXT FROM c_curs
INTO @num
 
WHILE @@FETCH_STATUS = 0
BEGIN
   [...]
   SELECT TOP @num [...] ORDER BY NEWID()
 
   FETCH NEXT FROM c_curs
   INTO @anno, @num
END
 
CLOSE c_curs
DEALLOCATE c_curs

Ho risolto utilizzando ROWCOUNT che è invece parametrico:

SET ROWCOUNT @num
SELECT [...] ORDER BY NEWID()
SET ROWCOUNT 0
 
FETCH NEXT FROM c_curs
INTO @num

E’ importante ricordarsi di ripristinare il valore di ROWCOUNT a 0.

Curiosando in rete ho trovato questo articolo che spiega meglio il problema, e offre più di una soluzione: http://sqlserver2000.databases.aspfaq.com/how-do-i-use-a-variable-in-a-top-clause-in-sql-server.html

Transact-SQL: creare un password di caratteri casuali

April 21st, 2010 No comments

Si può fare utilizzando SELECT TOP 1 … ORDER BY NEWID(), da una tabella contenente i caratteri disponibili. Ecco l’esempio:

WITH char_table AS (
	SELECT 'a' AS a
    UNION
	SELECT 'b' AS a
        ...
        /* inserire qui l'elenco dei caratteri che si 
            vogliono prendere in considerazione */
        ...
	UNION
	SELECT 'z' AS a
)
SELECT
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid()) +
(SELECT top 1 a FROM char_table ORDER BY newid())
Categories: Database, SQL Server Tags: , ,

SQL Server: Hash and Stream aggregate algorithm

April 19th, 2010 No comments

SQL Server utilizza due operatori fisici per gestire le aggregazioni (GROUP BY): lo Stream aggregate e l’Hash aggregate. Alla base di questi due operatori vi sono due diversi algoritmi, con caratteristiche diverse, che SQL Server sceglie a seconda della situazione.

Un esempio:

DROP TABLE ORDINI
CREATE TABLE ORDINI (IDPRODOTTO INT, QUANTITA INT)
GO
 
INSERT INTO ORDINI VALUES (1, 1)
GO 50
INSERT INTO ORDINI VALUES (2, 1)
GO 30
INSERT INTO ORDINI VALUES (3, 1)
GO 60

La query di aggregazione:

SELECT IDPRODOTTO, COUNT(IDPRODOTTO) AS [ORDINATO]
  FROM ORDINI
 GROUP BY IDPRODOTTO

Viene risolta in questo modo attraverso l’operatore Stream:

aggregate1

Ora inseriamo un pò di record e rifacciamo la stessa query:

INSERT INTO ORDINI VALUES (1, 1)
GO 5000
INSERT INTO ORDINI VALUES (2, 1)
GO 3000
INSERT INTO ORDINI VALUES (3, 1)
GO 6000
 
SELECT IDPRODOTTO, COUNT(IDPRODOTTO) AS [ORDINATO]
  FROM ORDINI
 GROUP BY IDPRODOTTO

Il piano di esecuzione questa volta prevere l’operatore Hash Match, scelta dovuta alla dimensione maggiore della tabella:

aggregate2

I due algoritmi alla base degli operatori sono:

  • Algoritmo hash
    Per ogni riga in input
    {
       Calcola il valore hash basato sulle colonne incluse nella clausola group by
       cerca il valore trovato nella tabella hash
       if non trovato
          inserisce una nuova riga nella tabella hash</code>
       else
          aggiorna la riga trovato con il nuovo valore</code>
       }
       restituisce tutte le righe presenti nella tabella hash
  • Algoritmo Stream

    pulisce i totali correnti
    pulisce il valore attuale delle colonne di group by
    per ogni riga in input
    {
       se la riga attuale non corrisponde con il valore attuale delle colonne del group by
       {
          restituisce i totali corrente
          pulisce i totali correnti
          imposta il valore attuale delle colonne di group by con la riga corrente
       }
       update del totale di aggregazione
    }

SQL: Ottenere solo un record con determinate caratteristiche da ogni raggruppamento di un group by

April 15th, 2010 1 comment

Oggi ho guadagnato una medaglia per aver risolto questo problema:

Data questa tabella:

columnn1 	Column2
a 		2010-04-15 00:00:00.000
a 		2010-04-17 00:00:00.000
b 		2010-04-17 00:00:00.000
b 		2010-04-15 00:00:00.000
b 		2010-04-20 00:00:00.000
c 		2010-04-19 00:00:00.000
d 		2010-04-12 00:00:00.000
d 		2010-04-13 00:00:00.000

come si fa ad estrarre, per ogni codice, quello la cui data è più vicina alla data corrente? Ovvero ad avere il risultato:

a 		2010-04-15 00:00:00.000
b 		2010-04-15 00:00:00.000
c 		2010-04-19 00:00:00.000
d 		2010-04-13 00:00:00.000

La soluzione è:

  SELECT column1, column2
    FROM prova
   WHERE
    (SELECT COUNT(*)
       FROM prova AS p
      WHERE p.column1 = prova.column1
        AND  abs(datediff(DAY, p.column2, getdate())) <  abs(datediff(DAY, prova.column2, getdate()))
    ) = 0

Il problema è simile al solito dilemma: dato un group by, come faccio ad estrarre solo i record che hanno valore massimo (o che hanno massima occorrenza o altre condizioni)?
Il risultato è un self-join che in maniera generica può essere scritto così:


select [campi]

from [tabella] t1

where

( select [funzione scalare per la scelta dei campi]

from [tabella] as t2

where t1.key = t2.key

and [condizione di confronto fra due campi (anche calcolati) della tabella]

) [condizione sulla funzione scalare definita]

Il classico esempio: di ogni gruppo vogliamo solo il record di valore massimo

select key, valore

from [tabella] t1

where

( select count(*)

from [tabella] as t2

where t1.key = t2.key

and t2.valore > t1.valore -- prendo il maggiore

) = 0

Ovvero, prendo il record della prima tabella che non ha record con valore superiore sulla seconda tabella.
Se volessimo prendere il valore minino basterebbe invertire la condizione di confronto fra i due valori:


select key, valore

from [tabella] t1

where

( select count(*)

from [tabella] as t2

where t1.key = t2.key

and t2.valore < t1.valore -- prendo il minore

) = 0

Se vogliamo prendere i 2 valori più alti di ogni gruppo, basta cambiare la condizione finale:


select key, valore

from [tabella] t1

where

( select count(*)

from [tabella] as t2

where t1.key = t2.key

and t2.valore minore t1.valore

) < 2 -- numero di record restituiti per la condizione

Categories: Database Tags: ,