Wyliczenie dystrybucja denominacja

głosy
0

Niektóre tła historia: Firma A rozdaje bony na zwycięzców wyzwanie. SQL, które obecnie piszę musi zdecydować wymaganą nominał bonu, że sum wartości przyznanego osobie. Mam tabeli, która przechowuje nominały dostępne dla kuponów, w zależności od kraju i waluty.

W poniższym przykładzie dana osoba jest przyznawana z € 80 wartości kuponów.

Zapytanie poniżej wyświetla wyniki tabeli odnośników dla bonów nominałów dostępnych dla danego kraju.

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

Wynik:

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

Mój obecny SQL jest jak poniżej w celu określenia wymaganych kuponów nominałów za € 80 bonu:

   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

Wynikać z SQL powyżej:

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

UWAGA: Wartość w kolumnie AmountAwarded będzie taka sama dla wszystkich 3 rzędów. Ilość w kolumnie Kupony do 3 rzędów powinny sumują się do 80.

Wynik powyżej jest oczywiście błędne, ponieważ jeśli zsumować wartości w kolumnie talony, to daje 85, który jest więcej niż 5 AmountAwarded

Wynik (a przynajmniej najbliżej) oczekuje:

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

Ktoś w stanie pomóc?

Utwórz 02/12/2019 o 23:58
źródło użytkownik
W innych językach...                            


3 odpowiedzi

głosy
1

To może być kosztowne zapytania, ale dostaje różne opcje, aby dostarczyć do 7 kuponów, aby dostać się do oczekiwanego rezultatu. To jednak będzie generować ogromną ilość treści, jeżeli wiersze lub zwiększyć ilość kuponów może być większa.

  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
;
Odpowiedział 03/12/2019 o 01:05
źródło użytkownik

głosy
1

Wygląda jak trzeba rozwiązać równanie:

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

gdzie v1,v2 ...są wartościami, które przechowywane są w bazie danych i trzeba znaleźć n1, n2 ..., które są {0, N} Nie ma sposobu, jak wdrożyć go w SQL. Z wyjątkiem: - w stosunku do wszystkich możliwych wartości, ale nie jest to lepszy sposób.

Również zobaczyć tę informację: https://math.stackexchange.com/questions/431367/solving-a-first-order-diophantine-equation-with-many-terms

Odpowiedział 03/12/2019 o 01:08
źródło użytkownik

głosy
0

Logika

  1. Znajdź największą ilość (czyli mniej niż lub równe kwoty wyjściowej) bony o nominale 1 można dokonać.
  2. Odjąć tę wartość od ilości początkowej aby dostać resztę,
  3. Znajdź największą ilość (która jest mniejsza niż lub równa pozostałej) liczba kuponów od 1 mniejszego nominału może uczynić.
  4. Odjąć tę wartość z poprzedniego pozostały.
  5. Wróć do kroku 3

Cechy:

  • Uchwyty kilka najlepszych kombinacji.
  • Mała liczba kombinacji są przeszukiwane.
  • Na moim laptopie: 100 biegnie zająć około 3 sekundy

Uwagi

Wydajność może być poprawiona przez oszczędność wyjście VoucherCombinationsdo zmiennej tabeli, a następnie używając go w kolejnych współczynniki CTE.

Kod:

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
Odpowiedział 03/12/2019 o 04:20
źródło użytkownik

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