【问题标题】:Dynamic FROM source based on query results基于查询结果的动态 FROM 源
【发布时间】:2020-11-03 18:06:54
【问题描述】:

我有一个包含多个数据库的 SQL Server 实例。每个数据库都有一个用户表。我正在尝试编写一个查询来计算每个数据库的用户记录数。我已经到了可以将所有database_nameschema_nametable_name 选择到临时表中的地步。我需要能够从该查询的结果中进行选择,但我不知道该怎么做。

DECLARE @sql nvarchar(max);

SELECT @sql = 
    (SELECT ' UNION ALL
        SELECT ' +  + quotename(name,'''') + ' as database_name,
               s.name COLLATE DATABASE_DEFAULT
                    AS schema_name,
               t.name COLLATE DATABASE_DEFAULT as table_name 
               FROM '+ quotename(name) + '.sys.tables t
               JOIN '+ quotename(name) + '.sys.schemas s
                    ON s.schema_id = t.schema_id'
    FROM sys.databases 
    WHERE state = 0
    ORDER BY [name] FOR XML PATH(''), type).value('.', 'nvarchar(max)');

SET @sql = stuff(@sql, 1, 12, '') + ' order by database_name, 
                                               schema_name,
                                               table_name';

DECLARE @allTables TABLE 
                   (
                        database_name NVARCHAR(MAX), 
                        schema_name NVARCHAR(MAX), 
                        table_name NVARCHAR(MAX)
                   )

INSERT INTO @allTables 
    EXEC (@sql)

SELECT
    *
--    ,(select count(*) from [database_name].[schema_name].[table_name] where isadmin = 0) as 'Users'
--    ,(select count(*) from [database_name].[schema_name].[table_name] where isadmin = 1) as 'Admins'
FROM
    @allTables 
WHERE
    table_name = 'C2_SEC_USERS'

这会产生以下结果集:

我已经注释掉了我需要实际计算用户数的选择部分,因为它不是有效的 SQL。是否可以根据结果集选择FROM?如果有,怎么做?

【问题讨论】:

  • 仅适用于更动态的 SQL。虽然如果你有这么多数据库和同一个对象,听起来你有一个设计缺陷。
  • 不同意你@Larnu。在多租户应用程序中,这种数据库结构很常见。
  • 在多租户应用程序中,您不需要合并数据,@Raihan。正如 OP 正在尝试的那样,这意味着他们需要这样做,这意味着设计缺陷;他们正在使用数据库来推断应该由列定义的内容。
  • 对于软件供应商来说,它可能是与所有租户中有多少用户正在使用系统相关的有用信息的一部分。

标签: sql sql-server dynamic-sql


【解决方案1】:

你快到了。现在你需要做两件事 -

首先在表变量@allTables 中添加一个标识字段,如下所示

DECLARE @allTables TABLE
(
    id  int identity(1,1),
    database_name NVARCHAR(MAX), 
    schema_name NVARCHAR(MAX), 
    table_name NVARCHAR(MAX)
 )

二次构建动态查询并执行

DECLARE 
    @CurrentId      AS INT = 0,
    @CurrentDb      AS NVARCHAR(MAX),
    @CurrentSchema  AS NVARCHAR(MAX);

SET @sql = '';

WHILE (EXISTS(SELECT id FROM @allTables WHERE table_name = 'C2_SEC_USERS' AND id > @CurrentId))
BEGIN
    SELECT TOP 1 @CurrentId = id, @CurrentDb = [database_name], @CurrentSchema = [schema_name] FROM @allTables WHERE table_name = 'C2_SEC_USERS' AND id > @CurrentId
    SET @sql += CONCAT('SELECT ''', @CurrentDb, ''' ''database'', COUNT(*) ''User Count'' FROM ', @CurrentDb,'.',@CurrentSchema,'.C2_SEC_USERS');
    SET @sql += '
';
END;

EXEC (@sql)

【讨论】:

    【解决方案2】:

    我最终通过使用光标解决了这个问题

    declare @sql nvarchar(max);
    declare @allTables TABLE (database_name NVARCHAR(MAX), schema_name NVARCHAR(MAX), table_name NVARCHAR(MAX))
    select @sql = 
        (select ' UNION ALL
            SELECT ' +  quotename(name,'''') + ' as database_name,
                   s.name COLLATE DATABASE_DEFAULT
                        AS schema_name,
                   t.name COLLATE DATABASE_DEFAULT as table_name 
                   FROM '+ quotename(name) + '.sys.tables t
                   JOIN '+ quotename(name) + '.sys.schemas s
                        on s.schema_id = t.schema_id'
        from sys.databases
        where state=0
        order by [name]
        for xml path(''), type).value('.', 'nvarchar(max)');
    set @sql = stuff(@sql, 1, 12, '') + ' order by database_name, schema_name, table_name';
    
    insert @allTables
    exec (@sql)
    
    declare @userCounts TABLE (database_name NVARCHAR(MAX),
        users int,
        admins int)
    DECLARE @dbName VARCHAR(MAX)
    DECLARE @schemaName VARCHAR(MAX)
    DECLARE @tableName VARCHAR(MAX)
    DECLARE @countSql NVARCHAR(MAX)
    DECLARE tables_cursor CURSOR FOR SELECT database_name, schema_name, table_name
    FROM @allTables
    where table_name = 'C2_SEC_USERS' and database_name != 'master'
    
    OPEN tables_cursor
    FETCH NEXT FROM tables_cursor INTO @dbName, @schemaName, @tableName
    WHILE @@FETCH_STATUS = 0 BEGIN
        set @countSql = 'Select ''' + @dbName + ''' as database_name, (select count(*) from [' + @dbName + '].dbo.C2_SEC_USERS where isadmin = 0 and isinactive = 0) as users, (select count(*) from [' + @dbName + '].dbo.C2_SEC_USERS where isadmin = 1 and isinactive = 0) as admins'
        INSERT INTO @userCounts
        exec (@countSql)
        FETCH NEXT FROM tables_cursor INTO @dbName, @schemaName, @tableName;
    END;
    CLOSE tables_cursor;
    DEALLOCATE tables_cursor;
    select * from @userCounts;
    

    【讨论】:

    • 不知道为什么你对光标感兴趣,因为你有更简单和更优化的方法来做同样的事情。我想向您推荐此处提供的其他答案。
    猜你喜欢
    • 2015-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多