使用jOOQ将Oracle风格的隐式连接自动转换为ANSI JOIN

虽然jOOQ主要被用作Java中嵌入动态SQL的内部SQL DSL,它提供了市场上最好的解决方案,但jOOQ也越来越多地被用于其次要功能之一。在jOOQ 3.9中引入的解析器主要是为了能够解析DDL语句,例如为代码生成目的对你的模式进行逆向工程,我们已经增加了越来越多的功能和SQL转换能力,使解析器能够作为一个独立的产品,通过命令行界面我们的网站或通过普通的jOOQ API来使用。 在jOOQ 3.14中增加的一个功能,主要是对那些使用jOOQ作为分析器的人有用,就是将旧的Oracle风格的隐式连接转换为ANSI JOIN的能力。

为什么要避免 "隐式连接"?

大多数RDBMS供应商都支持旧的Oracle风格的隐式连接语法,并进行了适当的优化。在过去,在SQL-92之前,这就是我们用于内部连接表的方式,例如,在查询Sakila数据库时。


SELECT *
FROM actor a, film_actor fa, film f
WHERE a.actor_id = fa.actor_id
AND fa.film_id = f.film_id


诚然,这个语法是很直观的。只要声明所有你想获取数据的表,然后确保只通过过滤匹配的主键/外键值来保留适当的数据。 当然,这可能会出大错。由于许多明显的原因,例如,当你在添加一个表之后忘记了一个连接谓词。如果查询是复杂的,这可能是很难调试的。解决方案是ANSI JOIN。从SQL-92开始(差不多30年了!),这就是我们在大多数RDBMS中的连接方式。


SELECT *
FROM actor a
JOIN film_actor fa ON a.actor_id = fa.actor_id
JOIN film f ON fa.film_id = f.film_id


虽然仍然有可能定义错误的连接谓词,但至少不再有可能忘记一个谓词,因为这在语法上是不正确的(除了MySQL,遗憾的是,在那里,ON子句是可选的)。


SELECT *
FROM actor a
JOIN film_actor fa -- Syntax error
JOIN film f -- Syntax error


jOOQ的隐式JOIN

注意,人们通常把上述语法称为 "隐式连接",而JPQL和jOOQ回收了另一种"隐式连接 "的术语,它是基于外键路径的,甚至比ANSI SQL语法更不容易出错。使用jOOQ,上述查询可以写成如下。


ctx.select(
FILM_ACTOR.actor().asterisk(),
FILM_ACTOR.asterisk(),
FILM_ACTOR.film().asterisk())
.from(FILM_ACTOR)
.fetch();


仅仅在查询中存在这些to-one的关系路径,就会隐含地将适当的LEFT JOININNER JOIN 添加到FROM 子句中。这只是在普通ANSI JOIN之上的便利,而不是替代。

改造Oracle的隐式连接

当你有一个旧的代码库,你希望升级并将所有的查询转换为使用ANSI JOIN时,请使用jOOQ来实现。你可以使用jOOQ的编程功能(如前所述),或免费网站www.jooq.org/translate。在网站上,只需选择 "Oracle风格到ANSI JOIN "选项,在左边放置以下SQL:输入


SELECT
a.first_name,
a.last_name,
count(c.category_id)
FROM
actor a,
film_actor fa,
film f,
film_category fc,
category c
WHERE a.actor_id = fa.actor_id
AND fa.film_id = f.film_id
AND fc.category_id = c.category_id
GROUP BY
a.actor_id,
a.first_name,
a.last_name


输出


SELECT
a.first_name,
a.last_name,
count(c.category_id)
FROM actor a
JOIN film_actor fa
ON a.actor_id = fa.actor_id
JOIN film f
ON fa.film_id = f.film_id
CROSS JOIN (
film_category fc
JOIN category c
ON fc.category_id = c.category_id
)
GROUP BY
a.actor_id,
a.first_name,
a.last_name


然后......呼啦啦。输出正确地显示了结果,不需要的CROSS JOIN,因为其中一个连接谓词丢失了:是的,这个工具已经帮了大忙!让我们来修正输入的查询。让我们来修正输入的查询:修正输入


SELECT
a.first_name,
a.last_name,
count(c.category_id)
FROM
actor a,
film_actor fa,
film f,
film_category fc,
category c
WHERE a.actor_id = fa.actor_id
AND fa.film_id = f.film_id
AND f.film_id = fc.film_id -- This was missing
AND fc.category_id = c.category_id
GROUP BY
a.actor_id,
a.first_name,
a.last_name


固定输出


SELECT
a.first_name,
a.last_name,
count(c.category_id)
FROM actor a
JOIN film_actor fa
ON a.actor_id = fa.actor_id
JOIN film f
ON fa.film_id = f.film_id
JOIN film_category fc
ON f.film_id = fc.film_id
JOIN category c
ON fc.category_id = c.category_id
GROUP BY
a.actor_id,
a.first_name,
a.last_name


如果你使用Oracle的神秘的外层连接语法(+) (或者SQL Server的*= ,它已经有一段时间不被支持了),这也是可行的。你可能有这样的输入:输入


SELECT
a.first_name,
a.last_name,
count(c.category_id)
FROM
actor a,
film_actor fa,
film f,
film_category fc,
category c
WHERE a.actor_id = fa.actor_id(+)
AND fa.film_id = f.film_id(+)
AND f.film_id = fc.film_id(+)
AND fc.category_id(+) = c.category_id
GROUP BY
a.actor_id,
a.first_name,
a.last_name


产生这样的输出


SELECT
a.first_name,
a.last_name,
count(c.category_id)
FROM actor a
LEFT OUTER JOIN film_actor fa
ON a.actor_id = fa.actor_id
LEFT OUTER JOIN film f
ON fa.film_id = f.film_id
LEFT OUTER JOIN (
film_category fc
RIGHT OUTER JOIN category c
ON fc.category_id = c.category_id
)
ON f.film_id = fc.film_id
GROUP BY
a.actor_id,
a.first_name,
a.last_name


呃,哇。又来了!其中一个(+) 符号在错误的一边,这就是为什么我们得到这个RIGHT OUTER JOIN 。这个工具再次表明,旧的语法是很容易出错的。让我们来修正它。修正输入


SELECT
a.first_name,
a.last_name,
count(c.category_id)
FROM
actor a,
film_actor fa,
film f,
film_category fc,
category c
WHERE a.actor_id = fa.actor_id(+)
AND fa.film_id = f.film_id(+)
AND f.film_id = fc.film_id(+)
AND fc.category_id = c.category_id(+)
GROUP BY
a.actor_id,
a.first_name,
a.last_name


固定输出


SELECT
a.first_name,
a.last_name,
count(c.category_id)
FROM actor a
LEFT OUTER JOIN film_actor fa
ON a.actor_id = fa.actor_id
LEFT OUTER JOIN film f
ON fa.film_id = f.film_id
LEFT OUTER JOIN film_category fc
ON f.film_id = fc.film_id
LEFT OUTER JOIN category c
ON fc.category_id = c.category_id
GROUP BY
a.actor_id,
a.first_name,
a.last_name


总结

玩一玩,告诉我们你的想法!www.jooq.org/translate

猜你喜欢

转载自juejin.im/post/7126375424967311391