sql在join vs where子句中左连接条件

时间:2015-03-02 13:36:39

标签: sql sql-server

我有一个现有的查询来选择一些付款

我想过滤掉另一个名为ClientAlert的表中有活动警报的客户的付款

所以我想我会做一个左连接并检查ClientAlertId是否为空。

select      * 
from        payments p
left join   client c on c.clientid = p.clientid
left join   ClientAlert ca on ca.CRMId = c.CRMId and ca.ClientAlertSubjectId = 1 and ca.IsActive = 1 and (ca.ExpiryDate is null or ca.ExpiryDate > GetDate())
where       
            ca.clientalertid is null and            
            p.PaymentStatusId = 2 and 
            p.PaymentDate <= GetDate() and 
            p.PaymentCategoryId = 1

这似乎有效我认为

但我有两个问题:

  1. 是否会出现通过添加此联接而导致多次付款而不是一次付款的情况?

  2. 当我在where子句而不是join中指定以下内容时,它没有给出相同的结果,我也不明白为什么

    和ca.ClientAlertSubjectId = 1且ca.IsActive = 1且(ca.ExpiryDate为null或ExpiryDate&gt; GetDate())

  3. 我认为在where子句woiuld中的那个标准对于在连接中具有等效性

4 个答案:

答案 0 :(得分:1)

  1. 理论上如果他们可以有多个警报。但是,由于您要使用提醒排除付款,因此这应该不是问题。如果你包括它们可能是。如果这是一个问题,你应该使用“not in”子查询而不是左外连接,因为如果它不是1:1会导致重复记录。

  2. 如果与条件不匹配,则where子句中的条件会排除整行。在join子句中使用它意味着未显示连接记录,但“父”是。

答案 1 :(得分:1)

  1. 如果链接到多个客户记录,您可以获得每个付款记录的倍数。但是,基于WHERE子句,我没有看到多个ClientAlert记录如何导致重复。
  2. 当没有匹配时,
  3. LEFT JOIN记录会在其所有列中返回NULL。将ca.ClientAlertSubjectId = 1 and ca.IsActive = 1添加到WHERE子句基本上会强制该连接的行为类似于INNER JOIN,因为它必须找到匹配的记录,但我猜它永远不会返回数据,因为ClientAlertId是一个不可为空的列。所以基本上你创建了一个查询,你需要一个NULL行(表示没有警报),但该行必须包含数据。

答案 2 :(得分:1)

select      * 
from        payments p
left join   client c on c.clientid = p.clientid
left join   ClientAlert ca on ca.CRMId = c.CRMId 
and ca.ClientAlertSubjectId = 1 and ca.IsActive = 1 
and (ca.ExpiryDate is null or ca.ExpiryDate > GetDate())
where       
            ca.CRMId is null and            
            p.PaymentStatusId = 2 and 
            p.PaymentDate <= GetDate() and 
            p.PaymentCategoryId = 1
  1. 这个微小的变化将确保您永远不会得到任何重复项,如果clientid在表格客户端中是唯一的

  2. 将条件从左连接移动到where意味着您将左连接条件移动到该行的条件中。因此,如果不满足条件,则不返回该行。 左连接条件将始终包含连接左侧的行

答案 3 :(得分:0)

首先,除非有与客户关联的付款,否则第一次加入应该是内部联接。

其次,一旦客户端有适当的警报,您就会过滤掉该客户所做的所有付款,即使是在警报生效前数月或数年付款的情况。这就是你想要的吗?

第三个和最后一个(尽管这是一个很长的一个):外部联接具有以下格式:

select   ...
from     InnerTable i
left [outer] join OuterTable o
    on  <join criteria>
where   <filter criteria>;

仅对于外部联接,必须将涉及外部表的所有检查视为连接条件。在最低限度,将有

    on  o.JoinField = i.OtherJoinField

或在你的情况下

    on ca.CRMId = c.CRMId

为了保持外部联接的预期输出,其他外部表字段的检查也应该作为附加的连接标准进入ON子句:

    on  ca.CRMId = c.CRMId
    and ca.ClientAlertSubjectId = 1
    and ca.IsActive = 1
    and (ca.ExpiryDate is null or ca.ExpiryDate > GetDate())

如您所发现的那样,将这些添加检查添加到WHERE子句中将完全过滤掉外连接的所有好处,以提供与内连接相同的结果集。

但是,您可以通过稍微更改来使输出返回到外部联接结果集:

where   (ca.clientalertid is null
         or (ca.ClientAlertSubjectId = 1
             and ca.IsActive = 1
             and (ca.ExpiryDate is null or ca.ExpiryDate > GetDate()))
    and p.PaymentStatusId = 2
    and ...

但是这不会完全回到正常的外连接结果集。在您的情况下,常规外部联接将显示针对以下内容的警报数据为NULL的付款和客户端数据:

  1. 客户付款完全没有任何提醒。
  2. 客户通过提醒付款但未通过其他条件完全限定。例如,如果IsActive包含0。
  3. 然后,具有完全合格警报的客户将获得这些付款的警报数据。

    将附加条件移至WHERE子句,但采用上面显示的格式,将显示支付和客户数据,对于没有警报的付款,仅提供警报数据 的NULL,以及完全警报数据合格的警报。付款和没有完全合格警报的客户根本不会显示。

    请记住。有时候这正是你想要的。

    但这似乎不是你现在想要的。如果要查看没有警报和部分警报但过滤掉完全限定的警报,则不能将其他条件放在WHERE子句中。您的原始查询是唯一的方法。

    确实没有理由在WHERE子句中放置其他条件。它什么都没得到你。即使对于不影响输出的内部连接,也不会从中获得性能优势。将内部联接的执行计划与ON子句和WHERE子句中的其他条件进行比较。它们完全相同。外连接的相同比较是不同的。事实上,使用WHERE子句中的添加条件,执行计划与内部联接的执行计划相同。

    因此,只需使用您拥有的查询,并对您获得所需的输出感到满意。