PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Как да напиша комбинаторна функция в postgres?

След като го преспах, имах напълно нова, по-проста и по-бърза идея:

CREATE OR REPLACE FUNCTION f_combos(_arr anyarray)
  RETURNS TABLE (combo anyarray) LANGUAGE plpgsql AS
$BODY$
BEGIN
    IF array_upper(_arr, 1) IS NULL THEN
        combo := _arr; RETURN NEXT; RETURN;
    END IF;

    CASE array_upper(_arr, 1)
--  WHEN 0 THEN -- does not exist
    WHEN 1 THEN
        RETURN QUERY VALUES ('{}'), (_arr);
    WHEN 2 THEN
        RETURN QUERY VALUES ('{}'), (_arr[1:1]), (_arr), (_arr[2:2]);
    ELSE
        RETURN QUERY
        WITH x AS (
            SELECT f.combo FROM f_combos(_arr[1:array_upper(_arr, 1)-1]) f
            )
        SELECT x.combo FROM x
        UNION ALL
        SELECT x.combo || _arr[array_upper(_arr, 1)] FROM x;
    END CASE;
END
$BODY$;

Обаждане:

SELECT * FROM f_combos('{1,2,3,4,5,6,7,8,9}'::int[]) ORDER BY 1;

512 реда, общо време на изпълнение:2,899 ms

Обяснете

  • Третиране на специални случаи с NULL и празен масив.
  • Изградете комбинации за примитивен масив от две.
  • Всеки по-дълъг масив се разбива на:
    • комбинациите за същия масив с дължина n-1
    • плюс всички тези, комбинирани с елемент n .. рекурсивно .

Наистина просто, след като го разберете.

  • Работи за едномерни масиви с цели числа, започващи с долен индекс 1 (вижте по-долу).
  • 2-3 пъти по-бързо от старото решение, мащабира се по-добре.
  • Работи за всеки тип елемент отново (използвайки полиморфни типове).
  • Включва празния масив в резултата, както е показано във въпроса (и както @Craig ми посочи в коментарите).
  • По-къси, по-елегантни.

Това предполага индекси на масив започва от1 (По подразбиране). Ако не сте сигурни за вашите стойности, извикайте функцията по този начин, за да нормализирате:

SELECT * FROM  f_combos(_arr[array_lower(_arr, 1):array_upper(_arr, 1)]);

Не съм сигурен дали има по-елегантен начин за нормализиране на индексите на масива. Публикувах въпрос за това:
Нормализиране на индексите на масива за 1-измерен масив, така че да започват с 1

Старо решение (по-бавно)

CREATE OR REPLACE FUNCTION f_combos2(_arr int[], _a int[] = '{}', _z int[] = '{}')
 RETURNS SETOF int[] LANGUAGE plpgsql AS
$BODY$
DECLARE
   i int;
   j int;
   _up int;
BEGIN
   IF array_length(_arr,1) > 0 THEN 
      _up := array_upper(_arr, 1);

      FOR i IN array_lower(_arr, 1) .. _up LOOP
         FOR j IN i .. _up  LOOP
            CASE j-i
            WHEN 0,1 THEN
               RETURN NEXT _a || _arr[i:j] || _z;
            WHEN 2 THEN
               RETURN NEXT _a || _arr[i:i] || _arr[j:j] || _z;
               RETURN NEXT _a || _arr[i:j] || _z;
            ELSE
               RETURN NEXT _a || _arr[i:i] || _arr[j:j] || _z;
               RETURN QUERY SELECT *
               FROM f_combos2(_arr[i+1:j-1], _a || _arr[i], _arr[j] || _z);
            END CASE;
         END LOOP;
      END LOOP;
   ELSE
      RETURN NEXT _arr;
   END IF;
END;
$BODY$;

Обаждане:

SELECT * FROM f_combos2('{7,15,48}'::int[]) ORDER BY 1;

Работи за едномерни масиви с цели числа. Това може да бъде допълнително оптимизирано, но това със сигурност не е необходимо за обхвата на този въпрос.
ORDER BY за налагане на реда, показан във въпроса.

Осигурете NULL или празен масив, като NULL се споменава в коментарите.

Тестван с PostgreSQL 9.1, но трябва да работи с всяка съвременна версия.array_lower() и array_upper() съществуват поне от PostgreSQL 7.4. Само параметрите по подразбиране са нови във версия 8.4. Може лесно да се замени.

Производителността е прилична.

SELECT DISTINCT * FROM f_combos('{1,2,3,4,5,6,7,8,9}'::int[]) ORDER BY 1;

511 реда, общо време на изпълнение:7,729 ms

Обяснение

Той се основава на тази проста форма който създава само всички комбинации от съседни елементи:

CREATE FUNCTION f_combos(_arr int[])
  RETURNS SETOF int[] LANGUAGE plpgsql AS
$BODY$
DECLARE
   i  int;
   j  int;
  _up int;
BEGIN
   _up := array_upper(_arr, 1);

   FOR i in array_lower(_arr, 1) .. _up LOOP
      FOR j in i .. _up LOOP
         RETURN NEXT _arr[i:j];
      END LOOP;
   END LOOP;
END;
$BODY$;

Но това няма да успее за подмасиви с повече от два елемента. И така:

  • За всеки подмасив с 3 елемента се добавя един масив само с външните два елемента. това е пряк път за този специален случай, който подобрява производителността и не е строго необходим .

  • За всеки подмасив с повече от 3 елемента вземам външните два елемента и попълнете с всички комбинации от вътрешни елементи изграден от същата функция рекурсивно .



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. кортеж, актуализиран едновременно при създаване на функции в postgresql / PL/pgSQL

  2. Привилегии на PostgreSQL и управление на потребителите – какво трябва да знаете

  3. Как да премахнете няколко таблици в PostgreSQL с помощта на заместващ знак

  4. Вземете идентификатор от условно INSERT

  5. Използване на криптиране за засилване на сигурността на базата данни на PostgreSQL