|
| 1 | +虚字段 |
| 2 | +###### |
| 3 | + |
| 4 | +虚字段可以将任意的SQL表达式当作模型中的字段来使用,这些字段不能保存数据,但是像其他字段一样可以进行读操作。和其他字段一样。作为model的键名使用, |
| 5 | + |
| 6 | + |
| 7 | +创建虚字段 |
| 8 | +========== |
| 9 | + |
| 10 | +创建虚拟字段很简单。在每个模型中可以设置 ``$virtualFields`` 属性,该属性是一个数组, |
| 11 | +形式如 字段名 => 表达式。下面是一个使用MySQL定义虚字段的例子:: |
| 12 | + |
| 13 | + public $virtualFields = array( |
| 14 | + 'name' => 'CONCAT(User.first_name, " ", User.last_name)' |
| 15 | + ); |
| 16 | + |
| 17 | +PostgreSQL中的写法:: |
| 18 | + |
| 19 | + public $virtualFields = array( |
| 20 | + 'name' => 'User.first_name || \' \' || User.last_name' |
| 21 | + ); |
| 22 | + |
| 23 | +在随后的查找操作中,User的查询结果中会一个多出键名为 ``name`` 键值为连接内容的字段。虚字段不要与数据库中已存在的字段名相同。否则会导致错误。 |
| 24 | + |
| 25 | +有时可以不带 **User.first\_name**,如果不遵循规范,(比如和其他表有多个关联)可能会出现错误。这种情况下,应该不带模型名,最好使用 ``first_name || \' \' || last_name``。 |
| 26 | + |
| 27 | +使用虚字段 |
| 28 | +========== |
| 29 | + |
| 30 | +操作虚拟字段有多种方法。 |
| 31 | + |
| 32 | +Model::hasField() |
| 33 | +----------------- |
| 34 | + |
| 35 | +如果模型中存在第一个参数指定的实际存在的字段,将返回true。 |
| 36 | +将第二个参数 `hasField()` 设为true,虚拟字段也会被检查。使用上面的代码来举例:: |
| 37 | + |
| 38 | + $this->User->hasField('name'); // 返回false,因为不存在名为name的实际字段 |
| 39 | + $this->User->hasField('name', true); // 放回true,因为存在名为name的虚字段 |
| 40 | + |
| 41 | +Model::isVirtualField() |
| 42 | +----------------------- |
| 43 | + |
| 44 | +检测一个字段/列是虚字段还是实际字段。如果是虚拟字段将返回true:: |
| 45 | + |
| 46 | + $this->User->isVirtualField('name'); //true |
| 47 | + $this->User->isVirtualField('first_name'); //false |
| 48 | + |
| 49 | +Model::getVirtualField() |
| 50 | +------------------------ |
| 51 | + |
| 52 | +该方法用来取得虚字段对应的SQL表达式,如果没有参数,会返回该模型所有的虚字段:: |
| 53 | + |
| 54 | + //将返回 'CONCAT(User.first_name, ' ', User.last_name)' |
| 55 | + $this->User->getVirtualField('name'); |
| 56 | + |
| 57 | +模型的find()方法和虚字段 |
| 58 | +-------------------------------- |
| 59 | + |
| 60 | +如前所述,``Model::find()`` 像处理其他字段一样处理虚字段。虚字段的值会包括在模型的结果中:: |
| 61 | + |
| 62 | + $results = $this->User->find('first'); |
| 63 | + |
| 64 | + // 输出的结果 |
| 65 | + array( |
| 66 | + 'User' => array( |
| 67 | + 'first_name' => 'Mark', |
| 68 | + 'last_name' => 'Story', |
| 69 | + 'name' => 'Mark Story', |
| 70 | + //更多的字段... |
| 71 | + ) |
| 72 | + ); |
| 73 | + |
| 74 | +分页和虚字段 |
| 75 | +------------ |
| 76 | + |
| 77 | +由于查询时虚字段与一般字段的行为一样,所以也可以用 ``Controller::paginate()`` 来排序。 |
| 78 | + |
| 79 | +虚字段和模型别名(model aliases) |
| 80 | +================================ |
| 81 | + |
| 82 | +当你使用虚字段和带别名的模型,可能会遇到虚字段的模型不会更新映射到新的模型别名中去。 |
| 83 | +如果使用的虚字段所在的模型中有多个别名,最好在模型的构造函数中来定义虚字段:: |
| 84 | + |
| 85 | + public function __construct($id = false, $table = null, $ds = null) { |
| 86 | + parent::__construct($id, $table, $ds); |
| 87 | + $this->virtualFields['name'] = sprintf( |
| 88 | + 'CONCAT(%s.first_name, " ", %s.last_name)', $this->alias, $this->alias |
| 89 | + ); |
| 90 | + } |
| 91 | + |
| 92 | +这将使虚字段在给定模型的所有别名中都有效。 |
| 93 | + |
| 94 | +SQL查询中的虚字段 |
| 95 | +================= |
| 96 | + |
| 97 | +使用query函数直接进行SQL查询,其返回的数据并不和模型的数据一样。比如:: |
| 98 | + |
| 99 | + $this->Timelog->query( |
| 100 | + "SELECT |
| 101 | + project_id, SUM(id) as TotalHours |
| 102 | + FROM |
| 103 | + timelogs |
| 104 | + AS |
| 105 | + Timelog |
| 106 | + GROUP BY |
| 107 | + project_id;" |
| 108 | + ); |
| 109 | + |
| 110 | +返回如下格式的数据:: |
| 111 | +
|
| 112 | + Array |
| 113 | + ( |
| 114 | + [0] => Array |
| 115 | + ( |
| 116 | + [Timelog] => Array |
| 117 | + ( |
| 118 | + [project_id] => 1234 |
| 119 | + ) |
| 120 | + [0] => Array |
| 121 | + ( |
| 122 | + [TotalHours] => 25.5 |
| 123 | + ) |
| 124 | + ) |
| 125 | + ) |
| 126 | + |
| 127 | +如果想把TotalHours放到Timelog数组中,我们需要为合计列指定一个虚拟字段。 |
| 128 | +我们可以临时定义虚拟字段比定义在模型中更有效果。我们设定默认值为 ``0``,免得其他查询使用这个虚拟字段。 |
| 129 | +如果那样,TotalHours列会返回 ``0`` :: |
| 130 | + |
| 131 | + $this->Timelog->virtualFields['TotalHours'] = 0; |
| 132 | + |
| 133 | +除了增加虚拟字段,我们还需要用 ``MyModel__MyField`` 的形式给列起别名。比如:: |
| 134 | + |
| 135 | + $this->Timelog->query( |
| 136 | + "SELECT |
| 137 | + project_id, SUM(id) as Timelog__TotalHours |
| 138 | + FROM |
| 139 | + timelogs |
| 140 | + AS |
| 141 | + Timelog |
| 142 | + GROUP BY |
| 143 | + project_id;" |
| 144 | + ); |
| 145 | + |
| 146 | +在指定虚拟字段后再次查询,将会得到一个格式化好的的结果:: |
| 147 | + |
| 148 | + Array |
| 149 | + ( |
| 150 | + [0] => Array |
| 151 | + ( |
| 152 | + [Timelog] => Array |
| 153 | + ( |
| 154 | + [project_id] => 1234 |
| 155 | + [TotalHours] => 25.5 |
| 156 | + ) |
| 157 | + ) |
| 158 | + ) |
| 159 | +
|
| 160 | +虚字段的局限 |
| 161 | +============ |
| 162 | + |
| 163 | +使用虚字段会带来一些局限。首先,不能在模型关联的条件(conditions),排序(order),字段(fields)数组中使用虚拟字段。如果那样做会产生SQL错误。因为字段被没有被ORM替代。很难去估算模型关联的深度。 |
| 164 | + |
| 165 | +常见的解决办法是在运行时将 ``virtualFields`` 复制到其他模型:: |
| 166 | + |
| 167 | + $this->virtualFields['name'] = $this->Author->virtualFields['name']; |
| 168 | + |
| 169 | +或:: |
| 170 | + |
| 171 | + $this->virtualFields += $this->Author->virtualFields; |
| 172 | + |
| 173 | +.. meta:: |
| 174 | + :title lang=zh_CN: Virtual fields |
| 175 | + :keywords lang=zh_CN: sql expressions,array name,model fields,sql errors,virtual field,concatenation,model name,first name last name |
0 commit comments