使用SQL变量与子查询的优缺点是什么?

时间:2015-05-01 03:17:10

标签: sql postgresql

我想知道SQL变量和子查询之间存在差异。是否使用更多处理能力,或者更快,或者即使只是更易读。

对于(一个非常基本的)示例,我喜欢在PostGIS中使用变量来保存多边形和变换:

WITH region_polygon AS (
    SELECT ST_Transform(wkb_geometry, %(fishnet_srid)d) geom
    FROM regions
    LIMIT 1
), raster_pixels AS (
    SELECT (ST_PixelAsPolygons(rast)).*
    FROM test_regions_raster
    LIMIT 1
)
SELECT x, y
FROM raster_pixels a, region_polygon b
WHERE ST_Within(a.geom, b.geom)

但是以任何方式使用子查询会更好吗?

SELECT x, y
FROM (
    SELECT ST_Transform(wkb_geometry, %(fishnet_srid)d) geom
    FROM regions
    LIMIT 1
) a, (
    SELECT (ST_PixelAsPolygons(rast)).*
    FROM test_regions_raster
    LIMIT 1
) b
WHERE ST_Within(a.geom, b.geom)

请注意,我使用的是PostgreSQL。

3 个答案:

答案 0 :(得分:3)

当涉及到重用时,公共表表达式在派生表上具有重要的语法优势。请考虑以下使用自联接的等效示例:

使用常用表格表达式

WITH a(v) AS (SELECT 1 UNION SELECT 2)
SELECT *
FROM a AS x, a AS y

使用派生表

SELECT *
FROM (SELECT 1 UNION SELECT 2) x(v),
     (SELECT 1 UNION SELECT 2) y(v)

如您所见,使用公用表表达式,可以在查询中多次重用视图(SELECT 1 UNION SELECT 2)。使用派生表,您将不得不重复您的视图声明。在我的例子中,这仍然没问题。在你自己的例子中,这开始变得有点毛茸茸。

关于范围

SQL中的视图都是关于作用域的。声明视图基本上有四个级别:

  • 作为派生表。它们只能消耗一次。
  • 作为常用表表达式。它们可以被多次使用,但只能在一个查询中使用。
  • 视图。它们可以在多个查询中多次使用。
  • 作为具体化的观点。与视图相同,但数据已预先计算。

某些数据库(特别是PostgreSQL)也知道表值函数。从单纯的语法角度来看,它们就像视图一样 - 参数化视图。

性能

请注意,这些想法只关注语法,而不是查询规划。根据数据库供应商的不同,不同的方法可能会产生非常不同的性能影响。

答案 1 :(得分:1)

那些不是变量,它们是公共表表达式(cte)。在上面的查询中,执行计划可能相同,因为优化器应该识别它们是等效的查询。我更喜欢使用cte,因为我认为它们比子查询更容易阅读,但就是这样。

编辑:进一步阅读后,看起来PostgreSQL确实以不同于其他数据库的方式处理公用表表达式,例如,您无法在PostgreSQL中更新cte。我会在这里留下我的答案,因为我相信你的查询不会有所不同,但我对PostgreSQL并不十分熟悉。

答案 2 :(得分:1)

正如所指出的,这个构造称为Common Table Expression,而不是变量。

我更喜欢使用CTE而不是子查询,因为它更容易为我读写,特别是当你有几个嵌套的CTE时。

您可以编写CTE一次,并在查询的其余部分中多次引用它。使用子查询,您将不得不重复几次代码。

PostgreSQL与其他数据库(至少来自MS SQL Server)的重要区别在于PostgreSQL只评估每个CTE一次。

  

WITH查询的一个有用属性是它们只被评估一次   每次执行父查询,即使它们被引用更多   父查询或兄弟WITH查询不止一次。因此,昂贵   多个地方所需的计算可以放在一个地方   WITH查询以避免冗余工作。另一个可能的应用是   防止对副作用的功能进行多余的评估。   然而,这个硬币的另一面是优化器更少   能够将父查询中的限制推送到WITH查询   比普通的子查询。通常会评估WITH查询   如此编写,不会抑制父查询可能的行   之后丢弃。 (但是,如上所述,评估可能会停止   早期如果查询的引用只需要有限的数量   行。)

MS SQL Server会将CTE的每个引用内联到主查询中并优化整个结果,但PostgreSQL却没有。在某种意义上,PostgreSQL在这里更灵活。如果希望仅对子查询进行一次计算,请将其放在CTE中。如果您不想要,请将其放在子查询中并重复代码。在SQL Server中,您必须明确使用临时表。

您在问题中的示例过于简单,并且很可能两种变体都是等效的 - 请检查执行计划。

正如我上面引用的那样,官方文档提到它,但是Nick Barnes给了一个good article explaining it in more details的链接,我认为值得把它放在一个答案中,而不是那个评论。

  

在PostgreSQL中优化查询时(至少在9.4和   更老的),值得记住的是 - 不像更新的版本   各种其他数据库 - PostgreSQL将始终实现CTE   查询中的术语。

     

这可以有quite surprising effects for those used to working with DBs like MS SQL

     
      
  • 应该触及少量数据的查询会读取整数   表并可能将其溢出到临时文件;
  •   
  • 并且您无法更新或   从CTE术语中删除,因为它更像是只读临时表   而不是动态的观点。
  •   

因此,在PostgreSQL中CTE是否优于子查询还没有明确的答案。在某些情况下,它可能更快,在某些情况下它可能会更慢。但是,恕我直言,在大多数情况下,CTE更容易编写,阅读和维护。

显然,有一种情况是你没有其他选择,但使用所谓的递归CTE(递归查询通常用于处理分层或树状结构数据)。