$data findAll query in CakePHP checking values from second table - php

I have been stumped by CakePHP in how to query the DB in CakePHP and return things to $data only when the $data query table [id] has a matching [sub_id] in a second table
a standard query:
$data = $this->Table1->findAll(array("Table1.deleted" => "0"), null, "Table1.id DESC", 25, null, 1);
But I want to only have values put into $data when the $data['Table1']['id'] is found in ['table2']['sub_id']
Thanks!

If you have your relationships setup properly it should do this automatically. Can you paste your Model relationship setup for Table1 and Table2?

Supernovah -
Please clarify one thing for me: you write that you want to only have values put into $data when table1.id is found in table2.sub_id. Do you mean that table2.sub_id is a foreign key, linking to table1?
I think Beau is right -- if you have the models correctly linked, using a HABTM or belongsTo, etc., variable, the findAll should automatically pull the associated records from table2.
The final caveat is that the model associations are affected by the value of Model->recursive. If you have changed the value of the recursive property in your code, it would alter how deep the model relations are allowed to go on a given query.
HTH!

In the model, in the relation array add:
$hasMany = array(
.....
'required' => true
....
);
This should make it do an inner join in sql rather than a left join. Hope this helps.

Related

Id Column Overwritten by Join

Using Laravel 4.2 & MySQL.
I have an applications table with an id and a fit_score_id column, and a fit_scores table with an id column. It's a basic "belongs to" relationship.
The following code:
$query = Application::join('fit_scores', 'applications.fit_score_id', '=', 'fit_scores.id');
$collection = $query->get();
...produces a collection of Application models with the id property set to the value of the fit_score_id. What am I doing to cause this?
I should note that it is necessary to do this join rather than simply using eloquent relations, because I'm going to want to order the results by a column on the fit_scores table. I don't believe this is possible using Eloquent without an explicit join.
The best way to solve this is by chaining the join method to a select method as following:
Application::select('*', \DB::raw("applications.id as appid"))
->join('fit_scores', 'applications.fit_score_id', '=', 'fit_scores.id')
->get();
Explained: The solution simply suggest that instead of thinking to prevent the behavior of overwriting the first id with the joined id, we can hook into the primary selection query (before joining) and change the label of the id column into something else (in this case 'appid'). By doing so, we end up with both the id of the parent table being labeled 'appid' and the id of the joined table being labeled 'id' again while they lives together on the final result.
I was able to find a possible solution using this answer:
Laravel 4 - JOIN - Same column name
Basically, since Laravel does not automatically prefix column names with table_name. for joined tables, we need to manually work around it by aliasing any conflicting column names in joins. Adding this select statement to my query did it:
->select(DB::raw("applications.*, fit_scores.*, applications.id as id"))
It depends on what you need but probably you can achieve it using eager loading. In case you need to mix joins and eager loading check this out. http://www.jmilan.net/posts/eager-loading-joins-in-laravel

Why is Yii2's ActiveRecord using lots of single SELECTs instead of JOINs?

I'm using Yii2's ActiveRecord implementation in (hopefully) exactly the way it should be used, according to the docs.
Problem
In a quite simple setup with simple relations betweens the tables, fetching 10 results is fast, 100 is slow. 1000 is impossible. The database is extremely small and indexed perfectly. The problem is definitly Yii2's way to request data, not the db itself.
I'm using a standard ActiveDataProvider like:
$provider = new ActiveDataProvider([
'query' => Post::find(),
'pagination' => false // to get all records
]);
What I suspect
Debugging with the Yii2 toolbar showed thousands of single SELECTs for a simple request that should just get 50 rows from table A with some simple "JOINs" to table B to table C. In plain SQL everybody would solve this with one SQL statement and two joins. Yii2 however fires a SELECT for every relation in every row (which makes sense to keep the ORM clean). Resulting in (more or less) 1 * 50 * 30 = 1500 queries for just getting two relations of each row.
Question
Why is Yii2 using so many single SELECTs, or is this a mistake on my side ?
Addionally, does anybody know how to "fix" this ?
As this is a very important issue for me I'll provide 500 bounty on May 14th.
By default, Yii2 uses lazy loading for better performance. The effect of this is that any relation is only fetched when you access it, hence the thousands of sql queries. You need to use eager loading. You can do this with \yii\db\ActiveQuery::with() which:
Specifies the relations with which this query should be performed
Say your relation is comments, the solution is as follows:
'query' => Post::find()->with('comments'),
From the guide for Relations, with will perform an extra query to get the relations i.e:
SELECT * FROM `post`;
SELECT * FROM `comment` WHERE `postid` IN (....);
To use proper joining, use joinWith with the eagerLoading parameter set to true instead:
This method allows you to reuse existing relation definitions to perform JOIN queries. Based on the definition of the specified relation(s), the method will append one or multiple JOIN statements to the current query.
So
'query' => Post::find()->joinWith('comments', true);
will result in the following queries:
SELECT `post`.* FROM `post` LEFT JOIN `comment` comments ON post.`id` = comments.`post_id`;
SELECT * FROM `comment` WHERE `postid` IN (....);
From #laslov's comment and https://github.com/yiisoft/yii2/issues/2379
it's important to realise that using joinWith() will not use the JOIN query to eagerly load the related data. For various reasons, even with the JOIN, the WHERE postid IN (...) query will still be executed to handle the eager loading. Thus, you should only use joinWith() when you specifically need a JOIN, e.g. to filter or order on one of the related table's columns
TLDR:
joinWith = with plus an actual JOIN (and therefore the ability to filter/order/group etc by one of the related columns)
In order to use relational AR, it is recommended that primary-foreign key constraints are declared for tables that need to be joined. The constraints will help to keep the consistency and integrity of the relational data.
Support for foreign key constraints varies in different DBMS. SQLite 3.6.19 or prior does not support foreign key constraints, but you can still declare the constraints when creating tables. MySQL’s MyISAM engine does not support foreign keys at all.
In AR, there are four types of relationships:
BELONGS_TO: if the relationship between table A and B is one-to-many, then B belongs to A (e.g. Post belongs to User);
HAS_MANY: if the relationship between table A and B is one-to-many, then A has many B (e.g. User has many Post);
HAS_ONE: this is special case of HAS_MANY where A has at most one B (e.g. User has at most one Profile);
MANY_MANY: this corresponds to the many-to-many relationship in database. An associative table is needed to break a many-to-many relationship into one-to-many relationships, as most DBMS do not support many-to-many relationship directly. In our example database schema, the tbl_post_category serves for this purpose. In AR terminology, we can explain MANY_MANY as the combination of BELONGS_TO and HAS_MANY. For example, Post belongs to many Category and Category has many Post.
The following code shows how we declare the relationships for the User and Post classes.
class Post extends CActiveRecord
{
......
public function relations()
{
return array(
'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
'categories'=>array(self::MANY_MANY, 'Category',
'tbl_post_category(post_id, category_id)'),
);
}
}
class User extends CActiveRecord
{
......
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
);
}
}
The query result will be saved to the property as instance(s) of the related AR class. This is known as the lazy loading approach, i.e., the relational query is performed only when the related objects are initially accessed. The example below shows how to use this approach:
// retrieve the post whose ID is 10
$post=Post::model()->findByPk(10);
// retrieve the post's author: a relational query will be performed here
$author=$post->author;
You are somehow doing it the wrong please go through from the documentaion here http://www.yiiframework.com/doc/guide/1.1/en/database.arr

Laravel Left join returning empty result when record not available in right table

I'm having trouble with Laravel syntax. I have a leftjoin as such:
$query=DB::table('jobs');
$query->leftjoin( 'attribute_int_data', 'attribute_int_data.job_id', '=', 'jobs.job_id' );
The problem is that where there is no record in the attribute_int_data table, I do not get the job_id from the jobs table.
I understand this may be because I have two column names that are the same, and I need to apply an alias to one of the column names to get the job_id.
Can anyone help with the syntax please?
Cheers.
Found the solution, hope it's of use to someone.
I added an array to the get, where I renamed the conflicting job_id column name.
$get_array = array(
'jobs.job_id as real_id',
...other values...
)
Then, to implement this, when I get the query:
$query->get($get_array);
And i get the job_id even when there are no matching records in the right table.

Yii - Query Manipulation for Custom CGridView with Advanced Search

So, I've extended CGridView to include an Advanced Search feature tailored to the needs of my organization.
Filter - lets you show/hide columns in the table, and you can also reorder columns by dragging the little drag icon to the left of each item.
Sort - Allows for the selection of multiple columns, specify Ascending or Descending.
Search - Select your column and insert search parameters. Operators tailored to data type of selected column.
Version 1 works, albeit slowly. Basically, I had my hands in the inner workings of CGridView, where I snatch the results from the DataProvider and do the searching and sorting in PHP before rendering the table contents.
Now writing Version 2, where I aim to focus on clever CDbCriteria creation, allowing MySQL to do the heavy lifting so it will run quicker. The implementation is trivial when dealing with a single database table. The difficulty arises when I'm dealing with 2 or more tables... For example, if the user intends to search on a field that is a STAT relation, I need that relation to be present in my query so that I may include comparisons.
Here's the question. How do I assure that Yii includes all with relations in my query so that I include comparisons? I've included all my relations with my criteria in the model's search function and I've tried CDbCriteria's together set to true ...
public function search() {
$criteria=new CDbCriteria;
$criteria->compare('id', $this->id);
$criteria->compare( ...
...
$criteria->with = array('relation0','relation1','relation3');
$criteria->together = true;
return new CActiveDataProvider(
get_class($this), array(
'criteria'=>$criteria,
'pagination' => array('pageSize' => 50)
));}
Then I'll snatch the criteria from the DataProvider and add a few conditions, for example, looking for dates > 1234567890. But I still get errors like this...
CDbCommand failed to execute the SQL statement:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't.relation3' in 'where clause'.
The SQL statement executed was:
SELECT COUNT(DISTINCT `t`.`id`) FROM `table` `t`
LEFT OUTER JOIN `relation_table` `relation0` ON (`t`.`id`=`relation0`.`id`)
LEFT OUTER JOIN `relation_table` `relation1` ON (`t`.`id`=`relation1`.`id`)
WHERE (`t`.`relation3` > 1234567890)
Where relation0 and relation1 are BELONGS_TO relations, but any STAT relations, here depicted as relation3, are missing. Furthermore, why is the query a SELECT COUNT(DISTINCT 't'.'id') ?
Edit #DCoder Here's the specific relation I'm working with now. The main table is Call, which has a HAS_MANY relation to CallSegments, which keeps the times. So the startTime of the Call is the minimum start_time of all the related CallSegments. And startTime is the hypothetical relation3 in my anonymized query error.
'startTime' => array(self::STAT, 'CallSegments', 'call_id',
'select' => 'min(`start_time`)'),
Edit Other people have sent me to CDbCriteria's together property, but as you can see above, I am currently trying that to no avail.
Edit Looks like the issue has may have been reported: Yii and github tickets.
It is not a good idea to snatch the sql from a criteria and use it by yourself.
If you are using the "with" property then you could easily use comparisons like:
$criteria->compare("`relation1`.`id`", $yourVarHere);
Also Yii doesn't behave well with grouping.
My approach with STAT relations is using an subquery in the selects of Yii, followed by having:
$criteria->select = array("`t`.*", "(SELECT COUNT(*) FROM `relation3` WHERE `id` = `t`.id_relation3) AS `rel3`");
$criteria->having = "`rel3` > " . $yourValue;
The above method creates a bug in the gridview pagination because the count is done on a different query. A workaround will be to drop the "with" property and write the joins by yourself in the "join" property like:
$criteria->join = "LEFT OUTER JOIN `relation_table` `relation0` ON (`t`.`id`=`relation0`.`id`)
LEFT OUTER JOIN `relation_table` `relation1` ON (`t`.`id`=`relation1`.`id`)
LEFT OUTER JOIN `relation_table` `relation3` ON (`t`.`id`=`relation3`.`id`)";
If the bug is a little difficult to get working could you use the stat relation as a simple HAS_ONE with :
'select'=>'count(relation3.id)',
'joinType'=>'left join',
'group'=>'relation3.id',
'on'=>'t.id = relation3.id',
'together'=>true
to get the count value out along side everything else?
Not sure how well this would work for your case but it's been helpful for me from time to time.

Automatically joining tables without breaking default behaviour in Zend Framework

The situation is as follows: I've got 2 models: 'Action' and 'User'. These models refer to the tables 'actions' and 'users', respectively.
My action table contains a column user_id. At this moment, I need an overview of all actions, and the users to which they are assigned to. When i use $action->fetchAll(), I only have the user ID, so I want to be able to join the data from the user model, preferably without making a call to findDependentRowset().
I thought about creating custom fetchAll(), fetchRow() and find() methods in my model, but this would break default behaviour.
What is the best way to solve this issue? Any help would be greatly appreciated.
I designed and implemented the table-relationships feature in Zend Framework.
My first comment is that you wouldn't use findDependentRowset() anyway -- you'd use findParentRow() if the Action has a foreign key reference to User.
$actionTable = new Action();
$actionRowset = $actionTable->fetchAll();
foreach ($actionRowset as $actionRow) {
$userRow = $actionRow->findParentRow('User');
}
Edit: In the loop, you now have an $actionRow and a $userRow object. You can write changes back to the database through either object by changing object fields and calling save() on the object.
You can also use the Zend_Db_Table_Select class (which was implemented after I left the project) to retrieve a Rowset based on a join between Action and User.
$actionTable = new Action();
$actionQuery = $actionTable->select()
->setIntegrityCheck(false) // allows joins
->from($actionTable)
->join('user', 'user.id = action.user_id');
$joinedRowset = $actionTable->fetchAll($actionQuery);
foreach ($joinedRowset as $joinedRow) {
print_r($joinedRow->toArray());
}
Note that such a Rowset based on a join query is read-only. You cannot set field values in the Row objects and call save() to post changes back to the database.
Edit: There is no way to make an arbitrary joined result set writable. Consider a simple example based on the joined result set above:
action_id action_type user_id user_name
1 Buy 1 Bill
2 Sell 1 Bill
3 Buy 2 Aron
4 Sell 2 Aron
Next for the row with action_id=1, I change one of the fields that came from the User object:
$joinedRow->user_name = 'William';
$joinedRow->save();
Questions: when I view the next row with action_id=2, should I see 'Bill' or 'William'? If 'William', does this mean that saving row 1 has to automatically update 'Bill' to 'William' in all other rows in this result set? Or does it mean that save() automatically re-runs the SQL query to get a refreshed result set from the database? What if the query is time-consuming?
Also consider the object-oriented design. Each Row is a separate object. Is it appropriate that calling save() on one object has the side effect of changing values in a separate object (even if they are part of the same collection of objects)? That seems like a form of Content Coupling to me.
The example above is a relatively simple query, but much more complex queries are also permitted. Zend_Db cannot analyze queries with the intention to tell writable results from read-only results. That's also why MySQL views are not updateable.
You could always make a view in your database that does the join for you.
CREATE OR REPLACE VIEW VwAction AS
SELECT [columns]
FROM action
LEFT JOIN user
ON user.id = action.user_id
Then just use
$vwAction->fetchAll();
Just remember that views in MySQL are read-only (assuming this is MySQL)
isn't creating a view sql table a good solution to make joint ?
and after a simple table class to access it
I would think it's better if your logic is in sql than in php

Categories