Isso parece insuspeito, mas é uma pergunta infernal .
Suposições
- Suas contagens são
inteiro
. - Todas as colunas no livro de tabelas são definidas
NOT NULL
. -
O composto(name, sid, date)
é único na tabelabook
. Você deve ter umÚNICO
restrição, de preferência (para desempenho) com colunas neste ordem:
UNIQUE(sid, date, name)
Isso fornece o índice necessário para o desempenho automaticamente. (Senão, crie um.) Veja:
crosstab()
consultas
Para obter desempenho superior e strings de consulta curtas (especialmente se você executar essa consulta com frequência), sugiro o módulo adicional
tablefunc
fornecendo vários crosstab()
funções. Instruções básicas: Consultas básicas
Você precisa acertar isso primeiro.
Os últimos 10 dias:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
Números dos últimos 10 dias usando a função de janela
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;
(Não incluindo datas reais nesta consulta.)
Nomes de coluna para colunas de saída (para solução 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 simples com nomes de colunas estáticos
Isso pode ser bom o suficiente para você - mas não vemos datas reais no 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, sugiro que você crie esta função C genérica (muito rápida) para 10 colunas inteiras uma vez, para simplificar um pouco as coisas:
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';
Detalhes nesta resposta relacionada:
Então sua chamada se torna:
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!
Solução completa com nomes de colunas dinâmicos
Sua pergunta real é mais complicada, você também deseja nomes de coluna dinâmicos.
Para uma determinada tabela, a consulta resultante pode ser assim:
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");
A dificuldade é destilar nomes de colunas dinâmicas. Monte a string de consulta manualmente ou (muito melhor) deixe esta função fazer isso por você:
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$;
Ligar:
SELECT f_generate_date10_sql(1);
Isso gera a consulta desejada , que você executa por sua vez.
db<>fiddle aqui