2014-11-13 4 views
1

У меня есть две таблицы с отношением «один ко многим» (например, таблица клиентов и таблица заказов, каждый клиент может разместить несколько заказов). Для каждого клиента мне нужно получить только один заказ, определенный определенным критерием (например, самым последним заказом).SQL: получить последний заказ для каждого клиента

Мне нужно сделать это как в PostgreSQL 9.1, так и в Microsoft SQL Server 2014 (разные данные в двух системах, но с той же проблемой). Таблицы составляют около 2,5 миллионов записей (клиентов) и 10 миллионов записей (заказов).

Я думал об использовании внешнего приложения на SQL-сервере и занимал место над разделом или боковыми соединениями в PostgreSQL, но не слишком уверен в синтаксисе.

Простой max(order_date) ... group by clientid будет недостаточно, потому что мне нужны все поля из таблицы заказов. Например, мне нужен выход, такой как

c.clientid, c.client_name, o.order_id, o.order_date, o.order_amount, o.product_ordered 
where c = the clients table and o = the orders table. 

Любые предложения?

ответ

1

Я не вижу, как потребуется боковое соединение. Это хорошая старая «величайший-н-за группу» проблема, которая может быть решена с помощью равнины соединения и функция окна:

select * 
from clients c 
    join (
    select clientid, order_id, order_date, order_amount, product_ordered 
      row_number() over (partition by client_id order by order_date desc) as rn 
    from orders 
) o on o.clientid = c.clientid and o.rn = 1; 

Для Postgres это можно было бы сделать более эффективным с помощью distinct on вместо окна функция. Если у вас есть индекс на client_id, order_date, это должно работать достаточно хорошо.

0

Вот еще один способ, которым вы могли бы это сделать. Написано/протестировано для SQL 2005. Не слишком уверен в производительности при большом количестве записей.

SELECT 
    c.clientid, 
    c.client_name, 
    o.order_id, 
    o.order_date, 
    o.order_amount, 
    o.product_ordered 
FROM c JOIN o ON c.clientid = o.clientid 
WHERE order_id IN (
    SELECT TOP 1 order_id 
    FROM o AS o2 
    WHERE o.clientid = o2.clientid 
    ORDER BY order_date DESC) 
0

Для SQL Server я бы использовал OUTER APPLY.

SELECT 
    clients.clientid 
    ,clients.client_name 
    ,LastOrder.order_id 
    ,LastOrder.order_date 
    ,LastOrder.order_amount 
    ,LastOrder.product_ordered 
FROM 
    clients 
    OUTER APPLY 
    (
     SELECT TOP(1) 
      orders.order_id 
      ,orders.order_date 
      ,orders.order_amount 
      ,orders.product_ordered 
     FROM orders 
     WHERE orders.clientid = clients.clientid 
     ORDER BY order_date DESC 
    ) AS LastOrder 
; 

Если у вас есть индекс (orders.clientid, orders.order_date) и добавить другие столбцы индекса, которые включены, то OUTER APPLY должен быть искать по этому показателю. Итак, должен быть один поиск по индексу заказов для каждой строки в таблице клиентов. Если индекс не включает все остальные столбцы, будет поиск и поиск.

Если вы не хотите, чтобы клиенты, у которых нет заказов, используют CROSS APPLY вместо OUTER APPLY.

Я думаю, что этот вид OUTER APPLY был бы более эффективным, чем создание ROW_NUMBERS() для всех заказов, а затем отбрасывание большинства результатов и использование только первых строк.

Конечно, вы должны измерять фактическую производительность различных вариантов в вашей системе.

К сожалению, я не знаю Postgres.

+0

С помощью Postgres вы можете сделать то же самое, используя 'join lateral' (который является стандартом ANSI для Microsoft, называемым' apply'), действительно будет интересным, если это будет работать лучше. –

+1

@Pythonista анонимный, как вы в конечном итоге решили проблему? Было ли какое-либо из предлагаемых предложений полезным для вас? Если да, пожалуйста, поддержите их и примите ответ, который был наиболее полезен. –

Смежные вопросы