【问题标题】:TSQL query to merge data from multiple tables that may or may not have matching rows?TSQL 查询来合并来自多个表的数据,这些表可能有也可能没有匹配的行?
【发布时间】:2013-02-11 00:07:12
【问题描述】:

例如,假设我们正在进行一项研究,学生最多可以参加 10 项不同的测试,并且数据库中的每个表都存储了所有学生对一项测试的回答。这些表在每个测试后命名为:T1、T2、...、T10。假设每个表都有一个标识每个学生的主键列“用户名”。学生可能完成了每项测试,也可能没有完成,因此每个学生的每个表中可能有也可能没有记录。

从所有表中返回所有测试数据的正确 SQL 查询是什么,每个学生一行(每个用户名一行)?我想要返回正确结果的最简单的查询。我还想在最终查询中将用户名字段合并为一个用户名字段。

澄清一下,我知道 SQL 有一个主要限制,因为它不支持选择所有列的语法除了一个或多个字段,如“select *[^ExcludeColumn1][^ExcludeColumn2] ”。为避免在最终查询中专门命名所有列,可以将所有用户名列保留在那里,只要它在开头包含一个名为 RowID 之类的合并用户名字段。

对于整体查询,一个选项是对所有十个表的用户名列执行联合,然后在所有表中选择不同的用户名,然后针对不同用户名列表执行一系列左连接全部 10 张桌子。这将导致一个非常简单的查询,其中每个左连接都在同一组不同的用户名上执行,但我想避免对不同的用户名进行单独的预先查询。 (尽管如果这是最好的选择,请告诉我)。它看起来像这样:

select * from
(select distinct coalesce(t1.Username,t2.Username,...,t10.Username) as RowID from t1,t2,t3,t4,t5,t6,t7,t8,t9,t10) distinct_usernames
left join t1 on t1.Username =  distinct_usernames.RowID
left join t2 on t2.Username =  distinct_usernames.RowID
...
left join t10 on t10.Username =  distinct_usernames.RowID

虽然这很短且易于编写,但它的效率非常低,并且需要花费数小时才能在每个包含 5000 多行的测试表上运行,因此经过调整,可以在几秒钟内运行的等效版本是:

select * from (
select distinct Username as RowID from (
select Username from t1
union all
select Username from t2
union all
...
select Username from t10
) all_usernames) distinct_usernames
left join t1 on t1.Username = distinct_usernames.RowID
left join t2 on t2.Username = distinct_usernames.RowID
...
left join t10 on t10.Username = distinct_usernames.RowID

我认为我上面的查询可能是最有效和正确的查询(只需几秒钟即可运行并返回正确的结果集),但我也认为也许可以通过某种完全连接来简化它。问题是完全连接会与两个以上的表混淆,因为如果没有预先确定用户名,每个后续表都必须将记录与前面的 any 表进行匹配,从而导致查询中每个附加表在匹配用户名时具有“[previous table count] + 1”条件。

【问题讨论】:

  • 我会使用 SQL Database Engine Tuning Adviser,看看您能获得多高的效率。我真的没有看到任何其他方式来查询您想要的结果,因此您可能只需要集中精力进行优化。
  • 是的,我认为它可能接近于最佳状态,但正如 Tim 发布的那样,我可以通过使用“union”来消除“all”和“distinct”子句。我的想法是在一组已知的 ID 上合并多个(不平衡的)表是一种非常常见的情况,应该有一个非常简单的语法。这种操作可以在 SQL 中使用自己的语法,例如“select * from merge (t1,t2,t3,t4,t5) on [PrimaryKeyColName]”,只是因为完全连接不实用,而一系列左连接是最佳的,但只有在针对不同 ID 的完整列表加入时才能正常工作。

标签: sql-server tsql outer-join


【解决方案1】:

假设Username 在每个表中都是唯一的,那么您的第二个查询将是我首先尝试的方式,只需稍作修改即可删除distinct 并简单地使用union(这意味着不同)而不是@987654327 @:

select *
from (
        select Username from t1
        union
        select Username from t2
        union
        -- ...
        select Username from t10
    ) distinct_usernames
    left join t1 on t1.Username = distinct_usernames.Username
    left join t2 on t2.Username = distinct_usernames.Username
    -- ...
    left join t10 on t10.Username = distinct_usernames.Username

从那里我会确保用户名被编入索引,甚至可能将其用作clustered index。过去,我通过在 proc 开始时将 distinct_usernames 实现为临时表(可能是索引或索引视图)来获得优化运气,但只有测试才能确定这是否值得。

完整的外连接需要一堆or 条件或coalesce 参数,尽管它可能值得在几个表上尝试一下,看看性能是否存在。我无法猜测您的查询引擎最喜欢什么。

此外,可以通过查询sys.columnsinformation_schema.columns 并使用dynamic SQL 将查询构建为字符串然后执行它来获取您想要的列名。

【讨论】:

  • 感谢您指出默认情况下“联合”本身是不同的,因此我可以省略“全部”并包装“不同”查询。我认为我们同意左连接可能比完全连接可以完成的任何事情都更直接和有效。我已经将用户名设置为主键(集群),因为我知道这会大大加快速度。发生的事情是我的老板给了我 3 个 Excel 文件,并要求我根据 SQL Server 中的用户名导入和合并它们,所以我必须根据这些文件中的内容而不是我们实际的用户数据库来建立唯一用户列表。跨度>
  • 明白了,那么你就在那里。那么有什么真正提高了性能吗?
  • 是的,但并不明显,因为在这两种情况下运行查询只需要大约 3 秒。从查询计划来看,单独使用“union”会导致单个“Merge Join (Union)”取代“Stream Aggregate (Aggregate) 和 Merge Join (Concatenation)”。此外,另一个 Merge Join 从 Concatenation 切换到 Union。在此处查看计划:i.imgur.com/UbrOqCw.png 之前和之后。
猜你喜欢
  • 1970-01-01
  • 2019-07-16
  • 2015-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-26
  • 1970-01-01
  • 2021-11-17
相关资源
最近更新 更多