SQL Server - первичный ключ строки отметки времени MAX, сгруппированной по клиенту и дате отметки времени - PullRequest
2 голосов
/ 23 января 2012

Прежде всего, я увидел этот вопрос: SQL MAX столбца, включая его первичный ключ Мой вопрос отличается тем, что мне нужно больше, чем одна строка, поскольку мне нужны все идентификаторы клиентов.

Я считаю себя компетентным разработчиком SQL, но я имел дело с тайной конструкцией базы данных, над которой я не имею никакого контроля (но я отступаю).

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

Я имею дело с версионными таблицами (имеется в виду множество копий одной и той же строки с незначительными изменениями элементов данных до тех пор, пока она не будет «закрыта») Мне нужно получать последний Закрытый (Закрытый = 1) Заказ каждого Клиента каждый день между периодом времени (OrderDateTime) для группы «Заказов», содержащих определенный OrderItem (OrderItem = '1111'). Я не уверен, что я вообще это понимаю. : -)

* Обратите внимание, для краткости и понятности я сделал все возможное, чтобы преобразовать мой вариант использования в общие термины. Orders и OrderItems (поскольку они довольно академичны) вместо того, на что я на самом деле охотюсь.

Традиционно я написал что-то вроде этого.

SELECT
    Order.Order_ID
FROM
(
    SELECT
        Customer_ID,
        MAX(OrderedDateTime) AS OrderedDateTime
    FROM
        Order_versioned
    JOIN
        OrderItems_versioned
            ON Order_versioned.OrderID = OrderItems_versioned.OrderID
                AND OrderItem.Item_ID = '1111'
    WHERE
        Order_versioned.Closed = 1
        AND Order_versioned.OrderedDateTime BETWEEN '2012-01-01 00:00:00' AND '2012-01-31 23:59:59'
    GROUP BY
        Order.Customer_ID
        , CAST(Order.OrderedDateTime AS DATE)
) t1
JOIN
    Order
        ON t1.Customer_ID = Order.Customer_ID
            t1.OrderedDateTime = Order.OrderedDateTime

Справочная информация: Customer_ID и OrderedDateTime представляют собой уникальную строку, поэтому я могу присоединиться к ним и быть уверенным, что это одна строка.

Примечание. Для Order_versioned.Closed имеются все индексы и все столбцы * ID.

Проблема заключается в том, что, хотя Order_versioned.Customer_ID индексируется, Order_versioned.OrderedDateTime НЕ индексируется, и я не могу (по многим причинам ... спасибо в поддержку контрактов) добавить индекс. Само собой разумеется, что этот метод занимает некоторое время (только 274 000 000 OrderItems в 20 000 000 заказов).

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

Я надеюсь, что у кого-то больше джедаев, чем у меня, есть некоторые черты, которые я не знал об их рукаве и мог указать мне правильное направление. Я думаю, что возможности управления окнами в SQL Server (OVER, PARTITION и т. Д.) Вместе с соответствующей агрегацией могут дать мне то, что мне нужно, я просто недостаточно хорошо знаком с этими новыми функциями (да, я знаю, что они с 2005 года). ). Опять же, это может быть лучшим способом сделать это, учитывая мои ограничения. Я надеюсь, что SQL Server поддерживает какой-то внутренний указатель на запись агрегации MAX, и я просто не знаю, как к ней добраться.

Спасибо за ваше время.

Ответы [ 2 ]

2 голосов
/ 24 января 2012

Чтобы сделать то, что Арион предложил сделать еще дальше. Вот точный порт из того, что я предоставил в исходном вопросе, используя окна и CTE (предложения Ариона).

;WITH t1
AS
(
    SELECT
        RANK() OVER(PARTITION BY o.Customer_ID, CAST(o.OrderedDateTime AS Date) ORDER BY o.OrderedDateTime DESC) as iRank
        , o.Order_ID
    FROM
        Order_versioned o WITH(NOLOCK)
    JOIN
        OrderItems_version AS oi WITH(NOLOCK) 
            ON
            o.OrderID = oi.OrderID
            AND oi.Item_ID = '1111'
WHERE
    o.Closed = 1
    AND o.OrderedDateTime BETWEEN '2012-01-01 00:00:00' AND '2012-01-31 23:59:59'
)
SELECT 
    t1.Order_ID
FROM
    t1
WHERE
    t1.iRank = 1

Это очень быстро. Но я провожу еще несколько исследований, чтобы убедиться, что это дает наилучшую производительность.

2 голосов
/ 24 января 2012

Может быть, это поможет:
Я сделал небольшой пример, который использует валюту и валютные значения. В качестве примера задача состоит в том, чтобы взять последнее значение валюты для валюты. Я думаю, что вы можете легко применить этот пример к своему коду. Итак, вот пример:

DECLARE @tblCurrency TABLE
    (
        pkCurrencyID INT,
        name VARCHAR(100)
    )
DECLARE @tblCurrencyValues TABLE
    (
        pkCurrencyValueID INT,
        currencyDate DATETIME,
        fkCurrencyID INT,
        rate FLOAT
    )

INSERT INTO @tblCurrency
(
    pkCurrencyID,
    name
)
SELECT 1,'SEK' 
UNION ALL 
SELECT 2,'EURO'
UNION ALL
SELECT 3, 'DKK'

INSERT INTO @tblCurrencyValues
(
    pkCurrencyValueID,
    fkCurrencyID,
    currencyDate,
    rate
)
SELECT 1,1,GETDATE(),1.4
UNION ALL
SELECT 2,1,GETDATE()-2,1.4
UNION ALL
SELECT 3,1,GETDATE()-1,5
UNION ALL
SELECT 4,2,GETDATE(),1.4
UNION ALL
SELECT 5,2,GETDATE()-2,1.4
UNION ALL
SELECT 6,2,GETDATE()-1,5
UNION ALL
SELECT 7,3,GETDATE(),1.4
UNION ALL
SELECT 8,3,GETDATE()-2,1.4
UNION ALL
SELECT 9,3,GETDATE()-1,5

;WITH CTE
AS
(
    SELECT
        RANK() OVER(PARTITION BY tblCurrencyValues.fkCurrencyID order by tblCurrencyValues.currencyDate) as currencyValueRank,
        tblCurrencyValues.fkCurrencyID,
        tblCurrencyValues.currencyDate,
        tblCurrencyValues.rate
    FROM
        @tblCurrencyValues AS tblCurrencyValues
)
SELECT 
    *
FROM
    CTE
    JOIN @tblCurrency AS tblCurrency
        ON CTE.fkCurrencyID=tblCurrency.pkCurrencyID
WHERE 
    CTE.currencyValueRank=1
...