将列计数转换为行计数的 SQL 查询

Posted

技术标签:

【中文标题】将列计数转换为行计数的 SQL 查询【英文标题】:SQL Query to Transpose Column Counts to Row Counts 【发布时间】:2014-09-11 22:18:13 【问题描述】:

我有一个如下所示的表格,其中显示了类型的数量。我需要并且一直在尝试将数据显示为 1 列和 7 行,但是……没有成功。

__________________________________________________________________________
| col types      | win2k | winxp | win2k3 | vista | win7 | win8 | win8.1 |
--------------------------------------------------------------------------
| count of types |  2365 | 65655 | 422445 | 4822  | 482  | 2331 | 485323 |
--------------------------------------------------------------------------
Select 
count(case when col1 ~* '5.0.2195' then 1 else null end) as Win2k,
count(case when col1 ~* '5.1.2600' then 1 else null end) as WinXP, 
count(case when col1 ~* '5.2.3790' then 1 else null end) as W2k3,
count(case when (col1 ~* '6.0.6000'
    or col1 ~* '6.0.6001' or col1 ~* '6.0.6002') 
    then 1 else null end) as Vista,
count(case when (col1 ~* '6.1.7600'
    or col1 ~* '6.1.7601')
    then 1 else null end) as Win7,
count(case when col1 ~* '6.2.9200' then 1 else null end) as Win8,
count(case when (col1 ~* '6.3.9200'
    or col1 ~* '6.3.9600')
    then 1 else null end) as "Win8.1"
From col1

理想情况下应该是这样的:

___________________
| types  | count  |
-------------------
| win2k  | 2365   |
| winxp  | 65655  |
| win2k3 | 422445 | 
| vista  | 4822   |
| win7   | 482    |
| win8   | 2331   |
| win8.1 | 485323 |
-------------------

注意事项:

我正在使用带有 PGADMIN III 的 Postgresql 9.3 我无法创建任何自定义函数 如果有更多的列来完成这项工作并不重要

【问题讨论】:

unpivot and PostgreSQL 的可能重复项 我认为 Bulat 是正确的,你想要一个与 postgresql.org/docs/9.1/static/tablefunc.html 相反的(未)数据透视表 Bulat,我在发布之前查看并尝试了该参考资料,虽然它可能看起来相似,但它具有不适用的不同表/数据结构。我的单行是聚合数据,即使不是不可能,也使得 unpivot 非常困难。 当然可以。您提出的查询是非法且令人困惑的,因此很难说什么最适合您。 profile.foo 在您的查询上下文中不是合法的列名。请修复问题。您可以提供表定义和一些示例值来澄清。 发布的第一个问题...仍在学习中。 【参考方案1】:

这些类型的查询更容易以 GROUP BY 为目标,如下所示:

Select 
case when profile.foo ~* '5.0.2195' then 'Win2k'
     when profile.foo ~* '5.1.2600' then 'WinXP' 
     when profile.foo ~* '5.2.3790' then 'W2k3'
     when (profile.foo ~* '6.0.6000'
        or profile.foo ~* '6.0.6001'
        or profile.foo ~* '6.0.6002') 
        then 'Vista'
     when (profile.foo ~* '6.1.7600'
        or profile.foo ~* '6.1.7601')
        then 'Win7'
     when profile.foo ~* '6.2.9200' then 'Win8'
     when (profile.foo ~* '6.3.9200'
        or profile.foo ~* '6.3.9600')
        then 'Win8.1' ELSE 'Other' END as type,
     count(*) as cnt
From profile
GROUP BY 1

如下所述,此查询适用于互斥情况,即当profile.foo 包含代表每行一个操作系统的值时

【讨论】:

在 from 语句中,我删除了列名,它运行良好。谢谢! 正如对另一个(等效和更早)问题的评论,此答案仅对互斥情况有效。使用GROUP BY 1 进行简化。 @ErwinBrandstetter:感谢您的评论。我进行了反映您的评论的编辑。顺便说一句,如果 profile.foo 中有多个值,则可以使用 UNION ALL 执行计数 UNION ALL,是的,但由于多次顺序扫描,成本要高得多。【参考方案2】:

不使用条件聚合,只需使用CASE 适当地填充Type,然后在Type 上分组:

   ;with cte AS (Select   case when profile.foo ~* '5.0.2195' then 'Win2k'
                               when profile.foo ~* '5.1.2600' then 'WinXP' 
                               when profile.foo ~* '5.2.3790' then 'W2k3'
                               when profile.foo ~* '6.0.6000' or profile.foo ~* '6.0.6001' or profile.foo ~* '6.0.6002' then 'Vista'
                               when (profile.foo ~* '6.1.7600' or profile.foo ~* '6.1.7601') then 'Win7'
                               when profile.foo ~* '6.2.9200' then 'Win8'
                               when (profile.foo ~* '6.3.9200' or profile.foo ~* '6.3.9600') then 'Win8.1'
                          end as Type
                 From profile.foo)
    SELECT Type,COUNT(*) AS ct
    FROM cte
    GROUP BY Type

不是 100% 使用 postgresql 语法,但逻辑是兼容的。

【讨论】:

CASE 语句每行只生成 1 个匹配项,而在原始查询中,每行可以匹配多个表达式。 (除了 Q 中的非法列名。)可能不是作者的意图,但仍可能产生不同的结果。 我运行这个并得到:错误:模式“配置文件”不存在第 9 行:来自 profile.flavor) ^ ********** 错误 ******* *** 错误:架构“配置文件”不存在 SQL 状态:3F000 字符:914 @ErwinBrandstetter 原始查询中CASE 语句之间的条件都没有重叠,因此计数将等同于我在这里所做的,就像行而不是列一样,除非语法问题,当然。 @BooneStars 我不知道是什么导致了这个错误,我对 postgresql 语法不是很熟悉。 这是一个逻辑错误。条件不必重叠。单个字符串可以匹配任意数量的正则表达式,特别是因为它们没有锚定。考虑值“5.0.2195 5.1.2600”。我现在放弃这个。有太多的漂浮物无法加起来。【参考方案3】:

我喜欢为此使用 Postgres 特定的并行 unnest()

SELECT unnest('win2k,winxp,win2k3,vista,win7,win8,win8.1'::text[]) AS type
      ,unnest(ARRAY[
          count(some_column ~ '5.0.2195' OR NULL)
         ,count(some_column ~ '5.1.2600' OR NULL)
          .. the rest from your query above ...
       ]) AS ct
FROM   profile.foo

两个数组中值的顺序和数量必须匹配。 包含更多详细信息的相关答案(请务必阅读!):

Parallel unnest() and sort order in PostgreSQL

Is there something like a zip() function in PostgreSQL that combines two arrays?

替代计数技术(可选)

count() 只计算非空值..

(TRUE  OR NULL) IS TRUE  
(FALSE OR NULL) IS NULL  
(NULL  OR NULL) IS NULL

瞧。仅计算 TRUE。 More details in this answer on dba.SE.

旁白:在表达式中使用~ instead of ~*,因为这些字符串文字中没有区分大小写的字母。但我怀疑你根本不需要正则表达式匹配。

另外,列名profile.foo 没有意义,因为唯一的表名为foo,而不是profile

【讨论】:

以上是关于将列计数转换为行计数的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

SQL 查询将列转换为行

如何把SQL server 表里插入转换科学计数法的方法。

如何将任何给定的 SQL/HQL 选择查询动态转换为等效计数查询?

SQL:在 Chartio 中动态地将列转换为行

Oracle SQL - 将选择计数(*)转换为零或一

从 SQL 查询中捕获计数