I have a problem regarding merge of multiple queries.
In Yii 1.x you could merge a CDbCriteria with
$criteria->merge($otherCriteria)
How can I achieve the same nested conditions etc with queries in Yii2?
Edit:
Let's say I want separate queries to form subqueries. And after all subqueries are done I want to merge them together to the one big query.
There is no CDbCriteria concept in Yii2 anymore. Instead you can refer to the following classes:
http://www.yiiframework.com/doc-2.0/yii-db-query.html (yii\db\Query)
http://www.yiiframework.com/doc-2.0/yii-db-activequery.html (yii\db\ActiveQuery)
All you did before with CDbCriteria now you can do with above classes. So, there will be no need to merge two criteria with each other.
update
Yii2 also supports sub-queries like below (as Yii2's official guide):
$subQuery = (new Query)->select('COUNT(*)')->from('user');
$query = (new Query)->select(['id', 'count' => $subQuery])->from('post');
Which results in:
SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post`
http://www.yiiframework.com/doc-2.0/guide-db-query-builder.html#building-query
I also recently ran into this issue. Complete fusion of queries (select, join etc.) does not exist (as I understand it). But you can manually merge conditions, for example:
$query1 = \app\models\User::find()
->where(['column1' => 'value1', 'column2' => 'value2']);
$query2 = \app\models\User::find()
->where(['and', '[[column3]] = :column3', '[[column4]] = :column4'])
->addParams([
':column3' => 'value3',
':column4' => 'value4'
]);
// merge conditions
$query1->orWhere($query2->where);
$query1->addParams($query2->params);
// build SQL
echo $query1->createCommand()->rawSql;
Built SQL:
SELECT * FROM `yii2_user` WHERE
((`column1`='value1') AND (`column2`='value2')) OR
((`column3` = 'value3') AND (`column4` = 'value4'))
In addition to both the excellent answers above, should you need to merge two conditions to send on to a method which takes a 'condition' parameter in the same format as where(), then you can either build your own array:
$condition1 = ['column1' => 'value1', 'column2' => 'value2'];
$condition2 = ['column3' => 'value3', 'column4' => 'value4'];
$condition = [
'or',
$condition1,
$condition2,
];
$model->updateAll([ 'column5' => 'value5' ], $condition);
Or, if it feels more logical, you can use a temporary query object to build the conditions up and merge, etc. Then pass the generated where condition from it, e.g.:
$query1 = new \yii\db\Query;
$query1->andWhere(['column1' => 'value1', 'column2' => 'value2']);
$query2 = new \yii\db\Query;
$query2->andWhere(['column3' => 'value3', 'column4' => 'value4']);
$query = new \yii\db\Query;
$query->where($query1->where)
->orWhere($query2->where);
$model->updateAll([ 'column5' => 'value5' ], $query->where);
Obviously, this makes more sense when the queries or conditions are built up elsewhere in the code and separately passed the the place where the model method is executed.
Related
Cakephp informs me that it cannot find the table or does not recognize the alias what am i supposed to use?
Hello i am new to cake php ORM can any one tell me how to preform a left join on a subquery I'm really interested to know how to use in working the join
Here is sub query and left join so far please ignore any syntax error
$scl = TableRegistry::get('School');
$subquery = $scl->find();
$subquery->select([
'UID',
'SID',
'Total' => $subquery->func()->sum('numberOfStudents')
])->group(['UID, SID']);
$q->select([
'TeacherID',
'ClassID',
'StudentTotal' => 'sq.Total'
])->join([
'table' => $subquery,
'alias' => 'sq',
'type' => 'LEFT',
'conditions' => ['sq.UID = TeacherID', 'sq.SID = ClassID']
]);
here is the error:
[PDOException] SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sq.UID' in 'on clause'
The problem is that ORM based queries automatically alias the selected fields, so you don't get
SELECT UID, SID ...
but
SELECT UID as School__UID, SID as School__SID
hence referring to sq.UID will fail, as no such field name was selected.
To avoid this problem you can either use an alias that matches the original field name:
->select([
'UID' => 'UID',
'SID' => 'SID',
// ...
])
use the lower level database query that doesn't automatically create aliases:
$subquery = $scl
->getConnection() // connection() in older CakePHP versions
->newQuery()
->from($scl->getTable()); // table() in older CakePHP versions
or refer to the aliased fields in the main query:
'conditions' => [
'sq.' . $scl->getAlias() . '__UID = TeacherID', // alias() in older CakePHP versions
'sq.' . $scl->getAlias() . '__SID = ClassID',
]
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();
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']);
I am trying to do a check against 3 table that I join together. I do not want to use the real table name hard coded as my project is highly under develop and table prefix may be changed. What is the best way in Yii2 to select from 3 table where I have where statement on the joined table?
I can get what I want from the code below. But as I said, I do not want to use the table alias hard coded. Any idea how to fix this or suggestion of other ideas would be very appreciated.
$userId = Yii::$app->user->id;
$result = \app\models\UserPermission::find()->joinWith([
'permission',
'permission.service'
])->where([
'prefix_user_permission.user_id' => $userId,
'prefix_permission.flag' => Permission::LOGIN,
'prefix_service.login_available' => Service::LOGIN_AVAIABLE,
])->all();
I would like to end up with this query:
SELECT *
FROM `prefix_user_permission` `up`
INNER JOIN `prefix_permission` `p` ON `up`.`permission_id` = `p`.`id`
INNER JOIN `prefix_service` `s` ON `p`.`service_id` = `s`.`id`
WHERE (`up`.`user_id`=43)
AND (`p`.`flag`='LOGIN')
AND (`s`.`login_available`=1);
The table prefix can be configured using the 'tablePrefix' param along with the main db config as follows:
'components' => [
'db' => [
//other db config params
'tablePrefix' => 'pre_'
]
This prefix can be used as follows:
There's a special variant on this syntax specific to tablenames: {{%Y}} automatically appends the application's table prefix to the provided value, if a table prefix has been set:
$sql = "SELECT COUNT([[$column]]) FROM {{%table}}";
$rowCount = $connection->createCommand($sql)->queryScalar();
Or if you are using active record for models then you can also use the tableName() function to replace the hard-coded table names.
$conditions = Array
(
[table] => products_pages
[alias] => ProductsPage
[type] => inner
[foreignKey] =>
[conditions] => Array
(
[0] => ProductsPage.product_id = Product.id
)
)
I'm trying to set up NOT EXISTS conditions, like the following SQL statement:
SELECT * FROM products_pages,products
WHERE NOT EXISTS (SELECT id
from products_pages
where products_pages.product_id = products.id)
So basically select any product that doesn't exist in the products_pages table.
What is the proper way to format that SQL statement for CakePHP and replace it here:
[conditions] => Array
(
[0] => (What's the proper way to insert above SQL here?
)
Would really appreciate your help guys, I've been trying to figure this out for about 5 hours with no luck. Thanks!
You can always use query if you don't find the way to do it with CakePHP:
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#model-query
In this case security wouldn't be compromised as you are not using any input.
Anyway, something simple would be just to do it in more than one step:
//selecting the products in the productcs_pages table
$productsWithPages = /* query to get them*/
//getting an array of IDs
$productsWidthPagesIds = Hash::extract($productsWithPages, '{n}.Product.id');
//doing the NOT IN to select products without pages
$productsWithoutPages= $this->Product->find('all',
array('conditions' =>
array( 'NOT' => array('Product.id' => $productsWidthPagesIds )
)
);