0

SQL Server 2008 R2 を使用しています。

文字列として保持している特定の順序で結果を並べ替えたい

(文字列はプログラムで作成されます)。

次の表を検討してください。

Col1    Col2    Col3
1       Jon     a
2       Joan    b
3       John    a
4       Jonnie  b
5       Jonny   a

として宣言されたnvarchar変数が@myOrderStringあります。これには、選択したい行の順序が含まれています。

そう言えば@myOrderString = '213'(編集済み)

だから、私はそのようなことをしたいと思います:

SELECT 
    ROW_NUMBER() OVER (ORDER BY @mySortString) AS Row, 
    ( Col2 + '(' + Col1 + ')' ) AS Outcome
FROM 
    myTable 
WHERE 
    Col3 = 'a' 
ORDER BY 
    @mySortString

結果を得るために:(編集済み)

Row Outcome
1   John (3)
2   Jon (1)
3   Jonny (5)

どうすればこれを解決し始めることができますか?

PS

の値を@myOrderString分離する必要がある場合は、作成できます@myOrderString = '2,1,3'(編集)


質問を編集した理由:

  1. 文字列の内容に誤りがありました。[(編集済み)としてマーク]
  2. 結果に誤りがありました。Andriy M. に感謝 [(編集済み) とマーク]
  3. 見た目の変更を加え、いくつかの文法とタイプミスを修正しました。

明確化: (この明確化の一部は、Aaron Bertrand のコメントに基づいています)

  1. 文字列内の値の数は、結果の行数と常に一致します。
  2. 文字列の値は、常に 1 から行数までの数値です。
  3. 最初の結果の行は 1 としてマークされます。2 番目は 2 としてマークされ、3 番目は 3 としてマークされます。現在、文字列は結果を並べ替えるためのものです。文字列が の場合2,1,3、選択結果が並べ替えられたことを意味します。2 行目が最初の結果になり、最初の結果が 2 番目として表示され、3 行目が 3 番目のままになります。
  4. 最大行数は 15 であるため、文字列は の形式123456789ABCDEFにすることができます。または、この縮小形式のソリューションが単純でない場合、文字列は次の形式にすることができます。1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

多くの方々のおかげで、次の解決策にたどり着きました。

(関数やループは必要ありません)

-- creating the original table and filling it
DECLARE @t TABLE(Col1 INT, Col2 VARCHAR(22), Col3 CHAR(1));

INSERT @t VALUES
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(3,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a');

-- this is the required order of the results
DECLARE @myOrderString VARCHAR(32) = '213';

-- this is my current solution
SELECT 
    ROW_NUMBER()OVER (ORDER BY CHARINDEX(CAST(rr AS NVARCHAR(MAX)), @myOrderString)) As [Row],
    Outcome 
FROM 
    (
        SELECT 
            ROW_NUMBER()OVER (ORDER BY Col1) AS rr, 
            Col2 + ' (' + CONVERT(NVARCHAR(22),Col1)+ ')' AS Outcome 
        FROM @t 
        WHERE  Col3 = 'a'
    ) as r
ORDER BY 
    [Row]

ただし、最大 9 行の結果で機能しますが、フォームで最大 15 行を表し123456789ABCDEFます。

を使用して、結果の行番号に 10 進数から 16 進数への変換を使用しようとしましCONVERT(CHAR(1),CONVERT(VARBINARY(1),@Dec))たが、うまくいきませんでした。

これが機能するために利用できる簡単な修正はありますか?


長い例 (9 行以上)

9 行を超える例をテストするために、これを使用します。

-- creating the original table and filling it
DECLARE @t TABLE(Col1 INT, Col2 VARCHAR(22), Col3 CHAR(1));

INSERT @t VALUES
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(3,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a'),
(6,'Don',   'a'),
(7,'Doan',  'b'),
(8,'Dohn',  'a'),
(9,'Donnie','b'),
(10,'Donny', 'a'),
(11,'Gon',   'a'),
(12,'Goan',  'a'),
(13,'Gohn',  'a'),
(14,'Gonnie','a'),
(15,'Gonny', 'a');

-- this is the required order of the results
DECLARE @myOrderString VARCHAR(32) = '456B213A789';

完全な解決策:

ここでは、回答セクションでループを使用して完全なソリューションを提供しますが、それをTHE回答として信用することはありません。

4

5 に答える 5

3

文字列をコンマで区切ると、はるかに良くなります。次のような分割関数を使用できます。

CREATE FUNCTION dbo.SplitInts
(
    @List       NVARCHAR(MAX),
    @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
AS
    RETURN (SELECT Number = ROW_NUMBER() OVER (ORDER BY Number),
        Item FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(@List, Number, 
        CHARINDEX(@Delimiter, @List + @Delimiter, Number) - Number)))
    FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
      FROM sys.all_objects) AS n(Number)
      WHERE Number <= CONVERT(INT, LEN(@List))
      AND SUBSTRING(@Delimiter + @List, Number, 1) = @Delimiter
    ) AS y
    WHERE ISNUMERIC(Item) = 1
  );
GO

今、あなたはこれを行うことができます:

DECLARE @myOrderString VARCHAR(32) = '2,1,3';

DECLARE @t TABLE(col1 INT, col2 VARCHAR(32), col3 CHAR(1));

INSERT @t VALUES
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(3,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a');

SELECT 
  [Row] = s.Number, 
  Outcome = t.col2 + ' (' + CONVERT(VARCHAR(12), t.col1) + ')' 
FROM 
(
  SELECT col1, col2, rn = ROW_NUMBER() OVER (ORDER BY col1)
  FROM @t WHERE col3 = 'a'
) AS t 
INNER JOIN 
  dbo.SplitInts(@myOrderString, ',') AS s
  ON s.Item = t.rn 
ORDER BY s.Number;

繰り返しますが、結果は次のとおりです。

Row  Outcome
--   ----------
1    John (3)
2    Jon (1)
3    Jonny (5)

編集

これは、関数を使用しないバージョンです (この場合、それがなぜそれほど好ましくないのかはわかりませんが)、リストをコンマで区切る必要はありません (新しい「ハード」要件、 OPの自己回答)、続行する前にループ内のテーブル変数に手動で入力する必要はありません(これにより、ループを無視したときにOPの回答の最終クエリが安く見えても、全体的な計画のコストが低くなります +インサート)。これは、OP の自己回答と同じ結果を返します (新しい「長い例」のサンプル データが質問に追加された場合) が、同様に最大 15 個の並べ替え値に制限されます。

;WITH n(n,c) AS 
(
  SELECT CASE WHEN n < 10 THEN n ELSE n -7 END, CHAR(n+48)
  FROM 
  (
    SELECT TOP (21) n = ROW_NUMBER() OVER (ORDER BY [object_id])
    FROM sys.objects ORDER BY [object_id]
  ) AS x WHERE n BETWEEN 1 AND 9 OR n BETWEEN 17 AND 21
),
x(Outcome, n) AS 
(
  SELECT col2 + ' (' + CONVERT(VARCHAR(12), col1) + ')', 
  ROW_NUMBER() OVER (ORDER BY col1)
  FROM (SELECT col1, col2 FROM @t WHERE col3 = 'a') AS y
)
SELECT [Row] = ROW_NUMBER() OVER (ORDER BY 
    COALESCE(NULLIF(CHARINDEX(n.c, @myOrderString), 0), 16)), 
  Outcome FROM x LEFT OUTER JOIN n ON x.n = n.n
ORDER BY [Row], Outcome;
于 2012-06-16T19:15:00.437 に答える
2

私は質問の作者です。私はこの答えを答えとして信用しません。

このソリューションは、各文字のループに基づいてい@myOrderStringます。

この解決策は、私が質問で説明した解決策に基づいた解決策を期待して、インスピレーションとしてここに与えられています。

元のテーブルを作成して入力します。

DECLARE @t TABLE(Col1 INT, Col2 VARCHAR(32), Col3 CHAR(1));

INSERT @t VALUES
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(3,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a'),
(6,'Don',   'a'),
(7,'Doan',  'b'),
(8,'Dohn',  'a'),
(9,'Donnie','b'),
(10,'Donny', 'a'),
(11,'Gon',   'a'),
(12,'Goan',  'a'),
(13,'Gohn',  'a'),
(14,'Gonnie','a'),
(15,'Gonny', 'a')

これは結果の必須の順序です

DECLARE @myOrderString VARCHAR(32) = '456B213A789'

Order Colomn(OC)を使用して文字列をテーブルに変換する

[区切り]文字列をこのようなことを行うテーブルに変換する組み込み関数があればいいのにと思います。

DECLARE @ot TABLE (PK INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, OC INT)
DECLARE @i int
SET @i = 0
WHILE @i < LEN(@myOrderString) 
    BEGIN
        SET @i = @i + 1
        IF ASCII(UPPER(SUBSTRING(@myOrderString,@i,1))) < 65
            INSERT @ot VALUES (SUBSTRING(@myOrderString,@i,1))
        ELSE
            INSERT @ot VALUES (ASCII(UPPER(SUBSTRING(@myOrderString,@i,1)))-55)
    END

必要な順序で結果を表示する:

SELECT 
    ROW_NUMBER() OVER (ORDER BY PK) AS [Row], 
    r.Outcome 
FROM @ot 
INNER JOIN (
    SELECT 
        ROW_NUMBER() OVER (ORDER BY Col1) AS [RRow], 
        Col2 +' (' + CONVERT(varchar(11), Col1) + ')' AS Outcome
    FROM @t 
    WHERE Col3 = 'a') AS r
ON OC=RRow

付録

興味深い結果(結果の行を選択する-必須ではありません)については、設定してみてください@myOrderString = '333222111'

于 2012-06-17T12:49:03.727 に答える
1

要件に従って、以下は解決策です...

Select col1, Col2 + ' (' + Convert(Varchar, col1) + ')'
From #T
Where CHARINDEX(CAST(Col1 AS NVARCHAR(MAX)), '3,1,4') <> 0
order by CHARINDEX(CAST(Col1 AS NVARCHAR(MAX)), '3,1,4')

編集 -2 非数値は UDF で無視されています。

EDIT - 1 9 より大きい値をサポートします

declare @myTable table(col1 int, col2 varchar(10), col3 varchar(1))

insert @myTable values
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(322,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a')

Select col1, Col2 + ' (' + Convert(Varchar, col1) + ')'
From @myTable T
INNER JOIN 
(
    Select * From dbo.Split('322,1,4', ',')
)K
ON K.val = T.col1
Order by K.id

ユーザー定義関数

CREATE FUNCTION [dbo].[Split](@String varchar(8000), @Delimiter char(1))       
returns @temptable TABLE (id int IDENTITY(1,1), Val Int)       
as       
begin       
    declare @idx int       
    declare @slice varchar(8000)       

    select @idx = 1       
        if len(@String)<1 or @String is null  return       

    while @idx!= 0       
    begin       
        set @idx = charindex(@Delimiter,@String)       
        if @idx!=0       
            set @slice = left(@String,@idx - 1)       
        else       
            set @slice = @String       

        if(isnumeric(@slice) = 0) 
        Set @slice = '';

        if(len(@slice)>0)  
            insert into @temptable(Val) values(@slice)       

        set @String = right(@String,len(@String) - @idx)       
        if len(@String) = 0 break       
    end   
return       
end  
于 2012-06-16T19:25:02.033 に答える
1

文字列をコンマ区切りにする場合、それを一時テーブルに挿入してそのテーブルに結合すると、一時テーブルには、コンマ区切り文字列から整数の 1 つを挿入するたびに増加するシーケンス番号を含めることができます。あなたが注文するものです

そして、これは非常に高速な例です(おそらく最適化の余地があります)

DECLARE @string varchar(max),
    @delimiter char(1),
    @xml xml

SELECT @string = '3,1,4',
    @delimiter= ','

SELECT @xml = CONVERT(xml,'<root><s>' + REPLACE(@string,@delimiter,'</s><s>') + '</s></root>')

create table #values
(
    seq integer identity(1, 1),
    value integer
)

insert into #values (value)
SELECT [Value] = T.c.value('.','varchar(20)')
FROM @xml.nodes('/root/s') T(c)

select
    v.seq,
    m.Col2,
    m.Col1
from dbo.myTable m
inner join #values v on m.Col1 = v.value
order by v.seq

drop table #values
于 2012-06-16T19:02:44.147 に答える
0

Jonnie は 'a' ではなく 'b' であるため、提供されたデータでは出力テーブルを作成できません。また、次の例では、'Jonny' は 'a' ですが、指定されたソート文字列に 5 がないため返されません。そのような行を処理する方法については、いくつかのガイダンスが必要です。

declare @sortTable table (sortId int identity, sortOrder varchar(1))
declare @sortString nvarchar(100) = '314'

declare @position int = 1
declare @sortOrder varchar(1) = substring(@sortString,@position,1)
while @sortOrder != ''
begin
    insert @sortTable (sortOrder) values (@sortOrder)
    set @position = @position + 1
    set @sortOrder = SUBSTRING(@sortString,@position,1)
end


declare @myTable table(col1 varchar(1), col2 varchar(10), col3 varchar(1))

insert @myTable values
(1,'Jon',   'a'),
(2,'Joan',  'b'),
(3,'John',  'a'),
(4,'Jonnie','b'),
(5,'Jonny', 'a')

SELECT ROW_NUMBER() OVER (ORDER BY rsSort.SortID) AS Row, ( Col2 + '(' + Col1 + ')' ) AS Outcome
FROM @myTable rsMain
inner join @sortTable rsSort on rsSort.sortOrder = rsMain.col1
WHERE Col3 = 'a'

実施例

于 2012-06-16T19:28:00.313 に答える