Archivi tag: Italian postgresql planet

Articoli che vengono ripubblicati nel planet di postgresql in italiano

Using functions that return result set in SELECT part of an SQL query in PostgreSQL

[Pagina in italiano]
Lately I had to write a query that transform each record of a table into a serie of records. Specifically, from a table with two colums (first is a key, second is a list of car plates) I had to extract a result set with two colums: the key and a single plate. For all record that had more than one plate, I was asked to duplicate the record in order to have a single car plate as second column.

In other words:

postgres=# create temporary table t (key varchar primary key, plates varchar);
postgres=# insert into t values ('000000','AA888BB CC777DD GG333JJ'), ('111111','ZZ888KK');
INSERT 0 2
postgres=# select * from t;
  key   |         plates
--------+-------------------------
 000000 | AA888BB CC777DD GG333JJ
 111111 | ZZ888KK

what I wanted was:

  key   |         plate
--------+-------------------------
 000000 | AA888BB
 000000 | CC777DD
 000000 | GG333JJ
 111111 | ZZ888KK

The solution I found is:

postgres=# select key,
    unnest(regexp_split_to_array(plates, E'\\s+')) AS plate from t;
  key   |  plate
--------+---------
 000000 | AA888BB
 000000 | CC777DD
 000000 | GG333JJ
 111111 | ZZ888KK

What does this query? First, it convert a list (plates) into an array using a space separator, and second, it convert the arrary into a relation of possibly many records.

But, I didn’t like this solution, and moreover, I did not even undertand it. So, a few questions arose:

  1. how is it possible that postgresql allow me to specify a function tha return a set in the SELECT part instead of the FROM part? When I studied SQL, I learned that FROM is for specifying all my data sources (relations), and SELECT for specifying what to display and eventually how to format them.
  2. how does postgresql choose creating a cartesian product multiplying a first element (a single value “key”) and a second one (a relation “plate”)?
  3. how postgresql define this second relation that is not a fixed one since it depends on a filed taken from the current record? I.e., for each “key” there is a specific relation “plate”. Furthermore, if this is really a cartesia product, addin a new unnest would create 4×3 records. Let’s try:
    postgres=# select key,
        unnest(regexp_split_to_array(plates, E'\\s+')) AS plate1,
        unnest(regexp_split_to_array(plates, E'\\s+')) AS plate2 from t;
      key   | plate1  | plate2
    --------+---------+---------
     000000 | AA888BB | AA888BB
     000000 | CC777DD | CC777DD
     000000 | GG333JJ | GG333JJ
     111111 | ZZ888KK | ZZ888KK
  4. why this has not lead to a new cartesian product? May this be related to IMMUTABLE functions like, probably, the unnest one? (I think to remember that IMMUTABLE functions are functions that do not chage result when you call it using the same arguments. In this case postgresql would avoid to call them many time and directly use the result. But why it does not do a new product?) Let’s test is differently, with another array:
    postgres=# select key,
        unnest(regexp_split_to_array(plates, E'\\s+')) AS plate1,
        unnest('{1,2}'::int[]) AS array2 from t;
      key   | plate1  | array2
    --------+---------+--------
     000000 | AA888BB |      1
     000000 | CC777DD |      2
     000000 | GG333JJ |      1
     000000 | AA888BB |      2
     000000 | CC777DD |      1
     000000 | GG333JJ |      2
     111111 | ZZ888KK |      1
     111111 | ZZ888KK |      2

    here, a cartesian product is made.

So, without much understanding of that is going on, I contacted a couple of mailing lists until I got an answer by Tom Lane (here).

The answer contains a few points:

  1. the best way to wite this query is to use the LATERAL subqueries, moving the function that create the arry from the SELECT to the FROM part, and to replace the two functions unnest+regexp_split_to_array with a single function regexp_split_to_table:
    select key, targa
        from t,
        lateral regexp_split_to_table(plates, E'\\s+') as plate;
  2. utilizing functions that return a result set in SELECT part, is a hangover from Berkeley QUEL. This part of the code is not something Lane is happy. Moreover he notes that with postgresql 10, the code that manage these functions that returns more rows has been rewritten in order to better isolate it from other code (see the link in his email)
  3. the cartesian product is not made for every function called. The final number of records is base on the least common multiple of all periods of the functions results. This explain why using two function that produce 3 record, at the end produce a 3 records results, while using a function that produce 3 records and a function that produce 2 record, will end in a 6 records results.

Usare delle funzioni che restituiscono result set nella parte SELECT di costrutti SQL in PostgreSQL

[English page]
Di recente ho dovuto scrivere una query che trasformasse ogni record di una tabella, in una serie di record. In particolare, da una tabella che ha due colonne (la prima è la chiave, la seconda è un elenco di targhe) ho dovuto estrarre un result set che avesse due colonne: la chiave e una sola targa. Per quei record che hanno più targhe, si doveva arrivare a duplicare il record in modo da avere la coppia chiave/targa per ciascuna delle targhe nell’elenco relativo.

Altrimenti detto:

postgres=# create temporary table t (chiave varchar primary key, targhe varchar);
postgres=# insert into t values ('000000','AA888BB CC777DD GG333JJ'), ('111111','ZZ888KK');
INSERT 0 2
postgres=# select * from t;
 chiave |         targhe
--------+-------------------------
 000000 | AA888BB CC777DD GG333JJ
 111111 | ZZ888KK

quello che volevo era:

 chiave |         targa
--------+-------------------------
 000000 | AA888BB
 000000 | CC777DD
 000000 | GG333JJ
 111111 | ZZ888KK

la soluzione che avevo trovato era la seguente:

postgres=# select chiave,
    unnest(regexp_split_to_array(targhe, E'\\s+')) AS targa from t;
 chiave |  targa
--------+---------
 000000 | AA888BB
 000000 | CC777DD
 000000 | GG333JJ
 111111 | ZZ888KK

Cosa fa la query che ho scritto? Primo, converte in array la colonna targhe usando il separatore spazio, secondo converte l’array in una relazione di vari record.

Ma questa soluzione non mi piaceva, e neppure la capivo granché. Mi sono quindi fatto alcune domande:

  1. come mai posso mettere nella parte SELECT (e non nella FROM) una relazione? Difatti quando studi l’SQL impari che le relazioni (tabelle, viste, eccetera) vanno nella parte FROM che si usa proprio per dire da dove prendere i dati, mentre nella parte SELECT inserisci cosa visualizzare.
  2. come decide, postgresql, di fare un prodotto cartesiano tra il primo elemento (singolo campo “chiave”) e il secondo (relazione “targa”)?
  3. come fa postgresql a definire questa seconda relazione che non è costante, ma dipende dal primo campo del record corrente? Vale a dire che per ogni “chiave” c’è una relazione “targa” diversa. E poi, se veramente viene fatto il prodotto cartesiano, dovrei poter aggiungere un secondo unnest e produrre 4×3 record. Proviamo:
    postgres=# select chiave,
    unnest(regexp_split_to_array(targhe, E'\\s+')) AS targa1,
    unnest(regexp_split_to_array(targhe, E'\\s+')) AS targa2 from t;
     chiave | targa1  | targa2
    --------+---------+---------
     000000 | AA888BB | AA888BB
     000000 | CC777DD | CC777DD
     000000 | GG333JJ | GG333JJ
     111111 | ZZ888KK | ZZ888KK
  4. come mai non ha fatto un ulteriore prodotto? Può avere a che fare con il concetto di funzioni IMMUTABLE alla quale forse unnest appartiene? (Mi pare di ricordare che si chiamino IMMUTABLE le funzioni che a fronte dello stesso input, danno lo stesso output. In questo caso postgresql potrebbe non richiamare nuovamente la unnest perché tanto conosce già il risultato. Ma perché non fa un ulteriore prodotto?)controprova, faccio l’unnest con un altro array:
    postgres=# select chiave,
        unnest(regexp_split_to_array(targhe, E'\\s+')) AS targa1,
        unnest('{1,2}'::int[]) AS array2 from t;
     chiave | targa1  | array2
    --------+---------+--------
     000000 | AA888BB |      1
     000000 | CC777DD |      2
     000000 | GG333JJ |      1
     000000 | AA888BB |      2
     000000 | CC777DD |      1
     000000 | GG333JJ |      2
     111111 | ZZ888KK |      1
     111111 | ZZ888KK |      2

    qui il prodotto l’ha fatto.

Con tanta confusione in testa, ho contattato alcune mailing list fino ad ottenere la risposta chiarificatrice di Tom Lane (qui).

La risposta prevede vari punti:

  1. la query migliore da fare prevede l’utilizzo del LATERAL e lo spostamento della funzione che restituisce l’array dalla parte SELECT alla parte FROM, nonché la sostituzione delle due chiamate unnest+regexp_split_to_array con una sola regexp_split_to_table:
    select chiave, targa
        from t,
            lateral regexp_split_to_table(targhe, E'\\s+') as targa;
  2. il fatto che si possano mettere delle funzioni che restituiscono più di un valore nella parte SELECT è un dovuto alla derivazione di postgresql dal Berkeley QUEL. Di questa parte di codice Tom non è particolarmente contento. Tra l’altro fa notare che in postgresql 10, il codice per la gestione di queste funzioni che restituiscono più valori, è stato notevolmente cambiato per centralizzarlo e isolarlo dal resto (vedi link nel suo email)
  3. il prodotto cartesiano non viene fatto per ciascuna delle funzioni di questo tipo chiamate. Il numero finale di record viene in realtà prodotto in base alla ricerca di un minimo comune multiplo della cardinalità dei result set delle varie funzioni. Questo spiega perché se ho due funzioni (nel mio caso, uguali) che restituiscono entrambe tre record, il mcm è 3, se ne ho una da 3 una da 2, il mcm è 6.

XML/SQL e PostgreSQL: come recuperare due tag in parallelo da un dato XML in una sola SELECT

Tempo fa mi fu chiesto di lavorare ad una procedura interna a DB2 che si occupava di importare alcuni dati: un sistema esterno generava un testo XML con parecchi record da inserire, metteva tutto il testo tramite connessione ODBC in un campo XML di una tabella «di frontiera» e invocava questa procedura che doveva prendere quei dati e inserirli in varie tabelle. Il punto sul quale il DBA locale si era bloccato era che riusciva a fare query XML che  reperivano un singolo campo, ma non ci riusciva quando vi erano più campi da prendere allo stesso tempo (per inserirli nello stesso record).

Anche PostgreSQL è in grado di gestire un campo XML e di estrarre delle parti di XML da quei campi. Questa estensione del linguaggio segue uno standard chiamato SQL/XML. Vediamo come si può affrontare questo problema con PostgreSQL.

Continua a leggere

Autenticazione postgresql tramite PAM per winbind e shadow

A volte si vuole far sì che gli utenti del database postgresql siano autenticati su un sistema esterno al database stesso. Per questo postgresql permette di verificare le credenziali tramite LDAP o kerberos o altro ancora, ma quando non c’è un metodo direttamente implementato in postgresql è possibile utilizzare PAM, che ha svariati connettori. In questo esempio prendiamo in considerazione l’autenticazione sul sistema operativo (utenti in /etc/passwd e password in /etc/shadow) e su dominio Windows (tramite winbind).

Nel seguito verrà assunto che l’autenticazione tramite PAM sia già configurata e funzionante. I comandi sono riferiti ad un sistema Debian Jessie.
Continua a leggere