PostgreSQL:通过LATERAL join相关的值的总和

时间:2018-03-07 07:38:30

标签: postgresql lateral-join

我正在尝试清理PostgreSQL表中的数据,其中一些记录在email_address列中有大量亵渎(有问题的记录由于沮丧到期而被激动的用户输入到一个已修复的错误):

    ┌───────────────────┐
    │   email_address   │
    ├───────────────────┤
    │ foo@go.bar.me.net │
    │ foo@foo.com       │
    │ foo@example.com   │
    │ baz@example.com   │
    │ barred@qux.com    │
    └───────────────────┘

所需的查询输出

我想构建一个查询,用亵渎评分从数据表中注释每一行,并按分数对记录进行排序,以便人类可以浏览带注释的数据(在Web应用程序中显示)和采取必要的行动:

    ┌───────────────────┬───────┐
    │ email_address     │ score │
    ├───────────────────┼───────┤
    │ foo@foo.com       │    18 │
    │ foo@go.bar.me.net │    14 │
    │ foo@example.com   │     9 │
    │ baz@example.com   │     3 │
    │ barred@qux.com    │     0 │
    └───────────────────┴───────┘

尝试#1

我正在采取的方法是建立正则表达式列表(现在我有2个问题......)和分数,如果在email_address列中找到该单词,那么非常亵渎的单词将会产生大量的亵渎分数。我的profanities表看起来像这样:

    ┌──────────────────┬───────┐
    │ profanity_regexp │ score │
    ├──────────────────┼───────┤
    │ foo              │     9 │
    │ bar(?!red)       │     5 │
    │ baz              │     3 │
    └──────────────────┴───────┘

LATERAL JOIN

我发现我可以在LATERAL函数上使用regexp_matches联接从每个email_address中提取所有亵渎(但不丢弃任何亵渎的记录):

SELECT
    data.email_address,
    array_agg(matches)
FROM
    data,
    profanities p,
    LATERAL regexp_matches(data.email_address, p.posix_regexp, 'gi') matches
GROUP BY
    data.email_address;

这会产生以下结果:

    ┌───────────────────┬───────────────────┐
    │   email_address   │ profanities_found │
    ├───────────────────┼───────────────────┤
    │ foo@foo.com       │ {{foo},{foo}}     │
    │ foo@example.com   │ {{foo}}           │
    │ foo@go.bar.me.net │ {{foo},{bar}}     │
    │ baz@example.com   │ {{baz}}           │
    └───────────────────┴───────────────────┘

SUB-SELECT

我还想出了如何使用此SQL为每条记录获取一系列亵渎评分小计:

SELECT
    data.email_address,
    array(
        SELECT score * ( 
            SELECT COUNT(*)
            FROM (SELECT
                regexp_matches(data.email_address, p.posix_regexp, 'gi')
            ) matches
        )
        FROM profanities p
    ) prof
from data;

正确地产生所有行(包括没有亵渎的行):

    ┌───────────────────┬──────────┐
    │   email_address   │   prof   │
    ├───────────────────┼──────────┤
    │ foo@go.bar.me.net │ {9,5,0}  │
    │ foo@foo.com       │ {18,0,0} │
    │ foo@example.com   │ {9,0,0}  │
    │ baz@example.com   │ {0,0,3}  │
    │ barred@qux.com    │ {0,0,0}  │
    └───────────────────┴──────────┘

问题

如何对横向连接的结果求和以获得所需的输出?

我可以使用另一种策略来获得所需的结果吗?

我在http://sqlfiddle.com/#!17/6685c/4

发布了这个问题的实时代码小提琴

3 个答案:

答案 0 :(得分:1)

在查询中添加其他选择。当前查询很好,但您只需要对数组求和。

SELECT email_address,
(
    SELECT SUM(s)
    FROM
        UNNEST(prof.profanity_score_subtotals) s
) AS sum_prof FROM (
    SELECT
        data.email_address,
        array(
            SELECT score * ( 
                SELECT COUNT(*)
                FROM (SELECT
                    regexp_matches(data.email_address, p.profanity_regexp, 'gi')
                ) matches
            )
            FROM profanities p
        ) profanity_score_subtotals
    FROM data
) prof;

答案 1 :(得分:1)

由于某种原因,postgres不允许你使用set-returns函数作为where子句的一部分,所以你需要做两个横向连接:

SELECT
    data.email_address,
    t.score
FROM
    data,
    LATERAL (
        SELECT
            coalesce(sum(s.score), 0) AS score
        FROM
            profanities,
            LATERAL (
                SELECT
                    profanities.score * array_length(
                        regexp_matches(
                            data.email_address,
                            profanities.profanity_regexp,
                            'gi'
                        ),
                        1
                    ) score
            ) s
    ) t;

答案 2 :(得分:0)

我之前已经接受了@daurnimator的答案,但后来发现额外的LATERAL加入并不是必需的。这是我最终在我的应用中使用的内容:

SELECT
    data.email_address,
    (
        SELECT
            coalesce(sum(s.score), 0) AS score
        FROM
            profanities,
            LATERAL (
                SELECT
                    profanities.score * array_length(
                        regexp_matches(
                            data.email_address,
                            profanities.profanity_regexp,
                            'gi'
                        ),
                        1
                    ) score
            ) s
    ) AS score
FROM
    data;

事实证明我的版本是slightly faster,因为它避免了查询中的nested loop。另一个优点是它可以在我的应用程序中用作Django的RawSQL函数的注释,然后允许我order_by('-score')并首先显示最亵渎的条目。