Dette ser utænkeligt ud, men det er et surveligt spørgsmål .
Forudsætninger
- Dine tal er
heltal
. - Alle kolonner i tabelbogen er defineret
IKKE NULL
. -
Den sammensatte
(navn, side, dato)
er unik i tabelbog
. Du skal have enUNIQUE
begrænsning, helst (for ydeevne) med kolonner i dette ordre:UNIQUE(sid, date, name)
Dette giver automatisk det nødvendige indeks for ydeevne. (Opret ellers en.) Se:
crosstab()
forespørgsler
For at få den bedste ydeevne og korte forespørgselsstrenge (især hvis du kører denne forespørgsel ofte) foreslår jeg det ekstra modul tablefunc
leverer forskellige crosstab()
funktioner. Grundlæggende instruktioner:
Grundlæggende forespørgsler
Du er nødt til at få disse rigtige først.
De sidste 10 dage:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
Tal for de sidste 10 dage ved hjælp af vinduesfunktionen 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;
(Inkluderer ikke faktiske datoer i denne forespørgsel.)
Kolonnenavne for outputkolonner (for fuld løsning):
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;
Simpelt resultat med statiske kolonnenavne
Dette kan være godt nok for dig - men vi kan ikke se faktiske datoer i resultatet:
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);
Til gentagen brug foreslår jeg, at du opretter denne (meget hurtige) generiske C-funktion for 10 heltalskolonner én gang, for at forenkle tingene en smule:
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';
Detaljer i dette relaterede svar:
Så bliver dit opkald:
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!
Fuld løsning med dynamiske kolonnenavne
Dit faktiske spørgsmål er mere kompliceret, du vil også have dynamiske kolonnenavne.
For en given tabel kunne den resulterende forespørgsel se således ud:
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");
Vanskeligheden er at destillere dynamiske kolonnenavne. Saml enten forespørgselsstrengen i hånden, eller lad (meget snarere) denne funktion gøre det for dig:
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$;
Ring til:
SELECT f_generate_date10_sql(1);
Dette genererer den ønskede forespørgsel , som du udfører efter tur.
db<>fiddle her