将MySQL视图转换为Postgres

Dan*_*iel 2 mysql sql postgresql laravel-migrations

我继承了将生产MySQL数据库转换为Postgres的需求.使用简单的SQL语句创建表/函数(使用Navicat生成半自动转换)时,大多数情况下处理都没有问题,但现在我遇到了转换有点复杂视图的问题.

研究表明,这可能是由于两个DB如何处理子查询(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
Run Code Online (Sandbox Code Playgroud)

...触发错误

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)) ...
Run Code Online (Sandbox Code Playgroud)

任何人都可以建议如何格式化这样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;");
Run Code Online (Sandbox Code Playgroud)

更新,固定:

辉煌.在这里非常好的输入.

@patrick和@ErwinBrandstetter的解决方案都有效.我会赞成Patrick在这里,因为我在这方面的作用是"按原样"转换系统.将来可能有重构的余地,但在这个阶段我觉得混淆(或改进)别人的胶带解决方案是有风险的(即代码库在某些地方看起来过于复杂,没有文档的迹象,而且我如果没有关于业务逻辑的更多背景信息,我不愿意四处寻找或尝试核心改进.我怀疑无论如何都需要对模型的某些部分进行大修,所以[sic] -fix在这里很受欢迎.

我怀疑一些click-jiggery可能会产生原始查询...试图给原始开发者带来疑问的好处,并假设有一些业务压力要求快速(即模糊)转向.复杂的SQL不是我的强项,但我很高兴我的直觉是正确的,查询开始时是不必要的复杂.也许这个视图是一个无计划的螺栓 - 首先不是设计的.明智与否,我可能试图用基于ORM的方法解决问题.

我最后一刻在这个项目上,为重新启动运行清理(原始dev是"放手"),所以我正在处理一个充满未知功能的大多数未记录的代码库.按原样运行伞兵.值得庆幸的是,这个观点问题出现在最后一块拼图中.谢谢 :-)

Pat*_*ick 6

哦,我的,哦,我的.毫无疑问,开发人员在他的右手无名指中有一个抽搐,因为这句话有不少于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;
Run Code Online (Sandbox Code Playgroud)

没有子选择意味着此代码将更快地启动运行.就像他在评论中提到的Wolph一样,选择列表中未包含在聚合函数中的每一列都必须出现在GROUP BY子句中,如SQL标准所规定的那样,但是被MySQL忽略了.

通过使用CASE构造来避免子选择:列列表中的条件表达式评估.请注意,子选择中的过滤的重复子句现在作为JOIN子句执行campaigns,在主查询中每列仅评估一个相关列.发布10CASE语句中包装并在sum()函数中包装是一个很好的技巧,可以在单个查询中执行多个不同的计数.

正如沃尔夫在下面的评论中指出的那样,该条款

sum(CASE WHEN c.campaign_status_id = 4 THEN 1 ElSE 0 END) AS current_campaigns
Run Code Online (Sandbox Code Playgroud)

也可以写得更简洁

sum((c.campaign_status_id = 4)::integer) AS current_campaigns
Run Code Online (Sandbox Code Playgroud)

这可能比CASE语句更快,因为在编写PostgreSQL的C语言中,布尔到整数强制转换不需要任何操作(布尔值在C中为1或0).易读性当然少(更不用说使用两倍多的括号!).