查询按日期范围分隔的行数
Posted
技术标签:
【中文标题】查询按日期范围分隔的行数【英文标题】:Querying row counts segregated by date ranges 【发布时间】:2012-12-04 00:36:37 【问题描述】:我有一个 PostgreSQL 9.2.1 数据库,我正在尝试编写 SQL 查询但未能成功,该查询将向我显示失败的不同测试 (testname
) 的计数 (current_status='FAILED'
并显示 0 if没有失败),按月隔离(last_update
)。这是表定义:
Table "public.tests"
Column | Type | Modifiers
----------------+-----------------------------+-------------------------------------------------------------
id | bigint | not null default nextval('tests_id_seq'::regclass)
testname | text | not null
last_update | timestamp without time zone | not null default now()
current_status | text | not null
我想从中得到的回报是这样的:
testname | Jan2012 | Feb2012 | Mar2012 | Apr2012 | May2012 | Jun2012 | Jul2012 | Aug2012 | Sep2012 | Oct2012 | Nov2012 | Dec2012
-------------+-----------------------------------------------------------------------------------------------------------------------------------------
abq | 2 | 5 | 2 | 0 | 7 | 4 | 8 | 0 | 6 | 15 | 1 | 0
bar | 0 | 0 | 2 | 0 | 9 | 8 | 8 | 2 | 6 | 15 | 1 | 1
cho | 15 | 1 | 2 | 3 | 4 | 8 | 7 | 3 | 6 | 1 | 5 | 6
在这一点上,我能想到的最好的方法是以下,诚然不是很接近:
SELECT testname, count(current_status) AS failure_count
FROM tests
WHERE current_status='FAILED'
AND last_update>'2012-09-01'
AND last_update<='2012-09-30'
GROUP by testname
ORDER BY testname ;
我想我需要以某种方式使用 COALESCE
来获得 0
值以显示在结果中,加上一些疯狂的 JOIN 以显示多个月的结果,甚至可能是一个窗口函数?
【问题讨论】:
+1 用于显示版本、表定义和预期结果。您是否有机会发布一些示例数据(可能发布到 SQLFiddle.com),这样我们就不需要制作虚拟数据了? 顺便说一句,从tablefunc
扩展中签出crosstab
。
对不起,我不熟悉 SQLFiddle。您是否要求查看“从测试中选择测试名称、上次更新、当前状态”之类的内容?
是的,或者最好是INSERT
声明。试试pg_dump --data-only --inserts -t TABLENAME DATABASENAME
【参考方案1】:
crosstab()
有两个参数的函数。
应该像这样工作,以获得 2012 年的值:
SELECT * FROM crosstab(
$$SELECT testname, to_char(last_update, 'mon_YYYY'), count(*)::int AS ct
FROM tests
WHERE current_status = 'FAILED'
AND last_update >= '2012-01-01 0:0'
AND last_update < '2013-01-01 0:0' -- proper date range!
GROUP BY 1,2
ORDER BY 1,2$$
,$$VALUES
('jan_2012'::text), ('feb_2012'), ('mar_2012')
, ('apr_2012'), ('may_2012'), ('jun_2012')
, ('jul_2012'), ('aug_2012'), ('sep_2012')
, ('oct_2012'), ('nov_2012'), ('dec_2012')$$)
AS ct (testname text
, jan_2012 int, feb_2012 int, mar_2012 int
, apr_2012 int, may_2012 int, jun_2012 int
, jul_2012 int, aug_2012 int, sep_2012 int
, oct_2012 int, nov_2012 int, dec_2012 int);
Find detailed explanation under this related question.
我没有测试。正如@Craig 所说,样本值会有所帮助。
现在用我自己的测试用例进行了测试。
不显示 NULL 值
带有两个参数的crosstab()
函数避免了主要问题(没有行的月份根本不会出现)。
您不能在内部查询中使用COALESCE
,因为NULL
值是由crosstab()
本身插入的。你可以...
1。将整个事情包装成一个子查询:
SELECT testname
,COALESCE(jan_2012, 0) AS jan_2012
,COALESCE(feb_2012, 0) AS feb_2012
,COALESCE(mar_2012, 0) AS mar_2012
, ...
FROM (
-- query from above)
) x;
2。 LEFT JOIN
主要查询月份的完整列表。
在这种情况下,根据定义,您不需要第二个参数。
对于更大的范围,您可以使用 generate_series()
来创建值。
SELECT * FROM crosstab(
$$SELECT t.testname, m.mon, count(x.testname)::int AS ct
FROM (
VALUES
('jan_2012'::text), ('feb_2012'), ('mar_2012')
,('apr_2012'), ('may_2012'), ('jun_2012')
,('jul_2012'), ('aug_2012'), ('sep_2012')
,('oct_2012'), ('nov_2012'), ('dec_2012')
) m(mon)
CROSS JOIN (SELECT DISTINCT testname FROM tests) t
LEFT JOIN (
SELECT testname
,to_char(last_update, 'mon_YYYY') AS mon
FROM tests
WHERE current_status = 'FAILED'
AND last_update >= '2012-01-01 0:0'
AND last_update < '2013-01-01 0:0' -- proper date range!
) x USING (mon)
GROUP BY 1,2
ORDER BY 1,2$$
)
AS ct (testname text
, jan_2012 int, feb_2012 int, mar_2012 int
, apr_2012 int, may_2012 int, jun_2012 int
, jul_2012 int, aug_2012 int, sep_2012 int
, oct_2012 int, nov_2012 int, dec_2012 int);
带有样本数据的测试用例
这是一个测试用例,其中包含 OP 未能提供的一些示例数据。我用它来测试它并让它工作。
CREATE TEMP TABLE tests (
id bigserial PRIMARY KEY
,testname text NOT NULL
,last_update timestamp without time zone NOT NULL DEFAULT now()
,current_status text NOT NULL
);
INSERT INTO tests (testname, last_update, current_status)
VALUES
('foo', '2012-12-05 21:01', 'FAILED')
,('foo', '2012-12-05 21:01', 'FAILED')
,('foo', '2012-11-05 21:01', 'FAILED')
,('bar', '2012-02-05 21:01', 'FAILED')
,('bar', '2012-02-05 21:01', 'FAILED')
,('bar', '2012-03-05 21:01', 'FAILED')
,('bar', '2012-04-05 21:01', 'FAILED')
,('bar', '2012-05-05 21:01', 'FAILED');
【讨论】:
非常感谢!这让我到达那里的 99%。唯一缺少的是空计数不报告为 0。 我想我可以通过合并强制空计数为零,但它没有任何影响:SELECT testname, to_char(last_update, 'mon_YYYY') AS last_update, coalesce(count(*), 0) AS ct 尝试使用 CASE 将 NULL 强制为 0,但这也不起作用。我一定错过了一些愚蠢的东西:CASE WHEN count() IS NULL THEN 0 ELSE count() END as ct @netllama:我为额外的问题提供了额外的解决方案。 @netllama:第一个错误是类型不兼容。我将结果类型更改为整数以修复它。解决方案 2. 需要改进:CROSS JOIN
日历行到测试名称表以覆盖所有行。我们确实要求提供样本数据以便我们进行测试,我提到我的解决方案因此未经测试。以上是关于查询按日期范围分隔的行数的主要内容,如果未能解决你的问题,请参考以下文章