【问题标题】:SQL Query against multiple databases针对多个数据库的 SQL 查询
【发布时间】:2021-09-21 19:07:59
【问题描述】:

我正在尝试对同一服务器上的多个数据库运行查询。

如果数据库是在某个日期之后创建的,我需要根据数据库中第三个表的条件从数据库中的两个表中提取所有值。

我有一个查询要查找数据库的创建时间:

SELECT *
FROM sys.databases
WHERE STATE = 0 --ignores offline databases
AND database_id > 4 --does not include master, model, msdb, tempdb
AND create_date > CONVERT(datetime, '2021-01-01')

而我需要在每个数据库上运行的查询一般如下:

SELECT *
FROM Table1
INNER JOIN Table3 ON Table3.Column6=Table1.Column2
AND Table3.Column3='Value1'
AND Table3.Column4='Value2'
INNER JOIN Table2 ON Table3.Column6=Table2.Column2

我确实找到了this question,这基本上是我想做的,但是当我查看 INFORMATION_SCHEMA.TABLES 时,TABLE_CATALOG 列没有我想查询的表名。我想我可以尝试从上面的 sys.databases 表中提取名称,所以我尝试将其修改为:

DECLARE @cmd VARCHAR(max) = N''

SELECT @cmd += COALESCE(@cmd + ' UNION ALL ', '') + 'SELECT *
    FROM [' + name + '].dbo.Table1
    INNER JOIN [' + name + '].dbo.Table3 on Table3.Column6=Table1.Column2
    AND Table3.Column3= ''Value1''
    AND Table3.Column4=''Value2''
    INNER JOIN [' + name + '].dbo.Table2 on Table3.Column6=Table2.Column2'
FROM sys.databases
WHERE STATE = 0
AND database_id>4
AND create_date>CONVERT(datetime,'2021-08-26')

SET @cmd = STUFF(@cmd, CHARINDEX('UNION ALL', @cmd), 10, '')

PRINT @cmd

EXEC(@cmd)

但是当我使用早于 2021-08-26 的日期运行它时(它会抓取 5 个以上的表),我会收到内存错误。我需要至少运行到 4 月初(最好到年初),这将获得大约 500 张桌子。

在 SQL 中针对多个数据库运行查询的推荐方法是什么?

【问题讨论】:

  • I get a memory error. -> 这里有很多种可能性。你可以再详细一点吗?另外,您为什么要在 sys.databases 中查找表名?它包含数据库名称,而不是表名称。您是要从多个数据库中存在的 Table1、Table2 等中获取数据,还是要从单个数据库中的多个不同表中获取数据?请更具体地说明您的要求(日期,或者有 500 张桌子等,基本上是无关紧要的细节)。什么版本的 SQL Server?
  • 这听起来像是设计而不是查询的问题。您是否会随着时间的推移创建同一个数据库的多个副本?
  • 内存错误为“消息 701,级别 17,状态 123,第 1 行资源池“默认”中的系统内存不足,无法运行此查询。”是的,那是一个错字。在 sys.databases 中查找数据库名称。这 3 个表在不同的数据库中都具有相同的名称。 SQL Server 2018 版
  • 您是指 SQL Server 2017 吗? 2018 听起来像是客户端工具 Management Studio,而不是服务器版本 (SELECT @@VERSION;)。
  • 是的,抱歉,这是 Server 2016 版和 Management Studio 2018 版。

标签: sql sql-server tsql


【解决方案1】:

我的建议是不要尝试构建一个庞大的UNION ALL 动态 SQL 语句,而是构建一个#temp 表来保存每个输出的结果,然后发送相同的字符串会容易得多 /em> 到每个数据库:

CREATE TABLE #hold(dbname sysname, Column1 {data type}, ...);

DECLARE @sql nvarchar(max), @exec nvarchar(1024);

SET @sql = N'SELECT DB_NAME(), *
  FROM dbo.Table1
  INNER JOIN dbo.Table3 
   ON Table3.Column6 = Table1.Column2
  AND Table3.Column3 = ''Value1''
  AND Table3.Column4 = ''Value2''
  INNER JOIN dbo.Table2 
   ON Table3.Column6 = Table2.Column2;';

DECLARE @dbname sysname, @c cursor;

SET @c = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR
  SELECT name FROM sys.databases
  WHERE state = 0 -- ignores offline databases
    AND database_id > 4 -- does not include master, model, msdb, tempdb
    AND create_date > CONVERT(datetime, '20210101');

OPEN @c;

FETCH NEXT FROM @c INTO @dbname;

WHILE @@FETCH_STATUS = 0
BEGIN
  SET @exec = QUOTENAME(@dbname) + N'.sys.sp_executesql';
  INSERT #hold EXEC @exec @sql;
  FETCH NEXT FROM @c INTO @dbname;
END;

SELECT * FROM #hold;

您还可以考虑投资sp_ineachdb,这是我编写的一个程序,用于帮助简化在每个数据库的上下文中运行相同的命令。

【讨论】:

  • 我想知道您是否有任何资源可以阅读光标的工作原理。我一定会检查您编写的 sp_ineachdb 过程。快速附录。是否可以将 dbname 添加到插入语句中?我不确定我是否完全理解修改它的工作原理
  • @kennyh 我将数据库名称添加到#temp 表和 SQL 输出中。游标只是遍历每个与条件匹配的数据库名称,然后执行EXEC dbname.sys.sp_executesql @sql; - 这样您就可以在该数据库的上下文中运行 SQL,因此您无需使用方括号并手动为每个具有潜在不安全名称的表。
  • 太棒了。谢谢,亚伦!
猜你喜欢
  • 2011-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-13
  • 2013-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多