SQLストアドプロシージャ内の動的な並べ替え


126

これは、過去に何時間も調査してきた問題です。現代のRDBMSソリューションで対処すべきだったように思えますが、データベースバックエンドを備えたWebまたはWindowsアプリケーションで信じられないほど一般的なニーズであると私が見ているものに実際に対処するものはまだ見つかりません。

動的ソートについてお話します。私のファンタジーの世界では、それは次のような単純なものでなければなりません:

ORDER BY @sortCol1, @sortCol2

これは、初心者のSQLおよびストアドプロシージャの開発者がインターネット上のフォーラム全体で行った標準的な例です。「なぜこれが可能ではないのですか?」彼らが聞く。必ず、誰かが最終的にストアドプロシージャのコンパイルされた性質、一般的な実行計画、およびパラメーターを直接ORDER BY句に入れることができない他のあらゆる種類の理由について彼らに説明しに来ます。


「クライアントに並べ替えをしてもらいましょう」と既に思っている人もいます。当然、これはデータベースから作業をオフロードします。私たちの場合でも、データベースサーバーは99%の確率で汗をかいていませんし、マルチコアでも、6か月ごとに発生するシステムアーキテクチャの他の無数の改善でもありません。この理由だけで、データベースにソートを処理させることは問題になりません。さらに、データベースは非常に選別が得意です。それらはそれに最適化されており、正しく理解するために何年もかかっています。それを行うための言語は信じられないほど柔軟で直感的でシンプルであり、何よりも初心者のSQLライターはそれを行う方法を知っており、さらに重要なことに、編集方法を知っています。変更を加えたり、メンテナンスを行ったりします。データベースに負担がかけられず、開発時間を単純化(および短縮)したい場合、これは当然の選択のようです。

次に、ウェブの問題があります。私はクライアント側でHTMLテーブルの並べ替えを行うJavaScriptをいじってみましたが、それらは必然的に私のニーズに対して十分な柔軟性を備えていません。また、データベースに過度の負担がかからず、並べ替えを非常に簡単に実行できるため、私自身のJavaScriptソーターを書き換えたり、ロールバックしたりするのにかかる時間を正当化するのに苦労しています。同じことはサーバーサイドのソートにも当てはまりますが、おそらくJavaScriptよりもはるかに優先されています。私は特にDataSetsのオーバーヘッドが好きではないので、私を訴えます。

しかし、これはそれが不可能である、またはむしろ、容易ではないという点を取り戻します。以前のシステムでは、動的な並べ替えを行うための信じられないほどハックな方法を実行しました。それはきれいでもなく、直感的でも、単純でも、柔軟でもなかったため、初心者の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変数を宣言した場合、「sort」の値に基づいて並べ替える列を使って実際に創造的になる方法を確認できます...使用するには、通常、次のようになります。信じられないほど乱雑な条項:

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

明らかに、これは非常に簡略化された例です。実際のものは、通常、並べ替えをサポートするために4つまたは5つの列があり、それぞれにさらに2番目または3番目の列を並べ替えることができます(たとえば、日付が降順で、次に名前の昇順で2番目に並べ替えられます)。ケース数を事実上2倍にする方向ソート。ええ...毛深いです

考えは、vehicleidがstoragedatetimeの前にソートされるようにソートケースを「簡単に」変更できるということです...しかし、少なくともこの単純な例では、疑似柔軟性は本当にそこで終わります。基本的に、テストに失敗した各ケース(今回はsortメソッドが適用されないため)はNULL値をレンダリングします。したがって、次のように機能する句が作成されます。

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

あなたはアイデアを得ます。SQL Serverは、by句の順序でnull値を効果的に無視するため、機能します。これは、SQLの基本的な動作知識を持つ人なら誰でも見ることができるように、維持するのが非常に困難です。もし私があなたのどれかを失ったとしても、気分を悪くしないでください。それを機能させるのに長い時間を要し、それを編集したり、そのような新しいものを作成しようとしたりしても混乱します。ありがたいことに、頻繁に変更する必要はありません。そうしないと、すぐに「トラブルに値しない」状態になります。

それでもうまくいきまし


私の質問はそれです: より良い方法はありますか?

ストアドプロシージャ以外のソリューションでも大丈夫です。解決策にはならないかもしれません。できれば、ストアドプロシージャ内でもっとうまくできるかどうか知りたいのですが、できない場合は、ASP.NETを使用してユーザーがデータのテーブルを(双方向でも)動的に並べ替えられるようにするにはどうすればよいですか?

そして、長い質問を読んで(または少なくともスキミングして)くれてありがとう!

PS:私はROWNUMBER()OVER経由でストアドプロシージャそのサポートダイナミックソート、列の動的なフィルタリング/テキスト検索、ページネーションの私の例を示し、なかった喜んでいるしてみてください...キャッチをトランザクションがエラーにrollbackingで... 「巨大なサイズ」はそれらを説明し始めません。


更新:

  • 動的SQLは避けたい。文字列を一緒に解析し、その文字列でEXECを実行すると、そもそもストアドプロシージャを作成する多くの目的が損なわれます。そのようなことをすることの短所は、少なくともこれらの特別な動的ソートの場合には価値がないのではないかと思うことがあります。それでも、そのような動的SQL文字列を実行するときはいつも、いつも汚れていると感じます。たとえば、クラシックASPの世界にまだ住んでいるようなものです。
  • そもそもストアドプロシージャが必要な理由の多くは、セキュリティのためです。私はセキュリティ上の懸念について電話をかけることはできず、解決策を提案するだけです。SQL Server 2005では、個々のストアドプロシージャのスキーマレベルで(必要に応じてユーザーごとに)アクセス許可を設定し、テーブルに対するクエリを直接拒否できます。このアプローチの長所と短所を批判することはおそらく別の問題ですが、やはり私の決定ではありません。私はただのコードモンキーです。:)

stackoverflow.com/questions/3659981/…も参照してください-SQL Serverの動的なORDER BYと混合したデータ型
LCJ 2013

動的SQLはFARの優れた方法です... IF [これは大きなIFです]データレイヤーは厳密で、動的SQLは、RDBMSルールが完全な形式で厳密にプログラムされたシステムによって生成されます。アンアルゴリズム的にエンジニアデータベースのアーキテクチャは...美しさのものである
hajikelist

回答:


97

ええ、それは苦痛です、そしてあなたがそれをしている方法は私がしていることに似ています:

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を構築するよりもはるかに優れており、DBAにとってスケーラビリティとメンテナンスの悪夢に変わります。

私がコードから行うことは、ページングと並べ替えをリファクタリングすることです。そのため、少なくとも@SortExprとの値を入力することで、そこに多くの繰り返しはありません@SortDir

SQLに関する限り、異なるストアドプロシージャ間でデザインとフォーマットを同じに保つことで、変更を加えたときに少なくともきちんと認識できるようにします。


1
丁度。私の目標は、5000の大きなvarchar文字列でEXECコマンドを実行しないようにすることでした。スキーマレベルでアクセス許可を設定できるため、セキュリティを強化する場合にのみ、ストアドプロシージャを介してすべての処理を実行する必要があります。私たちの場合、スケーラビリティとパフォーマンスの向上はプラスです。
Sean Hanley、

1
{セキュリティ、スケーラビリティ、パフォーマンス}に保守性を追加します。DBに対して動的SQLを実行する3つまたは4つのアプリを用意すると、特にアプリの古さと開発者が進むにつれて、何も変更できなくなります。execと動的SQLは悪です。
エリックZビアード

それだけです---まだ実行中のすべてのClassic ASP Webアプリと、多数のAccess VBアプリがまだ流通しているため、ここに到着する前からすでに行っています。私はけいれんし、それらのいずれかでメンテナンスを実行する必要があるときはいつでも、明白な間違いを修正するという衝動を抑えなければなりません。
Sean Hanley、

1
私はSortExprに方向をエンコード以外これは、私も何をすべきかです:ORDER BY CASE WHENソート=「姓」THEN姓END ASC、CASEソート=「-FirstName」THEN姓END DESC
マイケル・ブレイ

これは、DBAとソフトウェアエンジニアの両方にとって悪夢です。したがって、情報スキーマに基づいて表現力豊かなSQLステートメントを生成する動的で厳密なシステムを使用できる代わりに、ハードコード化された意味不明の厄介な一群があります。それは最高の状態で貧弱なプログラミングです。
ハジケリスト2015年

23

このアプローチは、並べ替え可能な列が順序で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

これは良い答えのように見えましたが、並べ替え可能な列のデータ型が異なる場合は機能しないようです
SlimSim


6

私のアプリケーションはこれを頻繁に実行しますが、すべて動的にSQLを構築しています。しかし、私がストアドプロシージャを扱うときは、次のようにします。

  1. ストアドプロシージャを、値のテーブルを返す関数にします(並べ替えなし)。
  2. 次に、アプリケーションコードでを実行して、select * from dbo.fn_myData() where ... order by ...そこで動的に並べ替え順序を指定できるようにします。

次に、少なくとも動的な部分がアプリケーションに含まれていますが、データベースは依然として大きな作業を行っています。


1
これはおそらく、動的SQLとストアドプロシージャを一緒に使用することの間で私が見た中で最高の妥協案です。私はそれが好きです。私はいつか同じようなアプローチで実験するかもしれませんが、そのような変更は私たちの既存の進行中のプロジェクトのどれでも禁止されます。
Sean Hanley

1
データを返す表関数の代わりにローカルテーブル変数を使用して同じことを達成できます。デバッグ情報を出力できるため、ローカルテーブルは関数よりも柔軟です。
Sanjay Zalke 2011

5

特定のジョブで動的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列のフィールドを連結したり、数学関数や日付関数で順序を逆にしたりできます。

できれば、私はasp.netグリッドビューまたは組み込みの並べ替えを備えた他のオブジェクトを使用して、Sql-Serverからデータを取得した後で並べ替えを実行することをお勧めします。または、それが組み込みではない場合でも、たとえばasp.netのデータテーブルなど。


4

これをハックする方法はいくつかあります。

前提条件:

  1. sp内のSELECTステートメントは1つだけ
  2. 並べ替えを省略します(またはデフォルトを使用します)

次に、一時テーブルに挿入します。

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


4

サーバーには多くのスペアサイクルがあるため、3番目のオプションがある可能性があります-ヘルパープロシージャを使用して、一時テーブルを介して並べ替えを行います。何かのようなもの

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では機能するはずです(列を事前に指定せずに結果セットから一時テーブルを作成します)。


2

ある時点で、ストアドプロシージャから離れ、この種のハッカーを回避するためにパラメータ化されたクエリを使用するだけの価値はありませんか?


1
特定のケースでは、それらは釘の大槌であるかもしれませんが、多くの場合、ストアドプロシージャに直接アクセス許可(特にEXECUTE)を設定し、SQLクエリをSELECTであっても、テーブルに対して直接許可しないようにします。ハッカーもあまり好きではありませんが、セキュリティは私の要求ではありません。
Sean Hanley、

1
これが、非常に多くの人々がオブジェクトリレーショナルマッピングに移行する理由です。ソートのための不必要なラウンドトリップ、膨大な数の列に対する膨大なCASEブロックの巨大なCASEブロック。実際に更新する必要があるのは1つだけです。
ピッツバーグDBA、

ORMは全文検索をサポートしていないため、ORM(EF)からストアドプロシージャに移行しています。
Ronnie Overby 2013年

@RonnieOverbyフルテキスト検索は、多くの場合、Luceneなどの専用ソリューションによって適切に処理されます。
ハンクゲイ

@HankGayエンティティフレームワークもLuceneをサポートしていないという不思議な感覚があります。
Ronnie Overby 2013

2

クライアント側を使用することに同意します。しかし、それはあなたが聞きたい答えではないようです。

だから、それはそれがそうである方法で完璧です。なぜ変更したいのか、「もっと良い方法はありますか?」本当に、それは「道」と呼ばれるべきです。その上、それは機能し、プロジェクトのニーズにうまく適合しているようであり、おそらく今後数年間は十分に拡張可能です。データベースに負担がかかることはなく、並べ替えは本当に簡単なので、今後何年もその方法を維持する必要があります。

私はそれを発汗しないでしょう。


私はWindowsアプリでそのルートを進むので、クライアント側には問題はありません。しかし、ウェブアプリはどうですか?JavaScriptソリューションには本当に十分な柔軟性があるとは思いません。そして、そうです、私たちのやり方と同じように機能しますが、SQLの悪夢です。もちろん、もっと良い方法があるかどうか知りたいです。
Sean Hanley

新しい(2.0以降).NETコントロールに組み込まれています。または、独自に作成してデータビューに適用することもできます。 msdn.microsoft.com/en-us/library/hwf94875(VS.80).aspx
DS

2
私の問題は、スケーラビリティとパフォーマンスの1つです。クライアント側またはWebサーバー側のソートを行うには、一度にページに表示する10または15ではなく、すべてのデータをロードする必要があります。これは長期的に見ると非常にコストがかかりますが、データベースの並べ替えにはそれがありません。
Sean Hanley、

2

ソートされた結果をページングする場合は、動的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から#列の範囲内にあることを確認する必要があります。あなたも、列番号のリストにこれを拡張し、のような関数使用してintのテーブルにそれを解析することができ、これを。次に、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

1つの欠点は、クライアント側で各列の順序を覚えておく必要があることです。特に、すべての列を表示しない場合、または列を別の順序で表示する場合。クライアントがソートする場合は、列名を列の順序にマップし、intのリストを生成します。


動的なレポートクエリの作成には、sp_executesqlを使用します。非常に効果的。SQLはアプリケーションから構築できませんが、パラメーターは必要な場所に挿入され、通常どおりに実行されます。
Josh Smeaton、

2

クライアント側で並べ替えを行うことに反対する議論は、大量のデータとページネーションです。行数が簡単に表示できる範囲を超えると、スキップ/テイクの一部としてソートされることが多く、SQLで実行することになります。

Entity Frameworkでは、ストアドプロシージャを使用してテキスト検索を処理できます。同じ並べ替えの問題が発生した場合、解決策は、検索にストアドプロシージャを使用して、一致のIDキーセットのみを返すことです。次に、リスト(含まれる)のIDを使用して、データベースに対して(ソートを使用して)再クエリを実行します。IDセットがかなり大きい場合でも、EFはこれをかなりうまく処理します。はい、これは2回のラウンドトリップですが、DBで常に並べ替えを維持できるため、状況によっては重要であり、ストアドプロシージャに大量のロジックを記述できなくなります。


1

SQLではなく、結果を表示するもの(グリッド、レポートなど)でソートを処理してみませんか?

編集:

この回答が以前に反対票を投じられてからのことを明確にするために、少し詳しく説明します...

あなたはクライアント側のソートについて知っていたが、それを避けたいと述べました。もちろんそれがあなたの呼びかけです。

しかし、私が指摘したいのは、クライアント側でそれを行うことで、一度データをプルし、その後、好きなように操作できることです。ソートが変更されます。

現在、SQL Serverに負荷がかかっていないのですばらしいです。それはいけません。しかし、それがまだ過負荷になっていないからといって、それが永遠にそのように留まるという意味ではありません。

Webに表示するために新しいASP.NETのものを使用している場合、その多くのものが既に組み込まれています。

並べ替えを処理するためだけに、各ストアドプロシージャに多くのコードを追加する価値はありますか?もう一度、あなたの電話。

私が最終的にそれをサポートするのは私ではありません。ただし、ストアドプロシージャで使用されるさまざまなデータセット内で列が追加または削除されるときに(CASEステートメントの変更が必要)、または2つの列でソートする代わりに、3つが必要であるとユーザーが判断したときに、何が関係するかを考えてみてください-このメソッドを使用するすべてのストアドプロシージャを更新する必要があります。

私にとっては、機能するクライアント側のソリューションを取得し、それをユーザー向けの少数のデータ表示に適用して、それで完了するのは価値があります。新しい列が追加された場合、それはすでに処理されています。ユーザーが複数の列で並べ替える場合は、2列または20列で並べ替えることができます。


それは正しい方法ですが、「より良い方法」とは見なされません
DS

それでも私はまだ自分の並べ替えをC#またはJavaScriptのいずれかで記述しており、SQLの方がはるかに簡単で高速なはずです。したがって私の質問。私は明らかな何かを欠いているだけですか、それとも私たちが取り組んでいるすべてのいまいましいアプリ(C#またはJavaScript)で独自のカスタムソートを作成し続けているのですか?
Sean Hanley、

3
ちょっと待って、何万行もの結果セットはどうですか?すべてのデータをクライアントに返すことはできません。データベースでページングとソートを行う必要があります。
Eric Z Beard

ヤディン、わかった。しかし、グリッド用の汎用ソーターを取得したら、それをすべてのものに使用します。
ケビンフェアチャイルド

Eric、True ...そのような場合、追加の処理が必要であり、SQL内ではそれが理にかなっているでしょう。それは、正しい問題と間違った問題にはほど遠いものです。場合によっては、SQLで意味をなすこともあれば、クライアントで意味をなすこともあります。
ケビンフェアチャイルド

1

パーティーに遅れて申し訳ありませんが、動的SQLを避けたいが、SQLが提供する柔軟性を望んでいる人のための別のオプションがあります。

SQLを動的に動的に生成する代わりに、考えられるすべてのバリエーションに対して一意のプロシージャを生成するコードを記述します。次に、コードにメソッドを記述して検索オプションを調べ、呼び出す適切なプロシージャを選択させることができます。

バリエーションが少ない場合は、手動でプロシージャを作成できます。しかし、多くのバリエーションがある場合、それらすべてを維持する必要はなく、代わりにprocジェネレーターを維持して、それらを再作成させます。

追加の利点として、この方法でもパフォーマンスが向上するSQLプランが得られます。


-1

このソリューションは.NETでしか機能しない可能性があります。

SQL order by句の最初の並べ替え順序でデータをC#にフェッチし、そのデータをDataViewに入れて、Session変数にキャッシュし、それを使用してページを作成します。

ユーザーが列見出しをクリックしてソート(またはページ、またはフィルター)すると、データベースに戻りません。代わりに、キャッシュされたDataViewに戻り、その "Sort"プロパティを、動的SQLと同じように、動的に構築する式に設定します。(「RowFilter」プロパティを使用して、同じ方法でフィルタリングを行います)。

http://ifdefined.com/btnet/bugs.aspxにある私のアプリBugTracker.NETのデモで、動作を確認できます。


甘い!バグトラッカー.NETはすばらしい!
ディジグル2008年

-7

必要でない限り、SQL Serverの並べ替えは避けてください。アプリサーバーまたはクライアント側でソートしないのはなぜですか?また、.NET Genericsは並外れたソートを行います


6
スケーラビリティのため。数千行は問題ありませんが、1万行をプルダウンしてソートする必要はありません。以上。また、ページングについてはどうですか?表示する必要があるものだけを取得したいことがよくあります。事後の24056の行21〜30のソートは正しくありません。
Sean Hanley、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.