Мне нужна помощь с этим SQL-запросом. У меня есть таблица, в которую включены все счета-фактуры с 01.07.2020. В таблице есть CustomerID и InvoiceDt. Для каждого идентификатора клиента мне нужно знать, есть ли 18-месячный перерыв в транзакциях, и если был, мне нужна дата первой транзакции после самого последнего 18-месячного перерыва.
Вот как выглядит таблица, которую я использую в качестве входных данных (в своем коде я называю ее m)
Это результат, которого я хочу. У клиента 1 между первой и второй покупкой прошло 18 месяцев, поэтому мне нужна дата второй покупки. У клиента 2 нет пробелов, поэтому я не хочу, чтобы они включались в результат. Данные относятся к 01.07.2020, поэтому вначале для Клиента 3 имеется перерыв как минимум в 18 месяцев, поэтому мне нужна дата его первого счета. В своем коде я называю эту таблицу startDates.
Мне удалось сделать это без проблем на Python, но я не могу уложиться в том, чтобы сделать это только внутри группы без использования цикла в SQL.
for cid in m.CustomerID.unique():
m1 = m[m.CustomerID== cid]
m1["InvShift"] = m1.InvoiceDt.shift(1)
m1["Gap"] = ((m1.InvoiceDt - m1.InvShift)/np.timedelta64(1, 'D')/30.42)
m1["18MonthGap"] = m1.Gap >= 18
if m1["18MonthGap"].sum() > 0:
startDates.ModStartDate.loc[cid] = m1[m1["18MonthGap"]].drop_duplicates("18MonthGap", keep = "last").iloc[0].InvoiceDt
elif m1.iloc[0].InvoiceDt > pd.to_datetime("2022-1-1"):
startDates.ModStartDate.loc[cid] = m1.iloc[0].InvoiceDt
Я надеюсь, что ваши значения InvoiceDt хранятся как правильные типы дат, а не как строки. Если у клиента есть более одного 18-месячного перерыва, нужен ли вам счет после перерыва для всех таких случаев?






Вы используете оконную функцию lag(), которая, надеюсь, доступна в вашей среде.
select customer_id,invoice_dt
from
(select customer_id,invoice_dt
,lag(invoice_dt) over (partition by customer_id order by invoice_dt) prev_dt
from invoices
)
where months_between(invoice_dt,prev_dt) > 18
Если у вас нетmonths_between, у вас все равно должна быть какая-нибудь функция, которая позволит вам вычислить интервал между двумя датами. Я надеюсь, что ваши даты действительно являются датами, а не строками, какими они могут быть.
Что касается вашего требования к Клиенту 3. Это, вероятно, проще всего сделать, используя UNION для приведенного выше запроса:
select customer_id,first_invoice
from
(Select customer_id,min(invoice_dt) first_invoice
from invoices
group by customer_id
)
where first_invoice > date'2022-01-01'
Вопрос был помечен как SQL Server, но вы используете функцию months_between() и синтаксис date'...', который, по всей видимости, соответствует Oracle. В SQL Server можно использовать функцию DATEDIFF(month, ...), а литералы даты не используют префикс date. Кроме того, в качестве альтернативы UNION (или UNION ALL) можно использовать функцию ISNULL() для предоставления начальной даты по умолчанию, когда LAG() не возвращает значения. Следующие действия должны обрабатывать все случаи: where DATEDIFF(month, invoice_dt, ISNULL(prev_dt, '2022-01-01')) > 18.
Да, не заметил тег sql-server
Исправление к вышеизложенному: поменяйте местами значения даты в DATEDIFF(), чтобы получить положительную разницу. рабочий пример.
Ничего страшного, я думаю, это сработало. Функция lag выдавала отрицательные значения, поэтому мне просто пришлось изменить последнюю строку. WHERE DATEDIFF(MONTH, InvoiceDt, PrevDt) < -18
@Highflyer999 - Или поменяйте значения местами и используйте: WHERE DATEDIFF(MONTH, PrevDt, InvoiceDt) > 18. Обратите внимание, что при этом сравниваются границы и игнорируется день месяца, поэтому DATEDIFF(MONTH, '2024-03-31, 2024-04-01) и DATEDIFF(MONTH, '2024-03-01, 2024-04-30) равны 1. Альтернативой является проверка на InvoiceDt > DATEADD(month, 18, PrevDt), который распознает дни.
Вы можете использовать функцию окна LAG(), чтобы получить доступ к предыдущей дате счета. Если для клиента не существует предшествующего значения, ваши стартовые данные можно ввести с помощью функции ISNULL().
Затем вы можете добавить 18 месяцев и сравнить с текущими данными счета, чтобы проверить разницу.
-- All 18-month gaps
SELECT CustomerID, InvoiceDt
FROM (
SELECT
*,
LAG(InvoiceDt) OVER(PARTITION BY CustomerID ORDER BY InvoiceDt) PriorInvoiceDt
FROM Invoice
) IP
WHERE DATEADD(month, 18, ISNULL(PriorInvoiceDt, '2020-07-01')) <= InvoiceDt
ORDER BY CustomerID, InvoiceDt
Если вам нужна только последняя дата после перерыва, вы можете GROUP BY CustomerID и выбрать MAX(InvoiceDt).
-- Latest 18-month gap, using GROUP BY and MAX() to get latest per customer
SELECT CustomerID, MAX(InvoiceDt) AS InvoiceDt
FROM (
SELECT
*,
LAG(InvoiceDt) OVER(PARTITION BY CustomerID ORDER BY InvoiceDt) PriorInvoiceDt
FROM Invoice
) IP
WHERE DATEADD(month, 18, ISNULL(PriorInvoiceDt, '2020-07-01')) <= InvoiceDt
GROUP BY CustomerID
ORDER BY CustomerID
Однако если вы хотите включить в результаты другую информацию из строки «Счет-фактура» (не показанной в примере данных) или предыдущую дату, вы можете вместо этого использовать оконную функцию ROW_NUMBER(), чтобы назначить номера строк по убыванию, а затем отфильтровать самые последние.
-- Latest 18-month gap, using ROW_NUMBER() to get latest per customer
-- This allows access to the entire invoice row, not just the date.
SELECT CustomerID, InvoiceDt, PriorInvoiceDt
FROM (
SELECT
*,
ROW_NUMBER() OVER(PARTITION BY CustomerID ORDER BY InvoiceDt DESC) AS RN
FROM (
SELECT
*,
LAG(InvoiceDt) OVER(PARTITION BY CustomerID ORDER BY InvoiceDt) PriorInvoiceDt
FROM Invoice I
) IP
WHERE DATEADD(month, 18, ISNULL(PriorInvoiceDt, '2020-07-01')) <= InvoiceDt
) IPN
WHERE IPN.RN = 1
ORDER BY CustomerID
Наконец, если вы предпочитаете избегать множественных вложенных выборок, вы можете реорганизовать запрос, чтобы использовать CTE (общие табличные выражения).
-- Same as the above, but using CTEs instead of subqueries
;WITH InvoiceAndPriorDate AS (
SELECT
*,
LAG(InvoiceDt) OVER(PARTITION BY CustomerID ORDER BY InvoiceDt) PriorInvoiceDt
FROM Invoice I
),
InvoiceAndPriorDateNumbered AS (
SELECT
*,
ROW_NUMBER() OVER(PARTITION BY CustomerID ORDER BY InvoiceDt DESC) AS RN
FROM InvoiceAndPriorDate IP
WHERE DATEADD(month, 18, ISNULL(PriorInvoiceDt, '2020-07-01')) <= InvoiceDt
)
SELECT CustomerID, InvoiceDt, PriorInvoiceDt
FROM InvoiceAndPriorDateNumbered IPN
WHERE IPN.RN = 1
ORDER BY CustomerID
Результаты (с некоторыми дополнительными данными испытаний):
Посмотрите эту db<>fiddle для демонстрации.
пожалуйста, посмотрите функции LAG/LEAD