"andFilterWhere" work proper with "joinWith()" but not with "with()" in yii2 - yii

I am working in yii2.
There are employee and company table employee contains company_id.
I have a filter search running properly If I use joinWith()
$query = Employee::find();
$query->joinWith(['company']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false,
'sort' => false,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
//and below is the filterwhere
$query->andFilterWhere(['like', 'company.name', $this->company_id]);
But issue came when I make a query using with()
$query = Employee::find()->with(['company']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => false,
'sort' => false,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
//when query contain with() then this filter is not working.
$query->andFilterWhere(['like', 'company.name', $this->company_id]);
This gives error when I use with()
Database Exception – yii\db\Exception
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'company.name' in 'where clause'
The SQL being executed was: SELECT COUNT(*) FROM `employee` WHERE `company`.`name` LIKE '%1%'
Here is the relation in employee with company:
public function getCompany(){
return $this->hasOne(Company::className(), ['id'=> 'company_id']);
}
Can anyone help me or guide me how could I filter data properly using with() in a query?
Thanks.

You can't swap joinWith() and with() methods when you need to filter by the column from the related table. That's because these methods does completely different things.
Methods like joinWith() and join() actually modifies the query to add the "JOIN" part to the SQL query. The with in joinWith allows you to specify the joined table by the relation definition in the model. The eager loading in joinWith is only side effect and you can even turn that off by passing false as second parameter.
When you do:
Employee::find()->joinWith(['company'])->all();
The query that is run looks like:
SELECT * FROM employee LEFT JOIN company ON (...)
On the other side the method with() doesn't modify the query itself. It only forces the eager loading of related models. In reality the second query is used for preloading the related records.
When you do:
Employee::find()->with(['company'])->all();
It actually runs queries like these:
SELECT * FROM employee;
SELECT * FROM company WHERE id IN (...company ids selected in first query...);
So when you try to do:
$query = Employee::find()
->with(['company'])
->andFilterWhere(['like', 'company.name', $this->company_id])
->all();
The generated query is
SELECT * FROM employee WHERE company.name LIKE ...

Related

Add where clause to calculated field in cakephp 3 query

I have a query in which I want the name of a company and its employee quantity. The thing is I want to filter this result by some conditions (like employee_number > 50 etc.). My problem is that, when building the query, I don't know how to filter this result, as the condition is set over a calculated field, so when applying the condition it gives me the below
Error: `SQLSTATE[42S22]: Column not found: 1054 Unknown column 'employee_number' in 'where clause'`.
I have been trying different things, but this is what I currently have:
$query = $this->Companies->find('all')->where($conditions)->contain(['Users']);
$query
->select(['Users.name',
'Company.modified',
'employee_number' => $query->func()->count('DISTINCT(Employees.id)')])
->where('employee_number >' => 50 )
->leftJoinWith('Employees', function (\Cake\ORM\Query $query) {
return $query->where(['deleted' => 0]);
})
->group(['Employees.company_id', 'Company.id']);
First things first, you cannot refer to an aggregate in the WHERE clause, as grouping happens afterwards, hence the error, the field employee_number doesn't exist when the WHERE conditions are being applied, you have to leverage the HAVING clause instead.
Depending on the DBMS that you are using you can reference the column from the select list, MySQL for example allows that:
$query
->select([
'Users.name',
'Company.modified',
'employee_number' => $query->func()->count('DISTINCT Employees.id')
])
->leftJoinWith('Employees', function (\Cake\ORM\Query $query) {
return $query->where(['deleted' => 0]);
})
->group(['Employees.company_id', 'Company.id'])
->having(['employee_number >' => 50]);
while Postgres for example doesn't, and requires you to repeat the aggregation inside of the HAVING clause:
->having(function (
\Cake\Database\Expression\QueryExpression $exp,
\Cake\ORM\Query $query
) {
return $exp->gt($query->func()->count('DISTINCT Employees.id'), 50);
});
ps. using DISTINCT should only be necessary when you have for example multiple joins that would result in duplicate joined rows.
See also
Cookbook > Database Access & ORM > Query Builder > Aggregates - Group and Having

Yii2: how to use subquery with ActiveRecord?

I want to create a find function to use then in a GridView widget My I don't know how to use a subquery.
This is the simple version of the PostgreSQL query that I have:
SELECT color
FROM cars
LEFT JOIN (
SELECT name
FROM companies
)
Here is what I a trying and doesn't work:
$query = Cars::find()
->select([
'color' => '
SELECT name // Problem here.
FROM companies // Problem here.
',
]);
Your expected query is not what you are trying to do. You can add a custom subquery as
$query = Cars::find()
->select([
'(SELECT name FROM companies) AS xxx'
]);
But if you want to add a field from joined table to the grid, then you should have a relation defined and then you can easily add such a column to your grid. See https://www.yiiframework.com/doc/guide/2.0/en/db-active-record#relational-data
You can use instance of ActiveQuery in leftJoin() method like this:
$subQuery = Companies::find()
->select('name', 'id'); //I've added ID to show how to write on condition
$query = Cars::find()
->select('color')
->leftJoin(
['alias' => $subQuery],
'alias.id = cars.company_id'
);
See documentation for more details about leftJoin() and join() methods.
$query = Cars::find()
->select(['cars.color','companies.name'])
->leftJoin('companies', 'cars.company_id = companies.id');

CakePHP - add custom SQL to a Query object

I'm using CakePHP 3.5.13 to build an application which has 4 separate databases.
The main database (Datasource default in config/app.php) for the application has been baked. It was a legacy database, and the naming conventions are not written according to the way CakePHP specifies. Nonetheless it works, after going through the Models and editing things.
In a controller I have the following:
$substances = TableRegistry::get('Substances');
$query = $substances->find()->limit(250)->offset(0);
$query->select(['id', 'app_id', 'name']);
$query->contain([
'Cas' => [
'sort' => ['Cas.id' => 'ASC']
]
]);
$query->contain([
'Ecs' => [
'sort' => ['Ecs.id' => 'ASC']
]
]);
If I var_dump($query) I get an SQL string as follows:
SELECT Substances.id AS `Substances__id`,
Substances.app_id AS `Substances__app_id`,
Substances.name AS `Substances__name`
FROM substances Substances LIMIT 250 OFFSET 0
I need to modify this so the query contains an INNER JOIN to a table which is stored in one of the other databases (Datasource sdb5_tmpdata in config/app.php). The SQL I need is as follows:
SELECT Substances.id AS `Substances__id`,
Substances.app_id AS `Substances__app_id`,
Substances.name AS `Substances__name`,
Substances.date AS `Substances__date`
FROM substances Substances
INNER JOIN `sdb5_tmpdata`.`searching_1745` AS tf
ON tf.id = Substances.id LIMIT 250 OFFSET 0;
The difference between the above query and original is the following SQL:
INNER JOIN `sdb5_tmpdata`.`searching_1745` AS tf
ON tf.id = Substances.id
I don't have a corresponding Model for the table 'searching_1745' because these tables are dynamically created (and later dropped) on a database which holds "temporary" user data.
Is it possible to modify the query object, $query, such that I can introduce the custom SQL that does the inner join?
I have tried $query = $query->newExpr()->add('INNER JOIN 'sdb5_tmpdata'.'searching_1745' AS tf ON tf.id = Substances.id'); but it doesn't work.
the query builder let you build the query as you like it. It does not matter if you don't have a Table Object for that table
$query->join([
'tf ' => [
'table' => 'sdb5_tmpdata.searching_1745',
'type' => 'INNER',
'conditions' => 'tf.id = Substances.id',
]);
see here
I don't know if you can modify the query object, but tou can always write your own custom queries:
use Cake\Datasource\ConnectionManager;
$conn = ConnectionManager::get('default');
$query = $conn->query('query goes here');
for more info, read: https://book.cakephp.org/3.0/en/orm/database-basics.html#executing-queries

Why does a UNION ALL query treat the outer ORDER column as unknown?

I'm using unionAll() and return the data perfectly, but I need ordernate the data and always return error because the column not exists.
$events = $this->Events
->find('available')
->where([
'Events.group_of_event_id IS NULL'
])
->select('Events.id')
->select('Events.name')
->select('Events.slug')
->select('Events.date_event_start')
->select([
'is_group' => 0
]);
$groups = $this->GroupOfEvents
->find('available')
->select('GroupOfEvents.id')
->select('GroupOfEvents.name')
->select('GroupOfEvents.slug')
->select('GroupOfEvents.date_event_start')
->select([
'is_group' => 1
]);
$limit = 10;
$page = 1;
if($this->request->query('limit'))
$limit = $this->request->query('limit');
if($this->request->query('page'))
$page = $this->request->query('page');
$offset = ($page - 1) * $limit;
$connection = ConnectionManager::get('default');
$union = $events->unionAll($groups)->epilog(
$connection
->newQuery()
->order(['date_event_start' => 'ASC'])
->limit($limit)
->offset($offset)
);
Return this error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'date_event_start' in 'order clause'
As the error states, there is no date_event_start column.
Unlinke normale SQL queries, where a non-table prefixed column would fall back to referring to one of the involved tables, similar doesn't happen for union results, with union results you have to explicity refer to the columns as they have been selected.
So you have to make sure that either the columns are selected without a table prefix, or to select and use proper aliases in the ORDER clause. In order to avoid ambiguity, I'd strongly suggest going for the latter, something like
->select(['date_event_start_alias' => 'Events.date_event_start'])
// ...
->select(['date_event_start_alias' => 'GroupOfEvents.date_event_start'])
// ...
->order(['date_event_start_alias' => 'ASC'])
It should be noted that at least with MySQL and Postgres (I'm not sure about other DBMS like SQLite or SQL Server), you actually have to set the alias only for the first SELECT. Setting it for all selects won't do any harm, so I'm including it in the example, but it's not actually necessary.
See also
Cookbook > Database Access & ORM > Query Builder > Selecting Data

Multiple joins using active record with YII framework

im getting stuck with active record for yii.
im using models for each table in my db.
the sql i try to get is querying and joining from 3 tables.
what i want to accomplish is the equivalent of the sql command :
SELECT location_code as stopkey, bay_no bay_no, description stop_name, route_area_code route_area_code, latitude latitude, longitude, build_code build_code, message_time message_time, ip_address ip_address, route.route_code route, make make, last_impact last_impact, impact_count impact_count, last_bootup last_bootup, bootup_count bootup_count, last_active_hour last_active_hour, last_active_day last_active_day, operator.operator_code operator_code, routes routes, bearing
FROM snapshot_stop_status route
JOIN route_visibility ON route.route_id = route_visibility.route_id
JOIN operator ON operator.operator_id = route_visibility.operator_id
WHERE usernm = 'me'
ORDER BY location_code
here is what i tried so far :
public function relations() {
return array(
'RoutesVisibility' =>array(self::MANY_MANY,'route_visibility','route_id'),
);
}
How can i accomplish this using relations function in the models ?
any kind of help would be appreciated
route_visibility is the joining table that tie operator to snapshot_stop_status. So in yii you can specify the table doing the relations:
'operators' =>array(self::MANY_MANY,'Operator','route_visibility(route_id,operator_id )'),
And in Operator:
'routes' =>array(self::MANY_MANY,'Route','route_visibility(operator_id, route_id)'),
Then you can request with something like:
$criteria = new CDbCriteria;
$criteria->with = array('operators' => array('condition' => 'usernm = "me"'));
$models => Route:model()->findAll($criteria);