我认为没有光标是不可能的。
即使可以在没有游标的情况下执行此操作,也肯定需要自连接,可能不止一次。因此,性能可能很差,可能比直接游标更差。而且很可能很难理解逻辑并在以后维护此代码。有时光标很有用。
主要难点是这部分问题:
选择值后,该值不能重复使用。
几天前有一个similar question。
逻辑很简单。游标遍历表A 的所有行,并且每次迭代都会向临时目标表添加一行。要确定要添加的value,我使用EXCEPT 运算符,该运算符从表B 中获取所有values,并从它们中删除之前使用过的所有values。我的解决方案假定表B 中的value 中没有重复项。 EXCEPT 运算符删除重复项。如果表B中的values不是唯一的,那么临时表将保持唯一的indexB而不是valueB,但主要逻辑保持不变。
这里是SQL Fiddle。
样本数据
DECLARE @TA TABLE (idx int, value int);
INSERT INTO @TA (idx, value) VALUES
(1, 123),
(2, 245),
(3, 342),
(4, 456),
(5, 608);
DECLARE @TB TABLE (idx int, value int);
INSERT INTO @TB (idx, value) VALUES
(1, 152),
(2, 159),
(3, 259);
主查询将结果插入临时表@TDst。可以不使用显式变量@CurrValueB 来编写INSERT,但使用变量看起来更简洁。
DECLARE @TDst TABLE (idx int, valueA int, valueB int);
DECLARE @CurrIdx int;
DECLARE @CurrValueA int;
DECLARE @CurrValueB int;
DECLARE @iFS int;
DECLARE @VarCursor CURSOR;
SET @VarCursor = CURSOR FAST_FORWARD
FOR
SELECT idx, value
FROM @TA
ORDER BY idx;
OPEN @VarCursor;
FETCH NEXT FROM @VarCursor INTO @CurrIdx, @CurrValueA;
SET @iFS = @@FETCH_STATUS;
WHILE @iFS = 0
BEGIN
SET @CurrValueB =
(
SELECT TOP(1) Diff.valueB
FROM
(
SELECT B.value AS valueB
FROM @TB AS B
EXCEPT -- remove values that have been selected before
SELECT Dst.valueB
FROM @TDst AS Dst
) AS Diff
ORDER BY ABS(Diff.valueB - @CurrValueA)
);
INSERT INTO @TDst (idx, valueA, valueB)
VALUES (@CurrIdx, @CurrValueA, @CurrValueB);
FETCH NEXT FROM @VarCursor INTO @CurrIdx, @CurrValueA;
SET @iFS = @@FETCH_STATUS;
END;
CLOSE @VarCursor;
DEALLOCATE @VarCursor;
SELECT * FROM @TDst ORDER BY idx;
结果
idx valueA valueB
1 123 152
2 245 259
3 342 159
4 456 NULL
5 608 NULL
拥有以下索引会有所帮助:
TableA - (idx) include (value),因为我们SELECT idx, value ORDER BY idx;
TableB - (value) unique,临时目标表 - (valueB) unique filtered NOT NULL,帮助EXCEPT。因此,最好为结果(或永久表)使用临时#table 而不是表变量,因为表变量不能有索引。
另一种可能的方法是从表B(从原始或副本)中删除一行,因为它的值被插入到结果中。在这种方法中,我们可以避免一次又一次地执行EXCEPT,并且总体上可以更快,特别是如果最后将表B留空是可以的。不过,我不知道如何避免游标和按顺序处理单个行。
SQL Fiddle
DECLARE @TDst TABLE (idx int, valueA int, valueB int);
DECLARE @CurrIdx int;
DECLARE @CurrValueA int;
DECLARE @iFS int;
DECLARE @VarCursor CURSOR;
SET @VarCursor = CURSOR FAST_FORWARD
FOR
SELECT idx, value
FROM @TA
ORDER BY idx;
OPEN @VarCursor;
FETCH NEXT FROM @VarCursor INTO @CurrIdx, @CurrValueA;
SET @iFS = @@FETCH_STATUS;
WHILE @iFS = 0
BEGIN
WITH
CTE
AS
(
SELECT TOP(1) B.idx, B.value
FROM @TB AS B
ORDER BY ABS(B.value - @CurrValueA)
)
DELETE FROM CTE
OUTPUT @CurrIdx, @CurrValueA, deleted.value INTO @TDst;
FETCH NEXT FROM @VarCursor INTO @CurrIdx, @CurrValueA;
SET @iFS = @@FETCH_STATUS;
END;
CLOSE @VarCursor;
DEALLOCATE @VarCursor;
SELECT
A.idx
,A.value AS valueA
,Dst.valueB
FROM
@TA AS A
LEFT JOIN @TDst AS Dst ON Dst.idx = A.idx
ORDER BY idx;