在Postgres 9.3数据库中,我有一个场景,我需要得到最后10个图书销售的日期。请看下面的例子。
Store Book
---------- ----------------------
Id Name Id Name Sid Count Date
1 ABC 1 XYZ 1 20 11/11/2015
2 DEF 2 JHG 1 10 11/11/2015
3 UYH 1 10 15/11/2015
4 TRE 1 50 17/11/2015
目前没有 UNIQUE
约束 (name, sid, date)
表中 book
但我们有一个服务,每天只插入一个计数。
我必须基于以下几点获得结果 store.id
. 当我传递ID时,应该生成报告,包括书名、销售日期和销售册数。
希望的输出。
BookName 11/11/2015 15/11/2015 17/11/2015
XYZ 20 -- --
JHG 10 -- --
UYH -- 10 --
TRE -- -- 50
这看起来不可疑,但它是一个 糟糕的问题.
integer
.NOT NULL
.复合材料 (name, sid, date)
独一无二 book
. 你应该有一个 UNIQUE
约束,最好是(为了性能)在列中加入 这个 顺序。
UNIQUE(sid, date, name)
这将自动提供性能所需的索引。(否则创建一个。)参见。
crosstab()
查询为了获得最高的性能和较短的查询字符串(特别是当你经常运行这个查询时),我建议使用额外的模块--------。tablefunc
提供各种 crosstab()
功能。基本说明。
你要先把这些做好。
过去10天的数据。
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
过去十天的数字,用窗口函数 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;
(本查询中不包括实际日期。)
输出列的列名(完整解决方案)。
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;
这对你来说可能已经足够好了--但我们在结果中看不到实际的日期。
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);
如果要重复使用,我建议你为10个整数列创建这个(非常快的)通用C函数一次,以简化事情。
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';
详细内容请参考相关答案。
那么你的调用就变成了。
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!
你的实际问题比较复杂,你还想要动态列名。对于一个给定的表,结果查询可以是这样的,那么。
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");
困难的是如何提炼出动态列名。要么手工组装查询字符串,要么(更愿意)让这个函数为你做。
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$;
Call:
SELECT f_generate_date10_sql(1);
Call: This 产生所需的查询,你依次执行。
db<>提琴。此处