sql >> Database >  >> RDS >> PostgreSQL

Test voor null in functie met verschillende parameters

Ik ben het niet eens met sommige adviezen in andere antwoorden. Dit kan met PL/pgSQL en ik denk dat het meestal veel beter is voor het samenstellen van query's in een clienttoepassing. Het is sneller en schoner en de app stuurt alleen het absolute minimum over de draad in verzoeken. SQL-instructies worden opgeslagen in de database, wat het onderhoud gemakkelijker maakt - tenzij u alle bedrijfslogica in de clienttoepassing wilt verzamelen, hangt dit af van de algemene architectuur.

PL/pgSQL-functie met dynamische SQL

CREATE OR REPLACE FUNCTION func(
      _ad_nr       int  = NULL
    , _ad_nr_extra text = NULL
    , _ad_info     text = NULL
    , _ad_postcode text = NULL
    , _sname       text = NULL
    , _pname       text = NULL
    , _cname       text = NULL)
  RETURNS TABLE(id int, match text, score int, nr int, nr_extra text
              , info text, postcode text, street text, place text
              , country text, the_geom geometry)
  LANGUAGE plpgsql AS
$func$
BEGIN
   -- RAISE NOTICE '%', -- for debugging
   RETURN QUERY EXECUTE concat(
   $$SELECT a.id, 'address'::text, 1 AS score, a.ad_nr, a.ad_nr_extra
        , a.ad_info, a.ad_postcode$$

   , CASE WHEN (_sname, _pname, _cname) IS NULL THEN ', NULL::text' ELSE ', s.name' END  -- street
   , CASE WHEN (_pname, _cname) IS NULL         THEN ', NULL::text' ELSE ', p.name' END  -- place
   , CASE WHEN _cname IS NULL                   THEN ', NULL::text' ELSE ', c.name' END  -- country
   , ', a.wkb_geometry'

   , concat_ws('
   JOIN   '
   , '
   FROM   "Addresses" a'
   , CASE WHEN NOT (_sname, _pname, _cname) IS NULL THEN '"Streets"   s ON s.id = a.street_id' END
   , CASE WHEN NOT (_pname, _cname) IS NULL         THEN '"Places"    p ON p.id = s.place_id' END
   , CASE WHEN _cname IS NOT NULL                   THEN '"Countries" c ON c.id = p.country_id' END
   )

   , concat_ws('
   AND    '
      , '
   WHERE  TRUE'
      , CASE WHEN $1 IS NOT NULL THEN 'a.ad_nr = $1' END
      , CASE WHEN $2 IS NOT NULL THEN 'a.ad_nr_extra = $2' END
      , CASE WHEN $3 IS NOT NULL THEN 'a.ad_info = $3' END
      , CASE WHEN $4 IS NOT NULL THEN 'a.ad_postcode = $4' END
      , CASE WHEN $5 IS NOT NULL THEN 's.name = $5' END
      , CASE WHEN $6 IS NOT NULL THEN 'p.name = $6' END
      , CASE WHEN $7 IS NOT NULL THEN 'c.name = $7' END
   )
   )
   USING $1, $2, $3, $4, $5, $6, $7;
END
$func$;

Bel:

SELECT * FROM func(1, '_ad_nr_extra', '_ad_info', '_ad_postcode', '_sname');

SELECT * FROM func(1, _pname := 'foo');

Aangezien alle functieparameters standaardwaarden hebben, kunt u positional . gebruiken notatie, genoemd notatie of gemengd notatie naar keuze in de functieaanroep. Zie:

  • Functies met variabel aantal invoerparameters

Meer uitleg voor de basis van dynamische SQL:

  • Refactor een PL/pgSQL-functie om de uitvoer van verschillende SELECT-query's te retourneren

De concat() functie is instrumenteel voor het bouwen van de string. Het werd geïntroduceerd met Postgres 9.1.

De ELSE tak van een CASE instructie is standaard NULL wanneer niet aanwezig. Vereenvoudigt de code.

De USING clausule voor EXECUTE maakt SQL-injectie onmogelijk omdat waarden worden doorgegeven als waarden en maakt het mogelijk om parameterwaarden direct te gebruiken, precies zoals in voorbereide instructies.

NULL waarden worden hier gebruikt om parameters te negeren. Ze worden niet echt gebruikt om te zoeken.

U hebt geen haakjes nodig rond de SELECT met RETURN QUERY .

Eenvoudige SQL-functie

Je zou doe het met een gewone SQL-functie en vermijd dynamische SQL. In sommige gevallen kan dit sneller. Maar ik zou het niet verwachten in dit geval . Het plannen van de query zonder onnodige joins en predikaten levert doorgaans de beste resultaten op. De planningskosten voor een eenvoudige zoekopdracht als deze zijn bijna te verwaarlozen.

CREATE OR REPLACE FUNCTION func_sql(
     _ad_nr       int  = NULL
   , _ad_nr_extra text = NULL
   , _ad_info     text = NULL
   , _ad_postcode text = NULL
   , _sname       text = NULL
   , _pname       text = NULL
   , _cname       text = NULL)
  RETURNS TABLE(id int, match text, score int, nr int, nr_extra text
              , info text, postcode text, street text, place text
              , country text, the_geom geometry)
  LANGUAGE sql AS 
$func$
SELECT a.id, 'address' AS match, 1 AS score, a.ad_nr, a.ad_nr_extra
     , a.ad_info, a.ad_postcode
     , s.name AS street, p.name AS place
     , c.name AS country, a.wkb_geometry
FROM   "Addresses"      a
LEFT   JOIN "Streets"   s ON s.id = a.street_id
LEFT   JOIN "Places"    p ON p.id = s.place_id
LEFT   JOIN "Countries" c ON c.id = p.country_id
WHERE ($1 IS NULL OR a.ad_nr = $1)
AND   ($2 IS NULL OR a.ad_nr_extra = $2)
AND   ($3 IS NULL OR a.ad_info = $3)
AND   ($4 IS NULL OR a.ad_postcode = $4)
AND   ($5 IS NULL OR s.name = $5)
AND   ($6 IS NULL OR p.name = $6)
AND   ($7 IS NULL OR c.name = $7)
$func$;

Identieke oproep.

Om effectief parameters te negeren met NULL waarden :

($1 IS NULL OR a.ad_nr = $1)

Om daadwerkelijk NULL-waarden als parameters te gebruiken , gebruik in plaats daarvan deze constructie:

($1 IS NULL AND a.ad_nr IS NULL OR a.ad_nr = $1)  -- AND binds before OR

Dit zorgt ook voor indexen te gebruiken.
Vervang in dit geval alle instanties van LEFT JOIN met JOIN .

db<>viool hier - met eenvoudige demo voor alle varianten.
Oude sqlfiddle

Terzijdes

  • Gebruik geen name en id als kolomnamen. Ze zijn niet beschrijvend en wanneer u zich bij een aantal tabellen voegt (zoals u doet bij a lot in een relationele database), krijg je verschillende kolommen met de naam name of id , en moet aliassen toevoegen om de rommel te sorteren.

  • Formatteer uw SQL op de juiste manier, in ieder geval wanneer u openbare vragen stelt. Maar doe het ook privé, voor je eigen bestwil.



  1. Doe mee voor een Microsoft Access met SQL Server Academy-sessie

  2. PostgreSQL-fout:relatie bestaat al

  3. Snapshot controlfile-functie met RMAN en ORA-00245

  4. Beste databasesoftware voor ontwikkelaars (2022-editie)