在 SQL 中,为啥这个 JOIN 会两次返回键列?

Posted

技术标签:

【中文标题】在 SQL 中,为啥这个 JOIN 会两次返回键列?【英文标题】:in SQL, why is this JOIN returning the key column twice?在 SQL 中,为什么这个 JOIN 会两次返回键列? 【发布时间】:2017-12-31 21:12:41 【问题描述】:

如果这是一个愚蠢的问题,我很抱歉,但我似乎无法理解它。我对 SQL 很陌生,这种行为在 R 或 Pandas 或我习惯使用的其他东西中会很奇怪。

基本上,我在两个不同的数据库中有两个表,有一个公共键user_id。我想加入所有列

SELECT * FROM db1.first_table t1 
JOIN db2.second_table t2 
ON t1.user_id = t2.user_id

很好,它有效。除了有两个(相同的)列称为user_id。这并不重要,除非我在 pyspark 中执行此操作,并且当我尝试将连接的表导出到平面文件时,我收到一个错误,即其中两列具有相同的名称。有解决方法,但我只是想知道是否有人可以解释为什么连接返回 both user_id 列。看起来它是一个内部连接,所以根据定义,列是相同的。为什么会同时返回?

作为一个附带问题,有没有一种简单的方法可以避免这种行为?

提前致谢!

【问题讨论】:

因为“select *”正在返回两个表中的所有列。如果你需要限制,那么明确定义你想要的table.columns 因为您使用的是SELECT *,这意味着它会返回连接中每个表的每一列 解决方法?使用select * 实际上不是很好的做法,您应该始终定义要检索的列。这不是一种解决方法——你应该这样做! 我唯一的问题是,这两个表实际上都有超过 1000 列。我可以把它们都列出来,但它会有点乏味,更不用说丑陋了。你能做 SELECT * EXCEPT user_id 吗? 【参考方案1】:

SELECT * 返回查询的所有表中的所有列。这包括两个 user_id 列 - 一个来自表 A,一个来自表 B。

最好的做法是列出您要专门返回的列名,但缩短列表的另一种选择是:

SELECT TableA.*, 
       TableB.col1, 
       TableB.col2, 
       ...rest of B columns except user_id

【讨论】:

这不可能是最好的方法吗?在数据科学中,像提出这个问题的人一样,我经常有 1000 多列。祝你好运手动列出所有这些... @Thomas 在 SQL 中,这是唯一的方法。有很多方法可以生成列选择,而无需手动输入,但问题是,“在 SQL 中”是否有解决此行为的方法,但没有。 this answer 怎么样? USING 而不是 ON 似乎可以解决问题? (诚​​实的问题,我对 SQL 的了解不够深入,无法知道任何潜在的陷阱) 另见this excellent and highly upvoted answer @Thomas 这些都是好点,只是USING 并非在所有 SQL 风格中都可用。鉴于pyspark 标签,我认为您的答案是正确的。【参考方案2】:

所有这些答案(除了 OP 自己写的答案)似乎都假设我们在非常小的表上进行操作,我们可以手动输入我们需要的每一列。

PySpark 中最简单的解决方案是使用 DataFrame 连接语法:

df = left_df.join(right_df, ["name"])

这不会复制列并且表现得像熊猫合并。如果没有特殊原因必须将其编写为 sql 命令,我会推荐这个。对比一下

df = left_df.join(right_df, left.name == right.name) 

其行为类似于 SQL 连接并保留两列!

这也适用于 Scala 和 R,see here。

另一种解决方案是将第二个目标列重命名为“target_dataframe2”之类的名称,然后加入 sql,然后再次简单地删除“target_dataframe2”。

【讨论】:

谢谢,这很有帮助。 spark 命令是返回两个 user_id cols 还是更像 pandas 合并(即只保留一份连接键的副本)? 这取决于你如何使用它:如果你如上所述使用它(只是列名),它的行为就像熊猫。如果您像这样提及这两个列:left_df.name == right_df.name,它会给您两个列。【参考方案3】:

您可以减少引用所需字段的字段数量。

现在你有

  SELECT *

等于

  SELECT t1.*, t2.*

也许你想要类似的东西

  SELECT t1.*, t2.field1, t2.field2 ...

【讨论】:

【参考方案4】:

这是因为您使用的是Select *。当在SELECT 之后仅定义* 时,它将返回两个表中的所有列。您必须定义列名。始终定义要显示的列。你可以这样做:

SELECT t1.userid, t1.ColumnName1, t2.ColumnName2
FROM db1.first_table t1 
INNER JOIN db2.second_table t2 ON t1.user_id = t2.user_id

*可以通过以下方式使用:

以下查询将返回两个表中的所有列:

SELECT *
FROM db1.first_table t1 
INNER JOIN db2.second_table t2 ON t1.user_id = t2.user_id

以下查询将返回 first_table 表中的所有列:

SELECT t1.*
FROM db1.first_table t1 
INNER JOIN db2.second_table t2 ON t1.user_id = t2.user_id

以下查询将返回 Second_table 表中的所有列:

SELECT t2.*
FROM db1.first_table t1 
INNER JOIN db2.second_table t2 ON t1.user_id = t2.user_id

此外,您可以通过这种方式从一个表中获取所有列,并从另一个表中获取某些列:

SELECT t1.*, t2.ColumnName
FROM db1.first_table t1 
INNER JOIN db2.second_table t2 ON t1.user_id = t2.user_id

【讨论】:

【参考方案5】:

好的,我想出了一种无需输入所有列名的方法(正如我在 cmets 中提到的,总共有大约 5k 列)。

这是特定于 pyspark 的,但我只是将列名导出到 csv 并加载它们并执行以下操作:

with open("t1_cols.csv") as data_file:    
    t1_cols = data_file.read().split('\n')
with open("t2_cols.csv") as data_file:    
    t2_cols = data_file.read().split('\n')

sql = 'SELECT t1.user_id, t1.' + ', t1.'.join(t1_cols) + \
', t2.' + ', t2.'.join(t2_cols) + ' ' + \
'FROM db1.first_table t1 JOIN db2.second_table t2 ON t1.user_id = t2.user_id'

df = sqlContext.sql(sql)

有点讨厌,但它确实有效。

另外,我接受了第一个答案,因为上述所有答案在技术上都是正确的,而且那是第一个。感谢您的帮助!

【讨论】:

【参考方案6】:

如果您只想打印一列 user_id,那么您应该使用带有 USING 关键字的内连接。

当您将USING 关键字与列名一起使用时,它会从两个表中过滤掉该公共列并仅显示一个。但是,当您将ON 与条件t1.user_id = t2.user_id 一起使用时,这只是条件中使用了同名列的巧合。

ON 也用于比较两个表的不同列,因此它不会根据条件过滤掉列。所以,如果你想在加入后只显示一次公共列,那么你应该使用USING关键字。

【讨论】:

以上是关于在 SQL 中,为啥这个 JOIN 会两次返回键列?的主要内容,如果未能解决你的问题,请参考以下文章

检查为啥记录在 SQL 中返回两次

sql join 与列上的多个条件

当我更改监视文件时,fs.watch会两次触发

在一行上返回函数中的多个主键列。 PLSQL、SQL ORACLE 开发

为啥我必须在这个插入语句中包含主键列?

为啥 Hibernate 不填充这个外键列