【问题标题】:Combinatorial Optimization Matching in SQLSQL中的组合优化匹配
【发布时间】:2012-12-13 10:48:22
【问题描述】:

我在用 SQL 开发匹配算法时遇到问题。我有一张桌子subjects。这些中的每一个都需要与表controls 中相同数量的行匹配(为了这个问题,假设需要为每个主题选择两行或控件)。所选控件的位置必须完全匹配,并且所选控件的match_field 中的值应尽可能接近主题。

这是一些示例数据:

表格主题:

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 匹配。

我相信Hungarian Algorithm 接近这个问题的“正确”解决方案。但是,受试者和对照的数量并不相同,也不会使用所有对照(我有几千名受试者和几百万个潜在对照)。

没有必要获得绝对最优的结果;一个很好的近似值对我来说很好。

似乎应该有一个很好的基于集合的解决方案来解决这个问题,但我想不出该怎么做。下面是一些仅根据位置为每个主题分配相同数量的控件的代码:

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 解决方案。它需要是一个基于集合的解决方案。它可以使用迭代(在同一个查询上多次循环以获得结果),但迭代次数需要远小于大表的行数。它不应遍历表中的每个元素或使用游标。

【问题讨论】:

  • 在最优解中,为什么要将对照 11 (180) 匹配到主题 2 (2000) 而不是主题 1 (190)?
  • @Pharaoh,哎呀,这是一个错字。已更正,谢谢。
  • @dan1111 。 . .如果有一个简单的基于集合的解决方案,我会感到惊讶。 SQL 是相当确定性的。很难想象如何将其中一个控件分配给不是最接近的主题的主题。不过,有趣的问题。你能简要描述一下这个应用在现实世界中的问题吗?
  • @GordonLinoff,用于科学研究,从数据库中创建匹配的对照组。
  • 承诺你没有办法在使用有限数量的子查询的单个SELECT 查询中做到这一点!最简单的可能方法是在二分图中寻找未加权的最大匹配,只要它们的match_fields 差异小于某个固定阈值,就在主题和控件之间放置一条边,并解决即使你需要 迭代.

标签: sql algorithm db2 combinatorics


【解决方案1】:

虽然匈牙利算法会起作用,但在您的情况下可以使用更简单的算法。你的隐含成本矩阵是一个特殊形式的对称矩阵:

ABS(SUBJ.match_field-CTRL.match_field)

因此,您可以相对容易地证明,在由SUBJ.match_field 排序的最优赋值 {SUBJi, CTRLj} 中的CTRL.match_field 也将被订购。

证明:考虑一个赋值{SUBJi, CTRLj}SUBJ.match_field 订购的不是由CTRL.match_field 订购的。那么你至少有一个inversion,即一对赋值{SUBJi1、CTRLj1}和{SUBJi2, CTRLj2} 这样

SUBJ.match_fieldi1SUBJ.match_fieldi2,但是

CTRL.match_fieldj1 > CTRL.match_fieldj2

然后你可以用非倒置的一对替换倒置的对

{SUBJi1, CTRLj2}{SUBJi2, CTRL j1}

对于SUBJ.match_field(i1, i2) 和 @987654336 的所有六个相对布局,成本小于或等于反向分配的成本@(j1, j2) (link to Wolfram Alpha)。 :证明

有了这个观察,很容易证明下面的dynamic programming 算法提出了最优分配:

  • 为每个主题制作N 副本;通过match_field订购
  • 通过match_field订购控件
  • 准备一个空数组assignments,大小为N * subject.SIZE
  • 通过control.SIZEmemoization准备一个大小为N * subject.SIZE的空二维数组mem;将所有元素设置为-1
  • 调用下面伪代码中定义的Recursive_Assign
  • assignments 表现在包含 N 分配给每个主题 iN*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

Here is an implementation of the above pseudocode using C# on ideone.

此算法已准备好在 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

这是你如何调用这个存储过程:

-- 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

上面的调用假定subjectscontrols 都已按位置过滤,并且Nsubjects 副本已插入到表值参数(或DB2 的临时表中) ) 在拨打电话之前。

这里是a running demo on sqlfiddle

【讨论】:

  • 关于排序属性的精彩观察,+1。这里已经有很多工作了,但我认为如果你使用自下而上的 DP 策略会更好。应该可以使用单个SELECT 计算 DP 矩阵的每个“/”形对角线,因为每个对角线中的值都是相互独立的,并且仅取决于前 2 个对角线。因此,DP 矩阵可以建立在 max(nSubjects, nControls) INSERT ... SELECT 通道中。
  • @j_random_hacker 谢谢!我考虑过使用更类似于 DP 的实现,而不是记忆化的实现,但很快意识到尝试将 2D 数组拟合到 SQL 中会有些棘手。我的 SQL 技能有些局限,尤其是在编写 SP 方面,因为我从来没有在工作中这样做过(事实上,这个答案有我写过的最复杂的 SP)。
  • 感谢您为这个问题付出了这么多努力。你的答案绝对值得赏金。使其适应 DB2 需要一些工作,但您对问题的简化是一个关键的见解。
【解决方案2】:

推荐你看看maximum matchings in a bipartite graph的问题和算法。这个想法是建立一个图,左边的节点是你的subjects,右边的节点是controls(这就是为什么被称为二分)。构建图表很简单,您创建一个连接到所有主题的source 节点,并将所有控制节点连接到一个sink 节点。然后,如果适用,则在 subjectcontrol 节点之间创建一条边。然后,您运行最大匹配算法,该算法将为您提供您正在寻找的内容,即主题和控件的最大可能匹配。

一定要查看this Boost BGL example怎么做,你只需要构建图并调用BGL函数edmonds_maximum_cardinality_matching

【讨论】:

  • +1。如果您使用最大 cardinality(不是权重)匹配,您将需要确定某个阈值,然后在主题和控件之间创建一个边缘,只要它们的 match_fields 相差小于阈值.
  • 感谢这个有用的建议;这可能是一个很好的探索方向。不过,我真正希望的是在以更 SQL 友好的方式执行此类操作时获得一些帮助。我已经找到了其他已经匹配的程序代码。也许这对于基于集合的逻辑是不可能的,但它似乎非常喜欢一个 SQL 可以处理的问题。
  • 我知道这种感觉,但这是一个基本的算法问题,恕我直言,SQL 是错误的工具。我知道这不是您所期望的,但至少您不会浪费时间试图找出用 SQL 制定解决方案的方法。
  • 但是,您可以做的是在 Java 或 C++ 中创建一个批处理来读取您的数据、运行最大匹配算法并更新您的表。好消息是您可以重用最有效的方法来使用 BGL 进行计算,这是最先进的算法。
  • “然后,如果适用,您在主题和控制节点之间创建一条边”这是棘手的部分:您能否在 OP 问题的上下文中解释“如果适用”的事情?跨度>
猜你喜欢
  • 2021-11-30
  • 2020-06-13
  • 2012-09-28
  • 1970-01-01
  • 2010-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-22
相关资源
最近更新 更多