在相关子查询中将OR转换为UNION

时间:2018-04-20 19:12:01

标签: mysql sql

在大多数情况下,当我尝试优化OR条件时,我试图了解UNION替代方案将如何执行。在许多情况下,UNION替代方案表现更好,因为它可以正确索引,而OR条件不能。

让我们假设以下示例:

SELECT 
    *
FROM
    posts
WHERE
    posts.id = (SELECT 
            postid
        FROM
            comments
        WHERE
            comments.postid = posts.id
                AND (comments.userid = 5 OR comments.score = 8)
        ORDER BY postid
        LIMIT 1);

在这种情况下,将子查询转换为UNION子句并不是一件容易的事,因为这样做会使我们用另一个子查询包装整个UNION子句,以确保结果的顺序不会成为影响。 但是,当应用包装时,我们现在有一个3级嵌套查询,而不是2级查询,因此表帖子不再可用于UNION的内部SELECT查询,这使得语法无效。

这是我正在寻找的转变,希望能够发挥作用:

SELECT
        * 
    FROM
        posts 
    WHERE
        posts.id = (
            SELECT
                * 
            FROM
                ((SELECT
                    comments.postid,
                    comments.postid 
                FROM
                    comments 
                WHERE
                    comments.postid = posts.id 
                    AND (
                        comments.score = 8
                    ) 
                ORDER BY
                    comments.postid LIMIT 1) 
            UNION
            DISTINCT (SELECT
                comments.postid,
                comments.postid 
            FROM
                comments 
            WHERE
                comments.postid = posts.id 
                AND (comments.userid = 5) 
            ORDER BY
                comments.postid LIMIT 1)
        ) AS union1 
    ORDER BY
        union1.postid LIMIT 1)

因此,在这种情况下,MySQL只会返回此错误:Error Code: 1054. Unknown column 'posts.id' in 'where clause'

是否有创造性的方法将此OR条件转换为此类相关子查询中的UNION?

重要提示:我完全清楚这个查询可以以不同方式重写,而不需要原始子查询,这可能会使整个问题无关紧要。但是,为了这个讨论,我试图看看在我之后进行转换的最佳方式是什么,只需要进行最小的更改。

2 个答案:

答案 0 :(得分:1)

与大多数相关的子查询一样(根据我的经验),这似乎更好地写成不相关的,如下所示:

SELECT *
FROM posts
WHERE posts.id IN (
        SELECT postid
        FROM comments
        WHERE comments.userid = 5 OR comments.score = 8
   )
;

或至少,作为存在

SELECT *
FROM posts
WHERE EXISTS (
   SELECT * 
   FROM comments AS c 
   WHERE c.postid = posts.id
      AND (c.userid = 5 OR c.score = 8)
  )
;

虽然这并没有直接解决OR到UNION的转换,但它们的转换要简单得多。

转换:

SELECT *
FROM posts
WHERE posts.id IN (
        SELECT postid
        FROM comments
        WHERE comments.userid = 5
        UNION 
        SELECT postid
        FROM comments
        WHERE comments.score = 8
   )
;

从技术上讲,第二个转换不需要/使用UNION,但它会使相关子查询加倍。

SELECT *
FROM posts
WHERE EXISTS (
   SELECT * 
   FROM comments AS c 
   WHERE c.postid = posts.id
      AND c.userid = 5
  )
OR EXISTS (
   SELECT * 
   FROM comments AS c 
   WHERE c.postid = posts.id
      AND c.score = 8
  )
;

答案 1 :(得分:0)

为什么不只是join

从这里开始:

SELECT  p.*
FROM  posts p
INNER JOIN comments c ON c.postid = posts.id
WHERE c.userid = 5 or c.score = 8
ORDER p.id
LIMIT 1;

我敢于你找到一种产生不同结果的情况,而且它已经可能比发布的结果快得多。

一旦你有了,你可以正常联合:

SELECT *
FROM posts 
WHERE id IN (
    SELECT  p.id
    FROM  posts p
    INNER JOIN comments c ON c.postid = posts.id
    WHERE c.userid = 5 

    UNION

    SELECT  p.id
    FROM  posts p
    INNER JOIN comments c ON c.postid = posts.id
    WHERE c.score = 8
)
ORDER BY id
LIMIT 1;

OR

SELECT *
FROM (
    SELECT  p.*
    FROM  posts p
    INNER JOIN comments c ON c.postid = posts.id
    WHERE c.userid = 5 

    UNION

    SELECT  p.*
    FROM  posts p
    INNER JOIN comments c ON c.postid = posts.id
    WHERE c.score = 8
) t
ORDER BY id
LIMIT 1;

第一个选项可以更简单,甚至不需要连接:

SELECT *
FROM posts 
WHERE id IN (
    SELECT  c.postid
    FROM  comments c
    WHERE c.userid = 5 

    UNION

    SELECT  c.postid
    FROM  comments c
    WHERE c.score = 8
)
ORDER BY id
LIMIT 1;