侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

介绍PostgreSQL中的Lateral类型

数据库  /  管理员 发布于 5年前   391

PostgreSQL 9.3 用了一种新的联合类型! Lateral联合的推出比较低调,但它实现了之前需要使用编写程序才能获得的强大的新查询. 在本文中, 我将会介绍一个在 PostgreSQL 9.2 不可能被实现的渠道转换分析.
什么是 LATERAL 联合?

对此的最佳描述在文档中 可选 FROM 语句清单 的底部:

LATERAL 关键词可以在前缀一个 SELECT FROM 子项. 这能让 SELECT 子项在FROM项出现之前就引用到FROM项中的列. (没有 LATERAL 的话, 每一个 SELECT 子项彼此都是独立的,因此不能够对其它的 FROM 项进行交叉引用.)
…
当一个 FROM 项包含 LATERAL 交叉引用的时候,查询的计算过程如下: 对于FROM像提供给交叉引用列的每一行,或者多个FROM像提供给引用列的行的集合, LATERAL 项都会使用行或者行的集合的列值来进行计算. 计算出来的结果集像往常一样被加入到联合查询之中. 这一过程会在列的来源表的行或者行的集合上重复进行.

这种计算有一点密集。你可以比较松散的将 LATERAL 联合理解作一个 SQL 的foreach 选择, 在这个循环中 PostgreSQL 将循环一个结果集中的每一行,并将那一行作为参数来执行一次子查询的计算.

我们可以用这个来干些什么?

看看下面这个用来记录点击事件的表结构:
 

CREATE TABLE event (  user_id BIGINT,  event_id BIGINT,  time BIGINT NOT NULL,  data JSON NOT NULL,  PRIMARY KEY (user_id, event_id))

每一个事件都关联了一个用户,拥有一个ID,一个时间戳,还有一个带有事件属性的JSON blob. 在堆中,这些属性可能包含一次点击的DOM层级, 窗口的标题,会话引用等等信息.

加入我们要优化我们的登录页面以增加注册. 第一步就是要计算看看我们的哪个渠道转换上正在丢失用户.

示例:一个注册流程的个步骤之间的渠道转换率.


假设我们已经在前端配备的装置,来沿着这一流程来记录事件日志,所有的数据都会保存到上述的事件数据表中.[1] 最开始的问题是,我们要计算有多少人查看了我们的主页,而他们之中有百分之多少在那次查看了主页之后的两个星期之内输入了验证信息. 如果我们使用 PostgreSQL 较老的版本, 我们可能需要使用PL/pgSQL这一PostgreSQL内置的过程语言 来编写一些定制的函数. 而在 9.3 中, 我们就可以使用一个 lateral 联合,只用一个搞笑的查询就能计算出结果,不需要任何扩展或者 PL/pgSQL.

 

SELECTuser_id,view_homepage,view_homepage_time,enter_credit_card,enter_credit_card_timeFROM (-- Get the first time each user viewed the homepage.SELECTuser_id,1 AS view_homepage,min(time) AS view_homepage_timeFROM eventWHEREdata->>'type' = 'view_homepage'GROUP BY user_id) e1 LEFT JOIN LATERAL (-- For each row, get the first time the user_id did the enter_credit_card-- event, if one exists within two weeks of view_homepage_time.SELECT1 AS enter_credit_card,time AS enter_credit_card_timeFROM eventWHEREuser_id = e1.user_id ANDdata->>'type' = 'enter_credit_card' ANDtime BETWEEN view_homepage_time AND (view_homepage_time + 1000*60*60*24*14)ORDER BY timeLIMIT 1) e2 ON true

没有人会喜欢30多行的SQL查询,所以让我们将这些SQL分成片段来分析。第一块是一段普通的 SQL:
 

SELECT  user_id,  1 AS view_homepage,  min(time) AS view_homepage_timeFROM eventWHERE  data->>'type' = 'view_homepage'GROUP BY user_id

也就是要获取到每个用户最开始触发 view_homepage 事件的时间. 然后我们的 lateral 联合就可以让我们迭代结果集的每一行,并会在接下来执行一次参数化的子查询. 这就等同于针对结果集的每一行都要执行一边下面的这个查询:
 

SELECT  1 AS enter_credit_card,  time AS enter_credit_card_timeFROM eventWHERE  user_id = e1.user_id AND  data->>'type' = 'enter_credit_card' AND  time BETWEEN view_homepage_time AND (view_homepage_time + 1000*60*60*24*14)ORDER BY timeLIMIT 1

例如,对于每一个用户,要获取他们在触发 view_homepage_time 事件后的两星期内触发 enter_credit_card  事件的时间. 因为这是一个lateral联合,我们的子查询就可以从之前的子查询出引用到 view_homepage_time 结果集. 否则,子查询就只能单独执行,而没办法访问到另外一个子查询所计算出来的结果集.

之后哦我们整个封装成一个select,它会返回像下面这样的东西:

user_id | view_homepage | view_homepage_time | enter_credit_card | enter_credit_card_time---------+---------------+--------------------+-------------------+------------------------567 | 1 | 5234567890 | 1 | 5839367890234 | 1 | 2234567890 | |345 | 1 | 3234567890 | |456 | 1 | 4234567890 | |678 | 1 | 6234567890 | |123 | 1 | 1234567890 | |... 


因为这是一个左联合,所以查询结果集中会有不匹配 enter_credit_card 事件的行,只要有 view_homepage 事件就行. 如果我们汇总所有的数值列,就会得到渠道转换的一个清晰汇总:
 

SELECT  sum(view_homepage) AS viewed_homepage,  sum(enter_credit_card) AS entered_credit_cardFROM (  -- Get the first time each user viewed the homepage.  SELECT  user_id,  1 AS view_homepage,  min(time) AS view_homepage_time  FROM event  WHERE  data->>'type' = 'view_homepage'  GROUP BY user_id) e1 LEFT JOIN LATERAL (  -- For each (user_id, view_homepage_time) tuple, get the first time that  -- user did the enter_credit_card event, if one exists within two weeks.  SELECT  1 AS enter_credit_card,  time AS enter_credit_card_time  FROM event  WHERE  user_id = e1.user_id AND  data->>'type' = 'enter_credit_card' AND  time BETWEEN view_homepage_time AND (view_homepage_time + 1000*60*60*24*14)  ORDER BY time  LIMIT 1) e2 ON true

… 它会输出:

 viewed_homepage | entered_credit_card-----------------+---------------------827 | 10


我们可以向这个渠道中填入带有更多lateral联合的中间步骤,来得到流程中我们需要重点改进的部分. 让我们在查看主页和输入验证信息之间加入对使用示例步骤的查询.
 

SELECT  sum(view_homepage) AS viewed_homepage,  sum(use_demo) AS use_demo,  sum(enter_credit_card) AS entered_credit_cardFROM (  -- Get the first time each user viewed the homepage.  SELECT  user_id,  1 AS view_homepage,  min(time) AS view_homepage_time  FROM event  WHERE  data->>'type' = 'view_homepage'  GROUP BY user_id) e1 LEFT JOIN LATERAL (  -- For each row, get the first time the user_id did the use_demo  -- event, if one exists within one week of view_homepage_time.  SELECT  user_id,  1 AS use_demo,  time AS use_demo_time  FROM event  WHERE  user_id = e1.user_id AND  data->>'type' = 'use_demo' AND  time BETWEEN view_homepage_time AND (view_homepage_time + 1000*60*60*24*7)  ORDER BY time  LIMIT 1) e2 ON true LEFT JOIN LATERAL (  -- For each row, get the first time the user_id did the enter_credit_card  -- event, if one exists within one week of use_demo_time.  SELECT  1 AS enter_credit_card,  time AS enter_credit_card_time  FROM event  WHERE  user_id = e2.user_id AND  data->>'type' = 'enter_credit_card' AND  time BETWEEN use_demo_time AND (use_demo_time + 1000*60*60*24*7)  ORDER BY time  LIMIT 1) e3 ON true

这样就会输出:

 viewed_homepage | use_demo | entered_credit_card-----------------+----------+---------------------827 | 220 | 86 


从查看主页到一周之内使用demo,再到一周以内向其输入信用卡信息,这就向我们提供了三个步骤的通道转换. 从此,功能强大的 PostgreSQL 使得我们可以深入分析这些数据结果集,并对我们的网站性能进行整体的分析. 接着我们可能会有下面这些问题要解决:

  •     使用demo是否能增加注册的可能性?
  •     通过广告找到我们主页的用户是否同来自其他渠道的用户拥有相同的转换率?
  •     转换率会跟随不同的 A/B 测试变量发生怎样的变化?

这些问题的答案会直接影响到产品的改进,它们可以从 PostgreSQL 数据库中找到答案,因为现在它支持 lateral 联合.


没有 lateral 联合,我们就只能借助 PL/pgSQL 来做这些分析。或者,如果我们的数据集很小,我们可能就不会碰这些复杂、低效的查询. 在一项探索性数据研究使用场景下,你可能只是将数据从 PostgreSQL 里面抽取出来,并使用你所选择的脚本语言来对其进行分析。但是其实还存在更强大的理由来用SQL表述这些问题, 特别是如果你正想要把整个全封装到一套易于理解的UI中,并向非技术型用户发布功能 的时候.

注意这些查询可以被优化,以变得更加高效. 在本例中,如果我们在 (user_id, (data->>'type'), time)上创建一个btree索引, 我们只用一次索引查找就能针对每一个用户计算每一个渠道步骤. 如果你使用的是SSD,在上面做查找花费是很小的,那这就足够了。而如果不是,你就可能需要用稍微不同的手段来图示化你的数据,详细的内容我会留到另外一篇文章之中进行介绍.



  • 上一条:
    SQL语句实现删除重复记录并只保留一条
    下一条:
    详细讲解PostgreSQL中的全文搜索的用法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 分库分表的目的、优缺点及具体实现方式介绍(0个评论)
    • DevDB - 在 VS 代码中直接访问数据库(0个评论)
    • 在ubuntu系统中实现mysql数据存储目录迁移流程步骤(0个评论)
    • 在mysql中使用存储过程批量新增测试数据流程步骤(0个评论)
    • php+mysql数据库批量根据条件快速更新、连表更新sql实现(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-06
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-01
    • 2018-05
    • 2018-10
    • 2018-11
    • 2020-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2021-02
    • 2021-04
    • 2021-07
    • 2021-08
    • 2021-11
    • 2021-12
    • 2022-02
    • 2022-03
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-07
    • 2023-08
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-03
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客