SQL中的组合优化匹配

SQL中的组合优化匹配,sql,algorithm,db2,combinatorics,Sql,Algorithm,Db2,Combinatorics,我在SQL中开发匹配算法时遇到问题。我有一个表主题。其中每一个都需要与表控件中相同数量的行相匹配(为了解决这个问题,我们假设需要为每个主题选择两行或两个控件)。所选控件的位置必须完全匹配,并且所选控件在match_字段中的值应尽可能接近受试者 以下是一些示例数据: subject_id control_id location match_field_diff 1 12 1 30 1 13 1

我在SQL中开发匹配算法时遇到问题。我有一个表
主题
。其中每一个都需要与表
控件中相同数量的行相匹配(为了解决这个问题,我们假设需要为每个主题选择两行或两个控件)。所选控件的位置必须完全匹配,并且所选控件在
match_字段
中的值应尽可能接近受试者

以下是一些示例数据:

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
表格主题:

id   location    match_field
1    1           190
2    2           2000
3    1           100
表控件:

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
以下是样本数据的最佳结果:

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
它变得很棘手,因为,例如,控件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
但是,我不知道如何添加模糊匹配组件。我使用的是DB2,但如果您使用的是其他东西,则可以将算法转换为DB2

提前感谢您的帮助


更新:我确信SQL不是适合这项工作的工具。然而,为了确定(因为这是一个有趣的问题),我提供了一个悬赏,看看是否有一个可行的SQL解决方案。它需要是一个基于集合的解决方案。它可以使用迭代(多次循环同一查询以获得结果),但迭代次数需要远远小于大型表的行数。它不应该在表中的每个元素上循环,也不应该使用游标。

我建议您看看的问题和算法。其思想是构建一个图,其中左侧的节点是
主题
,右侧的节点是
控件
(这就是为什么称为二部)。构建图形非常简单,您可以创建一个连接到所有主题的
节点,并将所有控制节点连接到一个
接收器
节点。然后在
主题
控件
节点之间创建一条边(如果适用)。然后运行最大匹配算法,该算法将为您提供所需的内容,即主题和控件的最大可能匹配


确保检查如何操作,您只需构建图形并调用BGL函数
edmonds_maximum_cardinality_matching

尽管匈牙利算法将起作用,但在您的案例中可以使用更简单的算法。隐式成本矩阵是一种特殊形式的对称矩阵:

ABS(SUBJ.match_field-CTRL.match_field)
因此,您可以相对容易地证明,在按
subc.match\u字段
排序的优化赋值{subci,CTRLj}中,
CTRL.match\u字段
的值也将被排序

<强>证明:考虑一个赋值{Visti,Ctrl j},该命令由<代码> Suj.MatCHYField排序,该命令不是由<代码> Ctrl . MatCHiField排序的。然后至少有一个反转,即一对赋值{subci1,CTRLj1}和{subci2,CTRLj2},这样

sub.match\u字段
i1
sub.match\u字段i2,但是

CTRL.match\u字段
j1>
CTRL.match\u字段
j2

然后,您可以用一个非反转对替换反转对

{subci1,CTRLj2}和{subci2,CTRLj1}

成本小于或等于
sub.match_字段
(i1,i2)和
CTRL.match_字段
(j1,j2)()的所有六个相对位置的反向赋值成本:证明

有了这一观察结果,很容易证明下面的算法得出了最优分配:

  • 制作每个主题的
    N
    副本;订单依据
    匹配\u字段
  • 匹配字段排序控制
  • 准备一个大小为
    N*subject.size的空数组
    assignments
  • 准备一个空的2D数组
    mem
    ,大小为
    N*受试者.size
    by
    control.size
    for;将所有元素设置为
    -1
  • 调用下面的伪代码中定义的递归赋值
  • assignments
    表现在包含每个受试者
    i
    N
    作业,其位置介于
    N*i
    N*(i+1)
    之间

上面的调用假设
主题
控件
都已按位置过滤,并且在调用之前,
N
主题
的副本已插入到表值参数中(对于DB2,则插入temp表)


以下是。

为什么在最佳解决方案中,控制11(180)与受试者2(2000)而不是受试者1(190)相匹配?@phalum,哎呀,这是一个输入错误。更正,谢谢。@dan1111。如果有一个简单的基于集合的解决方案,我会感到惊讶。SQL是非常确定的。很难想象如何告诉一个控件分配给一个不是最近的主题。不过,这个问题很有趣。你能简单描述一下这个应用于现实世界的问题吗?@GordonLinoff,这是一项科学研究,从数据库中创建一个匹配的对照组。我向你保证,在使用有限数量子查询的单个
SELECT
查询中,没有办法做到这一点!最简单的方法是寻找未加权的最大匹配i
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
-- 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