【问题标题】:How to join to tables whose names are stored as values in another table?如何加入名称作为值存储在另一个表中的表?
【发布时间】:2017-06-25 20:27:55
【问题描述】:

我有一些表(例如[Table1][Table2][Table3] 等),每个表都以[ID] 作为主键,RecTime 作为DATETIME

我也有一个表[Files],它在varbinary(max) 列中保存文件,并引用具有其名称和ID 的其他表。

[Table2][Table3] 和其他具有不同的结构,但共享 [ID][RecTime] 列与 [Table1] 中完全相同

以下是可视化数据的快速示例。

DECLARE @Table1 as table (
      [ID] [bigint]
    , [RecTime] [datetime]
)
DECLARE @Table2 as table (
      [ID] [bigint]
    , [RecTime] [datetime]
)
DECLARE @Table3 as table (
      [ID] [bigint]
    , [RecTime] [datetime]
)

DECLARE @Files as table (
      [ID] [bigint]
    , [tblName] nvarchar(255) NULL
    , [tblID] bigint NULL
    , [BinaryData]  varbinary(max)
    /* and some other columns */
)

INSERT INTO @Table1 (
      [ID]
    , [RecTime]
)
          SELECT '1', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '2', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '3', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '4', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '5', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)

INSERT INTO @Table2 (
      [ID]
    , [RecTime]
)
          SELECT '11', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '12', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '13', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '14', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '15', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)

INSERT INTO @Table3 (
      [ID]
    , [RecTime]
)
          SELECT '21', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '22', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '23', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '24', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
UNION ALL SELECT '25', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)

INSERT INTO @Files (
      [ID]
    , [tblName]
    , [tblID]
    , [BinaryData]
)
          SELECT '1', 'Table1', '1', 0x010203040506
UNION ALL SELECT '2', 'Table1', '2', 0x010203040506
UNION ALL SELECT '3', 'Table1', '2', 0x010203040506
UNION ALL SELECT '4', 'Table1', '3', 0x010203040506
UNION ALL SELECT '5', 'Table1', '4', 0x010203040506
UNION ALL SELECT '6', 'Table1', '5', 0x010203040506
UNION ALL SELECT '7', 'Table1', '5', 0x010203040506

UNION ALL SELECT '8', 'Table2', '11', 0x010203040506
UNION ALL SELECT '9', 'Table2', '11', 0x010203040506
UNION ALL SELECT '10', 'Table2', '12', 0x010203040506
UNION ALL SELECT '11', 'Table2', '13', 0x010203040506
UNION ALL SELECT '12', 'Table2', '14', 0x010203040506
UNION ALL SELECT '13', 'Table2', '12', 0x010203040506
UNION ALL SELECT '14', 'Table2', '15', 0x010203040506

UNION ALL SELECT '15', 'Table3', '21', 0x010203040506
UNION ALL SELECT '16', 'Table3', '22', 0x010203040506
UNION ALL SELECT '17', 'Table3', '24', 0x010203040506
UNION ALL SELECT '18', 'Table3', '23', 0x010203040506
UNION ALL SELECT '19', 'Table3', '25', 0x010203040506
UNION ALL SELECT '20', 'Table3', '25', 0x010203040506
UNION ALL SELECT '21', 'Table3', '21', 0x010203040506

SELECT * FROM @Table1
SELECT * FROM @Table2
SELECT * FROM @Table3

SELECT * FROM @Files

如何将[Files] 表连接到其他表,其中NameID 派生自“[Files]”表中的值?

我需要来自[Files] 表的[BinaryData] 和来自[Files] 表中相应表引用的[RecTime]

真正的问题是[Table1][Table2][Table3] 并不是唯一的表,它们被引用[Files] 表。可以创建新表,其二进制数据必须存储在[Files]表中。

所以我正在寻找一种动态“加入”它们的方法。

附:我不是这个系统的创建者,也不能对其进行任何结构上的改变,只是试图解决这个问题。

任何帮助将不胜感激。

【问题讨论】:

  • select tblName,BinaryData,RecTime from files f join table1 t on f.tblID = t.id where tblName = 'Table1' union all select tblName,BinaryData,RecTime from files f join table2 t on f .tblID = t.id where tblName = 'Table2' union all select tblName,BinaryData,RecTime from files f join table3 t on f.tblID = t.id where tblName = 'Table3' 您需要在示例中替换为表名
  • 你试过动态查询并在最后执行了吗?
  • 我认为您的问题是模式设计之一。显然,实体之间存在未(正确)表示的关系。解决方案是否必须使用所描述的模式/数据,还是使用替代数据库设计的解决方案可以达到相同的目的?
  • 您的示例基于 3 个表,实际上可以有多少个表(即超过 100 个)?
  • 您好,请查看此链接stackoverflow.com/questions/8851801/…@Harun Prasad 回答

标签: sql sql-server sql-server-2012 dynamic-sql cursors


【解决方案1】:

一种方法是创建一个cte,它将包含所有表数据(当然,使用动态sql来创建它),然后从文件中选择左连接该cte。

这样动态sql的编写和维护都很简单,生成的sql语句也很简单:

DECLARE @SQL varchar(max) = ''
SELECT @SQL = @SQL +' UNION ALL SELECT ID, 
                                       RecTime, 
                                       '''+ tblName +''' AS TableName 
                                FROM ' + tblName 
FROM (
    SELECT DISTINCT tblName FROM files    
) x
-- replace the first 'UNION ALL' with ';WITH allTables as ('
SELECT @SQL = STUFF(@SQL, 1, 11, ';WITH allTables as (') 
       +') 
       SELECT * 
       FROM Files 
       LEFT JOIN allTables ON(tblName = TableName AND tblId = allTables.Id)'

你从中得到的 sql statemet 是:

;WITH allTables as ( 
    SELECT ID, RecTime, 'Table1' AS TableName 
    FROM Table1 
    UNION ALL  
    SELECT ID, RecTime, 'Table2' AS TableName 
    FROM Table2 
    UNION ALL  
    SELECT ID, RecTime, 'Table3' AS TableName 
    FROM Table3
 ) 
 SELECT * 
 FROM Files 
 LEFT JOIN allTables ON(tblName = TableName AND tblId = allTables.Id)

执行它:

EXEC(@SQL)

结果:

ID    tblName   tblID   BinaryData  ID    RecTime                 TableName
1     Table1    1       123456      1       31.03.2060 00:00:00   Table1
2     Table1    2       123456      2       03.12.1997 00:00:00   Table1
3     Table1    2       123456      2       03.12.1997 00:00:00   Table1
4     Table1    3       123456      3       02.07.2039 00:00:00   Table1
5     Table1    4       123456      4       17.06.1973 00:00:00   Table1
6     Table1    5       123456      5       06.12.2076 00:00:00   Table1
7     Table1    5       123456      5       06.12.2076 00:00:00   Table1
8     Table2    1       123456      NULL    NULL                    NULL
9     Table2    3       123456      NULL    NULL                    NULL
10    Table2    3       123456      NULL    NULL                    NULL
11    Table2    4       123456      NULL    NULL                    NULL
12    Table2    5       123456      NULL    NULL                    NULL
13    Table2    5       123456      NULL    NULL                    NULL
14    Table2    5       123456      NULL    NULL                    NULL
15    Table3    1       123456      NULL    NULL                    NULL
16    Table3    1       123456      NULL    NULL                    NULL
17    Table3    1       123456      NULL    NULL                    NULL
18    Table3    3       123456      NULL    NULL                    NULL
19    Table3    3       123456      NULL    NULL                    NULL
20    Table3    3       123456      NULL    NULL                    NULL
21    Table3    4       123456      NULL    NULL                    NULL

Live demo on rextester

【讨论】:

    【解决方案2】:

    一种解决方案是使用cursor@Files 表中的每一行执行一些dynamic SQL

    -- Copy table variables into temporary tables so they can be referenced from dynamic SQL
    SELECT * INTO #Table1 FROM @Table1;
    SELECT * INTO #Table2 FROM @Table2;
    SELECT * INTO #Table3 FROM @Table3;
    
    -- Create a temporary table for storing the results
    CREATE TABLE #results (
          [ID] [bigint]
        , [tblName] nvarchar(255) NULL
        , [tblID] bigint NULL
        , [BinaryData]  varbinary(max)
        , [RecTime] [datetime]
    );
    
    -- Declare placeholders and cursor
    DECLARE @ID bigint;
    DECLARE @tblName nvarchar(255);
    DECLARE @tblID bigint;
    DECLARE @BinaryData varbinary(max);
    DECLARE @RecTime datetime;
    DECLARE @sql nvarchar(max);
    DECLARE @params nvarchar(max);
    
    DECLARE files_cursor CURSOR FOR  
    SELECT ID, tblName, tblID, BinaryData
    FROM @Files
    
    -- Loop over all rows in the @Files table
    OPEN files_cursor   
    FETCH NEXT FROM files_cursor INTO @ID, @tblName, @tblID, @BinaryData
    
    WHILE @@FETCH_STATUS = 0   
    BEGIN   
       -- Find the referenced table row and extract its RecTime.
       SET @RecTime = NULL;
       SET @sql = CONCAT(
           'SELECT @RecTime = RecTime FROM #', @tblName, ' WHERE ID = ', @tblID);
       SET @params = '@RecTime datetime out';
    
       EXEC SP_EXECUTESQL @sql, @params, @RecTime out;
    
       -- Add result
       INSERT INTO #results (ID, tblName, tblID, BinaryData, RecTime)
       VALUES (@ID, @tblName, @tblID, @BinaryData, @RecTime); 
    
       FETCH NEXT FROM files_cursor INTO @ID, @tblName, @tblID, @BinaryData;
    END   
    
    -- Finalise
    CLOSE files_cursor;
    DEALLOCATE files_cursor;
    
    -- Display the results from temporary table
    SELECT * FROM #results;
    

    在线演示: http://rextester.com/DXCK86463

    【讨论】:

    • 嗨,史蒂夫,我有一个问题,为什么 RecTime 字段是 NULL 对于 Table2Table3 在光标的结果?例如,tblName Table2tblId 1 RecTime 字段值应该是 12.12.1934 00:00:00 我想是这样,也许我没有得到一些东西
    • 查看表格数据,Table1的ID为1-5,Table2的ID为11-15,Table3的ID为21-25。但是 Files 表中的 tblID 都是 1-5,这就是为什么没有找到 Table2 和 Table3 的原因。
    • 更多与性能相关的改进: - 在末尾删除临时表 - 如果要使用游标,请使用 fast_forward 只读游标 - 我将创建一个更长的 sql 文本,其中包含结果集的 union alls并且只使用游标来创建一个 sql 语句。并在最后运行该语句。在这种情况下,每行只有一个调用而不是一个调用。无论如何,干得好。
    【解决方案3】:

    这是执行上述操作的最简单的方法。不需要循环或任何东西。您需要动态代码,因为表格可以随时添加。

    注意:Files 表的示例数据中,tblId 中的数据似乎有误?

    所以我正在更改您的数据以将 ID 与相应的表相匹配。

    架构:

    CREATE TABLE Table1   (
          [ID] [bigint]
        , [RecTime] [datetime]
    )
    CREATE TABLE Table2 (
          [ID] [bigint]
        , [RecTime] [datetime]
    )
    CREATE TABLE Table3 (
          [ID] [bigint]
        , [RecTime] [datetime]
    )
    
    CREATE TABLE Files (
          [ID] [bigint]
        , [tblName] nvarchar(255) NULL
        , [tblID] bigint NULL
        , [BinaryData]  varbinary(max)
        /* and some other columns */
    )
    
    INSERT INTO Table1 (
          [ID]
        , [RecTime]
    )
              SELECT '1', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '2', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '3', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '4', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '5', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    
    INSERT INTO Table2 (
          [ID]
        , [RecTime]
    )
              SELECT '11', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '12', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '13', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '14', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '15', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    
    INSERT INTO Table3 (
          [ID]
        , [RecTime]
    )
              SELECT '21', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '22', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '23', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '24', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    UNION ALL SELECT '25', DATEADD(day, (ABS(CHECKSUM(NEWID())) % 65530), 0)
    
    INSERT INTO Files (
          [ID]
        , [tblName]
        , [tblID]
        , [BinaryData]
    )
              SELECT '1', 'Table1', '1', 0x010203040506
    UNION ALL SELECT '2', 'Table1', '2', 0x010203040506
    UNION ALL SELECT '3', 'Table1', '2', 0x010203040506
    UNION ALL SELECT '4', 'Table1', '3', 0x010203040506
    UNION ALL SELECT '5', 'Table1', '4', 0x010203040506
    UNION ALL SELECT '6', 'Table1', '5', 0x010203040506
    UNION ALL SELECT '7', 'Table1', '5', 0x010203040506
    
    UNION ALL SELECT '8', 'Table2', '11', 0x010203040506
    UNION ALL SELECT '9', 'Table2', '11', 0x010203040506
    UNION ALL SELECT '10', 'Table2', '12', 0x010203040506
    UNION ALL SELECT '11', 'Table2', '13', 0x010203040506
    UNION ALL SELECT '12', 'Table2', '14', 0x010203040506
    UNION ALL SELECT '13', 'Table2', '12', 0x010203040506
    UNION ALL SELECT '14', 'Table2', '15', 0x010203040506
    
    UNION ALL SELECT '15', 'Table3', '21', 0x010203040506
    UNION ALL SELECT '16', 'Table3', '22', 0x010203040506
    UNION ALL SELECT '17', 'Table3', '24', 0x010203040506
    UNION ALL SELECT '18', 'Table3', '23', 0x010203040506
    UNION ALL SELECT '19', 'Table3', '25', 0x010203040506
    UNION ALL SELECT '20', 'Table3', '25', 0x010203040506
    UNION ALL SELECT '21', 'Table3', '21', 0x010203040506
    

    现在是您的动态查询部分:

    DECLARE @QRY VARCHAR(MAX)='', @Tables VARCHAR(MAX)='';
    
    --Capturing List of Table names for selecting RecTime
    SELECT @Tables = @Tables+ tblName+'.RecTime,' FROM (
    SELECT DISTINCT tblName FROM Files
    )A
    
    --To remove last comma
    SELECT @Tables = SUBSTRING(@Tables,1, LEN(@Tables)-1)
    
    --Preparing Dynamic Qry
    SELECT @QRY = '
    SELECT Files.ID,Files.BinaryData
    ,COALESCE('+@Tables+') AS RecTime
    FROM Files '
    
    SELECT @QRY =@QRY+ JOINS FROM (
    SELECT  DISTINCT '
    LEFT JOIN '+ tblName + ' ON Files.tblID = '+tblName+'.ID AND Files.tblName= '''+tblName+''''
    as JOINS
    FROM Files
    )A
    
    print @QRY
    
    EXEC( @QRY)
    

    如果您想查看@Qry 包含的内容

    /*
    Print Output:
    
    SELECT Files.ID,Files.BinaryData
    ,COALESCE(Table1.RecTime,Table2.RecTime,Table3.RecTime) AS RecTime
    FROM Files 
    LEFT JOIN Table1 ON Files.tblID = Table1.ID AND Files.tblName= 'Table1'
    LEFT JOIN Table2 ON Files.tblID = Table2.ID AND Files.tblName= 'Table2'
    LEFT JOIN Table3 ON Files.tblID = Table3.ID AND Files.tblName= 'Table3'
    
    */
    

    【讨论】:

    • 太棒了!感谢您提及我关于错误数据的错误。我会更正它以备将来使用。
    【解决方案4】:

    试试下面的。

    Select res.* , F.* From Files F
    Left join
    (
    Select 'table1' as tablename, a.* From table1 a
    Union
    Select 'table2' as tablename, b.* From table2 b
    Union
    Select 'table3' as tablename, c.* From table3 c
    )Res
    On res.tablename = F.tblname
    

    【讨论】:

    • 正如我所提到的,[Table1]、[Table2] 和 [Table3] 并不是唯一的表。可以创建新表,必须将二进制数据存储在 [Files] 表中。
    • 将此内联查询作为视图,然后在文件表中添加新表时更改视图。为此要求编写动态查询可能会影响性能。
    • 我发现这很有用。谢谢。
    【解决方案5】:

    这种设计只是在 ER 中对层次结构进行建模的一种方式。您基本上有一个基于表名(即Table1Table2 等)的物理分区表。 因此,加入这些表的最简单方法是创建一个分区视图,然后加入它。

    在你的例子中,你只需要这样做:

    CREATE VIEW vmAll AS
    SELECT 'Table1' AS 'tblName', [ID], [RecTime] FROM Table1
    UNION ALL
    SELECT 'Table2' AS 'tblName', [ID], [RecTime] FROM Table2
    UNION ALL
    SELECT 'Table3' AS 'tblName', [ID], [RecTime] FROM Table3;
    GO
    

    现在只需像往常一样将它与Files 表连接起来(记得也要指定分区字段):

    例如:

    SELECT 
          F.[ID] 
        , F.[tblName]
        , F.[tblID]
        , F.[BinaryData] 
        , A.RecTime
        FROM [Files] F
    LEFT OUTER JOIN vmAll A ON
        F.[ID] = A.[ID] AND
        F.tblName = A.tblName
    

    给出预期的结果:

    注意一件重要的事情:由于它是一个分区视图,SQL Server 能够执行分区消除,从而大大加快了连接速度(这里的正确术语应该是表消除 )。

    例如之前的执行计划是:

    如果我们在分区列上添加过滤谓词:

    SELECT 
          F.[ID] 
        , F.[tblName]
        , F.[tblID]
        , F.[BinaryData] 
        , A.RecTime
        FROM [Files] F
    LEFT OUTER JOIN vmAll A ON
        F.[ID] = A.[ID] AND
        F.tblName = A.tblName
    
    WHERE A.tblName = 'Table1'
    

    我们会得到这个执行计划(注意两个表根本没有被扫描):

    当然,为了使用分区视图,您必须首先能够创建它。您可以使用如下查询以编程方式查找特定字段:

    ;WITH CTE AS
    (
        SELECT C.object_id FROM sys.columns C
        INNER JOIN sys.objects O ON C.object_id = O.object_id
        WHERE 
            (C.[name] = 'ID' OR C.[name] = 'RecTime')
            AND O.[type] = 'U'
        GROUP BY C.object_id
        HAVING COUNT(*) = 2
    )
    SELECT OBJECT_NAME(object_id), object_id FROM CTE;
    

    【讨论】:

      【解决方案6】:
      【解决方案7】:

      如果你只有几个表,那么你可以这样做,它可能会稍微快一些,因为它避免了动态 SQL。

      如果您不知道会有多少张桌子或者是否太多,请查看其他解决方案(我喜欢 Steve Chamber 的解决方案)。

      SELECT F.*, RecTime = 
            CASE tblName  
               WHEN 'Table1' THEN COALESCE(T1.RecTime, NULL)
               WHEN 'Table2' THEN COALESCE(T2.RecTime, NULL)
               WHEN 'Table3' THEN COALESCE(T3.RecTime, NULL)
               ELSE NULL
            END 
      FROM @Files F
      LEFT JOIN @Table1 T1 ON F.tblID = T1.ID
      LEFT JOIN @Table2 T2 ON F.tblID = T2.ID
      LEFT JOIN @Table3 T3 ON F.tblID = T3.ID
      

      演示:http://rextester.com/FWWD90002

      【讨论】:

        猜你喜欢
        • 2012-08-18
        • 2013-08-26
        • 1970-01-01
        • 1970-01-01
        • 2023-04-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-07
        • 2016-12-27
        相关资源
        最近更新 更多