[英]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.