I have a many to many relation between sizes and toppings and and have a thrird table which contains extra fields the table is named toppings_sizes
Now I want to select only one field from toppings_sizes, at the moment I have a query which delivers all available field in all 3 tables.
$rs = $this->Toppings
->find()
->contain(['Sizes'])
->where(['category_id' => $categoryId]);
debug( json_encode($rs, JSON_PRETTY_PRINT));
I this can I need only 2 fields Sizes.name and ToppingsSizes.price
One way is by adding ->select to specify fields:
$rs = $this->Toppings
->find()
->select(['Sizes.name', 'ToppingsSizes.price'])
->contain(['Sizes'])
->where(['category_id' => $categoryId]);
$rs = $this->Toppings
->find()
->contain([
'Sizes' => function ($q) {
return $q
->select(['name']);
},
'ToppingsSizes' => function ($q) {
return $q
->select(['price']);
}
]);
->where(['category_id' => $categoryId]);
By Using this code you can get desired fields from table.
You need to pass Relationship model names into contain() method array which model entity you need in result data.
$rs = $this->Toppings
->find()
->select(['Sizes.name', 'ToppingsSizes.price'])
->contain(['Sizes','ToppingsSizes'])
->where(['Toppings.category_id' => $categoryId]);
Related
I have the following code in a Table definition.
$paginationQuery = $this->find()
->select([
'Members__id','Members__member_type','Members__first_name',
'Members__middle_name','Members__last_name','Members__suffix'
])
->contain([
'SocialMembers' => [
'foreignKey' => false,
'queryBuilder' => function (Query $q) {
return $q->where([
'Members.Members__id' => 'SocialMembers.full_member_id'
]);
}
]
])
->from([
$this->getAlias() => $query
])
->order([
'Members__last_name' => 'ASC',
'Members__first_name' => 'ASC'
]);
return $paginationQuery;
This is to paginate the results of a union of two sets of extracted data.
The problem comes from the queryBuilder function. The left join that is generated looks like this:
LEFT JOIN members SocialMembers ON (
SocialMembers.member_type = 2
AND Members.Members__id = 'SocialMembers.full_member_id'
)
There is an unneeded pair of single quotes around SocialMembers.full_member_id. queryBuilder appears to correctly handle the Members.Members__id, but not the value field of the array. Is there any way to get this to generate correctly?
Was able to resolve this by moving the contains clause to the preceding finder methods.
I want do implement a search function for recipes and their associated ingredients. The user should specify ingredients that he wants to exclude from the search and at the same time ingredients that are contained in the recipes he is looking for.
Those are my two finders:
public function findByContainingIngredients(Query $query, array $params)
{
$ingredients = preg_replace('/\s+/', '', $params['containing_ingredients']);
if($ingredients) {
$ingredients = explode(',', $ingredients);
$query->distinct(['Recipes.id']);
$query->matching('Ingredients', function ($query) use($ingredients) {
return $query->where(function ($exp, $query) use($ingredients) {
return $exp->in('Ingredients.title', $ingredients);
});
});
}
return $query;
}
public function findByExcludingIngredients(Query $query, array $params)
{
$ingredients = preg_replace('/\s+/', '', $params['excluding_ingredients']);
if($ingredients) {
$ingredients = explode(',', $ingredients);
$query->distinct(['Recipes.id']);
$query->notMatching('Ingredients', function ($query) use ($ingredients) {
return $query->where(function ($exp, $query) use ($ingredients) {
return $exp->in('Ingredients.title', $ingredients);
});
});
}
return $query;
}
In the controller I call:
$recipes = $this->Recipes->find()
->find('byExcludingIngredients', $this->request->data)
->find('byContainingIngredients', $this->request->data);
If the user excludes an ingredient from the search and specifies one ore more ingredient that he wants to include, there are zero results.
When I take a look at the generated SQL I see the problem:
SELECT
Recipes.id AS `Recipes__id`,
Recipes.title AS `Recipes__title`,
.....
FROM
recipes Recipes
INNER JOIN ingredients Ingredients ON (
Ingredients.title IN (: c0)
AND Ingredients.title IN (: c1)
AND Recipes.id = (Ingredients.recipe_id)
)
WHERE
(
Recipes.title like '%%'
AND (Ingredients.id) IS NULL
)
GROUP BY
Recipes.id,
Recipes.id
The problem is "AND (Ingredients.id) IS NULL". This line makes the results from the including ingredients disappear.
My approaches:
Creating an alias when calling notMatching() on the association twice. I think this is not possible in Cake3.1
Using a left join on the PK/FK and the excluded title and creating an alias. Basically writing my own notMatching function. This works, but it does not feel right.
Are there other solutions?
To anybody coming to this page and concluding you cannot combine a matching() and notMatching() on the same associated class:
Yes, it is possible (as of Cake 3.4.9 anyway) to do such a find. But you have to use a different alias for the target table - that is an alias that is different to the usual class name.
So in OP's situation, you would put this in RecipesTable.php :
public function initialize(array $config) {
... usual stuff
$this->belongsToMany('Ingredients', [
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id',
'joinTable' => 'ingredients_recipes'
]);
// the next association uses an alias,
// but is otherwise *exactly* the same as the previous assoc.
$this->belongsToMany('ExcludedIngredients', [
'className' => 'Ingredients',
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id',
'joinTable' => 'ingredients_recipes'
]);
}
And you should be able to write a find statement like this:
$this->find()
-> ... usual stuff
->matching('Ingredients',function($q) use($okIngredients) {
... check for ingredients ...
})
->notMatching('ExcludedIngredients', function($q) use($excludedIngredients) {
... check for ingredients ...
});
This does work. Unfortunately, when I used it in an analogous situation with thousands of rows in my 'Recipes' table the query took 40 seconds to run. So I had to go back and replace the notMatching() by a hand-crafted join anyway.
I think what you could do is manually join ingridients table once more with different alias (http://book.cakephp.org/3.0/en/orm/query-builder.html#adding-joins) and then use it in matching/notMatching
I have a query
$p = $this->Products
->findById($id)
->select(['name'])
->contain(['Categories.Sizes' => function($q) {
return $q->select(['id', 'name']);
}
]);
which is only returning product's name and not the Size of the product's category. But if the remove the select function which accepts the field names then it delivers also the Sizes
Is there any solution for that?
According to the CakePHP 3 book, in the area "Selecting Rows From A Table", you can specify which fields you want returned by including them in a select array:
$query = $articles
->find()
->select(['id', 'name']) // <-- Notice this line
->where(['id !=' => 1])
->order(['created' => 'DESC']);
So for yours, you're limiting the fields that are returned because you're specifying that you only want the 'name' field.
I have a query which looks like
$entity = $this->Products
->findById($id)
->contain( [ 'Categories',
'Categories.Sizes',
'ProductsPrices.Sizes' => function($q) {
return $q->select('Sizes.name');
}
])->first();
In this case a Product belongTo a Category and in turn Category hasMany Sizes Similerly Producthas many prices depending on it's size.
For example if a Product is available in 4 different sizes, then it will have 4 prices in the ProductsPrices table.
The query is returning the desired results, but problem is it is returning all the fields from each row. For example from Categories table i only need it's name. And from Categories.Sizes only id and name
Can I limit it to only fields which I need. I tried with ->select at different levels but not working. For example 'ProductsPrices.Sizes' I see only the Sizes query not the ProductsPrices one
Read more:
http://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html
$entity = $this->Products
->findById($id)
->contain( [
'Categories' => function($q){
return $q->select(['name'])
->contain([
'Sizes' => function($q) {
return $q->select(['id','name']);
}
]);
},
// -------------
])->first();
How do you make it where in clause in new CakePHP? I'm trying:
$restaurants->find()->where(['id' => $r_ids])->all();
Where $r_ids is array of ids I need in my query, but this doesn't work as expected.
With CakePHP 3.x it's now necessary to either indicate the data type, which for an array of values need to have [] appended to the type:
$query = $articles
->find()
->where(['id' => $ids], ['id' => 'integer[]']);
or to explicitly make use of the IN keyword:
$query = $articles
->find()
->where(['id IN' => $ids]);
See also
Cookbook > Database Access & ORM > Query Builder > Automatically Creating IN Clauses
Try this one for CakePHP 3.x
$query = $restaurants
->find()
->where(['id IN' => $r_ids]);
$query->all();
You can also use the short syntax:
$result = $restaurants->find('all',
['conditions' => ['id IN' =>$r_ids]]
)->all();
In Cakephp 3.x, If you have multiple where conditions then your query will be as below:
return $this
->find()
->contain([
'XYZ.Articles',
'YYY.Managers',
'ZZZ.Readers',
])
->where([
'ID' => $ids,
'READER.STATUS' => $xyz,
],[
'ID' => 'integer[]'
])
;