sql >> Database teknologi >  >> RDS >> PostgreSQL

Hvordan skriver man kombinatorisk funktion i postgres?

Efter at jeg havde sovet over det, fik jeg en helt ny, enklere og hurtigere 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 til:

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

512 rækker, samlet køretid:2.899 ms

Forklar

  • Behandle særlige tilfælde med NULL og tom matrix.
  • Byg kombinationer til en primitiv række af to.
  • Enhver længere matrix er opdelt i:
    • kombinationerne for samme array med længde n-1
    • plus alle dem kombineret med element n .. rekursivt .

Virkelig enkelt, når du først har fået det.

  • Fungerer for 1-dimensionelle heltalsarrays, der starter med subscript 1 (se nedenfor).
  • 2-3 gange så hurtigt som en gammel løsning, skalerer bedre.
  • Fungerer for alle elementtype igen (ved brug af polymorfe typer).
  • Inkluderer det tomme array i resultatet, som det vises i spørgsmålet (og som @Craig påpegede for mig i kommentarerne).
  • Kortere, mere elegant.

Dette forudsætter array-abonnementer starter ved 1 (Standard). Hvis du ikke er sikker på dine værdier, skal du kalde funktionen som denne for at normalisere:

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

Ikke sikker på, om der er en mere elegant måde at normalisere array-abonnementer på. Jeg stillede et spørgsmål om det:
Normaliser array-underskrifter for 1-dimensionelle array, så de starter med 1

Gammel løsning (langsommere)

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 til:

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

Fungerer til 1-dimensionelle heltalsarrays. Dette kunne optimeres yderligere, men det er bestemt ikke nødvendigt for dette spørgsmåls omfang.
ORDER BY for at pålægge den rækkefølge, der vises i spørgsmålet.

Angiv NULL eller tom matrix som NULL er nævnt i kommentarerne.

Testet med PostgreSQL 9.1, men burde fungere med enhver halvvejs moderne version.array_lower() og array_upper() har eksisteret i det mindste siden PostgreSQL 7.4. Kun parameterstandarder er nye i version 8.4. Kunne nemt udskiftes.

Ydeevnen er anstændig.

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

511 rækker, samlet køretid:7,729 ms

Forklaring

Den bygger på denne enkle form der kun skaber alle kombinationer af tilstødende elementer:

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 dette vil mislykkes for sub-arrays med mere end to elementer. Så:

  • For ethvert underarray med 3 elementer tilføjes et array med kun de ydre to elementer. dette er en genvej til dette specielle tilfælde, der forbedrer ydeevnen og er ikke strengt nødvendigt .

  • For enhver sub-array med mere end 3 elementer tager jeg de ydre to elementer og udfyld med alle kombinationer af indre elementer bygget af den samme funktion rekursivt .



  1. Hvordan samler man fødselsdato fra php-formular og indsætter i mysql?

  2. Hvordan kan jeg oprette en mappe via Oracle Form Builder?

  3. Hvordan forvandler man 2 forespørgsler med fælles kolonner (A, B) og (A, C) til kun én (A, B, C)?

  4. at vælge et specifikt tal som kolonneværdi i forespørgslen