4:使用数据库的表连接(Yii权威指南)

来源:互联网 发布:2017云计算大会 ppt 编辑:程序博客网 时间:2024/05/29 16:19
我们已经了解了怎样使用 Active Record (AR) 从单个数据表中获取数据。 在本节中,我们讲解怎样使用 AR 连接多个相关数据表并取回关联(join)后的数据集。

 我们使用如下所示的实体-关系(ER)图中的数据结构演示此节中的例子



 从数据库的角度来说,表 A 和 B 之间有三种关系:一对多(one-to-many,例如 tbl_user 和 tbl_post),一对一( one-to-one 例如 tbl_user和 tbl_profile)和 多对多(many-to-many 例如 tbl_category 和tbl_post)。

在 AR 中,有四种关系:

  • BELONGS_TO(属于): 如果表 A 和 B 之间的关系是一对多,则 表 B 属于 表 A (例如 Post 属于 User);

  • HAS_MANY(有多个): 如果表 A 和 B 之间的关系是一对多,则 A 有多个 B (例如 User 有多个 Post);

  • HAS_ONE(有一个): 这是 HAS_MANY 的一个特例,A 最多有一个 B (例如 User 最多有一个 Profile);

  • MANY_MANY: 这个对应于数据库中的 多对多 关系。 由于多数 DBMS 不直接支持 多对多 关系,因此需要有一个关联表将 多对多 关系分割为 一对多 关系。 在我们的示例数据结构中,tbl_post_category 就是用于此目的的。在 AR 术语中,我们可以解释 MANY_MANY 为 BELONGS_TO 和 HAS_MANY 的组合。 例如,Post 属于多个(belongs to many) Category ,Category 有多个(has many) Post.


AR 中定义关系需要覆盖 CActiveRecord 中的 relations() 方法。此方法返回一个关系配置数组。每个数组元素通过如下格式表示一个单一的关系。
'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...additional options)
其中 VarName 是关系的名字;RelationType 指定关系类型,可以是一下四个常量之一:self::BELONGS_TO,self::HAS_ONEself::HAS_MANY and self::MANY_MANYClassName 是所关联的 AR 类的名字;ForeignKey 指定关系中使用的外键(一个或多个)。额外的选项可以在每个关系的最后指定(稍后详述)。


以下代码演示了怎样定义 User 和 Post 类的关系:
class Post extends CActiveRecord{    ......     public function relations()    {        return array(            'author'=>array(self::BELONGS_TO, 'User', 'author_id'),            'categories'=>array(self::MANY_MANY, 'Category',                'tbl_post_category(post_id, category_id)'),        );    }} class User extends CActiveRecord{    ......     public function relations()    {        return array(            'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),            'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),        );    }}
注:外键可能是复合的,包含两个或更多个列。 这种情况下,我们应该将这些外键名字链接,中间用空格或逗号分割。对于 MANY_MANY 关系类型, 关联表的名字必须也必须在外键中指定。例如, Post 中的 categories 关系由外键 tbl_post_category(post_id, category_id) 指定。

AR 类中的关系定义为每个关系向类中隐式添加了一个属性。在一个关联查询执行后,相应的属性将将被以关联的 AR 实例填充。 例如,如果 $author 代表一个 User AR 实例, 我们可以使用 $author->posts 访问其关联的Post 实例。


执行关联查询
执行关联查询,我们可以读取一个AR实例的属性,如果第一次读取就初始化AR实例并使用当前 AR 实例的主键过滤,否则就将关联的AR实例的保存在属性中。这就是懒惰式加载(也称为迟加载)。例如:
// 获取 ID 为 10 的帖子      $post=Post::model()->findByPk(10);
// 获取帖子的作者(author): 此处将执行一个关联查询     $author=$post->author;

懒惰式加载用起来很方便,但在某些情况下并不高效。如果我们想获取 N 个帖子的作者,使用这种懒惰式加载将会导致执行 N 个关联查询。 这种情况下,我们应该改为使用 渴求式加载(eager loading)方式。
渴求式加载方式会在获取主 AR 实例的同时获取关联的 AR 实例。 这是通过在使用 AR 中的 find 或 findAll 方法时配合使用 with 方法完成的。例如:
$posts=Post::model()->with('author')->findAll();

上述代码将返回一个 Post 实例的数组。与懒惰式加载方式不同,在我们访问每个 Post 实例中的 author 属性之前,它就已经被关联的 User 实例填充了。 渴求式加载通过 一个 关联查询返回所有帖子及其作者,而不是对每个帖子执行一次关联查询。

我们可以在 with() 方法中指定多个关系名字,渴求式加载将一次性全部取回他们。例如,如下代码会将帖子连同其作者和分类一并取回。

$posts=Post::model()->with('author','categories')->findAll();

从版本 1.1.0 开始,渴求式加载也可以通过指定 CDbCriteria::with 的属性执行,就像下面这样:
$criteria=new CDbCriteria;$criteria->with=array(    'author.profile',    'author.posts',    'categories',);$posts=Post::model()->findAll($criteria);

或者

$posts=Post::model()->findAll(array(    'with'=>array(        'author.profile',        'author.posts',        'categories',    ));
上述示例将取回所有帖子及其作者和所属分类。它还同时取回每个作者的简介(author.profile)和帖子(author.posts)



关系型查询选项
我们提到在关系声明时可以指定附加的选项。这些 名-值 对形式的选项用于自定义关系型查询:
  • select: 关联的 AR 类中要选择(select)的列的列表。 默认为 '*',即选择所有列。此选项中的列名应该是已经消除歧义的。

  • condition: 即 WHERE 条件。默认为空。此选项中的列名应该是已经消除歧义的。

  • params: 要绑定到所生成的 SQL 语句的参数。应该以 名-值 对数组的形式赋值。此选项从 1.0.3 版起有效。

  • on: 即 ON 语句。此处指定的条件将会通过 AND 操作符附加到 join 条件中。此选项中的列名应该是已经消除歧义的。 此选项不会应用到 MANY_MANY 关系中。此选项从 1.0.2 版起有效。

  • order: 即 ORDER BY 语句。默认为空。 此选项中的列名应该是已经消除歧义的。

  • with: a list of child related objects that should be loaded together with this object. Be aware that using this option inappropriately may form an infinite relation loop.

  • joinType: type of join for this relationship. It defaults to LEFT OUTER JOIN.

  • alias: the alias for the table associated with this relationship. This option has been available since version 1.0.1. It defaults to null, meaning the table alias is the same as the relation name.

  • together: whether the table associated with this relationship should be forced to join together with the primary table and other tables. This option is only meaningful for HAS_MANY and MANY_MANYrelations. If this option is set false, the table associated with the HAS_MANY or MANY_MANY relation will be joined with the primary table in a separate SQL query, which may improve the overall query performance since less duplicated data is returned. If this option is set true, the associated table will always be joined with the primary table in a single SQL query, even if the primary table is paginated. If this option is not set, the associated table will be joined with the primary table in a single SQL query only when the primary table is not paginated. For more details, see the section "Relational Query Performance". This option has been available since version 1.0.3.

  • join: the extra JOIN clause. It defaults to empty. This option has been available since version 1.1.3.

  • group: the GROUP BY clause. It defaults to empty. Column names referenced in this option should be disambiguated.

  • having: the HAVING clause. It defaults to empty. Column names referenced in this option should be disambiguated. Note: option has been available since version 1.0.1.

  • index: the name of the column whose values should be used as keys of the array that stores related objects. Without setting this option, an related object array would use zero-based integer index. This option can only be set for HAS_MANY and MANY_MANY relations. This option has been available since version 1.0.7.

In addition, the following options are available for certain relationships during lazy loading:

  • limit: limit of the rows to be selected. This option does NOT apply to BELONGS_TO relation.

  • offset: offset of the rows to be selected. This option does NOT apply to BELONGS_TO relation.

例如:
class User extends CActiveRecord{    public function relations()    {        return array(            'posts'=>array(self::HAS_MANY, 'Post', 'author_id',                            'order'=>'posts.create_time DESC',                            ),            'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),        );    }}
如果我们访问$author->posts,则我们可以得到用户的posts按照时间顺序排序。


关系表相同列名的处理方法
当一个列名出现在2个或多个表的时候,我们就需要区别开来。最好的办法是在列名前加前缀。
你的主表的前缀名默认是t,而你的关系表默认前缀是表名。现在架设2个表Post和Comment有同一个列名是create_time,则我们可以如下使用:
$posts=Post::model()->with('comments')->findAll(array(    'order'=>'t.create_time, comments.create_time'));

动态关系执行选项
在1.0.5的版本中,动态选项可以在懒加载中使用,例如我们使用User模型得到了数据,并且希望得到他发布的文章status=1的列表,可以使用动态加载,例如:
$user=User::model()->findByPk(1);$posts=$user->posts(array('condition'=>'status=1'));


关系的执行性能问题
假设我们现在需要得到发布的文章数据和留言数据,而一篇文章有10条留言数据。所以当我们执行一个大的复杂的Joining SQL语句时,我们会得到许多的数据并且每个留言对应他相应的文章,这样就造成了许多的冗余数据。架设我们执行2条SQL语句先得到所有的文章,在通过文章得到留言,虽然没有数据的冗余,但增加了数据库的开销。
现在Yii提供一个together执行选项给我们,默认Yii使用的是第一种方法,生成一个大的SQL语句来。但是我们现在可以将together=>false来决定选择第二种方法,使用方法:
public function relations(){    return array(        'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false),    );} 
当然我们也可以动态的使用:
$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll();

执行统计的Query
Yii提供一个统计的方法来检索相关对象,例如检索每篇文章的留言数量,产品的平均等级等。Statistical query只能够被执行在HAS_MANY或者MANY_MANY的对象中。
执行一个统计Query和执行一个关联Query非常相似。首先我们需要在relations()里面定义statistical query,例如:
class Post extends CActiveRecord{    public function relations()    {        return array(            'commentCount'=>array(self::STAT, 'Comment', 'post_id'),            'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'),        );    }} 
在这里我们定义了2个statistical querycommentCount calculates the number of comments belonging to a post, and categoryCount calculates the number of categories that a post belongs to. 
然后我们可以通过$post->commentCount来取回得到的数据,这是一种懒惰式的执行方法,如果我们想直接获得数据,就可以使用渴求式的执行方法,例如:
$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();
上面的SQL语句会直接返回所有的counts数据。


原创粉丝点击