【问题标题】:How to eliminate cartesian product with joins while using subquery?如何在使用子查询时通过连接消除笛卡尔积?
【发布时间】:2014-08-24 16:57:32
【问题描述】:

我有以下数据库:

 paperid | authorid | name
---------+----------+---------------
 1889374 |   897449 | D. N. Page
 1889374 |  1795881 | C. N. Pope
 1889374 |  1952069 | S. W. Hawking

我想创建一个包含以下列的表格:

  • paperid
  • 作者姓名 - 此论文的每位作者 ID
  • 合著者 - 该论文的每位合著者

结果应该是这样的:

 paperid |    author     |          coauthors          
---------+---------------+---------------------------
 1889374 | D. N. Page    |  C. N. Pope S. W. Hawking
 1889374 | C. N. Pope    | D. N. Page  S. W. Hawking
 1889374 | S. W. Hawking | D. N. Page C. N. Pope 

这是通过以下查询实现的:

SELECT  foo.paperid, npa.name as author, foo.coauthors
INTO npatest
FROM newpaperauthor npa
CROSS JOIN (
   SELECT paperid, string_agg(name, ' ') as coauthors
   FROM newpaperauthor
   GROUP BY paperid
   ORDER BY paperid) foo;
UPDATE npatest SET coauthors = regexp_replace(coauthors, author, '');
SELECT * FROM npatest;

当数据库中有更多paperids 时出现问题:

 paperid | authorid |       name       |      affiliation       
---------+----------+------------------+------------------------
 1889373 |   122817 | Kazuhiro Hongo   | 
 1889373 |  1091191 | Hiroshi NAKAGAWA | 
 1889373 |  1874415 | Hiroshi Nakagawa | University of Oklahoma
 1889373 |  2149773 | Han Soo Chang    | 
 1889374 |   897449 | D. N. Page       | 
 1889374 |  1795881 | C. N. Pope       | 
 1889374 |  1952069 | S. W. Hawking    | 

然后我将得到它们的笛卡尔积,例如:

 paperid |      author      |                           coauthors                            
---------+------------------+----------------------------------------------------------------
 1889373 | Kazuhiro Hongo   |  Hiroshi NAKAGAWA Hiroshi Nakagawa Han Soo Chang
 1889374 | Kazuhiro Hongo   | D. N. Page C. N. Pope S. W. Hawking
 1889373 | Hiroshi NAKAGAWA | Kazuhiro Hongo  Hiroshi Nakagawa Han Soo Chang
 1889374 | Hiroshi NAKAGAWA | D. N. Page C. N. Pope S. W. Hawking
 1889373 | Hiroshi Nakagawa | Kazuhiro Hongo Hiroshi NAKAGAWA  Han Soo Chang
 1889374 | Hiroshi Nakagawa | D. N. Page C. N. Pope S. W. Hawking
 1889373 | Han Soo Chang    | Kazuhiro Hongo Hiroshi NAKAGAWA Hiroshi Nakagawa 
 1889374 | Han Soo Chang    | D. N. Page C. N. Pope S. W. Hawking
 1889373 | D. N. Page       | Kazuhiro Hongo Hiroshi NAKAGAWA Hiroshi Nakagawa Han Soo Chang
 1889374 | D. N. Page       |  C. N. Pope S. W. Hawking
 1889373 | C. N. Pope       | Kazuhiro Hongo Hiroshi NAKAGAWA Hiroshi Nakagawa Han Soo Chang
 1889374 | C. N. Pope       | D. N. Page  S. W. Hawking
 1889373 | S. W. Hawking    | Kazuhiro Hongo Hiroshi NAKAGAWA Hiroshi Nakagawa Han Soo Chang
 1889374 | S. W. Hawking    | D. N. Page C. N. Pope 

如何摆脱那里的笛卡尔积?

【问题讨论】:

  • 您想以任何特定方式对共同作者进行排序吗?
  • @ErwinBrandstetter 不是真的,对我来说没关系。

标签: sql postgresql join postgresql-9.3 cartesian-product


【解决方案1】:

使用array_agg() 作为窗口聚合函数并结合array_remove()(在 pg 9.3 中引入)可以非常简单

CREATE TABLE npatest AS
SELECT paperid, name AS author
     , array_to_string(array_remove(array_agg(name) OVER (PARTITION BY paperid), name), ', ') AS coauthors
FROM   newpaperauthor n;

如果作者姓名不是唯一的,则会出现复杂情况。
再说一次,如果作者姓名不是唯一的,那么你的整个操作就有缺陷。

使用array_agg()array_remove() 而不是string_agg()regexp_replace(),因为后者对于类似名称(如“Jon Fox”和“Jon Foxy”)很容易失败,而且分隔符也很乱。

array_to_string() 将数组转换为字符串。我使用', ' 作为分隔符,这对我来说似乎比空格更合理。

不鼓励使用SELECT INTO。请改用上级CREATE TABLE ASPer documentation:

CREATE TABLE AS 是推荐的语法,因为这种形式的 SELECT INTO 在 ECPG 或 PL/pgSQL 中不可用,因为它们 以不同的方式解释 INTO 子句。此外,CREATE TABLE AS 提供SELECT INTO 提供的功能的超集。

SQL Fiddle.

【讨论】:

  • 您的查询非常简单,但它以 JSON 数组格式返回共同作者,例如:1 | Mahmoud Refaat | {"Motaz Ahmad El-saban","Ayman Kaheel","Ahmed Abdul-hamid"}
  • @Patryk:这不是 JSON 格式,而是数组的 Postgres 文本表示。我留下了如何使用array_to_string() 转换为文本的说明。现在拼出来了。
【解决方案2】:

@GordonLinoff 的查询可以通过抑制聚合中的第一作者来简化一点:

SELECT DISTINCT
        p0.paperid , p0.authorid , p0.name as name1
        , string_agg(p1.name, ', ' ) AS others
FROM papers p0
JOIN papers p1 ON p1.paperid = p0.paperid AND p1.authorid <> p0.authorid
GROUP BY p0.paperid, p0.authorid, p0.name
ORDER BY p0.paperid, p0.authorid
        ;

【讨论】:

    【解决方案3】:

    这是解决这个问题的一种方法:

    生成所有共同作者的列表作为子查询。生成所有作者的列表。然后将它们连接在一起并进行字符串操作以获得您想要的。


    作者很简单:

    select paperid, npa.name as author
    from newpaperauthor npa;
    

    合著者很容易:

    select paperid, string_agg(npa.name, ' ') as coauthors
    from newpaperauthor npa
    group by paperid;
    

    组合需要一些列表替换:

    select a.paperid, a.author,
           replace(replace(coauthors, author, ''), '  ', ' ') as coauthors
    from (select paperid, npa.name as author
          from newpaperauthor npa
         ) a join
         (select paperid, string_agg(npa.name, ' ') as coauthors
          from newpaperauthor npa
          group by paperid
         ) ca
         on a.paperid = ca.paperid;
    

    【讨论】:

    • 我不得不将最后一行更改为on a.paperid = ca.paperid;,因为它给了我argument of JOIN/ON must be type boolean, not type bigint。谢谢:)
    • @Patryk 。 . .那是一个错字。感谢您修复它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 2015-09-18
    • 2017-02-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多