Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 基于动态列查找匹配记录_Sql_Sql Server_Tsql_Sql Server 2016 - Fatal编程技术网

Sql 基于动态列查找匹配记录

Sql 基于动态列查找匹配记录,sql,sql-server,tsql,sql-server-2016,Sql,Sql Server,Tsql,Sql Server 2016,我有一份宠物清单: 我需要从主人表中为每只宠物找到一个正确的主人 为了正确匹配每只宠物和主人,我需要使用一个特殊的匹配表,如下所示: 因此,对于PetID=2的宠物,我需要根据三个字段找到一个匹配的主人: Pet.Zip = Owner.Zip and Pet.OwnerName = Owner.Name and Pet.Document = Owner.Document 在我们的示例中,它的工作原理如下: select top 1 OwnerID from

我有一份宠物清单:

我需要从主人表中为每只宠物找到一个正确的主人

为了正确匹配每只宠物和主人,我需要使用一个特殊的匹配表,如下所示:

因此,对于PetID=2的宠物,我需要根据三个字段找到一个匹配的主人:

    Pet.Zip = Owner.Zip 
    and Pet.OwnerName = Owner.Name 
    and Pet.Document = Owner.Document
在我们的示例中,它的工作原理如下:

 select top 1 OwnerID from owners
         where Zip = 23456 
         and Name = 'Alex' 
         and Document = 'a.csv'
如果找不到OwnerID,则需要基于2个字段进行匹配(不使用具有最高优先级的字段)

在我们的例子中:

 select top 1 OwnerID from owners where
             Name = 'Alex' 
             and Document = 'a.csv'
select top 1 OwnerID from owners where Document = 'a.csv'
因为没有找到记录,所以我们需要在更少的字段上进行匹配。在我们的例子中:

 select top 1 OwnerID from owners where
             Name = 'Alex' 
             and Document = 'a.csv'
select top 1 OwnerID from owners where Document = 'a.csv'
现在,我们找到了OwnerID=6的所有者

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

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

不使用循环+动态sql是否可以实现这一点?也许是什么东西+支点

sql fiddle:

样本数据:

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)

编辑:我被许多很棒的建议和回应淹没了。我已经测试过了,很多对我来说都很好。不幸的是,我只能将赏金奖励给一个解决方案。

要完成这项任务相当艰巨。。。我是这样做的:

首先,您需要添加一个表,该表将包含半
where
子句,即基于
temp\u builder
表准备使用的条件。另外,由于您有5列,我假设最多可以有5个条件。下面是表格的创建:

CREATE TABLE [dbo].[temp_builder_with_where](
    [petid] [int] NULL,
    [priority1] [bit] NULL,
    [priority2] [bit] NULL,
    [priority3] [bit] NULL,
    [priority4] [bit] NULL,
    [priority5] [bit] NULL,
    [whereClause] [varchar](200) NULL
) 
--it's good to create index, for better performance
create clustered index idx on [temp_builder_with_where]([petid])

insert into temp_builder_with_where
select petid,[priority1],[priority2],[priority3],[priority4],[priority5],
         '[pets].' + CAST(field as varchar(100)) + ' = [owners].' + CAST(matchto as varchar(100)) [whereClause]
from (
select petid, field, matchto, [priority],
        1 Priority1,
        case when [priority] > 1 then 1 else 0 end Priority2,
        case when [priority] > 2 then 1 else 0 end Priority3,
        case when [priority] > 3 then 1 else 0 end Priority4,
        case when [priority] > 4 then 1 else 0 end Priority5       
from temp_builder) [builder]
现在我们将循环浏览该表。您说过这个表包含8000行,所以我选择了另一种方式:动态查询现在将一次只为一个
petid
插入结果

为此,我们需要一个表来存储我们的结果:

CREATE TABLE [dbo].[TableWithNewId](
    [petid] [int] NULL,
    [ownerid] [int] NULL,
    [priority] [int] NULL
)
现在,动态SQL用于
insert
语句:

declare @query varchar(1000) = ''
declare @i int, @max int
set @i = 1
select @max = MAX(petid) from temp_builder_with_where

while @i <= @max
begin

    set @query = ''

    select @query = @query + whereClause1 + whereClause2 + whereClause3 + whereClause4 + whereClause5 + ' union all ' from (
    select 'insert into [MY_DATABASE].dbo.TableWithNewId  select ' + CAST(petid as varchar(3)) + ' [petid], [owners].ownerid, 1 [priority] from temp_pets [pets], temp_owners [owners] where (' + [where_petid] + [where1] + ')' [whereClause1],
           case when [where2] is null then '' else ' union all select ' + CAST(petid as varchar(3)) + ' [petid], [owners].ownerid, 2 [priority] from temp_pets [pets], temp_owners [owners] where (' + [where_petid] + [where2] + ')' end [whereClause2], 
           case when [where3] is null then '' else ' union all select ' + CAST(petid as varchar(3)) + ' [petid], [owners].ownerid, 3 [priority] from temp_pets [pets], temp_owners [owners] where (' + [where_petid] + [where3] + ')' end [whereClause3], 
           case when [where4] is null then '' else ' union all select ' + CAST(petid as varchar(3)) + ' [petid], [owners].ownerid, 4 [priority] from temp_pets [pets], temp_owners [owners] where (' + [where_petid] + [where4] + ')' end [whereClause4], 
           case when [where5] is null then '' else ' union all select ' + CAST(petid as varchar(3)) + ' [petid], [owners].ownerid, 5 [priority] from temp_pets [pets], temp_owners [owners] where (' + [where_petid] + [where5] + ')' end [whereClause5]
    from (
            select petid, 'petid = ' + CAST(petid as nvarchar(3)) [where_petid],
               (select ' and ' + whereClause from temp_builder_with_where where petid = t.petid and priority1 = 1 for xml path(''),type).value('(.)[1]', 'varchar(500)') [where1],
               (select ' and ' + whereClause from temp_builder_with_where where petid = t.petid and priority2 = 1 for xml path(''),type).value('(.)[1]', 'varchar(500)') [where2],
               (select ' and ' + whereClause from temp_builder_with_where where petid = t.petid and priority3 = 1 for xml path(''),type).value('(.)[1]', 'varchar(500)') [where3],
               (select ' and ' + whereClause from temp_builder_with_where where petid = t.petid and priority4 = 1 for xml path(''),type).value('(.)[1]', 'varchar(500)') [where4],
               (select ' and ' + whereClause from temp_builder_with_where where petid = t.petid and priority5 = 1 for xml path(''),type).value('(.)[1]', 'varchar(500)') [where5]
       from temp_builder_with_where [t]
       where petid = @i
        group by petid
    ) a
    ) a
    --remove last union all
    set @query = left(@query, len(@query) - 10)
    exec (@query)

    set @i = @i + 1

end
DECLARE @Sql nvarchar(max) = ''

基于该结果,您现在可以基于最低优先级将
OwnerId
分配给
PetId
(好吧,您没有说如何处理发现多个
OwnerId
具有相同优先级的情况)。

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

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

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

  • 内部循环迭代当前匹配器集,并构建
    WHERE
    子句,该子句比较
    Pets
    Owners
    表之间的字段

  • 执行当前查询,如果某些所有者符合给定的条件,我们将从外部循环中断

  • 以下是实现此逻辑的代码:

    DECLARE @PetId INT = 2;
    
    DECLARE @MatchersLimit INT;
    DECLARE @OwnerID INT;
    
    WHILE (@MatchersLimit IS NULL OR @MatchersLimit > 0) AND @OwnerID IS NULL
    BEGIN
    
        DECLARE @CurrMatchFilter VARCHAR(max) = ''
        DECLARE @Field VARCHAR(30)
        DECLARE @MatchTo VARCHAR(30)
        DECLARE @CurrMatchersNumber INT = 0;
    
        DECLARE @GetMatchers CURSOR;
        IF @MatchersLimit IS NULL
            SET @GetMatchers = CURSOR FOR SELECT Field, MatchTo FROM temp_builder WHERE PetID = @PetId ORDER BY Priority ASC;
        ELSE
            SET @GetMatchers = CURSOR FOR SELECT TOP (@MatchersLimit) Field, MatchTo FROM temp_builder WHERE PetID = @PetId ORDER BY Priority ASC;
    
        OPEN @GetMatchers;
        FETCH NEXT FROM @GetMatchers INTO @Field, @MatchTo;
        WHILE @@FETCH_STATUS = 0
        BEGIN
            IF @CurrMatchFilter <> '' SET @CurrMatchFilter = @CurrMatchFilter + ' AND ';
            SET @CurrMatchFilter = @CurrMatchFilter + ('temp_pets.' + @Field + ' = ' + 'temp_owners.' + @MatchTo);
            FETCH NEXT FROM @GetMatchers INTO @field, @matchTo;
            SET @CurrMatchersNumber = @CurrMatchersNumber + 1;
        END
        CLOSE @GetMatchers;
        DEALLOCATE @GetMatchers;
    
        IF @CurrMatchersNumber = 0 BREAK;
    
        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));
        EXECUTE sp_executesql @CurrQuery, N'@id int OUTPUT', @id=@OwnerID OUTPUT;
    
        IF @MatchersLimit IS NULL
            SET @MatchersLimit = @CurrMatchersNumber - 1;
        ELSE
            SET @MatchersLimit = @MatchersLimit - 1;
    
    END
    
    SELECT @OwnerID AS OwnerID, @MatchersLimit + 1 AS Matched;
    
    DECLARE@PetId INT=2;
    声明@matcherslimitint;
    声明@owneridint;
    而(@MatchersLimit为NULL或@MatchersLimit>0)和@OwnerID为NULL
    开始
    声明@CurrMatchFilter VARCHAR(最大值)=”
    声明@Field VARCHAR(30)
    声明@MatchTo VARCHAR(30)
    声明@CurrMatcherNumber INT=0;
    声明@GetMatchers游标;
    如果@MatchersLimit为NULL
    为选择字段设置@GetMatchers=光标,从temp_builder中匹配到,其中PetID=@PetID按优先级排序ASC;
    其他的
    将@GetMatchers=光标设置为SELECT TOP(@MatchersLimit)字段,从temp_builder中选择MatchTo,其中PetID=@PetID ORDER BY Priority ASC;
    打开@GetMatchers;
    将下一个从@GetMatchers获取到@Field,@MatchTo;
    而@@FETCH\u STATUS=0
    开始
    如果@CurrMatchFilter''设置@CurrMatchFilter=@CurrMatchFilter+'和';
    设置@CurrMatchFilter=@CurrMatchFilter+('temp_pets.++@Field+'='+'temp_owners.++@MatchTo);
    将下一个从@GetMatchers获取到@field,@matchTo;
    设置@CurrMatchersNumber=@CurrMatchersNumber+1;
    结束
    关闭@GetMatchers;
    释放@GetMatchers;
    如果@CurrMatchersNumber=0,则中断;
    声明@CurrQuery-nvarchar(max)=N'SELECT@id=temp_-owners.OwnerID从temp_-owners内部加入temp_-pets ON('+CAST(@CurrMatchFilter作为nvarchar(max))+N'),其中temp_-pets.PetID='+CAST(@PetID作为nvarchar(max));
    执行sp_executesql@CurrQuery,N'@id int OUTPUT',@id=@OwnerID OUTPUT;
    如果@MatchersLimit为NULL
    设置@MatchersLimit=@CurrMatchersNumber-1;
    其他的
    设置@MatchersLimit=@MatchersLimit-1;
    结束
    选择@OwnerID作为OwnerID,选择@MatchersLimit+1作为匹配项;
    
    性能注意事项

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

  • 从temp_builder中选择匹配字段,其中PetID=@PetID

    您应该在
    temp\u builder
    表中的
    PetID
    字段中添加索引,此查询将快速执行

  • 选择@id=temp\u owners.OwnerID从temp\u owners内部加入temp\u pets ON(temp\u pet
    
    , UpdateStatementCTE AS
    (
        SELECT  PetId,
                Priority,
                'UPDATE p 
                SET OwnerID = o.OwnerID 
                FROM temp_pets p 
                INNER JOIN temp_owners o ON ' + OnClause + ' 
                WHERE p.PetId = '+ CAST(PetId as varchar(10)) +'
                AND p.OwnerID IS NULL; -- THIS IS CRITICAL!
                ' AS SQL
        FROM OnClauseCTE
    )
    
    SELECT @Sql = @Sql + SQL
    FROM UpdateStatementCTE    
    ORDER BY PetId, Priority DESC -- ORDER BY Priority is CRITICAL!
    
    UPDATE p 
    SET OwnerID = o.OwnerID 
    FROM temp_pets p 
    INNER JOIN temp_owners o ON p.Address = o.Addr AND p.Zip = o.Zip AND p.Country = o.Country AND p.OwnerName = o.Name 
    WHERE p.PetId = 1
    AND p.OwnerID IS NULL;
    
    ...
    
    UPDATE p 
    SET OwnerID = o.OwnerID 
    FROM temp_pets p 
    INNER JOIN temp_owners o ON p.OwnerName = o.Name 
    WHERE p.PetId = 1
    AND p.OwnerID IS NULL;
    
    ...
    
    UPDATE p 
    SET OwnerID = o.OwnerID 
    FROM temp_pets p 
    INNER JOIN temp_owners o ON p.OwnerName = o.Name AND p.Document = o.Document 
    WHERE p.PetId = 2
    AND p.OwnerID IS NULL;
    
    ...
    
    UPDATE p 
    SET OwnerID = o.OwnerID 
    FROM temp_pets p 
    INNER JOIN temp_owners o ON p.Country = o.Country 
    WHERE p.PetId = 3
    AND p.OwnerID IS NULL;
    
    PetID   Address         Zip     Country     Document    OwnerName   OwnerID     Field1  Field2
    1       123 5th st      12345   US          test.csv    John        5           NULL    NULL
    2       234 6th st      23456   US          a.csv       Alex        6           NULL    NULL
    3       345 7th st      34567   US          b.csv       Mike        1           NULL    NUL
    
    -- We start off by converting the priority values into int values that are suitable to add up to a bit array
    -- I'll save those in a #Temp table to cut that piece of logic out of the final query
    IF EXISTS(SELECT 1 FROM #TempBuilder)
    BEGIN
        DROP TABLE #TempBuilder
    END
    SELECT 
        PetID, Field, MatchTo, 
        CASE [Priority] 
        WHEN 1 THEN 16 -- Priority one goes on the 16-bit (10000)
        WHEN 2 THEN 8 -- Priority two goes on the 8-bit (01000)
        WHEN 3 THEN 4 -- Priority three goes on the 4-bit (00100)
        WHEN 4 THEN 2 -- Priority four goes on the 2-bit (00010)
        WHEN 5 THEN 1 END AS [Priority] -- Priority five goes on the 1-bit (00001)
    INTO #TempBuilder
    FROM dbo.temp_builder;
    
    -- Then we pivot the match priorities to be able to join them on our pets
    WITH PivotedMatchPriorities AS (
        SELECT
            PetId,
            [Address], [Zip], [Country], [OwnerName], [Document]
        FROM (SELECT PetId, Field, [Priority] FROM #TempBuilder) tb
            PIVOT 
            (
                SUM([Priority])
                FOR [Field] IN ([Address], [Zip], [Country], [OwnerName], [Document])
            )
            AS PivotedMatchPriorities
    ),
    -- Next we get (for each pet) all owners with ANY matching value
    -- We want to filter the matching owners to find these that match priorities 1 (priority sum 10000, i.e. 16), 
        --- or match priorities 1 & 2 (priority sum 11000, i.e. 24)
        --- or match priorities 1 & 2 & 3 (priority sum 11100, i.e. 28)
        --- etc.
    MatchingOwners AS (
        SELECT o.*,
            p.PetID,
            pmp.[Address] AS AddressPrio,
            pmp.Country AS CountryPrio,
            pmp.Zip AS ZipPrio,
            pmp.OwnerName AS OwnerPrio,
            pmp.Document AS DocumentPrio,
            CASE WHEN o.Addr = p.[Address] THEN ISNULL(pmp.[Address],0) ELSE 0 END
            + CASE WHEN o.Zip = p.Zip THEN ISNULL(pmp.Zip,0) ELSE 0 END
            + CASE WHEN o.Country = p.Country THEN ISNULL(pmp.Country,0) ELSE 0 END
            + CASE WHEN o.Document = p.Document THEN ISNULL(pmp.[Document],0) ELSE 0 END
            + CASE WHEN o.[Name] = p.OwnerName THEN ISNULL(pmp.OwnerName,0) ELSE 0 END AS MatchValue -- Calculate a match value for each matching owner
        FROM dbo.temp_pets p
            INNER JOIN dbo.temp_owners o 
                ON p.[Address] = o.Addr
                OR p.Country = o.Country
                OR p.Document = o.Document
                OR p.OwnerName = o.[Name]
                OR p.Zip = o.Zip
            INNER JOIN PivotedMatchPriorities pmp ON pmp.PetId = p.PetId
    ),
    -- Now we can get all owners that match the pet, along with a match value for each owner.
    -- We want to rank the matching owners for each pet to allow selecting the best ranked owner
    -- Note: In the demo data there are multiple owners that match petId 3 equally well. We'll pick a random one in such cases.
    RankedValidMatches AS (
        SELECT 
            PetID,
            OwnerID,
            MatchValue,
            ROW_NUMBER() OVER (PARTITION BY PetID ORDER BY MatchValue DESC) AS OwnerRank
        FROM MatchingOwners
        WHERE MatchValue IN (16, 24, 28, 30, 31)
    )
    -- Finally we can get the best valid match per pet
    --SELECT * FROM RankedValidMatches WHERE OwnerRank = 1
    -- Or we can update our pet table to reflect our results
    UPDATE dbo.temp_pets
    SET OwnerID = rvm.OwnerID
    FROM dbo.temp_pets tp
        INNER JOIN RankedValidMatches rvm ON rvm.PetID = tp.PetID AND rvm.OwnerRank = 1
    
    -- Adding indexes to OP's tables to optimize the queries that follow.
    CREATE INDEX IX_PetID ON temp_builder (PetID)
    CREATE INDEX IX_Priority ON temp_builder (Priority)
    CREATE INDEX IX_PetID ON temp_pets (PetID)
    CREATE INDEX IX_OwnerID ON temp_owners (OwnerID)
    
    -- Helper table for pets. Each column has its own index.
    CREATE TABLE PetKey (
        PetID int NOT NULL PRIMARY KEY CLUSTERED,
        KeyNames varchar(200) NOT NULL INDEX IX_KeyNames NONCLUSTERED,
        KeyValues varchar(900) NOT NULL INDEX IX_KeyValues NONCLUSTERED
    )
    
    -- Helper table for owners. Each column has its own index.
    CREATE TABLE OwnerKey (
        OwnerID int NOT NULL PRIMARY KEY CLUSTERED,
        KeyValues varchar(900) NULL INDEX IX_KeyValues NONCLUSTERED
    )
    
    -- For every pet, create a record in table PetKey.
    -- (Unless the pet already belongs to someone.)
    INSERT INTO PetKey (PetID, KeyNames, KeyValues)
    SELECT PetID, '', ''
    FROM temp_pets
    WHERE OwnerID IS NULL
    
    -- For every owner, create a record in table OwnerKey.
    INSERT INTO OwnerKey (OwnerID, KeyValues)
    SELECT OwnerID, ''
    FROM temp_owners
    
    -- Populate columns KeyNames and KeyValues in table PetKey.
    -- Lowest priority (i.e. highest number in column Priority) comes first.
    -- We use CHAR(1) as a separator character; anything will do as long as it does not occur in any column values.
    -- Example: when a pet has address as prio 1, zip as prio 2, then:
    --    KeyNames = 'Zip' + CHAR(1) + 'Address' + CHAR(1)
    --    KeyValues = '12345' + CHAR(1) + 'John' + CHAR(1)
    -- NULL is replaced by CHAR(2); can be any value as long as it does not match any owner's value.
    DECLARE @priority int = 1
    WHILE EXISTS (SELECT * FROM temp_builder WHERE Priority = @priority)
    BEGIN
        UPDATE pk
        SET KeyNames = b.Field + CHAR(1) + KeyNames,
            KeyValues = ISNULL(CASE b.Field
                                   WHEN 'Address' THEN p.Address
                                   WHEN 'Zip' THEN CAST(p.Zip AS varchar)
                                   WHEN 'Country' THEN p.Country
                                   WHEN 'Document' THEN p.Document
                                   WHEN 'OwnerName' THEN p.OwnerName
                               END, CHAR(2)) +
                        CHAR(1) + KeyValues
        FROM PetKey pk
        INNER JOIN temp_pets p ON p.PetID = pk.PetID
        INNER JOIN temp_builder b ON b.PetID = pk.PetID
        WHERE b.Priority = @priority
    
        SET @priority = @priority + 1
    END
    
    -- Loop through all distinct key combinations.
    DECLARE @maxKeyNames varchar(200), @namesToAdd varchar(200), @index int
    SELECT @maxKeyNames = MAX(KeyNames) FROM PetKey
    WHILE @maxKeyNames <> '' BEGIN
        -- Populate column KeyValues in table OwnerKey.
        -- The order of the values is determined by the column names listed in @maxKeyNames.
        UPDATE OwnerKey
        SET KeyValues = ''
    
        SET @namesToAdd = @maxKeyNames
        WHILE @namesToAdd <> '' BEGIN
            SET @index = CHARINDEX(CHAR(1), @namesToAdd)
    
            UPDATE ok
            SET KeyValues = KeyValues +
                            CASE LEFT(@namesToAdd, @index - 1)
                                WHEN 'Address' THEN o.Addr
                                WHEN 'Zip' THEN CAST(o.Zip AS varchar)
                                WHEN 'Country' THEN o.Country
                                WHEN 'Document' THEN o.Document
                                WHEN 'OwnerName' THEN o.Name
                            END +
                            CHAR(1)
            FROM OwnerKey ok
            INNER JOIN temp_owners o ON o.OwnerID = ok.OwnerID
    
            SET @namesToAdd = SUBSTRING(@namesToAdd, @index + 1, 200)
        END
    
        -- Match pets with owners, based on their KeyValues.
        UPDATE p
        SET OwnerID = (SELECT TOP 1 ok.OwnerID FROM OwnerKey ok WHERE ok.KeyValues = pk.KeyValues)
        FROM temp_pets p
        INNER JOIN PetKey pk ON pk.PetID = p.PetID
        WHERE pk.KeyNames = @maxKeyNames
    
        -- Pets that were successfully matched are removed from PetKey.
        DELETE FROM pk
        FROM PetKey pk
        INNER JOIN temp_pets p ON p.PetID = pk.PetID
        WHERE p.OwnerID IS NOT NULL
    
        -- For pets with no match, strip off the first (lowest priority) name and value.
        SET @namesToAdd = SUBSTRING(@maxKeyNames, CHARINDEX(CHAR(1), @maxKeyNames) + 1, 200)
    
        UPDATE pk
        SET KeyNames = @namesToAdd,
            KeyValues = SUBSTRING(KeyValues, CHARINDEX(CHAR(1), KeyValues) + 1, 900)
        FROM PetKey pk
        INNER JOIN temp_pets p ON p.PetID = pk.PetID
        WHERE pk.KeyNames = @maxKeyNames
    
        -- Next key combination.    
        SELECT @maxKeyNames = MAX(KeyNames) FROM PetKey
    END
    
    ;WITH CTE_Builder
     AS
     (
         SELECT  [PetID]
                ,[Field]
                ,[Priority]
                ,[MatchTo]
                ,POWER(2, [Priority] - 1) AS [FieldRank] -- Define the field ranking as bit set numbered item.
                ,SUM(POWER(2, [Priority] - 1)) OVER (PARTITION BY [PetID] ORDER BY [Priority] ROWS UNBOUNDED PRECEDING) FieldSetRank -- Sum all the bit set IDs to define what constitutes a completed field set ordered by priority.
         FROM   temp_builder
     ),
    CTE_PetsUnpivoted
    AS
    (   -- Unpivot pets table and assign Field Rank and Field Set Rank.
        SELECT   [PetsUnPivot].[PetID]
                ,[PetsUnPivot].[Field]
                ,[Builder].[MatchTo]
                ,[PetsUnPivot].[FieldValue]
                ,[Builder].[Priority]
                ,[Builder].[FieldRank]
                ,[Builder].[FieldSetRank]
    
        FROM 
           (
                SELECT [PetID], [Address], CAST([Zip] AS VARCHAR(100)) AS [Zip], [Country], [Document], [OwnerName]
                FROM temp_pets
            ) [Pets]
        UNPIVOT
           (FieldValue FOR Field IN 
              ([Address], [Zip], [Country], [Document], [OwnerName])
        ) AS [PetsUnPivot]
        INNER JOIN [CTE_Builder] [Builder] ON [PetsUnPivot].PetID = [Builder].PetID AND [PetsUnPivot].Field = [Builder].Field
    ),
    CTE_Owners
    AS
    (
        -- Unpivot Owners table and join with unpivoted Pets table on field name and field value.  
        -- Next assign Pets field rank then calculated the field set rank (MatchSetRank) based on actual matches made.
        SELECT   [OwnersUnPivot].[OwnerID]
                ,[Pets].[PetID]
                ,[OwnersUnPivot].[Field]
                ,[Pets].Field AS [PetField]
                ,[Pets].FieldValue as PetFieldValue
                ,[OwnersUnPivot].[FieldValue]
                ,[Pets].[Priority]
                ,[Pets].[FieldRank]
                ,[Pets].[FieldSetRank]
                ,SUM([FieldRank]) OVER (PARTITION BY [Pets].[PetID], [OwnersUnPivot].[OwnerID] ORDER BY [Pets].[Priority] ROWS UNBOUNDED PRECEDING) MatchSetRank
        FROM 
           (
                SELECT [OwnerID], [Addr], CAST([Zip] AS VARCHAR(100)) AS [Zip], [Country], [Document], [Name]
                FROM temp_owners
            ) [Owners]
        UNPIVOT
           (FieldValue FOR Field IN 
              ([Addr], [Zip], [Country], [Document], [Name])
        ) AS [OwnersUnPivot]
        INNER JOIN [CTE_PetsUnpivoted] [Pets] ON [OwnersUnPivot].[Field] = [Pets].[MatchTo] AND [OwnersUnPivot].[FieldValue] = [Pets].[FieldValue]
    ),
    CTE_FinalRanking
    AS
    (
        SELECT   [PetID]
                ,[OwnerID]
                -- -- Calculate final rank, if multiple matches have the same rank then multiple rows will be returned per pet. 
                -- Change the “RANK()” function to "ROW_NUMBER()" to only return on result per pet.
                ,RANK() OVER (PARTITION BY [PetID] ORDER BY [MatchSetRank] DESC) AS [FinalRank] 
        FROM    CTE_Owners
        WHERE   [FieldSetRank] = [MatchSetRank] -- Only return records where the field sets calculated based on 
                                                -- actual matches is equal to desired field set ranks. This will 
                                                -- eliminate matches where the number of fields that meets the 
                                                -- criteria is the same but does not meet priority requirements. 
    )
    SELECT   [PetID]
            ,[OwnerID]
    FROM    CTE_FinalRanking
    WHERE   [FinalRank] = 1
    
    create table builder
    (
        PetID int not null,
        Query varchar(max)
    )
    
    INSERT INTO builder
    VALUES (1, 'SELECT TOP 1 *
    FROM pets
    INNER JOIN Owners
        ON Owners.Name = pets.OwnerName 
    WHERE petId = 1
    ORDER BY 
        CASE WHEN Owners.Country = pets.Country THEN 0 ELSE 1 END,
        CASE WHEN Owners.Zip = pets.Zip THEN 0 ELSE 1 END,
        CASE WHEN Owners.Addr = pets.Address THEN 0 ELSE 1 END'),
    (2, 'SELECT TOP 1 *
    FROM pets
    INNER JOIN Owners
        ON Owners.Name = pets.OwnerName 
    WHERE petId = 2
    ORDER BY 
        CASE WHEN Owners.Document = pets.Document THEN 0 ELSE 1 END,
        CASE WHEN Owners.Name = pets.OwnerName THEN 0 ELSE 1 END,
        CASE WHEN Owners.Zip = pets.Zip THEN 0 ELSE 1 END'),
    (3, 'SELECT TOP 1 *
    FROM pets
    INNER JOIN Owners
        ON Owners.Name = pets.OwnerName 
    WHERE petId = 3
    ORDER BY 
        CASE WHEN Owners.Country = pets.Country THEN 0 ELSE 1 END
    ')
    
    create table 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 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 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 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)
    
    DECLARE @query varchar(max)
    SELECT TOP 1 @query = query
    FROM builder
    WHERE petId =1
    
    EXEC (@query)
    
    declare @Pets table 
    (
        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 @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)
    
    declare @owners table
    (
        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 @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)
    
    declare @builder table  
    (
        PetID int not null,
        Field varchar(30) not null,
        MatchTo varchar(30) not null,
        Priority int not null
    )
    
    insert into @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)
    
    select distinct p.PetID, min(o.OwnerID) as ownerID from @pets p
    inner join @builder b on p.PetID = b.PetID
    inner join @owners o on 
    ( 
       (case when b.Field = 'Address' and b.Priority = 1 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 1 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 1 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 1 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 1 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 1 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 1 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 1 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 1 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 1 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 2 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 2 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 2 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 2 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 2 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 2 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 2 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 2 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 2 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 2 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 3 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 3 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 3 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 3 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 3 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 3 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 3 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 3 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 3 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 3 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 4 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 4 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 4 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 4 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 4 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 4 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 4 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 4 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 4 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 4 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 5 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 5 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 5 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 5 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 5 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 5 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 5 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 5 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 5 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 5 then o.Document else '-1' end)                    
    )
    group by p.PetID
    
    union
    --------------------------
    
    select distinct p.PetID, min(o.OwnerID) as ownerID from @pets p
    inner join @builder b on p.PetID = b.PetID
    inner join @owners o on 
    ( 
       (case when b.Field = 'Address' and b.Priority = 1 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 1 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 1 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 1 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 1 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 1 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 1 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 1 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 1 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 1 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 2 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 2 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 2 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 2 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 2 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 2 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 2 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 2 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 2 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 2 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 3 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 3 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 3 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 3 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 3 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 3 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 3 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 3 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 3 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 3 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 4 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 4 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 4 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 4 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 4 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 4 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 4 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 4 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 4 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 4 then o.Document else '-1' end)                    
    )
    group by p.PetID
    
    union
    --------------------------
    
    select distinct p.PetID, min(o.OwnerID) as ownerID from @pets p
    inner join @builder b on p.PetID = b.PetID
    inner join @owners o on 
    ( 
       (case when b.Field = 'Address' and b.Priority = 1 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 1 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 1 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 1 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 1 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 1 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 1 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 1 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 1 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 1 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 2 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 2 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 2 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 2 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 2 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 2 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 2 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 2 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 2 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 2 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 3 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 3 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 3 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 3 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 3 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 3 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 3 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 3 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 3 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 3 then o.Document else '-1' end)                    
    )
    group by p.PetID
    
    union
    ------------------------
    
    select distinct p.PetID, min(o.OwnerID) as ownerID from @pets p
    inner join @builder b on p.PetID = b.PetID
    inner join @owners o on 
    ( 
       (case when b.Field = 'Address' and b.Priority = 1 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 1 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 1 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 1 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 1 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 1 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 1 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 1 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 1 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 1 then o.Document else '-1' end)                    
    )
    AND
    ( 
       (case when b.Field = 'Address' and b.Priority = 2 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 2 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 2 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 2 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 2 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 2 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 2 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 2 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 2 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 2 then o.Document else '-1' end)                    
    )
    group by p.PetID
    
    union
    ------------------------
    
    select distinct p.PetID, min(o.OwnerID) as ownerID from @pets p
    inner join @builder b on p.PetID = b.PetID
    inner join @owners o on 
    ( 
       (case when b.Field = 'Address' and b.Priority = 1 then p.Address else '0' end) = (case when b.MatchTo = 'Addr' and b.Priority = 1 then o.Addr else '-1' end)                  
    or (case when b.Field = 'Zip' and b.Priority = 1 then p.Zip else '0' end) = (case when b.MatchTo = 'Zip' and b.Priority = 1 then o.Zip else '-1' end)                    
    or (case when b.Field = 'Country' and b.Priority = 1 then p.Country else '0' end) = (case when b.MatchTo = 'Country' and b.Priority = 1 then o.Country else '-1' end)                    
    or (case when b.Field = 'OwnerName' and b.Priority = 1 then p.OwnerName else '0' end) = (case when b.MatchTo = 'Name' and b.Priority = 1 then o.Name else '-1' end)                  
    or (case when b.Field = 'Document' and b.Priority = 1 then p.Document else '0' end) = (case when b.MatchTo = 'Document' and b.Priority = 1 then o.Document else '-1' end)                    
    )
    group by p.PetID
    
    PetID   OwnerID
    1       2
    2       6
    3       1
    
    select PetID ,COALESCE(
     (select  top 1 OwnerID from temp_owners
         where Zip = pets.Zip 
         and Name = pets.OwnerName
         and Document = pets.Document) ,
         (select top 1 OwnerID from temp_owners where
             Name = pets.OwnerName 
             and Document = pets.Document)  ,
             (select top 1 OwnerID from temp_owners where
              Document = pets.Document)  ) OwnerId
           from 
    temp_pets pets
    
    PetID   OwnerId
    1       (null)
    2       6
    3       2
    
    ;with
    -- r: rules table
    r as (select * from temp_builder),
    -- o0: owners table with all fields unpivotable (varchar)
    o0 as (SELECT [OwnerID], [Addr], CAST([Zip] AS VARCHAR(100)) AS [Zip], [Country], [Document], [Name] FROM temp_owners ),
    -- o: owners table unpivoted
    o as (
        SELECT * FROM o0 
        UNPIVOT (FieldValue FOR Field IN ([Addr], [Zip], [Country], [Document], [Name])) AS p
    ),
    -- p0: pets table with all fields unpivotable (varchar)
    p0 as (SELECT [PetID], [Address], CAST([Zip] AS VARCHAR(100)) AS [Zip], [Country], [Document], [OwnerName] FROM temp_pets),
    -- p: petstable unpivoted
    p as (
        SELECT * FROM p0
        UNPIVOT (FieldValue FOR Field IN ([Address], [Zip], [Country], [Document], [OwnerName])) AS p
    ),
    -- t: join up all data and keep only matching priority
    d as (
        select petid, ownerid, priority 
        from (
            select r.*, o.ownerid, ROW_NUMBER() over (partition by r.petid, o.ownerid order by r.petid, o.ownerid, priority) calc_priority
            from r
            join p on (r.field = p.field) and (p.petid = r.petid)
            join o on (r.matchto = o.field) and (p.fieldvalue=o.fieldvalue) 
        ) x
        where calc_priority=priority
    ),
    -- g: group by the matching rows to know the best priority reached for each pet
    g as (
        select petid, max(priority) max_priority
        from d
        group by petid
    )
    -- output only the rows with best priority
    select d.*
    from d
    join g on d.petid = g.petid and d.priority = g.max_priority
    order by petid, ownerid, priority