仅在所有联接行的条件为true的情况下联接其他表

时间:2018-09-02 13:27:56

标签: sql sql-server sql-server-2008

我有两个要尝试JOIN的表。

dbo.Users看起来像这样:

UserID
------
24525
5425
7676

dbo.TelemarketingCallAudits看起来像这样(日期格式dd / mm / yyyy):

UserID Date       CampaignID
------ ---------- ----------
24525  21/01/2018 1
24525  26/08/2018 1
24525  17/02/2018 1
24525  12/01/2017 2
5425   22/01/2018 1
7676   16/11/2017 2

我想返回一个表,其中包含仅在至少30天前(如果CampaignID = 1)和至少70天前(如果CampaignID = 2)调用的用户。

最终结果应如下所示(今天是18/02/09):

UserID Date       CampaignID
------ ---------- ----------
5425   22/01/2018 1
7676   16/11/2017 2
  • 请注意,由于我仅在7天之前致电了广告系列1用户24524,所以我将完全看不到该用户。

我尝试了这个简单的AND / OR条件,然后发现它仍然会返回我不希望看到的用户,因为他们确实有表明其他调用的行,而只是忽略了条件调用...显然错过了目标

如果用户在第二张表中的任何相关行都不满足该条件,我不知道如何调整用户的整体外观。

AND 
(
    internal_TelemarketingCallAudits.CallAuditID IS NULL --No telemarketing calls is fine
    OR 
    (
        internal_TelemarketingCallAudits.CampaignID = 1 --Campaign 1
        AND 
        DATEADD(dd, 75, MAX(internal_TelemarketingCallAudits.Date)) < GETDATE() --Last call occured at least 10 days ago
    ) 
    OR 
    (
        internal_TelemarketingCallAudits.CampaignID != 1 --Other campaigns
        AND 
        DATEADD(dd, 10, MAX(internal_TelemarketingCallAudits.Date)) < GETDATE() --Last call occured at least 10 days ago
    )
 )

非常感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

尝试一下:SQL Fiddle

select *
from dbo.Users u
inner join ( --get the most recent call per user (taking into account different campaign timescales)
    select tca.UserId
    , tca.CampaignId
    , tca.[Date]
    , case when DateAdd(Day,c.DaysSinceLastCall, tca.[Date]) > getutcdate() then 1 else 0 end LastCalledInWindow
    , row_number() over (partition by tca.UserId order by case when DateAdd(Day,c.DaysSinceLastCall, tca.[Date]) > getutcdate() then 1 else 0 end desc, tca.[Date] desc) r
    from dbo.TelemarketingCallAudits tca
    inner join (
        values (1, 60)
        , (2, 70)
    ) c (CampaignId, DaysSinceLastCall)
    on tca.CampaignId = c.CampaignId
) mrc
on mrc.UserId = u.UserId
and mrc.r = 1 --only accept the most recent call
and mrc.LastCalledInWindow = 0 --only include if they haven't been contacted in the last x days

我不在这里比较所有行;而是看到您对最近的通话时间感兴趣;那么您只需关心是否在X天窗口中即可。由于X天因广告系列而异,因此还会有一些额外的复杂性。因此,它不是您最关心的最近呼叫,而是最可能属于该窗口的呼叫。为了解决这个问题,我将每个用户的呼叫按窗口中的呼叫顺序排序,然后再按非用户的呼叫顺序排序;然后按这两个组中的最新优先顺序进行排序。这给了我r字段。

通过在r = 1上为每个用户进行过滤,我们只会得到最近的通话(针对广告系列窗口进行了调整)。通过过滤LastCalledInWindow = 0,我们排除了在广告系列窗口内被呼叫的人。

注意:我使用了内部查询(别名为c)来保存广告系列ID及其对应的窗口。实际上,您可能想要一个包含相同信息的Campaigns表,而不是在查询内部进行编码。

希望其他所有事情都是不言自明的;但是如果您需要任何其他信息,请在评论中稍加提及。


更新

刚刚意识到您还说过“没有电话就可以了” ...这是一个经过调整的版本,可用于未呼叫此人的情况。

SQL Fiddle Example

    select *
    from dbo.Users u
    left outer join ( --get the most recent call per user (taking into account different campaign timescales)
        select tca.UserId
        , tca.CampaignId
        , tca.[Date]
        , case when DateAdd(Day,c.DaysSinceLastCall, tca.[Date]) > getutcdate() then 1 else 0 end LastCalledInWindow
        , row_number() over (partition by tca.UserId order by case when DateAdd(Day,c.DaysSinceLastCall, tca.[Date]) > getutcdate() then 1 else 0 end desc, tca.[Date] desc) r
        from dbo.TelemarketingCallAudits tca
        inner join (
            values (1, 60)
            , (2, 70)
        ) c (CampaignId, DaysSinceLastCall)
        on tca.CampaignId = c.CampaignId
    ) mrc
    on mrc.UserId = u.UserId
    where 
    (
        mrc.r = 1 --only accept the most recent call
        and mrc.LastCalledInWindow = 0 --only include if they haven't been contacted in the last x days
    )
    or mrc.r is null --no calls at all

更新:包括默认的广告系列偏移量

要包含默认值,您可以执行以下代码(SQL Fiddle Example)。在这里,我将每个广告系列的偏移值都放在了Campaigns表中,但是创建了一个默认的广告系列,其中ID = -1用于处理未定义偏移的任何内容。我在审核表和活动表之间使用left join,以便我们从审核表中获取所有记录,而不管是否定义了活动,然后使用cross join来获取默认活动。最后,我用coalesce说“如果未定义广告系列,请使用默认广告系列”。

select *
from dbo.Users u
left outer join ( --get the most recent call per user (taking into account different campaign timescales)
    select tca.UserId
    , tca.CampaignId
    , tca.[Date]
    , case when DateAdd(Day,coalesce(c.DaysSinceLastCall,dflt.DaysSinceLastCall), tca.[Date]) > getutcdate() then 1 else 0 end LastCalledInWindow
    , row_number() over (partition by tca.UserId order by case when DateAdd(Day,coalesce(c.DaysSinceLastCall,dflt.DaysSinceLastCall), tca.[Date]) > getutcdate() then 1 else 0 end desc, tca.[Date] desc) r
    from dbo.TelemarketingCallAudits tca
    left outer join Campaigns c
    on tca.CampaignId = c.CampaignId
    cross join Campaigns dflt
    where dflt.CampaignId = -1
) mrc
on mrc.UserId = u.UserId
where 
(
    mrc.r = 1 --only accept the most recent call
    and mrc.LastCalledInWindow = 0 --only include if they haven't been contacted in the last x days
)
or mrc.r is null --no calls at all

也就是说,我建议不要使用默认值,而是要确保每个广告系列都定义了偏移量。也就是说,假设您已经有一个Campaigns表;并且由于此偏移量值是针对每个广告系列定义的,因此您可以在该表中包含一个用于保存此偏移量的字段。您可以将其设置为默认值,而不是将其保留为null来保存某些记录。因此简化了逻辑/避免了可能随后使用该值的其他地方的潜在问题。


您还询问了order by子句。没有order by 1/0;所以我认为这是一个错字。完整的语句是row_number() over (partition by tca.UserId order by case when DateAdd(Day,coalesce(c.DaysSinceLastCall,dflt.DaysSinceLastCall), tca.[Date]) > getutcdate() then 1 else 0 end desc, tca.[Date] desc) r

本文的目的是为每个用户找到“最重要的”呼叫。 “最重要的”我基本上是指最新的,因为这通常是我们所追求的。尽管有一个警告。如果某个用户属于2个广告系列的一部分,则一个偏移量为30天,一个偏移量为60天,则他们可能有2个呼叫,一个是32天前,另一个是38天前。尽管32天前的致电是较新的致电,但如果广告系列中有30天的偏移,则不在窗口内,而38天前的较早致电是在广告系列中,偏移为60天,这意味着窗口,因此会引起更多关注(即,已在广告系列窗口中调用了该用户)。

鉴于上述要求,以下是此代码满足的方式:

  • row_number()为(子)查询结果中的每一行从1开始计算一个数字。每个partition
  • 的计数器都重置为1
  • partition by tca.UserId表示我们正在按用户ID进行分区;因此,对于每个用户,将有1行row_number()返回1,然后对于该用户,每增加一行,就会返回一个连续的数字。
  • 该语句的order by部分定义了每个用户行中的哪一行获得#1,然后数字如何递增;即按顺序排列的第一行的编号为1,下一个编号为2,依此类推。
  • case when DateAdd(Day,coalesce(c.DaysSinceLastCall,dflt.DaysSinceLastCall), tca.[Date]) > getutcdate() then 1 else 0 end对于其广告系列窗口内的呼叫返回1,对于窗口外的呼叫返回0。由于我们按此结果的升序排列,也就是说,应先返回其广告系列窗口中的所有记录,然后再返回其广告系列窗口之外的任何记录。
  • 然后我们以tca.[Date] desc进行订购;也就是说,较新的通话会在以后的通话之前返回。
  • 最后,我们将此行号的输出命名为r,并在r = 1的外部查询过滤器中命名;这意味着对于每个用户,我们只需要一行,根据上面的订购标准,这是第一行;即,如果广告系列的窗口中有一行,我们将采用该行,然后以最近的那个呼叫为准(如果有,则在窗口中;如果没有,则在窗口外)。

看看子查询的输出,以更好地了解它的工作原理:SQL Fiddle

我希望解释有意义/可以帮助您理解代码?可悲的是,我找不到一种比代码本身更简洁的解释方式。因此,如果没有任何意义,请尝试使用代码并查看其如何影响输出,以帮助您理解。