具有动态列数的PostgreSQL查询
我试图找到一种方法来返回一个列数动态的记录集。我可以编写一个查询,生成所需的列名列表:具有动态列数的PostgreSQL查询,sql,postgresql,pivot,crosstab,postgresql-9.5,Sql,Postgresql,Pivot,Crosstab,Postgresql 9.5,我试图找到一种方法来返回一个列数动态的记录集。我可以编写一个查询,生成所需的列名列表: SELECT DISTINCT name FROM tests WHERE group = 'basic'; 这将返回一个短列表,如“poke”、“prod”、“hit”、“drop”等。然后我希望生成一个表,显示一系列测试,其中每个测试都在运行。每天早上,我们都会查看开发人员一直在做什么,并对其进行戳戳和戳戳,以便每天都运行每个测试。我可以静态编写此查询: SELECT (SELECT success F
SELECT DISTINCT name FROM tests WHERE group = 'basic';
这将返回一个短列表,如“poke”、“prod”、“hit”、“drop”等。然后我希望生成一个表,显示一系列测试,其中每个测试都在运行。每天早上,我们都会查看开发人员一直在做什么,并对其进行戳戳和戳戳,以便每天都运行每个测试。我可以静态编写此查询:
SELECT (SELECT success FROM test_results AS i
WHERE i.name = 'poke'
AND i.date = o.date) AS 'poke',
(SELECT success FROM test_results AS i
WHERE i.name = 'prod'
AND i.date = o.date) AS 'prod',
...
FROM test_results AS o GROUP BY date
HAVING date > now() - '1 week'::interval;
然而,这是硬编码到我们每天运行的测试中的。如果我们现在需要每天启动设备,我们需要更新查询。如果我们决定不再需要跌落测试,那么一周后,跌落测试列应该退出报告,因为它不再出现在结果中。当只有某些日期有结果条目时,为缺少的测试返回NULL是完全可以接受的
是否有一种方法可以通过在查询中使用常规SQL从结果创建列的动态列表
我试图通过使用带有查询的部分地建立我需要的数据,但我找不到一种方法来从动态信息正确地建立最后一行
编辑:以下是过去两天的一些样本数据:
CREATE TABLE test_results (
name TEXT NOT NULL,
date DATE default now() NOT NULL,
success BOOLEAN NOT NULL
);
INSERT INTO test_results (name, date, success) VALUES ('hit', '2017-06-20', TRUE);
INSERT INTO test_results (name, date, success) VALUES ('poke', '2017-06-20', TRUE);
INSERT INTO test_results (name, date, success) VALUES ('prod', '2017-06-20', TRUE);
INSERT INTO test_results (name, date, success) VALUES ('poke', '2017-06-21', TRUE);
INSERT INTO test_results (name, date, success) VALUES ('prod', '2017-06-21', TRUE);
INSERT INTO test_results (name, date, success) VALUES ('poke', '2017-06-22', TRUE);
INSERT INTO test_results (name, date, success) VALUES ('prod', '2017-06-22', FALSE);
INSERT INTO test_results (name, date, success) VALUES ('poke', '2017-06-23', TRUE);
INSERT INTO test_results (name, date, success) VALUES ('prod', '2017-06-23', TRUE);
INSERT INTO test_results (name, date, success) VALUES ('drop', '2017-06-23', TRUE);
如果我对2017-06-21至2017-06-23的数据范围进行查询,我希望得到如下结果,包括当时运行的所有测试的矩阵:
date | poke | prod | drop
------------+--------+--------+-----
2017-06-21 | TRUE | TRUE | NULL
2017-06-22 | TRUE | FALSE | NULL
2017-06-23 | TRUE | TRUE | TRUE
名称poke、prod和drop都是在该时间段内一行的名称字段中找到的名称。对于没有该日期记录的任何测试的详细查询,将返回NULL。我将描述如下查询:
SELECT tr.name, tr.date, tr.success
FROM tests t JOIN
test_results tr
ON t.testid = tr.testid
WHERE t.group = 'basic' AND tr.date > now() - '1 week'::interval;
您最好在应用程序级别旋转数据。启用“tablefunc”扩展,然后使用“crosstab”函数;请参阅PG文档:。crosstab函数的参数应该是一个查询的文本,该查询生成三列:日期、测试名称和测试成功,按顺序排列。使用了不同的方法,有些方法在这里已经提到过,如crosstab。此外,您还可以构建自己的函数,动态构建查询并返回as和其他几个方法
但所有这些都要求您预定义输出的确切数量及其数据类型
如果我理解你的情况,你不会想要你提到的:
如果我们现在需要开始每天踢设备,我们需要更新
查询
使用交叉表和其他方法的缺点几乎相同
所以有一种方法可以使用。这可能不是最好的方法,如果您可以使用交叉表
,则可能更好
但至少我会在代码中添加注释
解决方案:
-- Function for opening cursor
CREATE OR REPLACE
FUNCTION test_stats(
c REFCURSOR, -- cursor name
sdate date, -- start date of period wanted (included)
edate date, -- end date of period wanted (included)
gtype text -- you had in your 'tests' table some group type which I included just in case
)
RETURNS REFCURSOR
LANGUAGE PLPGSQL
AS
$main$
BEGIN
OPEN c
FOR
-- Following dynamic query building can be
-- used also if want to go with function that RETURNS TABLE
EXECUTE format(
' SELECT r.date,
%s
FROM test_results r
WHERE r.date BETWEEN %L AND %L
GROUP BY 1
',
-- Here we build for each 'name' own statement and
-- aggregate together with comma separator to feed
-- into main query.
-- P.S. We need to double check result unfortunately
-- against test_results table once to get pre-filter
-- for names in specified date range.
-- With this we eliminate tests that for sure will
-- not be presented in the range. In given test data
-- this means eliminating 'hit'.
(
SELECT string_agg(
DISTINCT format(
'( SELECT success
FROM test_results i
WHERE i.name = %1$L
AND i.date = r.date ) AS "%1$s"',
t.name
),
','
)
FROM tests t,
LATERAL ( SELECT array_agg( DISTINCT r.name )
FROM test_results r
WHERE r.date BETWEEN sdate AND edate
) a( lst )
WHERE t.group = gtype -- the group type is used here
AND t.name = ANY ( a.lst::text[] )
),
sdate, -- start date for between statement
edate -- end date for between statement
);
RETURN c;
END;
$main$;
-- Usage example:
BEGIN;
SELECT test_stats( 'teststats1', '2017-06-21'::date, '2017-06-23'::date, 'basic' );
FETCH ALL IN teststats1;
COMMIT;
-- Result (from your given test data set):
date | drop | poke | prod
------------+------+------+------
2017-06-22 | | t | f
2017-06-21 | | t | t
2017-06-23 | t | t | t
(3 rows)
正如我所提到的,这不是一种完美的方法,但它确实起到了作用:)编辑您的问题并提供示例数据和所需结果。您需要在过程中动态构建语句,然后执行该语句。我过去也做过类似的事情,但我需要在我的硬盘上查找。如果没有人给你正确的答案,我明天再看。我尝试在SQL中这样做的原因是因为我不控制应用层。它只是将SQL语句的结果显示为应用程序中的表/矩阵。交叉表的主要缺点是:仍然需要对主查询中的字段列表进行硬编码。下面是一篇博客文章,展示了如何使用动态SQL生成字段列表,因此,硬编码是不必要的:因为实现现在已经设置为SQL本身是一种静态类型的语言,就像C或Java一样,我所要求的是不可能的。我考虑的是动态类型语言,比如Python或JavaScript。虽然各种函数可以更改其签名以匹配其上下文,但最终,类型是在编译(准备?)原始SQL语句时确定的。