PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak uniknąć wielu ewaluacji funkcji za pomocą składni (func()).* w zapytaniu SQL?

Możesz umieścić to w podzapytaniu, ale nie gwarantuje to bezpieczeństwa bez OFFSET 0 włamać się. W 9.3 użyj LATERAL . Problem jest spowodowany tym, że parser skutecznie rozszerza makro * do listy kolumn.

Obejście

Gdzie:

SELECT (my_func(x)).* FROM some_table;

oceni my_func n razy dla n kolumny wynikowe z funkcji, to sformułowanie:

SELECT (mf).* FROM (
    SELECT my_func(x) AS mf FROM some_table
) sub;

generalnie nie będzie i zwykle nie dodaje dodatkowego skanowania w czasie wykonywania. Aby zagwarantować, że nie zostanie przeprowadzona wielokrotna ocena, możesz użyć OFFSET 0 zhakować lub nadużyć niepowodzenia PostgreSQL w zakresie optymalizacji ponad granicami CTE:

SELECT (mf).* FROM (
    SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;

lub:

WITH tmp(mf) AS (
    SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;

W PostgreSQL 9.3 możesz użyć LATERAL aby uzyskać rozsądniejsze zachowanie:

SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;

LEFT JOIN LATERAL ... ON true zachowuje wszystkie wiersze, tak jak oryginalne zapytanie, nawet jeśli wywołanie funkcji nie zwraca żadnego wiersza.

Demo

Utwórz funkcję, która nie jest inlineable jako demonstracja:

CREATE OR REPLACE FUNCTION my_func(integer)
RETURNS TABLE(a integer, b integer, c integer) AS $$
BEGIN
    RAISE NOTICE 'my_func(%)',$1;
    RETURN QUERY SELECT $1, $1, $1;
END;
$$ LANGUAGE plpgsql;

oraz tabelę fikcyjnych danych:

CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;

następnie wypróbuj powyższe wersje. Zobaczysz, że pierwsze powoduje trzy powiadomienia na wywołanie; ci ostatni podbijają tylko jeden.

Dlaczego?

Dobre pytanie. To okropne.

Wygląda tak:

(func(x)).*

jest rozwinięty jako:

(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l

w parsowaniu, zgodnie ze spojrzeniem na debug_print_parse , debug_print_rewritten i debug_print_plan . (Przycięte) drzewo analizy wygląda tak:

   :targetList (
      {TARGETENTRY 
      :expr 
         {FIELDSELECT 
         :arg 
            {FUNCEXPR 
            :funcid 57168 
                 ...
            }
         :fieldnum 1 
         :resulttype 23 
         :resulttypmod -1 
         :resultcollid 0
         }
      :resno 1 
      :resname i 
       ...
      }
      {TARGETENTRY 
      :expr 
         {FIELDSELECT 
         :arg 
            {FUNCEXPR 
            :funcid 57168 
                 ...
            }
         :fieldnum 2 
         :resulttype 20 
         :resulttypmod -1 
         :resultcollid 0
         }
      :resno 2 
      :resname j 
       ...
      }
      {TARGETENTRY 
      :expr 
         {FIELDSELECT 
         :arg 
            {FUNCEXPR 
            :funcid 57168 
             ...
            }
         :fieldnum 3 
         :...
         }
      :resno 3 
      :resname k 
       ...
      }
      {TARGETENTRY 
      :expr 
         {FIELDSELECT 
         :arg 
            {FUNCEXPR 
            :funcid 57168 
             ...
            }
         :fieldnum 4 
          ...
         }
      :resno 4 
      :resname l 
       ...
      }
   )

Więc w zasadzie używamy głupiego hacka parsera, aby rozszerzyć symbole wieloznaczne przez klonowanie węzłów.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Używanie aktualnego czasu w UTC jako wartości domyślnej w PostgreSQL

  2. Analiza porównawcza zarządzanych rozwiązań chmurowych PostgreSQL — część pierwsza:Amazon Aurora

  3. Wystąpił błąd podczas instalacji pg (0.17.1), a Bundler nie może kontynuować

  4. ECONNREFUSED dla Postgres na nodeJS z dokerami

  5. Opcjonalna instrukcja INSERT w łańcuchu transakcji przy użyciu NodeJS i Postgres