将 MySQL 视图转换为 Postgres
Convert MySQL view to Postgres
我继承了将生产 MySQL 数据库转换为 Postgres 的需要。使用简单的 SQL 语句进行 table/function 创建(使用 Navicat 生成半自动转换),这大部分都没有问题地处理了,但是现在我在转换有点复杂的视图时遇到了问题。
研究表明这可能是由于两个数据库处理子查询(WHERE 语句)的方式不同所致,也许这只是语法差异。这里的业务逻辑是未知的,因为代码库是从另一个开发人员那里继承的。
运行 以下(使用 Laravel 迁移/PHP 脚本):
SELECT
parent.is_owner AS is_owner,
parent.brand AS first_name,
parent.id AS id,
(SELECT count(c.id)
FROM campaigns c
WHERE((
(c.user_id = parent.id)
OR
(c.user_id = child.id)
)
AND
(c.campaign_status_id = 4)
))
AS current_campaigns,
(SELECT count(c.id)
FROM campaigns c
WHERE
((
(c.user_id = parent.id)
OR (c.user_id = child.id)
)
AND (c.campaign_status_id = 5)
))
AS past_campaigns,
(SELECT count(c.id)
FROM campaigns c
WHERE
((
(c.user_id = parent.id)
OR (c.user_id = child.id))
AND (c.campaign_status_id = 2)
))
AS pending_campaigns,
(SELECT count(c.id)
FROM campaigns c
WHERE ((
(c.user_id = parent.id)
OR (c.user_id = child.id)
)
AND (c.invoice_status = '1')
))
AS past_invoices
FROM ((users parent LEFT JOIN campaigns mc ON
((parent.id = mc.user_id)))
LEFT JOIN users child ON ((child.parent_owner = parent.id)
))
WHERE
(
(parent.is_owner = 1)
OR (child.is_retailer = 1)
)
GROUP BY parent.id
ORDER BY parent.brand
...触发错误
SQLSTATE[42803]: Grouping error: 7 ERROR: subquery uses ungrouped column "child.id" from outer query
LINE 1: ...c where (((c.user_id = parent.id) or (c.user_id = child.id)) ...
谁能建议如何格式化它以便 Postgres 运行子查询?
顺便说一句,Laravel 迁移脚本中使用的 PHP 代码是:
...
DB::unprepared("CREATE VIEW client AS
select parent.is_owner AS is_owner,parent.brand AS first_name,parent.id AS id
,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 4))) AS current_campaigns
,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 5))) AS past_campaigns
,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 2))) AS pending_campaigns
,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.invoice_status = '1'))) AS past_invoices
from ((users parent
left join campaigns mc on((parent.id = mc.user_id)))
left join users child on((child.parent_owner = parent.id)))
where ((parent.is_owner = 1) or (child.is_retailer = 1))
group by parent.id
order by parent.brand;");
更新,修复:
太棒了。大家的意见都很好。
@patrick 和@ErwinBrandstetter 的解决方案都有效。我会在这里支持 Patrick,因为我在其中的角色是转换系统 "as-is"。未来可能会有重构的空间,但在这个阶段我觉得搞乱(或改进)别人的胶带解决方案是有风险的(即代码库在某些地方似乎过于复杂,没有文档的迹象,我在没有更多关于业务逻辑的背景信息的情况下,不愿意四处寻找或尝试核心改进)。我怀疑模型的某些部分可能无论如何都需要大修,所以 [原文如此]-fix 在这里受到青睐。
我怀疑某些点击操作可能生成了原始查询...试图让原始开发人员从怀疑中获益,并假设存在一些需要快速(即鼠标)周转的业务压力.复杂 SQL 不是我的强项,但我很高兴我的直觉是正确的,查询一开始就不必要地复杂。也许这个视图是一个计划外的螺栓连接 - 不是首先设计的。不管明智与否,我可能会尝试使用基于 ORM 的方法来解决问题。
我在最后一分钟参与这个项目,运行 清理以重新启动(原始开发人员是 "let go"),所以我正在使用一个几乎没有记录的代码库,其中充满了未知的功能. 运行 伞兵。值得庆幸的是,这个视图问题 出现 到拼图的最后一块。谢谢:-)
天哪,天哪。毫无疑问,开发人员的右手无名指抽搐了,因为该语句有不少于 74 个括号。以下是仅使用 8 个括号和 14 行而不是 54 行的方法:
SELECT
parent.is_owner AS is_owner,
parent.brand AS first_name,
parent.id AS id,
sum(CASE WHEN c.campaign_status_id = 4 THEN 1 ElSE 0 END) AS current_campaigns,
sum(CASE WHEN c.campaign_status_id = 5 THEN 1 ElSE 0 END) AS past_campaigns,
sum(CASE WHEN c.campaign_status_id = 2 THEN 1 ElSE 0 END) AS pending_campaigns,
sum(CASE WHEN c.invoice_status = '1' THEN 1 ElSE 0 END) AS past_invoices,
FROM users parent
LEFT JOIN users child ON child.parent_owner = parent.id
LEFT JOIN campaigns c ON c.user_id = parent.id OR c.user_id = child.id
WHERE parent.is_owner = 1 OR child.is_retailer = 1
GROUP BY parent.is_owner, parent.brand, parent.id
ORDER BY parent.brand;
没有 sub-selects 意味着此代码将 运行 更快地启动。就像 Wolph 在他的评论中提到的那样,select 列表中未包含在聚合函数中的每一列都必须出现在 GROUP BY
子句中,正如 SQL 标准所指定的那样,但被 SQL 幸福地忽略了=34=].
通过使用 CASE
结构可以避免子 select:列列表中的条件表达式求值。请注意,子 select 中过滤的重复子句现在作为 JOIN
子句执行,主查询中的每一列仅评估 campaigns
中的一个相关列。从 CASE
语句发出 1
或 0
并将其包装在 sum()
函数中是在单个查询中执行多个不同计数的绝妙技巧。
正如 Wolph 在此答案下方的评论中指出的那样,子句
sum(CASE WHEN c.campaign_status_id = 4 THEN 1 ElSE 0 END) AS current_campaigns
也可以更简洁地写成
sum((c.campaign_status_id = 4)::integer) AS current_campaigns
这可能比 CASE
语句快一些,因为在编写 PostgreSQL 的 C 语言中将布尔值转换为整数不需要任何操作(布尔值要么是 1或 C 中的 0)。易读性肯定更差(更不用说使用两倍的括号了!)。
问题中缺少解释,但可能的用例是:
计算每个用户有多少活动"owns"。一个用户可以有 child 个用户,其活动应添加到 parent 个用户。
除了 令人难以置信的嘈杂语法之外,查询也有歧义(并且可能完全错误):
如果我们可以假设:
引用完整性:child用户仅引用现有parent用户,强制执行具有 FOREIGN KEY
约束。
Parents 和 children 可靠地标记为 is_owner
/ is_retailer
,这些列仅包含值 0
和 1
。 见下文。
这个查询可以完成这项工作:
SELECT CASE WHEN u.is_retailer = 1 THEN u.parent_owner
WHEN u.is_owner = 1 THEN u.id END AS user_id
, max(u.is_owner) AS is_owner
, max(u.brand) FILTER (WHERE u.is_owner = 1) AS first_name
, count(*) FILTER (WHERE c.campaign_status_id = 4) AS current_campaigns
, count(*) FILTER (WHERE c.campaign_status_id = 5) AS past_campaigns
, count(*) FILTER (WHERE c.campaign_status_id = 2) AS pending_campaigns
, count(*) FILTER (WHERE c.invoice_status = '1') AS past_invoices
FROM users u
LEFT JOIN campaigns c ON u.id = c.user_id
AND (c.campaign_status_id IN (4, 5, 2) OR
c.invoice_status = '1') -- exclude irrelevant early
WHERE 1 IN (u.is_owner, u.is_retailer) -- parent & child, may be redundant
GROUP BY 1
ORDER BY 2;
应该很快。一定要有适合大表的索引。
如果没有其他选项,这个条件是多余的:
WHERE 1 IN (u.is_owner, u.is_retailer)
我使用了你的数据模型 "as is",但你可能应该只有 boolean
列:
is_child
:true
为 children,false
为 parents。
is_owner
:所有者 true
,零售商 false
。
使用 Postgres 9.4 中引入的新 聚合 FILTER
子句:
- How can I simplify this game statistics query?
我继承了将生产 MySQL 数据库转换为 Postgres 的需要。使用简单的 SQL 语句进行 table/function 创建(使用 Navicat 生成半自动转换),这大部分都没有问题地处理了,但是现在我在转换有点复杂的视图时遇到了问题。
研究表明这可能是由于两个数据库处理子查询(WHERE 语句)的方式不同所致,也许这只是语法差异。这里的业务逻辑是未知的,因为代码库是从另一个开发人员那里继承的。
运行 以下(使用 Laravel 迁移/PHP 脚本):
SELECT
parent.is_owner AS is_owner,
parent.brand AS first_name,
parent.id AS id,
(SELECT count(c.id)
FROM campaigns c
WHERE((
(c.user_id = parent.id)
OR
(c.user_id = child.id)
)
AND
(c.campaign_status_id = 4)
))
AS current_campaigns,
(SELECT count(c.id)
FROM campaigns c
WHERE
((
(c.user_id = parent.id)
OR (c.user_id = child.id)
)
AND (c.campaign_status_id = 5)
))
AS past_campaigns,
(SELECT count(c.id)
FROM campaigns c
WHERE
((
(c.user_id = parent.id)
OR (c.user_id = child.id))
AND (c.campaign_status_id = 2)
))
AS pending_campaigns,
(SELECT count(c.id)
FROM campaigns c
WHERE ((
(c.user_id = parent.id)
OR (c.user_id = child.id)
)
AND (c.invoice_status = '1')
))
AS past_invoices
FROM ((users parent LEFT JOIN campaigns mc ON
((parent.id = mc.user_id)))
LEFT JOIN users child ON ((child.parent_owner = parent.id)
))
WHERE
(
(parent.is_owner = 1)
OR (child.is_retailer = 1)
)
GROUP BY parent.id
ORDER BY parent.brand
...触发错误
SQLSTATE[42803]: Grouping error: 7 ERROR: subquery uses ungrouped column "child.id" from outer query
LINE 1: ...c where (((c.user_id = parent.id) or (c.user_id = child.id)) ...
谁能建议如何格式化它以便 Postgres 运行子查询?
顺便说一句,Laravel 迁移脚本中使用的 PHP 代码是:
...
DB::unprepared("CREATE VIEW client AS
select parent.is_owner AS is_owner,parent.brand AS first_name,parent.id AS id
,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 4))) AS current_campaigns
,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 5))) AS past_campaigns
,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.campaign_status_id = 2))) AS pending_campaigns
,(select count(c.id) from campaigns c where (((c.user_id = parent.id) or (c.user_id = child.id)) and (c.invoice_status = '1'))) AS past_invoices
from ((users parent
left join campaigns mc on((parent.id = mc.user_id)))
left join users child on((child.parent_owner = parent.id)))
where ((parent.is_owner = 1) or (child.is_retailer = 1))
group by parent.id
order by parent.brand;");
更新,修复:
太棒了。大家的意见都很好。
@patrick 和@ErwinBrandstetter 的解决方案都有效。我会在这里支持 Patrick,因为我在其中的角色是转换系统 "as-is"。未来可能会有重构的空间,但在这个阶段我觉得搞乱(或改进)别人的胶带解决方案是有风险的(即代码库在某些地方似乎过于复杂,没有文档的迹象,我在没有更多关于业务逻辑的背景信息的情况下,不愿意四处寻找或尝试核心改进)。我怀疑模型的某些部分可能无论如何都需要大修,所以 [原文如此]-fix 在这里受到青睐。
我怀疑某些点击操作可能生成了原始查询...试图让原始开发人员从怀疑中获益,并假设存在一些需要快速(即鼠标)周转的业务压力.复杂 SQL 不是我的强项,但我很高兴我的直觉是正确的,查询一开始就不必要地复杂。也许这个视图是一个计划外的螺栓连接 - 不是首先设计的。不管明智与否,我可能会尝试使用基于 ORM 的方法来解决问题。
我在最后一分钟参与这个项目,运行 清理以重新启动(原始开发人员是 "let go"),所以我正在使用一个几乎没有记录的代码库,其中充满了未知的功能. 运行 伞兵。值得庆幸的是,这个视图问题 出现 到拼图的最后一块。谢谢:-)
天哪,天哪。毫无疑问,开发人员的右手无名指抽搐了,因为该语句有不少于 74 个括号。以下是仅使用 8 个括号和 14 行而不是 54 行的方法:
SELECT
parent.is_owner AS is_owner,
parent.brand AS first_name,
parent.id AS id,
sum(CASE WHEN c.campaign_status_id = 4 THEN 1 ElSE 0 END) AS current_campaigns,
sum(CASE WHEN c.campaign_status_id = 5 THEN 1 ElSE 0 END) AS past_campaigns,
sum(CASE WHEN c.campaign_status_id = 2 THEN 1 ElSE 0 END) AS pending_campaigns,
sum(CASE WHEN c.invoice_status = '1' THEN 1 ElSE 0 END) AS past_invoices,
FROM users parent
LEFT JOIN users child ON child.parent_owner = parent.id
LEFT JOIN campaigns c ON c.user_id = parent.id OR c.user_id = child.id
WHERE parent.is_owner = 1 OR child.is_retailer = 1
GROUP BY parent.is_owner, parent.brand, parent.id
ORDER BY parent.brand;
没有 sub-selects 意味着此代码将 运行 更快地启动。就像 Wolph 在他的评论中提到的那样,select 列表中未包含在聚合函数中的每一列都必须出现在 GROUP BY
子句中,正如 SQL 标准所指定的那样,但被 SQL 幸福地忽略了=34=].
通过使用 CASE
结构可以避免子 select:列列表中的条件表达式求值。请注意,子 select 中过滤的重复子句现在作为 JOIN
子句执行,主查询中的每一列仅评估 campaigns
中的一个相关列。从 CASE
语句发出 1
或 0
并将其包装在 sum()
函数中是在单个查询中执行多个不同计数的绝妙技巧。
正如 Wolph 在此答案下方的评论中指出的那样,子句
sum(CASE WHEN c.campaign_status_id = 4 THEN 1 ElSE 0 END) AS current_campaigns
也可以更简洁地写成
sum((c.campaign_status_id = 4)::integer) AS current_campaigns
这可能比 CASE
语句快一些,因为在编写 PostgreSQL 的 C 语言中将布尔值转换为整数不需要任何操作(布尔值要么是 1或 C 中的 0)。易读性肯定更差(更不用说使用两倍的括号了!)。
问题中缺少解释,但可能的用例是:
计算每个用户有多少活动"owns"。一个用户可以有 child 个用户,其活动应添加到 parent 个用户。
除了
如果我们可以假设:
引用完整性:child用户仅引用现有parent用户,强制执行具有
FOREIGN KEY
约束。Parents 和 children 可靠地标记为
is_owner
/is_retailer
,这些列仅包含值0
和1
。 见下文。
这个查询可以完成这项工作:
SELECT CASE WHEN u.is_retailer = 1 THEN u.parent_owner
WHEN u.is_owner = 1 THEN u.id END AS user_id
, max(u.is_owner) AS is_owner
, max(u.brand) FILTER (WHERE u.is_owner = 1) AS first_name
, count(*) FILTER (WHERE c.campaign_status_id = 4) AS current_campaigns
, count(*) FILTER (WHERE c.campaign_status_id = 5) AS past_campaigns
, count(*) FILTER (WHERE c.campaign_status_id = 2) AS pending_campaigns
, count(*) FILTER (WHERE c.invoice_status = '1') AS past_invoices
FROM users u
LEFT JOIN campaigns c ON u.id = c.user_id
AND (c.campaign_status_id IN (4, 5, 2) OR
c.invoice_status = '1') -- exclude irrelevant early
WHERE 1 IN (u.is_owner, u.is_retailer) -- parent & child, may be redundant
GROUP BY 1
ORDER BY 2;
应该很快。一定要有适合大表的索引。
如果没有其他选项,这个条件是多余的:
WHERE 1 IN (u.is_owner, u.is_retailer)
我使用了你的数据模型 "as is",但你可能应该只有 boolean
列:
is_child
:true
为 children,false
为 parents。is_owner
:所有者true
,零售商false
。
使用 Postgres 9.4 中引入的新 聚合 FILTER
子句:
- How can I simplify this game statistics query?