教义-如何打印出真正的sql,而不仅仅是准备好的语句?


167

我们正在使用PHP ORM Doctrine。我正在创建这样的查询:

$q = Doctrine_Query::create()->select('id')->from('MyTable');

然后在函数中添加各种where子句和适当的东西,像这样

$q->where('normalisedname = ? OR name = ?', array($string, $originalString));

稍后,在execute()为该查询对象添加对象之前,我想打印出原始SQL以便对其进行检查,并执行以下操作:

$q->getSQLQuery();

但是,这只会打印出准备好的语句,而不是完整的查询。我想查看它发送给MySQL的内容,但是它正在打印出一条准备好的语句,包括?。有什么办法可以查看“完整”查询吗?


我找到的最佳方法是在此答案中描述:stackoverflow.com/a/678310/229077
Marek 2015年

您可以利用Doctrine所做的工作(探查器显示可运行的查询)。请参阅我的回答下面的详细资料
文森特Pazeller

Answers:


164

教义没有向数据库服务器发送“真正的SQL查询”:它实际上是在使用准备好的语句,这意味着:

  • 发送该语句,以供准备(这是所返回的$query->getSql()
  • 然后,发送参数(由 $query->getParameters()
  • 并执行准备好的语句

这意味着在PHP端永远不会有“真正的” SQL查询-因此,Doctrine无法显示它。


14
Pascal:您不应该说它不是“真正的SQL查询”,因为prepared语句是真正的SQL查询,只是参数分别发送出去。这种措辞可能会使人们感到困惑(例如olivierpons.fr/2014/03/22/symfony-2-avantages-et-inconvenients)。
Matthieu Napoli 2014年

$query->getParameters();不会以正确的顺序返回参数,因为它们应该出现在准备好的查询语句中
gondo

4
我认为这里的问题作者并不关心教义是发送还是不发送。我和用户想知道的是如何获取查询,我们可以复制粘贴并运行查询,而不必手动用参数替换问号。就像在codeigniter中一样。我想我已经在symfony调试器中找到了这个,但是当我从命令行运行脚本时仍然找不到。
Darius.V

104

一个工作示例:

$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL: 
echo $query->getSQL(); 
// Show Parameters: 
echo $query->getParameters();

5
虽然它可以作为变量赋值,但您可能需要考虑以下因素:print $ query-> getSQL(); foreach($ query-> getParameters()as $ param){打印“ {$ param-> getName()}-> {$ param-> getValue()} \ n”; },这样您就会获得更具可读性的输出
贾斯汀·芬克尔斯坦

它给小伙伴带来好处。复制sql时,仍然有search wichi参数在哪里手动插入,需要花费大量时间。我们想要一个带有插入参数的查询,为什么我们找不到这么长时间?就我所知,即使在codeigniter框架中,您也可以在探查器中复制查询并立即运行,而无需手动进行。我们在symfony上也需要同样的东西。
Darius.V,

35

如果您将所有查询记录在mysql中,则可以检查应用执行的查询:

http://dev.mysql.com/doc/refman/5.1/zh-CN/query-log.html

不仅会有您要查找的查询,而且还会有更多查询。

但通常->getSql();可以

编辑:

查看我使用的所有mysql查询

sudo vim /etc/mysql/my.cnf 

并添加这两行:

general_log = on
general_log_file = /tmp/mysql.log

然后重启mysql


17

我创建了一个Doctrine2 Logger来做到这一点。它使用Doctrine 2自己的数据类型转换程序将值与参数化的sql查询“混合在一起”。

<?php


namespace Drsm\Doctrine\DBAL\Logging;
use Doctrine\DBAL\Logging\SQLLogger,
    Doctrine\DBAL\Types\Type,
    Doctrine\DBAL\Platforms\AbstractPlatform;
/**
 * A SQL logger that logs to the standard output and
 * subtitutes params to get a ready to execute SQL sentence

 * @author  dsamblas@gmail.com
 */
class EchoWriteSQLWithoutParamsLogger implements SQLLogger

{
    const QUERY_TYPE_SELECT="SELECT";
    const QUERY_TYPE_UPDATE="UPDATE";
    const QUERY_TYPE_INSERT="INSERT";
    const QUERY_TYPE_DELETE="DELETE";
    const QUERY_TYPE_CREATE="CREATE";
    const QUERY_TYPE_ALTER="ALTER";

    private $dbPlatform;
    private $loggedQueryTypes;
    public function __construct(AbstractPlatform $dbPlatform, array $loggedQueryTypes=array()){
        $this->dbPlatform=$dbPlatform;
        $this->loggedQueryTypes=$loggedQueryTypes;
    }
    /**
     * {@inheritdoc}
     */
    public function startQuery($sql, array $params = null, array $types = null)

    {
        if($this->isLoggable($sql)){
            if(!empty($params)){
                foreach ($params as $key=>$param) {
                    $type=Type::getType($types[$key]);
                    $value=$type->convertToDatabaseValue($param,$this->dbPlatform);
                    $sql = join(var_export($value, true), explode('?', $sql, 2));
                }

            }
            echo $sql . " ;".PHP_EOL;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function stopQuery()
    {

    }
    private function isLoggable($sql){
        if (empty($this->loggedQueryTypes)) return true;
        foreach($this->loggedQueryTypes as $validType){
            if (strpos($sql, $validType) === 0) return true;
        }
        return false;
    }
}

用法示例:以下代码和平将在标准输出上回显使用$ em实体管理器生成的所有INSERT,UPDATE,DELETE SQL语句,

/**@var  \Doctrine\ORM\EntityManager $em */
$em->getConnection()
                ->getConfiguration()
                ->setSQLLogger(
                    new EchoWriteSQLWithoutParamsLogger(
                        $em->getConnection()->getDatabasePlatform(),
                        array(
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_UPDATE,
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_INSERT,
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_DELETE
                        )
                    )
                );

1
当参数是日期字符串,例如“ 2019-01-01”时
不起作用

14

getSqlQuery() 从技术上讲确实显示了整个SQL命令,但是当您还可以看到参数时,它的作用要大得多。

echo $q->getSqlQuery();
foreach ($q->getFlattenedParams() as $index => $param)
  echo "$index => $param";

为了使这种模式更可重用,在“ Doctrine查询对象的原始SQL”中的注释中描述了一种不错的方法。


我知道这是一篇旧文章,但是您的两个链接都指向404页面。你能更新你的答案吗?我问,是因为我不确定你的意思$q。它似乎既不是查询也不是查询生成器。
k00ni

1
恐怕找不到更可重用的代码。 $q在这种情况下,是“教义1”查询。您可能使用的是Doctrine 2,在这种情况下,您会希望获得$qb = $this->createQueryBuilder('a'); $q = $qb->getQuery(); $sql = $q->getSQL(); $params = $q->getParameters(); 希望的帮助!
ladenedge

13

没有其他实际查询,这是预准备语句的工作方式。这些值绑定在数据库服务器中,而不是在应用程序层中。

请参阅我对以下问题的回答:在具有PDO的PHP中,如何检查最终的SQL参数化查询?

(为方便起见,在此重复:)

使用带有参数值的预准备语句不是动态创建SQL字符串的另一种简单方法。您在数据库上创建一个准备好的语句,然后单独发送参数值。

所以可能发送到数据库的将是PREPARE ...,然后SET ...最后EXECUTE ....

SELECT * FROM ...即使将产生相等的结果,您也将无法获得某些SQL字符串,例如,因为实际上从未将此类查询发送到数据库。


9

我的解决方案:

 /**
 * Get SQL from query
 * 
 * @author Yosef Kaminskyi 
 * @param QueryBilderDql $query
 * @return int
 */
public function getFullSQL($query)
{
    $sql = $query->getSql();
    $paramsList = $this->getListParamsByDql($query->getDql());
    $paramsArr =$this->getParamsArray($query->getParameters());
    $fullSql='';
    for($i=0;$i<strlen($sql);$i++){
        if($sql[$i]=='?'){
            $nameParam=array_shift($paramsList);

            if(is_string ($paramsArr[$nameParam])){
                $fullSql.= '"'.addslashes($paramsArr[$nameParam]).'"';
             }
            elseif(is_array($paramsArr[$nameParam])){
                $sqlArr='';
                foreach ($paramsArr[$nameParam] as $var){
                    if(!empty($sqlArr))
                        $sqlArr.=',';

                    if(is_string($var)){
                        $sqlArr.='"'.addslashes($var).'"';
                    }else
                        $sqlArr.=$var;
                }
                $fullSql.=$sqlArr;
            }elseif(is_object($paramsArr[$nameParam])){
                switch(get_class($paramsArr[$nameParam])){
                    case 'DateTime':
                             $fullSql.= "'".$paramsArr[$nameParam]->format('Y-m-d H:i:s')."'";
                          break;
                    default:
                        $fullSql.= $paramsArr[$nameParam]->getId();
                }

            }
            else                     
                $fullSql.= $paramsArr[$nameParam];

        }  else {
            $fullSql.=$sql[$i];
        }
    }
    return $fullSql;
}

 /**
 * Get query params list
 * 
 * @author Yosef Kaminskyi <yosefk@spotoption.com>
 * @param  Doctrine\ORM\Query\Parameter $paramObj
 * @return int
 */
protected function getParamsArray($paramObj)
{
    $parameters=array();
    foreach ($paramObj as $val){
        /* @var $val Doctrine\ORM\Query\Parameter */
        $parameters[$val->getName()]=$val->getValue();
    }

    return $parameters;
}
 public function getListParamsByDql($dql)
{
    $parsedDql = preg_split("/:/", $dql);
    $length = count($parsedDql);
    $parmeters = array();
    for($i=1;$i<$length;$i++){
        if(ctype_alpha($parsedDql[$i][0])){
            $param = (preg_split("/[' ' )]/", $parsedDql[$i]));
            $parmeters[] = $param[0];
        }
    }

    return $parmeters;}

用法示例:

$query = $this->_entityRepository->createQueryBuilder('item');
$query->leftJoin('item.receptionUser','users');
$query->where('item.customerid = :customer')->setParameter('customer',$customer)
->andWhere('item.paymentmethod = :paymethod')->setParameter('paymethod',"Bonus");
echo $this->getFullSQL($query->getQuery());

谢谢你:D
Saad Achemlal'1

非常好。适用于普通查询,但我有一个正则表达式查询,看起来不支持$ qb = $ this-> createQueryBuilder('r')-> innerJoin('r.profile','p')-> addSelect(' p')-> where('REGEXP(:fileNamePattern,r.fileNamePattern)= 1')-> andWhere('p.incomingLocation =:incomingLocation')-> setParameters(['fileNamePattern'=> $ fileName,'incomingLocation' => $ location])-> getQuery();
Fahim

不适用于所有查询。当我有这个-> setParameters(array('insuranceCarrier'=> $ insuranceCarrier,'dateFrom'=> $ dateFrom-> format('Ym-d'),'dateTo'=> $ dateTo-> format('Ym- d'),))那些留着?sql中的标记。
Darius.V

9

您可以使用以下方法轻松访问SQL参数。

   $result = $qb->getQuery()->getSQL();

   $param_values = '';  
   $col_names = '';   

   foreach ($result->getParameters() as $index => $param){              
            $param_values .= $param->getValue().',';
            $col_names .= $param->getName().',';
   } 

   //echo rtrim($param_values,',');
   //echo rtrim($col_names,',');    

因此,如果您打印出$param_values$col_names,则可以获取通过sql和相应列名传递的参数值。

注意:如果$param返回数组,则需要重新迭代,作为内部的参数IN (:?)通常是嵌套数组。

同时,如果您发现其他方法,请足够与我们分享:)

谢谢!


6

更清晰的解决方案:

 /**
 * Get string query 
 * 
 * @param Doctrine_Query $query
 * @return string
 */
public function getDqlWithParams(Doctrine_Query $query){
    $vals = $query->getFlattenedParams();
    $sql = $query->getDql();
    $sql = str_replace('?', '%s', $sql);
    return vsprintf($sql, $vals);
}

$ query-> getFlattenedParams(); 不存在
开发人员

5
Solution:1
====================================================================================

function showQuery($query)
{
    return sprintf(str_replace('?', '%s', $query->getSql()), $query->getParams());
}

// call function  
echo showQuery($doctrineQuery);

Solution:2
====================================================================================

function showQuery($query)
{
    // define vars              
    $output    = NULL;
    $out_query = $query->getSql();
    $out_param = $query->getParams();

    // replace params
   for($i=0; $i<strlen($out_query); $i++) {
       $output .= ( strpos($out_query[$i], '?') !== FALSE ) ? "'" .str_replace('?', array_shift($out_param), $out_query[$i]). "'" : $out_query[$i];
   }

   // output
   return sprintf("%s", $output);
}

// call function  
echo showQuery($doctrineQueryObject);

5

您可以使用 :

$query->getSQL();

如果使用的是MySQL,则可以使用Workbench查看正在运行的SQL语句。您还可以使用以下命令,从mysql中查看正在运行的查询:

 SHOW FULL PROCESSLIST \G

4

也许对某人有用:

// Printing the SQL with real values
$vals = $query->getFlattenedParams();
foreach(explode('?', $query->getSqlQuery()) as $i => $part) {
    $sql = (isset($sql) ? $sql : null) . $part;
    if (isset($vals[$i])) $sql .= $vals[$i];
}

echo $sql;

2

TL; DR

$qb = ... // your query builder
$query = $qb->getQuery();
// temporarily enable logging for your query (will also work in prod env)
$conf = $query->getEntityManager()->getConnection()->getConfiguration();
$backupLogger = $conf->getSQLLogger();
$logger = new \Doctrine\DBAL\Logging\DebugStack();
$conf->setSQLLogger($logger);
// execute query
$res = $query->getResult();
$conf->setSQLLogger($backupLogger); //restore logger for other queries
$params = [
  'query' => array_pop($logger->queries) //extract query log details
  //your other twig params here...
]
return $params; //send this to your twig template...

在您的树枝文件中,使用Doctrine的树枝助手过滤器:

// show raw query:
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)
// highlighted
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)|doctrine_pretty_query(highlight_only = true) }}
// highlighted and formatted (i.e. with tabs and newlines)
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)|doctrine_pretty_query }}

说明:

提到Prepared语句实际上是“真实查询”的其他答案是正确的,但是它们并不能满足明显要求者的期望……每个开发人员都希望显示“可运行查询”以进行调试(或向用户显示) 。

因此,我调查了Symfony探查器的来源,以了解他们是如何做到的。教义部分是教义的责任,因此他们制定了学说束以与Symfony集成。看看doctrine-bundle/Resources/views/Collector/db.html.twig文件后,您将了解他们的操作方式(这可能会因版本而异)。有趣的是,他们创建了可重复使用的树枝过滤器(请参见上文)。

为了使一切正常,我们需要为查询启用日志记录。有多种方法可以执行此操作,这里我使用DebugStack,它允许在不实际打印查询的情况下记录查询。如果您需要的话,这也可以确保它可以在生产模式下工作。

如果需要进一步的格式设置,您会发现它们在样式标签中包含一些CSS,因此只需“偷”它^^:

.highlight pre { margin: 0; white-space: pre-wrap; }
.highlight .keyword   { color: #8959A8; font-weight: bold; }
.highlight .word      { color: #222222; }
.highlight .variable  { color: #916319; }
.highlight .symbol    { color: #222222; }
.highlight .comment   { color: #999999; }
.highlight .backtick  { color: #718C00; }
.highlight .string    { color: #718C00; }
.highlight .number    { color: #F5871F; font-weight: bold; }
.highlight .error     { color: #C82829; }

希望这会有所帮助;-)


1

我写了一个简单的记录器,它可以记录带有插入参数的查询。安装:

composer require cmyker/doctrine-sql-logger:dev-master

用法:

$connection = $this->getEntityManager()->getConnection(); 
$logger = new \Cmyker\DoctrineSqlLogger\Logger($connection);
$connection->getConfiguration()->setSQLLogger($logger);
//some query here
echo $logger->lastQuery;

1
$sql = $query->getSQL();

$parameters = [];
    foreach ($query->getParameters() as $parameter) {
        $parameters[] = $parameter->getValue();
    }

$result = $connection->executeQuery($sql, $parameters)
        ->fetchAll();

您应该在答案中添加一些文本,以解释代码的作用。
DarkMukke

0

修改后的@dsamblas函数可在参数为日期字符串(例如``2019-01-01'')并且使用IN等传递数组时使用

$qb->expr()->in('ps.code', ':activeCodes'),

。因此,请执行dsamblas编写的所有内容,但用此代码替换startQuery或查看不同之处并添加我的代码。(以防他修改了功能,而我的版本没有修改)。

public function startQuery($sql, array $params = null, array $types = null)

{
    if($this->isLoggable($sql)){
        if(!empty($params)){
            foreach ($params as $key=>$param) {

                try {
                    $type=Type::getType($types[$key]);
                    $value=$type->convertToDatabaseValue($param,$this->dbPlatform);
                } catch (Exception $e) {
                    if (is_array($param)) {
                        // connect arrays like ("A", "R", "C") for SQL IN
                        $value = '"' . implode('","', $param) . '"';
                    } else {
                        $value = $param; // case when there are date strings
                    }
                }

                $sql = join(var_export($value, true), explode('?', $sql, 2));
            }

        }
        echo $sql . " ;".PHP_EOL;
    }
}

没有测试太多。


0

我对此主题进行了一些研究,因为我想调试生成的SQL查询并在sql编辑器中执行它。从所有答案中可以看出,这是一个高度技术性的话题。

当我假设最初的问题基于dev-env时,此刻一个非常简单的答案就丢失了。您可以只使用Symfony探查器中的构建。只需单击“教义”选项卡,滚动到要检查的查询。然后单击“查看可运行查询”,您可以将查询直接粘贴到SQL编辑器中

更多的基于UI的方法,但是非常快速,并且没有调试代码开销。

在此处输入图片说明


0
$sql = $query->getSQL();
$obj->mapDQLParametersNamesToSQL($query->getDQL(), $sql);
echo $sql;//to see parameters names in sql
$obj->mapDQLParametersValuesToSQL($query->getParameters(), $sql);
echo $sql;//to see parameters values in sql

public function mapDQLParametersNamesToSQL($dql, &$sql)
{
    $matches = [];
    $parameterNamePattern = '/:\w+/';
    /** Found parameter names in DQL */
    preg_match_all($parameterNamePattern, $dql, $matches);
    if (empty($matches[0])) {
        return;
    }
    $needle = '?';
    foreach ($matches[0] as $match) {
        $strPos = strpos($sql, $needle);
        if ($strPos !== false) {
            /** Paste parameter names in SQL */
            $sql = substr_replace($sql, $match, $strPos, strlen($needle));
        }
    }
}

public function mapDQLParametersValuesToSQL($parameters, &$sql)
{
    $matches = [];
    $parameterNamePattern = '/:\w+/';
    /** Found parameter names in SQL */
    preg_match_all($parameterNamePattern, $sql, $matches);
    if (empty($matches[0])) {
        return;
    }
    foreach ($matches[0] as $parameterName) {
        $strPos = strpos($sql, $parameterName);
        if ($strPos !== false) {
            foreach ($parameters as $parameter) {
                /** @var \Doctrine\ORM\Query\Parameter $parameter */
                if ($parameterName !== ':' . $parameter->getName()) {
                    continue;
                }
                $parameterValue = $parameter->getValue();
                if (is_string($parameterValue)) {
                    $parameterValue = "'$parameterValue'";
                }
                if (is_array($parameterValue)) {
                    foreach ($parameterValue as $key => $value) {
                        if (is_string($value)) {
                            $parameterValue[$key] = "'$value'";
                        }
                    }
                    $parameterValue = implode(', ', $parameterValue);
                }
                /** Paste parameter values in SQL */
                $sql = substr_replace($sql, $parameterValue, $strPos, strlen($parameterName));
            }
        }
    }
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.