【问题标题】:SQL: Update with all possible combinationsSQL:使用所有可能的组合进行更新
【发布时间】:2020-04-08 17:11:37
【问题描述】:

我有关系

+-----+----+
| seq | id |
+-----+----+
|   1 | A1 |
|   2 | B1 |
|   3 | C1 |
|   4 | D1 |
+-----+----+

并希望在 PostgreSQL 中加入

+----+-------+
| id | alter |
+----+-------+
| B1 | B2    |
| D1 | D2    |
+----+-------+

所以我得到了所有可能的替换组合(即更多或更少替换的笛卡尔积)。所以第 1 组没有更新,第 2 组只有 B2,第 3 组只有 D2,第 4 组同时有 B2 和 D2。

结尾应该是这样的,但应该对更多人开放(比如 D1 的额外 D3)

+-------+-----+----+
| group | seq | id |
+-------+-----+----+
|     1 |   1 | A1 |
|     1 |   2 | B1 |
|     1 |   3 | C1 |
|     1 |   4 | D1 |
|     2 |   1 | A1 |
|     2 |   2 | B2 |
|     2 |   3 | C1 |
|     2 |   4 | D1 |
|     3 |   1 | A1 |
|     3 |   2 | B1 |
|     3 |   3 | C1 |
|     3 |   4 | D2 |
|     4 |   1 | A1 |
|     4 |   2 | B2 |
|     4 |   3 | C1 |
|     4 |   4 | D2 |
+-------+-----+----+

编辑:

另一个可能的替换表可能是

+----+-------+
| id | alter |
+----+-------+
| B1 | B2    |
| D1 | D2    |
| D1 | D3    |
+----+-------+

应该可以分成6组(希望没有忘记一个案例)

+-------+-----+----+
| group | seq | id |
+-------+-----+----+
|     1 |   1 | A1 |
|     1 |   2 | B1 |
|     1 |   3 | C1 |
|     1 |   4 | D1 |
|     2 |   1 | A1 |
|     2 |   2 | B2 |
|     2 |   3 | C1 |
|     2 |   4 | D1 |
|     3 |   1 | A1 |
|     3 |   2 | B2 |
|     3 |   3 | C1 |
|     3 |   4 | D2 |
|     4 |   1 | A1 |
|     4 |   2 | B2 |
|     4 |   3 | C1 |
|     4 |   4 | D3 |
|     5 |   1 | A1 |
|     5 |   2 | B1 |
|     5 |   3 | C1 |
|     5 |   4 | D2 |
|     6 |   1 | A1 |
|     6 |   2 | B1 |
|     6 |   3 | C1 |
|     6 |   4 | D3 |
+-------+-----+----+

如果你有三个替代品,比如

+----+-------+
| id | alter |
+----+-------+
| B1 | B2    |
| C1 | C2    |
| D1 | D3    |
+----+-------+

它将产生 8 个组。 到目前为止我尝试过的并没有什么帮助:


WITH a as (SELECT * FROM (values (1,'A1'),(2,'B1'), (3,'C1'), (4,'D1')   ) as a1(seq, id) )
, b as (SELECT * FROM (values ('B1','B2'), ('D1','D2')) as b1(id,alter) )
---------
SELECT row_number() OVER (PARTITION BY a.id) as g, * FROM 
a
CROSS JOIN  b as b1
CROSS JOIN  b as b2
LEFT JOIN b as b3 ON a.id=b3.id
ORDER by g,seq;

我很高兴为标题提供更好的建议。

【问题讨论】:

  • 这是针对这两个替换的吗?或者第二张桌子可以有超过 2 个吗?如果是这样,如果多次替换相同的值怎么办?
  • 嗨,戈登,当然可以多两个(我的意思是“像 D1 的额外 D3”):例如,D1 也将被额外的 D3 替换。我猜这将导致 46 行和 16 个组。等等……
  • (what I meant with "like an extra D3 for D1"): So for example D1 would also be replaced by extra D3. This would result in 46 rows with 16 groups I guess "也将被替换" 留有解释空间; “46 行 16 组”的数字也没有点燃我的热情。你能再解释一下吗? (请在问题中澄清!)
  • 您想要CROSS JOINs 的动态数字...有趣。 +1
  • 组合的数量呈指数增长。表 b 中的行数不能超过 20 或 30 行。

标签: sql postgresql routes combinatorics variations


【解决方案1】:

所以第 1 组没有更新,第 2 组只有 B2,第 3 组只有 D2 和第 4 组 B2 和 D2。

由于该语句的逻辑不在表中,因此我决定将此逻辑添加到表 c 中,即在现有表 a 中添加 3 个新列,具体取决于必须考虑的字段选择。

WITH a as (SELECT * FROM (values (1,'A1'),(2,'B1'), (3,'C1'), (4,'D1')   ) as a1(seq, id) )
, b as (SELECT * FROM (values ('B1','B2'), ('D1','D2')) as b1(id,alter) )
, c as (
SELECT a.seq, a.id,
COALESCE(b1.alter,a.id) as id2,
COALESCE(b2.alter,a.id) as id3,
COALESCE(b3.alter,a.id) as id4
FROM a
LEFT JOIN (SELECT * FROM b WHERE b.alter='B2') b1 ON a.id = b1.id
LEFT JOIN (SELECT * FROM b WHERE b.alter='D2') b2 ON a.id = b2.id
LEFT JOIN (SELECT * FROM b WHERE b.alter IN ('B2','D2')) b3 ON a.id = b3.id)
, d as (SELECT * FROM (values (1),(2), (3), (4)   ) as d1(gr) )



SELECT d.gr,
CASE d.gr
   WHEN 1 THEN c.id
   WHEN 2 THEN c.id2
   WHEN 3 THEN c.id3
   WHEN 4 THEN c.id4 END as id

FROM d
CROSS JOIN  c
ORDER by d.gr, c.seq

【讨论】:

    【解决方案2】:

    你需要什么

    从您的 cmets 获得更多信息后,您的情况似乎是这样:

    您的收费站具有给定数量的摊位:

    CREATE TABLE station (
      station text PRIMARY KEY
    , booths  int NOT NULL  -- number of cashiers in station
    );
    INSERT INTO station VALUES 
      ('A', 1)
    , ('B', 2)
    , ('C', 1)
    , ('D', 3);
    

    对于给定的路线,说 A --> B --> C --> D 您想要生成所有可能的路径,同时考虑展位号。我建议使用recursive CTE 的 SQL 函数,例如:

    CREATE OR REPLACE FUNCTION f_pathfinder(_route text[])
      RETURNS TABLE (grp int, path text[]) LANGUAGE sql STABLE PARALLEL SAFE AS
    $func$
    WITH RECURSIVE rcte AS (
       SELECT cardinality($1) AS hops, 1 AS hop, ARRAY[s.station || booth] AS path
       FROM   station s, generate_series(1, s.booths) booth
       WHERE  s.station = $1[1]
    
       UNION ALL
       SELECT r.hops, r.hop + 1, r.path || (s.station || booth)
       FROM   rcte  r
       JOIN   station s ON s.station = _route[r.hop + 1], generate_series(1, s.booths) booth
       WHERE  r.hop < r.hops
       )
    SELECT row_number() OVER ()::int AS grp, path
    FROM   rcte r
    WHERE  r.hop = r.hops;
    $func$;
    

    简单的调用:

    SELECT * FROM f_pathfinder('{A,B,C,D}'::text[]);
    

    结果:

    grp |小路 ---: | :-------- 1 | {1,1,1,1} 2 | {1,1,1,2} 3 | {1,1,1,3} 4 | {1,2,1,1} 5 | {1,2,1,2} 6 | {1,2,1,3}

    或者使用未嵌套的数组(结果就像您在问题中显示的那样):

    SELECT grp, seq, booth
    FROM   f_pathfinder('{A,B,C,D}'::text[])
         , unnest(path) WITH ORDINALITY AS x(booth, seq);  -- ①
    

    结果:

    grp |序列 |展位 --: | --: | :---- 1 | 1 | A1 1 | 2 | B1 1 | 3 | C1 1 | 4 | D1 2 | 1 | A1 2 | 2 | B1 2 | 3 | C1 2 | 4 | D2 3 | 1 | A1 3 | 2 | B1 3 | 3 | C1 3 | 4 | D3 4 | 1 | A1 4 | 2 | B2 4 | 3 | C1 4 | 4 | D1 5 | 1 | A1 5 | 2 | B2 5 | 3 | C1 5 | 4 | D2 6 | 1 | A1 6 | 2 | B2 6 | 3 | C1 6 | 4 | D3

    db小提琴here

    变体的数量随着您路线中停靠点的数量而快速增长。 M1*M2* .. *Mn 其中Mn是第n站的摊位数。

    ①关于ORDINALITY

    你问了什么(最初)

    您似乎想将替换表rpl 中列出的更改集合中的所有可能的组合应用到目标表tbl

    只有两行,形成 4 (2^n) 种可能的组合很简单。对于一般解决方案,我建议使用基本组合函数来生成所有组合。有无数种方式。这是一个纯SQL函数:

    CREATE OR REPLACE FUNCTION f_allcombos(_len int)
      RETURNS SETOF bool[] LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
    $func$
    WITH RECURSIVE
       tf(b) AS (VALUES (false), (true))
    
     , rcte AS (
       SELECT 1 AS lvl, ARRAY[b] AS arr
       FROM   tf
    
       UNION ALL
       SELECT r.lvl + 1, r.arr || tf.b
       FROM   rcte r, tf 
       WHERE  lvl < _len
       )
    SELECT arr
    FROM   rcte
    WHERE  lvl = _len;
    $func$;
    

    类似于这里讨论的内容:

    仅 2 个替换行的示例:

    SELECT * FROM f_allcombos(2);
    
    {f,f} {t,f} {f,t} {t,t}

    查询

    WITH effective_rpl AS (  -- ①
       SELECT *, count(alter) OVER (ORDER BY seq) AS idx  -- ②
       FROM   tbl LEFT JOIN rpl USING (id)
       )
    SELECT c.grp, e.seq
         , CASE WHEN alter IS NOT NULL AND c.arr[e.idx] THEN e.alter  -- ③
                ELSE e.id END AS id
    FROM   effective_rpl e
         , f_allcombos((SELECT count(alter)::int FROM effective_rpl))  -- ④
              WITH ORDINALITY AS c(arr, grp); -- ⑤
    

    准确地产生您想要的结果。

    db小提琴here

    ① 部分替换可能在目标表中不匹配;所以首先要确定有效的替代品。

    count() 只计算非空值,因此这可以作为从f_allcombos() 返回的从 1 开始的数组的索引。

    ③ 仅当替换可用时才替换,并且布尔数组对于给定索引idx 具有true

    CROSS JOIN 将目标表中的行集乘以可能的替换组合数

    ⑤我使用WITH ORDINALITY生成“组号”。见:

    我们可以将它直接连接到函数中,但我宁愿保持通用性。

    另外:“alter”在 Postgres 中是非保留的,但在标准 SQL 中是 reserved word

    【讨论】:

    • 您好欧文,感谢您的回答。它似乎适用于我提供的表格。如果我为 D1 添加另一个第三个可能的替代品(比如 D3),它不会产生预期的结果。我应该更清楚地指出这个案例。
    • @sequoia:你似乎有你的答案,但我有更多的想法来改进解决方案。你可能会感兴趣。
    【解决方案3】:

    编辑问题后更新答案

    这个问题的棘手部分是生成替换的幂集。然而,幸运的是,postgres 支持递归查询和 powersets 可以递归计算。因此,我们可以为这个问题构建一个通用的解决方案,无论替换集的大小如何,它都可以工作。

    让我们将第一张桌子称为source,将第二张桌子称为replacements,我将避免使用令人讨厌的名称alter

    CREATE TABLE source (seq, id) as (
      VALUES (1, 'A1'), (2, 'B1'), (3, 'C1'), (4, 'D1')
    );
    CREATE TABLE replacements (id, sub) as (
      VALUES ('B1', 'B2'), ('D1', 'D2')
    );
    

    需要生成要替换的 id 的第一个 powerset。可以省略空集,因为无论如何它都不适用于连接,并且在最后,source 表可以union'ed 到中间结果以提供相同的输出。

    在递归步骤中,JOIN 条件rec.id &gt; repl.id 确保每个id 对于每个生成的子集仅出现一次。

    在最后一步:

    交叉连接扇出源N次,其中N是替换的非空组合的数量(有变化)

    组名称是使用seq 上的过滤运行总和生成的。

    如果替换 id 等于源 id,则子集是未嵌套的,并且使用合并替换 id。

    WITH RECURSIVE rec AS (
      SELECT ARRAY[(id, sub)] subset, id FROM replacements
      UNION ALL
      SELECT subset || (repl.id, sub), repl.id 
      FROM replacements repl 
      JOIN rec ON rec.id > repl.id
    )
    SELECT NULL subset, 0 set_name, seq, id FROM source
    UNION ALL
    SELECT subset
    , SUM(seq) FILTER (WHERE seq = 1) OVER (ORDER BY subset, seq) set_name 
    , seq
    , COALESCE(sub, source.id) id
    FROM rec 
    CROSS JOIN source
    LEFT JOIN LATERAL (
      SELECT id, sub 
      FROM unnest(subset) x(id TEXT, sub TEXT)
      ) x ON source.id = x.id;
    

    测试

    使用替换值('B1', 'B2'), ('D1', 'D2'),查询返回4组。

            subset         | set_name | seq | id 
    -----------------------+----------+-----+----
                           |        0 |   1 | A1
                           |        0 |   2 | B1
                           |        0 |   3 | C1
                           |        0 |   4 | D1
     {"(B1,B2)"}           |        1 |   1 | A1
     {"(B1,B2)"}           |        1 |   2 | B2
     {"(B1,B2)"}           |        1 |   3 | C1
     {"(B1,B2)"}           |        1 |   4 | D1
     {"(D1,D2)"}           |        2 |   1 | A1
     {"(D1,D2)"}           |        2 |   2 | B1
     {"(D1,D2)"}           |        2 |   3 | C1
     {"(D1,D2)"}           |        2 |   4 | D2
     {"(D1,D2)","(B1,B2)"} |        3 |   1 | A1
     {"(D1,D2)","(B1,B2)"} |        3 |   2 | B2
     {"(D1,D2)","(B1,B2)"} |        3 |   3 | C1
     {"(D1,D2)","(B1,B2)"} |        3 |   4 | D2
    (16 rows)
    

    用替换值('B1', 'B2'), ('D1', 'D2'), ('D1', 'D3'),查询返回6组:

            subset         | set_name | seq | id 
    -----------------------+----------+-----+----
                           |        0 |   1 | A1
                           |        0 |   2 | B1
                           |        0 |   3 | C1
                           |        0 |   4 | D1
     {"(B1,B2)"}           |        1 |   1 | A1
     {"(B1,B2)"}           |        1 |   2 | B2
     {"(B1,B2)"}           |        1 |   3 | C1
     {"(B1,B2)"}           |        1 |   4 | D1
     {"(D1,D2)"}           |        2 |   1 | A1
     {"(D1,D2)"}           |        2 |   2 | B1
     {"(D1,D2)"}           |        2 |   3 | C1
     {"(D1,D2)"}           |        2 |   4 | D2
     {"(D1,D2)","(B1,B2)"} |        3 |   1 | A1
     {"(D1,D2)","(B1,B2)"} |        3 |   2 | B2
     {"(D1,D2)","(B1,B2)"} |        3 |   3 | C1
     {"(D1,D2)","(B1,B2)"} |        3 |   4 | D2
     {"(D1,D3)"}           |        4 |   1 | A1
     {"(D1,D3)"}           |        4 |   2 | B1
     {"(D1,D3)"}           |        4 |   3 | C1
     {"(D1,D3)"}           |        4 |   4 | D3
     {"(D1,D3)","(B1,B2)"} |        5 |   1 | A1
     {"(D1,D3)","(B1,B2)"} |        5 |   2 | B2
     {"(D1,D3)","(B1,B2)"} |        5 |   3 | C1
     {"(D1,D3)","(B1,B2)"} |        5 |   4 | D3
    (24 rows)
    

    用替换值('B1', 'B2'), ('C1', 'C2'), ('D1', 'D2'),查询返回8组:

                 subset              | set_name | seq | id 
    ---------------------------------+----------+-----+----
                                     |        0 |   1 | A1
                                     |        0 |   2 | B1
                                     |        0 |   3 | C1
                                     |        0 |   4 | D1
     {"(B1,B2)"}                     |        1 |   1 | A1
     {"(B1,B2)"}                     |        1 |   2 | B2
     {"(B1,B2)"}                     |        1 |   3 | C1
     {"(B1,B2)"}                     |        1 |   4 | D1
     {"(C1,C2)"}                     |        2 |   1 | A1
     {"(C1,C2)"}                     |        2 |   2 | B1
     {"(C1,C2)"}                     |        2 |   3 | C2
     {"(C1,C2)"}                     |        2 |   4 | D1
     {"(C1,C2)","(B1,B2)"}           |        3 |   1 | A1
     {"(C1,C2)","(B1,B2)"}           |        3 |   2 | B2
     {"(C1,C2)","(B1,B2)"}           |        3 |   3 | C2
     {"(C1,C2)","(B1,B2)"}           |        3 |   4 | D1
     {"(D1,D2)"}                     |        4 |   1 | A1
     {"(D1,D2)"}                     |        4 |   2 | B1
     {"(D1,D2)"}                     |        4 |   3 | C1
     {"(D1,D2)"}                     |        4 |   4 | D2
     {"(D1,D2)","(B1,B2)"}           |        5 |   1 | A1
     {"(D1,D2)","(B1,B2)"}           |        5 |   2 | B2
     {"(D1,D2)","(B1,B2)"}           |        5 |   3 | C1
     {"(D1,D2)","(B1,B2)"}           |        5 |   4 | D2
     {"(D1,D2)","(C1,C2)"}           |        6 |   1 | A1
     {"(D1,D2)","(C1,C2)"}           |        6 |   2 | B1
     {"(D1,D2)","(C1,C2)"}           |        6 |   3 | C2
     {"(D1,D2)","(C1,C2)"}           |        6 |   4 | D2
     {"(D1,D2)","(C1,C2)","(B1,B2)"} |        7 |   1 | A1
     {"(D1,D2)","(C1,C2)","(B1,B2)"} |        7 |   2 | B2
     {"(D1,D2)","(C1,C2)","(B1,B2)"} |        7 |   3 | C2
     {"(D1,D2)","(C1,C2)","(B1,B2)"} |        7 |   4 | D2
    (32 rows)
    

    【讨论】:

    • 非常感谢,这很有帮助! subset 属性非常方便。
    【解决方案4】:

    我只能想到一种蛮力方法。枚举组并乘以第二个表——所以每个组都有一组行。

    下面然后使用位操作来选择哪个值:

    WITH a as (
          SELECT * FROM (values (1,'A1'),(2,'B1'), (3,'C1'), (4,'D1')   ) as a1(seq, id)
          ),
         b as (
          SELECT * FROM (values ('B1','B2'), ('D1','D2')) as b1(id,alter)
         ),
         bgroups as (
          SELECT b.*, grp - 1 as grp, ROW_NUMBER() OVER (PARTITION BY grp ORDER BY id) - 1 as seqnum
          FROM b CROSS JOIN
               GENERATE_SERIES(1, (SELECT POWER(2, COUNT(*))::int FROM b)) gs(grp)
         )
    SELECT bg.grp, a.seq, 
           COALESCE(MAX(CASE WHEN a.id = bg.id AND (POWER(2, bg.seqnum)::int & bg.grp) > 0 THEN bg.alter END),
                    MAX(a.id)
                   ) as id
    FROM a CROSS JOIN
         bgroups bg
    GROUP BY bg.grp, a.seq
    ORDER BY bg.grp, a.seq;
    

    Here 是一个 dbfiddle。

    【讨论】:

    • 我在理解您如何在这里使用位操作时遇到了一些麻烦,但我非常渴望了解更多信息。我能想到的唯一方法是递归地构建 powerset。您能否详细说明一下位操作的工作原理是这个解决方案吗
    • @HaleemurAli 。 . .您应该提出一个问题来阐明您真正想要更好地理解的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-20
    • 2015-11-10
    • 1970-01-01
    • 2018-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多