回答

收藏

SQL中的组合优化匹配

技术问答 技术问答 349 人阅读 | 0 人回复 | 2023-09-14

我在用SQL开发匹配算法时遇到了麻烦。我有一张桌子subjects。这些中的每一个都需要与表中的相同行数匹配controls(出于这个问题的原因,我们需要为每个主题选择两行或控件)。所选控件的位置必须完全匹配,并且所选控件的值应match_field尽可能接近主题。6 i3 W' [$ a! V8 r
以下是一些示例数据:5 {, j% d4 ~/ I5 U* ^6 A
表主题:; `: S- t( q( M9 J* G
id   location    match_field3 {/ J/ M; Y- U# B! E
1    1           190, ^1 _" G' K9 U. Z
2    2           2000
  ^8 H! E% |2 S3 F( ?- d3 q3    1           100" p# k2 ^0 e8 }# h
表控件:, A5 G( Z2 v$ R! t& C5 \
id   location    match_field
6 s+ M8 B. ~3 z; a2 t5 q; N17    1          70
8 U% N/ g" a' L. V0 u$ E6 ]3 o11    1          180
8 W- F) F$ K4 M1 w# c2 v5 s12    1          220& ?' m- O6 v9 \) r
13    1          240
; O3 A  ]7 ?9 {! m6 r- }14    1          500
- A* A  j4 m8 D+ j. L15    1          600. q9 t$ }) A1 @6 O( U
16    1          600
( v, i8 B$ J5 ~' h. ]10    2          30
5 f8 }% w7 S( R  t78    2          1840
, F6 T- g$ [# Q3 O$ Z$ W79    2          2250  @& f& L5 T# k1 t; \. g
这是来自样本数据的最佳结果:
; k+ Y% s. E1 V- s: n! Z8 `subject_id control_id  location    match_field_diff
2 x* J5 j/ [; C8 P; @  y1          12          1           30
2 U1 _4 s# p7 |4 X1          13          1           50( i1 x* l' o8 |/ b
2          78          2           160& G5 Z4 o0 M& @
2          79          2           250
5 ]+ V, {4 c8 ?6 k" t1 n3          17          1           30) l3 z& O9 Z& i7 T6 a- \4 f
3          11          1           80
; m3 I. K) L4 \0 i- b" r: s这很棘手,因为例如控件11与主体1最接近。但是,在最佳解中,控件11与主体3匹配。
2 r, M3 L4 p- ]我相信匈牙利算法已接近解决此问题的“正确”解决方案。但是,没有相同数量的主题和控件,也不会使用所有控件(我有几千个主题和几百万个潜在控件)。
- @& \" q5 D! Y' o没有必要获得绝对的最佳结果。一个很好的近似值对我来说很好。; a3 s1 n" |) ^* x( J6 B
似乎应该有一个很好的基于集合的解决方案来解决这个问题,但是我想不起来该怎么做。以下是一些代码,仅根据位置将相同数量的控件分配给每个主题:" K3 O- S* H7 k* ~" Z- W
select * from (1 Y; i+ Q$ N# u2 I! r1 P! Z2 Y
    select   subject.id,
2 R9 s3 ^, ~* q             control.id,
- u# R; d1 B# x0 p( J7 K. g" b) u3 b             subject.location,: z; d. o$ f  F, n& O0 l
             row_number() over (. [: x' B1 w1 b. ~/ b, I
                 partition by subject.location( Z7 K4 F- u1 s' o. H
                 order by subject.id, control.id, U/ Y) D" V1 j( h
             ) as rn,* i/ q( H' Q/ H5 I2 }' i% d! j
             count(distinct control.id)     over ($ j/ e! w2 f% _1 {- x2 [. s
                 partition by subject.location$ a* l( Q0 U+ P7 f: n* J
             ) as controls_in_loc& P1 T% a' |8 V
         from subjects
% B" s0 E  W6 L9 O2 h         join controls on control.location = subject.location
4 {5 Z- I- r# O: i: h9 l    )
" B( \8 G5 T$ U3 ?0 X# @4 O    where mod(rn,controls_in_loc+1) = 15 V9 X* ~; C7 U# L. W
但是,我不知道如何添加模糊匹配组件。我正在使用DB2,但是如果您使用其他方法,则可以将算法转换为DB2。
% n7 Q% P# g0 i& H- \在此先感谢您的帮助!/ d0 }' _, L' K- N# q  r
更新:9 q- t! |6 _" u; \( L( W
我最确信SQL不是适合此工作的工具。但是,为了确定(并且因为这是一个有趣的问题),我提供了赏金,以查看是否可以使用有效的SQL解决方案。它必须是基于集合的解决方案。它可以使用迭代(多次遍历同一查询以获得结果),但是迭代次数必须远远小于大型表的行数。它不应在表中的每个元素上循环或使用游标。
) m9 z3 C6 M9 r2 i; k9 W- m                % o0 t; [' M$ v* H0 x" l& I
解决方案:: A+ s# N  X* f* c
                2 L9 ~. a$ W& ~
. g5 f$ b# X  M3 g6 }" G

. e! o' c. G  Y' }9 z                尽管匈牙利算法将起作用,但在您的情况下,可以使用更简单的算法。您的隐式成本矩阵是一种特殊形式的对称矩阵:) `9 G# t; L: \- @  A9 j
ABS(SUBJ.match_field-CTRL.match_field)
7 }* ^0 {% z( `( e; |* X因此,您可以相对容易地证明,在最佳分配 _{SUBJ i,CTRL j! F. c  G  R, `/ {' P; o
}中,_按SUBJ.match_field的值CTRL.match_field排序也将是有序的。" }; Z& o$ R5 `5 e
证明: 考虑一个由 { }_排序的赋值 {SUBJ_ i ,CTRL j6 e- R3 T9 S0 j& ]0 ?
}SUBJ.match_field而不是CTRL.match_field。然后,您至少会有一个 反转 ,即一对赋值 {SUBJ5 H7 l6 u/ P; C, j' P
i1,CTRL j1 }_和 _这样,2 Z# A( L* q" Q& w6 y8 U
SUBJ.match_fieldi1
0 F+ R3 k# L1 g# f+ H7 T1 JCTRL.match_fieldj1 > CTRL.match_fieldj2
. U$ l+ V% b8 L2 v, Z( Q然后,您可以将反相对替换为非反相对
' c# b# w& b4 O& t$ f9 J. x{SUBJ i1,CTRL j2 }_和 _- ~& P/ Z" I+ b' }
小于或等于SUBJ.match_field(i1,i2)和CTRL.match_field(j1,j2)的所有六个相对位置的反向分配的成本(链接到Wolfram
2 V9 v) S% e8 {0 n3 hAlpha)。" Z, j0 w, b% C" Y+ L) \  r' N: l
:证明
6 t5 w; J& \% A1 ]' y有了这个观察,就很容易证明以下 动态编程: S3 C6 \: M2 m, d( D
算法具有最佳分配:
" }' V: O& N3 O1 c. U3 _/ e2 u让N每个主题的复印件; 排序match_field2 o& R$ W7 o0 }. U  `9 v
订单控制依据 match_field
) K9 o3 ^. V0 f准备一个assignments大小为空的数组N * subject.SIZE
% S2 V* ^; d2 [' x& l$ K" A准备一个空的2D阵列mem尺寸的N * subject.SIZE由control.SIZE用于 记忆化 ; 将所有元素设置为-1  v' n0 {6 N8 X+ U2 P7 O
Recursive_Assign在下面的伪代码中定义的调用
0 G3 J6 j+ S' I5 e7 R- j+ F该assignments表现在包含N每个主题i在N*i包括(含)和N*(i+1)排除(排除)之间的作业。9 S0 q! d2 f" x

. t9 Z$ I* S4 t/ m% iFUNCTION Recursive_Assign& L* _, H7 n5 ~( C) ^, z
    // subjects contains each original subj repeated N times& ^, t8 `4 \+ ]& b' n7 I
    PARAM subjects : array of int[subjectLength]
8 L7 o' N4 j' J    PARAM controls: array of int[controlLength]% z; G! t. @3 d4 e" N" R
    PARAM mem : array of int[subjectLength,controlLength]
: C' G2 N8 g6 |# |) b    PARAM sp : int // current subject position& G& G  |+ z" R4 B$ }, [
    PARAM cp : int // current control position
( \* Q+ P" V9 x2 |; z) ?$ W    PARAM assign : array of int[subjectLength]
" s1 H# h, ]5 }BEGIN1 a; J$ [* `" e: r- b( V% a1 W0 n
    IF sp == subjects.Length THEN RETURN 0 ENDIF9 D! ~  v' r" M& {  u
    IF mem[sp, cp] > 0 THEN RETURN mem[sp, cp] ENDIF
6 B8 k) m9 Q* b9 T    int res = ABS(subjects[sp] - controls[cp])
) Q& r( D  L. \! {            + Recursive_Assign(subjects, controls, mem, sp + 1, cp + 1, assign)( G& `; c& R7 N. e3 d& A6 [
    assign[sp] = cp9 f# h# E$ \8 _$ W: t) [& R& @' `
    IF cp+1+subjects.Length-sp 4 r- S& a, B0 w
这是在ideone上使用C#的上述伪代码的实现。! v7 m2 G+ u- o( w2 f, ^. {" h9 |
该算法已准备好在SQL中重新编写为基于集合的算法。尝试使其适应原始问题设置(按位置分组并制作主题的多个副本)会给已经相当复杂的过程增加不必要的复杂性,因此我将通过使用表来简化一些事情值的SQL
- A0 j8 a0 z% pServer参数。我不确定DB2是否提供类似的功能,但是如果没有,您应该能够将它们替换为临时表。* C1 Y( {) j; g& M6 X$ k/ S
下面的存储过程几乎是将上述伪代码直接转换为SQL Server的存储过程语法:! `0 I* I8 U7 _! ~
CREATE TYPE SubjTableType AS TABLE (row int, id int, match_field int)
7 ]% S& I' I3 f" ^; k% M$ qCREATE TYPE ControlTableType AS TABLE (row int, id int, match_field int)
5 `% h) T0 F& x) [( n1 S& LCREATE PROCEDURE RecAssign (
! R0 {9 d3 A4 p( i$ {# c    @subjects SubjTableType READONLY4 G, S% b2 E$ @5 `" b0 i
,   @controls ControlTableType READONLY2 ^, F& Z: M; O; G3 P) b' H7 i5 ~2 Q4 g
,   @sp int
9 e* K6 O# _$ R,   @cp int  g. D( v4 t. D3 s
,   @subjCount int" t8 D9 }* G; ?; z5 _) G& R- N1 @
,   @ctrlCount int
! _  h& W0 u) R0 f; ?9 s0 e) AS BEGIN
, b3 B0 m& w9 B2 b    IF @sp = @subjCount BEGIN) r+ Q1 a& e, b+ s4 @9 q/ i7 e& _  H
        RETURN 00 Q# @/ c: \1 d0 k5 d
    END) _1 [1 q6 w1 c6 U! \5 G9 I: c3 C. @
    IF 1 = (SELECT COUNT(1) FROM #MemoTable WHERE sRow=@sp AND cRow=@cp) BEGIN3 b& e  W* c" L; b% Q: l" _
        RETURN (SELECT best FROM #MemoTable WHERE sRow=@sp AND cRow=@cp)& P: Z, C$ Y  E* }& a
    END/ Q0 ?1 w) k9 F8 N& q8 H
    DECLARE @res int, @spNext int, @cpNext int, @prelim int, @alt int, @diff int, @sId int, @cId int
% \8 X* i( E' @! ?0 K1 O; o# s    SET @spNext = @sp + 12 P4 v" Z1 @: }$ T% c. W
    SET @cpNext = @cp + 11 e$ |, e* p1 m& W9 j
    SET @sId = (SELECT id FROM @subjects WHERE row = @sp)
# }6 t% F! D7 D  e) F7 K0 u) Q    SET @cId = (SELECT id FROM @controls WHERE row = @cp)
9 z! L/ K4 v9 O( w3 S    EXEC @prelim = RecAssign @subjects=@subjects, @controls=@controls, @sp=@spNext, @cp=@cpNext, @subjCount=@subjCount, @ctrlCount=@ctrlCount
! O! f- z& |. ?$ V4 W    SET @diff = ABS((SELECT match_field FROM @subjects WHERE row=@sp)-(SELECT match_field FROM @controls WHERE row=@cp))
1 w- Q5 ^. @3 \) z, n( l    SET @res = @prelim + @diff! u8 a2 F+ J- v7 f$ i& O2 i1 Z
    IF 1 = (SELECT COUNT(1) FROM #Assignments WHERE sRow=@sp) BEGIN
# p# K( r4 n& l1 ?  k        UPDATE #Assignments SET cId=@cId, sId=@sId, diff=@diff WHERE sRow=@sp
! E( w9 b9 g1 [( n7 T    END* d' _% x6 i7 Y7 {5 m
    ELSE BEGIN
- U; k1 a& b3 R7 n        INSERT INTO #Assignments(sRow, sId, cId, diff) VALUES (@sp, @sId, @cId, @diff). m) ^% Z" t6 ~1 t
    END
: D( l1 ?7 c4 \4 u    IF @cp+1+@subjCount-@sp 这是调用此存储过程的方式:- G2 w8 F, e  b& T; a, b" A$ Y- q* n! f
-- The procedure uses a temporary table for memoization:# _7 Y, K/ P) H& H3 j* s
CREATE TABLE #MemoTable (sRow int, cRow int, best int)$ z, d! H) _5 v5 Q
-- The procedure returns a table with assignments:
. D) `: B8 Q3 E7 p  q7 }" dCREATE TABLE #Assignments (sRow int, sId int, cId int, diff int)5 V7 W6 R9 N9 @9 _; E
DECLARE @subj as SubjTableType. ]4 S6 s9 b% B3 c5 ]3 M& M
INSERT INTO @SUBJ (row, id, match_field) SELECT ROW_NUMBER() OVER(ORDER BY match_field ASC)-1 AS row, id, match_field FROM subjects: D! M' w" \) }9 b( J& c. d
DECLARE @ctrl as ControlTableType- \7 l8 o0 Q1 T5 L5 k( {  u
INSERT INTO @ctrl (row, id, match_field) SELECT ROW_NUMBER() OVER(ORDER BY match_field ASC)-1 AS row, id, match_field FROM controls
) ~) F; m7 G* s7 g1 H4 N7 fDECLARE @subjCount int
( }3 b- F; T) K" l1 \( A5 _SET @subjCount = (SELECT COUNT(1) FROM subjects)0 H; Q4 P$ V! \
DECLARE @ctrlCount int
  i. y+ v: s2 p% p; ]SET @ctrlCount = (SELECT COUNT(1) FROM controls)# d& K. Z# }3 A/ L9 W' \6 N
DECLARE @best int
) ^7 n- t) o. J5 f  K9 gEXEC @best = RecAssign
* v$ _. x- Y" o# Z' B! c: r    @subjects=@subj  e; P2 u8 H% M( g8 k( m
,   @controls=@ctrl: Z4 d* m& V' l: s. b
,   @sp=0: e% N6 k/ }( y5 ?+ Z' A/ e
,   @cp=0
/ a' h  S" p6 I,   @subjCount=@subjCount: Z5 f* F! `4 `2 W  I5 V5 z
,   @ctrlCount=@ctrlCount
7 I+ A8 D( U$ V3 s  a9 u: Q0 RSELECT @best
" Q. c; t: v- s1 e1 v$ mSELECT sId, cId, diff FROM #Assignments
$ D/ `3 K' v2 t) y5 S& ~$ a% L上面的呼叫假定两个subjects和controls已经由位置滤除,以及N拷贝subjects已被插入到在进行呼叫之前的表值参数(或DB2的情况下,临时表)。
$ d9 i1 `* l) f! ~这是在sqlfiddle上运行的演示。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则