|
我在用SQL开发匹配算法时遇到了麻烦。我有一张桌子subjects。这些中的每一个都需要与表中的相同行数匹配controls(出于这个问题的原因,我们需要为每个主题选择两行或控件)。所选控件的位置必须完全匹配,并且所选控件的值应match_field尽可能接近主题。; C- M) s+ J4 r7 N$ ?
以下是一些示例数据:
6 l0 h X' x7 e表主题:( K/ d+ X9 i& o/ A: i4 l5 x
id location match_field
7 D! s4 w8 W. d4 D' s1 1 190
8 ^: T9 x$ o/ f2 R' {2 2 2000
+ ~0 ?- b4 A- ]2 n6 A0 |3 1 1006 Q& A2 q% t! _; z, s$ T' U
表控件:$ b5 L0 a& h. ]* x3 @9 O- d
id location match_field
) v& V* d0 i+ b: B/ D5 y" S: g, P17 1 70' w- u4 O4 J/ v! }
11 1 180
3 i' A, D" V! X; _+ N12 1 2205 ^$ |# \) T2 u
13 1 2401 G- M% I* a% S- {9 v4 T7 B" x
14 1 500
; p4 k1 s# e) z% x9 y0 }' T" f15 1 600( Y, q7 y/ a2 Q" P6 ]9 H e+ e4 p) M
16 1 600 }, A6 ?5 @* e7 [
10 2 308 b- A5 Z) @ r: }3 }7 M+ D( W2 C
78 2 1840
! o) c: [' N9 J2 ]) b0 ?: Q+ w# A79 2 2250) E) H& v0 S% `; }4 g9 A1 K" ?
这是来自样本数据的最佳结果:
4 S! s; o/ Y6 f% _! y* O0 Bsubject_id control_id location match_field_diff1 F- |. y' K( x( w
1 12 1 306 q8 i. O1 ^4 d
1 13 1 50, l/ B4 ]# X8 E+ m6 S( b
2 78 2 160
6 L* r) m$ O2 O% q5 X5 E2 79 2 250" O1 B: m6 n) ^& y6 U% |
3 17 1 30% o, H8 J* q, S" [+ ~1 p
3 11 1 80
+ g. ^! R5 M' N' f. h# @, S. m这很棘手,因为例如控件11与主体1最接近。但是,在最佳解中,控件11与主体3匹配。1 p5 Z" c2 b1 C1 T3 e2 R2 @2 e
我相信匈牙利算法已接近解决此问题的“正确”解决方案。但是,没有相同数量的主题和控件,也不会使用所有控件(我有几千个主题和几百万个潜在控件)。
7 _6 ~# G7 U2 F没有必要获得绝对的最佳结果。一个很好的近似值对我来说很好。
7 j) O/ d$ \" `5 p' N: W" q似乎应该有一个很好的基于集合的解决方案来解决这个问题,但是我想不起来该怎么做。以下是一些代码,仅根据位置将相同数量的控件分配给每个主题:& t" P! ~/ b2 U% p
select * from (! j. R6 f$ h x: ]' M
select subject.id, 3 {) O+ g3 }0 D$ V t6 K# ]
control.id,5 m+ P2 {+ W% x1 z: I1 J
subject.location,5 C4 v* s' f( u+ `
row_number() over (
) b# v6 Q; U5 N8 |. { partition by subject.location
4 `$ o2 k8 H" S6 k h order by subject.id, control.id
. b' |) c4 t* K1 T0 }. R ) as rn,# M y3 B# D3 H( L* }
count(distinct control.id) over (- V( ]2 B# g* ^' m" X8 J) c
partition by subject.location
; Z5 p6 ~% E! w2 f, W, q) ] ) as controls_in_loc1 g# l6 s1 C) e- R$ ~) J
from subjects9 K6 d6 s& g* t+ N }
join controls on control.location = subject.location" N% |/ J; F* r5 ~
)6 |; H, |2 [9 V3 K
where mod(rn,controls_in_loc+1) = 1- D, c+ y5 {- v
但是,我不知道如何添加模糊匹配组件。我正在使用DB2,但是如果您使用其他方法,则可以将算法转换为DB2。
+ O3 Z0 D3 g0 k3 p" h: W在此先感谢您的帮助!3 T- o1 A, j4 v J# n0 {+ a
更新:
% p" w* I, F4 p/ D0 t8 N我最确信SQL不是适合此工作的工具。但是,为了确定(并且因为这是一个有趣的问题),我提供了赏金,以查看是否可以使用有效的SQL解决方案。它必须是基于集合的解决方案。它可以使用迭代(多次遍历同一查询以获得结果),但是迭代次数必须远远小于大型表的行数。它不应在表中的每个元素上循环或使用游标。
) n1 a& ^3 C* P# W7 t ]
; k4 R7 w6 S" P5 A% x, D8 M解决方案:
; _# K' a. G J) D% W
; I3 W$ p2 x; `$ `! X f
) q! e. W/ a. ]4 _! i1 v
, Q9 I. F/ R F+ I, H5 G/ c 尽管匈牙利算法将起作用,但在您的情况下,可以使用更简单的算法。您的隐式成本矩阵是一种特殊形式的对称矩阵:
3 V6 E5 L% `+ @9 s3 VABS(SUBJ.match_field-CTRL.match_field)' J8 Z1 J8 U% d- ^, S# W3 y& A
因此,您可以相对容易地证明,在最佳分配 _{SUBJ i,CTRL j0 X, O' @# s1 ^4 w: E1 s
}中,_按SUBJ.match_field的值CTRL.match_field排序也将是有序的。
# G: C! {7 g! f' ~证明: 考虑一个由 { }_排序的赋值 {SUBJ_ i ,CTRL j
g$ Y l _) k, \& `2 k' t4 Q4 ^}SUBJ.match_field而不是CTRL.match_field。然后,您至少会有一个 反转 ,即一对赋值 {SUBJ7 H% \1 P6 y1 a; `/ Z
i1,CTRL j1 }_和 _这样, U9 o% d8 Q) t0 E1 Q) J3 J
SUBJ.match_fieldi1 7 a3 k6 m, _2 O
CTRL.match_fieldj1 > CTRL.match_fieldj2$ |1 R$ _0 ]3 q& P s! F5 d; l+ p
然后,您可以将反相对替换为非反相对
6 N8 l7 c% {; J" R) p- Q$ h; ^0 J3 E) |{SUBJ i1,CTRL j2 }_和 _
, x/ A* D' i" {% o; \0 j小于或等于SUBJ.match_field(i1,i2)和CTRL.match_field(j1,j2)的所有六个相对位置的反向分配的成本(链接到Wolfram+ s& k0 T6 G; I
Alpha)。$ O) _0 W- V$ \4 q" h q/ j9 f9 X, a
:证明
. `! j- r' Y5 d2 `) u# w6 Q) t- W2 k! b有了这个观察,就很容易证明以下 动态编程
5 _4 g1 k" X3 j% V5 E4 K, e算法具有最佳分配:
. `" j) b" o6 o) w2 a/ a5 u' ]让N每个主题的复印件; 排序match_field% T& l; ~- W5 b- P' v5 n0 i
订单控制依据 match_field; z F6 d0 ^" f9 [1 Q
准备一个assignments大小为空的数组N * subject.SIZE
$ q- R- G6 c. B v3 B准备一个空的2D阵列mem尺寸的N * subject.SIZE由control.SIZE用于 记忆化 ; 将所有元素设置为-15 |. F& i/ u- `+ t2 r9 K
Recursive_Assign在下面的伪代码中定义的调用4 q; L- u2 ?2 p1 ^+ R, O( P
该assignments表现在包含N每个主题i在N*i包括(含)和N*(i+1)排除(排除)之间的作业。7 X* K1 o7 t5 Z6 l% ]" d; S
: I' P* Y, q9 o4 Z: D8 v
FUNCTION Recursive_Assign
% X5 n0 l9 F1 B- y // subjects contains each original subj repeated N times
L9 C b) [, d$ k# M1 C' @ PARAM subjects : array of int[subjectLength]
; t* t& X$ E( a; t PARAM controls: array of int[controlLength]
* i w ] \. B2 O4 e; _- D3 V7 j PARAM mem : array of int[subjectLength,controlLength]
6 ]( |: z4 r0 K5 R3 ^ PARAM sp : int // current subject position, M' E# U% y7 e# W3 C$ Y% _. i
PARAM cp : int // current control position
) g& \& d8 F- d/ i. f# m% l; Y1 t PARAM assign : array of int[subjectLength]+ `$ X% {4 O8 o1 v: K
BEGIN
4 U1 a5 [ T0 X: I' b4 l3 X6 a IF sp == subjects.Length THEN RETURN 0 ENDIF
* {- O! t) e! g1 P IF mem[sp, cp] > 0 THEN RETURN mem[sp, cp] ENDIF' C7 ]2 N3 d& w* U
int res = ABS(subjects[sp] - controls[cp])( a# ~9 x% n3 A% b# Q
+ Recursive_Assign(subjects, controls, mem, sp + 1, cp + 1, assign)5 ~% t$ l" o1 s$ _
assign[sp] = cp
' o# x9 R4 J2 Z3 \0 z9 C IF cp+1+subjects.Length-sp 2 m# r* r$ @% z4 l# Y- M. \, _
这是在ideone上使用C#的上述伪代码的实现。# V5 t+ r, O5 b9 b3 w. n
该算法已准备好在SQL中重新编写为基于集合的算法。尝试使其适应原始问题设置(按位置分组并制作主题的多个副本)会给已经相当复杂的过程增加不必要的复杂性,因此我将通过使用表来简化一些事情值的SQL; {% w6 ~0 E/ T( p7 s& c
Server参数。我不确定DB2是否提供类似的功能,但是如果没有,您应该能够将它们替换为临时表。6 c6 S/ A3 d& b. H
下面的存储过程几乎是将上述伪代码直接转换为SQL Server的存储过程语法:3 \7 P9 e R. _; Z4 q6 P& m/ g( P
CREATE TYPE SubjTableType AS TABLE (row int, id int, match_field int)% O$ E9 `! `! F; T
CREATE TYPE ControlTableType AS TABLE (row int, id int, match_field int)( b- v1 r. E v# e' A8 ~
CREATE PROCEDURE RecAssign (
$ i! C$ k5 Y% d0 s# G @subjects SubjTableType READONLY
3 Y0 F2 E" f. w& y! U, @controls ControlTableType READONLY7 R' Z0 J1 @9 L
, @sp int
# Z2 h6 i9 C& n5 M! B }1 N, @cp int2 n' h, D5 K) ~8 I" {6 I
, @subjCount int; }6 j; y" t) S, i, P1 }- z
, @ctrlCount int
4 B' f' U" b& Q7 s" |) AS BEGIN4 a. }; U# a: A! y8 H
IF @sp = @subjCount BEGIN
3 G8 F4 i$ H) y3 u* i RETURN 0+ n0 i, v# X: v+ _8 ^1 v* t' @
END8 A, ^! u3 r1 e! v# e, U' J6 g0 _
IF 1 = (SELECT COUNT(1) FROM #MemoTable WHERE sRow=@sp AND cRow=@cp) BEGIN( C4 a' k/ f2 \$ x$ G# b% S
RETURN (SELECT best FROM #MemoTable WHERE sRow=@sp AND cRow=@cp)
( ~ S1 A( r I2 A8 `# }& z END/ h/ Y6 `' |: Z: |& |
DECLARE @res int, @spNext int, @cpNext int, @prelim int, @alt int, @diff int, @sId int, @cId int
. Z1 ]% D: R) k3 E7 \ SET @spNext = @sp + 12 ^% P4 W' I8 N8 [3 x
SET @cpNext = @cp + 12 T( O4 o$ { L# w9 I1 N7 v
SET @sId = (SELECT id FROM @subjects WHERE row = @sp)
& j: x& ^1 D, L6 P. } SET @cId = (SELECT id FROM @controls WHERE row = @cp)
9 i* G7 a) \( B& @- @ {% L2 c2 B EXEC @prelim = RecAssign @subjects=@subjects, @controls=@controls, @sp=@spNext, @cp=@cpNext, @subjCount=@subjCount, @ctrlCount=@ctrlCount
J3 F( u+ b2 H* W! e1 ] SET @diff = ABS((SELECT match_field FROM @subjects WHERE row=@sp)-(SELECT match_field FROM @controls WHERE row=@cp))/ }3 ~. ^: i0 h, i/ t
SET @res = @prelim + @diff
3 [6 e" Y3 l% x( J& Y IF 1 = (SELECT COUNT(1) FROM #Assignments WHERE sRow=@sp) BEGIN: d0 ^: b% Q8 h+ G; m+ x
UPDATE #Assignments SET cId=@cId, sId=@sId, diff=@diff WHERE sRow=@sp/ v# ^; V4 h* S @3 e6 K) y
END
" m, g3 q" [* ?2 h7 e ELSE BEGIN
' n1 X' p3 s d8 d, j6 l INSERT INTO #Assignments(sRow, sId, cId, diff) VALUES (@sp, @sId, @cId, @diff)3 w2 g# s' H( W( j8 G+ A* f
END: i# q: A, N0 Q w5 R# H) {2 W
IF @cp+1+@subjCount-@sp 这是调用此存储过程的方式:
% ~& z6 R3 A; E, K$ ]' D) b-- The procedure uses a temporary table for memoization:
3 H+ R G1 c# |+ N1 ]) Z0 v9 J+ _CREATE TABLE #MemoTable (sRow int, cRow int, best int)
, y1 u9 n2 p& H3 \; f-- The procedure returns a table with assignments:" n9 c+ m `6 g
CREATE TABLE #Assignments (sRow int, sId int, cId int, diff int)
. C1 C y4 D, E" S3 ADECLARE @subj as SubjTableType
: w+ L$ f$ r) h: H; g% _+ ~INSERT INTO @SUBJ (row, id, match_field) SELECT ROW_NUMBER() OVER(ORDER BY match_field ASC)-1 AS row, id, match_field FROM subjects
# s7 ]6 l. V) h2 E: D/ oDECLARE @ctrl as ControlTableType3 H1 h/ ^/ [- A. x
INSERT INTO @ctrl (row, id, match_field) SELECT ROW_NUMBER() OVER(ORDER BY match_field ASC)-1 AS row, id, match_field FROM controls" q6 Y& H9 |! r# z
DECLARE @subjCount int
$ D! r. [" I# G6 j- o2 Z; QSET @subjCount = (SELECT COUNT(1) FROM subjects)
" e5 I: B Q# u4 e' gDECLARE @ctrlCount int
, h7 k, S3 t4 N+ T5 GSET @ctrlCount = (SELECT COUNT(1) FROM controls)
) _3 `0 c- S( h( ODECLARE @best int
2 I5 [0 n3 r' K# Q. E' @9 YEXEC @best = RecAssign" P" E4 K. L2 h M8 N5 m' ^0 h
@subjects=@subj& Z% c7 W0 o Q2 B6 |
, @controls=@ctrl6 r/ T( `* j5 _
, @sp=0
/ {3 E4 G; u [0 U3 [, @cp=0
0 z# ^7 j) c# y* N" y" i, X, @subjCount=@subjCount
0 E4 e* F( a9 ]/ n W, @ctrlCount=@ctrlCount6 \* n! ?8 `+ `8 h
SELECT @best) C' H' R$ i- H" `
SELECT sId, cId, diff FROM #Assignments. u7 E6 D# n1 L6 k( o+ y9 W
上面的呼叫假定两个subjects和controls已经由位置滤除,以及N拷贝subjects已被插入到在进行呼叫之前的表值参数(或DB2的情况下,临时表)。
3 H- n \' S# F这是在sqlfiddle上运行的演示。 |
|