web-dev-qa-db-ja.com

ORDER BYと文字と数字の混合文字列の比較

通常は「自然に」ソートする必要がある数字と文字の混合ストリングである値について、いくつかのレポートを作成する必要があります。たとえば、 「P7B18」または「P12B3」。 @文字列は、ほとんどが文字のシーケンスであり、次に数字が交互になります。ただし、これらのセグメントの数とそれぞれの長さは異なる場合があります。

これらの数値部分を数値順にソートしてください。明らかに、これらの文字列値をORDER BYで直接処理する場合、「P12B3」は「P7B18」の前に来るはずです。 「P7」は当然「P12」に先行します。

範囲の比較もできるようにしたいと思います。 @bin < 'P13S6'など。浮動小数点数や負の数を処理する必要はありません。これらは厳密に私たちが扱っている負でない整数になります。文字列の長さとセグメント数は、上限が固定されていないため、潜在的に任意である可能性があります。

私たちのケースでは、文字列の大文字小文字の区別は重要ではありませんが、照合に対応した方法でこれを行う方法がある場合、他の人が便利だと思うかもしれません。これらすべての最も醜い部分は、WHERE句で順序付けと範囲フィルタリングの両方を実行できるようにすることです。

これをC#で実行している場合、これは非常に単純なタスクです。いくつかの解析を行ってアルファを数値から分離し、IComparableを実装すれば、基本的にはこれで完了です。もちろん、SQL Serverは、少なくとも私の知る限り、同様の機能を提供していないようです。

誰かがこれを機能させるための良いトリックを知っていますか? IComparableを実装し、これを期待どおりに動作させるカスタムCLR型を作成する、あまり公表されていない機能はありますか?私はまた、愚かなXMLトリック(リストの連結も参照)に反対していません。また、サーバーでCLR正規表現のマッチング/抽出/置換ラッパー関数も使用できます。

編集:もう少し詳細な例として、データが次のように動作するようにしたいと思います。

SELECT bin FROM bins ORDER BY bin

bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0

つまり、文字列をすべての文字またはすべての数字のトークンに分割し、それらをそれぞれアルファベット順または数値順に並べ替えます。左端のトークンが最も重要な並べ替え条件です。先ほど触れたように、IComparableを実装した場合の.NETの簡単な説明ですが、SQL Serverでそのようなことをどのように(または可能か)行うことができるかはわかりません。それは確かに、私が10年程度の作業で遭遇したものではありません。

9
db2

文字列内の数値を実際の数値としてソートするための賢明で効率的な手段が必要ですか? Microsoft Connectの提案に投票することを検討してください: 「自然な並べ替え」をサポート/照合オプションとしてDIGITSASNUMBERS


これを行うための組み込みの簡単な方法はありませんが、可能性は次のとおりです。

文字列を固定長セグメントに再フォーマットして正規化します。

  • タイプVARCHAR(50) COLLATE Latin1_General_100_BIN2のソート列を作成します。最大長の50は、セグメントの最大数とそれらの潜在的な最大長に基づいて調整する必要がある場合があります。
  • アプリレイヤーでより効率的に正規化を行うことができますが、T-SQL UDFを使用してデータベースでこれを処理すると、スカラーUDFを_AFTER [or FOR] INSERT, UPDATE_トリガーに配置して、値を適切に設定することが保証されますすべてのレコード、アドホッククエリなどから受信したレコードも含まれます。もちろん、そのスカラーUDFはSQLCLRを介して処理することもできますが、実際にどちらがより効率的かを判断するためにテストする必要があります。 **
  • UDF(T-SQLまたはSQLCLRにあるかどうかに関係なく)は、
    • 各文字を読み取り、タイプがアルファから数値または数値からアルファに切り替わったときに停止することにより、不明な数のセグメントを処理します。
    • 各セグメントごとに、任意のセグメントの可能な最大の文字/桁に設定された固定長の文字列を返す必要があります(または将来の成長を考慮して最大+ 1または2)。
    • アルファセグメントは左揃えにし、スペースを右に埋め込む必要があります。
    • 数値セグメントは右揃えにし、左にゼロを埋め込む必要があります。
    • 大文字と小文字が混在する可能性があるが、順序付けで大文字と小文字を区別する必要がある場合は、UPPER()関数をすべてのセグメントの最終結果に適用します(そのため、1回だけ実行する必要があり、セグメント)。これにより、並べ替え列のバイナリ照合順序で適切な並べ替えが可能になります。
  • ソート列を設定するためにUDFを呼び出すテーブルに_AFTER INSERT, UPDATE_トリガーを作成します。パフォーマンスを向上させるには、UPDATE()関数を使用して、このコード列がSETステートメントのUPDATE句にさえあるかどうかを判断します(falseの場合はRETURNのみ) )、次にコード列のINSERTEDおよびDELETED疑似テーブルを結合して、コード値が変更された行のみを処理します。変更があるかどうかを正確に判断するには、そのJOIN条件で必ず_COLLATE Latin1_General_100_BIN2_を指定してください。
  • 新しいソート列にインデックスを作成します。

例:

_P7B18   -> "P     000007B     000018"
P12B3   -> "P     000012B     000003"
P12B3C8 -> "P     000012B     000003C     000008"
_

このアプローチでは、次の方法でソートできます。

_ORDER BY tbl.SortColumn
_

また、次の方法で範囲フィルタリングを実行できます。

_WHERE tbl.SortColumn BETWEEN dbo.MyUDF('P7B18') AND dbo.MyUDF('P12B3')
_

または:

_DECLARE @RangeStart VARCHAR(50),
        @RangeEnd VARCHAR(50);
SELECT @RangeStart = dbo.MyUDF('P7B18'),
       @RangeEnd = dbo.MyUDF('P12B3');

WHERE tbl.SortColumn BETWEEN @RangeStart AND @RangeEnd
_

_ORDER BY_フィルターとWHEREフィルターの両方で、照合優先順位により、SortColumnに定義されたバイナリ照合を使用する必要があります。

等価比較は、元の値列で引き続き行われます。


他の考え:

  • SQLCLR UDTを使用します。これは機能する可能性がありますが、上記のアプローチと比較して純利益が得られるかどうかは不明です。

    はい。SQLCLRUDTでは、比較演算子をカスタムアルゴリズムでオーバーライドできます。これは、値が既に同じカスタム型である別の値、または暗黙的に変換する必要がある値のいずれかと比較される状況を処理します。これはすべきWHERE条件で範囲フィルターを処理します。

    UDTを(計算列ではなく)通常の列タイプとしてソートすることに関しては、これはUDTが「バイト順」の場合にのみ可能です。 「バイト順」とは、UDTのバイナリ表現(UDTで定義可能)が適切な順序で自然にソートされることを意味します。パディングされる固定長セグメントを持つVARCHAR(50)列について、上記のアプローチと同様にバイナリ表現が処理されると仮定すると、条件を満たすことになります。または、バイナリ表現が適切な方法で自然に順序付けられるようにするのが容易でない場合は、適切に順序付けされる値を出力するUDTのメソッドまたはプロパティを公開してから、PERSISTEDそのメソッドまたはプロパティの計算列。メソッドは確定的で、_IsDeterministic = true_としてマークされている必要があります。

    このアプローチの利点は次のとおりです。

    • 「元の値」フィールドは不要です。
    • データを挿入したり値を比較したりするためにUDFを呼び出す必要はありません。 UDTのParseメソッドが_P7B18_値を受け取り、それを変換すると仮定すると、単純に値を_P7B18_として自然に挿入できるはずです。また、UDTで暗黙的な変換方法を設定すると、WHERE条件でも単純にP7B18`を使用できるようになります。

    このアプローチの結果は次のとおりです。

    • 列のデータ型としてバイト順UDTを使用している場合、フィールドを選択するだけでバイナリ表現が返されます。または、UDTのプロパティまたはメソッドでPERSISTED計算列を使用する場合、プロパティまたはメソッドによって返される表現を取得します。元の_P7B18_値が必要な場合は、その表現を返すようにコーディングされているUDTのメソッドまたはプロパティを呼び出す必要があります。とにかくToStringメソッドをオーバーライドする必要があるので、これはこれを提供するための良い候補です。
    • バイナリ表現に変更を加えるのがどれほど簡単/難しいかは、(少なくとも今のところ、私はこの部分をテストしていないので私には)はっきりしていません。格納されているソート可能な表現を変更するには、フィールドをドロップして再度追加する必要がある場合があります。また、UDTを含むアセンブリの削除は、どちらの方法で使用しても失敗するため、このUDT以外にアセンブリに何もないことを確認する必要があります。 _ALTER Assembly_を使用して定義を置き換えることができますが、これにはいくつかの制限があります。

      一方、VARCHAR()フィールドはアルゴリズムから切り離されたデータであるため、列の更新のみが必要です。また、数千万行(またはそれ以上)の行がある場合は、バッチアプローチで実行できます。

  • [〜#〜] icu [〜#〜] ライブラリを実装すると、実際にこの英数字の並べ替えが可能になります。このライブラリは非常に機能的ですが、C/C++とJavaの2つの言語しかありません。つまり、Visual C++で機能させるために微調整を行う必要があるか、Javaコードを [〜を使用してMSILに変換できる可能性があります。 #〜] ikvm [〜#〜] 。そのサイトには、マネージコードでアクセスできるCOMインターフェイスを提供する.NETサイドプロジェクトが1つまたは2つリンクされていますが、しばらく更新されていないと思いますそして、私はそれらを試していません。ここでの最善策は、並べ替えキーを生成する目的でアプリレイヤーでこれを処理することです。並べ替えキーは、新しい並べ替え列に保存されます。

    これは最も実用的なアプローチではないかもしれません。しかし、そのような能力が存在することはまだ非常にクールです。次の回答で、この例の詳細なウォークスルーを提供しました。

    次の文字列を1、2、3、6、10、10A、10B、11の順序で並べ替える照合順序はありますか?

    しかし、その質問で扱われているパターンは少し単純です。この質問で扱われているパターンのタイプも機能することを示す例については、次のページにアクセスしてください。

    ICU照合デモ

    [設定]で、[数値]オプションを[オン]に設定します。その他のオプションはすべて[デフォルト]に設定する必要があります。次に、[並べ替え]ボタンの右側で、[差分強度]のオプションをオフにし、[並べ替えキー]のオプションをオンにします。次に、「入力」テキスト領域のアイテムのリストを次のリストに置き換えます。

    _P12B22
    P7B18
    P12B3
    as456456hgjg6786867
    P7Bb19
    P7BA19
    P7BB19
    P007B18
    P7Bb20
    P7Bb19z23
    _

    「ソート」ボタンをクリックします。 「出力」テキスト領域には次のように表示されます。

    _as456456hgjg6786867
        29 4D 0F 7A EA C8 37 35 3B 35 0F 84 17 A7 0F 93 90 , 0D , , 0D .
    P7B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P007B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P7BA19
        47 0F 09 2B 29 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19
        47 0F 09 2B 2B 0F 15 , 09 , FD F2 , DC C5 DC 06 .
    P7BB19
        47 0F 09 2B 2B 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19z23
        47 0F 09 2B 2B 0F 15 5B 0F 19 , 0B , FD F4 , DC C5 DC 08 .
    P7Bb20
        47 0F 09 2B 2B 0F 16 , 09 , FD F2 , DC C5 DC 06 .
    P12B3
        47 0F 0E 2B 0F 05 , 08 , FD F1 , DC C5 DC 05 .
    P12B22
        47 0F 0E 2B 0F 18 , 08 , FD F1 , DC C5 DC 05 .
    _

    ソートキーは、コンマで区切られた複数のフィールドの構造であることに注意してください。各フィールドは個別に並べ替える必要があるため、SQL Serverでこれを実装する必要がある場合は、別の小さな問題を解決する必要があります。


** ユーザー定義関数の使用に関するパフォーマンスについて懸念がある場合は、提案されたアプローチがそれらの使用を最小限にすることに注意してください。実際、正規化された値を保存する主な理由は、各クエリの各行ごとにUDFを呼び出さないようにするためでした。主なアプローチでは、UDFを使用してSortColumnの値を設定します。これは、トリガーを介してINSERTおよびUPDATEに対してのみ実行されます。値の選択は、挿入および更新よりもはるかに一般的であり、一部の値は決して更新されません。 SELECT句の範囲フィルターにSortColumnを使用する各WHEREクエリごとに、UDFは、range_startおよびrange_endの値ごとに1回だけ必要です。正規化された値; UDFは行ごととは呼ばれません。

UDTに関しては、実際の使用方法はスカラーUDFと同じです。つまり、挿入および更新では、値を設定するために各行ごとに1回正規化メソッドが呼び出されます。次に、正規化メソッドは、範囲フィルターの各range_startおよびrange_valueごとに、クエリごとに1回呼び出されますが、行ごとには呼び出されません。

SQLCLR UDFで正規化を完全に処理することに有利な点は、データアクセスを行わず、確定的である場合、それが_IsDeterministic = true_としてマークされている場合、並列プランに参加できることです(これは、 INSERTおよびUPDATE操作)のに対し、T-SQL UDFは並列プランの使用を防止します。

8
Solomon Rutzky