T- SQL Nested Cursor - соответствие всех возможных перестановок - PullRequest
1 голос
/ 04 февраля 2020

У меня очень специфическая проблема c - я пытаюсь сопоставить вейлы - один ко многим. Я довольно плохо знаком с курсорами, поэтому не могу решить свою собственную проблему.

Немного понимания - у меня есть 2 таблицы - одна с положительными значениями и одна с отрицательными. Я должен сопоставить сумму отрицательных значений положительному, и разница не может быть больше +/- 5. Примеры таблиц ниже:

  TABLE1
-----------
200  | abc
125  | abc



  TABLE2
----------
-50  | abc
-50  | abc
-75  | abc
-100 | abc
-125 | abc

Я хочу попытаться суммировать отрицательные значения от TABLE2 независимо от того, порядка, чтобы попытаться сопоставить положительное значение от TABLE1 Проблема в том, что нет определенного порядка c, в котором мой курсор должен перемещаться между записями, и я использую CURSOR без SCROLL вариант, как я никогда не использовал его раньше.

Как бы я go написал о коде, который бы проверял все возможные перестановки или, по крайней мере, до точки, где он получал совпадение, в этом случае записывает 1,2 и 4 из TABLE2 суммирует до записи из TABLE1 или записи 3 и 5 из TABLE2 суммируют до записи из TABLE1.

Возможно ли это?

Дополнительно - Я должен подчеркнуть, что TABLE1 будет иметь больше записей, чем я использовал в примере, и что если я сопоставлю значения 1,2 и 4 из TABLE2, я не смогу снова использовать эти значения при следующем сопоставлении l oop для следующей записи в TABLE1, в этом случае можно использовать только значение 5 из TABLE2.

Это код, который я должен иметь до сих пор, и я не думаю, что это правильный.

DECLARE @idPos as int
DECLARE @zuonrPos as varchar(18)
DECLARE @belnrPos as varchar(10)
DECLARE @dmbtrPos as decimal(15,2)

DECLARE @idNeg as int
DECLARE @zuonrNeg as varchar(18)
DECLARE @belnrNeg as varchar(10)
DECLARE @dmbtrNeg as decimal(15,2)

DECLARE @SumVal as numeric(15,2) = 0

DECLARE @outerLoop as int
DECLARE @innerLoop as int

IF OBJECT_ID('tempdb..#Report') IS NOT NULL DROP TABLE #Report
CREATE TABLE #Report (
    ZUONR varchar(18),
    BELNRPos varchar(10),
    SumPosVal decimal(15,2),
    BELNRNeg varchar(10),
    SumNegVal decimal(15,2)
)

IF OBJECT_ID('tempdb..#OpenItems') IS NOT NULL DROP TABLE #OpenItems
SELECT DISTINCT ROW_NUMBER() OVER (ORDER BY MN.ZUONR) AS ID, MN.ZUONR, MN.BELNR, MN.DMBTR, NULL as Marker INTO #OpenItems
FROM dbo.FIS_BELEG MN WITH (NOLOCK)
WHERE MN.ZUONR IS NOT NULL AND MN.HKONT IN ('00123','00122') AND (MN.AUGBL IS NULL OR MN.AUGBL = '') AND MN.DMBTR > 0
ORDER BY MN.ZUONR, MN.DMBTR

IF OBJECT_ID('tempdb..#NegValues') IS NOT NULL DROP TABLE #NegValues
SELECT DISTINCT ROW_NUMBER() OVER (ORDER BY MN.ZUONR) AS ID, MN.ZUONR, MN.BELNR, MN.DMBTR, NULL as Marker INTO #NegValues
FROM dbo.FIS_BELEG MN WITH (NOLOCK)
WHERE MN.ZUONR IS NOT NULL AND MN.HKONT IN ('00123','00122') AND (MN.AUGBL IS NULL OR MN.AUGBL = '') AND MN.DMBTR < 0
ORDER BY MN.ZUONR, MN.DMBTR

DECLARE PosCurs CURSOR FOR
    SELECT DISTINCT ID, ZUONR, BELNR, DMBTR FROM #OpenItems
    WHERE Marker IS NULL AND ZUONR = @zuonrPos

OPEN PosCurs
FETCH NEXT FROM PosCurs INTO @idPos, @zuonrPos, @belnrPos, @dmbtrPos
SET @outerLoop = @@FETCH_STATUS
WHILE @outerLoop = 0
    BEGIN
    DECLARE NegCurs CURSOR FOR
        SELECT DISTINCT ID, ZUONR, BELNR, DMBTR FROM #NegValues
        WHERE Marker IS NULL
    OPEN NegCurs
    FETCH NEXT FROM NegCurs INTO @idNeg, @zuonrNeg, @belnrNeg, @dmbtrNeg
    SET @innerLoop = @@FETCH_STATUS
    WHILE @innerLoop = 0 AND (@dmbtrPos BETWEEN (@SumVal - 5) AND (@SumVal + 5)) AND (@SumVal * 0.01) < 5
        BEGIN
            SET @SumVal = @SumVal + ABS(@dmbtrNeg)
            INSERT INTO #Report VALUES (@zuonrPos, @belnrPos, @dmbtrPos, @belnrNeg, @dmbtrNeg)
            UPDATE #OpenItems
                SET Marker = 1 WHERE ZUONR = @zuonrPos AND BELNR = @belnrPos
            UPDATE #NegValues
                SET Marker = 1 WHERE ZUONR = @zuonrNeg AND BELNR = @belnrNeg
            FETCH NEXT FROM NegCurs INTO @idNeg, @zuonrNeg, @belnrNeg, @dmbtrNeg
        END
        CLOSE NegCurs
        DEALLOCATE NegCurs

        SET @SumVal = 0

    FETCH NEXT FROM PosCurs INTO @idPos, @zuonrPos, @belnrPos, @dmbtrPos
    END

CLOSE PosCurs
DEALLOCATE PosCurs

Я уже некоторое время просматриваю inte rnet, чтобы попытаться найти ответ, однако я с треском провалился. Есть идеи?

Ответы [ 3 ]

1 голос
/ 04 февраля 2020

Таким образом, использование рекурсивного CTE, вероятно, является наилучшим способом продвижения вперед, но у вас было множество других требований (совпадение в пределах +/- 5, недопустимо многократное сопоставление элементов и т. Д. c.). Я придумал шаблон basi c, но он, вероятно, требует немного больше работы. Пришлось go на встречу, но вот где я должен (заменить STRING_SPLIT на вашу UDF для разделения списков выбора через запятую).

DECLARE @table1 TABLE (id INT, code VARCHAR(10), [value] INT);
INSERT INTO @table1 SELECT 1, 'abc', 200;
INSERT INTO @table1 SELECT 2, 'abc', 125;
DECLARE @table2 TABLE (id INT, code VARCHAR(10), [value] INT);
INSERT INTO @table2 SELECT 1, 'abc', -50;
INSERT INTO @table2 SELECT 2, 'abc', -50;
INSERT INTO @table2 SELECT 3, 'abc', -75;
INSERT INTO @table2 SELECT 4, 'abc', -100;
INSERT INTO @table2 SELECT 5, 'abc', -125;
DECLARE @matches TABLE (id1 INT, id2 INT);
--Still have matches to be made
WHILE EXISTS (SELECT * FROM @table1 t1 LEFT JOIN @matches m ON m.id1 = t1.id WHERE m.id1 IS NULL)
BEGIN
    DECLARE @match_id INT;
    DECLARE @match_value INT;
    SELECT TOP 1 @match_id = t1.id, @match_value = t1.[value] FROM  @table1 t1 LEFT JOIN @matches m ON m.id1 = t1.id WHERE m.id1 IS NULL;

    --Make a list of the remaining possible combinations
    DECLARE @candidates TABLE (combination VARCHAR(100), [value] INT, [difference] INT);
    DELETE FROM @candidates;
    WITH cte AS (
        SELECT
            CONVERT(VARCHAR(100), t2.id) AS combination,
            t2.[value],
            t2.id,
            0 AS [level]
        FROM
            @table2 t2
        WHERE
            NOT EXISTS (SELECT * FROM @matches m WHERE m.id2 = t2.id)
        UNION ALL
        SELECT
            CONVERT(VARCHAR(100), cte.combination + ',' + CONVERT(VARCHAR(10), t2.id)) AS combination,
            cte.[value] + t2.[value],
            t2.id,
            cte.[level] + 1 AS [level]
        FROM
            @table2 t2
            INNER JOIN cte ON cte.id < t2.id
        WHERE
            NOT EXISTS (SELECT * FROM @matches m WHERE m.id2 = t2.id)
            AND cte.[level] < 4)
    INSERT INTO
        @candidates
    SELECT
        combination,
        [value],
        @match_value + cte.[value] AS [difference]
    FROM
        cte;

    --Find the best match within +/- 5
    DECLARE @best_match VARCHAR(100);
    SELECT @best_match = NULL;
    SELECT TOP 1 @best_match = combination FROM @candidates c WHERE ABS([difference]) <= 5 ORDER BY ABS([difference]) DESC;

    --If we didn't find a match
    IF @best_match IS NULL
    BEGIN
        INSERT INTO @matches SELECT @match_id, NULL; --note there was no match
    END
    ELSE
    --If we did find a match
    BEGIN
        INSERT INTO @matches SELECT @match_id, cs.[value] FROM STRING_SPLIT(@best_match, ',') cs; --note there was a match
    END;
    SELECT @match_id, @match_value, @best_match;
END;
SELECT * FROM @matches;

Когда я запустил это, я получил:

id1 id2
1   1
1   2
1   4
2   5
1 голос
/ 04 февраля 2020

рекурсивный cte (не работает):

declare @table2 table
(
    amount money,
    cola varchar(10)
);

insert into @table2(amount, cola)
values(-50, 'abc'), (-50, 'abc'), (-75, 'abc'), (-100, 'abc'), (-125, 'abc');

declare @table1 table
(
    total money,
    colb varchar(10)
);
insert into @table1(total, colb)
values(200, 'abc');


with o
as
(
    select *, row_number() over(order by amount) as rownum
    from @table2
),
cte as
(
    select amount, colA, rownum, amount as addamount, cast(concat(amount ,',') as varchar(max)) as concatamount
    from o
    union all
    select o.amount, o.colA, o.rownum, cte.addamount+o.amount, cast(concat(cte.concatamount, o.amount, ',') as varchar(max)) 
    from cte
    join o on cte.rownum< o.rownum
)
select *
from cte as c
left join @table1 as t1 on c.addamount = -1*t1.total;
0 голосов
/ 30 марта 2020

Хорошо, так что мне удалось преодолеть проблему с некоторой помощью от вас, ребята, за что я вам большое спасибо. Это то, что я придумал, и это работает очень хорошо:

IF OBJECT_ID('tempdb..#OpenItems') IS NOT NULL DROP TABLE #OpenItems
SELECT DISTINCT ROW_NUMBER() OVER (ORDER BY MN.ZUONR, MN.DMBTR ASC) AS ID, MN.ZUONR, CAST(MN.BELNR as varchar(MAX)) as BELNR, BUZEI, BUKRS, CAST(MN.DMBTR as money) as DMBTR, BLDAT, NULL as Marker INTO #OpenItems
FROM dbo.FIS_BELEG MN WITH (NOLOCK)
WHERE MN.ZUONR IS NOT NULL AND MN.HKONT IN ('0002403081','0002403080') AND (MN.AUGBL IS NULL OR MN.AUGBL = '') AND MN.DMBTR > 0 AND AUGDT IS NULL

IF OBJECT_ID('tempdb..#NegValues') IS NOT NULL DROP TABLE #NegValues
SELECT DISTINCT ROW_NUMBER() OVER (ORDER BY MN.ZUONR, MN.DMBTR DESC) AS ID, MN.ZUONR, CAST(MN.BELNR as varchar(MAX)) as BELNR, BUZEI, BUKRS, CAST(MN.DMBTR as money) as DMBTR, BLDAT, NULL as Marker INTO #NegValues
FROM dbo.FIS_BELEG MN WITH (NOLOCK)
WHERE MN.ZUONR IS NOT NULL AND MN.HKONT IN ('0002403081','0002403080') AND (MN.AUGBL IS NULL OR MN.AUGBL = '') AND MN.DMBTR < 0 AND AUGDT IS NULL;

CREATE CLUSTERED INDEX IDX_C_OpenItems_ID ON #OpenItems (ID)
CREATE CLUSTERED INDEX IDX_C_NegValues_ID ON #NegValues (ID)
CREATE INDEX IDX_NegValues_ZUONR_ID ON #NegValues (ZUONR, ID) INCLUDE (BELNR, DMBTR, Marker) WHERE (Marker IS NULL)

DECLARE @idPos as int
DECLARE @zuonrPos as varchar(18)
DECLARE @belnrPos as varchar(10)
DECLARE @dmbtrPos as money
DECLARE @bukrsPos as varchar(4)
DECLARE @buzeiPos as numeric(3,0)
DECLARE @bldatPos as datetime

DECLARE @matchZUONR as varchar(18)
DECLARE @matchBELNR as varchar(10)
DECLARE @matchDMBTR as money
DECLARE @matchBLDAT as datetime
DECLARE @sumAmount as money
DECLARE @concatDMBTR as varchar(MAX)
DECLARE @concatBELNR as varchar(MAX)
DECLARE @iCount as int = 20             --set to 20 recursions to speed up the process

IF OBJECT_ID('tempdb..#Report') IS NOT NULL DROP TABLE #Report
CREATE TABLE #Report (
    ZUONR varchar(18),
    BELNR varchar(10),
    BUKRS varchar(4),
    BUZEI numeric(3,0),
    DocumentMonth int,
    OI_DMBTR money,
    SumAmount money,
    ConcatAmount varchar(MAX),
    ConcatPO varchar(MAX))

DECLARE PosCurs CURSOR LOCAL FOR 
    SELECT DISTINCT ID, ZUONR, BELNR, DMBTR, BUZEI, BUKRS, BLDAT FROM #OpenItems
    WHERE Marker IS NULL ORDER BY ID

OPEN PosCurs
FETCH NEXT FROM PosCurs INTO @idPos, @zuonrPos, @belnrPos, @dmbtrPos, @buzeiPos, @bukrsPos, @bldatPos
WHILE @@FETCH_STATUS = 0
BEGIN

    SET @sumAmount = NULL
    SET @concatDMBTR = NULL
    SET @concatBELNR = NULL

;   WITH NegVals AS (
        SELECT ID, ZUONR, BELNR, DMBTR, BUZEI, BUKRS, BLDAT
        FROM #NegValues WITH (NOLOCK)
        WHERE ZUONR = @zuonrPos AND Marker IS NULL AND ABS(DMBTR) <= @dmbtrPos
    ), CTE AS ( 
        SELECT ZUONR, BELNR, ID, DMBTR as SumAmount, CONCAT(CAST(DMBTR as varchar(MAX)), ', ') as ConcatAmount, CONCAT(CAST(BELNR as varchar(MAX)), ', ') as ConcatPO, 1 as RecursionCounter
        FROM NegVals WITH (NOLOCK)

        UNION ALL

        SELECT NegVals.ZUONR, NegVals.BELNR, NegVals.ID, CTE.SumAmount + NegVals.DMBTR, CONCAT(CTE.ConcatAmount, CAST(NegVals.DMBTR as varchar(MAX)), ', '), CONCAT(CTE.ConcatPO, CAST(NegVals.BELNR as varchar(max)), ', '), RecursionCounter + 1
        FROM CTE
            INNER JOIN NegVals ON CTE.ZUONR = NegVals.ZUONR and cte.ID < NegVals.ID and (
            ABS(CTE.SumAmount + NegVals.DMBTR) <=  @dmbtrPos + 5  AND ABS(CTE.SumAmount + NegVals.DMBTR) >=  @dmbtrPos - 5
            )
        WHERE RecursionCounter <= @iCount AND MONTH(NegVals.BLDAT) = MONTH(@bldatPos))

    SELECT TOP 1 
                 @sumAmount = C.SumAmount,
                 @concatDMBTR = c.ConcatAmount,
                 @concatBELNR = C.ConcatPO
    FROM  CTE C
    WHERE 
        ABS(C.SumAmount) <=  @dmbtrPos + 5  and ABS(C.SumAmount) >=  @dmbtrPos - 5
    ORDER BY ABS(C.SumAmount - (-1 * @dmbtrPos))

    INSERT INTO #Report VALUES (@zuonrPos, @belnrPos, @bukrsPos, @buzeiPos, MONTH(@bldatPos), @dmbtrPos, @sumAmount, @concatDMBTR, @concatBELNR)

    UPDATE #NegValues
        SET Marker = 1 WHERE ZUONR = @zuonrPos AND @concatBELNR LIKE '%' + BELNR + '%'
    UPDATE #OpenItems
        SET Marker = 1 WHERE ZUONR = @zuonrPos AND BELNR = @belnrPos AND BUZEI = @buzeiPos

    FETCH NEXT FROM PosCurs INTO @idPos, @zuonrPos, @belnrPos, @dmbtrPos, @buzeiPos, @bukrsPos, @bldatPos
END

CLOSE PosCurs
DEALLOCATE PosCurs
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...