我有一个类似这样的表:
id feh bar
1 10 A
2 20 A
3 3 B
4 4 B
5 5 C
6 6 D
7 7 D
8 8 D
我希望它看起来像这样:
bar val1 val2 val3
A 10 20
B 3 4
C 5
D 6 7 8
我有这样的查询:
SELECT bar,
MAX(CASE WHEN abc."row" = 1 THEN feh ELSE NULL END) AS "val1",
MAX(CASE WHEN abc."row" = 2 THEN feh ELSE NULL END) AS "val2",
MAX(CASE WHEN abc."row" = 3 THEN feh ELSE NULL END) AS "val3"
FROM
(
SELECT bar, feh, row_number() OVER (partition by bar) as row
FROM "Foo"
) abc
GROUP BY bar
这是一个非常make-shifty的方法,如果有很多新列要创建,就会变得很笨拙。我想知道CASE
语句是否可以做得更好,使这个查询更动态?而且,我很想看到其他方法来做这件事。
6条答案
按热度按时间2wnc66cl1#
如果您尚未安装附加模块tablefunc,请对每个数据库运行此命令 * 一次 *:
问题答案
一个非常基本的交叉表解决方案:
这里的特殊困难是,在基表中没有 * category *(
cat
)。对于基本的1-参数形式,我们可以只提供一个具有伪值的伪列作为category。无论如何,该值都被忽略。这是一个罕见的情况,其中
crosstab()
函数的第二个参数是不需要,因为根据这个问题的定义,所有NULL
值只出现在右边的悬空列中,并且顺序可以由 * value * 确定。如果我们有一个实际的 * category * 列,其名称决定了结果中值的顺序,我们需要
crosstab()
的2参数形式,这里我借助窗口函数row_number()
合成了一个category列,作为crosstab()
的基础:剩下的都是些普通的问题。在这些紧密相关的答案中找到更多的解释和链接。
如果您不熟悉
crosstab()
函数,请先阅读本文!正确的测试设置
问题中缺少的测试设置:
动态交叉表?
正如@Clodoaldo评论的那样,还不是很"动态"。动态返回类型很难用plpgsql实现。但是有一些"方法"可以绕过它--"有一些限制"。
为了不使剩下的部分变得更加复杂,我用一个***更简单的***测试用例来演示:
电话:
退货:
tablefunc
模块内置功能tablefunc模块为通用
crosstab()
调用提供了一个简单的基础结构,而无需提供列定义列表。crosstab1()
-crosstab4()
是预定义的。它们需要并返回所有的text
,所以我们需要强制转换integer
的值,但是这样可以简化调用:结果:
| 行名称|类别_1|类别_2|类别_3|类别_4|
| - ------| - ------| - ------| - ------| - ------|
| A类|十个|二十个|||
| 乙|三个|四个|||
| C级|五个||||
| D级|六个|七|八个||
自定义
crosstab()
函数对于***更多列***或***其他数据类型***,我们创建自己的 * 复合类型 * 和 * 函数 *(一次)。
类型:
功能:
电话:
结果:
| 行名称|值1| val2值|val3| val4| val5|
| - ------| - ------| - ------| - ------| - ------| - ------|
| A类|十个|二十个||||
| 乙|三个|四个||||
| C级|五个|||||
| D级|六个|七|八个|||
* 一个 * 多态动态函数适用于所有
tablefunc
模块的范围。*为了使返回类型动态化,我使用了多态类型,相关答案中详细介绍了这种技术:
1-参数形式:
2参数形式的此变量的过载:
pg_typeof(_rowtype)::text::regclass
:每个用户定义的复合类型都定义了一个行类型,因此属性(列)列在系统目录pg_attribute
中。将注册的类型(regtype
)强制转换为text
,并将此text
强制转换为regclass
。创建复合类型一次
对于要使用的每个返回类型,您都需要定义一次:
对于临时调用,您还可以创建一个临时表,以达到相同的(临时)效果:
或者使用现有表、视图或实体化视图的类型(如果可用)。
呼叫
使用上述行类型:
1-参数表单(无缺失值):
2-参数表单(某些值可能缺失):
这个一个函数适用于所有返回类型,而
tablefunc
模块提供的crosstab
N()
框架需要为每种类型使用一个单独的函数。如果你已经像上面演示的那样按顺序命名了你的类型,你只需要替换粗体数字。要找到基表中类别的最大数量:
如果你想要 * 单独的列 *,这就像这样的动态,像demonstrated by @Clocoaldo这样的数组,或者简单的文本表示,或者 Package 在
json
或hstore
这样的文档类型中的结果,都可以动态地适用于任何数量的类别。将用户输入转换为代码时总是存在潜在危险。请确保不能将其用于SQL注入。不要接受来自不受信任用户的输入(直接)。
致电原始问题:
x4shl7ld2#
虽然这是一个老问题,但我想添加另一个解决方案,它是通过PostgreSQL最近的改进而成为可能的。这个解决方案实现了同样的目标,即从动态数据集返回结构化结果**,而根本不使用交叉表函数。**换句话说,这是一个很好的例子,重新检查那些阻止我们发现旧问题的新解决方案的无意的和隐含的假设。)
为了进行说明,您需要一个方法来转置具有以下结构的数据:
转换为以下格式:
传统的解决方案是创建动态交叉表查询的一种聪明(而且知识渊博)的方法,ErwinBrandstetter的回答中对此进行了非常详细的解释。
但是,如果您的特定用例足够灵活,可以接受稍有不同的结果格式,那么另一种解决方案也是可行的,它可以完美地处理动态透视。
使用PostgreSQL的新
jsonb_object_agg
函数以JSON对象的形式动态构建透视数据。我将用布兰德斯泰特先生的"更简单的测试案例"来说明:
使用
jsonb_object_agg
函数,我们可以创建所需的透视结果集,它具有以下简洁之处:其输出:
正如您所看到的,这个函数的工作原理是从示例数据中的
attrib
和value
列在JSON对象中创建键/值对,所有这些都按row_name
分组。虽然这个结果集看起来明显不同,但我相信它实际上可以满足许多(如果不是大多数)真实世界的用例,特别是那些数据需要动态生成透视表的用例,或者生成的数据由父应用程序使用的用例(例如,需要重新格式化以便在http响应中传输)。
这种方法的好处:
差异(和可能的缺点):
我很想听听别人的意见(特别是@ErwinBrandstetter's),尤其是在性能方面。当我在Andrew Bender的博客上发现这种方法时,就像被人打了一下脑袋。用一种新的方法解决PostrgeSQL中的难题是多么美妙的一种方式。它完美地解决了我的用例,我相信它同样也会为许多其他人服务。
slsn1g293#
这是对@Damian good答案的补充。在9.6的
json_object_agg
函数之前,我已经在其他答案中建议了JSON方法。它只是需要使用以前的工具集做更多的工作。上面提到的两个可能的缺点实际上并不存在。随机密钥顺序在必要时会被修正。丢失的密钥如果相关,则需要几乎微不足道的代码量来解决:
对于理解JSON的最终查询使用者来说,没有任何缺点,唯一的缺点是它不能作为表源使用。
jmo0nnb34#
在您的情况下,我想数组是好的。SQL Fiddle
ttvkxqim5#
很抱歉返回过去的数据,但是解决方案“动态交叉表”返回了错误的结果表。因此,valN值错误地“左对齐”,并且它们与列名不对应。当输入表中的值有“洞”时,例如“C”有val1和val3,但没有val2。这会产生错误:val3值将在最终表格的val2列(即下一个空闲列)中确定范围。
为了返回右列中有“洞”的正确单元格,交叉表查询需要在交叉表中执行第二次SELECT,类似于
"crosstab('SELECT row_name, attrib, val FROM tbl ORDER BY 1,2', 'select distinct row_name from tbl order by 1')"
gr8qqesn6#
从某种意义上说,这并不是真正的“动态”,因为您仍然需要枚举与预期值一样多的列,但通过这种方式很容易做到这一点。一个问题是,列需要一个序数键来匹配,没有中断。而且,如果有重复的键,它会使所有的键都不正常。因此也需要去重。这些集合都必须被预先分区以容纳N的适当集合。
对我来说,它看起来很笨拙,所以不确定它是否会带来很大的好处,但我把它添加到社区的狗堆中,希望它能为其他人提供一些刺激,让他们想出更好的方法。
/然后是枢轴/
结果: