数据查询
本章节主要是介绍,通过模型对象实现数据的简单查询、复杂查询、连表查询、分页显示等。本章节的内容建立在您已经熟悉《模型介绍》的基础之上。如不熟悉,请先阅读上一章节内容。
一、简单查询
获取数据库表的数据,是我们做Web项目经常用到的功能,ShopWind提供了简单易懂且功能强大的数据查询能力,我们先来看一个简单的例子,假设我们需要在商品表中 读取前10条的商品信息,并显示到视图,我们在商品控制器中编写的代码如下:
namespace frontend\controllers\home;
use Yii;
use common\models\GoodsModel;
use common\controllers\BaseMallController;

class GoodsController extends BaseMallController
{
    ...
    public function actionIndex() 
    {
        // 方式一
        $model = new GoodsModel();
        $result = $model->find()->limit(10)->asArray()->all();
        
        // 方式二
        $result = GoodsModel::find()->limit(10)->asArray()->all();

        print_r($result);
        // return $this->render('../goods.html', $result);
    }
}
从代码看,方式二更为简洁,也是我们推荐的用法。返回的结果列表result就是一个数组,如果需要返回对象,去掉方法asArray()即可。 得到这个结果之后,我们就可以通过render方法传递到视图,有关在视图中显示数据的问题,请参考《数据绑定》章节,这里不再介绍。
如果需要给查询结果加个条件,可以使用where语法:
$result = GoodsModel::find()->where(['if_show' => 1])->asArray()->all();
如果需要查询指定ID的某条记录,可以为:
$result = GoodsModel::find()->where(['goods_id' => 1])->asArray()->one();
以下就是常用的查询例子:
// 多个条件并(and)查询
$result = GoodsModel::find()->where(['if_show' => 1, 'closed' => 0])->limit(10)->all();
$result = GoodsModel::find()->where(['and', ['if_show' => 1], ['closed' => 0]])->limit(10)->all();
$result = GoodsModel::find()->where(['if_show' => 1])->andWhere(['closed' => 0])->limit(10)->all();

// 多个条件或(or)查询
$result = GoodsModel::find()->where(['or', ['if_show' => 1], ['closed' => 0]])->limit(10)->all();
$result = GoodsModel::find()->where(['if_show' => 1])->orWhere(['closed' => 0])->limit(10)->all();

// 只获取需要的字段
$result = GoodsModel::find()->select('goods_id, goods_name')->asArray()->all();

// 获取记录总数
$result = GoodsModel::find()->count();

// 结果倒序排序
$result = GoodsModel::find()->orderBy(['goods_id' => SORT_DESC])->all();

// 先按商品ID倒序,再按添加时间正序
$result = GoodsModel::find()->orderBy(['goods_id' => SORT_DESC, 'add_time' => SORT_ASC])->all();

// 设置数组key为某个字段的值 
$result = GoodsModel::find()->indexBy('goods_id')->asArray()->all();

// 如果ID是自增字段,则获取指定ID的记录
$result = GoodsModel::findOne($id); 
// 相当于 $result = GoodsModel::find()->where(['goods_id' => $id])->one();
二、复杂查询
有时候,我们需要连接多个表查询,才能获取我们想要的数据,比如我想获取某个用户的性别、年龄、积分和金钱数据,性别,年龄在用户表, 积分数据在积分表,金钱数据在资金账户表。

建立表关联关系,一般有两种情况:

第一种是:一对一关系,即:当前表与关联表的数据是一对一的关系,如用户表与积分表,一个用户只有一个积分数据。

第二种是:一对多关系,即:当前表与关联表的数据是一对多的关系,比如商品表与商品图片表,一个商品可能会有多张商品图片,所以是一对多的关系。

建立关系是通过模型类实现的,所以我们先要修改模型文件代码,假定我们有一个商品模型GoodsModel,需要建立与店铺模型StroreModel一对一的关系,与商品相册模型GoodsImageModel一对多的关系。 对于一对一的关系,我们使用hasOne方法,一对多的关系使用hasMany方法, 修改模型文件@shopwind/common/models/GoodsModel.php如下代码所示:
namespace common\models;
use Yii;
use yii\db\ActiveRecord;
                        
class GoodsModel extends ActiveRecord
{
    public static function tableName()
    {
        return '{{%goods}}';
    }
    public function getStore()
    {
        return parent::hasOne(StoreModel::className(), ['store_id' => 'store_id']);
    }
    public function getGoodsImage()
    {
        return parent::hasMany(GoodsImageModel::className(), ['goods_id' => 'goods_id']);
    }
}
在以上的代码中,我们建立了两个关系,一个是与店铺表store的一对一关系(hasOne),因为一个商品只能属于一个店铺,一个是与商品相册表 goodsImage的一对多关系(hasMany),因为一个商品会有多个主图。
关系方法名中的get是保留字,拼接的模型名首字母必须大写。hanOne/hanMany方法的第一个参数是关联表的模型类名,第二个参数是关联字段,第一个字段是当前表字段,第二个字段是关联表字段(请确保设计数据表的时候,必须有这些字段)。
注意:如果您是开发新功能,使用的是当前系统没有的数据表和模型类,请先建立相关的表和模型文件,如果对此不熟悉的,可以查阅上一章节模型介绍的内容。如果您还有更复杂的关联需求,可以查看Yii2官方文档使用关联数据
建立好模型关联之后,我们就可以通过以下代码来实现连表查询
// 一对一关系
$result = GoodsModel::find()->joinWith('store', false)->asArray()->all();

// 一对多关系
$result = GoodsModel::find()->with('goodsImage')->asArray()->all();

// 两者兼有
$result = GoodsModel::find()->joinWith('store', false)->with('goodsImage')->asArray()->all();

// 只获取需要的字段
$result = GoodsModel::find()->select('goods_id, goods_name, store_id, store_name')
            ->joinWith('store', false)->asArray()->all();

// 给表添加别名
$result = GoodsModel::find()->alias('g')->select('g.goods_id, g.goods_name, s.store_id, s.store_name')
            ->joinWith('store s', false)->asArray()->all();

// 一个复杂的例子
$result = GoodsModel::find()->alias('g')->select('g.goods_id, g.goods_name, s.store_id, s.store_name')
            ->joinWith('store s', false)
            ->with('goodsImage')
            ->where(['g.if_show' => 1, 'g.closed' => 0])
            ->andWhere(['s.state' => 1])
            ->orderBy(['g.goods_id' => SORT_DESC, 'g.sort_order' => SORT_ASC])
            ->limit(10)->asArray()->all();
这里我们需要注意的是,连表查询的结果必须返回数组形式,不能返回对象,即必须使用asArray()方法,不能缺省,要不然会得不到关联表的数据。 如果两个关联的表有相同的字段名,建议在使用字段时(如:select语句、where语句)定义表别名alias,使用字段的时候加上表别名前缀,如上例中的g.goods_id s.store_id,要不会有异常报错提示。除此之外,我们可能还需要以下形式的查询能力。
查询商品ID为大于等于100的数据
$result = GoodsModel::find()->where(['>=', 'goods_id', 100])->asArray()->all();
查询商品ID为不等于100的数据
$result = GoodsModel::find()->where(['!=', 'goods_id', 100])->asArray()->all();
查询商品ID位于给定数组的数据
$result = GoodsModel::find()->where(['in', 'goods_id', [1,2,3,4]])->asArray()->all();
判断记录是否存在,返回boolean
$result = GoodsModel::find()->where(['goods_id' => 100])->exists();
三、关于分页
当我们读取一个数据集列表的时候,由于数据太多,常常需要用到分页来显示,分页需要用到一个Page类来格式化分页数组。如果是接口API 使用的不需要分页链接的数组,则将第二个参数设置为false即可,即:Page::formatPage($page, false)
use common\models\GoodsModel;
use common\library\Page;

class GoodsController extends \common\base\BaseMallController 
{
    ...
    public function actionIndex() 
    {
        // 获取所有商品记录,每页显示20条
        $query = GoodsModel::find()->orderBy(['goods_id' => SORT_DESC]);
        $page = Page::getPage($query->count(), 20);
        $this->params['list'] = $query->offset($page->offset)->limit($page->limit)->asArray()->all();
        $this->params['pagination'] = Page::formatPage($page); // 分页数组

        return $this->render('../goods.html', $this->params);
    }
}
然后在视图中,可以直接引入通用分页组件,即可显示分页效果
<html> ... <body> ... <div class="page">{include file="page.bottom.html"}</div> </body> </html>
以上代码就完成了一个分页查询功能,视图显示部分我们在前一章节已经有过介绍,这里不再重复,如果还不熟悉如何显示数据到视图,可以查看数据绑定章节的内容。
由于在控制器渲染视图的render方法,我们只能调用一次,该方法的第二个参数就是视图所需要的所有数据,为了方便,我们在控制器父类定义了一个变量$this->params 并且父类也对该变量做了初始化赋值,所以我们建议所有传递到视图的数据,都应该使用$this->params来接收。
有关模型的更多问题,也可以从Yii2官方文档Active Record 类获取,其所有的类名和方法都可以在ShopWind系统中使用。此外,您还可以访问我们的 开发者社区反馈,我们会有官方技术人员在线解答。