【问题标题】:SQL query to count a column in all tablesSQL查询计算所有表中的列
【发布时间】:2018-02-16 20:54:23
【问题描述】:

到目前为止,我可以使用以下 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 | 

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

【问题讨论】:

  • 使用枢轴命令
  • 可能您需要光标来执行该操作。
  • 它使用 Microsoft SQL Server。我过去的同事已经创建了这些表。我需要做的只是数数。顺便说一句,我是初学者。

标签: sql sql-server tsql pivot


【解决方案1】:

虽然每个 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

...

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

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

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 语句,我们可以使其动态化。

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

此外,我们可以通过使用(滥用)作为sprintf 实现的FORMATMESSAGE 函数来避免手动字符串连接的丑陋部分。要使用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

哒哒!

较旧的 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 )

【讨论】:

  • 感谢您的说明。我试试上面的建议。在所有这些声明之后,它在执行“SET _pivotQuery = FORMATMESSAGE(_pivotQuery, _unionQuery)”时显示错误消息。错误消息是“字符串或二进制数据将被截断。[SQL State=22001, DB Errorcode=8152]”
  • @ChrisWong 您可能想要使用REPLACE 方法(在我的答案的底部)而不是最终的FORMATMESSAGE - 您的查询可能超过2000 个字符(即@987654355 @的内部限制)。
  • @ChrisWong 另外,您是否在 SSMS 中执行它?如果是这样,您可能需要使用 Step-through Debugger 来确保没有任何内容被不必要地截断。
  • 我正在使用 SQL Workbench/J。我是从数据库中提取生产数据的最终用户。
  • @ChrisWong SQL Workbench/J 似乎不包含 Microsoft SQL Server 的逐步调试器。我建议您从 Microsoft 的网站下载并安装 SSMS,然后改用它。这是一个免费软件下载:docs.microsoft.com/en-us/sql/ssms/…
猜你喜欢
  • 1970-01-01
  • 2012-06-08
  • 1970-01-01
  • 2019-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 2012-05-27
相关资源
最近更新 更多