#sql #sas #proc-sql
#sql #sas #proc-sql
Вопрос:
Я хочу создать столбец «Вылечено», который принимает значение 1, когда клиент ушел have1
либо на следующий день, либо в течение двух следующих дней после появления have2
. Данные have1
— это набор данных, который определяет, когда клиент вошел в определенный статус и вышел из него. have2
показывает, когда с клиентом связались.
Это код, который я сделал:
proc SQL;
create table want2 as
select b.*
, case when b.Outcome = "Answered" or
b.Outcome = "Answerphone Message Left" or
b.Outcome = "Answerphone Message Not Left" or
b.Outcome = "No Answer"
and
a.Start_date <= b.Date <= a.End_date
and
a.End_date <= b.Date 2
then 1 else 0 END as Cured
from have1 a, have2 b
where a.ID=b.ID;
quit;
Наборы данных, с которыми я работаю, являются:
data have1;
infile datalines dlmstr=' | ';
input ID Start_date :ddmmyy10. End_date :ddmmyy10.;
format date start_date date9.;
datalines;
ID | Start_date | End_date
1 | 01/01/2021 | 03/01/2021
1 | 20/01/2021 | 21/01/2021
2 | 05/01/2021 | 07/01/2021
3 | 10/01/2021 | 30/01/2021
3 | 25/01/2021 | 25/01/2021
;;;
run;
data have2;
infile datalines dlmstr=' | ';
input ID Date :ddmmyy10. Outcome ;
format Date date9.;
datalines;
ID | Date | Outcome
1 | 01/01/2021 | Answered
2 | 05/01/2021 | Asnwerphone Message Left
3 | 12/01/2021 | Answerphone Message Left
3 | 25/01/2021 | No Answer
;;;
run;
В этом случае я должен получить следующее:
ID | Date | Outcome | Cured
1 | 01/01/2021 | Answered | 1
2 | 05/01/2021 | Asnwerphone Message Left | 1
3 | 12/01/2021 | Answerphone Message Left | 0
3 | 25/01/2021 | No Answer | 1
ID1 вылечен, потому что он ушел have1
через 2 дня после 01/01
ID2 также вылечен, потому что они ушли have1
через 2 дня после 05/01
ID3 не вылечился, потому что они ушли have1
более чем через 15 дней после 12/01
ID3 вылечен, потому что они ушли have1
в тот же день
Это результат с этим кодом:
ID| Date | Outcome | Cured
1 01JAN2021 Answered 1
1 01JAN2021 Answered 0
2 05JAN2021 Asnwerph 0
3 12JAN2021 Answerph 0
3 25JAN2021 No Answe 0
3 12JAN2021 Answerph 0
3 25JAN2021 No Answe 1
Что неверно: прежде всего, он дублирует наблюдения, но он не точно выбирает те, которые должны быть 1 в вылеченном (ID 2).
Есть идеи, что может быть не так / отсутствует?
Комментарии:
1. используйте явное
join
соединение вместо неявного. особенно в вашем текущем запросе нетwhere
, и логика соединения распространяется повсюду, так сложно сказать, какова фактическая логика соединения и каков план выполнения на самом деле.2. Кстати: в SQL символьные константы заключаются в одинарные кавычки. Если SAS решит иначе: избегайте этого.
3. Пожалуйста, объясните на словах, что вы пытаетесь сделать. Что представляют собой HAVE1 и HAVE2? Что означает ВЫЛЕЧЕННЫЙ = 1?
4. Почему последняя строка должна иметь значение = 1? Для ID = 3 дата 2021-01-25 не меньше 2021-01-25. Вы хотели изменить тест на be
a.Start_date <= b.Date <= a.End_date
? Кроме того, почему последняя строка не повторяется? Один раз для каждого интервала от HAVE1 для ID = 3, который содержит эту дату?5. @Tom объяснил немного больше о моей проблеме, и вы правы, я пропустил это <= во втором условии. Я не совсем понимаю ваш вопрос о последней строке
Ответ №1:
Я подозреваю, что вы хотите:
select b.*,
(case when b.Outcome in ('Answered', 'Answerphone Message Left',
'Answerphone Message Not Left', 'No Answer') and
a.Start_date <= b.Date and
b.Date < a.End_date and
a.End_date <= b.Date 2
then 1 else 0
end) as Cured
Мне также интересно, действительно ли вам нужно декартово произведение. Обычно вам нужно join
определенное join
условие.
Комментарии:
1. спасибо, на самом деле второе условие было
a.Start_date <= b.Date <= a.End_date
таким, я думаю, что все в порядке, чтобы собрать все это вместе, верно? Я также включилwhere
в свой код оператор, который, похоже, работает нормально, за исключением того, что по какой-то причине он дублирует наблюдения.2. @amestrian . , , Это может быть нормально в SAS-native SQL. Однако это не стандартный SQL, и я не думаю, что какие-либо базы данных поддерживают этот синтаксис (ну, точнее, некоторые поддерживают синтаксис, но он не делает то, что вы ожидаете). Если вы собираетесь использовать
proc sql
, вы могли бы также использовать больше кода SQLish.3. Имеет смысл, я изменил его сейчас. Знаете ли вы, что может происходить с повторяющимися наблюдениями? Я отредактировал свой первоначальный пост, чтобы описать это
Ответ №2:
В любом программном коде (за пределами SAS и SQL) всякий раз, когда вы используете OR
with AND
, всегда разделяйте условия круглыми скобками:
CASE
WHEN (b.Outcome = "Answered" OR
b.Outcome = "Answerphone Message Left" OR
b.Outcome = "Answerphone Message Not Left" OR
b.Outcome = "No Answer")
AND
(a.Start_date <= b.Date < a.End_date)
AND
(a.End_date <= b.Date 2)
THEN 1
ELSE 0
END AS Cured
А еще лучше, используйте IN
operator для сбора значений и избегайте необходимости OR
:
CASE
WHEN b.Outcome IN ("Answered",
"Answerphone Message Left",
"Answerphone Message Not Left",
"No Answer")
AND
a.Start_date <= b.Date < a.End_date
AND
a.End_date <= b.Date 2
THEN 1
ELSE 0
END AS Cured
Комментарии:
1.
IN
Оператор и скобки были хороши! У меня все еще возникают проблемы, потому что по какой-то причине ответы дублируются. Даже после добавленияwhere
оператора в конце моего кода.
Ответ №3:
Похоже, вы хотите СГРУППИРОВАТЬ результаты и установить CURE = 1, если КАКАЯ-ЛИБО из конечных дат для этого идентификатора соответствует вашим критериям. Поэтому используйте агрегатную функцию MAX().
Я не уверен, почему вы тестируете значения РЕЗУЛЬТАТА, поскольку в вашем примере нет значений, которые не включены в ваш список значений для проверки.
data have1;
input ID Start_date :yymmdd. End_date :yymmdd.;
format Start_date End_date yymmdd10.;
datalines4;
1 2021-01-01 2021-01-03
1 2021-01-20 2021-01-21
2 2021-01-05 2021-01-07
3 2021-01-10 2021-01-30
3 2021-01-25 2021-01-25
;;;;
data have2;
input ID Date :yymmdd. Outcome $40.;
format Date yymmdd10.;
datalines4;
1 2021-01-01 Answered
2 2021-01-05 Answerphone Message Left
3 2021-01-12 Answerphone Message Left
3 2021-01-25 No Answer
4 2021-01-25 No Answer
;;;;
proc SQL;
create table want2 as
select b.id
, b.date
, b.outcome
, max(
case when (a.Start_date <= b.Date <= a.End_date)
and (a.End_date <= b.Date 2) then 1
else 0
end
) as Cured
from have1 a
right join have2 b
on a.id=b.id
group by b.id,b.date,b.outcome
;
quit;
Результат:
Obs ID Date Outcome Cured
1 1 2021-01-01 Answered 1
2 2 2021-01-05 Answerphone Message Left 1
3 3 2021-01-12 Answerphone Message Left 0
4 3 2021-01-25 No Answer 1
5 4 2021-01-25 No Answer 0
PS Отображение дат в порядке D-M-Y или M-D-Y просто запутает половину вашей аудитории.
Комментарии:
1. Спасибо. Я должен указать результат, потому что у меня есть больше, что я не включил в фрагмент. Разве нам не нужно указывать.Start_date <= b.Date <= a.End_date, потому что если нет, то как еще программа узнает, какой период искать? В любом случае я попытался запустить этот код, и он по-прежнему выдает дубликаты, я думаю, я знаю, почему это так, но не знаю, как это решить
2. Я собираюсь сделать новый пост, потому что на исходный вопрос уже был дан ответ
3. Вы пытаетесь взять что-то еще из HAVE1, чтобы вам нужно было знать, какое наблюдение сопоставить? Если вы совпадаете только по ДАТЕ между НАЧАЛОМ и КОНЦОМ, тогда ID = 3 должен иметь две выходные записи, поскольку он соответствует двум наблюдениям в HAVE1.
4. Хорошо, я понимаю, что вы имеете в виду, извините, это был плохой пример, я не понимал, что даты были наложены. В идеале этого не произойдет.
Ответ №4:
Сложная логика обычно требует круглых скобок для правильной группировки выражений.
Логический оператор SQL AND
имеет приоритет над OR
Линейно закодированный логический оператор, такой как
P OR Q or R OR S AND X AND Y AND Z
вычисляется как
P or Q or R or (((S and X) and Y) and Z)
и, как правило, необходимо исправить, чтобы
(P OR Q or R OR S) AND X AND Y AND Z
Как указано @Parfait, вы можете использовать IN
operator, когда P
Q
R
S
все выражения имеют дело с сопоставлением одной символьной переменной с несколькими различными терминами. Используйте IN:
для сопоставления префиксов терминов.