Чтобы найти пересечение двух диапазонов дат, вы должны:
, чтобы не пытаться работать с тем, что делают все ваши начальные CTE ...
WITH data AS (
SELECT * FROM VALUES
(1,'2020-05-01', '2020-05-20'),
(2,'2020-05-10', '2020-05-25'),
(3,'2020-05-15', '2020-05-30'),
(4,'2020-05-24', '2020-06-14')
v(id, start_date, end_date)
)
select
d1.id,
d1.start_date,
d1.end_date,
d2.id,
d2.start_date,
d2.end_date,
greatest(d1.start_date,d2.start_date) as overlap_start,
least(d1.end_date,d2.end_date) as overlap_end
from data as d1
join data as d2
on d1.id != d2.id
and d1.start_date < d2.end_date and d1.end_date > d2.start_date
order by 1,4;
это дает вам все перестановки, которые исключая ту же идею. Если вам нужны только комбинации, замените on d1.id != d2.id
на on d1.id > d2.id
, но суть в том, что D1.start должен быть до D2.end И D1.end должен быть после d2.start для вещей с дробными значениями как отметка времени. Но если вы используете целочисленные значения или дату, где конец является включительным, тогда вы хотите разрешить D1.start> = D2.end И D1.end> = d2.start
пример вывода для исходного sql :
ID START_DATE END_DATE ID START_DATE END_DATE OVERLAP_START OVERLAP_END
1 2020-05-01 2020-05-20 2 2020-05-10 2020-05-25 2020-05-10 2020-05-20
1 2020-05-01 2020-05-20 3 2020-05-15 2020-05-30 2020-05-15 2020-05-20
2 2020-05-10 2020-05-25 1 2020-05-01 2020-05-20 2020-05-10 2020-05-20
2 2020-05-10 2020-05-25 3 2020-05-15 2020-05-30 2020-05-15 2020-05-25
2 2020-05-10 2020-05-25 4 2020-05-24 2020-06-14 2020-05-24 2020-05-25
3 2020-05-15 2020-05-30 1 2020-05-01 2020-05-20 2020-05-15 2020-05-20
3 2020-05-15 2020-05-30 2 2020-05-10 2020-05-25 2020-05-15 2020-05-25
3 2020-05-15 2020-05-30 4 2020-05-24 2020-06-14 2020-05-24 2020-05-30
4 2020-05-24 2020-06-14 2 2020-05-10 2020-05-25 2020-05-24 2020-05-25
4 2020-05-24 2020-06-14 3 2020-05-15 2020-05-30 2020-05-24 2020-05-30
Но вы спрашиваете не об этом.
Ваш вопрос был больше. Если у вас есть дата начала / окончания, как мне получить все строки между ними. Ответ: у вас есть date_table
, присоединяющееся к этому.
WITH rows_of_interest AS (
select
'important other stuff' as other,
'2020-05-01'::date as p2_sale_date,
'2020-05-10'::date as p1_return_or_cancel_date
)
SELECT roi.other,
roi.p2_sale_date,
roi.p1_return_or_cancel_date,
d.date as day_in_range
FROM rows_of_interest AS roi
JOIN date_table d
ON roi.p2_sale_date >= d.date AND roi.p1_return_or_cancel_date <= d.date
, а затем есть SQL вещи, которые я бы сделал по-другому.
Первое, что я замечаю, это fiscal_month материал может быть перемещен в CTE и присоединен для уменьшения строк примерно так:
WITH cur_fiscal_month AS (
SELECT DATEADD(MONTH, -1, fiscal_month)::DATE AS fm
FROM date_table
WHERE date = CURRENT_DATE()
), product_1 AS (
SELECT zip_code
,requested_date
,sale_date
,return_date
,cancel_order_date
FROM main_db
JOIN cur_fiscal_month cfm ON cfm.fm = fiscal_month
WHERE product = 'PRODUCT 1'
), product_2 AS (
SELECT zip_code
,requested_date
,sale_date
,return_date
FROM main_db
JOIN cur_fiscal_month cfm ON cfm.fm = fiscal_month
WHERE product = 'PRODUCT 2'
)
тогда также, когда вы используете product_1 и product_2, у вас есть одно и то же предложение WHERE для обоих из них .SALE_DATE IS NOT NULL
, таким образом это должно быть pu sh в CTE, поскольку обе эти таблицы используются только один раз. Да, если ветер в правильном направлении, Snowflake сделает это за вас. Но это делает более поздний очиститель кода ihmo.
Также в том же блоке кода вы используете псевдоним от product_1
до p1
, но в CASE используйте имя таблицы, строго говоря, если у вас есть псевдоним, следует использовать только это, и это упростило чтение кода регистра, что почти стало причиной появления псевдонимов.
И я склонен использовать все SQL токенов в CAPS, а все идентификаторы в нижнем регистре, так что есть меньше кричать .. это чисто я.
и вам не нужно втыкать свой CTE на три глубины.
Итак, учитывая все это, это будет мой SQL:
WITH cur_fiscal_month AS (
SELECT DATEADD(MONTH, -1, fiscal_month)::DATE AS fm
FROM date_table
WHERE date = CURRENT_DATE()
), product_1 AS (
SELECT zip_code
,requested_date
,sale_date
,greatest(return_date, cancel_order_date) AS great_end_date
,least(return_date, cancel_order_date) AS least_end_date
FROM main_db
JOIN cur_fiscal_month cfm ON cfm.fm = fiscal_month
WHERE product = 'PRODUCT 1'
AND sale_date IS NOT NULL
), product_2 AS (
SELECT zip_code
,requested_date
,sale_date
--,return_date
FROM main_db
JOIN cur_fiscal_month cfm ON cfm.fm = fiscal_month
WHERE product = 'PRODUCT 2'
AND sale_date IS NOT NULL
), rows_of_interesting_sales AS (
SELECT zip_code
,p2.sale_date as start_date
,great_end_date as end_date
FROM product_1 AS p1
LEFT JOIN product_2 AS p2
ON p1.zip_code = p2.zip_code
AND p2.sale_date >= p1.sale_date
AND p2.sale_date <= great_end_date
), final AS (
SELECT p.zip_code,
d.date as product_ol
FROM rows_of_interesting_sales AS p
JOIN date_table d
ON p.start_date >= d.date AND p.end_date <= d.date
)
SELECT md.state
,md.city
,COUNT(DISTINCT f.product_ol) AS ol_count
FROM main_db AS md
LEFT JOIN final AS f
ON md.zip_code = final.zip_code
GROUP by md.state, md.city;
Но меня что-то не устраивает в ваших таблицах product_1
и product_2
, и я подозреваю, что мне нужна комбинация для всех продуктов ... но ваш вариант использования кажется странным ..