修改PostgreSQL主键为复合键后GROUP BY语句失效问题
我有两个分支,用的是完全相同的PostgreSQL查询,但GROUP BY子句突然失效了。原因是我把表的主键从id改成了复合键(tenant_id, id)。
旧documents表
Documents Table "public.documents" Column | Type | Collation | Nullable | Default --------------------------------+-----------------------------+-----------+----------+--------------------------------------- id | integer | | not null | nextval('documents_id_seq'::regclass) user_id | integer | | | Indexes: "documents_pkey" PRIMARY KEY, btree (id) "index_documents_on_user_id" btree (user_id)
新documents表
Documents Column | Type | Collation | Nullable | Default --------------------------------+-----------------------------+-----------+----------+--------------------------------------- id | integer | | not null | nextval('documents_id_seq'::regclass) user_id | integer | | | tenant_id | bigint | | not null | Indexes: "documents_pkey" PRIMARY KEY, btree (tenant_id, id) "index_documents_on_user_id" btree (user_id) "index_documents_on_tenant_id_and_id" UNIQUE, btree (tenant_id, id) Foreign-key constraints: "fk_rails_5ca55da786" FOREIGN KEY (tenant_id) REFERENCES tenants(id)
现在新分支里的这个SQL查询不再合法,我搞不懂分组逻辑是怎么工作的,为什么查询不能像之前那样用了?
我的SQL查询如下:
SELECT "documents".* FROM "documents" GROUP BY "documents"."id"
新分支报错信息:
ERROR: column "documents.user_id" must appear in the GROUP BY clause or be used in an aggregate function
问题原因与解决办法
PostgreSQL的GROUP BY规则核心是:SELECT列表里的非聚合列,要么出现在GROUP BY子句中,要么能被分组键唯一确定。
之前单主键id的时候,id是全局唯一的主键,PostgreSQL明确知道每个id对应的其他列(比如user_id)只有唯一值,所以即使SELECT用了documents.*,GROUP BY只写id也没问题——分组后每个组里只有一行数据,其他列的值是确定的。
但改成复合主键(tenant_id, id)之后,单独的id不再是全局唯一的了(不同租户下可能有相同的id值)。这时候PostgreSQL无法保证同一个id对应的user_id或tenant_id是唯一的,所以就会报错要求你把这些列加到GROUP BY里,或者用聚合函数处理。
可选解决办法
方法1:把复合主键全列加入GROUP BY
直接遵循PostgreSQL的要求,将复合主键的所有字段都放到GROUP BY中:SELECT "documents".* FROM "documents" GROUP BY "documents"."tenant_id", "documents"."id"方法2:改用DISTINCT ON(适合去重场景)
如果原查询的GROUP BY只是为了去重,可以用DISTINCT ON替代,注意如果id不是全局唯一,可能需要结合tenant_id来保证逻辑正确:SELECT DISTINCT ON (tenant_id, id) "documents".* FROM "documents"方法3:给
id单独加唯一索引
如果你的业务逻辑里id实际上还是全局唯一的(只是主键改成了复合形式),可以给id加一个唯一索引,让PostgreSQL识别到它的唯一性,这样原查询就能正常运行:CREATE UNIQUE INDEX index_documents_on_id ON documents(id);
内容的提问来源于stack exchange,提问作者George Morris

