1

任何人都可以帮助解决我遇到的 SQL 问题,我需要将 n 行合并到一条记录中。个别记录可能会或可能不会填充其他人所做的字段。

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

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

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

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

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

希望这有意义吗?

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存储过程将其与原始表交换。

于 2012-08-12T20:02:01.737 回答
0

如果没有您正在处理的数据示例,就很难准确地解释这个问题。你可以在这里创建一个活生生的例子供我们玩。

因此,如果没有示例,如果我假设 myTable1 和 myTable2 两个表中有数字字段 [N] 和 [M],那么为什么不直接使用带有 FULL OUTER JOIN 的 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 是一个字段或一些逻辑,用于确定需要合并为一个的一组行。排名是你的排名功能。

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 回答