web-dev-qa-db-ja.com

キーと値のペアを含む列の操作

レプリケートされたSQL Serverデータベースを介してベンダーからレポートにアクセスして作成しています。彼らは私が解決しようとしているいくつかの絶対に狂ったことをしましたが、これは簡単です。

彼らは多くの標準的な列を持つテーブルを持っています。ただし、このテーブルには「データ」という列もあります。この列は従来の「テキスト」データ型であり、キーと値のペアの巨大な(数百の)リストが含まれています。各ペアはCRLFで区切られ、キーと値は等号で区切られます。例:

select myTable.[data] from myTable where tblKey = 123

結果:

Key 1=Value 1
Key 2=Value 2
Key 3=Value 3
...
Key 500=Value 500

その列を使用可能なデータのテーブルに分割する最も効率的な方法を決定しようとしています。最終的な目標は、次のように列/フィールドとして指定されたキー/値とともにテーブルキーを返す方法でテーブルをクエリできるようにすることです。

tblKey | [Key 1] | [Key 3] | [Key 243]
-------|---------|---------|-----------
 123     Value 1   Value 3   Value 243
 124     Value 1   Value 3   Value 243
 125     Value 1   Value 3   Value 243

その列をビューに成形する方法はありますか?関数が特に効率的であるとは想像できませんが、string_splitまたはそのようなものを使用して、そのように構文解析できると確信しています。以前にこのタイプの残虐行為に遭遇し、それを操作可能なデータに操作する良い方法を見つけたことがありますか?

dbfiddle サンプルデータを追加して編集します。

データはベンダーのソースから複製されるため、新しいテーブルを作成できません。ビュー、プロシージャ、関数を作成できます。それは私が達成するためのまともな方法のためのアドバイスを探しているものです。

6
Andy

[〜#〜]更新[〜#〜]

あなた自身の答えに投稿したときに、UDFを使用して特定のキー値を取得できる場合は、これを提案させてください(すべてのキー/値を分割する必要はなく、表を再度読む必要もありません) 、テキスト関数を使用して取得できます。)

_CREATE FUNCTION fnGetKey(@Data text, @Key varchar(20))
RETURNS varchar(100)
AS
BEGIN

  RETURN
  (
  SELECT 
      SUBSTRING (
                  @Data,
                  /* Position of first '=' after key + 1 */
                  CHARINDEX('=', @Data, PATINDEX('%' + @key + '%', @Data)) + 1,
                  /* Lenght, Position of first chr(13) after key less previuos value - 1 */
                  (CHARINDEX(CHAR(13), @Data, PATINDEX('%' + @key + '%', @Data)) 
                  - 
                  CHARINDEX('=', @Data, PATINDEX('%' + @key + '%', @Data))) - 1
                )
  )

END

SELECT
    FruitID, Name, Description,
    dbo.fnGetKey([Data], 'key 2') as [key 2],
    dbo.fnGetKey([Data], 'key 4') as [key 4]
FROM
    [Fruit];
_
 FruitID |名前|説明|キー2 |キー4 
 ------:| :-- :---------- | :------ | :------ 
 1 |バナナ|おいしい|値2 |値4 
 2 |梨| Rotton |値2 |値4 
 3 |キウイ|わかりました|値2 |値4 

db <> fiddle ---(ここ

元の答え

私が理解できる唯一の解決策は、キー/値を分割し、それをピボットして目的の結果を取得することです。

残念ながら、いくつかの不便があります:

  • STRING_SPLITはtext列では機能しません。したがって、操作する前にvarcharにキャストする必要があります。
  • STRING_SPLITにはnchar(1)またはnvarchar(1)が必要です。エルゴでは、CHAR(3)+CHAR(10)を1文字に置き換える必要があります。
  • PIVOTの集計関数は数値でより適切に機能するため、Valueを数値データ型にキャストする必要があります。
  • PIVOTには既知の数の列が必要です。私の例では、それらのいくつかを使用しましたが、動的クエリを処理するのでない限り、シーケンス全体を記述する必要があります。

これは私があなたのサンプルデータを使って得たものです:

_WITH KP AS
(
    SELECT FruitID, Name, Description, value as KPair
    FROM   Fruit
    CROSS APPLY STRING_SPLIT(REPLACE(CAST(Data AS varchar(max)), CHAR(13)+CHAR(10), ','), ',') /* STRING_SPLIT only allows nchar(1),  varchar(1) */
)
, KP1 AS
(
  SELECT
      FruitID,  
      SUBSTRING(KPair, 5, CHARINDEX('=', KPair) - 5) AS [Key],
      SUBSTRING(KPair, CHARINDEX('=', KPair) + 7, LEN(KPair) - CHARINDEX('=', KPair) - 6) AS [Value]
  FROM
      KP
)
SELECT [FruitID], [1],[2],[3],[4],[5]
FROM   KP1
PIVOT (MAX([Value]) FOR [Key] IN ([1],[2],[3],[4],[5])) AS PVT;
_

最初のCTEは_Key X=Value Y_ごとに分割します。 2番目は、この値をカットして、それぞれの[キー]と[値]を取得します。そして、最終的なPIVOTは最終結果を列にまとめます。

 FruitID | 1 | 2 | 3 | 4 | 5 
 ------:| : :-| :-| :-| :-
 1 | 1 | 2 | 3 | 4 | 5 
 2 | 1 | 2 | 3 | 4 | 5 
 3 | 1 | 2 | 3 | 4 | 5 

db <> fiddle ---(ここ

注:[キー1]と[値1]を維持する必要があるか、それとも[キー]&[値]という名前の列として変換する必要があるかはわかりません。

別のアプローチ

サードパーティのデータベースを使用する場合は、通常、可能な場合は同じサーバー/インスタンスに新しいデータベースを追加し、DB所有者との競合を避けるために、それを自分の目的で使用します。

この場合、新しいテーブルを追加し、定期的にプロセスをスローして、新しい値で更新することができます。

すべての列を持つテーブルを使用できます。

_CREATE TABLE [FruitKeys]
(
    [FruitID] int NOT NULL PRIMARY KEY,
    [V1]      int NULL,
    [V2]      int NULL,
    [V3]      int NULL,
    [V4]      int NULL,
    [V5]      int NULL
);
_

またはキーと値のペアを含むテーブルで、ピボットを使用して最終結果を取得します。

_CREATE TABLE [FruitKeys]
(
    [FruitID] int NOT NULL,
    [Key]     int NOT NULL,
    [Value]   int NOT NULL,
    CONSTRAINT [PK_FruitKeys] PRIMARY KEY ([FruitID], [Key])
);
_
5
McNets

ソースデータはJSON形式からそれほど離れていないようです。

あなたはそれをかなり直接変換し、次にOPENJSONを使用してリレーショナル出力を生成することができます:

SELECT
    F.FruitID,
    F.[Name],
    OJ.[key 1],
    OJ.[key 2],
    OJ.[key 3],
    OJ.[key 4],
    OJ.[key 5],
    F.[Description]
FROM dbo.Fruit AS F
CROSS APPLY OPENJSON
(
    -- Convert source data to JSON format
    '{' + 
        CHAR(34) + 
        REPLACE
        (
            REPLACE
            (
                CONVERT(varchar(max), F.Data), 
                '=', CHAR(34) + ':' + CHAR(34)
            ), 
            CHAR(13) + CHAR(10), 
            CHAR(34) + ',' + CHAR(34)
        ) + 
        CHAR(34) + 
    '}'
) 
WITH
(
    [key 1] varchar(100),
    [key 2] varchar(100),
    [key 3] varchar(100),
    [key 4] varchar(100),
    [key 5] varchar(100)
) AS OJ;

出力:

 FruitID |名前|キー1 |キー2 |キー3 |キー4 |キー5 |説明
 ------:| :-- :------ | :------ | :------ | :------ | :------ | :---------- 
 1 |バナナ|値1 |値2 |値3 |値4 |値5 |美味しい
 2 |梨|値1 |値2 |値3 |値4 |値5 |ロトン
 3 |キウイ|値1 |値2 |値3 |値4 |値5 |はい       

---(db <> fiddleデモ

5
Paul White 9

McNetsは合理的なアプローチを提供しましたが、すべてのペアの分割は、明らかに必要ですが、かなり時間がかかるプロセスです。テーブル内のすべてのレコードに500以上のキーと値のペアがあるため、目的に合うかどうかはわかりません。影響を受けるテーブルのペアが少なく、行数が少ない場合は、おそらく適切なアプローチです。

私は何百ものキーと値のペアとテーブル自体の何千ものレコードを操作しているので、特定のキーと値を使用するレポートとクエリで必要に応じて使用するユーザー定義関数(以下)を実装することを考えていますペアが必要です(既知です)。

CREATE FUNCTION udfsv_GetFruitDataValue(
    @FruitID int, 
    @DataId varchar(100)
)
RETURNS varchar(100)
AS BEGIN
  DECLARE @DataVal varchar(100)

  set @DataVal = (
    select 
      replace(replace(split1, @DataId + '=', ''), char(13), '') as DataValue
    from Fruit
    left outer join (
      select
        FruitID,
           value as split1
      from Fruit 
      cross apply string_split(cast([data] as varchar(max)), char(10))
    ) line1 on line1.FruitID = Fruit.FruitID         
    where Fruit.FruitID = @FruitID
    and split1 like @DataId + '=%'
  )

  RETURN @DataVal
END

これにより、すべてのキー/値ではなく、指定されたキー/値を含めるクエリを実行できます。

SELECT
  FruitID,
  Name,
  Description,
  udfsv_GetFruitDataValue(FruitID, 'Key 1') as [Key 1],
  udfsv_GetFruitDataValue(FruitID, 'Key 4') as [Key 4]
FROM
  Fruit
WHERE FruitID = 123
1
Andy