Динамическая сортировка в хранимых процедурах SQL - PullRequest
126 голосов
/ 29 сентября 2008

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

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

ORDER BY @sortCol1, @sortCol2

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


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

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

Но это возвращает нас к мысли, что это невозможно & mdash; точнее, не легко. С предыдущими системами я сделал невероятно хакерский способ динамической сортировки. Это не было ни красиво, ни интуитивно понятно, ни просто, ни гибко, и начинающий писатель SQL был бы потерян через несколько секунд. Уже это выглядит не столько «решением», сколько «осложнением».


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

Мы передаем целочисленное значение в качестве параметра хранимой процедуре (давайте назовем параметр просто «sort»), и из этого мы определяем кучу других переменных. Например ... скажем, сортировка равна 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

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

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

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

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

И все же сделал сработало.


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

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

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

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


Обновление:

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

Ответы [ 15 ]

2 голосов
/ 29 сентября 2008

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

0 голосов
/ 20 декабря 2018

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

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

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

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

0 голосов
/ 30 сентября 2008

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

0 голосов
/ 30 сентября 2008

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

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

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

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

0 голосов
/ 29 сентября 2008

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

EDIT:

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

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

Однако я хочу отметить, что, делая это на стороне клиента, вы можете извлекать данные ОДИН РАЗ, а затем работать с ними так, как вам хочется, - вместо того, чтобы выполнять несколько поездок назад и вперед к сервер каждый раз, когда сортировка меняется.

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...