Получить комбинацию чисел из столбца в таблице, равную 30 в plsql - PullRequest
0 голосов
/ 05 ноября 2018

У меня есть таблица со столбцом, имеющим несколько чисел. Я хочу сгруппировать комбинации чисел в 4 группы, сумма которых равна 30. Ниже приведен пример таблицы.

NUMBERS
12
3
12
8
10
4
4
2
7
10
10
11
12
11
4

мне нужно ниже группы

Group 1:
3
4
12
11

Group 2:
12
10
8

Group 3:
11
12
7

Group 4:
2
4
10
10
4

1 Ответ

0 голосов
/ 05 ноября 2018
CREATE TABLE test_ ( num_ NUMBER);


INSERT INTO test_ VALUES (12);
INSERT INTO test_ VALUES (3);
INSERT INTO test_ VALUES (12);
INSERT INTO test_ VALUES (8);
INSERT INTO test_ VALUES (10);
INSERT INTO test_ VALUES (4);
INSERT INTO test_ VALUES (4);
INSERT INTO test_ VALUES (2);
INSERT INTO test_ VALUES (7);
INSERT INTO test_ VALUES (10);
INSERT INTO test_ VALUES (10);
INSERT INTO test_ VALUES (11);
INSERT INTO test_ VALUES (12);
INSERT INTO test_ VALUES (11);
INSERT INTO test_ VALUES (4);

COMMIT;


CREATE OR REPLACE TYPE T_NUMBER AS TABLE OF NUMBER;


DECLARE
  lv_numbers T_NUMBER;
  lv_result VARCHAR2(32000);

  /* Convert collection of number to VARCHAR2
   *
   * @param pi_numbers    collection to process
   * @param pi_separator  separator
   * @return              pi_numbers converted to VARCHAR2 with pi_separator as separator
  */
  FUNCTION toVarchar2(
    pi_numbers   T_NUMBER,
    pi_separator VARCHAR2 DEFAULT ','
  )
  RETURN VARCHAR2
  IS
    lv_result VARCHAR2(4000);
  BEGIN
    FOR i IN 1..pi_numbers.COUNT LOOP
      lv_result := lv_result || pi_numbers(i) || pi_separator;  
    END LOOP;
    lv_result := RTRIM(lv_result, pi_separator);
    RETURN lv_result;
  END toVarchar2;

  /* Roll collection to left 
   *
   * @param  pi_numbers   collection to process
   * @return              rolled colletion
  */
  FUNCTION rollLeft(
    pi_numbers   T_NUMBER
  )
  RETURN T_NUMBER
  IS
    lv_result     T_NUMBER;
    lv_currentTop NUMBER;
  BEGIN
    lv_result := pi_numbers;

    IF lv_result.COUNT > 1 THEN
      lv_currentTop := lv_result(1);
      FOR i IN 2..lv_result.COUNT LOOP
        lv_result(i-1) := lv_result(i);  
      END LOOP;
      lv_result(lv_result.COUNT) := lv_currentTop;
    END IF;
    RETURN lv_result;
  END rollLeft;

  /* Split collection to a grup in which we have items with SUM equal to pi_sum
   *
   * @param  pi_numbers  collection to process
   * @param  pi_sum      our goal sum
   * @return             result of split
  */
  FUNCTION split_(
    pi_numbers T_NUMBER,
    pi_sum     NUMBER
  )
  RETURN VARCHAR2
  IS
    lv_groupSeparator  CONSTANT VARCHAR2(1) := '|';
    lv_result          VARCHAR2(4000);
    lv_numbersInGroup  T_NUMBER := new T_NUMBER();
    lv_toFartherSplit  T_NUMBER;
    lv_currentSum      NUMBER   := 0;
    lv_allProcessed    BOOLEAN  := FALSE;
  BEGIN
    IF pi_numbers.COUNT > 0 THEN

      lv_currentSum := pi_numbers(1);
      lv_numbersInGroup.EXTEND;
      lv_numbersInGroup(lv_numbersInGroup.COUNT) := pi_numbers(1);

      FOR i IN 2..pi_numbers.COUNT LOOP
        IF lv_currentSum + pi_numbers(i) <= pi_sum THEN
          lv_currentSum := lv_currentSum + pi_numbers(i);
          lv_numbersInGroup.EXTEND;
          lv_numbersInGroup(lv_numbersInGroup.COUNT) := pi_numbers(i);
        END IF;
        IF lv_currentSum = pi_sum THEN
          EXIT;
        END IF;  
      END LOOP;

      IF lv_currentSum = pi_sum THEN
        lv_result         := toVarchar2(lv_numbersInGroup)  || lv_groupSeparator;
        lv_toFartherSplit := pi_numbers MULTISET EXCEPT lv_numbersInGroup;
      ELSE 
        lv_toFartherSplit := rollLeft(pi_numbers => pi_numbers);
        IF lv_toFartherSplit(lv_toFartherSplit.COUNT) < lv_toFartherSplit(1) THEN
          lv_result       := 'NotUsed:' || toVarchar2(pi_numbers);
          lv_allProcessed := TRUE;
        END IF;
      END IF;  

      IF lv_allProcessed = FALSE THEN
        lv_result := lv_result || 
                     split_(
                       pi_numbers => lv_toFartherSplit, 
                       pi_sum     => pi_sum
                     );
      END IF;

    END IF;


    RETURN lv_result;

  END split_;

BEGIN

  SELECT num_
    BULK COLLECT INTO lv_numbers
    FROM TEST_
   ORDER BY num_ desc;

  lv_result := split_(
                 pi_numbers => lv_numbers, 
                 pi_sum     => 30
               );

  dbms_output.put_line(lv_result);

END;
/
...