簡體   English   中英

如何使用GROUP BY子句將查詢移植到PostgreSQL?

[英]How do I port query with GROUP BY clause to PostgreSQL?

我正在將一個簡單的費用數據庫移植到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添加列出的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

我從您的名字中得出,您在operationtag之間具有一個:: 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 未聚合表示未包裝在諸如minmaxstring_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM