如何连接到名称在另一个表中存储为值的表?

时间:2017-02-08 09:37:17

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

Ι有一些表格(例如。[Table1][Table2][Table3]等),其中[ID]为主键,RecTime为{每个{1}}。

ΑlsoΙ有一个表DATETIME,用于保存[Files]列中的文件,并引用具有其名称和ID的其他表。

varbinary(max)[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]Name从“[Files]”表中的值派生?

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

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

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

P.S。我不是这个系统的创造者,也不能对它进行任何结构性改变,只是试图解决这个问题。

任何帮助都将不胜感激。

7 个答案:

答案 0 :(得分:7)

一种方法是创建一个包含所有表数据的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

答案 1 :(得分:5)

一种解决方案是使用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

答案 2 :(得分:2)

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

注意: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'

*/

答案 3 :(得分:1)

此设计只是在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

给出预期结果:

full outer join result

注意一件重要的事情:因为它是一个分区视图,SQL Server能够执行分区消除,从而大大加快了连接速度(正确的术语应该是 table elimination < / em>的)。

例如,之前的执行计划是:

full outer join no where execution plan

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

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'

我们将获得此执行计划(请注意根本不扫描两个表):

full outer join filter predicate on partitioning column execution plan

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

;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;

答案 4 :(得分:0)

请尝试以下操作。

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

答案 5 :(得分:0)

如果你只有很少的表,那么你可以这样做,它可能会稍快一点,因为它避免了动态SQL。

如果你不能告诉我们有多少桌子或者会有太多的桌子,请看看其他解决方案(我喜欢史蒂夫室的解决方案)。

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

答案 6 :(得分:0)