1

n個の行を1つのレコードにマージする必要があるSQLの問題を誰かが助けてくれますか?個々のレコードには、他のレコードと同じようにフィールドが入力されている場合とされていない場合があります。

基本的に、SQLで重複レコードが作成されるという問題があります。一部には、他の人にはない情報が含まれています。それらをマージする必要があり(ランク付けできます)、前のレコードに値が存在しない場合はフィールドを更新します(最初にランク付けされたものから始めます)。

たとえば、2つのユーザーレコードがある場合、1つには名前が入力され、もう1つには名が入力されます。これらは重複しており、合体のように1つのレコードにマージする必要があります。ただし、n行です。

基本的に、多くのレコードを1つに転置します。この場合、ランクの低い重複レコードにそのフィールドが入力され、ランクの高い行にフィールドが存在しない場合にのみ、フィールドが更新されます。

これは、問題の非常に単純化されたバージョンです。ご覧のとおり、SQL Fiddleを使用すると、スクリプトによって6つのレコードが作成されます。これらのレコードは2つのレコードにマージされ、すべてのフィールドに入力する必要があります。

問題は、x個の行が存在する可能性があることです。行数にばらつきがあるため、colesceステートメントを使用できません。

それが理にかなっていることを願っていますか?

CREATE TABLE [dbo].[Employee]([EmployeeId] varchar(10) NULL,
[First Name] [varchar](30) NULL,
[Middle Name] [varchar](30) NOT NULL,
[Last Name] [varchar](30) NOT NULL,
[E-Mail] [varchar](80) NOT NULL)


insert into Employee(EmployeeId,[First Name],[Middle Name],[Last Name],[E-Mail])
values('BOB1','Bob','','','bob@hotmail.com');

insert into Employee(EmployeeId,[First Name],[Middle Name],[Last Name],[E-Mail])
values('BOB1','','John','','bob@hotmail.com');

insert into Employee(EmployeeId,[First Name],[Middle Name],[Last Name],[E-Mail])
values('BOB1','','','Smith','bob@hotmail.com');

insert into Employee(EmployeeId,[First Name],[Middle Name],[Last Name],[E-Mail])
values('MARK1','','Peter','','mark@hotmail.com');

insert into Employee(EmployeeId,[First Name],[Middle Name],[Last Name],[E-Mail])
values('MARK1','Mark','','','mark@hotmail.com');

insert into Employee(EmployeeId,[First Name],[Middle Name],[Last Name],[E-Mail])
values('MARK1','','','Davis','mark@hotmail.com');


select * from [Employee]

それが理にかなっていることを願っています。

ありがとう

4

3 に答える 3

1

パフォーマンスが数時間のコーディングを正当化するのに十分重要であり SQLCLRの使用が許可されている場合は、マルチパラメーターのユーザー定義集計を使用して、単一のテーブルスキャンですべての値を計算できます。

NULLランクが最も低い非文字列を返す集計の例を次に示します。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.IO;
using Microsoft.SqlServer.Server;

[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined, MaxByteSize = -1, IsNullIfEmpty = true)]
public struct LowestRankString : IBinarySerialize
{
    public int currentRank;
    public SqlString currentValue;

    public void Init()
    {
        currentRank = int.MaxValue;
        currentValue = SqlString.Null;
    }

    public void Accumulate(int Rank, SqlString Value)
    {
        if (!Value.IsNull)
        {
            if (Rank <= currentRank)
            {
                currentRank = Rank;
                currentValue = Value;
            }
        }
    }

    public void Merge(LowestRankString Group)
    {
        Accumulate(Group.currentRank, Group.currentValue);
    }

    public SqlString Terminate()
    {
        return currentValue;
    }

    public void Read(BinaryReader r)
    {
        currentRank = r.ReadInt32();
        bool hasValue = r.ReadBoolean();
        if (hasValue)
        {
            currentValue = new SqlString(r.ReadString());
        }
        else
        {
            currentValue = SqlString.Null;
        }
    }

    public void Write(BinaryWriter w)
    {
        w.Write(currentRank);

        bool hasValue = !currentValue.IsNull;
        w.Write(hasValue);
        if (hasValue)
        {
            w.Write(currentValue.Value);
        }
    }
}

テーブルが次のようになっていると仮定します。

CREATE TABLE TopNonNullRank(Id INT NOT NULL、UserId NVARCHAR(32)NOT NULL、Value1 NVARCHAR(128)NULL、Value2 NVARCHAR(128)NULL、Value3 NVARCHAR(128)NULL、Value4 NVARCHAR(128)NULL、PRIMARY KEY CLUSTERED(Id ASC));

INSERT INTO TopNonNullRank (Id, UserId, Value1, Value2, Value3, Value4) VALUES 
    (1, N'Ada', NULL, N'Top value 2 for A', N'Top value 3 for A', NULL),
    (2, N'Ada', N'Top value 1 for A', NULL, N'Other value 3', N'Top value 4 for A'),
    (3, N'Ada', N'Other value 1 for A', N'Other value 2 for A', N'Other value 3 for A', NULL),
    (4, N'Bob', N'Top value 1 for B', NULL, NULL, NULL),
    (5, N'Bob', NULL, NULL, NULL, N'Top value 4 for B'),
    (6, N'Bob', N'Other value 1 for B', N'Top value 2 for B', NULL, N'Other value 4');

次の単純なクエリはNULL、各列の上位の非値を返します。

SELECT 
    UserId,
    dbo.LowestRankString(Id, Value1) AS TopValue1,
    dbo.LowestRankString(Id, Value2) AS TopValue2,
    dbo.LowestRankString(Id, Value3) AS TopValue3,
    dbo.LowestRankString(Id, Value4) AS TopValue4
FROM TopNonNullRank
GROUP BY UserId

残っているのは、結果を元のテーブルにマージすることだけです。最も簡単な方法は次のようになります。

WITH TopValuesPerUser AS
(
    SELECT 
        UserId,
        dbo.LowestRankString(Id, Value1) AS TopValue1,
        dbo.LowestRankString(Id, Value2) AS TopValue2,
        dbo.LowestRankString(Id, Value3) AS TopValue3,
        dbo.LowestRankString(Id, Value4) AS TopValue4
    FROM TopNonNullRank
    GROUP BY UserId
)
UPDATE TopNonNullRank
SET
    Value1 = TopValue1,
    Value2 = TopValue2,
    Value3 = TopValue3,
    Value4 = TopValue4
FROM TopNonNullRank AS OriginalTable
LEFT JOIN TopValuesPerUser ON TopValuesPerUser.UserId = OriginalTable.UserId;

この更新ではまだ重複行が残っているため、それらを削除する必要があることに注意してください。

さらに凝ったものを取得して、このクエリの結果を一時テーブルに保存し、MERGEステートメントを使用してそれらを元のテーブルに適用することもできます。

sp_renameもう1つのオプションは、結果を新しいテーブルに格納し、ストアドプロシージャを使用して元のテーブルと交換することです。

于 2012-08-12T20:02:01.737 に答える
0

扱っているデータの例がなければ、この質問を正確に解釈することは困難です。ここでライブの例を作成して、遊んでもらうことができます。

したがって、例なしで、2つのテーブルmyTable1とmyTable2に数値フィールド[N]と[M]があると仮定すると、完全外部結合でCOALESCEを使用しないのはなぜですか。

SELECT
  fieldx = COALESCE(a.fieldx, b.fieldx),
  fieldy = COALESCE(a.fieldy, b.fieldy),
  fieldz = COALESCE(a.fieldz, b.fieldz),
  [N]=SUM(ISNULL(a.[N],0.0)),
  [M]=SUM(ISNULL(b.[M],0.0))
FROM
  myTable1 a
  FULL OUTER JOIN myTable2 b
     ON a.fieldx = b.fieldx 
        a.fieldy = b.fieldy 
        a.fieldz = b.fieldz 
GROUP BY
   COALESCE(a.fieldx, b.fieldx),
   COALESCE(a.fieldy, b.fieldy),
   COALESCE(a.fieldz, b.fieldz)
于 2012-08-12T16:16:43.107 に答える
0

これは、特定のフィールドの各行の値を、重複する行のグループの最高ランクのNULL以外の値に設定するために機能するはずです。

GroupingKeyは、1つにマージする必要のある行のセットを決定する方法のフィールドまたはロジックのいずれかです。ランキングはあなたのランキング機能です。

UPDATE SomeTable SET
    Field1 =
    (
        SELECT TOP 1 t2.Field1
        FROM SomeTable t2
        WHERE
            t2.Field1 IS NOT NULL
            AND
            t2.GroupingKey = SomeTable.GroupingKey
        ORDER BY Ranking DESC
    )
FROM SomeTable

更新する必要のあるすべてのフィールドに対してこれを繰り返すか、クエリを更新してすべてを設定することができます。コピーして貼り付けるだけです。すべてのフィールドを更新したら、残っているのは重複するレコードを削除することだけです。同じGroupingKeyとRankingを使用して、特定のGroupingKeyの最高ランクの行を除くすべてを削除できます。

DELETE SomeTable
FROM SomeTable
INNER JOIN
(
    SELECT 
        GroupingKey, 
        MAX(Ranking) as MaxRanking
    FROM SomeTable
) t2 ON
    SomeTable.GroupingKey = t2.GroupingKey
    AND
    SomeTable.Ranking < t2.MaxRanking
于 2012-08-12T16:52:13.030 に答える