用于计算所有表中的列的SQL查询

时间:2017-09-08 02:45:35

标签: sql sql-server tsql pivot

到目前为止,我可以使用以下SQL查询提取数据库表的列表:

SELECT
    DISTINCT
    TABLE_SCHEMA,
    TABLE_NAME
FROM
    INFORMATION_SCHEMA.COLUMNS

在这些表的每一个中,第一列命名为" Year"。这些值来自年份" 2011"到年" 2017":

CREATE TABLE foo (
    [Year] int,
    AnotherColumn varchar(50),
    ...
)

CREATE TABLE bar (
    [Year] int,
    SomeOtherColumn guid,
    ...
)

CREATE TABLE ...

现在,我需要计算每个表中不同年份的行数,并以下面的格式显示输出:

| TABLE_SCHEMA | TABLE_NAME | 2011                | 2012                | ... | 2017                |
|:-------------|-----------:|:-------------------:|:-------------------:|:----|:-------------------:|
| SCHEMA       | foo        | no. of rows of 2011 | no. of rows of 2012 | ... | no. of rows of 2017 | 
| SCHEMA       | bar        | no. of rows of 2011 | no. of rows of 2012 | ... | no. of rows of 2017 | 
| SCHEMA       | ...        | no. of rows of 2011 | no. of rows of 2012 | ... | no. of rows of 2017 | 

有没有人有任何建议?非常感谢!

1 个答案:

答案 0 :(得分:4)

虽然每个SQL实现都提供某种形式的值参数化,但是没有这样的工具来参数化对象标识符(例如表名,列名等) - 这意味着您必须使用动态SQL,这会引入自己的风险(即SQL注入)。

针对您的具体问题,我们可以首先尝试在没有Dynamic-SQL的情况下解决它,通过假设一组已知且固定的表来查询,然后我们可以将其转换为Dynamic-SQL,希望以安全的方式: / p>

SELECT
    'Table1' AS TableName
    [Year],
    COUNT(*) AS YearRowCount
FROM
    Table1
GROUP BY
    [Year]

UNION ALL

SELECT
    'Table2' AS TableName
    [Year],
    COUNT(*) AS YearRowCount
FROM
    Table2
GROUP BY
    [Year]

UNION ALL

...

希望你在这里看到一种模式。

到目前为止,此查询将为我们提供此表单的结果:

TableName    Year    YearRowCount
'Table1'     2017            1234
'Table1'     2016            2345
'Table1'     2015            3456
'Table1'     2014            1234
'Table1'     2013            1234
'Table1'     2011            1234
'Table2'     2017            1234
'Table2'     2016            2345
'Table2'     2015            3456
'Table2'     2013            1234
'Table2'     2012            1234
'Table2'     2011            1234
...

然后我们可以使用PIVOT将行转置为列。不幸的是,PIVOT(和UNPIVOT)确实要求您明确命名每个要转置的列 - 但如果它们具有PIVOT ALL功能或其他内容会很好。

SELECT
    tableName,
    YearRowCount,
    [2011], [2012], [2013], [2014], [2015], [2016], [2017]
FROM
(
    -- our UNION query goes here --
)
PIVOT
(
    SUM( YearRowCount )
    FOR [Year] IN ( 2011, 2012, 2013, 2014, 2015, 2016, 2017 )
)

所以现在我们知道内部查询的模式和围绕它的PIVOT语句,我们可以使它变得动态。

有三种方法可以在“for each row ...”的基础上生成动态SQL。第一种是使用CURSOR,第二种是使用某种T-SQL循环(WHILE等) - 这两种方法都采用迭代方法 - 但是有一个更具功能性和语法更简单的第三个版本。我将演示这种功能性方法。

此外,我们可以通过使用(滥用)充当FORMATMESSAGE实现的sprintf函数来避免手动字符串连接的丑陋部分。使用FORMATMESSAGE格式化字符串需要SQL Server 2016或更高版本(尽管根据我的判断,兼容性级别不需要130)。如果您运行的是早期版本,则需要使用CONCAT'foo' + @var + 'bar' - 样式连接。

我也在使用这个答案中描述的COALESCE( [aggregate] + [separator], '' ) + [value]技巧:https://stackoverflow.com/a/194887/159145 - 这是连接(聚合)行值的一种方法,虽然感觉有点难看。请记住,SQL主要关注的是无序的元组数据集(即表)的关系代数,它通常不包括视图级问题,如排序或聚合排序数据 - 这就是串联。

DECLARE @unionTemplate varchar(1024) = '
SELECT
    ''%s.%s'' AS TableName
    [Year],
    COUNT(*) AS YearRowCount
FROM
    [%s].[%s]
GROUP BY
    [Year]
'

DECLARE @unionSeparator varchar(20) = '
UNION ALL
'

DECLARE @unionQuery varchar(max)

SELECT
    @unionQuery = COALESCE( @unionQuery + @unionSeparator, '' ) + FORMATMESSAGE( @unionTemplate, SCHEMA_NAME, TABLE_NAME, SCHEMA_NAME, TABLE_NAME )
FROM
    INFORMATION_SCHEMA.TABLES
ORDER BY
    SCHEMA_NAME,
    TABLE_NAME

无论如何,此查询将生成存储在@unionQuery中的查询,所以现在我们只需要编写它......

DECLARE @pivotQuery varchar(max) = '
SELECT
    tableName,
    YearRowCount,
    [2011], [2012], [2013], [2014], [2015], [2016], [2017]
FROM
(
    %s
)
PIVOT
(
    SUM( YearRowCount )
    FOR [Year] IN ( 2011, 2012, 2013, 2014, 2015, 2016, 2017 )
)'

SET @pivotQuery = FORMATMESSAGE( @pivotQuery, @unionQuery )

...并执行它(EXEC sp_executesql比古代EXEC()更受欢迎) - 另请注意,EXEC()EXEC不同!

EXEC sp_executesql @pivotQuery

TA-哒!

较早的SQL Server版本(2014,2012,2008 R2,2008):

这些是未经测试的,但是如果您需要在早于2016年(v13.0)的SQL Server版本上运行,请尝试FORMATMESSAGE的这些替代方案:

DECLARE @unionQuery nvarchar(max)

SELECT
    @unionQuery =
        COALESCE( @unionQuery + ' UNION ALL ', '' ) +
        CONCAT(
            'SELECT ''',
            SCHEMA_NAME, '.', TABLE_NAME, '[Year],
    COUNT(*) AS YearRowCount
FROM
    [', SCHEMA_NAME, '].[', TABLE_NAME, ']
GROUP BY
    [Year]
'
    )
FROM
    INFORMATION_SCHEMA.TABLES
ORDER BY
    SCHEMA_NAME,
    TABLE_NAME

因为@pivotQuery只插入一次,所以可以使用REPLACE插入内部@unionQuery,但在处理用户提供的值时永远不会这样做,因为你打开了自己SQL注入式攻击:

SET @pivotQuery = REPLACE( @pivotQuery, '%s', @unionQuery )