I'm doing this:
$students = Student::find()->all();
return $this->render('process', array('students' => $students));
and then this in the view:
foreach($students as $student)
{
echo $student->name . ', ';
echo $student->getQuizActivitiesCount(); ?> <br /> <?php
}
i would like to see the sql query being performed. a student "has many" quiz activities, and the query performs perfectly, but i need to see the raw SQL. is this possible?
Method 1
With relations that return yii\db\ActiveQuery instance it's possible to extract the raw SQL query directly in code for example with var_dump().
For example if we have user relation:
/**
* #return \yii\db\ActiveQuery
*/
public function getUser()
{
return $this->hasOne(User::className(), ['id' => 'user_id']);
}
You can then var_dump() the raw SQL like that:
var_dump($model->getUser()->prepare(Yii::$app->db->queryBuilder)->createCommand()->rawSql);
exit();
Note that you should call it like that and not $model->user->... (the latter returns User instance).
But in your case it's not possible because count() immediately returns int. You can var_dump() partial query without count(), but I think it's not convenient.
Note that you can use this method for dumping generated SQL of any ActiveQuery instances (not only those that were returned by relation), for example:
$query = User::find()->where(['status' => User::STATUS_ACTIVE]);
var_dump($query->prepare(Yii::$app->db->queryBuilder)->createCommand()->rawSql);
exit();
Method 2
This is much simpler in my opinion and I personally prefer this one when debugging SQL queries.
Yii 2 has built-in debug module. Just add this to your config:
'modules' => [
'debug' => [
'class' => 'yii\debug\Module',
],
],
Make sure you only have it locally and not on production. If needed, also change allowedIPs property.
This gives you functional panel at the bottom of the page. Find the DB word and click on either count or time. On this page you can view all executed queries and filter them.
I usually don't filter them in Grid and use standard browser search to quickly navigate through and find the necessary query (using the table name as keyword for example).
Method 3
Just make an error in query, for example in column name - cityy instead of city. This will result as database exception and then you can instantly see the generated query in error message.
If you want to log all relational queries of ActiveRecord in console application all proposed methods don't help. They show only main SQL on active record's table, \yii\debug\Module works only in browser.
Alternative method to get all executed SQL queries is to log them by adding specific FileTarget to configuration:
'log' => [
'targets' => [[
...
], [
'class' => 'yii\log\FileTarget',
'logFile' => '#runtime/logs/profile.log',
'logVars' => [],
'levels' => ['profile'],
'categories' => ['yii\db\Command::query'],
'prefix' => function($message) {
return '';
}
]]
]
UPDATE
In order to log insert/update/delete queries one should also add yii\db\Command::execute category:
'categories' => ['yii\db\Command::query', 'yii\db\Command::execute']
you can try this, assume you have a query given like:
$query = new Books::find()->where('author=2');
echo $query->createCommand()->sql;
or to get the SQL with all parameters included try:
$query->createCommand()->getRawSql()
In addition to arogachev answer, when you already work with an ActiveQuery object, here is the line I search to view the rawsql.
/* #var $studentQuery ActiveQuery */
$studentQuery = Student::Find();
// Construct the query as you want it
$studentQuery->where("status=3")->orderBy("grade ASC");
// Get the rawsql
var_dump($studentQuery->prepare(Yii::$app->db->queryBuilder)->createCommand()->rawSql);
// Run the query
$studentQuery->all();
when you have a query object you can also use
$query->createCommand()->getRawSql()
to return the Raw SQL with the parameters included or
$query->createCommand()->sql
which will output the Sql with parameters separately.
In order to log/track every/all queries:
extend \yii\db\Connection and override createCommand method, like below:
namespace app\base;
class Connection extends \yii\db\Connection {
public function createCommand($sql = null, $params = array()) {
$createCommand = parent::createCommand($sql, $params);
$rawSql = $createCommand->getRawSql();
// ########### $rawSql -> LOG IT / OR DO ANYTHING YOU WANT WITH IT
return $createCommand;
}
}
Then, simply change your db connection in your db config like below:
'db' => [
'class' => 'app\base\Connection', // #### HERE
'dsn' => 'pgsql:host=localhost;dbname=dbname',
'username' => 'uname',
'password' => 'pwd',
'charset' => 'utf8',
],
Now, you can track/read/... all queries executed by db connection.
Try like,
$query = Yii::$app->db->createCommand()
->update('table_name', ['title' => 'MyTitle'],['id' => '1']);
var_dump($query->getRawSql()); die();
$query->execute();
Output:
string 'UPDATE `table_name`
SET `title`='MyTitle' WHERE `id`='1'
' (length=204)
Related
In config/database.php (Laravel 8) this is configured:
'connections' => [
'my_db' => [
'driver' => 'mysql',
'host' => 'xx.xx.xxx.xxx',
'port' => '3306',
'database' => 'Sqlxxx',
'username' => 'Sqlxxxx',
'password' => 'passxxx',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
],
'other_db' => [
...
],
],
I try to save some data, but it doesn't work.
DB::connection('my_db')->beginTransaction();
$data = [];
$data["A"] = "a";
$data["B"] = "b";
$pdo = DB::connection('my_db')->getPdo();
if ($pdo) {
DB::connection('my_db')
->table('my_table')
->insert($data);
}
DB::connection('my_db')->commit();
I specify that the connection to the DB my_db works, because I can get data from it. I have the impression that it can read data but not save them.
EDIT:
I have multiple connections defined in config/database.php
my_db is a database outside of my project
There is no error message; just a blank page (APP_DEBUG is set to true and APP_ENV to "local")
I added DB::connection('my_db')->beginTransaction(); to the beginning of the script, to no avail.
It doesn't work in the following way either: DB::connection('my_db')->insert('insert into my_table (A, B) values (?, ?)', ['a', 'b']);
I'm freaking out. Updating works, inserting doesn't. This works: DB::connection('my_db')->table('my_table')->where('id', '1')->update(['a' => '111']);
There are a couple of spots in the documentation on how to do an insert:
The DB Facade's insert() method:
DB::insert('insert into example_table values (col_1, col_2), (?, ?)', ['col_1_value', 'col_2_value']);
This uses a bound parameterized query to directly insert into the default Connection's example_table table. This doesn't appear to be compatible with DB::connection(...)->insert(), as the insert() method used while chaining is not the same method as above, but rather the Builder's method (see below).
The Query Builder's insert() method:
DB::table('example_table')->insert(['col_1' => 'col_1_value', 'col_2' => 'col_2_value']);
The Query Builder in Laravel is a Database-Agnostic wrapper for allowing communication with the database based on the driver (MySQL, PostGres, etc.). It expects a simple associative array representation of the columns being inserted, like ['a' => 'a', 'b' => 'b', ...], and performs the insert based on the supplied or default Connection and specified table (i.e. this is compatible with DB::connection()).
An additional approach would be to use a Model, with the specified Connection defined. For example, let's define an Example.php Model, with the my_db connection:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Example extends Model {
protected $connection = 'my_db';
...
}
Because we're setting protected $connection = 'my_db';, any calls to this model with automatically use the my_db connection defined in config/database.php's connections array. Examples:
$example = Example::create(['a' => 'a', 'b' => 'b']);
This would run INSERT into examples (a, b) VALUES ('a', 'b'); using the correct database syntax (MySQL, Postgres, etc.). Additional calls to Example::update(), Example::delete(), etc. would all know to use the proper connection.
I would recommend this approach over the DB::connection()->table() method, and would highly recommend not using the DB::insert() method at all.
SOLVED: It was my mistake. I was trying to create a new record forgetting to indicate all the NOT NULLABLE ones. I could have figured it out by the fact that the update worked while the insertion did not. I confirm that DB::connection('my_db')->table('my_table')->insert($data); works perfectly.
Since you don't get any errors upon inserting data it seem to be either of two cases:
Your transaction somehow reverts
Your data doesn't get flushed (only cached somewhere in unit of work or something similar)
Try removing the DB::connection('my_db')->beginTransaction(); and DB::connection('my_db')->commit(); statements.
I am using Yii2 and using the yii\rbac\DbManager for auth assignment.
I was looking at the logs to see where all the database calls are coming from and this query
SELECT `b`.* FROM `auth_assignment` `a`, `auth_item` `b` WHERE
((`a`.`item_name`=`b`.`name`) AND (`a`.`user_id`='91')) AND (`b`.`type`=1)
Keeps running again and again, sometimes 10/15 times in succession.
I have added
'authManager' => [
'class' => 'yii\rbac\DbManager',
'cache' => 'cache'
],
As the docs say that will cache the auth assignments (I am using Memcached). But it doesnt seem to work...
Anyone have any idea? Either how to cache it or why it keeps getting called so many times?
Cheers
Add caching in
vendor/yiisoft/yii2/rbac/DbManager.php
(Also in all places you need caching)
this code:
$all_data = $this->db->cache(function ($db) use ($query) {
return $query->all($db);
},360);
public function getAssignments($userId)
{
if (empty($userId)) {
return [];
}
$query = (new Query)
->from($this->assignmentTable)
->where(['user_id' => (string) $userId]);
$all_data = $this->db->cache(function ($db) use ($query) {
return $query->all($db);
},360);
$assignments = [];
foreach ($all_data as $row) {
$assignments[$row['item_name']] = new Assignment([
'userId' => $row['user_id'],
'roleName' => $row['item_name'],
'createdAt' => $row['created_at'],
]);
}
return $assignments;
}
https://github.com/yiisoft/yii2/issues/3168
Only cache auth_item, auth_rule and auth_item_child data. All these
data are cached as a single entry in cache. Note that
auth_assignment is too big to be cached (imagine a system with
millions of users).
How do I make a query like
http://localhost:9200/index/businesses/_search?q=services
in Cakephp3 ElasticSearch
I have tried
$this->Businesses->find('all')->where(['*'=>'services']);
However I get no results.
The more accurate answer is to use a builder
$q = 'services';
$businesses = $this->Businesses->find('all')->where(function ($builder) use($q) {
return $builder->query(new \Elastica\Query\SimpleQueryString($q));
});
The _all key might solve the problem
$this->Businesses->find('all')->where(['_all'=>'services']);
Not sure what you're asking, but the asterisk in where(['*'=>'services']) should be a column name in your table schema/database.
Another common issue is that the result of find() is not the result of the query by design. See my answer to Cake PHP 3 needs limit option for find all method and also CakePHP 3 Cookbook: ElasticSearch — Searching Indexed Documents:
$query = $this->Articles->find()
->where([
'title' => 'special',
'or' => [
'tags in' => ['cake', 'php'],
'tags not in' => ['c#', 'java']
]
]);
// The query returns multiple rows which you can loop through
// or alternatively you can call $query->all(); to get the object
// $query->toArray(); to get the array
foreach ($query as $article) {
echo $article->title;
}
I'm using CakePHP 1.3.8, and I've installed the CakeDC Search plugin. I have a Tutorial model, which is in a HABTM relationship with a LearningGoal model.
I have a search action & view in the Tutorials controller with which I can successfully search fields in the Tutorial model. I'd also like to filter my tutorial search results using LearningGoal checkboxes on the same form. I've tried adding various parameters to Tutorial's $filterArgs and TutorialsController's $presetVars. I've also tried moving the relevant $filterArgs to the LearningGoal model. I have not yet been able to successfully trigger the entry for learning goals in $filterArgs.
I think I must be missing something obvious. Or maybe the Search plugin doesn't support what I'm trying to do. Does anyone know how to use this plugin to search on associated models?
So here's what I've figured out. You can combine what's below with the Search plugin directions to search on related models.
The $filterArgs piece in the Tutorial model must look like this:
var $filterArgs = array(
array('name' => 'LearningGoal', 'type' => 'subquery', 'method' => 'findByLearningGoals', 'field' => 'Tutorial.id'),
);
Here's the supporting function in the Tutorial model:
function findByLearningGoals($data = array()) {
$ids = explode('|', $data['LearningGoal']);
$ids = join(',', $ids);
$this->LearningGoalsTutorial->Behaviors->attach('Containable', array('autoFields' => false));
$this->LearningGoalsTutorial->Behaviors->attach('Search.Searchable');
$query = $this->LearningGoalsTutorial->getQuery('all',
array(
'conditions' => array('LearningGoalsTutorial.learning_goal_id IN (' . $ids . ')'),
'fields' => array('tutorial_id'),
)
);
return $query;
}
In TutorialsController, $presetVars should look like this:
public $presetVars = array(
array('field' => 'LearningGoal', 'type' => 'checkbox', 'model' => 'Tutorial'),
);
And in my search action in TutorialsController, I did this:
$this->LearningGoal = $this->Tutorial->LearningGoal;
The Prg component seems to need that.
I am using CakePHP version 2.X
Every time I come to do this in a project I always spend hours figuring out how to do it using CakeDC search behavior so I wrote this to try and remind myself with simple language what I need to do. I've also noticed that although Michael is generally correct there is no explanation which makes it more difficult to modify it to one's own project.
When you have a "has and belongs to many" relationship and you are wanting to search the joining table i.e. the table that has the two fields in it that joins the tables on either side of it together in a many-to-many relationship you want to create a subquery with a list of IDs from one of the tables in the relationship. The IDs from the table on the other side of the relationship are going to be checked to see if they are in that record and if they are then the record in the main table is going to be selected.
In this following example
SELECT Handover.id, Handover.title, Handover.description
FROM handovers AS Handover
WHERE Handover.id in
(SELECT ArosHandover.handover_id
FROM aros_handovers AS ArosHandover
WHERE ArosHandover.aro_id IN (3) AND ArosHandover.deleted != '1')
LIMIT 20
all the records from ArosHandover will be selected if they have an aro_id of 3 then the Handover.id is used to decide which Handover records to select.
On to how to do this with the CakeDC search behaviour.
Firstly, place the field into the search form:
echo $this->Form->create('Handover', array('class' => 'form-horizontal'));?>
echo $this->Form->input('aro_id', array('options' => $roles, 'multiple' => true, 'label' => __('For', true), 'div' => false, true));
etc...
notice that I have not placed the form element in the ArosHandover data space; another way of saying this is that when the form request is sent the field aro_id will be placed under the array called Handover.
In the model under the variable $filterArgs:
'aro_id' => array('name' => 'aro_id', 'type' => 'subquery', 'method' => 'findByAros', 'field' => 'Handover.id')
notice that the type is 'subquery' as I mentioned above you need to create a subquery in order to be able to find the appropriate records and by setting the type to subquery you are telling CakeDC to create a subquery snippet of SQL. The method is the function name that are going to write the code under. The field element is the name of the field which is going to appear in this part of the example query above
WHERE Handover.id in
Then you write the function that will return the subquery:
function findByAros($data = array())
{
$ids = ''; //you need to make a comma separated list of the aro_ids that are going to be checked
foreach($data['aro_id'] as $k => $v)
{
$ids .= $v . ', ';
}
if($ids != '')
{
$ids = rtrim($ids, ', ');
}
//you only need to have these two lines in if you have not already attached the behaviours in the ArosHandover model file
$this->ArosHandover->Behaviors->attach('Containable', array('autoFields' => false));
$this->ArosHandover->Behaviors->attach('Search.Searchable');
$query = $this->ArosHandover->getQuery('all',
array(
'conditions' => array('ArosHandover.aro_id IN (' . $ids . ')'),
'fields' => array('handover_id'), //the other field that you need to check against, it's the other side of the many-to-many relationship
'contain' => false //place this in if you just want to have the ArosHandover table data included
)
);
return $query;
}
In the Handovers controller:
public $components = array('Search.Prg', 'Paginator'); //you can also place this into AppController
public $presetVars = true; //using $filterArgs in the model configuration
public $paginate = array(); //declare this so that you can change it
// this is the snippet of the search form processing
public function admin_find()
{
$this->set('title_for_layout','Find handovers');
$this->Prg->commonProcess();
if(isset($this->passedArgs) && !empty($this->passedArgs))
{//the following line passes the conditions into the Paginator component
$this->Paginator->settings = array('conditions' => $this->Handover->parseCriteria($this->passedArgs));
$handovers = $this->Paginator->paginate(); // this gets the data
$this->set('handovers', $handovers); // this passes it to the template
If you want any further explanation as to why I have done something, ask and if I get an email to tell me that you have asked I will give an answer if I am able to.
I'd like to exclude results from a call to a Lithium model's find() method. I need to do this for models with both MongoDB and MySQL data sources, but in SQL I mean something like WHERE myfield NOT IN (1,2,3).
I'd like to just be able to pass a not clause in the conditions array like below, but that doesn't appear to be possible.
Item::all(array('conditions' => array('not' => array('myfield' => array(1,2,3))));
So my question is, is this possible in Lithium in a way that I've overlooked? And if not, what would be the most Lithium-ish way to implement it for my models?
Just to clarify, Lithium's MongoDB adapter supports most SQL comparison operators as a convenience, so for either Mongo or MySQL, you could simply write the query as follows:
Item::all(array('conditions' => array(
'myfield' => array('!=' => array(1,2,3))
)));
And it should give you the results you expect. For MySQL, the query should look something like:
SELECT * FROM items WHERE myfield NOT IN (1, 2, 3);
And in Mongo:
db.items.find({ myfield: { $nin: [1, 2, 3] }})
Merely filtering for MongoDB can easily be achieved like this:
Item::all(array('conditions' =>
array('myfield' => array(
'$nin' => array(1,2,3)
))
));
If this is something you do a lot you could even create a custom finder for it :
class MyModel extends \lithium\data\Model {
public static function __init()
{
parent::__init();
static::finder('notin', function($self, $params, $chain) {
// Take all array keys that are not option keys
$array = array_diff_key($params['options'],
array_fill_keys(array('conditions', 'fields','order','limit','page'),0));
// Clean up options leaving only what li3 expects
$params['options'] = array_diff_key($params['options'], $array);
$params['options']['conditions'] = array(
'myfield' => array(
'$nin' => $array
)
);
return $chain->next($self, $params, $chain);
});
}
}
And call it like this :
MyModel::notin(array(1,2,3));
In the same manner you could create a custom finder for MySQL sources.
As you probably can see this creates some issues if you pass something like array('fields'=>$array) as it would overwrite the option.
What happens is that ::notin() (finders in general) has a distinct behavior for the (array,null) signature. If that happens it thinks the first array is options and the finder took no arguments.
Using notin($array,array()) breaks the previous finder because the first argument ends up in $params['notin'] when the real second argument (options) is passed.
If you mix data sources on the fly here I would create a custom model that does not inherit \lithium\data\Model and have it delegate
to the different models and create the conditions based on the end models data source.
class MyFacadeModel {
public static function byNotIn($conditions, $source) {
return ($source == "mongodb")
? $source::find( $rewrittenConditions)
: $source::find( $rewrittenConditionsForMysql );
}
}
(Code might be slightly incorrect as its mostly taken from the top of my head)