Снежинка - Получите все даты, которые перекрываются между двумя значениями, чтобы их можно было как отображать, так и подсчитывать. - PullRequest
0 голосов
/ 28 мая 2020

Самоучка SQL пользователь здесь. Я пытаюсь найти, где два значения имеют перекрывающиеся даты, отображать эти перекрывающиеся даты и подсчитывать их. Это взгляд на предыдущий финансовый месяц. Вот с чем я сейчас работаю.

WITH FINAL AS
(
WITH OVER_LAP AS
(
WITH PRODUCT_1 AS
(SELECT ZIP_CODE
,REQUESTED_DATE
,SALE_DATE
,RETURN_DATE
,CANCEL_ORDER_DATE
FROM MAIN_DB
WHERE PRODUCT = 'PRODUCT 1'
AND FISCAL_MONTH = (SELECT DATEADD(MONTH, -1, FISCAL_MONTH)::DATE FISCAL_MONTH
                     FROM DATE_TABLE 
                     WHERE DATE = CURRENT_DATE())
)
,PRODUCT_2 AS
(SELECT ZIP_CODE
,REQUESTED_DATE
,SALE_DATE
,RETURN_DATE
FROM MAIN_DB
WHERE PRODUCT = 'PRODUCT 2'
AND FISCAL_MONTH = (SELECT DATEADD(MONTH, -1, FISCAL_MONTH)::DATE FISCAL_MONTH
                     FROM DATE_TABLE 
                     WHERE DATE = CURRENT_DATE())
)
SELECT ZIP_CODE
,(CASE WHEN PRODUCT_1.ZIP_CODE = PRODUCT_2.ZIP_CODE
         AND (PRODUCT_2.SALE_DATE >= PRODUCT_1.SALE_DATE
           AND (PRODUCT_2.SALE_DATE <= PRODUCT_1.RETURN_DATE OR PRODUCT_2.SALE_DATE <= PRODUCT_1.CANCEL_ORDER_DATE))
            THEN *<Display These Days, one day per row>* END) AS PRODUCT_OL
FROM PRODUCT_1 P1
LEFT JOIN PRODUCT_2 P2 ON P1.ZIP_CODE = P2.ZIP_CODE
WHERE P1.SALE_DATE IS NOT NULL
AND P2.SALE_DATE IS NOT NULL
)
SELECT MD.STATE
,MD.CITY
,COUNT(DISTINCT PRODUCT_OL) AS OL_COUNT
FROM MAIN_DB MD
LEFT JOIN FINAL ON MD.ZIP_CODE = FINAL.ZIP_CODE
GROUP BY MD.STATE
,MD.CITY

Я, очевидно, повесил трубку на нижней 1/3. К сожалению, мне нужно, чтобы это было сделано аналогичным образом, и я не могу просто сделать DATEDIFF, чтобы получить количество дней между PRODUCT_2.SALE_DATE и PRODUCT_1.RETURN_DATE / CANCEL_ORDER_DATE. Мы будем благодарны за любую информацию или помощь.

1 Ответ

0 голосов
/ 28 мая 2020

Чтобы найти пересечение двух диапазонов дат, вы должны:

, чтобы не пытаться работать с тем, что делают все ваши начальные 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, и я подозреваю, что мне нужна комбинация для всех продуктов ... но ваш вариант использования кажется странным ..

...