sql >> Databáze >  >> RDS >> PostgreSQL

Jak se vyhnout vícenásobným hodnotám funkcí se syntaxí (func()).* v dotazu SQL?

Můžete to zabalit do dílčího dotazu, ale to není zaručeno bezpečné bez OFFSET 0 zaseknout. Ve verzi 9.3 použijte LATERAL . Problém je způsoben tím, že analyzátor efektivně makro rozbalí * do seznamu sloupců.

Řešení

Kde:

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

vyhodnotí my_func n časy pro n výsledné sloupce z funkce, tato formulace:

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

obecně nebude a má tendenci nepřidávat další skenování za běhu. Chcete-li zaručit, že nebude provedeno vícenásobné vyhodnocení, můžete použít OFFSET 0 hacknout nebo zneužít selhání PostgreSQL při optimalizaci přes hranice CTE:

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

nebo:

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

V PostgreSQL 9.3 můžete použít LATERAL abyste se chovali rozumněji:

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

LEFT JOIN LATERAL ... ON true zachová všechny řádky jako původní dotaz, i když volání funkce nevrátí žádný řádek.

Ukázka

Vytvořte funkci, kterou nelze vložit jako ukázku:

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;

a tabulka fiktivních dat:

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

pak vyzkoušejte výše uvedené verze. Uvidíte, že první vyvolá tři upozornění na vyvolání; posledně jmenovaný zvýší pouze jeden.

Proč?

Dobrá otázka. Je to hrozné.

Vypadá to takto:

(func(x)).*

je rozšířen jako:

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

při analýze, podle pohledu na debug_print_parse , debug_print_rewritten a debug_print_plan . (oříznutý) strom analýzy vypadá takto:

   :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 
       ...
      }
   )

V zásadě tedy používáme hloupý analyzátor k rozšíření zástupných znaků klonováním uzlů.




  1. Funkce NEXT_DAY() v Oracle

  2. Nelze vytvořit datový model entity - pomocí MySql a EF6

  3. Jak předat parametr dotazu mssql v uzlu js

  4. MySQL – ORDER BY hodnot v IN()