Print SQL query of ORM query builder in cakephp3 - cakephp

How to print ORM query
$query = $articles->find('all')->contain(['Comments']);
For example print =>
SELECT * FROM comments WHERE article_id IN (comments);

Wrapping your ORM query result with the debug function will show the SQL and bound params:
debug($query);
You can also similarly look at the query results with the debug function.See CakePHP 3: retrieving data and result sets — Debugging Queries and ResultSets

what about $query->sql()?
$qb = $this->Person->find()->select(["id", "text" => "concat(Name,' ',Family)"])
->where(['id >' => 0])
->where($query ? ["OR" => $filters] : null)
->limit(10);
dd($qb->sql());
and result:
.../src/Controller/ClientController.php (line 86)
'SELECT Person.id AS `Person__id`, concat(Name,' ',Family) AS `text` FROM person Person WHERE (id > :c0 AND (Family like '%sam%' OR Name like '%sam%' OR Family like '%sam%' OR Name like '%sam%')) LIMIT 10'

I prefer this:
public function __debugInfo()
{
return [
'query' => $this->_query,
'items' => $this->toArray(),
];
}
// Print the query
debug($query->__debugInfo()['sql']);
// Prints this
SELECT * FROM comments WHERE article_id IN (comments);

Related

union request and pagination in cakephp4

I made two requests. The first one gives me 2419 results and I store the result in $requestFirst. The second, 1 result and I store the result in $requestTwo.
I make a union :
$requestTot = $requestFirst->union($requestTwo);
The total of the $requestTot is 2420 results so all is well so far.
Then :
$request = $this->paginate($requestTot);
$this->set(compact('request'));
And here I don't understand, on each page of the pagination I find the result of $requestTwo. Moreover the pagination displays me :
Page 121 of 121, showing 20 record(s) out of 2,420 total
This is the right number of results except that when I multiply the number of results per page by the number of pages I get 2540. This is the total number of results plus one per page.
Can anyone explain?
Check the generated SQL in Debug Kit's SQL panel, you should see that the LIMIT AND OFFSET clauses are being set on the first query, not appended as global clauses so that they would affect the unionized query.
It will look something like this:
(SELECT id, title FROM a LIMIT 20 OFFSET 0)
UNION
(SELECT id, title FROM b)
So what happens then is that pagination will only be applied to the $requestFirst query, and the $requestTwo query will be unionized on top of it each and every time, hence you'll see its result on every single page.
A workaround for this current limitation would be to use the union query as a subquery or a common table expression from which to fetch the results. In order for this to work you need to make sure that the fields of your queries for the union are being selected without aliasing! This can be achieved by either using Table::subquery():
$requestFirst = $this->TableA
->subquery()
->select(['a', 'b'])
// ...
$requestTwo = $this->TableB
->subquery()
->select(['c', 'd'])
// ...
or by explicitly selecting the fields with aliases equal to the column names:
$requestFirst = $this->TableA
->find()
->select(['a' => 'a', 'b' => 'b'])
// ...
$requestTwo = $this->TableB
->find()
->select(['c' => 'c', 'd' => 'd'])
// ...
Then you can safely use those queries for a union as a subquery:
$union = $requestFirst->union($requestTwo);
$wrapper = $this->TableA
->find()
->from([$this->TableA->getAlias() => $union]);
$request = $this->paginate($wrapper);
or as a common table expression (in case your DBMS supports them):
$union = $requestFirst->union($requestTwo);
$wrapper = $this->TableA
->find()
->with(function (\Cake\Database\Expression\CommonTableExpression $cte) use ($union) {
return $cte
->name('union_source')
->field(['a', 'b'])
->query($union)
})
->select(['a', 'b'])
->from([$this->TableA->getAlias() => 'union_source']);
$request = $this->paginate($wrapper);

select count of records group by month in cakephp 3

I'm using CakePHP 3.x+
I have to show a graph on the page and thus want to build script for that.
I have to select count of records group by month for current year.
This is what I have tried.
$graph = $this->GenerateVideos->find()
->select('COUNT(id)', 'MONTH(created)')
->where(['YEAR(created)' => date('Y')])
->group(['MONTH(created)']);
which generates sql like
'sql' => 'SELECT GenerateVideos.COUNT(id) AS GenerateVideos__COUNT(`id`) FROM generate_videos GenerateVideos WHERE YEAR(created) = :c0 GROUP BY MONTH(created) ',
'params' => [
':c0' => [
'value' => '2018',
'type' => null,
'placeholder' => 'c0'
]
],
But this is giving error as
Error: SQLSTATE[42000]: Syntax error or access violation:
1064 You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use near '(`id`)
FROM generate_videos GenerateVideos WHERE YEAR(created) = '2018' GROUP BY' at line 1
Try using an array in your ->select() value:
->select(['COUNT(id)', 'MONTH(created)'])
In the book, it always shows an array, and it doesn't appear to be utilizing your second select value.
Or, per the book here, you could try this:
$query = $this->GenerateVideos->find();
$query->select(['count' => $query->func()->count('id'), 'month' => 'MONTH(created)']);
$query->where(['YEAR(created)' => date('Y')])
$query->group(['month' => 'MONTH(created)']);

Subquerys in cakephp 3.x, new ORM?

I'm new in Cakephp 3.x and I'm having some trouble to create a subquery in the new ORM format. I have this report in my application, that needs to return the follow result:
1. There are three entities - Users, Calls, CallStatus.
2. Users hasMany Calls, Calls hasMany CallStatus.
3. I need to count how many CallStatus each user has in Calls.
Now follow the query that I need to put on new ORM format:
SELECT U.name,
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =1 and C.user_id=U.id) AS 'Unavailable',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =2 and C.user_id=U.id) AS 'Busy',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =3 and C.user_id=U.id) AS 'Contacted',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =4 and C.user_id=U.id) AS 'Error'
FROM `users` AS U
WHERE U.profile=3 and U.is_active=1
Could someone give me a help, please? Thanks
If I understand you correctly, you want to see the number of calls for every callstatus you have for a specific user.
Try the following. Note that I used the CakePHP convention for naming the callstatuses (which is plural).
// get the tableregistry
use Cake\ORM\TableRegistry;
$callstatuses = Cake\ORM\TableRegistry::get('Callstatuses');
// for user with id 2, get the number of calls for each callstatus
$callstatuses->find()
->contain(['Calls'])
->where(['Calls.user_id' => 2, 'User.is_active' => 1])
->countBy('name')
->toArray();
// output could be:
//[ 'Unavailable' => 2, 'Busy' => 1 ]
You can find information about creating queries in the CakePHP book: see 'Query Builder'.
If you want to know more about working with/on queries, note that queries are Collections. Anything you can do on a Collection object, you can also do in a Query object. See the Collection section in the CakePHP book.
You have to use subqueries, as many as you want!
Here is an example for your case:
$q = $this->Calls->find();
$q1->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 1]);
$q2->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 2]);
$q3->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 3]);
$q4->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 4]);
$qUsers = $this->Users->find()
->select([
'id',
'first_name',
'Unavailable' => $q1,
'Busy' => $q2,
'Contacted' => $q3,
'Error' => $q4
])
->where(['profile' => 3, 'active' => 1])
->all();
Note: That nicer if you use a loop to create suqueries in this case.

CakePHP count query gives different result when run in phpmyadmin

I am running the following query on my database:-
SELECT COUNT(*) AS COUNT, `Doctor`.`device_type` FROM `doctors` AS `Doctor` WHERE 1 = 1 GROUP BY `Doctor`.`device_type`
and it gives the result:-
count device_type
47 Android
23 iPhone
Whereas when running this query as a CakePHP query it gives the result as '2':-
$this->Doctor->find('count',array('group'=>'Doctor.device_type'));
Can anyone please suggest why this is happening?
The CakePHP result is correct as it is returning a count of the number of results returned by your query. In your case you have 2 rows: 'Android' and 'iPhone'.
find('count') always returns an integer, not an array. What Cake is doing is basically this:-
$data = $this->Doctor->find('all', array('group' => 'Doctor.device_type'));
$count = count($data); // This is what find('count') will return.
You need to do something like the following instead:-
$data = $this->Doctor->find('all', array(
'fields' => array(
'Doctor.device_type',
'COUNT(Doctor.*) AS count'
)
'group' => 'Doctor.device_type'
));

Convert normal SQL query to Zend_Db?

I cant convert this below query to Zend_Db:
SELECT `mfaq`.* FROM `m_faq` AS `mfaq`
WHERE (mfaq.delete_flg <> 'D' OR mfaq.delete_flg IS NULL)
AND ((mfaq.title like '%$title%')
OR (mfaq.title like '%$title%')
AND (mfaq.title like '%$title%')
OR (mfaq.title like '%$title%'))
ORDER BY `create_date` DESC
Some help???
First you need to have created dbtable model:
class Application_Model_DbTable_Mfaq extends Zend_Db_Table_Abstract
{
protected $_name = 'm_faq';
}
Then use it as follows:
$table = new Application_Model_DbTable_Mfaq();
$select = $table->getAdapter()->select()
->from(array('mfaq'=>$table->info(Zend_Db_Table::NAME)))
->where("( mfaq.delete_flg <> 'D'")
->orWhere("mfaq.delete_flg IS NULL )")
->where("( mfaq.title like ?", "%$title%")
->orWhere("mfaq.title like ? ", "%$title%")
->where(" mfaq.title like ?", "%$title%")
->orWhere("mfaq.title like ? )", "%$title%")
->order("create_date DESC");
// echo $select; // shows your sql
$results = $select->query()->fetchAll();
I hope, you take into account that AND have higher priority than OR.

Resources