2016-04-18 6 views
2

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

purchase_id | cust_id | date | address | description 
----------------------------------------------------------- 
1   | 5  | jan | address1 | desc1 
2   | 6  | jan | address2 | desc2 
3   | 5  | feb | address1 | desc3 
4   | 6  | feb | address2 | desc4 
5   | 5  | mar | address3 | desc5 
6   | 5  | mar | address3 | desc6 
7   | 5  | apr | address1 | desc7 
8   | 6  | may | address4 | desc8 

Обратите внимание, что клиенты могут «двигаться назад» к предыдущему адресу, как клиент сделал 5 в строке 7.

То, что я хочу, чтобы выбрать (и как насколько это возможно, так как это довольно большая таблица) - это первая строка из каждого «блока», в которой заказчик отправил последующие заказы на тот же адрес. В этом примере это будут строки 1,2,5,7 и 8. У всех остальных клиент имеет тот же адрес, что и их предыдущий заказ.

Так эффективно я хочу сначала ORDER BY (cust_id, date), затем SELECT purchase_id, cust_id, min(date), address, description.

Однако у меня возникли проблемы, потому что SQL обычно требует, чтобы GROUP BY выполнялось до ORDER BY. Поэтому я не могу понять, как адаптироваться, например, либо из верхних ответов на this question (что мне в общем-то нравится.) Необходимо (по крайней мере, концептуально) упорядочить по дате перед группировкой или использовать агрегатные функции, такие как min(), иначе я пропустил бы экземпляры, подобные строке 7 в моей таблице примеров, где клиент «вернулся» к предыдущему адресу.

Обратите внимание, что два клиента могут совместно использовать адрес, поэтому мне необходимо эффективно группировать cust_id и address после заказа по дате.

Я использую снежинку, который я считаю имеет те же самые команды, доступных в последние версии PostgreSQL и SQL Server (хотя я довольно новый для снежинки поэтому не совсем уверен.)

+0

Вы хотите только вернуть покупки для клиентов более чем один адрес? –

+0

Может быть 1,2,5,8 вместо 1,2,7,8? –

+0

Anthony E: Нет, я хочу вернуть (по крайней мере) 1 строку для всех клиентов, которые когда-либо имели адрес, и больше строк для клиентов, которые меняли адреса один или несколько раз. Giorgi Nakeuri: Спасибо, должно быть 1,2,5,7 и 8. (Строки 5 и 7 имеют другой адрес, чем последний, который использовал клиент.) Отредактирован. – DNB

ответ

0

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

Чтобы уточнить, purchase_id является колонкой автоинкремента, правильно? Если это так, покупка с высшим purchase_id должны быть созданы на более поздний срок, а следующее должно хватить:

SELECT * 
FROM purchases 
WHERE purchase_id IN (
    SELECT MIN(purchase_id) AS first_purchase_id 
    FROM purchases 
    GROUP BY cust_id 
) 

Если вы хотите только первую покупку для клиентов с более чем один адрес, добавить пункт HAVING к вашему подзапросу:

SELECT * 
FROM purchases 
WHERE purchase_id IN (
    SELECT MIN(purchase_id) AS first_purchase_id 
    FROM purchases 
    GROUP BY cust_id 
    HAVING COUNT(DISTINCT address) > 1 
) 

Fiddle: http://sqlfiddle.com/#!9/12d75/6

Однако, если purchase_id НЕ столбец автоинкремента, то SELECT на обоих cust_id и min(date) на вашем подзапрос и использовать INNER JOIN на cust_id и min(date):

SELECT * 
FROM purchases 
INNER JOIN (
    SELECT cust_id, MIN(date) AS min_date 
    FROM purchases 
    GROUP BY cust_id 
    HAVING COUNT(DISTINCT address) > 1 
) cust_purchase_date 
ON purchases.cust_id = cust_purchase_date.cust_id AND purchases.date = cust_purchase_date.min_date 

Первый пример запроса, вероятно, будет быстрее, однако, так что используйте, что если purchase_id является столбцом автоинкремент.

+0

Спасибо Anthony, однако это не возвращает каждый последующий «новый» адрес для каждого клиента. Мне не нужна первая покупка каждого пользователя; я хочу * каждую * первую покупку, у которой есть адрес доставки, отличный от предыдущего адреса доставки. – DNB

2

Вы можете использовать row_number функцию окна, чтобы сделать трюк:

;with cte as(select *, row_number() over(partition by cust_id, address 
             order by purchase_id) as rn from table) 
select * from cte 
where rn = 1 
+0

Спасибо Giorgi Nakeuri, что работает. Я знал о row_number(), но не понимал, что могу разбивать несколько полей и получать желаемый результат. – DNB

+0

Я не уверен, что это правильный ответ. Он не находит строку 7, потому что она имеет тот же cust_id и адрес. –

0

Извините за поздний ответ.Я хотел отреагировать на этот пост несколько дней назад.

«Самый правильный» способ, который я могу придумать, - использовать функцию LAG.

Отнесите:

select purchase_id, cust_id, address, 
lag(address, 1) over (partition by cust_id order by purchase_id) prev_address 
from x order by cust_id, purchase_id; 
-------------+---------+----------+--------------+ 
PURCHASE_ID | CUST_ID | ADDRESS | PREV_ADDRESS | 
-------------+---------+----------+--------------+ 
1   | 5  | address1 | [NULL]  | 
3   | 5  | address1 | address1  | 
5   | 5  | address3 | address1  | 
6   | 5  | address3 | address3  | 
7   | 5  | address1 | address3  | 
2   | 6  | address2 | [NULL]  | 
4   | 6  | address2 | address2  | 
8   | 6  | address4 | address2  | 
-------------+---------+----------+--------------+ 

И тогда вы можете легко обнаружить строки с событиями, как вы описали

select purchase_id, cust_id, address, prev_address from (
    select purchase_id, cust_id, address, 
    lag(address, 1) over (partition by cust_id order by purchase_id) prev_address 
    from x 
) sub 
where not equal_null(address, prev_address) 
order by cust_id, purchase_id; 
-------------+---------+----------+--------------+ 
PURCHASE_ID | CUST_ID | ADDRESS | PREV_ADDRESS | 
-------------+---------+----------+--------------+ 
1   | 5  | address1 | [NULL]  | 
5   | 5  | address3 | address1  | 
7   | 5  | address1 | address3  | 
2   | 6  | address2 | [NULL]  | 
8   | 6  | address4 | address2  | 
-------------+---------+----------+--------------+ 

Обратите внимание, что я использую функцию EQUAL_NULL иметь NULL = NULL семантику.

Обратите внимание, что функция LAG может быть интенсивными вычислениями, хотя (но сравнимой с использованием ROW_NUMBER предложенного ранее)