it-swarm.com.de

"WHERE" -Parameter an PostgreSQL View übergeben?

Ich habe eine ziemlich komplizierte Abfrage in meiner PostgreSQL-Datenbank, die 4 Tabellen über eine Reihe verschachtelter Unterabfragen umfasst. Trotz des etwas kompliziert aussehenden Aussehens und Setups werden jedoch letztendlich zwei Spalten (aus derselben Tabelle, falls dies die Situation erleichtert) basierend auf dem Abgleich zweier externer Parameter zurückgegeben (zwei Zeichenfolgen müssen mit Feldern in verschiedenen Tabellen übereinstimmen). Das Datenbankdesign in PostgreSQL ist relativ neu. Ich weiß, dass dieses scheinbar magische Ding namens Views existiert. Das scheint mir hier zu helfen, aber vielleicht nicht.

Gibt es eine Möglichkeit, wie ich meine komplexe Abfrage in eine Ansicht verschieben und irgendwie die zwei Werte übergeben kann, die ich anpassen muss? Das würde meinen Code im Frontend stark vereinfachen (durch die Verlagerung der Komplexität in die Datenbankstruktur). Ich kann eine Ansicht erstellen, die meine statische Beispielabfrage umschließt und die einwandfrei funktioniert. Dies funktioniert jedoch nur für ein Paar von Zeichenfolgenwerten. Ich muss es in der Lage sein, es mit verschiedenen Werten zu verwenden.

Meine Frage ist also: Ist es möglich, Parameter in eine ansonsten statische Ansicht zu übergeben und "dynamisch" zu werden? Oder vielleicht ist eine Ansicht nicht der richtige Weg, um sich ihr zu nähern. Wenn es etwas anderes gibt, das besser funktionieren würde, bin ich ganz Ohr!

* Editieren: * Wie in Kommentaren angefordert, hier meine Abfrage, wie sie jetzt steht:

SELECT   param_label, param_graphics_label
  FROM   parameters
 WHERE   param_id IN 
         (SELECT param_id 
            FROM parameter_links
           WHERE region_id = 
                 (SELECT region_id
                    FROM regions
                   WHERE region_label = '%PARAMETER 1%' AND model_id =
                         (SELECT model_id FROM models WHERE model_label = '%PARAMETER 2%')
                 )
         ) AND active = 'TRUE'
ORDER BY param_graphics_label;

Parameter sind durch Prozentzeichen oben angegeben.

36
Devin

Sie können eine zurückkehrende Funktion verwenden:

create or replace function label_params(parm1 text, parm2 text)
  returns table (param_label text, param_graphics_label text)
as
$body$
  select ...
  WHERE region_label = $1 
     AND model_id = (SELECT model_id FROM models WHERE model_label = $2)
  ....
$body$
language sql;

Dann können Sie tun:

select *
from label_params('foo', 'bar')

Btw: Sind Sie sicher, dass Sie wollen: 

AND model_id = (SELECT model_id FROM models WHERE model_label = $2)

wenn model_label nicht eindeutig ist (oder der Primärschlüssel), wird eventuell ein Fehler ausgegeben. Sie wollen wahrscheinlich:

AND model_id IN (SELECT model_id FROM models WHERE model_label = $2)
47

Zusätzlich zu dem, was @a_horse bereits gelöscht hat, können Sie Ihre SQL vereinfachen, indem Sie JOIN-Syntax anstelle von verschachtelten Unterabfragen verwenden. Die Leistung ist ähnlich, aber die Syntax ist viel kürzer und einfacher zu verwalten.

CREATE OR REPLACE FUNCTION param_labels(_region_label text, _model_label text)
  RETURNS TABLE (param_label text, param_graphics_label text) AS
$func$
    SELECT p.param_label, p.param_graphics_label
    FROM   parameters      p 
    JOIN   parameter_links l USING (param_id)
    JOIN   regions         r USING (region_id)
    JOIN   models          m USING (model_id)
    WHERE  p.active
    AND    r.region_label = $1 
    AND    m.model_label = $2
    ORDER  BY p.param_graphics_label;
$func$ LANGUAGE sql;
  • Wenn model_label nicht eindeutig ist oder etwas anderes in der Abfrage zu doppelten Zeilen führt, möchten Sie möglicherweise SELECT DISTINCT p.param_graphics_label, p.param_label - mit einer passenden ORDER BY-Klausel für eine optimale Leistung erstellen. Oder verwenden Sie eine GROUP BY-Klausel.

  • Seit Postgres 9.2 können Sie die deklarierten Parameternamen anstelle von $1 und $2 in SQL-Funktionen verwenden. (Ist seit langem für PL/pgSQL-Funktionen möglich).

  • Es muss darauf geachtet werden, Namenskonflikte zu vermeiden. Aus diesem Grund mache ich es mir zur Gewohnheit, Parameternamen in der Deklaration (die überall in der Funktion sichtbar sind) und Tabellennamen der Spalten im Rumpf voranzustellen.

  • Ich habe WHERE p.active = 'TRUE' zu WHERE p.active vereinfacht, da die Spalte active höchstwahrscheinlich vom Typ boolean sein sollte, nicht text.

  • USING funktioniert nur, wenn die Spaltennamen in allen Tabellen links vom JOIN eindeutig sind. Ansonsten müssen Sie die explizite Syntax verwenden:
    ON l.param_id = p.param_id

25

In den meisten Fällen ist die Set-Returning-Funktion der richtige Weg, aber für den Fall, dass Sie das Set lesen und schreiben möchten, ist eine Ansicht möglicherweise geeigneter. Und es ist is möglich, dass eine Ansicht einen Sitzungsparameter lesen kann:

CREATE VIEW widget_sb AS SELECT * FROM widget WHERE column = cast(current_setting('mydomain.myparam') as int)

SET mydomain.myparam = 0
select * from widget_sb
[results]

SET mydomain.myparam = 1
select * from widget_sb
[distinct results]
20
Drew

Ich glaube nicht, dass eine "dynamische" Ansicht, wie Sie gesagt haben, möglich ist.

Warum nicht eine gespeicherte Prozedur schreiben, die stattdessen 2 Argumente akzeptiert?

2
Jin Kim

Ich würde die Abfrage wie folgt umformulieren:

SELECT   p.param_label, p.param_graphics_label
  FROM   parameters p
where exists (
    select 1
    from parameter_links pl
    where pl.parameter_id = p.id
    and exists (select 1 from regions r where r.region_id = pl.region_id
) and p.active = 'TRUE'
order by p.param_graphics_label;

Vorausgesetzt, Sie verfügen über Indizes für die verschiedenen id-Spalten, sollte diese Abfrage wesentlich schneller sein als die Verwendung des IN-Operators. Die vorhandenen Parameter verwenden hier nur die Indexwerte, ohne die Datentabelle zu berühren, außer die endgültigen Daten aus der Parametertabelle abzurufen.

0
Monty