Soluzioni informatiche per l'impresa
Principale Servizi Soluzioni Supporto & Download Chi siamo Contatta
 

UDF e parametri NULL

L'autore dell'articolo è Ivan Prenosil. L'originale, in inglese, è reperibile all'indirizzo: Ivan Prenosil's Firebird/InterBase site.

Non usare NULL

È un errore abbastanza frequente utilizzare NULL per scopi diversi da quelli per cui è stato concepito. Consideriamo, per esempio, una tabella anagrafica con la colonna SECONDO_NOME. In questa tabella è scorretto utilizzare NULL, al posto di una stringa vuota, in corrispondenza di persone che mancano del secondo nome: NULL indica una grandezza sconosciuta, non un valore mancante/ non assegnato o uno spazio vuoto. Utilizzare NULL per l'attributo SECONDO_NOME significa "questa persona probabilmente ha un secondo nome, ma non lo conosciamo" (NON significa "questa persona non ha un secondo nome").

Esempio

      CREATE TABLE tab (
        NOME          VARCHAR(32) NOT NULL,
        SECONDO_NOME  VARCHAR(32) NOT NULL,
        COGNOME       VARCHAR(32) NOT NULL,
        NOME_COMPLETO COMPUTED BY
         (CapitalizeUDF(NOME || ' ' || SECONDO_NOME || ' ' || COGNOME)) );
    
Senza il vincolo NOT NULL, la concatenazione produrrebbe come risultato NULL in presenza di un attributo nullo.

Non chiamare funzioni UDF con parametri NULL

Gran parte delle funzioni UDF sono del tipo write null through, vale a dire che restituiscono NULL nel caso di argomenti NULL. Se la situazione è questa, non è necessaria alcuna chiamata alla funzione perché conosciamo già il valore che verrà calcolato.

Esempio 1

      UPDATE tab
         SET tipo_prodotto = LowerUDF (tipo_prodotto)
       WHERE tipo_prodotto IS NOT NULL;
	

Esempio 2

      CREATE TRIGGER tr FOR tab BEFORE UPDATE AS
      BEGIN
        IF (NEW.tipo_prodotto IS NOT NULL) THEN
            NEW.tipo_prodotto = LowerUDF (NEW.tipo_prodotto);
      END
    

Esempio 3

      SELECT ID, Sin(angolo) as SINE            FROM tab WHERE angolo IS NOT NULL
       UNION ALL
      SELECT ID, CAST(NULL AS DOUBLE PRECISION) FROM tab WHERE angolo IS NOT NULL;
    

Controllare i valori NULL nell'applicazione (client side)

Una domanda frequente nei newsgroup è Come posso realizzare una funzione UDF che converta un valore NULL in una stringa vuota? Mi serve per concatenare Nome+SecondoNome+Cognome. Questa operazione è, di fatto, una formattazione di una stringa, per cui è meglio eseguirla nell'applicazione, dove è semplice identificare i NULL.

Esempio

Al posto di:
SELECT NOME || ' ' || SECONDO_NOME || ' ' || COGNOME FROM …;
selezionare separatamente le componenti:
SELECT NOME, SECONDO_NOME, COGNOME FROM …;
ed eseguire la formattazione (concatenazione) nell'applicazione (cosa che, fra l'altro, permette di avere un unico spazio di separazione nel caso di secondo nome mancante).

Cosa succede chiamando una funzione UDF con un argomento NULL?

È semplice, la funzione UDF riceverà un valore zero. Per una stringa si tratterà di una "stringa di lunghezza zero", per una data "data con offset zero" rispetto alla data di base (che corrisponde al 1858-11-17)… Non potrete distinguere (all'interno della funzione UDF) se il parametro sia zero o NULL. Talvolta questo comportamento risulta utile.

Esempio

      CREATE TRIGGER tr FOR tab BEFORE UPDATE AS
      BEGIN
        NEW.NOME    = TrimUDF (NEW.NOME);
        NEW.COGNOME = TrimUDF (NEW.COGNOME);
        …
        /* l'effetto collaterale della chiamata a TrimUDF è la conversione da NULL a stringa vuota */
      END
    

Passare argomenti diversi da stringhe convertendoli in stringa

Quando si ha bisogno di passare argomenti numerici (ovvero date, time, timestamp), è possibile scrivere una funzione UDF che accetta stringhe come parametri in ingresso. In questo modo, zero sarà convertito nella stringa '0', mentre NULL diverrà la stringa vuota ''.

Esempio

Questa funzione restituisce il valore assoluto di un numero o -1 nel caso di argomento scorretto o NULL.
      function TEST_Abs (val: PChar): integer; cdecl;
      begin
        try
          Result := Abs( StrToInt(val) );
        except
          Result := -1;
        end;
      end;
          
      DECLARE EXTERNAL FUNCTION TEST_ABS
        CSTRING(20)
        RETURNS INTEGER BY VALUE
        ENTRY_POINT 'TEST_Abs' MODULE_NAME 'UDF_Examples';

      SELECT I, TEST_Abs(I) FROM TAB;

             I TEST_ABS
      ======== ========
             5        5
            -5        5
             0        0
        <null>       -1
    

Argomento composto da singolo carattere concatenato ad una stringa

Il risultato della concatenazione di una stringa con il valore NULL ritorna NULL. Dunque nel caso di questa chiamata:
      MyUDF ('*' || MyField)
	
un argomento di lunghezza zero indica NULL.

Esempio

Questa funzione restituisce la lunghezza della stringa in ingresso dopo averle applicato l'operazione Trim, oppure -1 nel caso di argomento NULL:
      function TEST_TrimLen (val: PChar): integer; cdecl;
      begin
        Result := -1;
        if val[0] = #0 then Exit;

        Inc(val);  // Salta il carattere.
        Result := Length(Trim(val));
      end;

      DECLARE EXTERNAL FUNCTION TEST_TRIMLEN
        CSTRING(20)
        RETURNS INTEGER BY VALUE
        ENTRY_POINT 'TEST_TrimLen' MODULE_NAME 'UDF_Examples';

      SELECT X || '<', TEST_TrimLen('#' || X) FROM TAB2;

                  TEST_TRIMLEN
      =========== ============
      ABCD<                  4
       EFGH <                4
       XYZ <                 3
      <                      0
      <null>                -1         
    

Ricorso ad un parametro indicatore

È possibile utilizzare un parametro supplementare destinato ad identificare un argomento di tipo NULL:
      IF (IntegerVariable IS NOT NULL)
      THEN Result = MyUDF(IntegerVariable, 1);
      ELSE Result = MyUDF(IntegerVariable, 0);
	
oppure
      MyUDF(IntegerVariable, 1+0*IntegerVariable)
    
In questa situazione è sufficiente controllare il secondo argomento della funzione: 0 indica NULL, 1 NOT NULL.

Parametri di tipo Blob

Identificare il valore NULL in un argomento di tipo Blob è semplice perché un Blob viene passato alla funzione UDF come un puntatore ad una struttura. Se l'argomento è NULL, allora il valore blob_handle della struttura sarà nullo. La dichiarazione di una struttura BLOB può essere reperita nella Developer's Guide o nei file jrd/val.h o include/ibase.h (solo Firebird).

Esempio

Questa funzione ritorna la dimensione del blob o -1 nel caso di parametro NULL.
      type
        TBlob = record
          GetSegment       : Pointer;
          BlobHandle       : ^Integer;
          SegmentCount     : LongInt;
          MaxSegmentLength : LongInt;
          TotalSize        : LongInt;
          PutSegment       : Pointer;
        end;
      PBlob = ^TBlob;


      funtion TEST_BlobSize(inBlob: PBlob): integer; cdecl;
      begin
        Result := -1;
        if (not Assigned(inBlob)) or
            not Assigned(inBlob^.BlobHandle)) then Exit;

        Result := inBlob^.TotalSize;
      end;


      DECLARE EXTERNAL FUNCTION TEST_BlobSize
        BLOB
        RETURNS INTEGER BY VALUE
        ENTRY_POINT 'TEST_BlobSize' MODULE_NAME 'UDF_Examples';


      CREATE TABLE TAB3 (I INTEGER, B BLOB);
      INSERT INTO TAB3 (I,B) VALUES (1, 'abc');
      INSERT INTO TAB3 (I,B) VALUES (2, '');
      INSERT INTO TAB3 (I,B) VALUES (3, NULL);
      /* L'esempio si riferisce a Firebird. */
      /* Con InterBase può esser necessario utilizzare una funzione di conversione String->Blob. */


      SELECT I, TEST_BlobSize(B), FROM TAB3;

            I TEST_BLOBSIZE
      ======= =============
            1             3
            2             0
            3            -1
    

Utilizzare i descrittori (descriptor) di Firebird

Di solito i parametri vengono passati alle funzioni UDF per riferimento. Firebird supporta un'ulteriore modalità: il passaggio mediante descrittore che, fra le altre cose, permette una semplice identificazione dei valori NULL (il meccanismo dei descrittori è presente in tutte le versioni di IB, però solamente in Firebird sono stati risolti alcuni bug ed è stata specificata l'opportuna sintassi per le dichiarazioni). La dichiarazione completa della struttura PARAMDSC si trova nel file include/ibase.h (solo Firebird).

Esempio

Questa funzione ritorna il valore assoluto o -1 in caso di argomento NULL.
      const
        DSC_null = 1;

      type
        ParamDsc = record
          dsc_dtype    : Byte;
          dsc_scale    : ShortInt;
          dsc_length   : Word;
          dsc_sub_type : SmallInt;
          dsc_flags    : Word;
          dsc_address  : Pointer;
        end;
        PParamDsc = ^ParamDsc;

        PInteger  = ^Integer;

        function TEST_Abs_Dsc (val :PParamDsc): integer; cdecl;
        begin
          Result := -1;
          if (val=nil) or ((val^.dsc_flags and DSC_null) <> 0) then Exit;

          // Questo è solo un esempio. Una funzione UDF reale dovrebbe controllare dsc_dtype.
          Result := Abs( PInteger(val.dsc_address)^ );
        end;


        DECLARE EXTERNAL FUNCTION TEST_ABS_DSC
          INTEGER BY DESCRIPTOR
          RETURNS INTEGER BY VALUE
          ENTRY_POINT 'TEST_Abs_Dsc' MODULE_NAME 'UDF_Examamples';


        SELECT I, TEST_Abs_DSC(I) FROM TAB;

               I TEST_ABS_DSC
        ======== ============
               5            5
              -5            5
               0            0
          <null>           -1
    
Mappa del sitoOpportunitàCollegamentiRiservatezzaNote legaliAccessibilitàW3C
Tutti i marchi ed i copyright in questa pagina sono di proprietà dei rispettivi proprietari. Per il resto © EOS (Pisa). Ultimo aggiornamento: 2009-9-26.