Postgresql

如何計算 SQL 中輸入字元串的所有分組排列?

  • November 29, 2021

給定一個像“ABC”這樣的輸入,生成一個查詢,計算給定字元串的 0 個或多個潛在拆分,

所需的輸出,

A   B   C
A   BC
AB  C
ABC

給定一個像“ABCD”這樣的輸入

A    B   C    D
A    BC  D
A    B   CD
AB   C   D
A    BCD
AB   CD
ABC  D
ABCD

並不是所有與輸出的形成方式、數組、行、json 等有關。更多的是尋找所有分組排列的離散列表。

我想這只是一個有趣的挑戰,但這是我的解決方案:

WITH s(s) AS (VALUES ('ABCD'))
SELECT substr(s, 1, 1) ||
      string_agg(
         CASE WHEN i & (2::numeric ^ p)::bigint = 0 THEN '' ELSE ' ' END ||
         substr(s, p + 2, 1),
         ''
      )
FROM s
  CROSS JOIN generate_series(0, (2::numeric ^ (length(s) - 1) - 1)::bigint) AS i
  CROSS JOIN generate_series(0, length(s) - 2) AS p
GROUP BY s, i;

?column? 
══════════
A BC D
AB C D
ABCD
ABC D
A B CD
AB CD
A B C D
A BCD
(8 rows)

這個想法是讓二進制數從 0 到 2 ^ (length - 1) - 1 並在有 1 的地方插入空格。所以 101(十進制 5)將變為A BC D.

二項式問題可以轉化為這個簡單的虛擬碼算法:

take the first letter
while more letters, loop
  make two copies, one with trailing space
  append next letter
end loop

遞歸函式

這可以用任何體面的過程語言用遞歸函式優雅地實現。使用 PL/pgSQL:

CREATE OR REPLACE FUNCTION word_permutations(_word text)
 RETURNS SETOF text
 LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE STRICT AS
$func$
BEGIN
  IF length(_word) > 1 THEN
     RETURN QUERY
     SELECT left(_word, 1) || s || w
     FROM  (VALUES (''), (' ')) sep(s)
          , word_permutations(right(_word, -1)) w;
  ELSE
     RETURN NEXT _word;
  END IF;
END
$func$;

稱呼:

SELECT word_permutations('ABCD');

在我的測試中表現最好。~ 比 Laurenz 查詢快 50 倍,但仍然比以下 rCTE 快 2-3 倍(有或沒有函式包裝器)。

帶有 rCTE 的純 SQL

由於這是 dba.SE,一個具有遞歸 CTE 的純 SQL 解決方案:

WITH RECURSIVE
  val(w) AS (SELECT 'ABCD')      -- input
, sep(s) AS (VALUES (''), (' '))
, cte AS (
  SELECT LEFT(w, 1) AS perm, right(w, -1) AS rest FROM val
  UNION ALL
  SELECT perm || s || LEFT(rest, 1), right(rest, -1)
  FROM   cte, sep
  WHERE  rest <> ''
  )
SELECT perm FROM cte WHERE rest = '';

結果相同:

db<>fiddle here(添加了性能測試)

引用自:https://dba.stackexchange.com/questions/302389