SQL进阶(五):With 函数 vs 视图函数


本文是在原本sql闯关的基础上总结得来,加入了自己的理解以及疑问解答(by GPT4)

原活动链接

数据准备
提取码:grtn

1. 课前小问答 🔎

1. 为什么需要使用视图函数 或是 with 函数

A:视图函数(VIEW)或是 with 函数所代表的 CTE (Common Table Expression,公用表表达式)的主要目的是提高部分查询的可复用性,对于相当多的日常工作而言,很多的查询是高度重复的,如果为了每一个查询都单独写一份 SQL 代码将会花费大量的时间,这也是为什么 VIEW 和 WITH 可以说是对于 SQL 用户最有帮助的语法。

同样的,VIEW 和 WITH 能够帮助我们更好的去梳理 SQL 查询语句的结构,相较于使用复杂的子查询嵌套, 两者都可以帮助我们提升代码的可阅读性,方便代码报错的时候提供更好的纠错调整能力

2. VIEW 和 WITH 的区别在于哪里

A: 简单而言,两者的差别在于作用域(如果你熟悉一些编程的基础的话)和 持久性,下面的用表格列出他们的区别:

VIEWWITH
作用域可以跨查询复用只能在单次查询内复用
持久性存入数据库,持续可用单次使用,在数据库不存储

3. 我看到 VIEW 函数的结果是一张虚拟表,这种表和在数据库的数据表有什么关系?

A: 个人而言,我不是很愿意将 VIEW 函数所产出结果的结果称为“虚拟表”。尽管 VIEW 函数的结果确实 “可以像普通表格一样被访问”,但是 VIEW 储存的本质上是一组“当前可用的查询语句”,可以当做一个以某些数据表为参数输入,一张具体的表格作为输出的函数,当其所依赖的数据表出现改变,这些“查询语句”就有可能变得不再可用。

🔖小贴士 1
学习好 VIEW 和 WITH 并不能帮你写出更加“正确”的 SQL 语句,但是却能够帮助你更有效率地 维护和编写更多的语句。
在实际的工作中,维护好一套既定的 SQL 语句带来的工作效率/能力提升远大于你的想象

2. 开始之前的准备

# 导入库
from IPython.display import Markdown as md
from matplotlib import pyplot as plt
import sqlite3
import pandas as pd
import numpy as np
import json
from copy import deepcopy
from datetime import datetime

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 我们同样使用【天猫订单成交】的数据集
# 该数据集记录了 2w+ 条目成交订单数据

df = pd.read_csv('./data/tmall_order_report.csv')

# 将表格中 NaN 值替换为 NULL
df = df.where(pd.notnull(df), None)

# 为了简化下面的教程,我们新增一个【订单创建日期】的字段
df['订单创建日期'] = df['订单创建时间'].apply(lambda x:pd.Timestamp(x).date()).astype(str)

# 将数据写入一张名为 example_table 的表
with sqlite3.connect('example.db') as conn:
    df.to_sql('example_table', con=conn, if_exists='replace', index=False)

connection = sqlite3.connect('example.db')


# 查看该数据集的字段名称
df.columns
Index(['订单编号', '总金额', '买家实际支付金额', '收货地址 ', '订单创建时间', '订单付款时间 ', '退款金额',
       '订单创建日期'],
      dtype='object')
df.head()
订单编号总金额买家实际支付金额收货地址订单创建时间订单付款时间退款金额订单创建日期
01178.80.0上海2020-02-21 00:00:00None0.02020-02-21
1221.021.0内蒙古自治区2020-02-20 23:59:542020-02-21 00:00:020.02020-02-20
2337.00.0安徽省2020-02-20 23:59:35None0.02020-02-20
34157.0157.0湖南省2020-02-20 23:58:342020-02-20 23:58:440.02020-02-20
4564.80.0江苏省2020-02-20 23:57:042020-02-20 23:57:1164.82020-02-20

3. VIEW 函数基本

创建

SQLite

VIEW 函数的使用相当的简单,其基本的语句就是通过 SELECT 语句来创建

query = '''
CREATE VIEW     -- 创建 VIEW 
IF NOT EXISTS   -- 如果 VIEW 存在,则不创建
view_1          -- VIEW 的名字
AS              -- 关键词
SELECT              
    "订单创建时间" AS create_time 
    ,"收货地址 " AS address	
FROM example_table
                -- 常规的查询语句
'''
# result = connection.execute(query).fetchone()

在 MySQL 和 PostgreSQL 中创建视图(VIEW)的基本语法和 SQLite 中相似,但有一些关键的区别。以下是根据您提供的 SQLite 代码在 MySQL 和 PostgreSQL 中实现相同功能的方法:

MySQL

MySQL 支持 CREATE VIEW 语句,但它没有 IF NOT EXISTS 选项。因此,如果视图已经存在,尝试创建同名视图会导致错误。一种解决方法是先检查视图是否存在,如果存在,则删除它,然后创建新视图。但这通常不是推荐的做法,因为它会破坏现有的视图定义和可能的依赖关系。

在 MySQL 中创建视图的语法如下:

CREATE VIEW view_1 AS
SELECT
    `订单创建时间` AS create_time,
    `收货地址` AS address
FROM example_table;
PostgreSQL

PostgreSQL 支持 CREATE OR REPLACE VIEW 语句,这可以用来更新现有视图的定义或创建一个新视图,这和 SQLite 的 IF NOT EXISTS 功能相似。如果视图不存在,它会被创建;如果已经存在,它的定义会被替换。

在 PostgreSQL 中创建或替换视图的语法如下:

CREATE OR REPLACE VIEW view_1 AS
SELECT
    "订单创建时间" AS create_time,
    "收货地址" AS address
FROM example_table;

注意,列名如果包含特殊字符或空格,在 MySQL 中应该使用反引号 ` 来引用它们,在 PostgreSQL 中则应该使用双引号 "。

至于执行这个查询并获取结果的部分,这通常是在应用程序代码中完成的,使用相应的数据库驱动程序。例如,在 Python 中,您可以使用 pymysql(对于 MySQL)或 psycopg2(对于 PostgreSQL)来执行查询并获取结果。在实际应用中,创建视图通常不会返回结果集,因此使用 fetchone() 或类似的方法可能不适用。

删除

可以通过 DROP VIEW 的语句来删除已经有的 VIEW

在 SQLite、MySQL 和 PostgreSQL 中实现这一功能的方法是相同的

query = '''
DROP VIEW       -- 创建 VIEW 
IF EXISTS       -- 如果 VIEW 存在,则删除
view_1          -- VIEW 的名字
'''
# result = connection.execute(query).fetchone()

下面我们通过上个教程当中的一个例子来看看 VIEW 的用法

例1:我们以订单数据为例

假设我们需要按照 订单创建日期 计算累积订单量

这是原来的 query:

query = '''
SELECT
    t1.order_date,
    SUM(orders_num) OVER(ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM
    (SELECT "订单创建日期" AS order_date,
            COUNT("订单编号") AS orders_num
    FROM example_table
    GROUP BY "订单创建日期"
    )AS t1
'''
result = connection.execute(query).fetchall()
result
[('2020-02-01', 176),
 ('2020-02-02', 398),
 ('2020-02-03', 665),
 ('2020-02-04', 1134),
 ('2020-02-05', 1503),
 ('2020-02-06', 1647),
 ('2020-02-07', 1824),
 ('2020-02-09', 2228),
 ('2020-02-10', 2255),
 ('2020-02-11', 2270),
 ('2020-02-12', 2271),
 ('2020-02-13', 2276),
 ('2020-02-14', 2283),
 ('2020-02-15', 2288),
 ('2020-02-17', 2678),
 ('2020-02-18', 3693),
 ('2020-02-19', 4718),
 ('2020-02-20', 6063),
 ('2020-02-21', 8131),
 ('2020-02-22', 10158),
 ('2020-02-23', 12358),
 ('2020-02-24', 14356),
 ('2020-02-25', 17772),
 ('2020-02-26', 20621),
 ('2020-02-27', 23207),
 ('2020-02-28', 25898),
 ('2020-02-29', 28010)]
加入 VIEW
# 加入 VIEW
query_view = '''
CREATE VIEW IF NOT EXISTS orders_by_date AS
SELECT 
    "订单创建日期" AS order_date
    ,COUNT("订单编号") AS orders_num
FROM example_table
GROUP BY "订单创建日期";

SELECT
      order_date
      ,SUM(orders_num)                  -- 聚合函数 SUM()
       OVER(                            -- OVER 关键字,()内定 frame
             ORDER BY order_date        -- 按照 order_date 排序 
             ROWS                       -- 按照表当中的相对位置
             BETWEEN                    -- 界定上下界
                  UNBOUNDED PRECEDING   -- 上界,不设限制
             AND 
                  CURRENT ROW           -- 下界,当前行
             ) AS running_total

FROM orders_by_date;
'''
### 【!注意】这里将 execute 换为 executescript
### 这样我们可以在一个 query 当中执行多条 SQL 语句
result = connection.executescript(query_view).fetchall() 
result

[]
🔖小贴士 2
你可能很快注意到了,上面的 SQL 语句返回的是一个空的数组,不要担心,这是因为 executescript 在执行多条语句之后并不知道返回哪一条语句的结果
例2:查看视图

我们可以像查看正常表格一样去“查看”视图

### 例2:查看视图
### 我们可以像查看正常表格一样去“查看”视图
query_view = '''
SELECT order_date,orders_num
FROM orders_by_date;
'''
### 【!注意】这里将 execute 换为 executescript
### 这样我们可以在一个 query 当中执行多条 SQL 语句
result = connection.execute(query_view).fetchone() 
result

('2020-02-01', 176)

4. WITH 函数基本

通过 WITH 函数构建 CTE (公用表表达式)更加的简单和灵活:

例3 使用 WITH 函数构建 CTE

query_cte = '''
WITH                             -- 关键字
orders_by_date_cte               -- CTE 的名字
AS                               -- 关键字
(                                -- 需要使用() 分割 CTE
    SELECT 
    "订单创建日期" AS order_date
    ,COUNT("订单编号") AS orders_num
FROM example_table
GROUP BY "订单创建日期"
)
SELECT
      order_date
      ,SUM(orders_num)                  -- 聚合函数 SUM()
       OVER(                            -- OVER 关键字,()内定 frame
             ORDER BY order_date        -- 按照 order_date 排序 
             ROWS                       -- 按照表当中的相对位置
             BETWEEN                    -- 界定上下界
                  UNBOUNDED PRECEDING   -- 上界,不设限制
             AND 
                  CURRENT ROW           -- 下界,当前行
             ) AS running_total
FROM orders_by_date_cte;
'''
result = connection.execute(query_cte).fetchall() 
result
[('2020-02-01', 176),
 ('2020-02-02', 398),
 ('2020-02-03', 665),
 ('2020-02-04', 1134),
 ('2020-02-05', 1503),
 ('2020-02-06', 1647),
 ('2020-02-07', 1824),
 ('2020-02-09', 2228),
 ('2020-02-10', 2255),
 ('2020-02-11', 2270),
 ('2020-02-12', 2271),
 ('2020-02-13', 2276),
 ('2020-02-14', 2283),
 ('2020-02-15', 2288),
 ('2020-02-17', 2678),
 ('2020-02-18', 3693),
 ('2020-02-19', 4718),
 ('2020-02-20', 6063),
 ('2020-02-21', 8131),
 ('2020-02-22', 10158),
 ('2020-02-23', 12358),
 ('2020-02-24', 14356),
 ('2020-02-25', 17772),
 ('2020-02-26', 20621),
 ('2020-02-27', 23207),
 ('2020-02-28', 25898),
 ('2020-02-29', 28010)]
## 例4:
### CTE 不会储存在数据库当中
### 下面的语句会报错
query_cte = '''
SELECT
      order_date
      ,SUM(orders_num)                  -- 聚合函数 SUM()
       OVER(                            -- OVER 关键字,()内定 frame
             ORDER BY order_date        -- 按照 order_date 排序 
             ROWS                       -- 按照表当中的相对位置
             BETWEEN                    -- 界定上下界
                  UNBOUNDED PRECEDING   -- 上界,不设限制
             AND 
                  CURRENT ROW           -- 下界,当前行
             ) AS running_total
FROM orders_by_date_cte;
'''
try:
    result = connection.execute(query_cte).fetchall() 
except Exception as e:
    print(e)

no such table: orders_by_date_cte

🔖小贴士 3
WITH 函数创建的 CTE 是依赖于 后续的 SELECT 语句的,他们构成一条完整的语句,因此不需要使用 executescript。

WITH 函数的一个特色是,它可以在一条语句当中创建多个 CTE,并且在每个 CTE中可以使用已经预先定义好的 CTE

例5:with的连续使用

### 假设我们需要按照 订单创建日期 计算累积订单量
### 同时我们希望按照,收获地址展现累计值
### 只返回 北京 和 上海 的结果
query = '''
WITH orders_by_date AS
(
SELECT "订单创建日期" AS order_date
               ,"收货地址 " AS address
               ,COUNT("订单编号") AS orders_num
      FROM example_table
      GROUP BY "订单创建日期","收货地址 "
)
,filter_address AS            -- 新的 CTE
(
SELECT 
    order_date
    ,orders_num 
    ,address
FROM orders_by_date           -- 引用上一个 CTE
WHERE address IN ('北京','上海')
)
SELECT
      t1.order_date
      ,t1.address
      ,SUM(orders_num)                  -- 聚合函数 SUM()
       OVER(                            -- OVER 关键字,()内定 frame
             PARTITION BY address       -- 每个 frame 内只有 address 相同的数据
             ORDER BY order_date        -- 按照 order_date 排序 
             ROWS                       -- 按照表当中的相对位置
             BETWEEN                    -- 界定上下界
                  UNBOUNDED PRECEDING   -- 上界,不设限制
             AND 
                  CURRENT ROW           -- 下界,当前行
             ) AS running_total

FROM filter_address AS t1
'''
result = connection.execute(query).fetchall()
result[:20]


[('2020-02-01', '上海', 48),
 ('2020-02-02', '上海', 114),
 ('2020-02-03', '上海', 197),
 ('2020-02-04', '上海', 330),
 ('2020-02-05', '上海', 414),
 ('2020-02-06', '上海', 459),
 ('2020-02-07', '上海', 499),
 ('2020-02-09', '上海', 596),
 ('2020-02-10', '上海', 603),
 ('2020-02-11', '上海', 608),
 ('2020-02-13', '上海', 609),
 ('2020-02-14', '上海', 612),
 ('2020-02-17', '上海', 685),
 ('2020-02-18', '上海', 834),
 ('2020-02-19', '上海', 963),
 ('2020-02-20', '上海', 1090),
 ('2020-02-21', '上海', 1291),
 ('2020-02-22', '上海', 1508),
 ('2020-02-23', '上海', 1717),
 ('2020-02-24', '上海', 1957)]

5. 一些 WITH 函数的使用经验

  1. 充分利用 WITH 函数创建的 CTE 可以互相引用的特色,在 SQL 脚本当中将使用率最高的一部分语句率先定义成 CTE

  2. 控制 WITH 函数产生的 CTE 的数量:如果 CTE 数量过多的情况可能反而会导致代码维护起来较为吃力

  3. 对于有着复杂 JOIN 的场景,可以充分利用 CTE 在前期进行数据过滤 / 索引利用

  4. 如果一个 CTE 需要被多个查询语句使用,推荐将这个 CTE 变成一个 VIEW

6. 小结 🥡

作为通关题之前的最后一个小关卡,本教程用相当轻量级的方式介绍了两个可以简单优化我们 SQL 语句的方式。这两个函数的应用并不是直接帮助我们解决问题,但是却能够让我们在工作效率上事半功倍。

正如我在第三关的结尾说的:

因此提供相较于纯粹的知识输入和技能培养,我更希望你在本关结束后能够 形成更加良好的 SQL 书写习惯,而这也是这个训练营的核心公式:

良好的习惯 + 知识 / 技能的储备 + 大量的练习 = S Q L 的进一步提升 良好的习惯 + 知识/技能的储备 + 大量的练习 = SQL 的进一步提升 良好的习惯+知识/技能的储备+大量的练习=SQL的进一步提升

无论是善于使用 WITH 函数还是积极去构建 VIEW,本质上都是我们在培养编写 SQL 时的习惯,在面对最终的通关题之前,希望这些建议能够帮助到你~

7. 闯关题

Q1:下面哪个行为是 WITH 函数创建的 CTE 不支持的



# ...your code...
a1 = 'C'  # 在 = 后面填入你的结果,如 a1 = "A"
Q2:下面哪种场景最适合使用 VIEW (相对于使用 CTE )?



在决定使用视图(VIEW)或公用表表达式(CTE,Common Table Expression)时,需要考虑的主要因素是查询的复杂性、重用性和性能。视图更适合于经常需要重用的查询,尤其是当这些查询需要在多个地方使用时。CTE更适合于复杂查询的一部分,它们通常只在一个更大的查询中使用一次。

让我们来看看您给出的各个场景:

A. 统计某张表内的分组平均数,最后按照平均数大小排序输出

  • 这个场景涉及一个相对简单的查询,可能不需要重用。使用CTE或视图都可以,但CTE在一次性查询中可能更方便。

B. 统计某张表内的分组平均数,计算整体的方差

  • 这可能需要两个步骤:首先计算分组平均数,然后计算方差。如果这两个步骤经常被一起使用,可以考虑创建一个视图来存储分组平均数,但如果仅在一个复杂查询中使用,CTE可能更合适。

C. 统计某张表内的分组平均数,计算组内每个成员和平均数的之间的差距

  • 这个场景涉及到对每个组成员进行操作,这是一个比较复杂的查询。CTE在这里可以提供更好的可读性和组织结构。

D. 统计某张表内的分组平均数,并且在该表上列出和平均数差距最大的 3 个成员的id

  • 这个查询相当复杂,需要多步操作。首先是计算分组平均数,然后找到差距最大的成员。这种一次性且复杂的查询更适合用CTE实现。

综合考虑,如果你的需求是经常重用某个特定的统计结果,且这些统计结果不需要在更复杂的查询中作为子查询使用,那么使用视图是个好选择。如果你的统计需求是一次性的,或者是更大查询的一部分,那么使用CTE更加合适。根据你给出的选项,大部分场景似乎都更倾向于使用CTE,尤其是在涉及更复杂的数据处理时。

# ...your code...
a2 = 'D'  # 在 '' 中填入你的结果,如 a2 = "A"
# 生成 csv 作业答案文件
def save_csv(answers):
    import pandas as pd
    
    if len(answers)!=2:
        raise Exception("请检查你的答案数列中是否包含了 2 道题的答案。\n")
    df = pd.DataFrame({"id": ["q1","q2"], "answer": answers})
    df.to_csv("answer_workflow_1_5.csv", index=None, encoding='utf_8_sig')
save_csv([a1,a2])  # 该csv文件在左侧文件树project工作区下,你可以自行右击下载或者读取查看

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: SQL进阶教程第二版PDF是一本非常优秀的SQL语言进一步学习的教程。该教程深入讲解了SQL的高级技巧,适合于那些对SQL的基础知识已经掌握,并且需要更深入了解SQL语言的程序员或者数据分析师。 该教程首先介绍了SQL高级技巧中最重要的概念之一——联结。并通过丰富的实例,深入讲解了如何通过不同类型的联结来提高SQL查询的效率和准确性。同时,该教程还介绍了如何使用视图和子查询来优化SQL查询。此外,该教程还对SQL中的窗口函数、分组与聚合、复杂查询、事务处理等内容进行了详细的讲解。 SQL进阶教程第二版的强大之处在于它提供了大量实例代码和练习题,可以帮助读者更好地理解和掌握高级SQL技巧的运用。同时,该教程还特别注重将SQL技巧与实际应用场景相结合,从而让读者能够更方便地将所学的知识运用到具体的业务场景中去。 总之,SQL进阶教程第二版PDF是一本非常值得阅读和学习的SQL语言进阶教程,无论您是程序员,还是数据分析师,都会从中受益匪浅。 ### 回答2: SQL进阶教程第二版pdf是一本非常经典的SQL学习资源,它适合有一定SQL基础的人群,主要讲解了SQL高级技能和概念。本书的作者结构清晰,语言简明易懂,而且有很多实际案例和提高练习,很适合读者下班时间学习和练习使用。 该书主要包括以下主题:SQL高级操作、数据类型、数据表设计、索引和视图。其中,SQL高级操作包括联合查询、分组查询、排序、子查询和聚合输入。数据表设计部分详细介绍了如何设计关系数据表,并提供了实际案例参考。另外,索引和视图分别讲解了如何提升SQL查询速度和如何创建虚拟表格,以便于更高效地管理和处理数据。 此外,本书还强调了SQL实践中的常见问题和错误,并提供了解决方案。而且在不同数据库之间的差异性上也进行了详细介绍。 总的来说,SQL进阶教程第二版pdf对于有一定SQL基础并想要进一步提升技能的人员非常有帮助,它提供了很多实际案例和练习,让读者更快掌握SQL高级技能。 ### 回答3: SQL进阶教程第二版PDF是一本针对已经掌握基本SQL语言知识,并希望进一步扩展和深化技能的人群所准备的一本书。从基础连接和子查询开始,这本书带领我们进入了更高深的SQL技术世界,包括如何高效地应用索引、复杂查询的调优以及跨平台数据交换等方面的内容。 这本书主要分为四个部分:基础连接与子查询、优化查询、XML、JSON和全文本搜索、数据交换和分布式数据库。无论是哪个部分,作者都以实际案例作为引入,然后通过详细分析案例中的难点问题,引导我们深入了解相关SQL语句和技术。 通过学习这本书,读者能够了解到高效地应用SQL语句的方法,同时掌握如何应对大型数据库查询的相关技巧,提高数据查询效率和数据分析能力。此外,作者还对数据库查询中的特殊要求进行了详细介绍,包括如何使用XML、JSON、全文本搜索等技术,以及如何实现跨平台的数据交换和分布式数据库的使用。 总之,SQL进阶教程第二版PDF是一本深入学习SQL的好书,对于想要进一步提高技能和在实际工作中遇到挑战的数据库程序员和数据分析师来说,是一本不可多得的参考资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值