Home > News > Breaking News > Predicati sargable e valori NULL

Predicati sargable e valori NULL

In questo articolo parleremo del concetto noto con il nome di SARGability arricchendolo di alcune considerazioni sulla trattazione dei valori NULL su cui ho avuto modo di ragionare dopo aver partecipato ad una sessione di Itzik Ben-Gan e dalla quale ho tratto ispirazione.

Wikipedia definisce Sargable una condizione (o predicato) di una query se l’engine del DBMS può sfruttare un indice per accelerare l’esecuzione della query. Il termine Sargable deriva dalla contrazione di Search ARGument ABLE.

Una query le cui condizioni (o predicati) non possano produrre ricerche indicizzate su un particolare range di righe (Index Seek) è nota come query non-sargable. In genere, le query non-sargable non sono ottimizzate, quindi uno dei passaggi nell’ottimizzazione delle query è convertirle in sargable. L’effetto della ricerca sull’indice è simile a quello che si ha cercando un termine specifico in un libro sprovvisto di indici, si è costretti ad iniziare ogni volta dalla prima pagina (Index Scan), invece di accedere ad un elenco di pagine specifiche identificate dall’indice.

Perché è importante scrivere query sargable? Il tempo di esecuzione di una query si può considerare (con un minimo di approssimazione) proporzionale al numero di pagine lette. Accessi a pagine specifiche dell’indice riducono la probabilità di leggere record interessati da una transazione attiva, sui quali esiste un lock. Una concorrenza maggiore permette di far lavorare insieme un numero maggiore di operatori.

Consideriamo la query seguente che estrae gli ordini fornitore dalla tabella Purchasing.PurchaseOrders del database WideWorldImporters e li restituisce in ordine crescente per la colonna ExpectedDeliveryDate.

SELECT
  PurchaseOrderID, ExpectedDeliveryDate
FROM
  Purchasing.PurchaseOrders
ORDER BY
  ExpectedDeliveryDate;

By default le righe con la colonna ExpectedDeliveryDate valorizzata a NULL verranno estratte per prime, si tratta di una scelta implementativa del linguaggio T-SQL che considera, in questo caso, il valore NULL minore di qualsiasi data.

Per questa query si desidera invertire l’ordine di presentazione dei dati, presentando per prime le righe aventi ExpectedDeliveryDate con valori diversi da NULL in ordine crescente e successivamente le righe con ExpectedDeliveryDate valorizzata con NULL. Come possiamo fare? Una possibile soluzione è quella di applicare l’espressione CASE all’interno della clausola ORDER BY.

SELECT
  PurchaseOrderID, ExpectedDeliveryDate
FROM
  Purchasing.PurchaseOrders
ORDER BY
  CASE
    WHEN (ExpectedDeliveryDate IS NOT NULL) THEN 0 ELSE 1
  END;

La query che otteniamo non è sargable, utilizza l’indice IX_Purchasing_PurchaseOrders_ExpectedDeliveryDate per la colonna ExpectedDeliveryDate su cui viene effettuata una scansione (Index Scan) al posto di un accesso puntale (Index Seek) ottimizzato.

La figura seguente illustra il piano di esecuzione della query (non-sargable).

Come possiamo rendere questa query sargable mantenendo il pre-requisito sull’ordinamento?

Si può dividere la query in due query, dalla prima verranno estratte le righe con valori di ExpectedDeliveryDate diversi da NULL, dalla seconda query verranno estratte le righe con valori NULL in ExpectedDeliveryDate. La clausola UNION ALL permetterà di unire i dataset e la successiva clausola ORDER BY applicherà l’ordinamento richiesto.

SELECT
  PurchaseOrderID, ExpectedDeliveryDate, SortOrder = 0
FROM
  Purchasing.PurchaseOrders
WHERE
  ExpectedDeliveryDate IS NOT NULL

UNION ALL

SELECT
  PurchaseOrderID, ExpectedDeliveryDate, SortOrder = 1
FROM
  Purchasing.PurchaseOrders
WHERE
  ExpectedDeliveryDate IS NULL
ORDER BY
  SortOrder, ExpectedDeliveryDate;

Eseguendo la query di osserva che l’indice IX_Purchasing_PurchaseOrders_ExpectedDeliveryDate viene acceduto due volte per leggere un particolare range di righe (Index Seek). Il primo accesso restituirà le righe con valori diversi da NULL in ExpectedDeliveryDate. Il secondo accesso restituirà le righe con il valore NULL nella colonna ExpectedDeliveryDate. I due dataset vengono uniti con l’operatore Merge Join come illustrato nella figura seguente. La query è sargable.

Ora si desidera implementare una stored procedure in grado di estrarre gli ordini fornitore in consegna ad una certa data e quelli la cui data di consegna è indefinita (NULL). Il seguente codice T-SQL implementa la stored procedure sp_undefined_deliverydate nello schema Purchasing.

CREATE PROCEDURE Purchasing.sp_undefined_deliverydate
(@DeliveryDate Date)
AS BEGIN
  SELECT
    PurchaseOrderID, ExpectedDeliveryDate
  FROM
    Purchasing.PurchaseOrders
  WHERE
    ExpectedDeliveryDate = @DeliveryDate;
END;

Cosa accade se la stored procedure viene eseguita con il parametro @DeliveryDate valorizzato a NULL?

EXEC Purchasing.sp_undefined_deliverydate @DeliveryDate = NULL;

L’operatore di confronto “=” che si troverà ad operare con l’opzione ANSI_NULLS impostata a ON (default per ogni connessione) valuterà ogni valore NULL diverso da ogni altro NULL. Quando si troverà a valutare l’espressione NULL = NULL (?) restituirà false.

Il dataset restituito sarà vuoto. Dal punto di vista delle performance, la query è sargable ma deve essere modificata per gestire correttamente i NULL. Qualcuno di voi avrà pensato di utilizzare la funzione T-SQL ISNULL che restituisce un valore specificato qualora si incontri un record con un valore NULL nella colonna passata come primo parametro di input. La stored procedure viene quindi modificata come riportato di seguito.

ALTER PROCEDURE Purchasing.sp_undefined_deliverydate
(@DeliveryDate Date)
AS BEGIN
  SELECT
    PurchaseOrderID, ExpectedDeliveryDate
  FROM
    Purchasing.PurchaseOrders
  WHERE
    ISNULL(ExpectedDeliveryDate, '99991231') = ISNULL(@DeliveryDate, '99991231');
END;

Se la stored procedure viene ora eseguita con il parametro @DeliveryDate valorizzato a NULL il dataset restituito è corretto ma la query interna non è sargable come illustra il piano di esecuzione nella figura seguente.

Sì può ottimizzare la query di estrazione dati rendendola sargable in modo elegante ed efficiente?

L’operatore T-SQL INTERSECT ci viene in aiuto in questi casi. INTERSECT restituisce le righe distinte che vengono rilevate nelle query di input a sinistra e a destra dell’operatore, come suggerisce il nome dell’operatore viene restituita l’intersezione dei dataset forniti in input.

La stored procedure viene quindi modificata come riportato di seguito.

ALTER PROCEDURE Purchasing.sp_undefined_deliverydate
(@DeliveryDate Date)
AS BEGIN
  SELECT
    PurchaseOrderID, ExpectedDeliveryDate
  FROM
    Purchasing.PurchaseOrders
  WHERE
    EXISTS(SELECT ExpectedDeliveryDate INTERSECT SELECT @DeliveryDate);
END;

Ripetiamo l’esecuzione della stored procedure..

EXEC Purchasing.sp_undefined_deliverydate @DeliveryDate = NULL;

Il predicato nella query di estrazione dati è sargable, il piano di esecuzione illustrato nella figura seguente mostra ora l’operatore Index Seek applicato all’indice IX_Purchasing_PurchaseOrders_ExpectedDeliveryDate, la query è sargable.

Anche le conversioni implicite tra tipi di dato possono rendere non sargable le condizioni di una query, per maggiori dettagli potete leggere questi articoli:

Conclusioni

Uno degli step del processo di ottimizzazione query è quello di convertire le condizioni (o predicati) delle query in condizioni sargale.

La trattazione dei valori NULL merita sempre di essere verificata specialmente quando si utilizzano parametri passati a funzioni o stored procedure.

L’operatore INTERSECT fornisce un pattern di applicazione quando è necessario eseguire l’intersezione di due insiemi, anche quando uno dei due insiemi è vuoto.

Chi è Sergio Govoni

Sergio Govoni è laureato in Scienze e Tecnologie Informatiche. Da oltre 16 anni lavora presso una software house che produce un noto sistema ERP, distribuito a livello nazionale ed internazionale, multi azienda client/server su piattaforma Win32. Attualmente si occupa di progettazione e analisi funzionale, coordina un team di sviluppo ed è responsabile tecnico di prodotto. Lavora con SQL Server dalla versione 7.0 e si è occupato d'implementazione e manutenzione di database relazionali in ambito gestionale, ottimizzazione delle prestazioni e problem solving. Nello staff di UGISS si dedica alla formazione e alla divulgazione in ambito SQL Server e tecnologie a esso collegate, scrivendo articoli e partecipando come speaker ai workshop e alle iniziative del primo e più importante User Group Italiano sulla tecnologia SQL Server. Ha conseguito la certificazione MCP, MCTS SQL Server. Per il suo contributo nelle comunità tecniche e per la condivisione della propria esperienza con altri, dal 2010 riceve il riconoscimento SQL Server MVP (Microsoft Most Valuable Professional). Nel corso dell'anno 2011 ha contribuito alla scrittura del libro SQL Server MVP Deep Dives Volume 2 (http://www.manning.com/delaney/).

Leggi Anche

Azure Key Vault e certificati code-signing: Strategie per la conformità post 1° Giugno 2023!

In questi giorni, ho avuto l’opportunità di esplorare il rinnovo di un certificato di code-signing …