一、简单的单条件查询

工作都是从简单的开始,先从最简单的单表查询开始,这个一般用在首页以及一些比较独立的页面,只需要查找几个符合条件的产品展示出来即可,可以使用分页或者不使用分页。下面这个是产品控制器 ProductController 中的一个函数,用于简单的查询,比如199元专区就可以使用 getTypeSimPro('price=199');

/**简单的筛选条件分类产品,单表查询
* @param string $sql 单表查询的SQL
* @param int $countPerPage=16 每页商品数
* @param string $orderBy='salseF DESC' 排序 默认销量阈值
* @return array $res 产品二维数组
*/
function getTypeSimPro($sql, $countPerPage = 16, $orderBy='salesF DESC'){
//$sql = "SELECT ProductId,name,mainPic,priceN,priceVIP,isNew,isHot,sales FROM product WHERE ".$sql;
$productM = M('product'); // 实例化Data数据对象
$where = $sql ? 'onSale=1 AND '.$sql : 'onSale=1'; $tempSQL = $productM->field('ProductId,name,mainPic,priceN,priceVIP,isNew,isHot,sales,salesF')
->where( $where )
->order( $orderBy ); // $res = $this->executeTempSQL($tempSQL, $countPerPage);
$res = $tempSQL->select(); //演示不使用分页,直接返回结果集
return $res;
}

二、使用分页

由于Thinkphp的自带Page分页类有些不太好用,所以我进行了一点小改造,可以进行传递配置参数修改页码显示的方式。这里的主要实现逻辑是:

1、利用同一个临时数据库对象 $tempSQL ,使计数和查询结果的条件保持一致,注意这里使用了对象克隆,因为TP中,一个Model执行完操作后会被初始化成原始的Model对象,参见TP手册连贯操作说明>>

2、$_GET['p']是Page类默认的辨别当前页码的参数。Page类尤其里面的 show() 函数是经过我改造的,可以传递定制化页码导航栏参数。不定制也可以,就是页码导航有点太长。

3、这里的 count() 在后面多表查询的时候是有BUG的,后面再说。

/**
* 执行分类和搜索中的SQL对象
* @param TP.Model $tempSQL Thinkphp的Model对象
* @param int $countPerPage=16 每页的产品数
* @return array $res['nowP']当前页数 $res['totalP']总产品数 $res['links']分页栏HTML $res['productList']产品二维数组
* */
protected function executeTempSQL( $tempSQL, $countPerPage=16 ){
$tempSQL2 = clone $tempSQL; //对象复制,否则调用一次后第二次会被初始化成原始的M对象
// print_r($tempSQL);
$count = $tempSQL->count(); // 查询满足要求的总记录数,这里在多表查询时一定要以产品编号为限制条件
// var_dump($count);
// var_dump($tempSQL); $nowPage = isset($_GET['p'])?$_GET['p']:1; //当前页
import('ORG.Util.Page');// 导入分页类
$Page = new \Think\Page($count,$countPerPage); // 实例化分页类 传入总记录数,每页数
$list = $tempSQL2->page($nowPage.','.$Page->listRows)->select(); //查询结果集
// var_dump($list); //分页导航的定制
$showConfig = array(
'first' => '首页',
'prev' => '上一页',
'next' => '下一页',
// 'last' => '尾页', //这个不行
'rollPage' => 5, //最多显示5页导航
);
$links = $Page->show( $showConfig ); // 分页显示输出
//var_dump($links);
//var_dump($list);
$res['nowP'] = $nowPage;
$res['totalP'] = $count;
$res['links'] = $links; //分页输出
$res['productList'] = $list; //数据集
return $res;
}

三、多表查询功能概览

先来一张截图,要达到的筛选功能大概是这个样子的。

其中的数据库设计为:

product表:ProductId-产品ID、name-产品名、sort1-一级分类、sort2-二级分类、sort_brand-品牌分类、price-价格、onSale-上下架……等等
reserve表:ProductId-产品ID、color-颜色、size-尺码、reserve-库存
brand表:id-品牌ID、name-品牌名
tagpro表:Id-自增没实际用途、tagId-标签ID、ProductId-产品ID
tag表:Id-标签ID、tag_name-标签名

商品与品牌是多对一的关系,用字段做关联;商品与标签是多对多的关系,用表做关联。在上面展示的分类和搜索中,黑色导航栏、性别以及以后可能扩展的筛选项为标签联表查询,尺码为库存表联表查询。

四、SearchController控制器

定义了一个Search控制器,里面有下面几个方法:

function index() 方法是根据上面页面中的筛选选项拼装相应的SQL语句的,提交到ProductController去筛选出相关的产品;

function getCutURL($getKey, $CtrlName=CONTROLLER_NAME) 是为了给页面生成一系列切除了指定get值的URL地址的;

function pageCheck() 如果改变了筛选条件,则去除页码参数,回到从第一页开始;

在我的项目规划中IndexController负责页面的显示,所以IndexController中的 search() 方法则负责搜索页面的展示,代码如下

    function search(){
$searchC = A('search');
$res = $searchC->index();
$URLArr['type2URL'] = $searchC->getCutURL('type2'); //取消选择分类的URL
$URLArr['brandURL'] = $searchC->getCutURL('brand'); //取消选择品牌的URL
$URLArr['peopleURL'] = $searchC->getCutURL('people'); //取消选择性别人群的URL
$URLArr['sizeURL'] = $searchC->getCutURL('size'); //取消选择尺码的URL
$URLArr['priceURL'] = $searchC->getCutURL('price'); //取消选择价格区间的URL
$URLArr['keyword'] = $searchC->getCutURL('keyword'); //取消搜索关键字的URL
$URLArr['orderbyURL'] = $searchC->getCutURL('orderby'); //orderby按钮的URL前部分 $this->assign('URLArr', $URLArr)
->assign ( 'productArr', $res['productList'] )
->assign( 'totalNum', $res['totalP'] )
->assign( 'pageinfo', $res['links'])
->display();
}

五、两表多次查询

因为产品与标签是多对多的关系,所以有一种需求是:查询同时拥有两个标签一个产品,姑且设读取列为*即全部列。

一开始想到的SQL语句是这个样子的

SELECT * FROM product p INNER JOIN tagpro ON tagpro.ProductId = p.ProductId INNER JOIN tagpro ON tagpro.ProductId = p.ProductId WHERE onSale=1 AND tagpro.tagId=46 AND tagpro.tagId=40;

然而这条语句并没有执行成功,而是报错 Not unique table/alias: 'tagpro' ,意思是说两次INNER JOIN的表是同一个表/表别名,所以不行。所以我就试着把一个INNER JOIN删掉,然后再看是可以执行了,但是却是没有查到任何结果。到这里,我差点就要骂SQL不够智能了,明明是该产品在tagpro表中有tagId等于46也有tagId等于40,为什么你要理解成了 tagId同时等于46和40呢?

SELECT * FROM product p INNER JOIN tagpro ON tagpro.ProductId = p.ProductId WHERE onSale=1 AND tagpro.tagId=46 AND tagpro.tagId=40;

骂也没用,只能再想办法,相信这种事情很多人都遇到,SQL肯定有办法解决这种问题,后来尝试了一下这种写法,把同一个表分别用了两个不同的别名,然后终于能查出来了。 ≧▽≦

SELECT * FROM product p INNER JOIN tagpro a ON a.ProductId = p.ProductId INNER JOIN tagpro b ON b.ProductId = p.ProductId WHERE onSale=1 AND a.tagId=46 AND b.tagId=40;

六、产品控制器中的SQL查询函数

前面说了,Search控制器中的index()方法负责拼接SQL语句,提交到 Product控制器中进行产品的查询,现在在Product控制器中新建一个 getSearchPro() 方法,参考原来简单查询中的做法,另外加入JOIN的处理。这里其实就是把 where拼接起来, 把 join 拼接起来。原始的where和join的生成在Search控制器的index()中。

    /**根据筛选条件查找分类产品,多表查询     //默认每页16    //排序为销售阈值
* @param string $sql 单表查询的SQL
* @param int $countPerPage=16 每页商品数
* @param string $orderBy='salseF DESC' 销量阈值
* @param array $joinConfig=NULL 多表查询时
* $joinConfig['joinTable']为联合表名二维数组,每一列遍历为 $joinTableL
* $joinTableL[name]为真实表名 $joinTableL[asname]为as表名
* $joinConfig['where']为附加查询条件as表名的字段=条件 $joinTableL[asname].size=$size;
* @return array $res['nowP']当前页数 $res['totalP']总产品数 $res['links']分页栏HTML $res['productList']产品二维数组
*/
function getSearchPro($sql, $countPerPage = 16, $orderBy='salesF DESC', $joinConfig=NULL){
$productM = M('product')->alias('p'); // 实例化Data数据对象
$where = $sql ? 'p.onSale=1 AND '.$sql : 'p.onSale=1'; $joinTableArr = $joinConfig['joinTable']; //要JOIN的表名
$joinWhereArr = $joinConfig['where']; //要筛选的格外条件
foreach( $joinWhereArr as $joinLine ){
$where .= ' AND '.$joinLine;
} $tempSQL = $productM->distinct(true)
->field('p.ProductId,p.name,p.mainPic,p.priceN,p.priceVIP,p.isNew,p.isHot,p.sales,p.salesF')
->where( $where )
->order( $orderBy );
//处理JOIN
foreach( $joinTableArr as $joinTableL){
$tempSQL = $tempSQL->join("$joinTableL[name] AS $joinTableL[asname] ON $joinTableL[asname].ProductId = p.ProductId");
} $res = $this->executeTempSQL($tempSQL, $countPerPage, 'p.ProductId');
return $res;
}

注意最后的 $res = $this->executeTempSQL($tempSQL, $countPerPage, 'p.ProductId'); 最后比之前的函数多了一个参数,因为前文提到的 executeTempSQL()方法中的 $count = $tempSQL->count();  改为了  $count = $tempSQL->count('DISTINCT '.$countCond); 否则在多表查询时计数会出现count的数量比实际查到的结果条数多的情况。 这里的executeTempSQL()后面新增的参数为 $countCond,默认值为'ProductId',以便单表查询时不必填写这个无相紧要的参数。

七、Search控制器,筛选项转换成SQL拼接

index()函数:生成查询的SQL语句段。逻辑是:
1、根据 get 的参数,分别依次进行筛选/排序处理;
2、只在product表中产生where条件的,以一次查询加 简单where SQL拼接的方式处理;
3、多表联合并在其它表有 where条件的,以 join 数组的形式提交给产品控制器统一拼接处理;
4、这个是目前现行的方案,以后还要再优化的;

    //搜索入口
function index( $defaultTag=NULL ){
//如果改变了筛选条件,则去除页码参数
$this->pageCheck(); //********处理筛选**********************************
$type2 = I('get.type2'); // type2: 篮球鞋、跑步鞋……
$brand = I('get.brand'); // brand: 阿迪、匹克、李宁……
$people = I('get.people'); // people: 男、女、中性……
$size = I('get.size'); // size: 35~46、S~L……
$price = I('get.price'); // price: 小于99、100~199……
$tag = I('get.tag'); // tagsId: 限时、热销、性价比……
$keyword = trim( I('get.keyword') );// keyword: 搜索关键字……
$orderby = I('get.orderby'); $joinTableNameIndex = 0; //join所用的表替代名称后缀,为了两个表多次联合查询而设计所有的join表as tb0、tb1...
//例如 SELECT * FROM product p INNER JOIN tagpro tb0 ON tb0.ProductId = p.ProductId INNER JOIN tagpro tb1 ON tb1.ProductId = p.ProductId WHERE onSale=1 AND tb0.tagId=46 AND tb1.tagId=40; $sql = '';
//二级分类: /type2/篮球鞋
if( $type2 ){
$type2Id = M()->query("SELECT Id FROM sort WHERE sortName='$type2'");
$type2Id = $type2Id[0]['Id'];
$thesql = "p.sort_secType='$type2Id'";
$sql = $sql ? $sql.' AND '.$thesql : $thesql;
} //品牌筛选: /brand/李宁
if( $brand ){
$brandId = M()->query("SELECT Id FROM sort WHERE sortName='$brand'");
$brandId = $brandId[0]['Id'];
$thesql = "(p.sort_brand='$brandId' OR p.name LIKE '%$brand%')"; //这里除了品牌分类匹配外还根据商品名称模糊匹配
$sql = $sql ? $sql.' AND '.$thesql : $thesql;
} //性别: /people/男性 /people/女性 /people/中性
if( $people ){
//人群标签ID
$tagsPeopleId = M('tags')->where("tag_name='$people'")->field('Id')->select();
$tagsPeopleId = $tagsPeopleId[0]['Id']; $join['joinTable'][] = array('name'=>'tagpro', 'asname'=>'tb'.$joinTableNameIndex );
$join['where'][] = 'tb'.$joinTableNameIndex.'.tagId='.$tagsPeopleId ;
$joinTableNameIndex++;
// $sql = $sql ? $sql.' AND '.$thesql : $thesql;
} //营销标签(与人群标签一样处理逻辑)
if( $tag ){
$tagsId = M('tags')->where("tag_name='$tag'")->field('Id')->select();
$tagsId = $tagsId[0]['Id'];
$join['joinTable'][] = array('name'=>'tagpro', 'asname'=>'tb'.$joinTableNameIndex );
$join['where'][] = 'tb'.$joinTableNameIndex.'.tagId='.$tagsId ;
$joinTableNameIndex++;
// $sql = $sql ? $sql.' AND '.$thesql : $thesql;
} //尺码筛选: /size/35
if( $size ){
$join['joinTable'][] = array('name'=>'reserve', 'asname'=>'tb'.$joinTableNameIndex );
$join['where'][] = 'tb'.$joinTableNameIndex.'.size='.$size ;
$joinTableNameIndex++;
// $join['joinTable'][] = 'reserve';
// $join['where'][] = 'reserve.size='.$size ;
// $sql = $sql ? $sql.' AND '.$thesql : $thesql;
} //价格筛选: /price/100~199 /price/大于1999 /price/小于99
if( $price ){
// var_dump( $price );
if( preg_match('/~/', $price) ){ // echo '介于';
$priceArr = explode('~', $price);
$thesql = "p.priceVIP BETWEEN '$priceArr[0]' AND '$priceArr[1]'";
}elseif( preg_match('/大于/', $price) ){ // echo '大于';
$priceArr = explode('大于', $price);
$thesql = "p.priceVIP>'$priceArr[1]'";
}elseif( preg_match('/小于/', $price) ){ // echo '小于';
$priceArr = explode('小于', $price);
$thesql = "p.priceVIP<'$priceArr[1]'";
}
$sql = $sql ? $sql.' AND '.$thesql : $thesql;
} //搜索关键字
if( $keyword ){
$thesql = "(p.ProductId LIKE '%$keyword%' OR p.name LIKE '%$keyword%')";
$sql = $sql ? $sql.' AND '.$thesql : $thesql;
} //排序
if( $orderby ){
if( $orderby == 'default'){
$orderbySQL = NULL;
}elseif( $orderby == 'priceVIPa' ){
$orderbySQL = 'priceVIP ASC';
}elseif( $orderby == 'priceVIPd' ){
$orderbySQL = 'priceVIP DESC';
}else{
$orderbySQL = $orderby.' DESC';
}
} //*******************************************
$productC = A('DataProduct');
$res = $productC->getSearchPro($sql,20,$orderbySQL,$join);
// var_dump($res);
return $res;
}

Search 控制器下的另外两个函数如下:

    /**
* 如果改变了筛选条件,则去除页码参数,回到从第一页开始
* 实现原理:如果存在p参数且不是最后一个参数时,则认为是修改了筛选条件
* 这里有一点BUG,多项选择再翻页时、取消一个选项并不会回到第一页(因为p参数还是在最后)
*/
function pageCheck(){
// var_dump( $_SERVER['HTTP_REFERER'] );
// var_dump( $_GET );
$getKeyArr = array_keys( $_GET ); //var_dump($getKeyArr);
$pKeyIndex = array_search('p', $getKeyArr);
// var_dump($pKeyIndex);
$arrL = sizeof($getKeyArr); //var_dump($getKeyArr); if( $pKeyIndex!==FALSE && $pKeyIndex+1 < $arrL ){ //p参数如果不是在最后则为更改了筛选条件
$cutPurl = $this->getCutURL('p');
redirect( $cutPurl );
} } /**
* 获得切除了指定get值的URL
* @param string $getKey 要去除的get键
* @param string $CtrlName 控制器名,默认为页面URL中的控制器名
* @return string 不含http://域名 的URL,可直接用于前端输出
* */
function getCutURL($getKey, $CtrlName=CONTROLLER_NAME){
$getStr ='';
$getArr = I('get.');
unset($getArr[$getKey]);
foreach( $getArr as $getKey=>$getVal){
$getStr .= '/'.$getKey.'/'.$getVal;
}
// var_dump($getStr);
$thisURL = explode('.html', U("Index/search") );
$thisURL = $thisURL[0];
return $thisURL.$getStr;
}

pageCheck() 和 getCutURL()

产品列表页分类筛选、排序的算法实现(PHP)的更多相关文章

  1. Asp.net MVC 3实例学习之ExtShop(四)——完成产品列表页

    在完成产品列表页前要做一些准备功夫.首先是去下载MvcPager用了为产品列表分页.下载的可能是基于MVC 2的,没关系,可以用在MVC 3上.如果有担心,下载源代码重新编译一次好了.下载后将DLL添 ...

  2. python测试开发django-23.admin列表页优化和排序

    前言 列表页优化和排序 ModelAdmin django的options.py里面 ModelAdmin类定义的参数可以设置admin后台列表页面,相关的参数如下 class ModelAdmin( ...

  3. 通过angularjs的directive以及service来实现的列表页加载排序分页

    前两篇:(列表页的动态条件搜索,我是如何做列表页的)分别介绍了我们是如何做后端业务系统数据展示类的列表页以及动态搜索的,那么还剩下最重要的一项:数据展示.数据展示一般包含三部分: 数据列头 数据行 分 ...

  4. 通过angularjs的directive以及service来实现的列表页加载排序分页(转)

    前两篇:(列表页的动态条件搜索,我是如何做列表页的)分别介绍了我们是如何做后端业务系统数据展示类的列表页以及动态搜索的,那么还剩下最重要的一项:数据展示.数据展示一般包含三部分: 数据列头 数据行 分 ...

  5. javascript 一个关于时间排序的算法(一个页面多个倒计时排序)

    上周要做一个活动页面 秒杀列表页 需要一个时间的算法排序 自己琢磨了半天想了各种算法也没搞出来,后来问了下一个后台的php同学 他写了个算法给我看了下 ,刚开始看的时候觉得这就是个纯算法,不能转化成页 ...

  6. 夺命雷公狗ThinkPHP项目之----企业网站23之网站前台二级分类的跳转(URL跳转到列表页或产品页)

    我们现在开始做实现我们的二级菜单如何跳转到指定的列表页或者产品也呢?? 我们分享下数据库情况: 我们的数据库里提前给我们预留了一个cate_type的字段,那么我们可以让这个字段进行判断,从而遍历出指 ...

  7. 《React后台管理系统实战 :四》产品分类管理页:添加产品分类、修改(更新)产品分类

    一.静态页面 目录结构 F:\Test\react-demo\admin-client\src\pages\admin\category add-cate-form.jsx index.jsx ind ...

  8. PHP.25-TP框架商城应用实例-后台2-商品列表页-搜索、翻页、排序

    商品列表页 1.翻页 控制器GoodsController.class.php添加方法lst(),显示列表页 在商品模型GoodsModel.class.php类中添加search方法 /** *实现 ...

  9. Golang在京东列表页实践总结

    Golang在京东列表页实践总结 作者:张洪涛 10余年软件开发和设计经验,曾就职于搜狐.搜狗.前matrixjoy公司联合创始人.甘普科技CTO. 目前线上状态 基于搜索实现: 全量数据,搜索结果不 ...

随机推荐

  1. 用struts2获取session、request、parmeter的方法

    package com.hanqi.action; import java.util.Map; import com.opensymphony.xwork2.ActionContext; public ...

  2. 已知2个一维数组:a[]={3,4,5,6,7},b[]={1,2,3,4,5,6,7};把数组a与数组b ,对应的元素乘积再赋值给数组b,如:b[2]=a[2]*b[2];最后输出数组b的元素。

    int[]a={3,4,5,6,7}; int[]b={1,2,3,4,5,6,7}; int[] arry=new int[7]; System.out.print("数组b[]={&qu ...

  3. msysGit管理GitHub代码

    msysGit管理GitHub代码   代码的管理,在日常开发中是很重要的环节,程序员的修炼三部曲——版本控制,单元测试,项目自动化. 本篇就简单的说说通过msysGit来管理GitHub中的代码,实 ...

  4. 知方可补不足~sqlserver中对xml类型字段的操作

    回到目录 在sqlserver中有很多种数据类型,而XML数据类型是比较新奇怪的一种格式,我们平常接触的可能比较少,用的也少,而在某些场合,使用XML类型可能会使我们的开发变简单,下面就是一种情况: ...

  5. read

    从标准输入读入一行内容并以空格为分隔符赋值给变量,如果输入的内容过多,则把剩下的所有内容都赋值给最后一个变量 $read A B C 123 456 789 101 $echo "$A&qu ...

  6. 不要轻易使用linq代替sql

    使用Entityframework+Reposity模式 写出的代码执行效率极低. Linq代码: var querySql = rel_project_personservice.GetItems( ...

  7. nginx参数说明

    一.nginx的核心配置: >>> 正常运行的必备配置: 1. user username [groupname]; #指定运行worker子进程的用户或组 2. pid /path ...

  8. php反射机制

    PHP5添加了一项新的功能:Reflection.这个功能使得phper可以reverse-engineer class, interface,function,method and extensio ...

  9. android中ADT和SDK的关系

    ADT(Android Development Tools): 目前Android开发所用的开发工具是Eclipse,在Eclipse编译IDE环境中,安装ADT,为Android开发提供开发工具的升 ...

  10. Python特殊语法--filter、map、reduce、lambda

    一.filter(function, sequence) 对sequence中的item依次执行function(item),将执行结果为True的item组成一个List/String/Tuple( ...