Sembra insospettabile, ma è una domanda infernale .
Ipotesi
- I tuoi conteggi sono
integer
. - Tutte le colonne nel registro tabelle sono definite
NOT NULL
. -
Il composto
(name, sid, date)
è univoco nella tabellabook
. Dovresti avere unUNIQUE
vincolo, preferibilmente (per le prestazioni) con colonne in questo ordine:UNIQUE(sid, date, name)
Ciò fornisce automaticamente l'indice necessario per le prestazioni. (Altrimenti creane uno.) Vedi:
crosstab()
domande
Per ottenere le massime prestazioni e stringhe di query brevi (soprattutto se esegui spesso questa query) suggerisco il modulo aggiuntivo tablefunc
fornendo vari crosstab()
funzioni. Istruzioni di base:
Query di base
Devi prima metterli a posto.
Gli ultimi 10 giorni:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
Numeri degli ultimi 10 giorni utilizzando la funzione finestra 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;
(Non includendo le date effettive in questa query.)
Nomi delle colonne per le colonne di output (per la soluzione completa):
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;
Risultato semplice con nomi di colonne statici
Questo potrebbe essere abbastanza buono per te, ma non vediamo date effettive nel risultato:
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);
Per un uso ripetuto ti suggerisco di creare questa (molto veloce) funzione C generica per 10 colonne intere una volta, per semplificare un po' le cose:
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';
Dettagli in questa risposta correlata:
Quindi la tua chiamata diventa:
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!
Soluzione completa con nomi di colonne dinamici
La tua vera domanda è più complicata, vuoi anche nomi di colonne dinamici.
Per una determinata tabella, la query risultante potrebbe apparire così:
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");
La difficoltà consiste nel distillare nomi di colonne dinamici. O assembla la stringa di query a mano o (molto meglio) lascia che questa funzione lo faccia per te:
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$;
Chiama:
SELECT f_generate_date10_sql(1);
Questo genera la query desiderata , che esegui a turno.
db<>violino qui