Динамическая сортировка в хранимых процедурах SQL

Это проблема, которую я потратил на то, чтобы исследовать в прошлом. Мне кажется, что это должно было быть рассмотрено современными решениями RDBMS, но пока я не нашел ничего, что действительно касалось того, что я вижу быть невероятно распространенной потребностью в любом приложении Web или Windows с базовым контентом базы данных.

Я говорю о динамической сортировке. В моем мире фантазий это должно быть так просто, как что-то вроде:

ORDER BY @sortCol1, @sortCol2

Это канонический пример, предоставленный разработчиками newbie SQL и Хранимой процедурой на всех форумах через Интернет. "Почему это невозможно?" они спрашивают. Неизменно, кто-то в конце концов приходит, чтобы прочесть их о скомпилированном характере хранимых процедур, планов выполнения в целом и всех других причинах, по которым невозможно поставить параметр непосредственно в предложение ORDER BY.


Я знаю, что некоторые из вас уже думают: "Пусть клиент сделает сортировку". Естественно, это разгружает работу из вашей базы данных. Однако в нашем случае наши серверы баз данных даже не разбивают пот в 99% случаев, и они даже не являются многоядерными, а также любые другие незначительные улучшения в архитектуре системы, которые происходят каждые 6 месяцев. По этой причине, если сортировка сортировки наших баз данных не будет проблемой. Кроме того, базы данных очень хороши при сортировке. Они оптимизированы для этого, и у них были годы, чтобы понять это, язык для этого невероятно гибкий, интуитивно понятный и простой, и, прежде всего, любой начинающий разработчик SQL знает, как это сделать, и тем более важно знать, как его редактировать, вносить изменения, выполнять техническое обслуживание и т.д. Когда ваши базы данных далеки от налогообложения, и вы просто хотите упростить (и сократить) время разработки, это кажется очевидным выбором.

Тогда есть веб-проблема. Я играл с JavaScript, который будет выполнять сортировку HTML-таблиц на стороне клиента, но они неизбежно недостаточно гибки для моих нужд, и, опять же, поскольку мои базы данных не облагаются налогом и могут действительно сортировать действительно очень легко, я имеют трудное время, оправдывая время, которое потребуется для повторной записи или сортировки соавторов JavaScript. Как правило, это относится к сортировке на стороне сервера, хотя это, вероятно, гораздо предпочтительнее, чем JavaScript. Я не тот, кому особенно нравится накладные расходы DataSets, поэтому подавайте в суд на меня.

Но это приводит к тому, что это невозможно...— или, скорее, не легко. Я сделал, с предыдущими системами, невероятно рушительный способ получения динамической сортировки. Это было некрасиво, не интуитивно, просто или гибко, а начинающий автор SQL-кода терялся в течение нескольких секунд. Уже сейчас это выглядит не столько "решением", сколько "осложнением".


Следующие примеры не предназначены для раскрытия каких-либо передовых методов или хорошего стиля кодирования или чего-либо еще, и они не свидетельствуют о моих способностях как программиста T-SQL. Они такие, какие они есть, и я полностью признаю, что они сбивают с толку, плохая форма и просто хак.

Мы передаем целочисленное значение в качестве параметра хранимой процедуры (позвольте этому параметру просто "сортировать" ), и из этого мы определим связку других переменных. Например... let say sort is 1 (или по умолчанию):

DECLARE @sortCol1 AS varchar(20)
DECLARE @sortCol2 AS varchar(20)
DECLARE @dir1 AS varchar(20)
DECLARE @dir2 AS varchar(20)
DECLARE @col1 AS varchar(20)
DECLARE @col2 AS varchar(20)

SET @col1 = 'storagedatetime';
SET @col2 = 'vehicleid';

IF @sort = 1                -- Default sort.
BEGIN
    SET @sortCol1 = @col1;
    SET @dir1 = 'asc';
    SET @sortCol2 = @col2;
    SET @dir2 = 'asc';
END
ELSE IF @sort = 2           -- Reversed order default sort.
BEGIN
    SET @sortCol1 = @col1;
    SET @dir1 = 'desc';
    SET @sortCol2 = @col2;
    SET @dir2 = 'desc';
END

Вы уже можете видеть, как если бы я объявлял больше переменных @colX для определения других столбцов, я мог бы действительно создавать объявления с сортировкой столбцов на основе значения "сортировка"... чтобы использовать его, он обычно заканчивается поиском как и следующая невероятно беспорядочная статья:

ORDER BY
    CASE @dir1
        WHEN 'desc' THEN
            CASE @sortCol1
                WHEN @col1 THEN [storagedatetime]
                WHEN @col2 THEN [vehicleid]
            END
    END DESC,
    CASE @dir1
        WHEN 'asc' THEN
            CASE @sortCol1
                WHEN @col1 THEN [storagedatetime]
                WHEN @col2 THEN [vehicleid]
            END
    END,
    CASE @dir2
        WHEN 'desc' THEN
            CASE @sortCol2
                WHEN @col1 THEN [storagedatetime]
                WHEN @col2 THEN [vehicleid]
            END
    END DESC,
    CASE @dir2
        WHEN 'asc' THEN
            CASE @sortCol2
                WHEN @col1 THEN [storagedatetime]
                WHEN @col2 THEN [vehicleid]
            END
    END

Очевидно, это очень урезанный пример. Реальный материал, так как у нас обычно есть четыре или пять столбцов для поддержки сортировки, каждый с возможным вторичным или даже третьим столбцом для сортировки в дополнение к этому (например, дата по убыванию, затем сортировка по порядку по имени возрастает) и каждый поддерживающий би- направленная сортировка, которая эффективно удваивает количество случаев. Да... он быстро причесывается.

Идея состоит в том, что можно "легко" изменить случаи сортировки, так что vehicleid сортируется до хранения в течение времени... но псевдо-гибкость, по крайней мере в этом простом примере, действительно заканчивается. По сути, каждый случай, который не прошел тест (поскольку наш метод сортировки не применяется к нему на этот раз) отображает значение NULL. И таким образом вы получаете предложение, которое работает следующим образом:

ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah

Вы получаете идею. Он работает, потому что SQL Server эффективно игнорирует нулевые значения в соответствии с предложениями. Это невероятно сложно поддерживать, поскольку любой, у кого есть какие-либо базовые знания SQL, возможно, увидит. Если я потерял кого-то из вас, не чувствуйте себя плохо. Нам потребовалось много времени, чтобы заставить его работать, и мы все еще путаемся, пытаясь его отредактировать или создать новые. К счастью, ему не нужно часто меняться, иначе оно быстро станет "не стоило бы того".

Но это действительно сработало.


Мой вопрос: есть лучший способ?

У меня все в порядке с решениями, кроме хранимых процедур, поскольку я понимаю, что это может быть просто не выход. Предпочтительно, я хотел бы знать, сможет ли кто-нибудь сделать это лучше в хранимой процедуре, но если нет, как вы справляетесь с тем, чтобы позволить пользователю динамически сортировать таблицы данных (в двунаправленном порядке) с помощью ASP.NET? p >

И спасибо за то, что вы прочитали (или, по крайней мере, скимминг) такой длинный вопрос!

PS: Радуйтесь, я не показывал свой пример хранимой процедуры, поддерживающей динамическую сортировку, динамическую фильтрацию/текстовый поиск столбцов, разбиение на страницы с помощью ROWNUMBER() OVER и попытка... catch с откатом транзакций при ошибках... "behemoth-size" даже не начинает описывать их.


Update:

  • Я бы хотел избежать динамического SQL. Разбор строки вместе и запуск EXEC на ней сильно осложняет задачу сохранения хранимой процедуры. Иногда я удивляюсь, хотя если бы недостатки такого дела не стоили бы этого, по крайней мере в этих специальных случаях динамической сортировки. Тем не менее, я всегда чувствую себя грязным, когда я делаю динамические строки SQL, подобные this — как я все еще живу в мире классического ASP.
  • Большая причина, по которой мы хотим, чтобы хранимые процедуры в первую очередь были для безопасности. Я не собираюсь отвечать на вопросы безопасности, предлагаю только решения. С SQL Server 2005 мы можем устанавливать разрешения (на основе каждого пользователя, если необходимо) на уровне схемы для отдельных хранимых процедур, а затем напрямую отклонять любые запросы к таблицам. Критиковать плюсы и минусы этого подхода, возможно, по другому вопросу, но опять же это не мое решение. Я просто обезьяна кодов.:)

Ответ 1

Да, это боль, и то, как ты это делаешь, похоже на то, что я делаю:

order by
case when @SortExpr = 'CustomerName' and @SortDir = 'ASC' 
    then CustomerName end asc, 
case when @SortExpr = 'CustomerName' and @SortDir = 'DESC' 
    then CustomerName end desc,
...

Для меня это все еще намного лучше, чем создание динамического SQL из кода, который превращается в кошмар масштабируемости и обслуживания для администраторов баз данных.

То, что я делаю из кода, это рефакторинг подкачки и сортировки, поэтому у меня по крайней мере не так много повторений с @SortExpr значений для @SortExpr и @SortDir.

Что касается SQL, сохраняйте одинаковый дизайн и форматирование между различными хранимыми процедурами, чтобы он был хотя бы аккуратным и узнаваемым при внесении изменений.

Ответ 2

Этот подход позволяет сортировать столбцы дважды дублироваться в порядке и является немного более читаемым IMO:

SELECT
  s.*
FROM
  (SELECT
    CASE @SortCol1
      WHEN 'Foo' THEN t.Foo
      WHEN 'Bar' THEN t.Bar
      ELSE null
    END as SortCol1,
    CASE @SortCol2
      WHEN 'Foo' THEN t.Foo
      WHEN 'Bar' THEN t.Bar
      ELSE null
    END as SortCol2,
    t.*
  FROM
    MyTable t) as s
ORDER BY
  CASE WHEN @dir1 = 'ASC'  THEN SortCol1 END ASC,
  CASE WHEN @dir1 = 'DESC' THEN SortCol1 END DESC,
  CASE WHEN @dir2 = 'ASC'  THEN SortCol2 END ASC,
  CASE WHEN @dir2 = 'DESC' THEN SortCol2 END DESC

Ответ 3

Динамический SQL по-прежнему является опцией. Вам просто нужно решить, является ли этот вариант более привлекательным, чем то, что вы в настоящее время имеете.

Вот статья, которая показывает, что: http://www.4guysfromrolla.com/webtech/010704-1.shtml.

Ответ 4

Мои приложения делают это много, но все они динамически создают SQL. Однако, когда я занимаюсь хранимыми процедурами, я делаю это:

  • Сделать хранимую процедуру функцией, возвращающей таблицу ваших значений - без сортировки.
  • Затем в коде приложения введите select * from dbo.fn_myData() where ... order by ..., чтобы динамически указать порядок сортировки.

Тогда по крайней мере динамическая часть находится в вашем приложении, но база данных все еще делает тяжелый подъем.

Ответ 5

Может быть третий вариант, так как ваш сервер имеет множество запасных циклов - используйте вспомогательную процедуру для сортировки с помощью временной таблицы. Что-то вроде

create procedure uspCallAndSort
(
    @sql varchar(2048),        --exec dbo.uspSomeProcedure arg1,'arg2',etc.
    @sortClause varchar(512)    --comma-delimited field list
)
AS
insert into #tmp EXEC(@sql)
declare @msql varchar(3000)
set @msql = 'select * from #tmp order by ' + @sortClause
EXEC(@msql)
drop table #tmp
GO

Предостережение: я не тестировал это, но он должен "работать" в SQL Server 2005 (который создаст временную таблицу из набора результатов без предварительного указания столбцов.)

Ответ 6

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

Предпосылки:

  • Только одна инструкция SELECT в sp
  • Не оставляйте сортировку (или по умолчанию)

Затем вставьте в временную таблицу:

create table #temp ( your columns )

insert #temp
exec foobar

select * from #temp order by whatever

Способ №2: настройте связанный сервер на себя, затем выберите из него с помощью openquery: http://www.sommarskog.se/share_data.html#OPENQUERY

Ответ 7

Метод хранимой процедуры (взломать?) Я использовал, чтобы избежать динамического SQL для определенных заданий, должен иметь уникальный столбец сортировки. То есть.

SELECT
   name_last,
   name_first,
   CASE @sortCol WHEN 'name_last' THEN [name_last] ELSE 0 END as mySort
FROM
   table
ORDER BY 
    mySort

Это легко поддается представлению - вы можете конкатрировать поля в своей колонке mySort, отменить порядок с помощью функций математики или даты и т.д.

Предпочтительно, однако, я использую свои gridviews asp.net или другие объекты со встроенной сортировкой для сортировки для меня ПОСЛЕ получения данных для Sql-Server. Или даже если он не встроен - например, datatables и т.д. В asp.net.

Ответ 8

В какой-то момент не стоит ли уходить от хранимых процедур и просто использовать параметризованные запросы, чтобы избежать такого рода хакеров?

Ответ 9

Согласен, используйте клиентскую сторону. Но, похоже, это не тот ответ, который вы хотите услышать.

Итак, это прекрасно, как есть. Я не знаю, почему вы хотели бы изменить его или даже спросить: "Есть ли лучший способ". Действительно, его следует называть "Путь". Кроме того, он, кажется, работает и удовлетворяет потребностям проекта просто отлично и, вероятно, будет достаточно расширяемым на долгие годы. Поскольку ваши базы данных не облагаются налогом, и сортировка действительно очень проста, он должен оставаться таким образом на долгие годы.

Я бы не потел.

Ответ 10

Когда вы выполняете сортировку по поисковым вызовам, динамический SQL является хорошим вариантом. Если вы параноик относительно SQL-инъекции, вы можете использовать номера столбцов вместо имени столбца. Я сделал это, прежде чем использовать отрицательные значения для нисходящего. Что-то вроде этого...

declare @o int;
set @o = -1;

declare @sql nvarchar(2000);
set @sql = N'select * from table order by ' + 
    cast(abs(@o) as varchar) + case when @o < 0 then ' desc' else ' asc' end + ';'

exec sp_executesql @sql

Тогда вам просто нужно убедиться, что число находится внутри 1 до # столбцов. Вы даже можете развернуть это до списка номеров столбцов и разделить на таблицу с помощью функции, например this. Затем вы создадите порядок order by так:

declare @cols varchar(100);
set @cols = '1 -2 3 6';

declare @order_by varchar(200)

select @order_by = isnull(@order_by + ', ', '') + 
        cast(abs(number) as varchar) + 
        case when number < 0 then ' desc' else '' end
from dbo.iter_intlist_to_tbl(@cols) order by listpos

print @order_by

Один недостаток - вы должны помнить порядок каждого столбца на стороне клиента. Особенно, когда вы не показываете все столбцы или вы показываете их в другом порядке. Когда клиент хочет сортировать, вы сопоставляете имена столбцов с порядком столбцов и генерируете список int.

Ответ 11

Аргумент против сортировки на стороне клиента - данные большого объема и разбиение на страницы. Как только количество строк будет превышать то, что вы можете легко отобразить, вы часто сортируете как часть пропусков/выходов, которые вы, вероятно, захотите запустить в SQL.

Для Entity Framework вы можете использовать хранимую процедуру для обработки текстового поиска. Если вы столкнулись с такой же проблемой сортировки, решение, которое я видел, это использовать сохраненный процесс для поиска, возвращая только ключ ключа, установленный для соответствия. Затем повторите запрос (с сортировкой) против db, используя идентификаторы в списке (содержит). EF справляется с этим довольно хорошо, даже когда набор ID довольно большой. Да, это две круглые поездки, но это позволяет вам всегда сохранять свою сортировку в БД, что может быть важно в некоторых ситуациях и не позволяет писать логическую логику в хранимой процедуре.

Ответ 12

Как обрабатывать сортировку по материалам, отображающим результаты - сетки, отчеты и т.д., а не на SQL?

EDIT:

Чтобы прояснить ситуацию, так как этот ответ опустился - проголосовали раньше, я немного подробнее...

Вы заявили, что знаете о сортировке на стороне клиента, но хотели избегать этого. Это ваш звонок, конечно.

Тем не менее, я хочу отметить, что, делая это на стороне клиента, вы можете извлекать данные ONCE, а затем работать с ним, как хотите, и делать несколько поездок туда и обратно сервер при каждом изменении сортировки.

Ваш SQL Server сейчас не облагается налогом, и это потрясающе. Этого не должно быть. Но только потому, что он не перегружен, это еще не значит, что он будет оставаться навсегда.

Если вы используете какой-либо новый материал ASP.NET для отображения в Интернете, многие из этих вещей уже запекаются прямо.

Стоит ли добавлять столько кодов для каждой хранимой процедуры для обработки сортировки? Опять же, ваш звонок.

Я не тот, кто в конечном итоге будет отвечать за его поддержку. Но подумайте о том, что будет задействовано, поскольку столбцы добавляются/удаляются в разных наборах данных, используемых хранимыми процедурами (требующих изменений в операциях CASE), или когда вдруг вместо сортировки по двум столбцам пользователь решает, что им нужны три - требуя, чтобы вы обновили все свои хранимые процедуры, которые используют этот метод.

Для меня это стоит того, чтобы получить рабочее клиентское решение и применить его к нескольким отображаемым пользователем дисплеям данных и сделать с ними. Если добавлен новый столбец, он уже обрабатывается. Если пользователь хочет сортировать по нескольким столбцам, они могут сортировать два или двадцать из них.

Ответ 13

Извините, что опоздал на вечеринку, но здесь еще один вариант для тех, кто действительно хочет избежать динамического SQL, но хочет гибкости, которую он предлагает:

Вместо того, чтобы динамически генерировать sql на лету, напишите код, чтобы сгенерировать уникальный процесс для каждого возможного варианта. Затем вы можете написать метод в коде, чтобы просмотреть параметры поиска и выбрать подходящий метод для вызова.

Если у вас есть только несколько вариантов, вы можете просто создать прокы вручную. Но если у вас много вариантов, то вместо того, чтобы поддерживать их все, вы просто должны поддерживать свой генератор процессов, чтобы он воссоздал их.

В качестве дополнительного преимущества, вы получите лучшие планы sql для лучшей производительности, делая это также.

Ответ 14

Это решение может работать только в .NET, я не знаю.

Я извлекаю данные в С# с начальным порядком сортировки в предложении SQL order by, помещаю эти данные в DataView, кеширую его в переменной Session и использую для сборки страницы.

Когда пользователь нажимает заголовок столбца для сортировки (или страницы или фильтрации), я не возвращаюсь в базу данных. Вместо этого я возвращаюсь к своему кэшированному DataView и устанавливаю его свойство "Сортировка" в выражении, которое я строю динамически, так же, как и для динамического SQL. (Я делаю фильтрацию таким же образом, используя свойство "RowFilter" ).

Вы можете увидеть/почувствовать его работу в демонстрации моего приложения BugTracker.NET на http://ifdefined.com/btnet/bugs.aspx

Ответ 15

Вам следует избегать сортировки SQL Server, если это необходимо. Почему не сортировать сервер приложений или клиента? Также .NET Generics делает исключительную сортировку