【问题标题】:Select just one row for equal parameters and all rows if they differ为相等的参数只选择一行,如果它们不同,则选择所有行
【发布时间】:2022-01-22 20:48:09
【问题描述】:

我有一个数据库表:

| id | account | extra_account | state |special_value

我需要选择extra_accounts,它们与帐户列表相关联。

SELECT * FROM table
WHERE table.account in (111, 222, 333) and table.state = 'WORKS';
|id  | account         | extra_account     | state         |special_value
—-------------------------------------------------------------------------------
|100 |111              |111-1              |WORKS          |1
|200 |111              |111-2              |WORKS          |1
|300 |222              |222-1              |WORKS          |2
|400 |333              |333-1              |WORKS          |3
|500 |333              |333-2              |WORKS          |4

我必须将 extra_accounts 连接到一个用逗号分隔的字符串中。

如果一个帐户有两个或多个 extra_accounts 并且它们的 special_value 相同,我必须使用一个 extra_accounts,无论哪个。 所以对于id=100id=200,我只需要使用一个extra_account - 111-1 或111-2,因为它们的special_value 是相等的。

如果一个帐户有两个或多个 extra_accounts 并且它们的 special_value 不同,我必须全部使用它们。 所以对于id=400id=500,我需要 - 333-1 和 333-2,因为它们的 special_values 是 3 和 4。

最终结果应该是:

|string_agg
|text
—----
|111-1, 222-1, 333-1, 333-2

我知道我可以使用以下方法连接值:

SELECT  string_agg(table.extra_account, ', ')
FROM table WHERE table.account in (111, 222, 333) and table.state = 'WORKS';

但我没有找到一种方法来选择如果 special_values 不同的所有行,如果 special_values 相等则只选择一行。

【问题讨论】:

标签: sql postgresql aggregate


【解决方案1】:

这就是您想要的每个帐户:

SELECT account
     , CASE WHEN count(DISTINCT special_value) = 1
            THEN min(extra_account)
            ELSE string_agg(extra_account, ', ')
            END AS special_values
FROM   tbl
WHERE  account IN (111, 222, 333)
AND    state = 'WORKS'
GROUP  BY 1;

将上述内容包装在子查询中并聚合:

SELECT string_agg(special_values, ', ')
FROM (
   SELECT account
        , CASE WHEN count(DISTINCT special_value) = 1
               THEN min(extra_account)
               ELSE string_agg(extra_account, ', ')
               END AS special_values
   FROM   tbl
   WHERE  account IN (111, 222, 333)
   AND    state = 'WORKS'
   GROUP  BY 1
   ) sub;

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

如果你真的想要一个 extra_account 每个不同的 special_value(不像表达的那样):

SELECT string_agg(extra_account, ', ')
FROM  (SELECT DISTINCT ON (account, special_value) extra_account FROM tbl) sub;

dbfiddle here - 添加一行来显示差异

关于DISTINCT ON

(以及为什么是typically faster。)

【讨论】:

    【解决方案2】:

    如果您计算 ROW_NUMBER,那么您可以根据 account & special_value 取 1。

    SELECT 
     STRING_AGG(DISTINCT extra_account, ', ' ORDER BY extra_account) AS extra_accounts
    FROM (
      SELECT account, extra_account, special_value
      , ROW_NUMBER() OVER (PARTITION BY account, special_value ORDER BY id) AS rownum
      FROM "table" t
      WHERE account IN (111, 222, 333) 
        AND state = 'WORKS' 
    ) q
    WHERE rownum = 1;
    
    extra_accounts
    111-1, 222-1, 333-1, 333-2

    dbfiddle here

    上的演示

    【讨论】:

    • 聪明。但对于具有 一些 但并非所有重复特殊值的帐户,它会失败。请参阅:dbfiddle here(除非 OP 没有正确指定...)
    • @ErwinBrandstetter 好吧,我们将看看 nikiforov 对此有何看法。但是不应该采用额外的 special_value 4,因为无论如何我都理解这个问题。
    【解决方案3】:

    我会通过 account 和 special_value 列上的聚合函数 min 来做到这一点。

    With CTE As (
    Select account, special_value, Min(extra_account) As v
    From Tbl
    Where account In (111, 222, 333) And state = 'WORKS'
    Group by account, special_value
    )
    Select string_agg(v, ',' Order by v)
    From CTE
    

    数据输出:

    string_agg
    111-1,222-1,333-1,333-2

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-06
      • 1970-01-01
      • 2017-04-11
      • 1970-01-01
      • 1970-01-01
      • 2016-12-03
      • 1970-01-01
      • 2012-06-10
      相关资源
      最近更新 更多