[英]How do I port query with GROUP BY clause to PostgreSQL?
我正在将一个简单的费用数据库移植到Postgres,并使用GROUP BY
和多个JOIN
子句卡在视图上。 我认为Postgres希望我使用GROUP BY
子句中的所有表。
表定义在最后。 请注意, account_id
, receiving_account_id
和place
列可能为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
我进行了一些更新,目前的说法是:
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
添加列出的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
);
就像已经提供的@Andomar一样:大多数RDBMS要求按未聚集的每一列进行分组-查询中的其他任何位置(包括SELECT
列表,还包括WHERE
子句等)。
SQL标准还定义了GROUP BY
子句中的表达式也应涵盖功能相关的表达式。 Postgres实现了PK列覆盖同一表的所有列 。
因此op.id
涵盖了整个表格,这应该适用于您当前的查询:
GROUP BY op.id, c.name, 5, t.name, p.name
5
是对SELECT
列表的位置引用 ,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
我从您的名字中得出,您在operation
和tag
之间具有一个:: 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
。 如果您使用非限定名称,它将引用输出列-这就是您想要的(在注释中已阐明)。 细节:
但是 ,按文本表示法排序不会正确地根据您的时间轴排序。 按原始值排序,而不是上面我的查询中所建议的。
大多数数据库要求您group by
select
中未显示group by
每一列进行group by
。 未聚合表示未包装在诸如min
, max
或string_agg
类的聚合中。 因此,您需要分组: op.id, op.name, c.name, op.receiving_account_id, ...
等。
此要求的原因是数据库必须确定该组的值。 通过将列添加到group by
子句,可以确认组中的每一行都具有相同的值。 对于其他组,必须指定要用于汇总的值。 MySQL是一个例外,它会在您没有做出明智选择的情况下选择任意值。
如果您的group by
仅用于创建标签列表,则可以将其移至子查询:
left join
(
select id
, string_agg(tag.name, ', ') tags
from tag
group by
id
) t
on ot.tag_id = t.id
而且您可以避免对外部查询进行非常长的分组。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.