web-dev-qa-db-ja.com

文字列の複数の部分を複数の行のデータで置き換える方法は?

2つの列(整数IDとテキストベースの文字列)のテーブルがある場合、中かっこで囲まれた任意の数の整数をエンコードし、他の有効なテキスト文字と混合した文字列値から始めたいと思います。

例:'{1} / {9} ... {12}'

単一のSELECTステートメントを使用して、すべての整数(およびその中括弧)がテーブルから派生した値に置き換えられた文字列を返したいと思います。具体的には、ソース文字列で見つかった番号と一致するIDを持つ行のテキスト値....と中括弧の外の文字は変更されません。

タスクを完了できない例を次に示します。

select
  replace('{13} {15}','{'+cast(id as varchar)+'}',isNull(display,''))
from testing;

これにより、testingテーブルの行ごとに1行が返されます。 id値= 13の行の場合、文字列の '{13}'部分は正常に置き換えられますが、 '{15}'部分は置き換えられません(行15ではその逆です)。

すべてのtesting行をループして繰り返し置換を試行する関数を作成すると、問題が解決すると思います。いずれにせよ、ループよりもまっすぐなSQLステートメントの方が望ましいでしょう。

サンプルデータ

+----+-------------------+
| id |  display          |
+----+-------------------+
|  1 |  Apple            |
|  2 |  Banana           |
|  3 |  Celery           |
|  4 |  Dragonfruit      |
|  5 |  Eggplant         |
|  6 |  Fenugreek        |
|  7 |  Gourd            |
|  8 |  Honeydew         |
|  9 |  Iceberg Lettuce  |
| 10 |  Jackfruit        |
| 11 |  Kale             |
| 12 |  Lemon            |
| 13 |  Mandarin         |
| 14 |  Nectarine        |
| 15 |  Olive            |
+----+-------------------+

使用例

select replace('{1} {3}',null,null) 
-- Returns 'Apple Celery'

select replace('{3},{4},{5}',null,null); 
-- Returns 'Celery,Dragonfruit,Eggplant'

select replace('{1} / {9} ... {12}',null,null); 
-- Returns 'Apple / Iceberg Lettuce ... Lemon'

明らかに、replaceキーワードは機能しません。

PS。これを容易にするためにソリューションが文字列のフォーマットを変更する必要がある場合、それはオプションです。

例:'#1 / #9 ... #12'(前の例と関連付けるため)

このフォーマットでは、#に基づいて文字列を行セットに分割し、取得した数値に基づいてleftからjoinテーブルへの非数値を見つけるまでtesting文字を取得し、#と、testingテーブルのdisplay値を持つ数値、次にstuffこれらの個別に変更されたすべてのトークンを単一の文字列に戻しますfor xml path

string_aggをサポートしないSQL Server 2016を使用しています。とはいえ、string_aggを使用した解決策がある場合は、引き続き検討することにします。

2

これは再帰的なcteを使用した例ですtranslate変数

drop table if exists testing;
go
create table testing (id int, display varchar(16));
insert into testing values (1, 'Apple');
insert into testing values (2, 'Banana');
insert into testing values (3, 'Celery');
insert into testing values (4, 'Dragonfruit');
insert into testing values (5, 'Eggplant');

DROP FUNCTION IF EXISTS dbo.TranslateVariables
go
CREATE FUNCTION dbo.TranslateVariables
(
    @StringValue VARCHAR(MAX)
)
RETURNS TABLE

AS
RETURN (

--Common Table Expression for Translation
WITH TranslationTable
AS (
    SELECT FindValue = '{' + convert(varchar(5),id) + '}' ,ReplaceValue = display,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rn
    FROM testing
    )
--Recursive CTE to loop through the TranslationTable and replace FindValue with ReplaceValue
,RecursiveCte as
(
SELECT @StringValue AS StrValue
    ,(
        SELECT count(*)
        FROM TranslationTable
        ) AS cnt

UNION ALL

SELECT replace(StrValue, tt.FindValue, tt.Replacevalue)
    ,cnt - 1
FROM RecursiveCte
JOIN TranslationTable tt
    ON tt.rn = cnt )

SELECT StrValue
    ,cnt
FROM RecursiveCte where cnt = 0
    )
go

--Verify translation
SELECT *
FROM dbo.TranslateVariables('{1} {3}')
OPTION (MAXRECURSION 32767) -- Don't forget to use the maxrecursion option!

 StrValue     | cnt |
|--------------|-----|
| Apple Celery | 0   |

SELECT *
FROM dbo.TranslateVariables('{3},{4},{5}')
OPTION (MAXRECURSION 32767) -- Don't forget to use the maxrecursion option!

| StrValue                    | cnt |
|-----------------------------|-----|
| Celery,Dragonfruit,Eggplant | 0   |
3
Scott Hodgin

あなたがそれをサポートするバージョンを使用していると仮定し(そして 元のフィドル でバージョンを終了する)、ネイティブ string_split() &-を使用できます string_agg() 関数。

_declare @id_list varchar(10) = '1,3'; -- for 'Apple,Celery' 
-- set @id_list = '3,4,5'; --for 'Celery,Dragonfruit,Eggplant'

select string_agg(display, ',') as agg
from (
    select t.display 
    from testing t
    cross apply string_split(@id_list,',') ss 
    where try_cast(ss.[value] as int) = t.id
) x;
_

上記の例では、途中で中かっこを削除し、コンマで区切られた数値のリストであると想定しています。中かっこを途中で保持したい場合は、整形式のJSONが含まれていることを確認し、解析に ネイティブJSON関数 を使用する必要があります。上記の主要なビットは次のとおりです。

  1. _[Id]_ sの配列を提供...
  2. 必要な_testing.display_ sのみに絞り込み、次に...
  3. ...そのテキスト配列をstring_agg()にフィードします

あなたが2016年であり、string_agg()が2017年にのみ利用可能になることを明記すると、string_split()を使用して必要に応じて配列を作成し、従来のアプローチの1つを使用できますOPで検討しているように、string_agg()がないことを回避します。例えば:

_select stuff(agg,1,1,'') as agg_trim_first_comma
from (
    select stuff(x.display,1,0,'') 
    from (
        select ',' + t.display 
        from testing t
        cross apply string_split('1,3',',') ss 
        where try_cast(ss.[value] as int) = t.id
    ) x (display )
    for xml path('')
) y (agg);
_
0
Peter Vandivier