Bessere Abfrage
Für den Anfang können Sie die Syntax korrigieren, vereinfachen und einiges klarstellen:
SELECT *
FROM (
SELECT p.person_id, p.name, p.team, sum(s.score)::int AS score
,rank() OVER (PARTITION BY p.team
ORDER BY sum(s.score) DESC)::int AS rnk
FROM person p
JOIN score s USING (person_id)
GROUP BY 1
) sub
WHERE rnk < 3;
-
Aufbauend auf meinem aktualisierten Tabellenlayout. Siehe Geige unten.
-
Sie benötigen die zusätzliche Unterabfrage nicht. Fensterfunktionen werden nach ausgeführt Aggregatfunktionen, sodass Sie sie wie demonstriert verschachteln können.
-
Wenn Sie von „Rang“ sprechen, möchten Sie wahrscheinlich
rank()
verwenden , nichtrow_number()
. -
Angenommen
people.people_id
das PK ist, können SieGROUP BY
vereinfachen . -
Achten Sie darauf, alle Spaltennamen, die mehrdeutig sein könnten, durch Tabellen zu qualifizieren
PL/pgSQL-Funktion
Dann würde ich eine plpgsql-Funktion schreiben, die Parameter für Ihre variablen Teile übernimmt. Implementieren von a
- c
Ihrer Punkte. d
ist unklar, das müssen Sie hinzufügen.
CREATE OR REPLACE FUNCTION f_demo(_agg text DEFAULT 'sum'
, _left_join bool DEFAULT FALSE
, _where_name text DEFAULT NULL)
RETURNS TABLE(person_id int, name text, team text, score int, rnk int) AS
$func$
DECLARE
_agg_op CONSTANT text[] := '{count, sum, avg}'; -- allowed functions
_sql text;
BEGIN
-- assert --
IF _agg ILIKE ANY (_agg_op) THEN
-- all good
ELSE
RAISE EXCEPTION '_agg must be one of %', _agg_op;
END IF;
-- query --
_sql := format('
SELECT *
FROM (
SELECT p.person_id, p.name, p.team, %1$s(s.score)::int AS score
,rank() OVER (PARTITION BY p.team
ORDER BY %1$s(s.score) DESC)::int AS rnk
FROM person p
%2$s score s USING (person_id)
%3$s
GROUP BY 1
) sub
WHERE rnk < 3
ORDER BY team, rnk'
, _agg
, CASE WHEN _left_join THEN 'LEFT JOIN' ELSE 'JOIN' END
, CASE WHEN _where_name <> '' THEN 'WHERE p.name LIKE $1' ELSE '' END
);
-- debug -- quote when tested ok
-- RAISE NOTICE '%', _sql;
-- execute -- unquote when tested ok
RETURN QUERY EXECUTE _sql
USING _where_name; -- $1
END
$func$ LANGUAGE plpgsql;
Aufruf:
SELECT * FROM f_demo();
SELECT * FROM f_demo('sum', TRUE, '%2');
SELECT * FROM f_demo('avg', FALSE);
SELECT * FROM f_demo(_where_name := '%1_'); -- named param
-
Sie benötigen ein fundiertes Verständnis von PL/pgSQL. Ansonsten gibt es einfach zu viel zu erklären. Entsprechende Antworten finden Sie hier auf SO unter plpgsql für praktisch jedes Detail in der Antwort.
-
Alle Parameter werden sicher behandelt, keine SQL-Injection möglich. Mehr:
-
Beachten Sie insbesondere, wie ein
WHERE
-Klausel wird bedingt hinzugefügt (wenn_where_name
übergeben wird) mit dem Positionsparameter$1
im Abfrage-Sting. Der Wert wird anEXECUTE
übergeben als Wert mit dem Code <>VERWENDUNG Klausel . Keine Typkonvertierung, kein Escape, keine Chance für SQL-Injection. Beispiele: -
Verwenden Sie
DEFAULT
Werte für Funktionsparameter, Sie können also beliebige oder keine angeben. Mehr: -
Das Funktionsformat
()
ist entscheidend für den sicheren und sauberen Aufbau komplexer dynamischer SQL-Strings.