多次加入同一个表以每次使用过滤器检索不同的数据

Posted

技术标签:

【中文标题】多次加入同一个表以每次使用过滤器检索不同的数据【英文标题】:Joining to the same table multiple times to retrieve different data each time using a filter 【发布时间】:2020-10-17 23:03:29 【问题描述】:

我有一个看起来像这样的查询(实际的表名和列名要长得多,一团糟,实际上我需要从 FieldSummary 表中检索六个字段,但我还没有写出整个查询!)

select
    rh.RequirementDate,
    rt.RequirementType,
    f1s.Summary as F1,
    f2s.Summary as F2,
    f3s.Summary as F3
from
    RequirementHistory rh
    join RequirementTypeLU rt on rh.RequirementTypeLUID = rt.RequirementTypeLUID

    left join FieldSummary fs1 on rh.RequirementHistoryID = fs1.RequirementHistoryID -- all these identical joins, there's gotta be a better way...
    left join Field f1 on fs1.FieldID = f1.FieldID

    left join FieldSummary fs2 on rh.RequirementHistoryID = fs2.RequirementHistoryID
    left join Field f2 on fs2.FieldID = f2.FieldID

    left join FieldSummary fs3 on rh.RequirementHistoryID = fs3.RequirementHistoryID
    left join Field f3 on fs3.FieldID = f3.FieldID
where
    rh.LinkedEntityID = 3
    and f1.ScreenDescription = 'Field 1' -- this ScreenDescription lookup looks really brittle...
    and f2.ScreenDescription = 'Field 2'
    and f3.ScreenDescription = 'Field 3'

所以我使用 RequirementHistory 和 RequirementTypeLU(查找)表,并将它们加入到 FieldSummary(实际上是在字段中输入的数据,不知道为什么它被称为摘要)和字段(字段定义)表以检索字段数据,每个字段一次(实际字段名称在 where 子句中指定为 ScreenDescription)。这是某种奇怪的模块化事物,系统管理员可以定义自己的“附加字段”以链接到他们想要在系统中使用的任何实体类型。我知道它看起来很糟糕,我没有设计它! ????

现在我的问题是我没有得到任何结果。我确定我的连接有问题,但我不确定是什么。我还尝试将 ScreenDescription 比较放在字段的连接中,但这导致重复结果的数量非常可笑,随着我添加的每一对连接而扩展!我该怎么做才能使这项工作正常进行?理想情况下,我想要这样的结果:

RequirementDate  RequirementType    F1    F2   F3
10/07/1983       Someone's birthday some  data here
09/11/2001       A disaster         more  data here
01/20/2021       Recovery! At last! still more data      

其中 RequirementDate 和 RequirementType 是从各自的表中提取的,而 F1-3 是从 FieldSummary 表中提取的,方法是使用 where 子句中提供的 ScreenDescription 从 Field 中查找适当的字段。

我知道这是一个巨大的混乱,但我正在努力使用我拥有的数据库结构! ????

【问题讨论】:

and f1.ScreenDescription = 'Field 1'(和类似的)应该是左连接的ON 子句的一部分,而不是在WHERE 子句中显示为谓词。 你不需要fs2fs3等。fs1足以检索所有字段。 “现在我的问题是我没有得到任何结果。我确定我的连接有问题......” - 请参阅我的第一条评论。您的 leff 联接被静默转换为内部联接,因为您将联接谓词放在 WHERE 子句中。实际上,WHERE 子句正在删除您想要查看的行。将它们放在ON 子句中。 哦,好吧,这很有道理。我之前确实将它们放在on 子句中,但后来我得到了一堆重复的行,所以我尝试将它们放在where 子句中,这就是(呵呵)我没有的地方。 【参考方案1】:

使用CASE 逻辑根据“屏幕描述”有条件地选择“摘要”列值。此外,我更正了表别名应用不一致的一些小问题。像这样。

select
    rh.RequirementDate,
    rt.RequirementType,
    case when f1.ScreenDescription='Field 1' then fs1.Summary else null end as F1,
    case when f1.ScreenDescription='Field 2' then fs1.Summary else null end as F2,
    case when f1.ScreenDescription='Field 3' then fs1.Summary else null end as F3
from
    RequirementHistory rh
    join RequirementTypeLU rt on rh.RequirementTypeLUID = rt.RequirementTypeLUID
    left join FieldSummary fs1 on rh.RequirementHistoryID = fs1.RequirementHistoryID
    left join Field f1 on fs1.FieldID = f1.FieldID
where
    rh.LinkedEntityID=3;

【讨论】:

哇哦!这样就简单多了!非常感谢!我不知道会这么简单...?现在发生的一件奇怪的事情是,如果我只从 rhrt 中选择,我会在测试数据库中获得 42 行数据,但如果我包括前三个字段,我得到 54 行。我想不知何故我们有多次填写的字段?我必须检查FieldSummary 表是否保留了历史数据的记录,如果有,则只检索每个字段的最新记录。或者也许这只是糟糕的测试数据! ? 不,等等,这不可能......我运行了一个查询,我将在单独的评论中分享(这里的 cmets 格式很糟糕,不能换行!)会检查多次填写的字段,但没有找到任何东西...... select RequirementHistoryID, FieldID, count(*) from FieldSummary group by RequirementHistoryID, FieldID having count(*) > 1 如果您添加列 WHERE 子句,该子句引用了正在被左连接的表,然后 JOIN 将转换为内连接。尝试在 FROM 子句的 JOIN 的 ON 部分添加条件【参考方案2】:

@TheImpaler 的 cmets 给出了正确的答案提示 - 这是充实问题。

问题一:WHERE 子句过滤

(请注意,这与下面的问题 2 解决方案有些无关。但是,最好了解这里发生的事情,因为它阻碍了您当前的尝试。)

只要看看 FROM 子句,你就会说

    left join FieldSummary fs1 on rh.RequirementHistoryID = fs1.RequirementHistoryID
    left join Field f1 on fs1.FieldID = f1.FieldID

然后在 WHERE 子句中你有

    and f1.ScreenDescription = 'Field 1'

问题是查询将对 fs1 和 f1 执行 LEFT JOIN,并且作为 LEFT JOIN,它将在不匹配的字段中包含 NULL。 但是,然后您按 F1 中的字段进行过滤,然后排除那些具有 NULL 的行。它有效地将 LEFT JOIN 更改为 INNER JOIN。

相反,您需要将 LEFT JOINed 表上的 WHERE 子句移动到连接的一部分,例如,

    left join FieldSummary fs1 on rh.RequirementHistoryID = fs1.RequirementHistoryID
    left join Field f1 on fs1.FieldID = f1.FieldID
                       and f1.ScreenDescription = 'Field 1'

请注意,即使 f1 不匹配,上面也会报告来自 fs1 的值,因为它们是 LEFT JOINed。

如果您只希望 fs1 中的行也与 f1 上的过滤匹配,那么您需要类似以下内容,其中 fs1 和 f1 都按 f1 的描述进行过滤。

    left join 
        (FieldSummary fs1 
            INNER join Field f1 on fs1.FieldID = f1.FieldID
                                and f1.ScreenDescription = 'Field 1'
        ) on rh.RequirementHistoryID = fs1.RequirementHistoryID

问题 2:'必须有更好的方法'

还有 - 也包含在 @TheImpaler 的 cmets 中。

您实际上并不需要所有这些连接 - 您只需加入它们一次,例如,

select
    rh.RequirementDate,
    rt.RequirementType,
    fs.Summary,
    f.ScreenDescription
from
    RequirementHistory rh
    join RequirementTypeLU rt on rh.RequirementTypeLUID = rt.RequirementTypeLUID
    join FieldSummary fs on rh.RequirementHistoryID = fs.RequirementHistoryID
    join Field f on fs.FieldID = f.FieldId

这将为您提供一个表格,每个 rh/rt 组合最多包含 6 行,其中每行包含 FieldSummary 和字段“ScreenDescription”。

从那里,您只需将行转换为列 - 通过 PIVOT 命令或 MAX(CASE()) 或类似命令。搜索这些,例如,

https://docs.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-ver15 https://www.sqlservertutorial.net/sql-server-basics/sql-server-pivot/ Convert Rows to columns using 'Pivot' in SQL Server Efficiently convert rows to columns in sql server

【讨论】:

以上是关于多次加入同一个表以每次使用过滤器检索不同的数据的主要内容,如果未能解决你的问题,请参考以下文章

如何过滤我的表以在 Oracle 中显示结果?

我们可以使用不同的过滤条件在同一个表字段或列上多次使用相同的聚合函数吗?

MySQL基础知识和常用命令总结

获取并本地过滤 NSArray 或多次执行 fetchRequest

过滤表以省略特定行

布隆过滤