如何使用GROUP BY子句将查询移植到PostgreSQL?

时间:2016-01-02 12:29:28

标签: sql postgresql sqlite group-by sql-view

我正在将一个简单的费用数据库移植到Postgres,并使用GROUP BY和多个JOIN子句卡在一个视图上。我认为Postgres希望我使用GROUP BY子句中的所有表格。

表定义在最后。请注意,account_idreceiving_account_idplace列可能是NULL,而operation可以有0个标签。

原始CREATE声明

CREATE VIEW details AS SELECT
    op.id,
    op.name,
    c.name,
    CASE --amountsign
        WHEN op.receiving_account_id IS NOT NULL THEN
            CASE
                WHEN op.account_id IS NULL THEN '+'
                ELSE '='
            END
        ELSE '-' 
    END || ' ' || printf("%.2f", op.amount) || ' zł' AS amount,
    CASE --account
        WHEN op.receiving_account_id IS NOT NULL THEN
            CASE
                WHEN op.account_id IS NULL THEN ac2.name
                ELSE ac.name || ' -> ' || ac2.name
            END
        ELSE ac.name
    END AS account,
    t.name AS type,
    CASE --date
        WHEN op.time IS NOT NULL THEN op.date || ' ' || op.time
        ELSE op.date
    END AS date,
    p.name AS place,
    GROUP_CONCAT(tag.name, ', ') AS tags
FROM operation op
LEFT JOIN category c ON op.category_id = c.id
LEFT JOIN type t ON op.type_id = t.id
LEFT JOIN account ac ON op.account_id = ac.id
LEFT JOIN account ac2 ON op.receiving_account_id = ac2.id
LEFT JOIN place p ON op.place_id = p.id
LEFT JOIN operation_tag ot ON op.id = ot.operation_id
LEFT JOIN tag ON ot.tag_id = tag.id
GROUP BY IFNULL (ot.operation_id, op.id)
ORDER BY date DESC

Postgres中的当前查询

我做了一些更新,我目前的陈述是:

BEGIN TRANSACTION;
CREATE VIEW details AS SELECT
    op.id,
    op.name,
    c.name,
    CASE --amountsign
        WHEN op.receiving_account_id IS NOT NULL THEN
            CASE
                WHEN op.account_id IS NULL THEN '+'
                ELSE '='
            END
        ELSE '-' 
    END || ' ' || op.amount || ' zł' AS amount,
    CASE --account
        WHEN op.receiving_account_id IS NOT NULL THEN
            CASE
                WHEN op.account_id IS NULL THEN ac2.name
                ELSE ac.name || ' -> ' || ac2.name
            END
        ELSE ac.name
    END AS account,
    t.name AS type,
    CASE --date
        WHEN op.time IS NOT NULL THEN to_char(op.date, 'DD.MM.YY') || ' ' || op.time
        ELSE to_char(op.date, 'DD.MM.YY')
    END AS date,
    p.name AS place,
    STRING_AGG(tag.name, ', ') AS tags
FROM operation op
LEFT JOIN category c ON op.category_id = c.id
LEFT JOIN type t ON op.type_id = t.id
LEFT JOIN account ac ON op.account_id = ac.id
LEFT JOIN account ac2 ON op.receiving_account_id = ac2.id
LEFT JOIN place p ON op.place_id = p.id
LEFT JOIN operation_tag ot ON op.id = ot.operation_id
LEFT JOIN tag ON ot.tag_id = tag.id
GROUP BY COALESCE (ot.operation_id, op.id)
ORDER BY date DESC;
COMMIT;

我添加列出的错误时出现Column 'x' must appear in GROUP BY clause错误:

GROUP BY COALESCE(ot.operation_id, op.id), op.id, c.name, ac2.name, ac.name, t.name, p.name

当我添加p.name列时,我得到Column 'p.name' is defined more than once error.如何解决此问题?

表定义

CREATE TABLE operation (
  id integer NOT NULL PRIMARY KEY,
  name character varying(64) NOT NULL,
  category_id integer NOT NULL,
  type_id integer NOT NULL,
  amount numeric(8,2) NOT NULL,
  date date NOT NULL,
  "time" time without time zone NOT NULL,
  place_id integer,
  account_id integer,
  receiving_account_id integer,
  CONSTRAINT categories_transactions FOREIGN KEY (category_id)
      REFERENCES category (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT transactions_accounts FOREIGN KEY (account_id)
      REFERENCES account (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT transactions_accounts_second FOREIGN KEY (receiving_account_id)
      REFERENCES account (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT transactions_places FOREIGN KEY (place_id)
      REFERENCES place (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT transactions_transaction_types FOREIGN KEY (type_id)
      REFERENCES type (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);

2 个答案:

答案 0 :(得分:3)

@Andomar already provided类似:大多数RDBMS都需要按照未显示的每一列进行分组 - 查询中的任何其他位置(包括SELECT列表,还包括WHERE子句等)

SQL标准还定义GROUP BY子句中的表达式还应涵盖依赖于功能的表达式。 Postgres实现了 PK列涵盖了同一个表的所有列

所以op.id涵盖整个表格,这应该适用于您当前的查询:

GROUP BY op.id, c.name, 5, t.name, p.name

5SELECT列表的位置参考,这在Postgres中也是允许的。它只是重复长表达式的符号简写:

CASE
   WHEN op.receiving_account_id IS NOT NULL THEN
      CASE
         WHEN op.account_id IS NULL THEN ac2.name
         ELSE ac.name || ' -> ' || ac2.name
      END
   ELSE ac.name
END

我从你的名字中得出你在operationtag之间的n:m关系,用operation_tag实现。所有其他联接似乎不会增加行数,因此单独聚合标记会更有效 - 就像@Andomar暗示的那样,只是让逻辑正确。

这应该有效:

SELECT op.id
     , op.name
     , c.name
     , CASE  -- amountsign
          WHEN op.receiving_account_id IS NOT NULL THEN
             CASE WHEN op.account_id IS NULL THEN '+' ELSE '=' END
          ELSE '-' 
       END || ' ' || op.amount || ' zł' AS amount
     , CASE  -- account
          WHEN op.receiving_account_id IS NOT NULL THEN
             CASE
                WHEN op.account_id IS NULL THEN ac2.name
                ELSE ac.name || ' -> ' || ac2.name
             END
          ELSE ac.name
       END AS account
     , t.name AS type
     , to_char(op.date, 'DD.MM.YY') || ' ' || op.time AS date  -- see below
     , p.name AS place
     , ot.tags
FROM   operation op
LEFT   JOIN category c   ON op.category_id = c.id
LEFT   JOIN type     t   ON op.type_id = t.id
LEFT   JOIN account  ac  ON op.account_id = ac.id
LEFT   JOIN account  ac2 ON op.receiving_account_id = ac2.id
LEFT   JOIN place    p   ON op.place_id = p.id
LEFT   JOIN (
   SELECT operation_id, string_agg(t.name, ', ') AS tags
   FROM   operation_tag ot
   LEFT   JOIN tag      t  ON t.id = ot.tag_id
   GROUP  BY 1
   ) ot ON op.id = ot.operation_id
ORDER  BY op.date DESC, op.time DESC;

旁白

您可以替换:

CASE --date
   WHEN op.time IS NOT NULL THEN to_char(op.date, 'DD.MM.YY') || ' ' || op.time
   ELSE to_char(op.date, 'DD.MM.YY')
END AS date

这个较短的等价物:

concat_ws(' ', to_char(op.date, 'DD.MM.YY'), op.time) AS date

但由于两列都已定义NOT NULL,因此您可以简化为:

to_char(op.date, 'DD.MM.YY') || ' ' || op.time AS date

小心ORDER BY您至少有一个名为date的输入列。如果您使用非限定名称,它将引用输出列 - 这是您想要的(在注释中阐明)。详细说明:

但是 ,按文字表示排序不会根据您的时间线正确排序。按原始值排序,而不是我上面的查询中建议的。

答案 1 :(得分:2)

大多数数据库要求您group byselect中未按照集体显示的每一列min。 Unaggregated意味着没有包含在maxstring_aggop.id, op.name, c.name, op.receiving_account_id, ...这样的聚合中。所以你需要分组:group by等等。

此要求的原因是数据库必须确定该组的值。通过将列添加到group by子句,可以确认组中的每一行都具有相同的值。对于其他组,您必须指定要与聚合一起使用的值。例外是MySQL,如果你没有做出有意识的选择,它只会选择一个任意值。

如果您的left join ( select id , string_agg(tag.name, ', ') tags from tag group by id ) t on ot.tag_id = t.id 仅用于创建标记列表,则可以将其移至子查询:

{{1}}

你可以为外部查询避免很长的分组。