dan*_*111 5 sql db2 algorithm combinatorics
我在SQL中开发匹配算法时遇到问题.我有一张桌子subjects.这些中的每一个都需要与表中相同数量的行匹配controls(为了这个问题,我们需要为每个主题选择两行或控件).所选控件的位置必须完全匹配,并且所选控件的值应match_field尽可能接近主题.
以下是一些示例数据:
表科目:
id location match_field
1 1 190
2 2 2000
3 1 100
Run Code Online (Sandbox Code Playgroud)
表格控件:
id location match_field
17 1 70
11 1 180
12 1 220
13 1 240
14 1 500
15 1 600
16 1 600
10 2 30
78 2 1840
79 2 2250
Run Code Online (Sandbox Code Playgroud)
以下是样本数据的最佳结果:
subject_id control_id location match_field_diff
1 12 1 30
1 13 1 50
2 78 2 160
2 79 2 250
3 17 1 30
3 11 1 80
Run Code Online (Sandbox Code Playgroud)
它变得棘手,因为例如,控制11是与对象1最接近的匹配.然而,在最佳解决方案中,控制11与对象3匹配.
我相信匈牙利算法接近这个问题的"正确"解决方案.然而,没有相同数量的受试者和对照,也不会使用所有对照(我有几千个受试者和几百万个潜在的对照).
没有必要获得绝对最佳结果; 一个非常好的近似对我来说没问题.
似乎应该有一个很好的基于集合的解决方案来解决这个问题,但我想不出怎么做.以下是一些代码,仅根据位置为每个主题分配相同数量的控件:
select * from (
select subject.id,
control.id,
subject.location,
row_number() over (
partition by subject.location
order by subject.id, control.id
) as rn,
count(distinct control.id) over (
partition by subject.location
) as controls_in_loc
from subjects
join controls on control.location = subject.location
)
where mod(rn,controls_in_loc+1) = 1
Run Code Online (Sandbox Code Playgroud)
但是,我无法弄清楚如何添加模糊匹配组件.我正在使用DB2,但如果您使用其他东西,可以将算法转换为DB2.
在此先感谢您的帮助!
更新:我基本上确信SQL不适合这项工作.但是,为了确保(并且因为它是一个有趣的问题),我提供了一个赏金,看看是否可以使用有效的SQL解决方案.它需要是一个基于集合的解决方案.它可以使用迭代(多次循环同一个查询以实现结果),但迭代次数需要远远小于大型表的行数.它不应该循环遍历表中的每个元素或使用游标.
虽然匈牙利算法可以运行,但在您的情况下可以使用更简单的算法.隐式成本矩阵是特殊形式的对称矩阵:
ABS(SUBJ.match_field-CTRL.match_field)
Run Code Online (Sandbox Code Playgroud)
因此,可以相对容易地证明,在最佳的分配{SUBJ 我,CTRL Ĵ }通过有序SUBJ.match_field的值CTRL.match_field将被责令为好.
证明:考虑一个任务 {SUBJ 我,CTRL Ĵ }通过命令SUBJ.match_field不是由命令CTRL.match_field.然后你至少有一个反转,即一对赋值 {SUBJ i1,CTRL j1 }和 {SUBJ i2,CTRL j2 }这样
SUBJ.match_fieldi1 < SUBJ.match_fieldi2,但是
CTRL.match_fieldj1 > CTRL.match_fieldj2
然后你可以用一个非反转的对替换倒置的对
{SUBJ i1,CTRL j2 }和{SUBJ i2,CTRL j1 }
成本小于或等于SUBJ.match_field(i1,i2)和CTRL.match_field(j1,j2)的所有六个相对位置的反向分配的成本(链接到Wolfram Alpha).:证明
有了这个观察结果,很容易证明下面的动态编程算法提出了最佳分配:
N每个主题的副本; 订购match_fieldmatch_fieldassignments大小的N * subject.SIZEmem尺寸的N * subject.SIZE由control.SIZE用于记忆化 ; 将所有元素设置为-1Recursive_Assign以下伪代码定义assignments表现在包含N每个主题i在N*i(包括)和N*(i+1)独占之间的位置的分配.FUNCTION Recursive_Assign
// subjects contains each original subj repeated N times
PARAM subjects : array of int[subjectLength]
PARAM controls: array of int[controlLength]
PARAM mem : array of int[subjectLength,controlLength]
PARAM sp : int // current subject position
PARAM cp : int // current control position
PARAM assign : array of int[subjectLength]
BEGIN
IF sp == subjects.Length THEN RETURN 0 ENDIF
IF mem[sp, cp] > 0 THEN RETURN mem[sp, cp] ENDIF
int res = ABS(subjects[sp] - controls[cp])
+ Recursive_Assign(subjects, controls, mem, sp + 1, cp + 1, assign)
assign[sp] = cp
IF cp+1+subjects.Length-sp < controls.Length THEN
int alt = Recursive_Assign(subjects, controls, mem, sp, cp + 1, assign)
IF alt < res THEN
res = alt
ELSE
assign[sp] = cp
ENDIF
ENDIF
RETURN (mem[sp, cp] = res)
END
Run Code Online (Sandbox Code Playgroud)
该算法已准备好在SQL中以基于集合的方式重写.试图将其纳入原始问题设置(按位置分组并制作主题的多个副本)会给已经相当复杂的过程增加不必要的复杂层,因此我将通过使用表来简化相当多的事情SQL Server的评估参数.我不确定DB2是否提供类似的功能,但如果没有,您应该能够用临时表替换它们.
下面的存储过程几乎直接将上述伪代码转换为SQL Server的存储过程语法:
CREATE TYPE SubjTableType AS TABLE (row int, id int, match_field int)
CREATE TYPE ControlTableType AS TABLE (row int, id int, match_field int)
CREATE PROCEDURE RecAssign (
@subjects SubjTableType READONLY
, @controls ControlTableType READONLY
, @sp int
, @cp int
, @subjCount int
, @ctrlCount int
) AS BEGIN
IF @sp = @subjCount BEGIN
RETURN 0
END
IF 1 = (SELECT COUNT(1) FROM #MemoTable WHERE sRow=@sp AND cRow=@cp) BEGIN
RETURN (SELECT best FROM #MemoTable WHERE sRow=@sp AND cRow=@cp)
END
DECLARE @res int, @spNext int, @cpNext int, @prelim int, @alt int, @diff int, @sId int, @cId int
SET @spNext = @sp + 1
SET @cpNext = @cp + 1
SET @sId = (SELECT id FROM @subjects WHERE row = @sp)
SET @cId = (SELECT id FROM @controls WHERE row = @cp)
EXEC @prelim = RecAssign @subjects=@subjects, @controls=@controls, @sp=@spNext, @cp=@cpNext, @subjCount=@subjCount, @ctrlCount=@ctrlCount
SET @diff = ABS((SELECT match_field FROM @subjects WHERE row=@sp)-(SELECT match_field FROM @controls WHERE row=@cp))
SET @res = @prelim + @diff
IF 1 = (SELECT COUNT(1) FROM #Assignments WHERE sRow=@sp) BEGIN
UPDATE #Assignments SET cId=@cId, sId=@sId, diff=@diff WHERE sRow=@sp
END
ELSE BEGIN
INSERT INTO #Assignments(sRow, sId, cId, diff) VALUES (@sp, @sId, @cId, @diff)
END
IF @cp+1+@subjCount-@sp < @ctrlCount BEGIN
EXEC @alt = RecAssign @subjects=@subjects, @controls=@controls, @sp=@sp, @cp=@cpNext, @subjCount=@subjCount, @ctrlCount=@ctrlCount
IF @alt < @res BEGIN
SET @res = @alt
END
ELSE BEGIN
UPDATE #Assignments SET cId=@cId, sId=@sId, diff=@diff WHERE sRow=@sp
END
END
INSERT INTO #MemoTable (sRow, cRow, best) VALUES (@sp, @cp, @res)
RETURN @res
END
Run Code Online (Sandbox Code Playgroud)
以下是调用此存储过程的方法:
-- The procedure uses a temporary table for memoization:
CREATE TABLE #MemoTable (sRow int, cRow int, best int)
-- The procedure returns a table with assignments:
CREATE TABLE #Assignments (sRow int, sId int, cId int, diff int)
DECLARE @subj as SubjTableType
INSERT INTO @SUBJ (row, id, match_field) SELECT ROW_NUMBER() OVER(ORDER BY match_field ASC)-1 AS row, id, match_field FROM subjects
DECLARE @ctrl as ControlTableType
INSERT INTO @ctrl (row, id, match_field) SELECT ROW_NUMBER() OVER(ORDER BY match_field ASC)-1 AS row, id, match_field FROM controls
DECLARE @subjCount int
SET @subjCount = (SELECT COUNT(1) FROM subjects)
DECLARE @ctrlCount int
SET @ctrlCount = (SELECT COUNT(1) FROM controls)
DECLARE @best int
EXEC @best = RecAssign
@subjects=@subj
, @controls=@ctrl
, @sp=0
, @cp=0
, @subjCount=@subjCount
, @ctrlCount=@ctrlCount
SELECT @best
SELECT sId, cId, diff FROM #Assignments
Run Code Online (Sandbox Code Playgroud)
上面的呼叫假定两个subjects和controls通过位置已被滤除,以及N拷贝subjects已被插入到在进行呼叫之前的表值参数(或DB2的情况下,临时表).