how write orderByField query in cakephp 3 - mysql

In sql, I have this query, It is working perfectly and order by given array in "order BY Field". But in cakephp it is not working.
SELECT * FROM users
WHERE users.Id IN (3,7,2,13)
ORDER BY FIELD(users.Id ,3,7,2,13)
I get the users in following order. which is perfect
user _id
3
7
2
13
Cakephp 3
$unique_user_id_list = (3,7,2,13);
$id_list = '3','7','2','13'; This is string.
$users = $this->Users->find('all',[
'contain' => [],
'conditions'=>['Id IN' => $unique_user_id_list],
'order BY FIELD'=>['Users.Id'=>$id_list]
]);
In this query, I get user in following order
user_id
2
3
7
13
I tried both following ways but no one work. Only "order" clause gives error while other one "order by field" gives no error but does not work as expected.
'order'=>['Users.Id'=>$id_list]
'order BY FIELD'=>['Users.Id'=>$id_list]
Any idea how to write above sql query in cakephp 3 to get it work.

Answer for this question
In short words 'order' => "FIELD(id, '2', '3', '7', '13')"
In detail first use loop to convert array in string.
$id_list;
foreach ($unique_user_id_list as $key => $value) {
if($check == 1){
$id_list = 'FIELD ( Id'.',' . "'". $value. "'";
$check =2;
}
else{
$id_list = $id_list . "," . "'". $value. "'";
}
}
$id_list = $id_list . ")";
above loop will generate "FIELD(id, '2', '3', '7', '13')"
Full query how I used it is given below.
$users = $this->Users->find('all',[
'contain' => [],
'conditions'=>['Id IN' => $unique_user_id_list],
'order'=> $id_list
]);

Try formatting the order statement as follows
->order(['users.Id'=>'ASC']);

Related

Getting a raw SQL query with variables in Yii1

Is Yii1 has any native methods to get the raw SQL with variables built?
I try to get a complex query built on a few subqueries using CDbExpression and CommandBuilder. I got this as a result:
SELECT * FROM `news` `t` WHERE id IN (:ycp0, :ycp1, :ycp2, :ycp3, :ycp4) LIMIT 5
The dump of the criteria content:
CDbCriteria Object (
[select] => *
[condition] => id IN (:ycp0, :ycp1, :ycp2, :ycp3, :ycp4)
...
[params] => Array(
[:ycp0] => CDbExpression Object(
[expression] => SELECT id FROM `news` `t` WHERE (rubric=:rb1) AND (:im2 & `im`=:im2) LIMIT 1
[params] => Array(
[:rb1] => 1
[:im2] => 2
)
)
...
)
)
I expected for compiled query string like this:
SELECT * FROM .. WHERE id IN(
(SELECT id FROM .. WHERE .. ORDER BY .. LIMIT 1),
(SELECT id FROM .. WHERE .. ORDER BY .. LIMIT 1)
) ORDER BY .. LIMIT 5
This is what I do in my code
$criteria = new CDbCriteria( ... );
$sql = $this->commandBuilder->createFindCommand($tableName, $criteria)->getText();
$queries[] = new CDbExpression($sql, $criteria->params);
Then I try to combine subqueries to one complex query
$criteria = new CDbCriteria( ... );
$criteria->addInCondition('id', $queries);
And finally, I try to get the result as SQL-query
$sql = $this->commandBuilder->createFindCommand($tableName, $criteria)->getText();
You get the SQL with params, as you have
$sql = $this->commandBuilder->createFindCommand($tableName, $criteria)->getText();
Then get the params enclosed in quotes:
$params = array_map(function($param) { return '"' . $param . '"'; }, $criteria->params);
Finally, replace the pairs:
echo strtr($sql, $params);
You can use enableParamLogging in your db config (boolean). You can set it up so it's used conditionally -- make sure to not use it in production.
'db' => [
'connectionString' => 'mysql:host=' . $dbhost . ';port=' . $dbport . ';dbname=' . $dbname,
'emulatePrepare' => true,
'username' => $db_user,
'password' => $db_pass,
'schemaCachingDuration' => 3600,
'charset' => 'utf8',
'enableProfiling' => $db_profile,
'enableParamLogging' => $db_params,
],
Then you can add 'class'=>'CWebLogRoute' to your log routes if you want to see all the output in your browser. docs
I think you want to do something like this:
$criteria = ...;
$command = $builder->createFindCommand($schema->getTable('name_of_table'), $criteria);
$results = $command->text;
There are few other options to perform the same thing but i will suggest you
should use the following
$results = MyModel::model()->findAllBySql("...");
Or you can prefer the following :
Sub-queries ActiveRecord Yii

Not equal condition with findAll()

I am trying to pull record from a table using the following code
$userId = Yii::$app->user->id;
$lists = PromoLists::findAll(['user_id' => $userId, 'list_type' => 'custom']);
which outputs a query like below
select * from promo_lists where user_id ='$userId' and list_type='custom'
But i am unable to find any thing in the documentation that would help me achieve it with the following condition.
select * from promo_lists where user_id ='$userId' and list_type='custom' and status!='deleted'
as the status is an ENUM field and there are 4 different status
'active','pending','rejected','deleted'
currently i used the following approach
PromoLists::findAll(['user_id' => $userId, 'list_type' => 'custom', 'status'=>['active','pending','rejected']]);
which outputsthe following query
select * from promo_lists where user_id ='$userId' and list_type='custom' and status in ('active','pending','rejected')
which somehow achieves the same thing but this query would need to be edited every time when there is a new status type added to the table column status.
i know i can do this by using PromoLists::find()->where()->andWhere()->all()
but how to check with != / <> operator using findAll().
Simply like this:
PromoLists::find()->where(['and',
[
'user_id' => $userId,
'list_type' => 'custom',
],
['<>', 'status', 'deleted'],
])->all();
Using operator format in condition
http://www.yiiframework.com/doc-2.0/guide-db-query-builder.html#operator-format
PromoLists::find()
->andWhere([
'user_id' => $userId,
'list_type' => 'custom',
['!=', 'status', 'deleted']
])
->all();

How to use AND and OR in MySQL query?

I want to get those records whose date_last_copied field is empty or less than the current date. I tried this, but it did not give me the desired result:
$tasks = $this->Control->query("
SELECT *
FROM
`controls`
WHERE
`owner_id` = ".$user_id."
AND `control_frequency_id` = ".CONTROL_FREQUENCY_DAILY."
OR `date_last_copied` = ''
OR `date_last_copied` < ". strtotime(Date('Y-m-d'))."
");
Current query looks something like this, I think. That is, find the records with the correct owner_id and frequency_id, where the date_last_copied is null or less than a certain date. Is that logic correct?
SELECT *
FROM controls
WHERE owner_id = ::owner_id::
AND control_frequency_id = ::frequency_id::
AND (
date_last_copied IS NULL
OR date_last_copied < ::date::
)
But we should really be using the CakePHP query builder, rather than running raw SQL. This article gives some details. If I were to take a stab at a solution, we'd want something like the following. But we ideally want someone from the CakePHP community to chime in here. EDIT: Note that this seems to be for CakePHP 3.0, only.
// Build the query
$query = TableRegistry::get('controls')
->find()
->where([
'owner_id' => $ownerId,
'control_frequency_id' => $frequencyId,
'OR' => [
['date_last_copied IS' => null],
['date_last_copied <' => $date]
]
]);
// To make sure the query is what we wanted
debug($query);
// To get all the results of the query
foreach ($query as $control) {
. . .
}
I'm suggesting this, rather than the raw SQL string you have above, because:
We can now leverage the ORM model of CakePHP.
We don't have to worry about SQL injection, which you're currently vulnerable to.
EDIT: OK, this is a guess at the syntax applicable for CakePHP 2.0... YMMV
$controls = $this->controls->find('all', [
'conditions' => [
'owner_id' => $ownerId,
'control_frequency_id' => $frequencyId,
'OR' => [
['date_last_copied IS' => null],
['date_last_copied <' => $date]
]
]
];
Otherwise, we just use the raw query as a prepared statement:
$result = $this->getDataSource()->fetchAll("
SELECT *
FROM controls
WHERE owner_id = ?
AND control_frequency_id = ?
AND (
date_last_copied IS NULL
OR date_last_copied < ?
)",
[$ownerId, $frequencyId, $date]
);
Not sure about your whole logic but your final query statement should be something like:
SELECT * FROM `controls` WHERE (`owner_id` = <some owner_id>)
AND (`control_frequency_id` = <some id value>)
AND (`date_last_copied` = '' OR
`date_last_copied` IS NULL OR
`date_last_copied` < CURDATE() )
Use parentheses carefully to match your logic.
Always specify the version of cakePHP you are using for your App.
This query should work fine in CakePHP 3.0 for SQL AND and OR.
$query = ModelName>find()
->where(['colunm' => 'condition'])
->orWhere(['colunm' => 'otherCondition'])
->andWhere([
'colunm' => 'anotherContion',
'view_count >' => 10
])
->orWhere(['colunm' => 'moreConditions']);

Getting total rows using Drupal 7 Database API when using limit

I am using the Drupal 7 Database API to search my table. I am also using the paging and sorting extenders. So the problem is, how do I display the total number of records found when my query is using limit because of the pagination? Do I need to run my query that has all of the conditions TWICE? Once to get the count and another one with the limit? That seems inefficient. Here is my code for reference. I am new to the Database API so feel free to adjust my code or point me in the right direction if I'm doing something wrong. Also I'm not done with this yet and only have one condition in place, but I will end up having 3. THANKS:
function job_search() {
// Initialising output
$output = 'SOME STUFF';
// Table header
$header = array(
array('data' => 'Description'),
array('data' => 'Location', 'field' => 'job_location_display'),
array('data' => 'Specialty', 'field' => 'specialty_description'),
array('data' => 'Job Type', 'field' => 'job_board_type'),
array('data' => 'Job Number', 'field' => 'job_number'),
);
// Setting the sort conditions
if(isset($_GET['sort']) && isset($_GET['order'])) {
// Sort it Ascending or Descending?
if($_GET['sort'] == 'asc')
$sort = 'ASC';
else
$sort = 'DESC';
// Which column will be sorted
switch($_GET['order']) {
case 'Location':
$order = 'job_location_display';
break;
case 'Specialty':
$order = 'specialty_description';
break;
case 'Job Number':
$order = 'job_number';
break;
case 'Job Type':
$order = 'job_board_type';
break;
default:
$order = 'job_number';
}
}
else {
$sort = 'ASC';
$order = 'job_number';
}
// Query object
$query = db_select("jobs", "j");
// Adding fields
$query->fields('j');
if(isset($_GET['location'])) {
$query->condition('j.job_state_code', $_GET['location'], '=');
}
// Set order by
$query->orderBy($order, $sort);
// Pagination
$query = $query->extend('TableSort')->extend('PagerDefault')->limit(20);
// Executing query
$result = $query->execute();
// Looping for filling the table rows
while($data = $result->fetchObject()) {
$description = '<div class="thumbnail"><img src="/sites/all/themes/zen/vista_assets/images/job_headers/' . $data->job_image_file . '"/></div>';
$description .= '<div class="title">' . $data->job_board_subtitle . '</div>';
// Adding the rows
$rows[] = array($description, $data->job_location_display, $data->specialty_description, $data->job_board_type, $data->job_number);
}
$output .= theme('pager');
// Setting the output of the field
$output .= theme_table(
array(
'header' => $header,
'rows' => $rows,
'attributes' => array('id' => array('job-listing')),
'sticky' => true,
'caption' => '',
'colgroups' => array(),
'empty' => t("No records found.")
)
).theme('pager');
// Returning the output
return $output;
}
This ended up working:
//get total records
$num_rows = $query->countQuery()->execute()->fetchField();
// add paging and sorting
$query = $query->extend('TableSort')->extend('PagerDefault')->limit(20);
//execute again
$result = $query->execute();
According to the documentation:https://api.drupal.org/api/drupal/includes!database!database.inc/function/db_query/7
in order to get the total number of rows it is better to use db_query() function, cause it has the method rowCount() which returns total number of query rows:
<?php
// Using the same query from above...
$uid = 1;
$result = db_query('SELECT n.nid, n.title, n.created
FROM {node} n WHERE n.uid = :uid', array(':uid' => $uid));
// Fetch next row as a stdClass object.
$record = $result->fetchObject();
// Fetch next row as an associative array.
$record = $result->fetchAssoc();
// Fetch data from specific column from next row
// Defaults to first column if not specified as argument
$data = $result->fetchColumn(1); // Grabs the title from the next row
// Retrieve all records into an indexed array of stdClass objects.
$result->fetchAll();
// Retrieve all records as stdObjects into an associative array
// keyed by the field in the result specified.
// (in this example, the title of the node)
$result->fetchAllAssoc('title');
// Retrieve a 2-column result set as an associative array of field 1 => field 2.
$result->fetchAllKeyed();
// Also good to note that you can specify which two fields to use
// by specifying the column numbers for each field
$result->fetchAllKeyed(0,2); // would be nid => created
$result->fetchAllKeyed(1,0); // would be title => nid
// Retrieve a 1-column result set as one single array.
$result->fetchCol();
// Column number can be specified otherwise defaults to first column
$result->fetchCol($db_column_number);
// Count the number of rows
$result->rowCount();
?>

Codeigniter batch insert performance

Does $this->db->insert_batch(); insert with 1 table connection or does it insert each row separately incurring overhead of opening connections?
From the documentation of code igniter insert_batch do this kind of things
$data = array(
array(
'title' => 'My title' ,
'name' => 'My Name' ,
'date' => 'My date'
),
array(
'title' => 'Another title' ,
'name' => 'Another Name' ,
'date' => 'Another date'
)
);
$this->db->insert_batch('mytable', $data);
// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'), ('Another title', 'Another name', 'Another date')
So it would produce only one query with all the values, normally this way faster then doing separate inserts.
To answer your question: It uses one connection.
Actually #RageZ answer based on document is not always correct. Because it's totally based on the number of items you want to insert. When looking at codeigniter insert_batch() code, you can see that they slice batch inserts into 100 items.
// Batch this baby (Around line number 1077 in codeigniter 2.x)
for ($i = 0, $total = count($this->ar_set); $i < $total; $i = $i + 100)
{
$sql = $this->_insert_batch($this->_protect_identifiers($table, TRUE, NULL, FALSE), $this->ar_keys, array_slice($this->ar_set, $i, 100));
//echo $sql;
$this->query($sql);
}
It means that your values will be slice to 100s inserts and if you uncomment the echo $sql part you can see what it's look like when you use insert batch for 101 items. So based on your connection preferences there can be more than one connection needed for inserting in db.