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查询来解决您的问题,但即使您成功地构建了这样的查询,它也将非常棘手、复杂和不清楚。我不喜欢这样的解决方案。这就是为什么我还没有深入研究这个方向
- 另一方面,我的解决方案不需要创建临时表,这是一个优势
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