Left Join with Dynamic Table Name Derived from Column

Left join with dynamic table name derived from column

Either way, you need dynamic SQL.

Table name as given parameter

CREATE OR REPLACE FUNCTION foo(_number int)
RETURNS TABLE (cpa int, nr text, vym text) AS -- adapt to actual data types!
$func$
BEGIN
RETURN QUERY EXECUTE format(
'SELECT t.cpa, substring(t.ku,'[0-9]+'), p.vym
FROM public."table_data_C" t
LEFT JOIN %s p USING (cpa)'
, 'pa' || _number
);
END
$func$ LANGUAGE plpgsql;

Call:

SELECT * FROM foo(456887)

Generally, you would sanitize table names with format ( %I ) to avoid SQL injection. With just an integer as dynamic input that's not necessary. More details and links in this related answer:

INSERT with dynamic table name in trigger function

Data model

There may be good reasons for the data model. Like partitioning / sharding or separate privileges ...

If you don't have such a good reason, consider consolidating multiple tables with identical schema into one and add the number as column. Then you don't need dynamic SQL.

Consider inheritance. Then you can add a condition on tableoid to only retrieve rows from a given child table:

SELECT * FROM parent_table
WHERE tableoid = 'pa456887'::regclass

Be aware of limitations for inheritance, though. Related answers:

  • Get the name of a row's source table when querying the parent it inherits from
  • Select (retrieve) all records from multiple schemas using Postgres

Name of 2nd table depending on value in 1st table

Deriving the name of the join table from values in the first table dynamically complicates things.

For only a few tables

LEFT JOIN each on tableoid. There is only one match per row, so use COALESCE.

SELECT t.*, t.tbl, COALESCE(p1.vym, p2.vym, p3.vym) AS vym
FROM (
SELECT cpa, ('pa' || substring(ku,'[0-9]+'))::regclass AS tbl
FROM public."table_data_C"
-- WHERE <some condition>
) t
LEFT JOIN pa456887 p1 ON p1.cpa = t.cpa AND p1.tableoid = t.tbl
LEFT JOIN pa456888 p2 ON p2.cpa = t.cpa AND p2.tableoid = t.tbl
LEFT JOIN pa456889 p3 ON p3.cpa = t.cpa AND p3.tableoid = t.tbl

For many tables

Combine a loop with dynamic queries:

CREATE OR REPLACE FUNCTION foo(_number int)
RETURNS TABLE (cpa int, nr text, vym text) AS
$func$
DECLARE
_nr text;
BEGIN
FOR _nr IN
SELECT DISTINCT substring(ku,'[0-9]+')
FROM public."table_data_C"
LOOP
RETURN QUERY EXECUTE format(
'SELECT t.cpa, _nr, p.vym
FROM public."table_data_C" t
LEFT JOIN %I p USING (cpa)
WHERE t.ku LIKE (_nr || '%')'
, 'pa' || _nr
);
END LOOP;

END
$func$ LANGUAGE plpgsql;

MYSQL LEFT JOIN with table name dependent by table column value

It's not clear what the expected output is but here is my go

SELECT t.id,
CASE t.id_feature
WHEN 2 THEN (SELECT size FROM feature2 f WHERE f.id = t.id)
WHEN 4 THEN (SELECT radius FROM feature4 f WHERE f.id = t.id)
END AS feature_value
FROM Table1 t

I have a slightly different naming here but this will output the id and the value column for the corresponding id_feature table

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

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'

*/

SQL apply join dynamically based on column names mentioned in other table

You can build a dynamic SQL query quite neatly by using string aggregation:

Try to keep clear about which bits are static and which dynamic. And test the generated code by using PRINT @sql

DECLARE @sql nvarchar(max) = N'

SELECT *
FROM temp s
' +
(
SELECT STRING_AGG(CAST(
N'JOIN ' + QUOTENAME(SCHEMA_NAME(t.schema_id)) + N'.' + QUOTENAME(t.name) + N' AS T' + CAST(t.object_id AS nvarchar(10)) + N'
ON s.' + QUOTENAME(j.SourceKey) + N' = T' + CAST(t.object_id AS nvarchar(10)) + N'.' + QUOTENAME(c.name)
AS nvarchar(max)), N'
') WITHIN GROUP (ORDER BY j.Id)
FROM sys.tables t
JOIN sys.columns c ON c.object_id = t.object_id
JOIN joininfo j ON OBJECT_ID(j.[Table]) = t.object_id
AND j.TargetKey = c.name
);

PRINT @sql; -- for testing

EXECUTE sp_executesql @sql;

If your version of SQL Server does not support STRING_AGG you can use FOR XML PATH('') instead.

MySQL join tables where table name is a field of another table

If you want all rows (bulk output) and not one row at a time, the below should be fast and also the output for all rows will contain all columns.

Lets consider below to be the fields of the tables.
obj_mobiles - ID | M1 | M2
obj_tablets - ID | T1 | T2
obj_computers - ID | C1 | C2
objects - ID | type | name | etc.,

Select objects.*, typestable.*
from (
select ID as oID, "mobile" as otype, M1, M2, NULL T1, NULL T2, NULL C1, NULL C2 from obj_mobiles
union all
select ID as oID, "tablet" as otype, NULL, NULL, T1, T2, NULL, NULL from obj_tablets
union all
select ID as oID, "computer" as otype, NULL, NULL, NULL, NULL, C1, C2 from obj_computers) as typestable
left join objects on typestable.oID = objects.ID and typestable.otype = objects.type;

+------+--------------------+----------+------+----------+------+------+------+------+------+------+
| ID | name | type | ID | type | M1 | M2 | T1 | T2 | C1 | C2 |
+------+--------------------+----------+------+----------+------+------+------+------+------+------+
| 1 | Samsung Galaxy s2 | mobile | 1 | mobile | 1 | Thin | NULL | NULL | NULL | NULL |
| 2 | Samsung Galaxy Tab | tablet | 2 | tablet | NULL | NULL | 0.98 | 10 | NULL | NULL |
| 3 | Dell Inspiron | computer | 3 | computer | NULL | NULL | NULL | NULL | 4.98 | 1000 |
+------+--------------------+----------+------+----------+------+------+------+------+------+------+

The table are create as below.

mysql> create table objects (ID int, name varchar(50), type varchar (15));
Query OK, 0 rows affected (0.05 sec)
mysql> insert into objects values (1, "Samsung Galaxy s2", "mobile"), (2, "Samsung Galaxy Tab", "tablet"), (3, "Dell Inspiron", "computer");
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0

mysql> create table obj_mobiles (ID int, M1 int, M2 varchar(10));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into obj_mobiles values (1, 0.98, "Thin");
Query OK, 1 row affected (0.00 sec)

mysql> create table obj_tablets (ID int, T1 float, T2 int(10));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into obj_tablets values (2, 0.98, 10);
Query OK, 1 row affected (0.00 sec)

mysql> create table obj_computers (ID int, C1 float, C2 int(10));
Query OK, 0 rows affected (0.03 sec)
insert into obj_computers values (3, 4.98, 1000);

also to confirm the datatypes of the columns are same as the original columns, the result is saved into a table and datatypes are checked below.

create table temp_result as
Select objects.*, typestable.*
from (
select ID as oID, "mobile" as otype, M1, M2, NULL T1, NULL T2, NULL C1, NULL C2 from obj_mobiles
union all
select ID as oID, "tablet" as otype, NULL, NULL, T1, T2, NULL, NULL from obj_tablets
union all
select ID as oID, "computer" as otype, NULL, NULL, NULL, NULL, C1, C2 from obj_computers) as typestable
left join objects on typestable.oID = objects.ID and typestable.otype = objects.type;

mysql> desc temp_result;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| ID | int(11) | YES | | NULL | |
| name | varchar(50) | YES | | NULL | |
| type | varchar(15) | YES | | NULL | |
| oID | int(11) | YES | | NULL | |
| otype | varchar(8) | NO | | | |
| M1 | int(11) | YES | | NULL | |
| M2 | varchar(10) | YES | | NULL | |
| T1 | float | YES | | NULL | |
| T2 | int(11) | YES | | NULL | |
| C1 | float | YES | | NULL | |
| C2 | int(11) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
11 rows in set (0.00 sec)

MYSQL query using variable as table name in LEFT JOIN

Table names, as well as column names, can't be dynamic in an SQL query. So you have to apply your logic programmatically, using 2 queries, or with a stored procedure, see an example here: http://forums.mysql.com/read.php?98,126506,126598#msg-126598

Dynamic columns from a LEFT JOIN in SQL Server

While this can be done with PIVOT and/or CROSS APPLY, I would just use conditional aggregation, which is an efficient and cross-database solution:

select
i.id,
i.folio,
max(case when a.name = 'Order Number' then ai.value end) OrderNumber,
max(case when a.name = 'Branch' then ai.value end) Branch
from Invoice i
inner join AdditionalInvoice ai on ai.id_invoice = i.id
inner join Additional a on a.id = ai.id_additional
group by i.id, i.folio

Note: rereading your question I notice a mention of dynamic columns at the end.

Please note that the above query (or any other pure-sql solution, PIVOT and CROSS APPLY included) only works for a fixed set of columns. This is because a pure SQL query can only returned a pre-defined, fixed set of columns. If you want a dynamic solution, then you need dynamic SQL (that is: prepare a query string, then execute it).

On the other hand, the above statement can easily be extended to handle more values from the additional table, by adding max(case when ... then ... end) expressions.



Related Topics



Leave a reply



Submit