Необычная сортировка: какие индексы нужны?
От: Слава  
Дата: 06.10.20 15:24
Оценка:
Есть таблица в MS SQL Express, у неё есть минимум три поля:

1) username
2) state
3) date

Нужно сделать постраничную выборку (размер страницы: 20 записей) с фильтром по username, некое значение state вынести в начало выборки и затем отсортировать всё имеющееся по date в порядке убывания, то есть самые больше даты идут в начале.

Я использую linq2db.
  Код C#, linq2db
var res = await c.GetTable<Mytable>().
          Where(x=>x.Username == username).
          OrderByDescending(x=>x.State == state).
          ThenByDescending(x=>x.Date).
          Skip(startFromIndex ?? 0).Take(20).
          Select(r=>
               new MyRecord
               {
                   Rating = Math.Round(r.Rating, 2),
                   City = r.City,
                   State = r.State,
                   FirstName = r.FirstName,
                   LastName = r.LastName,
                   Description= r.Description,
                   Date = r.Date,
               }
                    ).
          ToListAsync();
  Сгенерированный код sql
DECLARE @username NVarChar(100) -- String
SET     @username = N'XXXXXXXXX'
DECLARE @state_2 NVarChar(2) -- String
SET     @state_2 = N'KY'
DECLARE @skip Int -- Int32
SET     @skip = 900
DECLARE @take Int -- Int32
SET     @take = 20

SELECT
    [x].[rating],
    [x].[city],
    [x].[state],
    [x].[firstName],
    [x].[lastName],
    [x].[description],
    [x].[date]
FROM
    [dbo].[mytable] [x]
WHERE
    [x].[username] = @username 
ORDER BY
    IIF([x].[state] = @state_2, 1, IIF((NOT ([x].[state] = @state_2)), 0, NULL)) DESC,
    [x].[date] DESC
OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY

Какие мне индексы следует навесить на таблицу, чтобы подобный запрос выполнялся максимально быстро? Вставками в таблицу можно пренебречь, обновлений и удалений в таблице не будет. Будет ли подобный запрос давать стабильную сортировку, чтобы по ней так же стабильно работал пейджинг?
Re: Необычная сортировка: какие индексы нужны?
От: vmpire Россия  
Дата: 06.10.20 15:48
Оценка:
Здравствуйте, Слава, Вы писали:


  Сгенерированный код sql
С>DECLARE @username NVarChar(100) -- String
С>SET     @username = N'XXXXXXXXX'
С>DECLARE @state_2 NVarChar(2) -- String
С>SET     @state_2 = N'KY'
С>DECLARE @skip Int -- Int32
С>SET     @skip = 900
С>DECLARE @take Int -- Int32
С>SET     @take = 20

С>SELECT
С>    [x].[rating],
С>    [x].[city],
С>    [x].[state],
С>    [x].[firstName],
С>    [x].[lastName],
С>    [x].[description],
С>    [x].[date]
С>FROM
С>    [dbo].[mytable] [x]
С>WHERE
С>    [x].[username] = @username 
С>ORDER BY
С>    IIF([x].[state] = @state_2, 1, IIF((NOT ([x].[state] = @state_2)), 0, NULL)) DESC,
С>    [x].[date] DESC
С>OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY

С>Какие мне индексы следует навесить на таблицу, чтобы подобный запрос выполнялся максимально быстро?
Перепишите запрос вручную через union all (может быть можно и через linq, если использовать .Concat), а индекс сделайте на (state, date)
С>Будет ли подобный запрос давать стабильную сортировку, чтобы по ней так же стабильно работал пейджинг?
В рамках двух полей, по которым сортировка — да.
Если эти значения повторяются, то по другим полям — нет.
Re[2]: Необычная сортировка: какие индексы нужны?
От: fmiracle  
Дата: 06.10.20 16:04
Оценка:
Здравствуйте, vmpire, Вы писали:

V>Перепишите запрос вручную через union all (может быть можно и через linq, если использовать .Concat), а индекс сделайте на (state, date)


А что без username? А если там миллионы пользователей разных? Индекс по ним может откинуть значительную часть базы.
Re[2]: Необычная сортировка: какие индексы нужны?
От: Слава  
Дата: 06.10.20 16:12
Оценка:
Здравствуйте, vmpire, Вы писали:

V>Перепишите запрос вручную через union all (может быть можно и через linq, если использовать .Concat), а индекс сделайте на (state, date)


Наверное индекс следует делать на (username, state, date).

И запрос будет выглядеть как:

SELECT * FROM t WHERE username = @username AND state = @state ORDER BY [date] DESC
UNION ALL
SELECT * FROM t WHERE username = @username AND state != @state ORDER BY [date] DESC

Вы это имели в виду?

Но как поверх этого union'а сделать пейджинг? Это вообще законно оно вообще будет вести себя нормально? Ведь мы уже на второй странице можем пропустить весь первый запрос, до union'а.

Я могу изменить API, вместо пейджинга по индексу и странице известного размера, сделать выборку в X элементов после указанного элемента.

С>>Будет ли подобный запрос давать стабильную сортировку, чтобы по ней так же стабильно работал пейджинг?

V>В рамках двух полей, по которым сортировка — да. Если эти значения повторяются, то по другим полям — нет.

Поля могут повторяться, потому что два две записи для одного username могут быть внесены в одно и то же время с точностью до секунды. Хоть это и маловероятно. Однозначно уникальным в таблице является искусственный первичный ключ bigint, может быть мне его стоит внести в сортировку, после всех остальных полей?
Re[3]: Необычная сортировка: какие индексы нужны?
От: vmpire Россия  
Дата: 06.10.20 16:26
Оценка:
Здравствуйте, fmiracle, Вы писали:

F>А что без username? А если там миллионы пользователей разных? Индекс по ним может откинуть значительную часть базы.

username, конечно, тоже нужен, это я пропустил. Причём, username должен быть первый в индексе.
Re[3]: Необычная сортировка: какие индексы нужны?
От: vmpire Россия  
Дата: 06.10.20 16:30
Оценка:
Здравствуйте, Слава, Вы писали:

С>Наверное индекс следует делать на (username, state, date).


С>И запрос будет выглядеть как:


С>SELECT * FROM t WHERE username = @username AND state = @state ORDER BY [date] DESC

С>UNION ALL
С>SELECT * FROM t WHERE username = @username AND state != @state ORDER BY [date] DESC

С>Вы это имели в виду?

Да, именно это

С>Но как поверх этого union'а сделать пейджинг? Это вообще законно оно вообще будет вести себя нормально? Ведь мы уже на второй странице можем пропустить весь первый запрос, до union'а.

Пейджинг — как обычно, не вижу в этом проблемы.
Чтобы решить, пропускать ли первый запрос, нужно его вс равно выполнить, чтобы узнать, сколько в нём строк.

С>Я могу изменить API, вместо пейджинга по индексу и странице известного размера, сделать выборку в X элементов после указанного элемента.

Думаю, это не принципиально

С>Поля могут повторяться, потому что два две записи для одного username могут быть внесены в одно и то же время с точностью до секунды. Хоть это и маловероятно. Однозначно уникальным в таблице является искусственный первичный ключ bigint, может быть мне его стоит внести в сортировку, после всех остальных полей?

Если нужен стабильный порядок — то да, имеет смысл.
Re: Необычная сортировка: какие индексы нужны?
От: Sinclair Россия https://github.com/evilguest/
Дата: 04.02.21 03:45
Оценка: 4 (1)
Здравствуйте, Слава, Вы писали:
>Какие мне индексы следует навесить на таблицу, чтобы подобный запрос выполнялся максимально быстро? Вставками в таблицу можно пренебречь, обновлений и удалений в таблице не будет.
Ещё можно навесить computed column, например, effectiveState, которая равна IIF([x].[state] = @state_2, 1, IIF((NOT ([x].[state] = @state_2)), 0, NULL)), и сделать индекс по username, effectiveState, date.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Необычная сортировка: какие индексы нужны?
От: Слава  
Дата: 04.02.21 07:48
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Ещё можно навесить computed column, например, effectiveState, которая равна IIF([x].[state] = @state_2, 1, IIF((NOT ([x].[state] = @state_2)), 0, NULL)), и сделать индекс по username, effectiveState, date.


Очень интересная и заманчивая рекомендация, спасибо.

Но ведь @state_2 — это параметр запроса, оно будет меняться от запроса к запросу. Как такое внести в выражение для computed column?

И вот пишут:
https://docs.microsoft.com/ru-ru/sql/t-sql/statements/alter-table-computed-column-definition-transact-sql?view=sql-server-ver15

For example, if the table has integer columns a and b, the computed column a + b may be indexed, but computed column a + DATEPART(dd, GETDATE()) cannot be indexed, because the value might change in subsequent invocations.

Re[3]: Необычная сортировка: какие индексы нужны?
От: Sinclair Россия https://github.com/evilguest/
Дата: 04.02.21 10:56
Оценка: +1
Здравствуйте, Слава, Вы писали:
С>Но ведь @state_2 — это параметр запроса, оно будет меняться от запроса к запросу. Как такое внести в выражение для computed column?
Да, виноват, это я проглядел. Почему-то подумал, что просто есть какой-то штат, который должен всегда быть сверху. Тогда — только так, как предложили коллеги ранее.
Вообще, постраничный вывод запросов с union — это интересная задачка.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Необычная сортировка: какие индексы нужны?
От: _ABC_  
Дата: 05.02.21 00:21
Оценка:
Здравствуйте, vmpire, Вы писали:

V>Пейджинг — как обычно, не вижу в этом проблемы.

Покажи? Вот что-то чтобы в лоб, быстро и надежно, я как-то придумать не смог с наскока. Думать надо...

Поправка. Решил ради интереса. Через временную/переменную таблицу, без union all. Но как это в linq перевести не знаю.
С union all интересно было бы взглянуть на решение с планом запроса.
Отредактировано 05.02.2021 0:41 Does not matter . Предыдущая версия .
Re[5]: Необычная сортировка: какие индексы нужны?
От: vmpire Россия  
Дата: 05.02.21 10:25
Оценка:
Здравствуйте, _ABC_, Вы писали:

_AB>Здравствуйте, vmpire, Вы писали:


V>>Пейджинг — как обычно, не вижу в этом проблемы.

_AB>Покажи? Вот что-то чтобы в лоб, быстро и надежно, я как-то придумать не смог с наскока. Думать надо...
Так у вас же это в запросе было:
... ORDER BY ... OFFSET X ROWS FETCH NEXT Y ROWS ONLY

_AB>Поправка. Решил ради интереса. Через временную/переменную таблицу, без union all. Но как это в linq перевести не знаю.

_AB>С union all интересно было бы взглянуть на решение с планом запроса.
Как-то так:

SELECT * FROM
(
  SELECT
  ...
  FROM [dbo].[mytable] [x]
  WHERE [x].[username] = @username AND [x].[state] = @state_2
  ORDER BY [x].[date] DESC
  UNION ALL
  SELECT
  ...
  FROM [dbo].[mytable] [x]
  WHERE [x].[username] = @username AND [x].[state] <> @state_2
  ORDER BY [x].[date] DESC
)
OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY


Это позволит хорошо использовать индекс по (username, state, date), хотя, возможно, для второй части запроса больше подойдёт (username, date), нужно проверять
Ещё нужно посмотреть, может быть (username, date, state) окажется эффективнее.
Если получится их использовать, то можно в индекс включить все выбираемые колонки (через include), тогда к таблице обращений вообще не будет.
Чтобы было прям совсем быстро, сделайте indexed view на этот запрос, хотя. не помню навскидку, поддерживается ли это в express версии.

А план запроса нужно смотреть по фпкту на вашей базе
Re: Необычная сортировка: какие индексы нужны?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 05.02.21 11:01
Оценка:
Здравствуйте, Слава, Вы писали:

С>Есть таблица в MS SQL Express, у неё есть минимум три поля:


С>1) username

С>2) state
С>3) date

С>Нужно сделать постраничную выборку (размер страницы: 20 записей) с фильтром по username, некое значение state вынести в начало выборки и затем отсортировать всё имеющееся по date в порядке убывания, то есть самые больше даты идут в начале.


С>Какие мне индексы следует навесить на таблицу, чтобы подобный запрос выполнялся максимально быстро? Вставками в таблицу можно пренебречь, обновлений и удалений в таблице не будет. Будет ли подобный запрос давать стабильную сортировку, чтобы по ней так же стабильно работал пейджинг?


Вариант первый:
Сделать два запроса — где state совпадает и где не совпадает, в обоих заросах получить по 20 записей. Потом склеить их на клиенте и взять первые 20 из получившегося набора.
Тогда индекс (state, date)
Пока писал понял что можно и на сервере сделать такое.

Вариант второй, если множество разных state конечно сверху — сделать нехранимые вычисляемые колонки isStateN = state == stateN и индексы по (isStateN, date). ну и на клиенте сделать switch и цеплять разный OrderBy в зависимости от значения state.
Отредактировано 05.02.2021 11:03 gandjustas . Предыдущая версия .
Re[6]: Необычная сортировка: какие индексы нужны?
От: _ABC_  
Дата: 05.02.21 11:02
Оценка:
Здравствуйте, vmpire, Вы писали:

_AB>>С union all интересно было бы взглянуть на решение с планом запроса.

V>Как-то так:
V>
  Запрос
V>
V>SELECT * FROM
V>(
V>  SELECT
V>  ...
V>  FROM [dbo].[mytable] [x]
V>  WHERE [x].[username] = @username AND [x].[state] = @state_2
V>  ORDER BY [x].[date] DESC
V>  UNION ALL
V>  SELECT
V>  ...
V>  FROM [dbo].[mytable] [x]
V>  WHERE [x].[username] = @username AND [x].[state] <> @state_2
V>  ORDER BY [x].[date] DESC
V>)
V>OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY
V>



V>Это позволит хорошо использовать индекс по (username, state, date), хотя, возможно, для второй части запроса больше подойдёт (username, date), нужно проверять

Извини, но мне тут непонятно несколько моментов. Возможно, я ошибаюсь, но:
0. Order by нельзя использовать в подзапросах union all. Будет syntax error. Нужен один в конце запроса.
1. При распаралеливании выполнения union all вернёт (может вернуть) записи из двух подзапросов вперемешку, что приведёт к неверной сортировке. Нельзя полагаться на то, что union all вернёт сначала записи из первого запроса, потом из второго.
2. Если есть записи, где x.state is null, то эти записи вообще не вернутся.
3. Интуитивно мне тут рисуется в плане запроса два индекс скана со спулингом в темпдб всех записей и только потом будет паджинация на этом наборе данных. Не думаю, что это достаточно быстрое решение и по своей логике оно не слишком отличается от выборки всех записей по username с последующей сортировкой и паджинацией в варианте linqdb.

Подытоживая, ИМХО, опуская синтаксические ошибки, в твоём решении даже правильное выполнение не гарантируется. Нельзя полагаться на то, что union вернёт все записи в порядке подзапросов и мы неявно полагаемся на то, что не будет state со значением null. А по скорости оно вряд ли быстрее того, что предлагает linq, но тут я могу ошибаться. Возможно, SQL Server умеет оптимизировать подобные запросы в паджинации, хотя по своему опыту я такого не встречал. С удовольствием ошибусь в данном вопросе.
Re[7]: Необычная сортировка: какие индексы нужны?
От: vmpire Россия  
Дата: 12.02.21 10:18
Оценка:
Здравствуйте, _ABC_, Вы писали:

_AB>Извини, но мне тут непонятно несколько моментов. Возможно, я ошибаюсь, но:

_AB>0. Order by нельзя использовать в подзапросах union all. Будет syntax error. Нужен один в конце запроса.
Да, этого я не учёл. Но тут выкрутится не сложно:

SELECT <нужные колонки> FROM
(
SELECT 1 as set_number, <остальные колонки>
FROM [dbo].[mytable] [x]
WHERE [x].[username] = @username AND [x].[state] = @state_2
ORDER BY [x].[date] DESC
UNION ALL
SELECT 2 as set_number, <остальные колонки>
FROM [dbo].[mytable] [x]
WHERE [x].[username] = @username AND [x].[state] <> @state_2
ORDER BY set_number, [x].[date] DESC
)
OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY

_AB>1. При распаралеливании выполнения union all вернёт (может вернуть) записи из двух подзапросов вперемешку, что приведёт к неверной сортировке. Нельзя полагаться на то, что union all вернёт сначала записи из первого запроса, потом из второго.

Нельзя, нужна явная сортировка (см. выше)

_AB>2. Если есть записи, где x.state is null, то эти записи вообще не вернутся.

Этого изначально в условиях не было. Если нужно, просто расширьте условие: AND ([x].[state] IS NULL OR [x].[state] <> @state_2)

_AB>3. Интуитивно мне тут рисуется в плане запроса два индекс скана со спулингом в темпдб всех записей и только потом будет паджинация на этом наборе данных. Не думаю, что это достаточно быстрое решение и по своей логике оно не слишком отличается от выборки всех записей по username с последующей сортировкой и паджинацией в варианте linqdb.

Тут нужно не интуицию включать, а пробовать и замерять.

_AB>Подытоживая, ИМХО, опуская синтаксические ошибки, в твоём решении даже правильное выполнение не гарантируется.

А я ничего и не гарантировал. Я идею предлагал.
_AB>Нельзя полагаться на то, что union вернёт все записи в порядке подзапросов
Это не UNION, а UNION ALL. Порядок можно задат какой угодно — см. выше
_AB> и мы неявно полагаемся на то, что не будет state со значением null.
Добавьте условие явно, только и всего
А по скорости оно вряд ли быстрее того, что предлагает linq, но тут я могу ошибаться. Возможно, SQL Server умеет оптимизировать подобные запросы в паджинации, хотя по своему опыту я такого не встречал. С удовольствием ошибусь в данном вопросе.
Тут два варианта: Либо вы оперируета словами "вряд ли и возможно", либо просто пробуете.
Изменить запрос и проверить — дело 10 минут.
Re[8]: Необычная сортировка: какие индексы нужны?
От: _ABC_  
Дата: 12.02.21 20:27
Оценка:
Здравствуйте, vmpire, Вы писали:

V>Тут нужно не интуицию включать, а пробовать и замерять.

V>А я ничего и не гарантировал. Я идею предлагал.
Ну, блин... Я-то думал, что есть реальные данные на похожих сценариях, а тут "мыши станьте ёжиками"...

V>Тут два варианта: Либо вы оперируета словами "вряд ли и возможно", либо просто пробуете.

V>Изменить запрос и проверить — дело 10 минут.
Ничего, что это не мой запрос?
В работе с данными нельзя быть настолько невнимательным, однако.
Re[9]: Необычная сортировка: какие индексы нужны?
От: vmpire Россия  
Дата: 12.02.21 21:34
Оценка:
Здравствуйте, _ABC_, Вы писали:

V>>Тут нужно не интуицию включать, а пробовать и замерять.

V>>А я ничего и не гарантировал. Я идею предлагал.
_AB>Ну, блин... Я-то думал, что есть реальные данные на похожих сценариях, а тут "мыши станьте ёжиками"...
Да нет, просто сервера, данные и настройки у каждого свои. То, что работало в одном случае может не сработать в другом.

V>>Тут два варианта: Либо вы оперируета словами "вряд ли и возможно", либо просто пробуете.

V>>Изменить запрос и проверить — дело 10 минут.
_AB>Ничего, что это не мой запрос?
_AB>В работе с данными нельзя быть настолько невнимательным, однако.
Это да, это я просмотрел.
Но раз Вас эта тема так заинтересовала, то ведь и Вы можете попробовать тоже
Правда, это будет уже больше 10 минут
Re[10]: Необычная сортировка: какие индексы нужны?
От: _ABC_  
Дата: 12.02.21 22:19
Оценка:
Здравствуйте, vmpire, Вы писали:

V>Но раз Вас эта тема так заинтересовала, то ведь и Вы можете попробовать тоже

Мой опыт в данном вопросе я уже высказал.
Будет два скана индексов. В лучшем случае два рэндж скана. Надеялся, что что-то пропустил.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.