sql >> Database teknologi >  >> RDS >> PostgreSQL

Test for nul i funktion med varierende parametre

Jeg er uenig i nogle af rådene i andre svar. Dette kan gøres med PL/pgSQL, og jeg synes, det for det meste er langt overlegent til at samle forespørgsler i en klientapplikation. Det er hurtigere og renere, og appen sender kun det absolutte minimum hen over ledningen i anmodninger. SQL-sætninger gemmes inde i databasen, hvilket gør det nemmere at vedligeholde - medmindre du ønsker at samle al forretningslogik i klientapplikationen, afhænger dette af den generelle arkitektur.

PL/pgSQL-funktion med dynamisk 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$;

Ring til:

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

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

Da alle funktionsparametre har standardværdier, kan du bruge positionelle notation, navngivet notation eller blandet notation efter eget valg i funktionskaldet. Se:

  • Funktioner med variabelt antal inputparametre

Mere forklaring på det grundlæggende i dynamisk SQL:

  • Refaktorer en PL/pgSQL-funktion for at returnere output fra forskellige SELECT-forespørgsler

concat() funktion er medvirkende til at bygge strengen. Det blev introduceret med Postgres 9.1.

ELSE gren af ​​en CASE sætningen er som standard NULL når de ikke er til stede. Forenkler koden.

USING klausul for EXECUTE gør SQL-injektion umulig, da værdier sendes som værdier og gør det muligt at bruge parameterværdier direkte, præcis som i forberedte udsagn.

NULL værdier bruges til at ignorere parametre her. De bruges faktisk ikke til at søge.

Du behøver ikke parenteser omkring SELECT med RETURN QUERY .

Simpel SQL-funktion

Du kunne gør det med en almindelig SQL-funktion og undgå dynamisk SQL. I nogle tilfælde kan dette være hurtigere. Men jeg ville ikke forvente det i dette tilfælde . Planlægning af forespørgslen uden unødvendige joinforbindelser og prædikater giver typisk de bedste resultater. Planlægningsomkostninger for en simpel forespørgsel som denne er næsten ubetydelig.

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$;

Identisk opkald.

For effektivt at ignorere parametre med NULL værdier :

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

For rent faktisk at bruge NULL-værdier som parametre , brug denne konstruktion i stedet:

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

Dette giver også mulighed for indekser skal bruges.
For det aktuelle tilfælde skal du erstatte alle forekomster af LEFT JOIN med JOIN .

db<>spil her - med simpel demo for alle varianter.
Gamle sqlfiddle

Udover

  • Brug ikke name og id som kolonnenavne. De er ikke beskrivende, og når du tilslutter dig en masse tabeller (som du gør for a lot i en relationsdatabase), ender du med flere kolonner, der alle hedder name eller id , og skal vedhæfte aliaser for at sortere rodet.

  • Formatér venligst din SQL korrekt, i det mindste når du stiller offentlige spørgsmål. Men gør det også privat, for dit eget bedste.



  1. Mysql Forbedre søgeydelsen med jokertegn (%%)

  2. Indkaldelse af papirer til PGDay.IT 2011 er blevet forlænget

  3. 6 måder at konvertere en streng til en dato/tidsværdi i SQL Server

  4. SQL unikt varchar spørgsmål om følsomhed over for store og små bogstaver