web-dev-qa-db-ja.com

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

これは私が過去に何時間も研究してきた問題です。私は現代の [〜#〜] rdbms [〜#〜] 解決策によって対処されるべきだったように思えますが、まだ私が見ているものに実際に対処するものは見つかりませんでしたデータベースバックエンドを備えたWebまたはWindowsアプリケーションでの非常に一般的なニーズ。

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

ORDER BY @sortCol1, @sortCol2

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


私はあなたの何人かがすでに考えていることを知っています:「クライアントにソートをさせてください。」当然、これによりデータベースから作業がオフロードされます。ただし、私たちの場合、データベースサーバーは99%の時間で汗をかくこともありません。また、6か月ごとに行われるシステムアーキテクチャのマルチコアやその他の無数の改善もありません。この理由だけでも、データベースでソートを処理しても問題ありません。さらに、データベースはveryソートに優れています。彼らはそれのために最適化されており、それを正しくするために何年もかかっています、それを行うための言語は信じられないほど柔軟で直感的でシンプルです変更、メンテナンスなどを行います。データベースに負担がかかりそうになく、開発時間を単純化(および短縮)したい場合、これは当然の選択のように思われます。

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

しかし、これは不可能であるという点を思い出させます-むしろ、簡単ではありません。私は、以前のシステムで、動的ソートを取得するための信じられないほどハッキングの方法を実行しました。それはきれいでもないし、直感的でも、単純でも、柔軟でもありませんでしたし、初心者の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

明らかにこれは非常に単純な例です。通常、ソートをサポートする列が4つまたは5つあり、それぞれに加えて2番目または3番目の列もソートできるため(たとえば、日付が降順で、名前が昇順で2番目にソートされます)、それぞれがケースの数を事実上2倍にする方向ソート。うん...それは本当に速く毛むくじゃらになる。

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

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

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

それでも、didは機能します。


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

ストアドプロシージャ以外のソリューションでも大丈夫です。進むべき道ではないかもしれません。できれば、ストアドプロシージャ内で誰かがそれをうまくできるかどうかを知りたいのですが、そうでない場合は、ASP.NETでユーザーがデータのテーブルを動的に(双方向にも)ソートできるようにする方法をすべて処理しますか?

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

PS:動的な並べ替え、列の動的なフィルタリング/テキスト検索、ROWNUMBER()OVERによるページネーション、[〜#〜] and [ 〜#〜]try ...エラー時のトランザクションロールバックでキャッチ...


更新:

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

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

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に関する限り、異なるストアドプロシージャ間で同じデザインとフォーマットを維持してください。そうすれば、変更を加える際に少なくともきちんと認識できます。

95
Eric Z Beard

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

私のアプリケーションはこれをたくさん行いますが、それらはすべて動的にSQLを構築しています。ただし、ストアドプロシージャを扱うときは次のようにします。

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

それから、少なくとも動的な部分はアプリケーションにありますが、データベースはまだ重労働をしています。

6
Ron Savage

動的SQLは依然としてオプションです。そのオプションが現在持っているものよりも味が良いかどうかを判断する必要があります。

http://www.4guysfromrolla.com/webtech/010704-1.shtml

6
jop

サーバーには多くの予備サイクルがあるため、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で動作するはずです(事前に列を指定せずに結果セットから一時テーブルを作成します)。

4
Steven A. Lowe

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

ただし、できれば、SQL Serverからデータを取得した後、asp.net gridviewsまたはその他の組み込みソート付きオブジェクトを使用して、ソートを実行してください。または、組み込みではない場合でも(たとえば、asp.netのデータテーブルなど)。

4
dave

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

前提条件:

  1. Spには1つのSELECTステートメントのみ
  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
Matt Rogish

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

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

2
Paul Schirf

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

だから、それはそのままの形で完璧です。なぜそれを変更したいのか、あるいは「もっと良い方法はありますか」と尋ねる理由さえわかりません。本当に、それは「道」と呼ばれるべきです。その上、それはうまく機能し、プロジェクトのニーズにうまく合っているようで、おそらく今後数年間は十分に拡張可能です。データベースは課税されず、並べ替えは本当に本当に簡単なので、今後数年間はそのままです。

汗をかかない。

2
D.S.

ソートされた結果をページングする場合、動的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 などの関数を使用して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のリストを生成します。

2
dotjoe

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

2
Hank Gay

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

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

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

追加の利点として、この方法でもパフォーマンスを向上させるためのより良いSQLプランが得られます。

0
BVernon

SQLではなく、グリッド、レポートなど、結果を表示するものでソートを処理するのはどうですか?

編集:

この回答が先に採決された後のことを明確にするために、少し詳しく説明します...

クライアント側の並べ替えについては知っているが、それを避けたいと言っていました。もちろん、それはあなたの電話です。

しかし、私が指摘したいのは、クライアント側でそれを行うことで、一度データを引き出してから、あなたが望むようにそれで作業することができるということです-毎回サーバーへの往復を複数回行うのとは対照的ですソートが変更されます。

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

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

ソートを処理するためだけに、各ストアドプロシージャに大量のコードを追加する価値はありますか?繰り返しますが、あなたの呼び出し。

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

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

0
Kevin Fairchild