如何仅使用内部联接创建左联接?
How to create left join using only an inner join?
我最近接受采访,被问及如何仅使用内部联接来创建左联接。面试官跟我说他们在公司用的一些软件不允许使用left join,所以我们必须使用inner joins来做。我被这个问题难住了,我想知道这里有人知道怎么做吗?
这是我的破解方法。它基本上是将内部联接变成交叉应用,然后在值与实际所需的联接条件不匹配时将其清零。
CREATE TABLE #testusers (userid INT)
CREATE TABLE #testusers2 (userID INT)
INSERT INTO #testusers (userid)
VALUES (1)
, (2)
, (3)
INSERT INTO #testusers2 (userid)
VALUES (2), (5), (1)
--Expecting to see one result due to straight inner join
SELECT t1.*
, t2.*
FROM #testusers t1
INNER JOIN #testusers2 t2
ON t1.userid = t2.userid
--Forcing all results by basically doing a cross apply and then NULLing out non matching values based on the join condition
SELECT distinct t1.*
, MAX(CASE
WHEN t1.userid <> t2.userID
THEN NULL
ELSE t2.userid
END) AS userid
FROM #testusers t1
INNER JOIN #testusers2 t2
ON t1.userid = t2.userid
OR 1 = 1
GROUP BY t1.userid
也适用于 VARCHAR。
CREATE TABLE #testusersnames (username varchar(10))
CREATE TABLE #testusersnames2 (username varchar(10))
INSERT INTO #testusersnames (username)
VALUES ('Tom')
, ('Jane')
, ('Dick')
INSERT INTO #testusersnames2 (username)
VALUES ('Tom'), ('Sally'), ('Mary')
SELECT t1.*
, t2.*
FROM #testusersnames t1
INNER JOIN #testusersnames2 t2
ON t1.username = t2.username
--Forcing all results by basically doing a cross apply and then NULLing out non matching values based on the join condition
SELECT distinct t1.*
, MAX(CASE
WHEN t1.username <> t2.username
THEN NULL
ELSE t2.username
END) AS username
FROM #testusersnames t1
INNER JOIN #testusersnames2 t2
ON t1.username = t2.username
OR 1 = 1
GROUP BY t1.username
这是我的尝试。在 http://sqliteonline.com/
上验证
局限性:我将不得不 "null" 删除我不想返回的列。
编辑 OP 说没有 UNION,因此这个答案不适用。
CREATE TABLE demo (id int, name varchar(32));
CREATE TABLE demo_details (demo_id int, description varchar(64));
INSERT INTO demo_details
VALUES (1,
'Woohooooooooo');
SELECT *
FROM demo d
INNER JOIN demo_details dd ON d.id = dd.demo_id
UNION
SELECT d.*,
NULL,
NULL
FROM demo d
INNER JOIN demo_details dd ON d.id <> dd.demo_id;
这个怎么样:
SELECT *
FROM Table1 t1
INNER JOIN (
SELECT * FROM Table2
UNION
SELECT {NULL for every column in Table2}
) t2
ON 1=1
WHERE t1.id = t2.id
OR t2.id IS NULL
你可以这样做:
-- dummy data
with a as (
select
1 as id,
'A' as name
union
select 2, 'B'
),
b as (
select
1 as id,
'C' as value
)
select *,
(select b.value
from
b
inner join a a1
on a1.id=b.id
and a1.id = a.id
) as bvalue
from
a
您必须为每个要左连接的值编写一个子查询,听起来很有趣!
如果您的子查询返回多个值,您可以编写一个多毛的窗口分区以确保每一行只连接一次。
我看到解决方案不是尝试在 select 语句中做一些花哨的事情,而是手动重新创建左连接。所以你创建了一个 temp table,从较大的 table 给它一个 id 值,从较小的 table 填充它,然后对其进行内部连接。它对性能不是很友好,但我想你可以将临时 table 转换为永久性 table,它始终为所有第一个 table 的 ID 保留一个值,同时只存储值从第二个 table.
--setup (optional for first time)
DROP TABLE demo;
DROP TABLE demoLeftJoin;
DROP TABLE temp;
--setup
CREATE TABLE demo(id int, lastName varchar(32), hint varchar(120));
CREATE TABLE demoLeftJoin(id int, name varchar(32), hint varchar(120));
--populate demo
INSERT INTO demo VALUES (1, 'Doe', 'nothing');
INSERT INTO demo VALUES (2, 'Doe', 'nothing2');
INSERT INTO demo VALUES (3, 'Deer', 'nothing3');
--populate demoLeftJoin
INSERT INTO demoLeftJoin VALUES (1, 'John', 'nothing');
INSERT INTO demoLeftJoin VALUES (2, 'Jane', 'nothing2');
INSERT INTO demoLeftJoin VALUES (3, 'John', 'nothing3');
INSERT INTO demoLeftJoin VALUES (4, 'Buck', 'nothing4');
INSERT INTO demoLeftJoin VALUES (5, 'Truck', 'nothing5');
--create temp table
CREATE TABLE temp(id int, lastName varchar(32), hint varchar(120));
--make sure it will match an inner join for all values from demoLeftJoin
INSERT INTO temp select id, NULL, NULL from demoLeftJoin;
--populate temp with values you want from demo
UPDATE temp
SET
lastName = (SELECT d.lastName FROM demo d WHERE temp.id = d.id),
hint = (SELECT d.hint FROM demo d WHERE temp.id = d.id)
WHERE temp.id in (SELECT d.id FROM demo d);
-- perform inner join
SELECT * FROM demoLeftJoin dlj INNER JOIN temp t on dlj.id = t.id;
您的结果应该相当于内部联接。您可能会注意到,对于您的第 4 个和第 5 个结果,提示列现在为空。我留下它是为了向您展示名称重叠的潜在问题。最简单的解决方案是确保列名不冲突,例如,在临时 table.
上将 'hint' 列命名为 'hint2'
+1 到@dfundako 的回答,如果要求是在单个 SQL 查询中完成,这就是我这样做的方式。
另一种选择是定义一个执行外部连接的 VIEW
CREATE VIEW joinedTable AS
SELECT ... FROM table1 LEFT OUTER JOIN table2 ON ...;
然后客户端可以在不违反其规则的情况下进行简单的查询:
SELECT ... FROM joinedTable;
做 select 从 Table A 到 Table B "Inner Join"
在不在 Table A
中的 Table B 中执行 select
然后将它们与多种方法结合起来。在 Sql 中,你可以做一个联合,或者你可以 select 将它们变成 table 或临时 table,然后 select 临时 table。
或者您可以在执行 sql 命令的客户端工具上组合它们。
这可能是一个技巧问题。问问题的人可能不知道他或她在做什么。我在面试问题中回答正确。但是提问的人并没有正确地知道答案,而是在寻找一个与他们有同样错误理解的候选人。这也可能是一个问题,看你如何反应。你是爆发,你是指责,还是你反应合理?
需要考虑的事情。
我最近接受采访,被问及如何仅使用内部联接来创建左联接。面试官跟我说他们在公司用的一些软件不允许使用left join,所以我们必须使用inner joins来做。我被这个问题难住了,我想知道这里有人知道怎么做吗?
这是我的破解方法。它基本上是将内部联接变成交叉应用,然后在值与实际所需的联接条件不匹配时将其清零。
CREATE TABLE #testusers (userid INT)
CREATE TABLE #testusers2 (userID INT)
INSERT INTO #testusers (userid)
VALUES (1)
, (2)
, (3)
INSERT INTO #testusers2 (userid)
VALUES (2), (5), (1)
--Expecting to see one result due to straight inner join
SELECT t1.*
, t2.*
FROM #testusers t1
INNER JOIN #testusers2 t2
ON t1.userid = t2.userid
--Forcing all results by basically doing a cross apply and then NULLing out non matching values based on the join condition
SELECT distinct t1.*
, MAX(CASE
WHEN t1.userid <> t2.userID
THEN NULL
ELSE t2.userid
END) AS userid
FROM #testusers t1
INNER JOIN #testusers2 t2
ON t1.userid = t2.userid
OR 1 = 1
GROUP BY t1.userid
也适用于 VARCHAR。
CREATE TABLE #testusersnames (username varchar(10))
CREATE TABLE #testusersnames2 (username varchar(10))
INSERT INTO #testusersnames (username)
VALUES ('Tom')
, ('Jane')
, ('Dick')
INSERT INTO #testusersnames2 (username)
VALUES ('Tom'), ('Sally'), ('Mary')
SELECT t1.*
, t2.*
FROM #testusersnames t1
INNER JOIN #testusersnames2 t2
ON t1.username = t2.username
--Forcing all results by basically doing a cross apply and then NULLing out non matching values based on the join condition
SELECT distinct t1.*
, MAX(CASE
WHEN t1.username <> t2.username
THEN NULL
ELSE t2.username
END) AS username
FROM #testusersnames t1
INNER JOIN #testusersnames2 t2
ON t1.username = t2.username
OR 1 = 1
GROUP BY t1.username
这是我的尝试。在 http://sqliteonline.com/
上验证局限性:我将不得不 "null" 删除我不想返回的列。
编辑 OP 说没有 UNION,因此这个答案不适用。
CREATE TABLE demo (id int, name varchar(32));
CREATE TABLE demo_details (demo_id int, description varchar(64));
INSERT INTO demo_details
VALUES (1,
'Woohooooooooo');
SELECT *
FROM demo d
INNER JOIN demo_details dd ON d.id = dd.demo_id
UNION
SELECT d.*,
NULL,
NULL
FROM demo d
INNER JOIN demo_details dd ON d.id <> dd.demo_id;
这个怎么样:
SELECT *
FROM Table1 t1
INNER JOIN (
SELECT * FROM Table2
UNION
SELECT {NULL for every column in Table2}
) t2
ON 1=1
WHERE t1.id = t2.id
OR t2.id IS NULL
你可以这样做:
-- dummy data
with a as (
select
1 as id,
'A' as name
union
select 2, 'B'
),
b as (
select
1 as id,
'C' as value
)
select *,
(select b.value
from
b
inner join a a1
on a1.id=b.id
and a1.id = a.id
) as bvalue
from
a
您必须为每个要左连接的值编写一个子查询,听起来很有趣!
如果您的子查询返回多个值,您可以编写一个多毛的窗口分区以确保每一行只连接一次。
我看到解决方案不是尝试在 select 语句中做一些花哨的事情,而是手动重新创建左连接。所以你创建了一个 temp table,从较大的 table 给它一个 id 值,从较小的 table 填充它,然后对其进行内部连接。它对性能不是很友好,但我想你可以将临时 table 转换为永久性 table,它始终为所有第一个 table 的 ID 保留一个值,同时只存储值从第二个 table.
--setup (optional for first time)
DROP TABLE demo;
DROP TABLE demoLeftJoin;
DROP TABLE temp;
--setup
CREATE TABLE demo(id int, lastName varchar(32), hint varchar(120));
CREATE TABLE demoLeftJoin(id int, name varchar(32), hint varchar(120));
--populate demo
INSERT INTO demo VALUES (1, 'Doe', 'nothing');
INSERT INTO demo VALUES (2, 'Doe', 'nothing2');
INSERT INTO demo VALUES (3, 'Deer', 'nothing3');
--populate demoLeftJoin
INSERT INTO demoLeftJoin VALUES (1, 'John', 'nothing');
INSERT INTO demoLeftJoin VALUES (2, 'Jane', 'nothing2');
INSERT INTO demoLeftJoin VALUES (3, 'John', 'nothing3');
INSERT INTO demoLeftJoin VALUES (4, 'Buck', 'nothing4');
INSERT INTO demoLeftJoin VALUES (5, 'Truck', 'nothing5');
--create temp table
CREATE TABLE temp(id int, lastName varchar(32), hint varchar(120));
--make sure it will match an inner join for all values from demoLeftJoin
INSERT INTO temp select id, NULL, NULL from demoLeftJoin;
--populate temp with values you want from demo
UPDATE temp
SET
lastName = (SELECT d.lastName FROM demo d WHERE temp.id = d.id),
hint = (SELECT d.hint FROM demo d WHERE temp.id = d.id)
WHERE temp.id in (SELECT d.id FROM demo d);
-- perform inner join
SELECT * FROM demoLeftJoin dlj INNER JOIN temp t on dlj.id = t.id;
您的结果应该相当于内部联接。您可能会注意到,对于您的第 4 个和第 5 个结果,提示列现在为空。我留下它是为了向您展示名称重叠的潜在问题。最简单的解决方案是确保列名不冲突,例如,在临时 table.
上将 'hint' 列命名为 'hint2'+1 到@dfundako 的回答,如果要求是在单个 SQL 查询中完成,这就是我这样做的方式。
另一种选择是定义一个执行外部连接的 VIEW
CREATE VIEW joinedTable AS
SELECT ... FROM table1 LEFT OUTER JOIN table2 ON ...;
然后客户端可以在不违反其规则的情况下进行简单的查询:
SELECT ... FROM joinedTable;
做 select 从 Table A 到 Table B "Inner Join" 在不在 Table A
中的 Table B 中执行 select然后将它们与多种方法结合起来。在 Sql 中,你可以做一个联合,或者你可以 select 将它们变成 table 或临时 table,然后 select 临时 table。
或者您可以在执行 sql 命令的客户端工具上组合它们。
这可能是一个技巧问题。问问题的人可能不知道他或她在做什么。我在面试问题中回答正确。但是提问的人并没有正确地知道答案,而是在寻找一个与他们有同样错误理解的候选人。这也可能是一个问题,看你如何反应。你是爆发,你是指责,还是你反应合理?
需要考虑的事情。