|
我在用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上运行的演示。 |
|