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

Chr*_*ong 6 sql t-sql sql-server pivot

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

SELECT
    DISTINCT
    TABLE_SCHEMA,
    TABLE_NAME
FROM
    INFORMATION_SCHEMA.COLUMNS
Run Code Online (Sandbox Code Playgroud)

在每个表中,第一列名为" Year".值从年" 2011"到年" 2017":

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

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

CREATE TABLE ...
Run Code Online (Sandbox Code Playgroud)

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

| 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 | 
Run Code Online (Sandbox Code Playgroud)

有没有人有什么建议?非常感谢!

Dai*_*Dai 4

虽然每个 SQL 实现都提供某种形式的值参数化,但不存在这样的工具来参数化对象标识符(例如表名、列名等)——这意味着您必须求助于 Dynamic-SQL,这会引入其自身的风险(即 SQL 注入) 。

对于您的具体问题,我们可以首先尝试在不使用 Dynamic-SQL 的情况下解决它,假设要查询一组已知且固定的表,然后我们可以将其转换为 Dynamic-SQL,希望以安全的方式:

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

...
Run Code Online (Sandbox Code Playgroud)

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

到目前为止,这个查询将为我们提供以下形式的结果:

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
...
Run Code Online (Sandbox Code Playgroud)

然后我们可以使用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 )
)
Run Code Online (Sandbox Code Playgroud)

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

有 3 种方法可以在“针对每一行...”的基础上生成动态 SQL。第一种是使用 a CURSOR,第二种是使用某种 T-SQL 循环(WHILE等) - 这两种方法都采用迭代方法 - 但有第三个版本,它功能更强大且语法更简单。我将演示这种功能性方法。

FORMATMESSAGE此外,我们可以通过使用(滥用)作为实现的函数来避免手动字符串连接的丑陋部分sprintf。要用于FORMATMESSAGE格式化字符串需要 SQL Server 2016 或更高130版本(尽管据我所知,兼容性级别不需要达到)。如果您运行的是早期版本,则需要使用CONCATor'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
Run Code Online (Sandbox Code Playgroud)

不管怎样,这个查询将生成存储在 中的查询@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 )
Run Code Online (Sandbox Code Playgroud)

...并执行它(EXEC sp_executesql优于古老的EXEC()) - 还要注意,这EXEC()EXEC

EXEC sp_executesql @pivotQuery
Run Code Online (Sandbox Code Playgroud)

哒哒!

较旧的 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
Run Code Online (Sandbox Code Playgroud)

由于@pivotQuery只插入一次,因此可以用来REPLACE插入内部@unionQuery,但在处理用户提供的值时切勿这样做,因为您容易遭受类似 SQL 注入的攻击:

SET @pivotQuery = REPLACE( @pivotQuery, '%s', @unionQuery )
Run Code Online (Sandbox Code Playgroud)