SQL Фильтрация повторяющихся строк из-за неправильного ETL

#sql #postgresql #duplicates #filtering

#sql #postgresql #дубликаты #фильтрация

Вопрос:

База данных Postgres, но любая логика SQL должна помочь.

Я извлекаю набор предложений по продажам, которые содержат данный продукт в спецификации. Я делаю это в два этапа: шаг 1, извлеките все ОТДЕЛЬНЫЕ номера котировок, которые содержат данный продукт (по номеру продукта).

На втором шаге извлеките полное предложение со всеми продуктами, перечисленными для каждого уникального номера предложения.

Пока все хорошо. Теперь сложный момент. Некоторые строки являются дубликатами, некоторые нет. Те, которые являются дубликатами (номер цитаты и версия цитаты и номер строки), могут иметь или не иметь обслуживания. Я хочу выбрать строку, которая имеет обслуживание больше 0. Повторяющиеся строки, которые я хочу исключить, — это те, которые имеют обслуживание 0. Проблема в том, что некоторые строки, которые не имеют дубликатов, имеют 0 обслуживания, поэтому я не могу просто фильтровать по обслуживанию.

Чтобы сделать это интересным, база данных хранит цитаты более 20 лет. И ребята из data scientists только что признали, что, возможно, в процессе ETL есть некоторые ошибки…

 --- step 0
--- cleanup the workspace
SET CLIENT_ENCODING TO 'UTF8';
DROP TABLE IF EXISTS product_quotes;

--- step 1
--- get list of Product Quotes
CREATE TEMPORARY TABLE product_quotes AS (
   SELECT DISTINCT master_quote_number
   FROM w_quote_line_d

   WHERE item_number IN ( << model numbers >> )
);

--- step 2
--- Now join on that list
SELECT 
d.quote_line_number,
d.item_number,
d.item_description,
d.item_quantity,
d.unit_of_measure,
f.ref_list_price_amount,
f.quote_amount_entered,
f.negtd_discount,
--- need to calculate discount rate based on list price and negtd discount (%)
CASE
    WHEN ref_list_price_amount > 0 
        THEN 100 - (ref_list_price_amount   negtd_discount) / ref_list_price_amount *100
    ELSE 0
END AS discount_percent,

f.warranty_months,
f.master_quote_number,
f.quote_version_number,
f.maintenance_months,
f.territory_wid,
f.district_wid,
f.sales_rep_wid,
f.sales_organization_wid,
f.install_at_customer_wid,
f.ship_to_customer_wid,
f.bill_to_customer_wid,
f.sold_to_customer_wid,
d.net_value,
d.deal_score,
f.transaction_date,
f.reporting_date

FROM w_quote_line_d d
INNER JOIN product_quotes pq ON (pq.master_quote_number = d.master_quote_number)

INNER JOIN w_quote_f f ON 
    (f.quote_line_number = d.quote_line_number 
    AND f.master_quote_number = d.master_quote_number 
    AND f.quote_version_number = d.quote_version_number)

WHERE d.net_value >= 0 AND item_quantity > 0
ORDER BY f.master_quote_number, f.quote_version_number, d.quote_line_number
  

Логика фильтрации повторяющихся строк выглядит следующим образом:
Для каждой пары master_quote_number / version_number проверьте, есть ли повторяющиеся номера строк. Если это так, выберите тот, у которого обслуживание> 0.

Даже в операторе CASE я не уверен, как это написать.

Мысли? База данных Postgres, но любая логика SQL должна помочь.

Ответ №1:

Я думаю, вы захотите использовать оконные функции. Одним словом, они потрясающие.

Вот запрос, который будет «дедуплицироваться» на основе ваших критериев:

 select *
from (
    select 
        * -- simplifying here to show the important parts
        ,row_number() over (
            partition by master_quote_number, version_number
            order by maintenance desc) as seqnum
    from w_quote_line_d d
    inner join product_quotes pq 
    on (pq.master_quote_number = d.master_quote_number)
    inner join w_quote_f f 
    on (f.quote_line_number = d.quote_line_number
      and f.master_quote_number = d.master_quote_number 
      and f.quote_version_number = d.quote_version_number)
) x
where seqnum = 1
  

Использование row_number() и выбранные partition by order by критерии и гарантируют, что только ОДНА строка для каждой комбинации quote_number/ version_number получит значение 1, и это будет строка с наибольшим значением в обслуживании (если ваши коллеги правы, в любом случае будет только одна со значением> 0).

Ответ №2:

Можете ли вы сделать что-то вроде…

 select
    *
from
    w_quote_line_d d
    inner join
        (
            select
                ...
                ,max(maintenance)
            from
                w_quote_line_d
            group by
                ...
        ) d1
   on 
       d1.id = d.id
       and d1.maintenance = d.maintenance;
  

Я правильно понимаю вашу проблему?

Редактировать: забыл группу by!

Комментарии:

1. Это сработает, но самосоединение почти всегда дороже, чем эквивалентный запрос с использованием оконных функций

Ответ №3:

Я не уверен, но, возможно, вы могли Group By бы использовать все остальные столбцы и использовать MAX(Maintenance) , чтобы получить только наибольшее.

Что вы думаете?