HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-03-10
1 <p><strong>SARGable</strong>(Search ARGument Able) -<a>термин</a>, который означает, что для условия WHERE в запросе возможен поиск по индексу.</p>
1 <p><strong>SARGable</strong>(Search ARGument Able) -<a>термин</a>, который означает, что для условия WHERE в запросе возможен поиск по индексу.</p>
2 <p>На днях коллега спросил у меня: "Можно ли, добавив индекс, сделать запрос SARGable?". Также он добавил, что изменить текст запроса нельзя и он ищет какие-то другие способы улучшить план запроса.</p>
2 <p>На днях коллега спросил у меня: "Можно ли, добавив индекс, сделать запрос SARGable?". Также он добавил, что изменить текст запроса нельзя и он ищет какие-то другие способы улучшить план запроса.</p>
3 <p>Выражение WHERE в запросе выглядело примерно так:</p>
3 <p>Выражение WHERE в запросе выглядело примерно так:</p>
4 WHERE RIGHT(SomeColumn,3) = '333'<p>Я спросил его, может ли он изменить таблицу. Он ответил, что изменять существующие столбцы нельзя, но можно их добавить. Хорошо. Это заставило меня задуматься о решении. Давайте посмотрим, что я придумал.</p>
4 WHERE RIGHT(SomeColumn,3) = '333'<p>Я спросил его, может ли он изменить таблицу. Он ответил, что изменять существующие столбцы нельзя, но можно их добавить. Хорошо. Это заставило меня задуматься о решении. Давайте посмотрим, что я придумал.</p>
5 <p>Сначала создадим таблицу:</p>
5 <p>Сначала создадим таблицу:</p>
6 USE tempdb GO CREATE TABLE StagingData (SomeColumn varchar(255) NOT NULL ) ALTER TABLE dbo.StagingData ADD CONSTRAINT PK_StagingData PRIMARY KEY CLUSTERED ( SomeColumn ) ON [PRIMARY] GO<p>Потом генерируем тестовые данные, добавив точку и число от 100 до 999 к GUID. Вставим одну строку, чтобы посмотреть, как будут выглядеть данные.</p>
6 USE tempdb GO CREATE TABLE StagingData (SomeColumn varchar(255) NOT NULL ) ALTER TABLE dbo.StagingData ADD CONSTRAINT PK_StagingData PRIMARY KEY CLUSTERED ( SomeColumn ) ON [PRIMARY] GO<p>Потом генерируем тестовые данные, добавив точку и число от 100 до 999 к GUID. Вставим одну строку, чтобы посмотреть, как будут выглядеть данные.</p>
7 DECLARE @guid uniqueidentifier SELECT @guid = 'DEADBEEF-DEAD-BEEF-DEAD-BEEF00000075' INSERT StagingData SELECT CONVERT(varchar(200),@guid) + '.100' SELECT * FROM StagingData<p>Результат:</p>
7 DECLARE @guid uniqueidentifier SELECT @guid = 'DEADBEEF-DEAD-BEEF-DEAD-BEEF00000075' INSERT StagingData SELECT CONVERT(varchar(200),@guid) + '.100' SELECT * FROM StagingData<p>Результат:</p>
8 SomeColumn -------------------------------- DEADBEEF-DEAD-BEEF-DEAD-BEEF00000075.100<p>Теперь вставим 999 999 строк.</p>
8 SomeColumn -------------------------------- DEADBEEF-DEAD-BEEF-DEAD-BEEF00000075.100<p>Теперь вставим 999 999 строк.</p>
9 INSERT StagingData SELECT TOP 999999 CONVERT(varchar(200),NEWID()) + '.' + CONVERT(VARCHAR(10),s2.number) FROM master..SPT_VALUES s1 CROSS JOIN master..SPT_VALUES s2 WHERE s1.type = 'P' AND s2.type = 'P' AND s1.number BETWEEN 100 AND 999 AND s2.number BETWEEN 100 AND 999<p>После этого у нас должен быть миллион строк. Если мы выполним наш запрос для поиска строк, где последние три символа равны 333, то мы увидим сканирование.</p>
9 INSERT StagingData SELECT TOP 999999 CONVERT(varchar(200),NEWID()) + '.' + CONVERT(VARCHAR(10),s2.number) FROM master..SPT_VALUES s1 CROSS JOIN master..SPT_VALUES s2 WHERE s1.type = 'P' AND s2.type = 'P' AND s1.number BETWEEN 100 AND 999 AND s2.number BETWEEN 100 AND 999<p>После этого у нас должен быть миллион строк. Если мы выполним наш запрос для поиска строк, где последние три символа равны 333, то мы увидим сканирование.</p>
10 SET STATISTICS IO ON GO SELECT SomeColumn FROM StagingData WHERE RIGHT(SomeColumn,3) = '333' SET STATISTICS IO OFF GO (900 rows affected) Table 'StagingData'. Scan count 1, logical reads 5404, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<p>Мы получили 900 строк и 5404 чтений. Вот как выглядит план выполнения:</p>
10 SET STATISTICS IO ON GO SELECT SomeColumn FROM StagingData WHERE RIGHT(SomeColumn,3) = '333' SET STATISTICS IO OFF GO (900 rows affected) Table 'StagingData'. Scan count 1, logical reads 5404, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<p>Мы получили 900 строк и 5404 чтений. Вот как выглядит план выполнения:</p>
11 <p>Если мы всегда ищем по последним трём символам, то можем добавить вычисляемый столбец, который содержит только последние три символа, а затем добавить для него некластеризованный индекс.</p>
11 <p>Если мы всегда ищем по последним трём символам, то можем добавить вычисляемый столбец, который содержит только последние три символа, а затем добавить для него некластеризованный индекс.</p>
12 ALTER TABLE StagingData ADD RightChar as RIGHT(SomeColumn,3) GO CREATE INDEX ix_RightChar on StagingData(RightChar) GO<p>Теперь давайте посмотрим, что мы получим, если будем использовать этот новый столбец.</p>
12 ALTER TABLE StagingData ADD RightChar as RIGHT(SomeColumn,3) GO CREATE INDEX ix_RightChar on StagingData(RightChar) GO<p>Теперь давайте посмотрим, что мы получим, если будем использовать этот новый столбец.</p>
13 SET STATISTICS IO ON GO SELECT SomeColumn FROM StagingData WHERE RightChar = '333' SET STATISTICS IO OFF GO (900 rows affected) Table 'StagingData'. Scan count 1, logical reads 10, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<p><strong>Число чтений уменьшилось</strong>с 5404 до 10 и это хорошее улучшение. Вот как выглядит план выполнения.</p>
13 SET STATISTICS IO ON GO SELECT SomeColumn FROM StagingData WHERE RightChar = '333' SET STATISTICS IO OFF GO (900 rows affected) Table 'StagingData'. Scan count 1, logical reads 10, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<p><strong>Число чтений уменьшилось</strong>с 5404 до 10 и это хорошее улучшение. Вот как выглядит план выполнения.</p>
14 <p>Однако есть небольшая проблема...Мы сказали, что не будем изменять запрос.</p>
14 <p>Однако есть небольшая проблема...Мы сказали, что не будем изменять запрос.</p>
15 <p>Но что произойдет, если мы выполним первоначальный запрос? Сможет ли<strong>оптимизатор SQL Server</strong>распознать, что наш новый столбец и индекс практически то же самое, что и условие в WHERE?</p>
15 <p>Но что произойдет, если мы выполним первоначальный запрос? Сможет ли<strong>оптимизатор SQL Server</strong>распознать, что наш новый столбец и индекс практически то же самое, что и условие в WHERE?</p>
16 SET STATISTICS IO ON GO SELECT SomeColumn FROM StagingData WHERE RIGHT(SomeColumn,3) = '333' SET STATISTICS IO OFF GO (900 rows affected) Table 'StagingData'. Scan count 1, logical reads 10, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<p>Чертовски верно. Оптимизатор смог. Он использует новый индекс и столбец, хотя мы указываем исходный столбец (должно быть там встроен искусственный интеллект...шутка!).</p>
16 SET STATISTICS IO ON GO SELECT SomeColumn FROM StagingData WHERE RIGHT(SomeColumn,3) = '333' SET STATISTICS IO OFF GO (900 rows affected) Table 'StagingData'. Scan count 1, logical reads 10, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<p>Чертовски верно. Оптимизатор смог. Он использует новый индекс и столбец, хотя мы указываем исходный столбец (должно быть там встроен искусственный интеллект...шутка!).</p>
17 <p>Если вы посмотрите на план выполнения, то увидите, что это<strong>действительно поиск по индексу</strong>.</p>
17 <p>Если вы посмотрите на план выполнения, то увидите, что это<strong>действительно поиск по индексу</strong>.</p>
18 <p>Итак, что мы имеем... Иногда вы не можете изменить запрос и существующие столбцы, но вы можете добавить столбец в таблицу, и в этом случае может быть полезен рассмотренный метод.</p>
18 <p>Итак, что мы имеем... Иногда вы не можете изменить запрос и существующие столбцы, но вы можете добавить столбец в таблицу, и в этом случае может быть полезен рассмотренный метод.</p>
19 <p><em>Материал подготовлен специально для OTUS на основании статьи<a>Can adding an index make a non SARGable query SARGable?</a></em></p>
19 <p><em>Материал подготовлен специально для OTUS на основании статьи<a>Can adding an index make a non SARGable query SARGable?</a></em></p>
20  
20