Группируйте и фильтруйте записи и находите max в огромной таблице в MSSQL - PullRequest
0 голосов
/ 25 февраля 2019

У меня есть таблица с 4 000 000 записей, которая растет с каждым днем.

Prices(Contract INT, Period DATE, Date DATE, PriceValue FLOAT)

Мне нужно отфильтровать цены по Дата (@dateFrom / @dateTo - это ввод пользователя) и найтиmax Дата цена за каждый уникальный Контракт / Период

Во-первых, я начал с:

SELECT p.*
  FROM  
  (
    SELECT [ContractId], [Period], MAX(p.Date) AS MaxDate FROM Prices p  WITH (NOLOCK)
    WHERE (@dateFrom IS NULL OR p.[Date] >= @dateFrom) AND p.[Date] <= @dateTo)
    GROUP BY [ContractId], [Period]
  ) md      
  INNER JOIN Prices p  WITH (NOLOCK) ON md.ContractId = p.ContractId AND md.PERIOD  = p.PERIOD AND md.MaxDate = p.Date
  WHERE (@dateFrom IS NULL OR p.[Date] >= @dateFrom) AND p.[Date] <= @dateTo)

Это работало нормально в течение 2-Диапазон дат 3 недели, но часто, когда запрашиваются данные за 1 год.Это занимает до 30-60 секунд ...

Затем я попробовал подход ROW_NUMBER (), но это было очень медленно ...

И, наконец, я переключаюсь на EF lamba, которая генерируетследующий запрос (немного очищен)

SELECT 
    [Limit1]......
    FROM   (SELECT 
        @toDate AS [p__linq__0], 
        @fromDate AS [p__linq__1], 
        [Distinct1].....
        FROM ( SELECT DISTINCT 
            [Extent1].[ContractId] AS [ContractId], 
            [Extent1].[Period] AS [Period], 
            FROM [dbo].[Prices] AS [Extent1]
            WHERE ([Extent1].[ContractId] IN ... AND ([Extent1].[Date] <= @toDate))
        )  AS [Distinct1] ) AS [Project2]
    OUTER APPLY  (SELECT TOP (1) [Project3].[Id] AS [Id], [Project3].[ContractId] AS [ContractId], [Project3].[Period] AS [Period], [Project3].[Date] AS [Date], [Project3].[PriceValue] AS [PriceValue])
        FROM ( SELECT 
            [Extent2].....
            FROM [dbo].[Prices] AS [Extent2]
            WHERE ([Extent2].[ContractId] IN ... AND ([Project2].[Period] = ...)
        )  AS [Project3]
        ORDER BY [Project3].[Date] DESC ) AS [Limit1]

Внешнее применение работало немного быстрее, но все еще 20-30 секунд.

Индексы в таблице

CREATE INDEX FilteredPricesOnlyLast
ON TradeBlotterDevPoc.dbo.Prices (ContractId, PERIOD, Date)
GO


CREATE INDEX IDX_Latest
ON TradeBlotterDevPoc.dbo.Prices (ContractId, PERIOD)
INCLUDE (Date)
GO

MSSQLStudio и Azure не дают никаких рекомендаций по индексам.

Итак, вопрос Есть ли способ выполнить эту задачу в 5 раз или быстрее, чем базовое применение GROUP BY или OUTER APPLY?Возможно, с любым другим хитрым SQL-запросом?Или есть какие-либо известные методы предварительной обработки данных?

1 Ответ

0 голосов
/ 25 февраля 2019

Я рекомендую вам создать разделы вашей таблицы (для года в этом примере)

DECLARE @YearStart INT, @YearEnd INT
SELECT @yearStart = MIN(Year(Date)), @yearEnd = Year(GETDATE()) 
FROM Prices
DECLARE @SQL VARCHAR(MAX)
WHILE @YearStart < @YearEnd -- keep current year in your original table
BEGIN
    SET @SQL = '
    SELECT * INTO 
    Prices' + CAST(@YearStart AS VARCHAR) + '
    FROM Prices
    WHERE Date < DateFromParts(@YearStart + 1, 1, 1)

    DELETE Prices WHERE DATE < DateFromParts(@YearStart + 1, 1, 1)
    '
    sp_executesql @SQL, N'@Year INT', @Year = @YearStart

    SET @SQL = '
    CREATE INDEX FilteredPricesOnlyLast' + CAST(@Year AS VARCHAR) + '
    ON TradeBlotterDevPoc.dbo.Prices' + CAST(@Year AS VARCHAR) + ' (ContractId, PERIOD, Date)'
    EXEC(@SQL)

    SET @YearStart = @YearStart + 1
END

Затем вы можете использовать CTE и Outer APPLY, использовать union all, если вам нужно использовать более одного раздела

;WITH Periods AS (
    SELECT DISTINCT ContractID, Period
    FROM Prices 
    WHERE (@dateFrom IS NULL OR p.[Date] >= @dateFrom) AND p.[Date] <= @dateTo)
    UNION ALL
    SELECT DISTINCT ContractID, Period
    FROM Prices2018 
    WHERE (@dateFrom IS NULL OR p.[Date] >= @dateFrom) AND p.[Date] <= @dateTo)
)
SELECT 
    Periods.ContractID, Period.Period, A.Date, A.Price
FROM Periods
OUTER APPLY (
    SELECT TOP 1 Date, Price 
    FROM Prices 
    WHERE ContractID = Periods.ContractID AND Period = Periods.Period
    AND (@dateFrom IS NULL OR p.[Date] >= @dateFrom) AND p.[Date] <= @dateTo)
    ORDER BY Date DESC
) A
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...