Vypadá to nepodezřele, ale je to pekelná otázka .
Předpoklady
- Vaše počty jsou
integer
. - Všechny sloupce v knize tabulky jsou definovány
NOT NULL
. -
Složený
(název, id, datum)
je jedinečný v tabulcekniha
. Měli byste mítUNIQUE
omezení, nejlépe (pro výkon) se sloupci v tomto objednávka:UNIQUE(sid, date, name)
To poskytuje index potřebný pro výkon automaticky. (Jinak jej vytvořte.) Viz:
crosstab()
dotazy
Chcete-li získat špičkový výkon a krátké řetězce dotazů (zejména pokud tento dotaz spouštíte často), navrhuji další modul tablefunc
poskytování různých crosstab()
funkcí. Základní pokyny:
Základní dotazy
Nejprve je musíte uvést do pořádku.
Posledních 10 dní:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
Čísla za posledních 10 dní pomocí funkce okna dense_rank()
:
SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = 1
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC;
(V tomto dotazu nejsou zahrnuta skutečná data.)
Názvy sloupců pro výstupní sloupce (pro úplné řešení):
SELECT 'bookname, "' || string_agg(to_char(date, 'DD/MM/YYYY'), '", "' ORDER BY date) || '"'
FROM (
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10
) sub;
Jednoduchý výsledek se statickými názvy sloupců
To vám může stačit – ale ve výsledku nevidíme skutečná data:
SELECT * FROM crosstab(
'SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = 1
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC'
, 'SELECT generate_series(10, 1, -1)'
) AS (bookname text
, date1 int, date2 int, date3 int, date4 int, date5 int
, date6 int, date7 int, date8 int, date9 int, date10 int);
Pro opakované použití doporučuji vytvořit tuto (velmi rychlou) obecnou C funkci pro 10 celých sloupců jednou, abyste věci trochu zjednodušili:
CREATE OR REPLACE FUNCTION crosstab_int10(text, text)
RETURNS TABLE (bookname text
, date1 int, date2 int, date3 int, date4 int, date5 int
, date6 int, date7 int, date8 int, date9 int, date10 int)
LANGUAGE C STABLE STRICT AS
'$libdir/tablefunc','crosstab_hash';
Podrobnosti v této související odpovědi:
Potom se váš hovor změní na:
SELECT * FROM crosstab(
'SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = 1
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC'
, 'SELECT generate_series(10, 1, -1)'
); -- no column definition list required!
Úplné řešení s dynamickými názvy sloupců
Vaše skutečná otázka je složitější, chcete také dynamické názvy sloupců.
Pro danou tabulku by výsledný dotaz mohl vypadat takto:
SELECT * FROM crosstab_int10(
'SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = 1
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC'
, 'SELECT generate_series(10, 1, -1)'
) AS t(bookname
, "04/11/2015", "05/11/2015", "06/11/2015", "07/11/2015", "08/11/2015"
, "09/11/2015", "10/11/2015", "11/11/2015", "15/11/2015", "17/11/2015");
Obtížnost spočívá v destilaci dynamických názvů sloupců. Buď sestavte řetězec dotazu ručně, nebo (mnohem spíše) nechte tuto funkci, aby to udělala za vás:
CREATE OR REPLACE FUNCTION f_generate_date10_sql(_sid int = 1)
RETURNS text
LANGUAGE sql AS
$func$
SELECT format(
$$SELECT * FROM crosstab_int10(
'SELECT *
FROM (
SELECT name
, dense_rank() OVER (ORDER BY date DESC) AS date_rnk
, count
FROM book
WHERE sid = %1$s
) sub
WHERE date_rnk < 11
ORDER BY name, date_rnk DESC'
, 'SELECT generate_series(10, 1, -1)'
) AS ct(bookname, "$$
|| string_agg(to_char(date, 'DD/MM/YYYY'), '", "' ORDER BY date) || '")'
, _sid)
FROM (
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10
) sub
$func$;
Volejte:
SELECT f_generate_date10_sql(1);
Tím se vygeneruje požadovaný dotaz , který provedete postupně.
db<>fiddle zde