Esto no parece sospechoso, pero es una gran pregunta .
Supuestos
- Sus recuentos son
integer
. - Todas las columnas en el libro de tablas están definidas
NOT NULL
. -
El
(name, sid, date)
compuesto es único en la tablabook
. Deberías tener unUNIQUE
restricción, preferiblemente (por rendimiento) con columnas en this orden:UNIQUE(sid, date, name)
Esto proporciona el índice necesario para el rendimiento de forma automática. (Si no, cree uno.) Consulte:
crosstab()
consultas
Para obtener el máximo rendimiento y cadenas de consulta cortas (especialmente si ejecuta esta consulta con frecuencia), sugiero el módulo adicional tablefunc
proporcionando varios crosstab()
funciones Instrucciones básicas:
Consultas básicas
Tienes que hacerlo bien primero.
Los últimos 10 días:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
Números de los últimos 10 días usando la función de ventana 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;
(Sin incluir fechas reales en esta consulta).
Nombres de columna para columnas de salida (para la solución 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;
Resultado simple con nombres de columna estáticos
Esto puede ser lo suficientemente bueno para usted, pero no vemos las fechas reales en el resultado:
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);
Para uso repetido, le sugiero que cree esta función C genérica (muy rápida) para 10 columnas enteras una vez, para simplificar un poco las cosas:
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';
Detalles en esta respuesta relacionada:
Entonces su llamada se convierte en:
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!
Solución completa con nombres de columna dinámicos
Su pregunta real es más complicada, también desea nombres de columna dinámicos.
Para una tabla dada, la consulta resultante podría verse así:
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 dificultad es destilar nombres de columnas dinámicas. Ensamble la cadena de consulta a mano o (más bien) deje que esta función lo haga por usted:
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$;
Llamar:
SELECT f_generate_date10_sql(1);
Esto genera la consulta deseada , que ejecuta a su vez.
db<>fiddle aquí