(一)关于ThinkPHP2.1版本操做MSSQL类的BUG--count统计结果异常

 最近在研究ThinkPHP,我用的版本是2.1,数据库为MSSQL2005,在按示例操做的时候出现了一个问题:php

BUG:Model类的对象使用count进行记录统计的时候结果老是为1sql

下面咱们分析下源码,看看具体的执行流程:thinkphp

1.Action中的代码以下数据库

  
  
  
  
  1. $agentInfo = new Model("tblagentinfo"); 
  2. $agentInfoCount = $agentInfo->count();  

下面咱们一步步追踪。数组

(1)首先咱们进入Model类的源码,位于:ThinkPHP/Lib/Think/Core/Model.class.php 。ide

咱们搜索count方法。发现没有这个方法的定义,但咱们能够搜索到这么一个方法__call。该方法是PHP5以后新增的魔术方法,做用就是若是调用类中不存在的方法,PHP会自动执行__call方法.具体用法这边咱们不作介绍,能够参考这里:this

咱们看看__call中有以下的代码片断:spa

 

  
  
  
  
  1. elseif(in_array(strtolower($method),array('count','sum','min','max','avg'),true)){ 
  2.   // 统计查询的实现 
  3.   $field =  isset($args[0])?$args[0]:'*'
  4.   return $this->getField(strtoupper($method).'('.$field.') AS tp_'.$method); 

咱们发如今调用Model类对象的count方法时,调用了Model对象的getField方法。根据代码咱们能分析传递给getField方法的参数是个字符串:"count(*) as tp_count"调试

(2)咱们再进入getField方法看看。对象

咱们发现该方法中存在个if else语句,根据咱们传入的参数如今执行的代码以下:

  
  
  
  
  1. else{   // 查找一条记录 
  2.         $options['limit'] = 1; 
  3.         $result = $this->db->select($options); 
  4.         if(!emptyempty($result)) { 
  5.             return reset($result[0]); 
  6.         } 

咱们发现这里对$options变量作了个赋值而且又调用了Db类的select方法。其实这里TP调用的应该是DbMssql类的select方法,由于DbMssql继承自Db类,且没有对Db类的select进行重写,所以咱们进入Db类看下。

(3)在Db类的select方法中有以下的代码片断:

 

  
  
  
  
  1. $sql   = str_replace
  2.             array('%TABLE%','%DISTINCT%','%FIELDS%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%'), 
  3.             array
  4.                 $this->parseTable($options['table']), 
  5.                 $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false), 
  6.                 $this->parseField(isset($options['field'])?$options['field']:'*'), 
  7.                 $this->parseJoin(isset($options['join'])?$options['join']:''), 
  8.                 $this->parseWhere(isset($options['where'])?$options['where']:''), 
  9.                 $this->parseGroup(isset($options['group'])?$options['group']:''), 
  10.                 $this->parseHaving(isset($options['having'])?$options['having']:''), 
  11.                 $this->parseOrder(isset($options['order'])?$options['order']:''), 
  12.                 $this->parseLimit(isset($options['limit'])?$options['limit']:''
  13.             ),$this->selectSql); 
  14.         $sql   .= $this->parseLock(isset($options['lock'])?$options['lock']:false); 
  15.         return $this->query($sql); 

这里的代码是对查询SQL进行了拼凑,这里你们咱们要留意$selectSql变量,这个变量实际是在DbMssql中的定义的。接着代码又调用了query方法,在该方法中执行了具体的数据库查询操做,并将查询结果传入了该类中的getAll方法,在getAll方法中将查询结果存于$result数组中进行了返回。

(4)到目前为止执行的流程已经结束,下面就是将$result数组一层层的返回。咱们回到第2部的代码片断。在调用了Db类的select后对返回的$result进行了判断并将最终的结果reset($result[0])返回给了Action。

(5)好,如今结果已经返回了,咱们看下具体的问题出如今哪。咱们开启TP的调试功能找出TP执行的SQL的具体代码,以下:

  
  
  
  
  1. SELECT T1.* FROM (SELECT ROW_NUMBER() OVER ( ORDER BY rand()) AS ROW_NUMBER, thinkphp.* FROM (SELECT COUNT(*) AS tp_count FROM tblagentinfo) AS thinkphp) AS T1 WHERE (T1.ROW_NUMBER BETWEEN 1 AND 1) 

该语句执行的结果以下图:

(6)到这里问题出现的缘由已经呼之欲出了,就是由于第4步中返回的结果是第一个字段的值,所以不管你怎么查询,结果都是1.那咱们该如何处理呢?

个人处理方法以下:

将DbMsql类中全局变量$selectSql定义进行了修改,修改后的语句以下:

  
  
  
  
  1. protected $selectSql  =     'SELECT T1.* FROM (SELECT  thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELDS% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 WHERE %LIMIT%' 

就是将查询结果中tp_count字段放在第一个字段便可。

相关文章
相关标签/搜索