V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
qiujin
V2EX  ›  PHP

框架改名成 phpegg 了,求 star,顺便介绍下其内置的 DB Query Builder 的联表查询。

  •  
  •   qiujin · 2017-10-09 19:42:15 +08:00 · 3530 次点击
    这是一个创建于 2604 天前的主题,其中的信息可能已经有所发展或是发生改变。

    之前叫 my_php_framework 被吐槽,决定改名为 phpegg,域名 phpegg.com 也一并拿下了。

    项目新地址 https://github.com/qiu-jin/phpegg 目前星少的可怜,求 star.

    前一帖发的 readme 重点介绍了几种应用模式,忽略 DB Query Builder 的介绍。虽然数据库只是在驱动层实现不算框架的核心功能,但是数据库是使用最频繁的,而且内置的 query builder 花费了我些功夫,特别是支持多种联表查询方法,平常使用很方便。

    query builder 的一些常用的 where order limit 等方法这里就不介绍了(大部分框架和库这部分都大同小异)

    query builder 的常用方法使用可以看这个临时文档 https://qiu-jin.gitbooks.io/docs-phpegg-com/shu-ju-ku/lian-biao-cha-xun.html

    下面重点介绍下联表查询方法

    在无需使用 ORM 无需定义数据库模型的情况下,用联表查询方法就可以连表查询多表数据。

    支持一对一,多对一,一对多,多对多等多种数据表关联。

    并且在默认设定和联表作用域下,查询语法更加简洁易懂,也不用像一些框架或库一样需要嵌杂原生的 SQL 的来实现( Query 语法和原生 SQL 混杂在一起不伦不类,不如直接用 SQL )。

    示例:查询主题 1 及其发布者与评论信息

    $db->post->join('user')->on('user_id', 'id')->select('name')->with('comment')->get(1)
    
    // 生成并执行的 SQL
    SELECT `post`.*,`user`.`name` AS `user_name` FROM `post` LEFT JOIN `user` ON `post`.`user_id` = `user`.`id` WHERE `post`.`id` = '1'
    SELECT * FROM `comment` WHERE `post_id` = '1'
    

    例中关联到了 post user comment 3 个表,其中 post 作为主表,user 是 join 从表,comment 是 with 从表。

    post 表与 user 表是多对一关系,一个 post 只有一个 user 发表者,一个 user 能发表多个 post 主题。

    post 表与 comment 表是一对多关系,一个 post 有多条 comment 评论,一条 comment 只属于一个 post 主题。

    先主表 post join user 表查询数据,表之间使用 on 方法关联字段,得到查询数据后使用 with 逻辑联表方法查询 comment 数据,表之间关联使用默认设定,然后组合数据返回。

    另一个示例:查找积分大于 0 的用户,及其在 2017-10-01 后发表的主题。

    $db->user->select('id', 'name')// 查找用户 id name
       ->sub('account')->where('score', '>', 0)// 查找用户积分 score 大于 0 的记录
       ->with('post')->select('id', 'title')->where('time', '>', '2017-10-01') //查找主表用户 2017-10-01 后发布主题的 id 和标题
       ->find(); //执行查询
    
    // 生成并执行的 SQL
    SELECT `id`,`name` FROM `user` WHERE `id` IN (SELECT `user_id` FROM `account` WHERE `score` > '0') 
    SELECT `id`,`title`,`user_id` FROM `post` WHERE `user_id` IN ('1','2','3','5') AND `time` > '2017-10-01'
    

    例中关联到了 user account post 3 个表,其中 user 作为主表,account 是子查询从表,post 是 with 从表。

    此连表查询示例的作用域:$db->usersub('account') 之间是主表user作用域,sub('account')with('post') 之间是从表account作用域,with('post')find() 之间是从表post作用域,在作用域内whereselect order limit等方法不需要显式的指定表名,因为作用域确定了这些方法是作用于那张表,并在生成 SQL 时自动处理。

    此联表查询示例用到的默认设定:在例中 user 表和 account post 表是如何关联的?在我们并没有显式的指定的情况下,会默认使用主表的 id 字段与从表中字段名为主表名+id的字段关联,此例中恰好 account post 表中都有user_id字段,所以我们可以直接使用默认设定,如不用默认设定我们也可以使用 on 方法来指定关联字段。

    1 join 方法

    生成原生 SQL JOIN 语句联表查询多表数据.

    通常用一对一和多对一表关系场合

    一对一:查询用户信息与其帐号积分信息(一个用户只有一个积分帐号表)

    $db->user->join('account')->select('score')->get($user_id);
    
    SELECT `user`.*,`account`.`score` AS `account_score` FROM `user` LEFT JOIN `account` ON `user`.`id` = `account`.`user_id` WHERE `user`.`id` = '1'
    

    多对一:查询最近 10 个主题以及发布者信息(一个用户可以发布多个主题)

    $db->post->order('id', true)->limit(10)->join('user')->on('user_id', 'id')->select('id', 'name')->find();
    
    SELECT `post`.*,`user`.`id` AS `user_id`,`user`.`name` AS `user_name` FROM `post` LEFT JOIN `user` ON `post`.`user_id` = `user`.`id` ORDER BY `post`.`id` DESC LIMIT 10
    
    2 with 方法

    使用逻辑联表查询多表数据。

    通常用于一对多和多对多表关系场合

    在默认优化条件下只需要 1+1 次 SQL 查询,先查主表数据,然后根据主表数据 where in 查询从表数据,最后逻辑组合 2 表数据。

    查询一个用户及其最近 10 个主题

    $db->user->with('post')->order('id', true)->limit(10)->get($user_id)
    
    SELECT * FROM `user` WHERE `id` = '1' LIMIT 1
    SELECT * FROM `post` WHERE `user_id` = '1' ORDER BY `id` DESC LIMIT 10
    

    查询主题以及回复评论第 1 页

    $db->post->with('comment')->page(1)->get($post_id)
    
    SELECT * FROM `post` WHERE `id` = '1' LIMIT 1
    SELECT * FROM `comment` WHERE `post_id` = '1' LIMIT 0,30
    
    3 relate 方法

    通常用于多对多表关系场合,并且有一个关系表存储 2 个表的对应关系。

    在默认优化条件下只需要 1+1+1 次 SQL 查询,先查主表数据,在查关系表数据,然后根据关系表数据查询从表数据,最后逻辑组合 3 表数据。

    查询一个用户及其最近收藏书签的主题,其中 bookmark 书签表是关系表保存 user 和 post 多对多的映射关系。

    $db->user->relate('post')->on('bookmark')->get($user_id);
    
    SELECT * FROM `user` WHERE `id` = '1' LIMIT 1
    SELECT `user_id`, `post_id` FROM `bookmark` WHERE `user_id` IN ('1')
    SELECT * FROM `post` WHERE `id` IN ('2','3','5')
    
    4 sub 方法

    生成原生 SQL 子查询语句联表查询主表数据。

    子查询通常只做为主表的过滤条件,用户不需要其本身数据。

    查询最新一个主题的作者的信息。

    $db->user->sub('post')->order('id', ture)->get()
    
    SELECT * FROM `user` WHERE `id` IN (SELECT `user_id` FROM `post` ORDER BY `id` DESC)  LIMIT 1
    
    5 union 方法

    使用原生 SQL union 语法联表查询主表数据。

    union 用于表结构相同的多个表。

    $db->table1->union('table2')->union('table3')->find()
    
    第 1 条附言  ·  2017-10-09 23:03:11 +08:00
    第 2 条附言  ·  2017-10-13 12:40:20 +08:00
    文档已经整理放到新的站点 http://www.phpegg.com
    23 条回复    2017-10-16 10:03:59 +08:00
    sexrobot
        1
    sexrobot  
       2017-10-10 01:00:39 +08:00
    PHP 个蛋
    ericgui
        2
    ericgui  
       2017-10-10 01:42:21 +08:00 via iPhone
    应该 Egg PHP,原因挺简单,你看那么多 js 库,都是 XXjs,比如 vuejs,比如 reactjs,比如 nodejs
    huntzhan
        3
    huntzhan  
       2017-10-10 02:38:14 +08:00
    ......我觉得完全不是起名字的问题呀。建议你思考一下:

    1. 你这个项目解决了什么问题?别人在什么场景下会用你这个项目?
    2. 这个项目有什么同类型的项目?相比之下你的优势在哪里?
    3. 如果( 2 )里面没有竞品,重新思考( 1 )是否真的有价值。

    软件工程师都是很现实的,遇到问题找轮子,货比三家挑个最好的。

    你这个项目应该是 boilerplate 性质的,建议你看一下 https://github.com/sahat/hackathon-starter,思考一下为什么 hackathon-starter 可以有 21k stars。
    xeis
        4
    xeis  
       2017-10-10 07:24:06 +08:00 via Android
    有使用文档么?
    xeis
        5
    xeis  
       2017-10-10 07:24:34 +08:00 via Android
    phpegg.com 找不到 dns
    allenhu
        6
    allenhu  
       2017-10-10 08:00:52 +08:00 via Android
    @huntzhan 就是为了 star 啊
    qiujin
        7
    qiujin  
    OP
       2017-10-10 09:18:12 +08:00
    @xeis 先看项目主页的 readme,后面会迁移到 http://docs.phpegg.com
    qiujin
        8
    qiujin  
    OP
       2017-10-10 09:27:12 +08:00
    @huntzhan 这是当然的啦,readme 也有介绍,我觉得还能算是特点吧,虽说不是很突出,不过一口也吃不成胖子,得持续更新。另外大家也不要纠结什么蛋不蛋的了啊,我明明写了一大坨 Query Builder 联表查询介绍,好像被忽视了。
    vincenth520
        9
    vincenth520  
       2017-10-10 10:31:32 +08:00
    厉害,已 star
    qiujin
        10
    qiujin  
    OP
       2017-10-10 11:05:01 +08:00
    @vincenth520 谢谢支持
    xeis
        11
    xeis  
       2017-10-10 12:17:52 +08:00 via Android
    一看到复杂的框架就头大,能否只使用数据库部分的功能,删掉路由、模板之类的东西。
    qiujin
        12
    qiujin  
    OP
       2017-10-10 12:45:17 +08:00
    @xeis 可以的,可以把数据库部分单独拿出来用(在 framework/driver/db ),这个框架耦合度很低,各应用模式之间没有任何关联,只用到一种的话其它的都可以干掉,多个驱动之间关联也少,不用的都可以干掉。

    路由也是可选选项,默认都是使用 url 默认调度,模板也是可选选项,直接用 php 没任何问题。
    explon
        13
    explon  
       2017-10-10 12:46:31 +08:00
    可是你说的这些 Zend DB 比你强得多
    qiujin
        14
    qiujin  
    OP
       2017-10-10 13:20:05 +08:00
    @explon 没怎么用过 zendframework,特意跑去看了下 https://docs.zendframework.com/zend-db/,还真没看出来 Zend DB 在联表查询方面强在哪儿(且不说它实现过于臃肿),query builder 常用的 select where order limit 等方法大部分框架和库都大同小异不一定谁比谁强,至于联表查询我找了半天找到了一个 join,看看它的写法$select -> join('table1','table2.field2= table1.field1'),table2.field2= table1.field1 是什么鬼到底是原生 SQL 还是 query 语法,混在一起不伦不类,还不如直接写 SQL,还有子查询 一对多 多得多的联表查询方法在哪我也没找到。
    qiujin
        15
    qiujin  
    OP
       2017-10-10 13:20:21 +08:00
    去搜了下 Zend DB 子查询的实现 https://stackoverflow.com/questions/1340564/writing-a-subquery-using-zend-db
    得分最高的回答的演示代码的其中一行。
    ->where('sles.id = (SELECT MAX(id) FROM sle_instances WHERE sle_id = sles.id)')
    我去这算什么,这个直接写 SQL 有什么区别。
    vindurriel
        16
    vindurriel  
       2017-10-10 13:38:16 +08:00
    with 语句有点怪,通常的习惯是一行查询代码对应一行 sql
    qiujin
        17
    qiujin  
    OP
       2017-10-10 14:30:28 +08:00
    @vindurriel with 语句是逻辑联表查询,一次查询的确需要执行 1+1 次 SQL,它类似于 ActiveRecord 型的 ORM 中的 hasMany 设定,相对 ORM 优点是不用生成大量对象也不用写 model 模型代码定义每个表和表之间关系。

    写一个简单的例子,像 v2 这样的社区有多个主题贴子,每一个主题也有多个回复评论,如果我一次想要查询自己所有的主题贴子和所有的主题贴子的回复评论回复怎么办?

    用常规写法,先查询所有的主题贴,再循环查询每个主题贴的回复评论

    $posts = $this->db->post->where('name', 'qiujin')->find();
    foreach ($posts as $i => $post) {
    $posts[$i] = $this->db->comment->where('post_id', $post['id'])->find();
    }
    return $posts;

    使用 with 语句一行代码即可。

    return $this->db->post->where('name', 'qiujin')->with('comment')->find();

    对比就可以知道常规写法不仅啰嗦,而且低效,因为常规写法执行了 1+N 次 SQL。

    而 with 语句简洁明了,查询 post 数据后,在把 post 数据中的 id 抽出组成 where in 查询 comment 表数据,最后把 comment 数据组合分配给 post 返回,其中只执行了 1+1 次 SQL,比常规写法效率高很多。
    crabRunning
        18
    crabRunning  
       2017-10-10 18:54:30 +08:00
    我发现一个问题 phper 都系欢喜秀一些没有单元测试,没有 ci,没有完整文档的东西~!
    huntzhan
        19
    huntzhan  
       2017-10-10 20:12:59 +08:00
    @qiujin good luck then
    qiujin
        20
    qiujin  
    OP
       2017-10-13 12:46:29 +08:00
    文档已经整理放到新的站点 http://www.phpegg.com
    tanszhe
        21
    tanszhe  
       2017-10-15 21:30:05 +08:00
    sql 有没有防注入的功能呢?
    $db->user->relate('post')->on('bookmark')->get(1 or 1);

    建议采用预处理的方式
    tanszhe
        22
    tanszhe  
       2017-10-15 21:34:25 +08:00
    with 和 relate 功能确实很好 👍
    qiujin
        23
    qiujin  
    OP
       2017-10-16 10:03:59 +08:00
    @tanszhe 嗯,是用预处理处理的参数,只是 LOG 打印 SQL 时采用拼接的方式,这样看起来更直观。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3189 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 13:25 · PVG 21:25 · LAX 05:25 · JFK 08:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.