Calcolo della distribuzione Denominazione

voti
0

Qualche storia di fondo: la società A distribuisce buoni per i vincitori di una sfida. Lo SQL che Attualmente sto scrivendo deve decidere la denominazione voucher necessario che le somme per il valore assegnato ad una persona. Ho una tabella che memorizza le denominazioni disponibili per i buoni, a seconda del paese e della valuta.

Nell'esempio che segue, una persona in particolare viene premiato con € 80 vale la pena di buoni.

La query sottostante mostra i risultati di una tabella di ricerca per tagli di voucher disponibili per un determinato paese.

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

Risultato:

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

My SQL attuale è la seguente per determinare le denominazioni voucher necessario per € 80 buono:

   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

Risultato da SQL di cui sopra:

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

NOTA: Il valore nella colonna AmountAwarded sarà lo stesso per tutti e 3 righe. L'importo nella colonna Buoni per le 3 righe dovrebbe riassumere a 80.

Il risultato di cui sopra è ovviamente corretta, perché se si somma dei valori nella colonna Buoni, ti dà 85, che è 5 più del AmountAwarded

risultato atteso (o almeno più vicino):

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

Chiunque in grado di aiutare?

È pubblicato 02/12/2019 alle 23:58
fonte dall'utente
In altre lingue...                            


3 risposte

voti
1

Questo potrebbe essere una query costoso, ma si ottiene diverse opzioni per fornire fino a 7 buoni per ottenere il risultato previsto. Questo, tuttavia, genera una quantità enorme di legge se le righe aumentare o la quantità di buoni possono essere maggiori.

  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
;
Risposto il 03/12/2019 a 01:05
fonte dall'utente

voti
1

Assomiglia è necessario risolvere un'equazione:

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

dove v1,v2 ...sono valori che si memorizzano nel database e hai bisogno di trovare n1, n2 ..., che sono in {0, N} Non c'è modo come implementarlo in SQL. Tranne che - su tutti i valori possibili, ma non è il modo più intelligente.

Inoltre, vedere queste informazioni: https://math.stackexchange.com/questions/431367/solving-a-first-order-diophantine-equation-with-many-terms

Risposto il 03/12/2019 a 01:08
fonte dall'utente

voti
0

Logica

  1. Trova la più grande quantità (che è inferiore o pari a importo iniziale) voucher di 1 denominazione può fare.
  2. Sottrarre questo valore avvio importo per ottenere resto,
  3. Trova la più grande quantità (che è inferiore o uguale a resto) un certo numero di buoni di 1 più piccola denominazione può fare.
  4. Sottrarre questo valore dal resto precedente.
  5. Tornare al punto 3

Caratteristiche:

  • Gestisce molteplici migliori combinazioni.
  • Piccolo numero di combinazioni vengono ricercati.
  • Sul mio portatile: 100 corre circa 3 secondi

Gli appunti

Prestazioni possono essere migliorate salvando uscita VoucherCombinationsad una variabile di tabella e quindi utilizzando in CTE successive.

Codice:

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
Risposto il 03/12/2019 a 04:20
fonte dall'utente

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