获取最近 10 个日期的行
Get rows for the last 10 dates
我在 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
目前 table book
中的 (name, sid, date)
没有 UNIQUE
约束,但我们有一项服务每天只插入一个计数。
我必须根据 store.id
获得结果。当我通过 ID 时,应该会生成包含书名、销售日期和销售数量的报告。
期望的输出:
BookName 11/11/2015 15/11/2015 17/11/2015
XYZ 20 -- --
JHG 10 -- --
UYH -- 10 --
TRE -- -- 50
这看起来并不可疑,但这是一个问题的地狱。
假设
- 您的计数是
integer
。
- table 书中的所有列均已定义
NOT NULL
。
复合 (name, sid, date)
在 table book
中是唯一的。你应该有一个 UNIQUE
约束,最好是(为了性能)列在 this order:
UNIQUE(sid, date, name)
这会自动提供性能所需的索引。 (否则创建一个。)参见:
crosstab()
查询
为了获得最佳性能和较短的查询字符串(尤其是如果您经常 运行 此查询)我建议使用额外的模块 tablefunc
提供各种 crosstab()
函数。 基本说明:
- PostgreSQL Crosstab Query
基本查询
你需要先把这些做好。
最近 10 天:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
最近 10 天使用 window 函数的数字 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';
此相关回答中的详细信息:
- Dynamically generate columns for crosstab in PostgreSQL
那么你的电话就变成了:
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!
具有动态列名的完整解决方案
您的实际问题比较复杂,您还需要动态列名。
对于给定的 table,结果查询可能如下所示:
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
<b> , "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")</b>;
困难在于提取动态列名。 assemble 手动查询字符串,或者(更确切地说)让这个函数为你做:
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$;
致电:
SELECT f_generate_date10_sql(1);
这 生成所需的查询,您依次执行。
db<>fiddle here
我在 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
目前 table book
中的 (name, sid, date)
没有 UNIQUE
约束,但我们有一项服务每天只插入一个计数。
我必须根据 store.id
获得结果。当我通过 ID 时,应该会生成包含书名、销售日期和销售数量的报告。
期望的输出:
BookName 11/11/2015 15/11/2015 17/11/2015
XYZ 20 -- --
JHG 10 -- --
UYH -- 10 --
TRE -- -- 50
这看起来并不可疑,但这是一个问题的地狱。
假设
- 您的计数是
integer
。 - table 书中的所有列均已定义
NOT NULL
。 复合
(name, sid, date)
在 tablebook
中是唯一的。你应该有一个UNIQUE
约束,最好是(为了性能)列在 this order:UNIQUE(sid, date, name)
这会自动提供性能所需的索引。 (否则创建一个。)参见:
crosstab()
查询
为了获得最佳性能和较短的查询字符串(尤其是如果您经常 运行 此查询)我建议使用额外的模块 tablefunc
提供各种 crosstab()
函数。 基本说明:
- PostgreSQL Crosstab Query
基本查询
你需要先把这些做好。
最近 10 天:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY date DESC
LIMIT 10;
最近 10 天使用 window 函数的数字 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';
此相关回答中的详细信息:
- Dynamically generate columns for crosstab in PostgreSQL
那么你的电话就变成了:
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!
具有动态列名的完整解决方案
您的实际问题比较复杂,您还需要动态列名。
对于给定的 table,结果查询可能如下所示:
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
<b> , "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")</b>;
困难在于提取动态列名。 assemble 手动查询字符串,或者(更确切地说)让这个函数为你做:
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$;
致电:
SELECT f_generate_date10_sql(1);
这 生成所需的查询,您依次执行。
db<>fiddle here