Denomination Verteilungsberechnung

stimmen
0

Einige Hintergrundgeschichte: Das Unternehmen A gibt Gutscheine an die Gewinner von einer Herausforderung aus. Die SQL, dass ich derzeit Bedarf schreibe die erforderliche Gutschein Stückelung, dass Summen auf den Wert einer Person verliehen zu entscheiden. Ich habe eine Tabelle, dass speichert die Nennwerte für Gutscheine zur Verfügung, je nach Land und Währung.

Im Beispiel unten wird eine bestimmte Person mit € 80 im Wert von Gutscheinen vergeben.

Die Abfrage unten zeigt die Ergebnisse einer Verweistabelle für Gutschein Nennwerte für ein bestimmtes Land.

SELECT * FROM tblDenominationScheme WHERE CountryCode IN ('AT', 'US')

Ergebnis:

No. | CountryCode  |   VoucherName | VoucherValue
-------------------------------------------------
1   | AT           |   €50 Shop A  |     50
2   | AT           |   €25 Shop A  |     25
3   | AT           |   €15 Shop A  |     15
4   | AT           |   €10 Shop A  |     10
5   | US           |   $50 Store B |     50
6   | US           |   $10 Store B |     10
7   | US           |   $5 Store B  |      5

Meine aktuelle SQL ist als unten die erforderlichen Gutschein Nennwerte für € 80 Gutschein zu bestimmen:

   DECLARE @CountryCode1 VARCHAR(2) = 'AT'
   DECLARE @ChallengerID INT = 1172
   DECLARE @RoundedAmount1 INT = 80
   DECLARE @Vouchers INT
   DECLARE @AmountAwarded INT = 0

   SET @AmountAwarded = @RoundedAmount1

   DROP TABLE IF EXISTS #tempVoucher

   CREATE TABLE #tempVoucher
   (
          CountryCode VARCHAR(2),
          ChallengerID INT,
          AmountAwarded INT,
          Vouchers INT,
   )

   WHILE (@RoundedAmount1 > 0)
   BEGIN

          SET @Vouchers = 0

          SELECT TOP 1 @Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = @CountryCode1 AND VoucherValue <= @RoundedAmount1 ORDER BY VoucherValue DESC

          IF (@Vouchers > 0)
          BEGIN
                 SET @RoundedAmount1 = @RoundedAmount1 - @Vouchers
          END
          ELSE
          BEGIN
                 SELECT TOP 1 @Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = @CountryCode1 ORDER BY VoucherValue
                 SET @RoundedAmount1 = @RoundedAmount1 - @RoundedAmount1
          END

          INSERT INTO #tempVoucher VALUES (@CountryCode1,@ChallengerID, @AmountAwarded, @Vouchers)
   END

   SELECT * FROM #tempVoucher

Ergebnis aus der SQL oben:

No. | CountryCode  | ChallengerID |   AmountAwarded | Vouchers
--------------------------------------------------------------
1   | AT           | 1172         |   80            |   50
2   | AT           | 1172         |   80            |   25
3   | AT           | 1172         |   80            |   10

HINWEIS: Der Wert in AmountAwarded Spalte wird das gleiche für alle drei Reihen sein. Der Betrag in der Spalte Gutscheine für die 3 Zeilen soll bis zu 80 summiert.

Das Ergebnis oben ist offensichtlich falsch, denn wenn man die Werte in der Spalte Gutscheine zusammenzufassen, es 85 gibt Ihnen, die 5 mehr als die AmountAwarded ist

Erwartetes Ergebnis (oder zumindest am nächsten):

No. | CountryCode  | ChallengerID |   AmountAwarded | Vouchers
--------------------------------------------------------------
1   | AT           | 1172         |   80            |   50
2   | AT           | 1172         |   80            |   10
3   | AT           | 1172         |   80            |   10
4   | AT           | 1172         |   80            |   10

Wer in der Lage zu helfen?

Veröffentlicht am 02/12/2019 um 23:58
quelle vom benutzer
In anderen Sprachen...                            


3 antworten

stimmen
1

Dies könnte eine teure Abfrage, aber bekommt man verschiedene Optionen bis 7 Gutscheine zu liefern bis zu Ihnen das erwartete Ergebnis. Dies wird jedoch erzeugt eine riesige Menge an liest, wenn die Reihen zu erhöhen oder die Menge von Gutscheinen kann größer sein.

  DECLARE @CountryCode1 VARCHAR(2) = 'AT'
   DECLARE @RoundedAmount1 INT = 80;

WITH cteDenominations AS(
    SELECT No, VoucherValue 
    FROM tblDenominationScheme 
    WHERE CountryCode = @CountryCode1
    UNION ALL
    SELECT 10000, 0
),
ctePermutations AS(
    SELECT a.No             AS a_No, 
           a.VoucherValue   AS a_Value, 
           b.No             AS b_No, 
           b.VoucherValue   AS b_Value,
           c.No             AS c_No, 
           c.VoucherValue   AS c_Value,
           d.No             AS d_No, 
           d.VoucherValue   AS d_Value,
           e.No             AS e_No, 
           e.VoucherValue   AS e_Value,
           f.No             AS f_No, 
           f.VoucherValue   AS f_Value,
           g.No             AS g_No, 
           g.VoucherValue   AS g_Value,
        ROW_NUMBER() OVER(ORDER BY a.No, b.No, c.No, d.No) Permutation
    FROM cteDenominations a
    JOIN cteDenominations b ON a.VoucherValue >= b.VoucherValue
    JOIN cteDenominations c ON b.VoucherValue >= c.VoucherValue
    JOIN cteDenominations d ON c.VoucherValue >= d.VoucherValue
    JOIN cteDenominations e ON d.VoucherValue >= e.VoucherValue
    JOIN cteDenominations f ON e.VoucherValue >= f.VoucherValue
    JOIN cteDenominations g ON f.VoucherValue >= g.VoucherValue
    WHERE @RoundedAmount1 = a.VoucherValue 
                          + b.VoucherValue 
                          + c.VoucherValue 
                          + d.VoucherValue 
                          + e.VoucherValue 
                          + f.VoucherValue 
                          + g.VoucherValue 
)
SELECT Permutation,
    u.No,
    u.VoucherValue
FROM ctePermutations
CROSS APPLY (VALUES(a_No, a_Value),
                   (b_No, b_Value),
                   (c_No, c_Value),
                   (d_No, d_Value),
                   (e_No, e_Value),
                   (f_No, f_Value),
                   (g_No, g_Value))u(No, VoucherValue)
WHERE VoucherValue > 0
AND   Permutation = 1 --Remove this to get all possibilities
;
Beantwortet am 03/12/2019 um 01:05
quelle vom benutzer

stimmen
1

Sieht aus wie Sie eine Gleichung lösen müssen:

80 = n1*v1 + k2*n2...

wo v1,v2 ...sind Werte , die Sie in der Datenbank speichern und Sie finden müssen n1, n2 ..., die sind in {0, N} Es gibt keine Art und Weise ist , wie es in SQL zu implementieren. Außer - über alle möglichen Werte, aber es ist nicht die intelligentere Weise.

Außerdem sehen diese Info: https://math.stackexchange.com/questions/431367/solving-a-first-order-diophantine-equation-with-many-terms

Beantwortet am 03/12/2019 um 01:08
quelle vom benutzer

stimmen
0

Logik

  1. Finden Sie die größte Menge (dh weniger als oder Ausgangsbetrag gleich) Gutscheine von 1 Stückelung machen.
  2. Subtrahieren Sie diesen Wert von Ausgangsbetrag Rest zu bekommen,
  3. Finden Sie die größte Menge (dh weniger als oder gleich Rest) eine Anzahl von Gutscheinen von 1 kleiner Stückelung machen.
  4. Subtrahieren dieses Wertes von früheren Rest.
  5. Gehen Sie zurück zu Schritt 3

Eigenschaften:

  • Verarbeitet mehrere besten Kombinationen.
  • Kleine Anzahl von Kombinationen durchsucht.
  • Auf meinem Laptop: 100 läuft dauern ca. 3 Sekunden

Anmerkungen

Die Leistung kann durch Einsparung Ausgang verbessert werden , VoucherCombinationsum eine Tabellenvariable und verwenden sie dann in nachfolgenden CTEs.

Code:

DECLARE @Vouchers TABLE( CountryCode CHAR( 2 ), VoucherValue DECIMAL( 10, 2 ))
INSERT INTO @Vouchers VALUES( 'AT', 50 ), ( 'AT', 40 ), ( 'AT', 25 ), ( 'AT', 20 ), ( 'AT', 15 ), ( 'AT', 10 ), ( 'US', 50 ), ( 'US', 10 ), ( 'US', 5 );

-- Small number table
-- Limits maximum count of Vouchers of a given denomination.
DECLARE @Numbers TABLE( Num INT )
INSERT INTO @Numbers VALUES( 1 ), ( 2 ), ( 3 ), ( 4 ), ( 5 ), ( 6 ), ( 7 ), ( 8 ), ( 9 ), ( 10 )

DECLARE @TargetAmount DECIMAL( 10, 2 ) = 60;
DECLARE @CountryCode CHAR( 2 ) = 'AT';

;WITH VoucherCombinations
AS (
    -- Anchor
    SELECT ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS ParentGroupID,
        ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS SubGroupID,
        1 AS IterationID,
        VoucherValue, Num AS VoucherCumulativeCount,
        CAST( VoucherValue * Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
        CAST( @TargetAmount - ( VoucherValue * Num ) AS DECIMAL( 10, 2 )) AS Remainder
    FROM @Vouchers
        -- Find the largest amount a given Voucher denomination can produce that is less than or equal to @TargetAmount
        INNER JOIN @Numbers ON ( VoucherValue * Num ) <= @TargetAmount AND @TargetAmount - ( VoucherValue * Num ) < VoucherValue
    WHERE CountryCode = @CountryCode
    UNION ALL
    -- Recursive query
    SELECT SubGroupID,
        SubGroupID * 10 + ROW_NUMBER() OVER( ORDER BY V.VoucherValue DESC ) AS SubGroupID,
        IterationID + 1,
        V.VoucherValue, VoucherCumulativeCount + N.Num AS VoucherCount,
        CAST( V.VoucherValue * N.Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
        CAST( Remainder - ( V.VoucherValue * N.Num ) AS DECIMAL( 10, 2 )) AS Remainder
    FROM VoucherCombinations AS VP
        -- For each denomination look at the smaller denominations
        INNER JOIN @Vouchers AS V ON VP.VoucherValue > V.VoucherValue
            INNER JOIN @Numbers AS N ON V.VoucherValue * N.Num <= Remainder AND Remainder - ( V.VoucherValue * N.Num ) < V.VoucherValue
    WHERE CountryCode = @CountryCode
),
-- Discard invalid combinations i.e. remainder is not 0
VoucherPoolValid AS(
    SELECT *, DENSE_RANK() OVER( ORDER BY VoucherCumulativeCount ASC ) AS BestCombos
    FROM VoucherCombinations
    WHERE Remainder = 0
),
-- Find best combinations i.e. smallest number of Vouchers; Note: logic supports having more than 1 best combination
VoucherPoolBestCombos AS(
    SELECT *, ROW_NUMBER() OVER( ORDER BY BestCombos ASC ) AS ComboID
    FROM VoucherPoolValid
    WHERE BestCombos = 1
),
-- Return all denominations for each combination
VoucherPoolAllDetails AS(
    SELECT *
    FROM VoucherPoolBestCombos
    UNION ALL
    SELECT Parent.*, BestCombos, ComboID
    FROM VoucherPoolAllDetails AS Child
        INNER JOIN VoucherCombinations AS Parent ON Child.ParentGroupID = Parent.SubGroupID
    WHERE Child.SubGroupID <> Child.ParentGroupID
)
SELECT * FROM VoucherPoolAllDetails
ORDER BY ComboID
Beantwortet am 03/12/2019 um 04:20
quelle vom benutzer

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more