Попробуйте это: 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
, мы исключаем тех, кто был вызван в окне кампании.
NB. Я использовал внутренний запрос (с псевдонимом c
) для хранения идентификаторов кампании и соответствующих им окон. В действительности вам, вероятно, понадобится таблица кампаний, содержащая ту же информацию, а не кодирование внутри самого запроса.
Надеюсь, все остальное говорит само за себя; но подтолкните меня в комментариях, если вам нужна дополнительная информация.
UPDATE
Только что понял, что вы также сказали «нет звонков - это хорошо» ... Вот измененная версия, позволяющая использовать сценарии, когда человек не был вызван.
Пример SQL Fiddle .
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 ). Здесь я поместил значение смещения каждой кампании в таблицу 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
Тем не менее, я бы рекомендовал не использовать значение по умолчанию, а убедиться, что в каждой кампании определено смещение. Т.е. предположительно у вас уже есть таблица кампаний; и поскольку это значение смещения определяется для каждой кампании, вы можете включить в эту таблицу поле для хранения этого смещения. Вместо того, чтобы оставить это как 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
.
Цель этой части - найти «самый важный» вызов для каждого пользователя. Под «самым важным» я в основном подразумеваю самое последнее, поскольку именно этого мы и добиваемся; хотя есть одна оговорка. Если пользователь участвует в двух кампаниях, одна со смещением 30 дней и одна со смещением 60 дней, у него может быть 2 вызова: 32 дня назад и 38 дней назад. Хотя вызов, поступивший 32 дня назад, является более поздним, если он включен в кампанию со смещением в 30 дней, он находится за окном, тогда как более старый вызов из 38 дней назад может быть выполнен в кампании со смещением в 60 дней, что означает, что он находится в пределах окно, так что больше интересует (то есть этот пользователь был вызван в окне кампании).
Учитывая приведенное выше требование, вот как этот код соответствует ему:
row_number()
производит число от 1, считая, для каждой строки в результатах (под) запроса. Счетчик сбрасывается в 1 для каждого partition
partition by tca.UserId
говорит, что мы разделяем по идентификатору пользователя; поэтому для каждого пользователя будет 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
Надеюсь, это объяснение имеет какой-то смысл / поможет вам понять код? К сожалению, я не могу найти способ объяснить это более кратко, чем сам код; так что, если это не имеет смысла, попробуйте поиграть с кодом и посмотреть, как это влияет на вывод, чтобы посмотреть, поможет ли это вашему пониманию.