Заметки Дмитрия Пилюгина о Microsoft SQL Server 

Twitter RSS
Home SQL Server (все заметки) Можно ли отсортировать результаты во вью?
formats

Можно ли отсортировать результаты во вью?

Ответ: можно, но это не правильно, т.к. недокументировано.
Согласно документации предложение order by запрещено во вью (если не указан оператор top, но и тогда порядок не гарантируется) и если вам нужно получить отсортированный результат из вью — то предложение order by необходимо указывать в запросе к этому вью.
Тем не менее, отсортированные данные получить все-таки можно, т.к. «как правило» (чисто эмпирически) top совместно с order by приводит к сортировке в плане (однако т.к. это не документировано само собой нет гарантии, что так будет всегда).
Рассмотрим некоторые из способов создания такой сортировки.

Все что здесь написано относится к версиям выше 2000.

Прежде создадим неупорядоченную таблицу.

use tempdb;
if object_id('dbo.NotOrderedTable') is not null drop table dbo.NotOrderedTable
go
-- создадим таблицу и заполним ее случайными значениями
select top 100000
	val = convert(int,rand(checksum(newid()))*100000)
into dbo.NotOrderedTable
from
	[master]..spt_values m1
	cross join [master]..spt_values m2
	cross join [master]..spt_values m3
go
-- создадим вью ( в документации принят термин "представление", но уж больно долго печатать=) )
if object_id('dbo.OrderedView') is not null drop view dbo.OrderedView
go
create view dbo.OrderedView
as
select val from	dbo.NotOrderedTable
go
-- результат простого запроса из вью (результаты неупорядочены)
select * from dbo.OrderedView

Теперь помотрим способы получения из вью упорядоченных результатов.

1. Рекомендованный документацией способ

select * from dbo.OrderedView order by val

Попробуем создать сортировку прямо во вью, но т.к. создание вью без предложения топ у нас закончится неудачей еще на этапе компиляции

Msg 1033, Level 15, State 1, Procedure OrderedView, Line 3
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified.

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

2. Задать в top очень большое число которое будет заведомо больше (как вы предполагаете) того количества строк которое вернет вью.

if object_id('dbo.OrderedView') is not null drop view dbo.OrderedView
go
create view dbo.OrderedView
as
-- например так (можно и больше, например максимальный bigint)
select top 1000000000000000000 val from dbo.NotOrderedTable order by val
go
select * from dbo.OrderedView
go

Но этот способ мне кажется, не очень красивым и напоминает, по идее реализации, создание «заведомо большего массива», вместо связанного списка для хранения заранее неизвестного числа элементов (да-да встречал я и такой код, C#: int[] a = new int[100] — и мотивация, «ну их же точно не будет больше ста!» =).
По этому, хотя, скорее всего такого большого кол-ва строк никогда не будет, но все же, поищем другой способ.
Вспомним, что top позволяет указывать не только кол-во строк, но и процент. Попробуем указать 100%.

if object_id('dbo.OrderedView') is not null drop view dbo.OrderedView
go
create view dbo.OrderedView
as
select top 100 percent val from	dbo.NotOrderedTable order by val
go
select * from dbo.OrderedView
go
select @@version

Если у вас sql server 2000, то результат будет упорядоченным и на этом можно остановиться. Если у вас версия выше 2000, то, скорее всего результаты будут не отсортированы.
Дело в том, что оптимизатор «распознал» нашу хитрость и исключил сортировку из плана выполнения.
MS выпустила патч по этому поводу, он находится здесь, но чтобы это исправление возымело эффект, необходимо иметь уровень совместимости БД 8.0, что безусловно является шагом назад.

Продолжим изыскания.
Логичным будет попробовать «обмануть» оптимизатор, чтобы он не мог на этапе компиляции заранее вычислить конструкцию «select top 100 percent» и исключить ее из плана вместе с сортировкой.
Так мы приходим к способу, который не отличается изяществом, но, тем не менее, работает.

3. Задать вычисляемое выражение в top (о, мсье знает толк в извращениях)

if object_id('dbo.OrderedView') is not null drop view dbo.OrderedView
go
create view dbo.OrderedView
as
select top (100 + 0*convert(int,getdate())) percent val from dbo.NotOrderedTable order by val
go
select * from dbo.OrderedView
go

Способ работает, но уж больно не красивое выражение в top, на самом деле можно написать проще и короче. Таким образом, приходим к способу 4. Идея такая же как и у предыдущего способа.

4. Задать более короткое вычисляемое выражение в top

if object_id('dbo.OrderedView') is not null drop view dbo.OrderedView
go
create view dbo.OrderedView
as
select top (select 100) percent val from dbo.NotOrderedTable order by val
go
select * from dbo.OrderedView
go

Результаты отсортированы, способ тоже работает.
Ну, вот пожалуй и все.
Удалим тестовые данные.

drop view dbo.OrderedView
drop table dbo.NotOrderedTable

Замечание.
Единственное, что я хочу заметить, это то, что все кроме первого способа — недокументированны и использовать их нужно на свой страх и риск.
Не исключена ситуация, когда МС «без предупреждения и объявления» изменит логику оптимизатора, как это случилось при переходе с 2000 на 2005, так что используйте осторожно и помните о том, что сказано в BOL:

Примечание

При использовании предложения ORDER BY в определении представления, встроенной функции, производной таблице или вложенном запросе, предложение используется только для определения строк, возвращаемых предложением TOP. Предложение ORDER BY не гарантирует упорядочивания результатов при запросе этих конструкций, если оно не указано в самом запросе.

Скажу, что лично мне, не приходилось в работе сталкиваться с острой необходимостью, когда нужно отсортировать данные именно во вью, так что я не использую ни один из перечисленных способов и заметка написана чисто из спортивного интереса и «passion for tsql». =)

 

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Анти-спам: введите результат (цифрами) *