智能SQL合并 - n行,合并

时间:2012-08-11 05:27:28

标签: sql sql-server tsql

任何人都可以帮助解决SQL问题我需要将n行合并到一个记录中。单个记录可能包含也可能没有其他人填写的字段。

基本上我遇到了在SQL中创建重复记录的问题。有些包含其他信息。我需要合并它们(我可以对它们进行排名),如果前一条记录中不存在该值,则更新一个字段(从排名最高的第一个开始)。

例如,如果我有两个用户记录,一个用户姓名填写,另一个用姓。这些是重复的,需要合并到一个记录中,如合并。但是,n行数。

它本质上是将许多记录转换为一个记录,只有当排名较低的重复记录填充该字段并且该字段在排名较高的行中不存在时才更新字段。

这是该问题的一个非常简化的版本。如您所见,使用SQL Fiddle,脚本会创建6条记录。这些记录应合并为2条记录,并填写所有字段。

问题是,可能有x行。我不能使用coalesce语句,因为行数存在差异。

希望有意义吗?

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]

希望这是有道理的。

由于

3 个答案:

答案 0 :(得分:0)

如果没有您正在处理的数据示例,很难准确地解释这个问题。您可以在here中创建一个实时示例供我们使用。

所以没有例子,如果我假设在两个表myTable1和myTable2中有数字字段[N]和[M]那么为什么不使用COALESCE和FULL OUTER JOIN ..

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)

答案 1 :(得分:0)

这应该可以将特定字段的每一行的值设置为该组重复行的最高排名非NULL值。

GroupingKey可以是一个字段,也可以是一个逻辑,用于确定一组需要合并为一行的行。排名是您的排名功能。

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

答案 2 :(得分:0)

如果性能足够重要,可以证明在几个小时的编码,您可以使用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,     主要集群(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存储过程将其与原始表交换。