如何动态计算 GROUP 中多列的总和?

Posted

技术标签:

【中文标题】如何动态计算 GROUP 中多列的总和?【英文标题】:How to dynamically calculate the sums of many columns in a GROUP? 【发布时间】:2018-01-04 12:22:39 【问题描述】:

在下表中,我有可变数量的列,该数字在 1000 秒以内。我需要对按人名分组的 1000 列中的每一列的所有值求和。所以,smith's 总计test_score_1,总计test_score_2,...总计test_score_1000。然后Jackson's 总计test_score_1,总计test_score_2,...总计test_score_1000

我事先不知道“test_score_n”列的数量,它们总是在变化。

所以给定这张表:

name      test_score_1 test_score_2 ...  test_score_1000
  smith        2              1                 0
  jackson      0              3                 1
  jackson      1              1                 2
  jackson      3              0                 3
  smith        4              5                 1

如何制作下表?

name      test_score_1 test_score_2 ...  test_score_1000
  smith        6              6                1
  jackson      4              4                6

【问题讨论】:

你有 1000 列?!第一项任务:立即修复这种疯狂。 生物数据很容易拥有超过 1024 列。有人可能会争辩说,不应该在数据库管理系统中进行数据转换,但是在 R 中处理大于 ram 的数据是很痛苦的,主要是因为我需要的算法不需要部分学习。我更喜欢在 sqlserver 中生成数据集,并在数据科学部分使用 R 或 python。 我仍然不相信 - 我仍然会将数据标准化为多个表。另外,我认为如果您仍然绝对需要 1000 列,那么 SQL 数据库可能不是存储数据的好地方。 【参考方案1】:

演示: http://rextester.com/MAFCP19297

SQL

DECLARE @cols varchar(max), @sql varchar(max);

SELECT @cols = 
     COALESCE(@cols + ', ', '') + 'SUM(' + COLUMN_NAME + ') AS ' + COLUMN_NAME
     FROM INFORMATION_SCHEMA.COLUMNS
     WHERE table_name = '<tbl name>'
       AND COLUMN_NAME <> 'name'
       -- The AND below may be optional - see "Additional Notes #1"
       AND TABLE_CATALOG = '<database schema name>';

SET @sql = 'SELECT name, ' + @cols + ' FROM tbl GROUP BY name;';

EXEC (@sql); 

说明

    DECLARE 创建两个变量 - 一个用于存储 SQL 的列求和部分,另一个用于存储要运行的整个动态创建的 SQL 语句。 SELECT 查询INFORMATION_SCHEMA.COLUMNS 系统表,获取tbl 中除name 列之外的所有列的名称。 (也可以使用 sys 表 - 对this question 的回答讨论每个表的相对优点)。然后使用this method(可以说比替代的FOR XML PATH ('') 方法简单一点)将这些行值转换为单个逗号分隔值。逗号分隔的值不仅仅是列名 - 它们SUM覆盖每个列名,然后为结果分配同名的别名。 SET 然后构建一个简单的 SQL 语句,选择名称和所有求和值 - 例如:SELECT name, SUM(test_score_1) AS test_score_1, SUM(test_score_2) AS test_score_2, SUM(test_score_1000) AS test_score_1000 FROM tbl GROUP BY name;EXEC 然后运行上述查询。

附加说明

    如果表名在所有数据库中可能不是唯一的,则选择中需要以下子句:AND TABLE_CATALOG = '&lt;database schema name&gt;' 我对这个问题的最初回答是错误地使用 mysql 而不是 SQL Server - 现在已更正,但以前的版本仍在编辑历史中,可能对某人有所帮助...

【讨论】:

非常感谢您的解释,帮了大忙。甚至还有一个演示。【参考方案2】:

生成 SQL 的 SQL

DECLARE @generatedSQL nvarchar(max);

SET @generatedSQL = (

SELECT
    'SELECT ' + 
    SUBSTRING(X.foo, 2, 2000) + 
    'FROM ' + 
    QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) +
    ' GROUP BY name' --fix this line , edited
FROM
    sys.tables t
    CROSS APPLY 
    (
    SELECT
        ', SUM(' + QUOTENAME(c.name) + ')'
    FROM 
        sys.columns c 
    WHERE 
        c.object_id = t.object_id
        AND
        c.name <> 'Name'
    FOR XML PATH('')
    ) X (foo)
WHERE
    t.name = 'MyTable'
 );

EXEC (@generatedSQL);

【讨论】:

嗨,选择“选择”是什么意思?我可以在这段代码中添加一个 group by 子句吗?我想按名称分组(请参阅修改后的问题),然后总结每个名称的 1000 列。谢谢! @gbn - 我认为您需要从 CROSS APPLY 中排除 name @user798719 - 我建议你去谷歌看看“什么是动态 SQL” @Alex 你为什么这么说?我需要它来生成 CSV @SteveChambers 谢谢,知道了。,该组是后来添加的【参考方案3】:

用你的表名更改tablename

     Declare @query as nvarchar(MAX) = (SELECT
    'SELECT name,' + SUBSTRING(tbl.col, 2, 2000) + ' FROM ' + QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) + 'Group By name'
FROM
    sys.tables t
    CROSS APPLY 
    (
    SELECT
        ', SUM(' + QUOTENAME(columns.name) + ') as ' + columns.name
    FROM 
        sys.columns columns 
    WHERE 
        columns.object_id = t.object_id and columns.name != 'name'
    FOR XML PATH('')
    ) tbl (col)
WHERE
    t.name = 'tablename')
select @query EXECUTE(@query)

【讨论】:

这个答案似乎很受@gbn的回答的启发 SELECT ', SUM(' + QUOTENAME(columns.name) + ') as ' + columns.name 我无法理解代码。您如何在每列中插入“总和”,而不是对所有列求和一次?在哪里添加 group by 子句?我想在对 10000 个动态列中的每一个求和之前按“名称”进行分组。 我在使用您的代码时出错:“SELECT id, SUM([003]) as 003, SUM([008]) as 008, SUM([009]) as 009, SUM([ 010]) as 010, SUM([011]) ... SUM( FROM [dbo].[t1]Group By name" 我使用了一个小样本,它有 362 列(003、008、009 等是列名)。生成的 select 语句没有显示所有列,切断了很多。 @user798719 请发送完整的查询和错误详细信息。在我的数据库中同样工作。【参考方案4】:

试试下面的脚本

(将@tableName= [yourTablename] 和@nameColumn 设置为您要分组的字段的名称)

    Declare @tableName varchar(50)='totalscores'
    Declare @nameColumn nvarchar(50)='name'

    Declare @query as nvarchar(MAX) ;

    select @query = 'select ' + nameColumn  + cast(sumColumns as nvarchar(max))  + 'from ' + @tableName +' group by ' + nameColumn     from (
    select @nameColumn nameColumn, (SELECT 
            ', SUM(' + QUOTENAME(c.name) + ') ' + QUOTENAME(c.name)
        FROM
            sys.columns c
        WHERE
            c.object_id=t.object_id and c.name != @nameColumn
        order by c.name
        FOR 
            XML path(''), type
     ) sumColumns
     from sys.tables t where  t.name= @tableName
    )t

    EXECUTE(@query)

【讨论】:

【参考方案5】:

GBN 的动态 SQL 将是我的首选 (+1),而且性能会更高。 但是,如果您有兴趣打破 1,000 多列的可怕循环,请考虑以下事项:

示例

Declare @YourTable Table ([col 1] int,[col 2] int,[col 1000] varchar(50))
Insert Into @YourTable Values 
 (2,1,0)
,(4,5,1)

Select Item  = replace(C.Item,'_x0020_', ' ')
      ,Value = sum(C.Value)
 From @YourTable A
 Cross Apply (Select XMLData= cast((Select A.* for XML RAW) as xml)) B
 Cross Apply (
                Select Item   = a.value('local-name(.)','varchar(100)')
                      ,Value  = a.value('.','int') 
                 From  B.XMLData.nodes('/row')  as C1(n)
                 Cross Apply C1.n.nodes('./@*') as C2(a)
                 Where a.value('local-name(.)','varchar(100)') not in ('Fields','ToExclude')
             ) C
 Group By C.Item

退货

Item        Value
col 1       6
col 2       6
col 1000    1

【讨论】:

【参考方案6】:

试试这个动态列生成Sql脚本

DECLARE @Sql nvarchar(max)

SET @Sql=( SELECT DISTINCT 'SELECT'+ 
                    STUFF((SELECT ', '+  ' SUM( '+ COLUMN_NAME +' ) AS '+ QUOTENAME( COLUMN_NAME )
                    FROM INFORMATION_SCHEMA.COLUMNS Where TABLE_NAME ='Tab1000' 
                    FOR XML PATH (''),type).value('.','varchar(max)'),1,2,'')
                    +' From Tab1000'From INFORMATION_SCHEMA.COLUMNS Where TABLE_NAME ='Tab1000')

EXEC (@sql)

【讨论】:

我收到此错误:“操作数数据类型 varchar 对 sum 运算符无效。”有什么想法吗? 你的表中列的数据类型是什么 更新的问题有一个更具体的例子。 “test_score”列的内容都是 INT 类型。我想按姓名分组,然后按人的姓氏对 1000 个“test_score”列中的每一个进行求和。如果我硬编码 1000 列,我可以做到这一点。我只是不知道如何做动态sql等价物。 @Srini131 - 您需要排除 name 列,例如Where TABLE_NAME ='Tab1000' AND COLUMN_NAME &lt;&gt; 'Name'

以上是关于如何动态计算 GROUP 中多列的总和?的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle 中计算并添加多列的总计行

iOS中使用CSS多列的电子书(计算不同列中的文本范围)

需要一种将多列动态汇总为不同高度的合并单元格的方法

使用Javascript的动态gridview的多列的列总和

以动态形式计算选择字段的总和

根据多列聚合函数的条件结果计算唯一记录