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


9

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

これらの数値部分を数値順にソートしてください。明らかに、これらの文字列値をで直接処理する場合ORDER BY、「P12B3」は「P7B18」の前に来るでしょう。 「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年ほどの作業で遭遇したものではありません。


文字列を整数に変換して、ある種のインデックス付き計算列でこれを行うことができます。そうP7B12なる可能性があるP 07 B 12(ASCII経由)、その後、80 07 65 12そう、80076512
Philᵀᴹ

各数値コンポーネントに大きな長さ(つまり10個のゼロ)を埋める計算列を作成することをお勧めします。形式はかなり任意なので、かなり大きなインライン式が必要になりますが、それは可能です。次に、その列のどこにでも、好きなだけインデックス付け/順序付けできます。
Nick.McDermaid 2016年

私の回答の先頭に追加したリンクを参照してください:)
ソロモンルツキー2016

1
@srutzkyニース、私はそれに投票しました。
db2

こんにちはdb2:MicrosoftがConnectからUserVoiceに移行し、投票数を正確に維持していないため(コメントに入れましたが、彼らがそれを見るかどうかは不明です)、それに対する再投票が必要になる場合があります:「自然な並べ替え」をサポートしてください照合オプションとしての/ DIGITSASNUMBERS。ありがとう!
ソロモンルツキー

回答:


8

文字列内の数値を実際の数値としてソートするための賢明で効率的な手段が必要ですか?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()は、すべてのセグメントの最終結果に関数を適用します(セグメントごとではなく、一度だけ実行する必要があるため)。これにより、ソート列のバイナリ照合が適切にソートされます。
  • AFTER INSERT, UPDATEUDFを呼び出してソート列を設定するトリガーをテーブルに作成します。パフォーマンスを向上させるには、UPDATE()関数を使用してこのコード列がステートメントのSET句内にあるかどうかを判断しUPDATERETURNfalseの場合のみ)、コード列のINSERTEDDELETED擬似テーブルを結合して、コード値が変更された行のみを処理します。変更があるかどうかをCOLLATE Latin1_General_100_BIN2正確に判断するために、そのJOIN条件を必ず指定してください。
  • 新しいソート列にインデックスを作成します。

例:

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 BYWHEREフィルタの両方で、SortColumn照合の優先順位のために定義されたバイナリ照合を使用する必要があります。

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


他の考え:

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

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

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

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

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

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

    • バイト順UDTを列のデータ型として使用している場合、フィールドを選択するだけでバイナリ表現が返されます。またはPERSISTED、UDTのプロパティまたはメソッドで計算列を使用する場合、プロパティまたはメソッドから返される表現を取得します。元の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のみ行われます。値の選択は、挿入および更新よりもはるかに一般的であり、一部の値は決して更新されません。各ごと使用するクエリに範囲フィルタの句、UDFのみ正規化した値を取得するRANGE_STARTとRANGE_END値の各々ごとに1回必要です。UDFは行ごととは呼ばれません。INSERTUPDATESELECTSortColumnWHERE

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.