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.
Related
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.
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 what I want to do with yii is a littly complex.
Database
I have a city, a city_has_product and a product table. So city is MANY_TO_MANY with products through city_has_product. And city_has_product has a column named amount.
For knowing the FK and PK names, here the tables with FK and PKs:
city
id
city_has_product
city_id
product_id
product
id
What I want as model
I want, that I can do this: $city->products[0]->amount
So the amount value comes from city_has_product to Product (in the model layer)
So far I am
City has this relations:
return array(
'city_has_product'=> array(self::HAS_MANY, 'CityHasProduct', 'city_id'),
'products'=> array(self::HAS_MANY, 'Product', array('product_id'=>'id'), 'through' => 'city_has_product'),
);
CityHasProducts has no relation
Product has this relations:
return array(
'city_has_product'=> array(self::HAS_MANY, 'CityHasProduct', 'product_id'),
'city'=> array(self::HAS_ONE, 'City', array('city_id'=>'id'), 'through' => 'city_has_product'),
'hasAmount' => array(self::STAT, 'CityHasProduct', 'product_id', 'select'=>'amount'),
);
Problem with my solution
hasAmount ignores the city. I tried it with 'condition'='city_id=city.id' but it didn't worked. I dont know how I can access the city.id from Product, because I have no direct relationsship with city, because on db level product and city are MANY_TO_MANY.
Questions
1. Is this possible as relation with yii?
2. How can I get city.id from Product?
3. Am I on the right track? If not how to get to this model I want?
Thanks for all your answers :)
I have table City (city_id, name), table Product (product_id, name) and table city_has_product (city_id, product_id, amount). Everything look like you, right?
1/ There are MANY to MANY relationship, if you don't provide the city_id and product_id, you will never get amount of two objects City and Product.
2/ Okay, we need two city_id and product_id, but how you pass and make them work together in order to get amount from category_has_product? Is it like following snipet?
$city->products[0]->amount
Oh, we got problem now, it was wrong for sure. Don't think that you got the city_id (from $city) and product_id (from $product by index 0) then amount should have been come up from Product. No, it doesn't.
Both of us knew that amount column was in CityHasProduct model, not City or Product, then amount was not Product or City property absolutely.
3/ You have had the relation on City and Product, but how they fit for your purpose. Let see the following query:
$product_id = 2;
$city_id = 4;
$product = Product::model()->with(array(
'cities' => array(
'condition'=>'cities.city_id = :city_id',
'params' => array(':city_id' => $city_id)
)
))->findByAttributes(array('product_id'=>$product_id));
//it return Product model.
//or we can do samething from City model
But wait, what are we doing here? Why I have to implement above query then continue to find out the Product and Category when I have already known exactly the City and Product (by city_id and product_id), yeah, in this case it seems silly at all
4/ Finally, I asked myself why couldn't it be simple like below query:
$rls = CityHasProduct::model()->findByAttributes(array( 'city_id' => $city_id, 'product_id' => $product_id));
Now you can access to both of city and product like below
$rts->amount;
$city = $rls->city->name; //BELONG_TO relation on CityHasProduct model
$product= $rls->product->name; //BELONG_TO relation on CityHasProduct model
CityHasProduct model relation should be
return array(
'product' => array(self::BELONGS_TO, 'Product', array('product_id'=>'product_id')),
'city' => array(self::BELONGS_TO, 'City', array('city_id'=>'city_id')),
);
5/ Btw, forget the way how you use self::STAT to try to make stuff work, it just gives you the count of record from CityHasProduct match with city_id or product_id
Statistical Query
Besides the relational query described above, Yii also supports the
so-called statistical query (or aggregational query). It refers to
retrieving the aggregational information about the related objects,
such as the number of comments for each post, the average rating for
each product, etc. Statistical query can only be performed for objects
related in HAS_MANY (e.g. a post has many comments) or MANY_MANY (e.g.
a post belongs to many categories and a category has many posts).
Performing statistical query is very similar to performing relation
query as we described before. We first need to declare the statistical
query in the relations() method of CActiveRecord like we do with
relational query.
http://www.yiiframework.com/doc/guide/1.1/en/database.arr
* Out there still has some tricks to help you to getting over. You just add more public property name 'pamount' on Product, then you create a function on City model
function getProductWithAmount([index from default search $city->products])
and go ahead with it.
$amountFromProductIndex = $city->getProductWithAmount(0)->pamount;
However, I don't encourage you follow it.
I think your relations have some problems. You say that City - Product is Many to Many yet your relation city in the Products model is a HAS_ONE instead of a HAS_MANY. and the other issue with your relations is that you are trying to use a statistical query to get a scalar value from CityHasProducts, but statistical queries are for retrieving aggregate values like COUNT(*) or SUM(amount).
Since City to product is Many to Many, there is no single City for a given Product, you can however get an array of cities for a given product. that array may only have one city in it, but it will be an array none the less.
you can retrieve the amount for a product given a city and product, and you can get at it from either the product or the city, your choice.
Try this:
$product = Product::model()->with('city_has_products')->findByAttributes(array(
'condition' => 't.id = :city_id and cityHasProducts.poduct_id = :product_id',
'params' = array(
':product_id' => $product_id,
':city_id' => $cty_id,
),
);
$amount = $product->city_has_products->amount;
You may have to tweak this a bit as it is untested, but it should provide a direction for you.
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 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?