根据动态列查找匹配的记录

use*_*076 12 sql t-sql sql-server sql-server-2016

我有一份宠物清单:
宠物

我需要从Owner表中找到每个宠物的正确拥有者

拥有者

为了正确地将每个宠物与所有者匹配,我需要使用一个特殊的匹配表,如下所示:

匹配

因此,对于PetID = 2的宠物,我需要找到一个基于三个字段匹配的所有者:

    Pet.Zip = Owner.Zip 
    and Pet.OwnerName = Owner.Name 
    and Pet.Document = Owner.Document
Run Code Online (Sandbox Code Playgroud)

在我们的示例中,它将像这样工作:

 select top 1 OwnerID from owners
         where Zip = 23456 
         and Name = 'Alex' 
         and Document = 'a.csv'
Run Code Online (Sandbox Code Playgroud)

如果找不到OwnerID,则需要根据2个字段进行匹配(不使用优先级最高的字段)

在我们的例子中:

 select top 1 OwnerID from owners where
             Name = 'Alex' 
             and Document = 'a.csv'
Run Code Online (Sandbox Code Playgroud)

由于没有找到记录,我们需要匹配较少的字段.在我们的例子中:

select top 1 OwnerID from owners where Document = 'a.csv'
Run Code Online (Sandbox Code Playgroud)

现在,我们找到了OwnerID = 6的所有者.

现在我们需要更新ownerID = 6的宠物,然后我们可以处理下一个宠物.

我现在唯一能做到这一点的方法是循环或游标+动态SQL.

是否有可能在没有循环+动态sql的情况下实现这一点?也许STUFF + Pivot不知何故?

sql小提琴:http://sqlfiddle.com/#!18/10982/1/0

样本数据:

create table  temp_builder
(
    PetID int not null,
    Field varchar(30) not null,
    MatchTo varchar(30) not null,
    Priority int not null
)

insert into temp_builder values
(1,'Address', 'Addr',4),
(1,'Zip', 'Zip', 3),
(1,'Country', 'Country', 2),
(1,'OwnerName', 'Name',1),
(2,'Zip', 'Zip',3),
(2,'OwnerName','Name', 2),
(2,'Document', 'Document', 1),
(3,'Country', 'Country', 1)


create table temp_pets
(
    PetID int null,
    Address varchar(100) null,
    Zip int null,
    Country varchar(100) null,
    Document varchar(100) null,
    OwnerName varchar(100) null,
    OwnerID int null,
    Field1 bit null,
    Field2 bit null
)

insert into temp_pets values
(1, '123 5th st', 12345, 'US', 'test.csv', 'John', NULL, NULL, NULL),
(2, '234 6th st', 23456, 'US', 'a.csv', 'Alex', NULL, NULL, NULL),
(3, '345 7th st', 34567, 'US', 'b.csv', 'Mike', NULL, NULL, NULL)

create table temp_owners
(
    OwnerID int null,
    Addr varchar(100) null,
    Zip int null,
    Country varchar(100) null,
    Document varchar(100) null,
    Name varchar(100) null,
    OtherField bit null,
    OtherField2 bit null,
)

insert into temp_owners values
(1, '456 8th st',  45678, 'US', 'c.csv', 'Mike',  NULL, NULL),
(2, '678 9th st',  45678, 'US', 'b.csv', 'John',  NULL, NULL),
(3, '890 10th st', 45678, 'US', 'b.csv', 'Alex',  NULL, NULL),
(4, '901 11th st', 23456, 'US', 'b.csv', 'Alex',  NULL, NULL),
(5, '234 5th st',  12345, 'US', 'b.csv', 'John',  NULL, NULL),
(6, '123 5th st',  45678, 'US', 'a.csv', 'John',  NULL, NULL)
Run Code Online (Sandbox Code Playgroud)

编辑:我被一些很棒的建议和回应所淹没.我测试了它们,很多都适合我.不幸的是,我只能奖励一个解决方案.

Cod*_*ler 2

为了节省您的时间,我会立即说:

\n
    \n
  • 我的解决方案使用动态 SQL。Micha\xc5\x82 Turczyn 正确地指出,当比较列的名称存储在数据库中时,您无法避免它。
  • \n
  • 我的解决方案使用循环。我坚信你不会用纯 SQL 查询来解决这个问题,它在你声明的数据大小上运行得足够快(表有超过 1M 条记录)。您描述的逻辑意味着其本质上的迭代 - 从较大的匹配字段集到较低的匹配字段集。SQL 作为查询语言并不是为了涵盖这种棘手的场景而设计的。您可以尝试使用纯 SQL 查询来解决您的问题,但即使您设法构建这样的查询,它也会非常棘手、复杂且不清楚。我不喜欢这样的解决方案。这就是为什么我什至没有深入研究这个方向。
  • \n
  • 另一方面,我的解决方案不需要创建临时表,这是一个优点。
  • \n
\n

鉴于此,我的方法相当简单:

\n
    \n
  1. 有一个外循环从最大的匹配器集(所有匹配字段)迭代到最小的匹配器集(一个字段)。在第一次迭代中,当我们还不知道数据库中存储了多少个宠物匹配器时,我们会读取并使用它们。在下一次迭代中,我们将使用的匹配器数量减少 1(删除优先级最高的匹配器)。

    \n
  2. \n
  3. 内部循环迭代当前的匹配器集并构建WHERE比较表之间字段的Pets子句Owners

    \n
  4. \n
  5. 执行当前查询,如果某个所有者符合给定条件,我们就会从外循环中中断。

    \n
  6. \n
\n

下面是实现这个逻辑的代码:

\n
DECLARE @PetId INT = 2;\n\nDECLARE @MatchersLimit INT;\nDECLARE @OwnerID INT;\n\nWHILE (@MatchersLimit IS NULL OR @MatchersLimit > 0) AND @OwnerID IS NULL\nBEGIN\n\n    DECLARE @CurrMatchFilter VARCHAR(max) = ''\n    DECLARE @Field VARCHAR(30)\n    DECLARE @MatchTo VARCHAR(30)\n    DECLARE @CurrMatchersNumber INT = 0;\n\n    DECLARE @GetMatchers CURSOR;\n    IF @MatchersLimit IS NULL\n        SET @GetMatchers = CURSOR FOR SELECT Field, MatchTo FROM temp_builder WHERE PetID = @PetId ORDER BY Priority ASC;\n    ELSE\n        SET @GetMatchers = CURSOR FOR SELECT TOP (@MatchersLimit) Field, MatchTo FROM temp_builder WHERE PetID = @PetId ORDER BY Priority ASC;\n\n    OPEN @GetMatchers;\n    FETCH NEXT FROM @GetMatchers INTO @Field, @MatchTo;\n    WHILE @@FETCH_STATUS = 0\n    BEGIN\n        IF @CurrMatchFilter <> '' SET @CurrMatchFilter = @CurrMatchFilter + ' AND ';\n        SET @CurrMatchFilter = @CurrMatchFilter + ('temp_pets.' + @Field + ' = ' + 'temp_owners.' + @MatchTo);\n        FETCH NEXT FROM @GetMatchers INTO @field, @matchTo;\n        SET @CurrMatchersNumber = @CurrMatchersNumber + 1;\n    END\n    CLOSE @GetMatchers;\n    DEALLOCATE @GetMatchers;\n\n    IF @CurrMatchersNumber = 0 BREAK;\n\n    DECLARE @CurrQuery nvarchar(max) = N'SELECT @id = temp_owners.OwnerID FROM temp_owners INNER JOIN temp_pets ON (' + CAST(@CurrMatchFilter AS NVARCHAR(MAX)) + N') WHERE temp_pets.PetID = ' + CAST(@PetId AS NVARCHAR(MAX));\n    EXECUTE sp_executesql @CurrQuery, N'@id int OUTPUT', @id=@OwnerID OUTPUT;\n\n    IF @MatchersLimit IS NULL\n        SET @MatchersLimit = @CurrMatchersNumber - 1;\n    ELSE\n        SET @MatchersLimit = @MatchersLimit - 1;\n\nEND\n\nSELECT @OwnerID AS OwnerID, @MatchersLimit + 1 AS Matched;\n
Run Code Online (Sandbox Code Playgroud)\n

性能考虑

\n

这种方法基本上执行了 2 个查询:

\n
    \n
  1. SELECT Field, MatchTo FROM temp_builder WHERE PetID = @PetId;

    \n

    PetID您应该在表中的字段上添加索引temp_builder,并且该查询将执行得非常快。

    \n
  2. \n
  3. SELECT @id = temp_owners.OwnerID FROM temp_owners INNER JOIN temp_pets ON (temp_pets.Document = temp_owners.Document AND temp_pets.OwnerName = temp_owners.Name AND temp_pets.Zip = temp_owners.Zip AND ...) WHERE temp_pets.PetID = @PetId;

    \n

    这个查询看起来很可怕,因为它连接了两个大表 -temp_ownerstemp_pets。然而,temp_pets表是按PetID列过滤的,应该只产生一条记录。因此,如果您在列上有索引temp_pets.PetID(并且您应该这样做,因为该列看起来像主键),则查询将导致temp_owners表扫描。即使对于超过 1M 行的表,这样的扫描也不会花费很长时间。如果查询仍然太慢,您可以考虑为匹配器( 、等)temp_owners中使用的表列添加索引。添加索引有缺点,例如更大的数据库和更慢的插入/更新操作。所以在给列添加索引之前,先检查一下没有索引的表的查询速度。AddrZiptemp_owners

    \n
  4. \n
\n