Kueri yang lebih baik
Sebagai permulaan, Anda dapat memperbaiki sintaks, menyederhanakan dan mengklarifikasi sedikit:
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;
-
Membangun tata letak tabel saya yang diperbarui. Lihat biola di bawah.
-
Anda tidak memerlukan subquery tambahan. Fungsi jendela dijalankan setelah fungsi agregat, sehingga Anda dapat menyusunnya seperti yang ditunjukkan.
-
Saat berbicara tentang "peringkat", Anda mungkin ingin menggunakan
rank()
, bukanrow_number()
. -
Dengan asumsi
people.people_id
adalah PK, Anda dapat menyederhanakanGROUP BY
. -
Pastikan untuk membuat tabel memenuhi syarat semua nama kolom yang mungkin ambigu
Fungsi PL/pgSQL
Kemudian saya akan menulis fungsi plpgsql yang mengambil parameter untuk bagian variabel Anda. Menerapkan a
- c
poin Anda. d
tidak jelas, biarkan Anda menambahkannya.
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;
Telepon:
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
-
Anda membutuhkan pemahaman yang kuat tentang PL/pgSQL. Lain, ada terlalu banyak untuk dijelaskan. Anda akan menemukan jawaban terkait di sini di SO di bawah plpgsql untuk hampir setiap detail dalam jawabannya.
-
Semua parameter diperlakukan dengan aman, tidak ada injeksi SQL yang memungkinkan. Selengkapnya:
-
Perhatikan khususnya, bagaimana
WHERE
klausa ditambahkan secara kondisional (ketika_where_name
dilewatkan) dengan parameter posisi$1
dalam sengatan kueri. Nilai diteruskan keEXECUTE
sebagai nilai denganUSING
klausa . Tidak ada konversi tipe, tidak ada pelolosan, tidak ada kesempatan untuk injeksi SQL. Contoh: -
Gunakan
DEFAULT
nilai untuk parameter fungsi, jadi Anda bebas memberikan salah satu atau tidak sama sekali. Selengkapnya: -
Fungsi
format()
sangat penting untuk membangun string SQL dinamis yang kompleks dengan cara yang aman dan bersih.