2012-04-19 13 views
5

マージするように選択された2つ以上の行がある場合、そのうちの1つがテンプレート行として識別されます。他の行は、テンプレートにある任意のNULL値の列にデータをマージする必要があります。特定の条件で行を1つの行にマージしようとする

例データ:

Id Name  Address   City   State Active Email    Date 
1 Acme1 NULL    NULL   NULL NULL [email protected]  3/1/2011 
2 Acme1 1234 Abc Rd  Springfield OR  0  [email protected] 1/12/2012 
3 Acme2 NULL    NULL   NULL 1  [email protected] 4/19/2012 

は、ユーザがテンプレート行としてIDが1の行を選択したと言う、およびIDS 2及び3を持つ行が行1にマージした後、削除されるべきです。行Id1の任意のヌル値列は、最新の(日付列を参照)非ヌル値で満たされなければならず、行Id1に既に存在する非ヌル値はそのまま残されるべきである。行ID 3は、最新の日付を持っていたので、アクティブな値は1、および0でないことを

Id Name  Address   City   State Active Email    Date 
1 Acme1 1234 Abc Road Springfield OR  1  [email protected]  3/1/2011 

注意:上記のデータでこのクエリの結果は、まさにこのする必要があります。

P.S.また、明示的に定義/事前にすべての列名が何であるかを知ることなくこれを行う方法はありますか?私が作業している実際のテーブルには、常に新しいテーブルが追加された、1トンのカラムがあります。テーブル内のすべての列名を検索し、そのサブクエリを使用するか、またはジョブを実行するように誘惑する方法はありますか?

+0

また、電子メールは[email protected]でもかまいませんか、Acme2はマージの範囲外であると考えていますか? –

+0

データグループの識別方法は?私はあなたがデータのdoubletsをマージしたいと思います。どのようにして、テーブル内の何千ものレコードのデータグループを特定していますか?あなたがそれを知っているなら、あなたはこれを行うストアドプロシージャを書くことができます。必要に応じてサンプルSPを回答として書くことができます – YvesR

+0

こんにちはRussell Fox、いいえ、電子メールは[email protected]のままでなければなりません。すでにID1のNULL値列だけが修正される必要があります。 – noahC

答えて

2

まず、テンプレートのフラグで日付を並べ替え、次に日付で並べ替えることで、行を並べ替えることができます。テンプレート行は常に最後の行にする必要があります。各行には、この順に番号が割り当てられます。 max()を使うと、占有しているセルを(番号の降順で)見つけることができます。次に、それらの最大値に一致する行から列を選択します。

; with rows as (
    select test.*, 
    -- Template row must be last - how do you decide which one is template row? 
    -- In this case template row is the one with id = 1 
    row_number() over (order by case when id = 1 then 1 else 0 end, 
         date) rn 
    from test 
    -- Your list of rows to merge goes here 
    -- where id in (...) 
), 
-- Finding first occupied row per column 
positions as (
    select 
    max (case when Name is not null then rn else 0 end) NamePosition, 
    max (case when Address is not null then rn else 0 end) AddressPosition, 
    max (case when City is not null then rn else 0 end) CityPosition, 
    max (case when State is not null then rn else 0 end) StatePosition, 
    max (case when Active is not null then rn else 0 end) ActivePosition, 
    max (case when Email is not null then rn else 0 end) EmailPosition, 
    max (case when Date is not null then rn else 0 end) DatePosition 
    from rows 
) 
-- Finally join this columns in one row 
select 
    (select Name from rows cross join Positions where rn = NamePosition) name, 
    (select Address from rows cross join Positions where rn = AddressPosition) Address, 
    (select City from rows cross join Positions where rn = CityPosition) City, 
    (select State from rows cross join Positions where rn = StatePosition) State, 
    (select Active from rows cross join Positions where rn = ActivePosition) Active, 
    (select Email from rows cross join Positions where rn = EmailPosition) Email, 
    (select Date from rows cross join Positions where rn = DatePosition) Date 
from test 
-- Any id will suffice, or even DISTINCT 
where id = 1 

You might check it at Sql Fiddle

EDIT:クロスは最後のセクションに参加し

は、実際に内部であるかもしれないrows.rn = xxxPositionに合流します。このように動作しますが、内部結合に変更すると改善されます。

1

それほど複雑ではありません。まず

..あなたはテンプレートとして扱われている行思い出すことができ..so DECLARE @templateID INT = 1 ...

は今(テンプレート行を除外)最新NOT NULL値を見つけます。最も簡単な方法は、列ごとにTOP 1サブクエリを使用することです:

SELECT 
(SELECT TOP 1 Name FROM DataTab WHERE Name IS NOT NULL AND NOT ID = @templateID ORDER BY Date DESC) AS LatestName, 
(SELECT TOP 1 Address FROM DataTab WHERE Address IS NOT NULL AND NOT ID = @templateID ORDER BY Date DESC) AS AddressName 
-- add more columns here 

上記CTEにラップ(共通テーブル式)ので、あなたのUDPATEのための素敵な入力を持っている...今

WITH Latest_CTE (CTE_LatestName, CTE_AddressName) -- add more columns here; I like CTE prefix to distinguish source columns from target columns.. 
AS 
-- Define the CTE query. 
(
    SELECT 
    (SELECT TOP 1 Name FROM DataTab WHERE Name IS NOT NULL AND NOT ID = @templateID ORDER BY Date DESC) AS LatestName, 
    (SELECT TOP 1 Address FROM DataTab WHERE Address IS NOT NULL AND NOT ID = @templateID ORDER BY Date DESC) AS AddressName 
    -- add more columns here 
) 
UPDATE 
<update statement here (below)> 

は、スマート行う UPDATEテンプレート行の ISNULLを使用します。条件付き更新として動作します。ターゲット列がNULLの場合のみ更新します。

WITH 
<common expression statement here (above)> 
UPDATE DataTab 
SET 
Name = ISNULL(Name, CTE_LatestName), -- if Name is null then set Name to CTE_LatestName else keep Name as Name 
Address = ISNULL(Address, CTE_LatestAddress) 
-- add more columns here.. 
WHERE ID = @templateID 

最後の作業はテンプレート行以外の行を削除することです。

DELETE FROM DataTab WHERE NOT ID = @templateID 

クリアしますか?

+0

私はそれが理にかなっていると思います。私はそれを行こうと思いますが、すべての列名が明示的に定義されていなくてもそれを実行する方法はありますか?私が作業している実際のテーブルには、常に新しいテーブルが追加された、1トンのカラムがあります。テーブル内のすべての列名を検索し、そのサブクエリを使用するか、またはジョブを実行するように誘惑する方法はありますか? – noahC

+0

sys.columnsカタログビューを使用して、テーブルからすべての列名を取得します(このセットからID列を除外します)。今1)動的SQLを使用する - それは私が思うと頭痛のパフォーマンスが低下します。または、2)sys.columnsのカラム名をテキストファイルに出力し、AWK/GAWKプログラムを使用して "template"を使用してターゲットスクリプトを生成します。 GAWKプログラムを将来の使用のために保存します(SQLスクリプトの再構築/更新)。 – huhu78

+0

アイデアを得るには別のSQL問題に関する私のGAWKソリューションを見てください:http://stackoverflow.com/a/10122169/1280816私は確かに多くのカラムを持っていますが、最終製品のカラム数は静的です。外部SQLスクリプトジェネレータを実行する方が、クエリの実行ごとにパフォーマンスが低下し、列名をループするよりも優れています。 – huhu78

1

動的列の場合、動的SQLを使用してソリューションを作成する必要があります。

sys.columnsとsys.tablesを照会して必要な列のリストを取得した後、NULL列ごとに1回ループして、その列の最初のNULLでない行を探し、その列。ループ内で0になると、完全な行が得られます。これをユーザーに表示することができます。

+0

ありがとうございます。それはまさに私がする必要があるように思えますが、コードを見ずに具体的なconeptualizationを持つのは難しい時です。それはあまり問題ではない場合は、私はより理解することができるようにここにいくつかのコードをスローしていただけますか?どうもありがとう。 – noahC

1

投稿日には注意が必要です。いずれにしても、動的SQLを使用して更新ステートメントを作成するソリューションがあります。それは、とにかくから、何かを構築する何かを与える必要があります。

途中で結果を検証するためのコードがいくつか追加されていますが、重要ではないコードを明らかにする方法でコメントを作成しようとしました。

CREATE TABLE 
dbo.Dummy 
    (
    [ID] int , 
    [Name] varchar(30), 
    [Address] varchar(40) null, 
    [City] varchar(30) NULL, 
    [State] varchar(2) NULL, 
    [Active] tinyint NULL, 
    [Email] varchar(30) NULL, 
    [Date] date NULL 
    ); 
-- 
INSERT dbo.Dummy 
VALUES 
(
    1, 'Acme1', NULL, NULL, NULL, NULL, '[email protected]', '3/1/2011' 
) 
, 
(
    2, 'Acme1', '1234 Abc Rd', 'Springfield', 'OR', 0, '[email protected]', '1/12/2012' 
) 
, 
(
    3, 'Acme2', NULL, NULL, NULL, 1, '[email protected]', '4/19/2012' 
); 
DECLARE 
    @TableName nvarchar(128) = 'Dummy', 
    @TemplateID int = 1, 
    @SetStmtList nvarchar(max) = '', 
    @LoopCounter int = 0, 
    @ColumnCount int = 0, 
    @SQL nvarchar(max) = '' 
    ; 
-- 
--Create a table to hold the column names 
DECLARE  
    @ColumnList table 
     (
     ColumnID tinyint IDENTITY, 
     ColumnName nvarchar(128) 
     ); 
-- 
--Get the column names 
INSERT @ColumnList 
(
    ColumnName 
) 
    SELECT 
     c.name 
    FROM 
     sys.columns AS c 
     JOIN 
     sys.tables AS t 
      ON 
       t.object_id = c.object_id 
    WHERE 
     t.name = @TableName; 
-- 
--Create loop boundaries to build out the SQL statement 
SELECT 
    @ColumnCount = MAX(l.ColumnID), 
    @LoopCounter = MIN (l.ColumnID) 
FROM 
    @ColumnList AS l; 
-- 
--Loop over the column names 
WHILE @LoopCounter <= @ColumnCount 
BEGIN 
    --Dynamically construct SET statements for each column except ID (See the WHERE clause) 
    SELECT 
     @SetStmtList = @SetStmtList + ',' + l.ColumnName + ' =COALESCE(' + l.ColumnName + ', (SELECT TOP 1 ' + l.ColumnName + ' FROM ' + @TableName + ' WHERE ' + l.ColumnName + ' IS NOT NULL AND ID <> ' + CAST(@TemplateID AS NVARCHAR(MAX)) + ' ORDER BY Date DESC)) ' 
    FROM 
     @ColumnList AS l 
    WHERE 
     l.ColumnID = @LoopCounter 
     AND 
     l.ColumnName <> 'ID'; 
-- 
    SELECT 
     @LoopCounter = @LoopCounter + 1; 
-- 
END; 

--TESTING - Validate the initial table values 
SELECT * FROM dbo.Dummy ; 
-- 
--Get rid of the leading common in the SetStmtList 
SET @SetStmtList = SUBSTRING(@SetStmtList, 2, LEN(@SetStmtList) - 1); 
--Build out the rest of the UPDATE statement 
SET @SQL = 'UPDATE ' + @TableName + ' SET ' + @SetStmtList + ' WHERE ID = ' + CAST(@TemplateID AS NVARCHAR(MAX)) 
--Then execute the update 
EXEC sys.sp_executesql 
    @SQL; 
-- 
--TESTING - Validate the updated table values 
SELECT * FROM dbo.Dummy ; 
-- 
--Build out the DELETE statement 
SET @SQL = 'DELETE FROM ' + @TableName + ' WHERE ID <> ' + CAST(@TemplateID AS NVARCHAR(MAX)) 
--Execute the DELETE 
EXEC sys.sp_executesql 
    @SQL; 
-- 
--TESTING - Validate the final table values 
SELECT * FROM dbo.Dummy; 
-- 
DROP TABLE dbo.Dummy; 
関連する問題