Я ищу эффективный способ получения двух верхних строк для каждой группы данных в SQL.У меня очень большая таблица данных (около 10 миллиардов строк).Каждая строка данных описывается четырьмя измерениями (которые составляют первичный ключ), а таблица разбивается на одно из измерений (последний столбец первичного ключа).
-- Medium table (2 to 3 million rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableA] (
[colA] [int] NOT NULL PRIMARY KEY
,[valueA] [int]
);
-- Small table (<1000 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableB] (
[colB] [int] NOT NULL PRIMARY KEY
,[valueB] [int]
);
-- Small table (<10000 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableC] (
[colC] [int] NOT NULL PRIMARY KEY
,[valueC] [int]
);
-- Small table (100 to 200 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableD] (
[colD] [int] NOT NULL PRIMARY KEY
,[grouperD] [int] NOT NULL
,[dateD] [date]
);
CREATE PARTITION FUNCTION [pfColD](int) AS RANGE RIGHT FOR VALUES (1, 2, 3, ..., n);
CREATE PARTITION SCHEME [psColD] AS PARTITION [pfColD] TO ([PRIMARY], [PRIMARY], [PRIMARY], ..., [PRIMARY]);
-- Large table (~10 billion rows)
CREATE TABLE [bigDatabase].[dbo].[factBigTable] (
[colA] [int] NOT NULL
,[colB] [int] NOT NULL
,[colC] [int] NOT NULL
,[colD] [int] NOT NULL
,[datum] [float] NULL
,PRIMARY KEY (
[colA] ASC
,[colB] ASC
,[colC] ASC
,[colD] ASC
)
) ON psColD([colD]);
Еще одно требование -что мне нужно сделать это только для подмножества данных в любое время.Чтобы представить данные, которые должны быть расположены, давайте использовать временную таблицу для фильтрации.
CREATE TABLE #filter (
[colA] [int] NOT NULL
,[colB] [int] NOT NULL
,PRIMARY KEY (
[colA] ASC
,[colB] ASC
)
);
В Интернете я нашел несколько других решений, которые предлагают использовать номер строки, выбирая верхние 2, например:
-- Get the most recent two data points for each group of data
SELECT *
FROM (
SELECT big.*
,dimD.[grouperD]
,ROW_NUMBER() OVER (
PARTITION BY dimD.[grouperD], big.[colA], big.[colB], big.[colC]
ORDER BY dimD.[dateD] DESC
) AS rowNumber
FROM [bigDatabase].[dbo].[factBigTable] AS big
INNER JOIN [smallDatabase].[dbo].[dimTableD] AS dimD
ON big.[colD] = dimD.[colD]
INNER JOIN #filter
ON big.[colA] = #filter.[colA]
AND big.[colB] = #filter.[colB]
) AS bigDataRanked
WHERE rowNumber <= 2;
Это действительно дает мне точные данные, которые я ищу;тем не менее, он очень медленный!
В этот момент я пробовал несколько разных решений, но все работали медленнее, чем хотелось бы.Стоит отметить, что из-за характера данных не каждая комбинация измерений имеет данные.Некоторые комбинации довольно редки.
Один алгоритм, который я попробовал, выглядел великолепно на бумаге, но в итоге работал очень медленно из-за редкой природы данных.Идея была:
- Кэшировать список каждой группы.То есть, [grouperD], [colA], [colB] и [colC].Следите за строками, найденными для каждой группы.
- Курсор над [colD], упорядоченный по [dateD].Остановитесь, когда в каждой группе будет найдено 2 строки.
- Выберите строки из [factBigTable], которые соответствуют группам, в которых количество найденных строк меньше 2. Кэшируйте результаты.
- Для кэшированных результатов увеличивайте количество найденных строк.
- Перемещение кэшированных результатов в промежуточную таблицу для последующего использования.
- Переход к следующему [colD] в цикле.
Каждый цикл выполняется относительно быстро, так как SQLумеет использовать PK, ищет по большинству запросов.Тем не менее, в некоторых из моих групп было очень низкое максимальное значение [colD], поэтому цикл должен был повторяться много раз.
Самое быстрое решение, которое я нашел до сих пор, выглядит ужасно на бумаге, но в итоге дает лучшие результаты.Тем не мение;он все еще медленнее, чем мне хотелось бы, и масштабируется очень плохо.
- Для подмножества данных, которое нам небезразлично (т. е. присоединение к фильтру), кэшируйте все первичные ключи для каждой группы.
- Выберите и кэшируйте максимальное значение [colD] для каждой группы.
- Удалите максимальное значение из списка PK по группам.
- Выберите и кэшируйте максимальное значение [colD] для каждой группы.группа, снова.Чтобы получить секунду для max [colD].
- Используйте ключи max и second для max, чтобы найти все нужные нам строки.
У кого-нибудь естьдругие идеи о том, как быстро получить нужные мне строки? Ни в коем случае это не нужно делать в одном запросе.Я в порядке с таким количеством промежуточных или временных таблиц, которые необходимы для быстрого получения данных.Кроме того, я открыт для добавления индексов или других изменений модели данных.Я бы предпочел этого не делать - просто потому, что таблица настолько велика, что любое изменение, скорее всего, будет означать значительные проблемы с хранилищем, но, если это единственный способ, я заставлю его работать.