I have a few tables that are joined through a distant relationship - for example:
A.id = B.a_id, B.id = C.b_id, C.id = D.c_id
And given A.id, I want to delete all the rows in D that are associated with A.id.
Since Model::deleteAll() does not accept any joins, only conditions, how do I go about it?
All the models (A, B, C, D) already have belongTo relationships defined.
My last resort would be raw SQL, but I would like to know if there's a way in CakePHP to do it.
I could not find similar questions as they all were about deleting ALL the associated data, rather than just one table's data via an associated key.
Use Containable behavior to find D records
public function deleteD($idA){
$this->ModelA->Behaviors->load('Containable');
$options = array(
'contain' => array(
'ModelB' => array(
'ModelC' = array(
'ModelD'
)
)
),
'conditions' => array('ModelA' => $idA)
);
$findDIds = $this->ModelA->find('all',$options);
debug($findDIds); // find right path to ModelD
$ids = Hash::extract($findDIds,'{n}.ModelD.id');
$this->loadModel('ModelD');
foreach($ids as $id){
$this->ModelD->delete($id);
}
}
Note, I not tested this function.
Related
I am selecting 3 entities via regular LEFT JOIN in DQL. They are related via join tables which also have entities defined for them as well as annotated relationships.
The query executes without issue but my results are returned as a flat array. I would expect an array with the three entities as array elements for each index:
SELECT e1, e2, e3 FROM AppBundle:EntityOne
JOIN AppBundle:JoinEntityOneWithTwo e1_2 WITH e1_2.entity1 = e1.id
JOIN AppBundle:EntityTwo e2 WITH e1_2.entity2 = e2.id
JOIN AppBundle:JoinEntityOneWithThree e1_3 WITH e1_3.entity1 = e1.id
JOIN AppBundle:EntityThree e3 WITH e3.id = e1_3.entity3
WHERE e1.some_field IN ('some','values','in','e1');
When I call getResult() on the query, either hydrating as an object or an array, I get a flat results set:
array(
/AppBundle/Entity/EntityOne ( ... ),
/AppBundle/Entity/EntityTwo ( ... ),
/AppBundle/Entity/EntityThree ( ... ),
/AppBundle/Entity/EntityOne ( ... ),
/AppBundle/Entity/EntityTwo ( ... ),
/AppBundle/Entity/EntityThree ( ... ),
/AppBundle/Entity/EntityTwo ( ... ),
/AppBundle/Entity/EntityThree ( ... ),
/AppBundle/Entity/EntityOne ( ... ),
)
I would expect, or like to have a multi dimensional array:
Array(
[0] => Array(
[0] /AppBundle/Entity/EntityOne,
[1] /AppBundle/Entity/EntityTwo,
[2] /AppBundle/Entity/EntityThree
),
[1] => . . .
)
The results are not related by row. Nor are they in any predictable order that I can group them by with array_chunk()
The generated sql runs fine. The DQL returns accurate results -- they're just not formulated in a way that I would expect. I am following Doctrines (elusive) documentation on eager loading joins:
http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html
This question is very similar to
Doctrine DQL returns multiple types of entities
But my select order is different as my join tables are many-to-many.
Much thanks!
A variant of this question was asked:
Getting Doctrine DQL results the SQL way
I'm pretty surprised that doctrine does not support eager loading via join table.
Call getScalarResult() instead of getResult() on your query and you will get all fields of all joined tables merged into one array per record:
# Replace this call ...
$query->getQuery()->getResult();
# ... by that call ...
$query->getQuery()->getScalarResult();
Here is the best that I could come up with:
//in my entity repository:
public function getFoo($hydrate = true) {
$sql = "SELECT e1, e2, e3 FROM entity_one
JOIN entity_one_entity_two e1_2 ON e1_2.entity_one_id = e1.id
JOIN entity_two e2 ON e1_2.entity_two_id = e2.id
JOIN entity_one_entity_three e1_3 ON e1_3.entity_one_id = e1.id
JOIN entity_three e3 ON e3.id = e1_3.entity_three.id
WHERE e1.some_field IN ('some','values','in','e1')";
$stmt = $con->prepare($sql);
$stmt->execute();
return ($hydrate)
? $this->hydrateResponses($stmt->fetchAll(PDO::FETCH_ASSOC))
: $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function hyrdateResponses($responses) {
//call find by ids on repositories from array.
}
This works great because you're performing 3XN the number of queries when you're only trying to get results from one!
For example I have 3 tables:
songs(id, song_name)
song_category(id, song_id, category_id)
categories(id, name)
I want to get songs which have categories with id higher than 5. I want to do it using ORM, not with simple SQL query. Is it possible to do it with one query like this:
$songs = ORM::factory("songs")->where("category.id > 5")
No, you cannot do this with a single Kohana ORM call.
The best way I have found to do it is something like this, which makes a modification to the SQL query that the ORM will generate:
// Get the basic "song" model
$songs = ORM::factory("songs");
// Get the information about how it is connected to
// the "category" model using the `through` model
$song_relations = $results->has_many();
$category_relation = $song_relations['categories'];
$through = $category_relation['through'];
// Join on `through` model's target foreign key (far_key) and `target` model's primary key
$join_col1 = $through.'.'.$category_relation['foreign_key'];
$join_col2 = $songs->object_name().'.'.$songs->primary_key();
$songs->join($through)->on($join_col1, '=', $join_col2);
// Now, filter on the
$songs->where($through.'.'.$category_relation['far_key'], '>', 5);
$arr = $results->find_all()->as_array();
You could save some code by hardcoding the values in the join method call, but this way leverages the ORM relation definitions that you already have.
This assumes that your Song model has the following code in it:
protected $_has_many = [
'categories' => [
'model' => 'category',
'through' => 'song_category',
'foreign_key' => 'song_id',
'far_key' => 'category_id',
]
];
So I have this query that I am trying to convert to cake ORM and I do not know how to go about it.
I have a user table and a word table. Users have many words (thats the relationship). I want to write a query that will give me the users that have added the most words in the system. This is the current query I wrote but I am having trouble converting it to cakephp ORM syntax, any ideas?
SELECT
users.username,
COUNT(word) AS n
FROM
users AS users
INNER JOIN words AS words
ON users.userid=words.userid
GROUP BY
users.username
ORDER BY
n DESC
LIMIT 3
There are many ways to write the query:- In controller you can write as -
$options = array(
'fields' => array(
'User.name',
'COUNT(Word.id) as word_count',
),
'group' => 'Word.user_id',
'order' => 'word_count DESC',
);
$users = $this->User->Word->find('first', $options);
debug($users);
In User model you have to write:
public $hasMany = 'Word';
In Word model you have to write:
public $belongsTo = 'User';
I have two models, Plant and Emp, that have a Has And Belongs To Many relationship. I've configured them to be associated and the query to get the data for each is correct, but the problem is Plant and Emp are on different databases. Emp is on Database 1, Plant is on Database 2. Because of this they don't query the join table properly; the join table is only on Database 1.
When the Plant model tries to access the join table it's querying Database 2, which does not have this data.
This is the association Emp has for Plant.
var $hasAndBelongsToMany = array(
'Plant' =>
array(
'className' => 'Plant',
'joinTable' => 'emp_plant',
'foreignKey' => 'employee_id',
'associationForeignKey' => 'LocationID',
'unique' => true,
'conditions' => '',
)
);
Update:I tried to set a "finderQuery" attribute to let me query the join table, but I don't know how to give a raw SQL query like that and allow it to dynamically use the id for the instance of the Model instead of a predefined value.
I can set something like
SELECT * FROM [Plant] AS [Plant] JOIN [DB].[DBO].[empplant] AS
[EmpPlant] ON ([EmpPlant].[employee_id] = **4**
AND [EmpPlant].[ID] = [Plant].[LocationID])
Which will give me the correct data for one employee, but I don't know how to make this finderQuery a dynamic query. There has to be a way for this to work.
Try
var $useDbConfig = 'alternate';
in your Model Class.
I needed to use a custom finderQuery and use the special {$__cakeID__$} identifier in place of the model ID being matched. This is a fixed version of the sample above, set as the finder query in the relationship entry for the $hasAndBelongsToMany array.
'finderQuery'=>'SELECT * FROM [Plant] AS [Plant] JOIN [DB].[DBO].[empplant] AS
[EmpPlant] ON ([EmpPlant].[employee_id] = {$__cakeID__$}
AND [EmpPlant].[ID] = [Plant].[LocationID])'
This works but if anyone knows how to fix this situation without a custom finder query (what I was trying to avoid by using associations) please post an answer and I will mark that correct instead.
I am trying to use ORM to access data stored, in three mysql tables 'users', 'items', and a pivot table for the many-many relationship: 'user_item'
I followed the guidance from Kohana 3.0.x ORM: Read additional columns in pivot tables
and tried
$user = ORM::factory('user',1);
$user->items->find_all();
$user_item = ORM::factory('user_item', array('user_id' => $user, 'item_id' => $user->items));
if ($user_item->loaded()) {
foreach ($user_item as $pivot) {
print_r($pivot);
}
}
But I get the SQL error:
"Unknown column 'user_item.id' in
'order clause' [ SELECT user_item.*
FROM user_item WHERE user_id = '1'
AND item_id = '' ORDER BY
user_item.id ASC LIMIT 1 ]"
Which is clearly erroneous because Kohana is trying to order the elements by a column which doesn't exist: user_item.id. This id doesnt exist because the primary keys of this pivot table are the foreign keys of the two other tables, 'users' and 'items'.
Trying to use:
$user_item = ORM::factory('user_item', array('user_id' => $user, 'item_id' => $user->items))
->order_by('item_id', 'ASC');
Makes no difference, as it seems the order_by() or any sql queries are ignored if the second argument of the factory is given.
Another obvious error with that query is that the item_id = '', when it should contain all the elements.
So my question is how can I get access to the data stored in the pivot table, and actually how can I get access to the all items held by a particular user as I even had problems with that?
Thanks
By default, all of Kohana's ORM models expect the table's primary key to be 'id.' You need to set $_primary_key in your model to something else.
$user_item = ORM::factory('user_item', array('user_id' => $user, 'item_id' => $user->items));
I think you need to provide a single item_id value for this to work, not an array of objects.
Also, to find all entries for a single user you should be able to do this:
$user_items = ORM::factory('user_item', array('user_id' => $user));
Does that answer your question?