sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur skriver man kombinatorisk funktion i postgres?

Efter att jag sovit över den fick jag en helt ny, enklare, snabbare idé:

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$;

Ring:

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

512 rader, total körtid:2,899 ms

Förklara

  • Behandla specialfall med NULL och tom array.
  • Skapa kombinationer för en primitiv uppsättning av två.
  • Alla längre arrayer delas upp i:
    • kombinationerna för samma array med längden n-1
    • plus alla dessa kombinerade med element n .. rekursivt .

Verkligen enkelt, när du väl fick det.

  • Fungerar för 1-dimensionella heltalsmatriser som börjar med subscript 1 (se nedan).
  • 2-3 gånger så snabbt som den gamla lösningen, skalas bättre.
  • Fungerar för alla elementtyp igen (med polymorfa typer).
  • Inkluderar den tomma arrayen i resultatet som visas i frågan (och som @Craig påpekade för mig i kommentarerna).
  • Kortare, mer elegant.

Detta förutsätter arrayprenumerationer från 1 (Standard). Om du inte är säker på dina värden, anropa funktionen så här för att normalisera:

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

Inte säker på om det finns ett mer elegant sätt att normalisera array-subskript. Jag ställde en fråga om det:
Normalisera arraysubscripts för 1-dimensionell array så att de börjar med 1

Gammal lösning (långsammare)

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$;

Ring:

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

Fungerar för 1-dimensionella heltalsmatriser. Detta skulle kunna optimeras ytterligare, men det behövs verkligen inte för den här frågans omfattning.
ORDER BY för att införa den ordning som visas i frågan.

Ange NULL eller tom array, som NULL nämns i kommentarerna.

Testad med PostgreSQL 9.1, men bör fungera med alla halvvägs moderna versioner.array_lower() och array_upper() har funnits åtminstone sedan PostgreSQL 7.4. Endast parameterstandarder är nya i version 8.4. Kan lätt bytas ut.

Prestanda är anständigt.

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

511 rader, total körtid:7,729 ms

Förklaring

Den bygger på denna enkla form som bara skapar alla kombinationer av angränsande element:

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$;

Men detta kommer att misslyckas för sub-arrayer med mer än två element. Så:

  • För varje sub-array med 3 element läggs en array med bara de två yttre elementen till. det här är en genväg för det här specialfallet som förbättrar prestandan och som inte absolut behövs .

  • För varje sub-array med mer än 3 element tar jag de yttre två elementen och fyll i med alla kombinationer av inre element byggd av samma funktion rekursivt .



  1. Hur installerar jag kommandoraden MySQL-klient på mac?

  2. Migrera en Oracle-databas till MySQL på AWS, del 1

  3. PostgreSQL gener_series() med SQL-funktion som argument

  4. Rails:FATAL - Peer-autentisering misslyckades för användaren (PG::Error)