web-dev-qa-db-ja.com

Null値を他の値で更新する

SQL Serverの世界は初めてです。以下の例では、2つの会社があります。

  • ビルディングブロック
  • レッドセメント

各会社の1つのエントリのみ、列power1, power2, power3の値があります。

各会社がすべての列の値を持つようにテーブルを更新する必要があります。つまり、すべての空白(null)値を更新する必要があります。

作成したデータの例:

Create Table #Rentarious
(
  c1 varchar(200)
  ,yryryryr datetime
  ,power1 varchar(200)
  ,power2 varchar(200)
  ,power3 varchar(200)
)   

Insert Into #Rentarious VALUES
('Building Blocks','2016','Red','Blue','Green')
,('Red Cement', '2012', 'Pink','Purple','Orange')

Insert Into #Rentarious(c1, yryryryr) VALUES 
('Building Blocks', '2012')
,('Building Blocks', '2013')
,('Building Blocks', '2014')
,('Red Cement', '2016')
,('Red Cement', '2011')

質問

power1, power2, power3のnull値を表に既にリストされている値に更新する更新ステートメントを作成するための構文は何ですか?

3
Habib Inman

各フィールドをコンマで区切ることにより、複数のフィールドをSETできます。いくつかの一意のパターンに基づいてレコードを指定する必要があります。この場合はc1 and yryryryr

UPDATE #Rentarious
SET   power1 = 'Red'
    , power2 = 'Blue'
    , power3 = 'Green'
WHERE   c1       = 'Build Blocks'
    AND yryryryr = '2012';
1

構文は、フィールドpower1, power2, power3のnull値をテーブルに既にリストされている値で更新する更新ステートメントを作成するにはどうすればよいでしょうか。

これは、各企業のnullは、投稿の他の場所で言うように、各企業が持つ1つの入力済みエントリから取得した値に基づいて入力する必要があることを意味します。したがって、疑似コードでは、UPDATEステートメントは次のようにする必要があります。

UPDATE
  #Rentarious
SET
  power1 = power1 from the same company’s populated row,
  power2 = power2 from the same company’s populated row,
  power3 = power3 from the same company’s populated row
WHERE power1 IS NULL
  AND power2 IS NULL
  AND power3 IS NULL
;

上記のパターンを実装する1つの方法は、相関サブクエリを使用することです。

UPDATE
  #Rentarious
SET
  power1 = (SELECT power1 FROM #Rentarious AS src WHERE src.c1 = #Rentarious.c1 AND src.power1 IS NOT NULL),
  power2 = (SELECT power2 FROM #Rentarious AS src WHERE src.c1 = #Rentarious.c1 AND src.power2 IS NOT NULL),
  power3 = (SELECT power3 FROM #Rentarious AS src WHERE src.c1 = #Rentarious.c1 AND src.power3 IS NOT NULL)
WHERE power1 IS NULL
  AND power2 IS NULL
  AND power3 IS NULL
;

これは機能しますが、ソース値を取得するために同じテーブルがさらに3回処理されるため、このような更新はあまり効率的ではない場合があります。 null以外の値は会社ごとに1行に格納されることがわかっているので、派生テーブルと独自の「結合による更新」構文を使用することで、テーブルに対する1回の追加パスですべてを取得できます。

UPDATE
  #Rentarious
SET
  power1 = sub.power1,
  power2 = sub.power2,
  power3 = sub.power3
FROM
  (
    SELECT
      c1,
      power1,
      power2,
      power3
    FROM
      #Rentarious
    WHERE power1 IS NOT NULL
      AND power2 IS NOT NULL
      AND power3 IS NOT NULL
  ) AS sub
WHERE #Rentarious.c1 = sub.c1
  AND #Rentarious.power1 IS NULL
  AND #Rentarious.power2 IS NULL
  AND #Rentarious.power3 IS NULL
;

明示的なJOIN構文を使用するように書き換えることもできます。

UPDATE
  tgt
SET
  power1 = src.power1,
  power2 = src.power2,
  power3 = src.power3
FROM
  #Rentarious AS tgt
  INNER JOIN
  (
    SELECT
      c1,
      power1,
      power2,
      power3
    FROM
      #Rentarious
    WHERE power1 IS NOT NULL
      AND power2 IS NOT NULL
      AND power3 IS NOT NULL
  ) AS sub
  ON tgt.c1 = sub.c1
WHERE tgt.power1 IS NULL
  AND tgt.power2 IS NULL
  AND tgt.power3 IS NULL
;

ご覧のとおり、両方のバリエーションにサブクエリは1つだけあり、3つの値すべてを提供して他の行に入力します。

ただし、余分なスキャンをせずにこの問題を解決することもできます。まず、これがSELECTステートメントである場合、次のように window 集約関数を使用して、すべての行のデータが設定された行の値を返すことができます。

SELECT
  c1,
  yryryryr,
  power1,
  power2,
  power3,
  populatedPower1 = MAX(power1) OVER (PARTITION BY c1),
  populatedPower2 = MAX(power2) OVER (PARTITION BY c1),
  populatedPower3 = MAX(power3) OVER (PARTITION BY c1)
FROM
  #Rentarious
;

[〜#〜] max [〜#〜] 関数は、指定されたセットのnull以外の値全体でのみ最大値を返すため、この状況で機能します。あなたのケースでは、3つのケースのそれぞれでc1のパーティションごとにnull以外の値が1つしかないため、関数はその1つの値を返します。これはあなたの質問の例のクエリの結果です:

c1               yryryryr    power1  power2  power3  populatedPower1  populatedPower2  populatedPower3
---------------  ----------  ------  ------  ------  ---------------  ---------------  ---------------
Building Blocks  2012-01-01  NULL    NULL    NULL    Red              Blue             Green
Building Blocks  2013-01-01  NULL    NULL    NULL    Red              Blue             Green
Building Blocks  2014-01-01  NULL    NULL    NULL    Red              Blue             Green
Building Blocks  2016-01-01  Red     Blue    Green   Red              Blue             Green
Red Cement       2012-01-01  Pink    Purple  Orange  Pink             Purple           Orange
Red Cement       2016-01-01  NULL    NULL    NULL    Pink             Purple           Orange
Red Cement       2011-01-01  NULL    NULL    NULL    Pink             Purple           Orange

残っているのは、

SET
  power1 = populatedPower1,
  power2 = populatedPower2,
  power3 = populatedPower3

また、上記のSELECTクエリの結果はUPDATEステートメントのターゲットとして使用できるため、SQL Serverで実際にそれを行うことは可能です。これを派生テーブルとして使用できます。

UPDATE
  tgt
SET
  power1 = populatedPower1,
  power2 = populatedPower2,
  power3 = populatedPower3
FROM
  (
    SELECT
      c1,
      yryryryr,
      power1,
      power2,
      power3,
      populatedPower1 = MAX(power1) OVER (PARTITION BY c1),
      populatedPower2 = MAX(power2) OVER (PARTITION BY c1),
      populatedPower3 = MAX(power3) OVER (PARTITION BY c1)
    FROM
      #Rentarious
  ) AS tgt
WHERE power1 IS NULL
  AND power2 IS NULL
  AND power3 IS NULL
;

または、CTE( 共通テーブル式 )として実装し、CTEのエイリアスをターゲットとして使用します。

WITH tgt AS
  (
    SELECT
      c1,
      yryryryr,
      power1,
      power2,
      power3,
      populatedPower1 = MAX(power1) OVER (PARTITION BY c1),
      populatedPower2 = MAX(power2) OVER (PARTITION BY c1),
      populatedPower3 = MAX(power3) OVER (PARTITION BY c1)
    FROM
      #Rentarious
  )
UPDATE
  tgt
SET
  power1 = populatedPower1,
  power2 = populatedPower2,
  power3 = populatedPower3
WHERE power1 IS NULL
  AND power2 IS NULL
  AND power3 IS NULL
;

どちらも同じように機能し、すべてのnullが対応する値に置き換えられます。つまり、次のようになります。

c1               yryryryr    power1  power2  power3
---------------  ----------  ------  ------  ------
Building Blocks  2016-01-01  Red     Blue    Green
Red Cement       2012-01-01  Pink    Purple  Orange
Building Blocks  2012-01-01  NULL    NULL    NULL
Building Blocks  2013-01-01  NULL    NULL    NULL
Building Blocks  2014-01-01  NULL    NULL    NULL
Red Cement       2016-01-01  NULL    NULL    NULL
Red Cement       2011-01-01  NULL    NULL    NULL

これに:

c1               yryryryr    power1  power2  power3
---------------  ----------  ------  ------  ------
Building Blocks  2016-01-01  Red     Blue    Green
Red Cement       2012-01-01  Pink    Purple  Orange
Building Blocks  2012-01-01  Red     Blue    Green
Building Blocks  2013-01-01  Red     Blue    Green
Building Blocks  2014-01-01  Red     Blue    Green
Red Cement       2016-01-01  Pink    Purple  Orange
Red Cement       2011-01-01  Pink    Purple  Orange
6
Andriy M

これは、多くの会社がある場合、手動で設定するよりも速くなります。 3または4よりも多くのフィールドがある場合、動的ループを構築することを検討します。

Update R1 Set R1.power1 = R2.power1
    From #Rentarious R1
    Left Join (Select * 
            From #Rentarious Where power1 Is Not Null) R2 On R2.C1 = R1.C1
    Where R1.power1 Is Null

Update R1 Set R1.power2 = R2.power2
    From #Rentarious R1
    Left Join (Select * 
            From #Rentarious Where power2 Is Not Null) R2 On R2.C1 = R1.C1
    Where R1.power2 Is Null

Update R1 Set R1.power3 = R2.power3
    From #Rentarious R1
    Left Join (Select * 
            From #Rentarious Where power3 Is Not Null) R2 On R2.C1 = R1.C1
    Where R1.power3 Is Null
1
Joe