Search code examples
sqlsql-serversql-server-2012dynamic-sqlcursors

How to join to tables whose names are stored as values in another table?


Ι have some tables ( eg. [Table1], [Table2], [Table3] and so on ) with a [ID] as primary key and a RecTime as DATETIME on each.

Αlso Ι have a table [Files] that hold files in a varbinary(max) column, and refers to the other tables having their Names and IDs.

[Table2], [Table3] and others have different structure, but share the [ID] and [RecTime] column exactly as in [Table1]

Below is a quick sample to visualize data.

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

How can I join [Files] table to other tables, the Name and ID of which derive from a value in '[Files]' table ?

I need [BinaryData] from [Files] table and [RecTime] from respective table reference in [Files] table.

The real problem is that [Table1], [Table2] and [Table3] are not the only tables that are referred [Files] table. New tables can be created, for which binary data must be stored in [Files] table.

So I'm looking for a way to "join" them dynamically.

P.S. I'm not the creator of this system, and can not perform any structural change on it, but just trying to solve this problem.

Any help would be appreciated.


Solution

  • This is the simplest way to do the above. No need of looping or any thing. You need dynamic code as Tables can be add at any time.

    Note: In your sample data for Files table seems have wrong data in tblId ?

    So I am changing your data to match IDs to respective tables.

    Schema:

    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
    

    Now your Dynamic Query Part:

    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)
    

    If you want to see what @Qry contains

    /*
    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'
    
    */