sql >> Databasteknik >  >> RDS >> PostgreSQL

Testa för noll i funktion med varierande parametrar

Jag håller inte med om några av råden i andra svar. Detta kan göras med PL/pgSQL och jag tror att det mestadels är vida överlägset att sammanställa frågor i en klientapplikation. Det är snabbare och renare och appen skickar bara det minsta över tråden i förfrågningar. SQL-satser sparas inuti databasen, vilket gör det lättare att underhålla - om du inte vill samla all affärslogik i klientapplikationen beror detta på den allmänna arkitekturen.

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:

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

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

Eftersom alla funktionsparametrar har standardvärden kan du använda positionell notation, namngiven notation eller blandad notering som du väljer i funktionsanropet. Se:

  • Funktioner med variabelt antal indataparametrar

Mer förklaring till grunderna för dynamisk SQL:

  • Refaktorera en PL/pgSQL-funktion för att returnera utdata från olika SELECT-frågor

concat() funktion är instrumentell för att bygga strängen. Det introducerades med Postgres 9.1.

ELSE gren av en CASE standardsatsen är NULL när den inte är närvarande. Förenklar koden.

USING sats för EXECUTE gör SQL-injektion omöjlig eftersom värden skickas som värden och gör det möjligt att använda parametervärden direkt, precis som i förberedda satser.

NULL värden används för att ignorera parametrar här. De används faktiskt inte för att söka.

Du behöver inte ha parenteser runt SELECT med RETURN QUERY .

Enkel SQL-funktion

Du kunde gör det med en vanlig SQL-funktion och undvik dynamisk SQL. I vissa fall kan detta vara snabbare. Men jag skulle inte förvänta mig det i det här fallet . Att planera frågan utan onödiga kopplingar och predikat ger vanligtvis bästa resultat. Planeringskostnaden för en enkel fråga som denna är nästan försumbar.

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

Identiskt samtal.

För att effektivt ignorera parametrar med NULL värden :

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

Att faktiskt använda NULL-värden som parametrar , använd den här konstruktionen istället:

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

Detta tillåter även index som ska användas.
För det aktuella fallet, ersätt alla instanser av LEFT JOIN med JOIN .

db<>spela här - med enkel demo för alla varianter.
Gammal sqlfiddle

Asides

  • Använd inte name och id som kolumnnamn. De är inte beskrivande och när du går med i ett gäng tabeller (som du gör för a lot i en relationsdatabas) får du flera kolumner som alla heter name eller id , och måste bifoga alias för att sortera röran.

  • Vänligen formatera din SQL korrekt, åtminstone när du ställer allmänna frågor. Men gör det privat också, för ditt eget bästa.



  1. Hur hittar man beroenden i ett orakelpaket?

  2. Formatera nummer med 2 decimaler

  3. Hur kan jag läsa data från en krypterad DB med SQLiteAssetHelper?

  4. 3 metoder för att bygga om alla index för alla tabeller med T-SQL i SQL Server Database