I wanted to ask, how can i get data from one table and use this in other find.
For example, i have films table.
I want to get highest rated 3 films. Result should return 3 ID's.
Now, i want to create other query from not associated table, and pass this 3 ID's as "conditions" to find data in other table.
I dont want to use associations, because, data is stored in many databases, and this is problematic.
Thank You.
Once you've got your film IDs you can use in to filter the results from your other Model:-
$filmIds = ['32','55','75'];
$query = TableRegistry::get('Model')->find()
->where(function ($exp, $q) use ($filmIds) {
return $exp->in('film_id', $filmIds);
});
// WHERE film_id IN ('32','55','75')
Check out the docs section on advanced conditions.
If you need to get your film IDs into the correct format (i.e. that shown in the example code) you can use Hash::extract() on the results from your previous query.
if your cakephp version 3.x you can use subqueries in fairly intuitive way
$films = TableRegistry::get('Films')->find('highestRated')
->select(['id'])
->limlt(3);
$query = $related->find()
->where(['id' => $films]);
Subqueries are accepted anywhere a query expression can be used. For example, in the select() and join() methods. http://book.cakephp.org/3.0/en/orm/query-builder.html#subqueries
Related
Have a formatResults callback function that adds a "custom calculated" field into the entities post returned from a model query in my Cakephp. I would like to sort by this field and use this on a paginate is this possible?
So far i cannot accomplish this because the paginate limits the records fetched and therefore only records less than the paginator limit get sorted and not all the resultset...
Current code:
$owners = $this->Owners->find('all');
$owners->formatResults(function (\Cake\Collection\CollectionInterface $owners) {
$owners = $owners->map(function ($entity) {
$entity->random = rand(0,1);
return $entity;
});
return $owners->sortBy(function ($item){
return $item->random;
},SORT_DESC);
});
This works as expected:
$owners->toArray();
This does not:
$owners = $this->paginate($owners);
$owners->toArray();
Mainly because its "callback processing" only the first 10 records, i would like to process the whole resultset.
After diggin around ive found a similar topic opened by a previous user on the this link, it seems that is not possible to use pagination sort in other than the fields in the database.
As a result, i would suggest:
1 - Either alter your model logic, to accommodate your requirements by creating virtual fields or alter database schema to include this data.
2 - If the data requires further or live processing and it cannot be added or calculated in the database, perhaps programming a component that will replicate the paginate functionality on a cakephp collection would be a good option.The downside of this approach is that all records will be returned from the database which may present performance issues on large resultsets.
Currently I have two tables, the typical master - detail, what I get is an array of id's from the detail table, [1,2,3,4,5, ...], from this I want to generate a grouping to obtain the details grouped by the master
Master
public function details()
{
return $this->hasMany('App\Models\Detail', 'detail_id');
}
Detail
public function master()
{
return $this->belongsTo('App\Models\Master', 'master_id');
}
Id's [1,2,3,4,5..]
my attempt to get the result, but they are not grouped
return Detail::whereIn('id', $array)->with(['master' => function($query){
$query->groupBy('name');
}])->get();
My question, It is not a duplicate, what makes that question is to get ids which we already have, what I want is to group. It is not correct to close;)
In summary, I don't believe that Eloquent supports groupBy (you'll have to write your own query); and you're not grouping all of the columns.
Your query is not modifying the columns to be selected, so laravel will be selecting all of them; and as such something unique like your IDs will be breaking up the groups. You either need to be explicit about what you're selecting to enable the groups, or explicit about how to summarise the other columns.
The examples on the docs all show customised select queries, with the exception of those specifically under the heading 'groupBy / Having', which is probably a failure of the docs.
But note the other examples, such as this one, that applies grouping functions to all selected columns:
$users = DB::table('users')
// count is a grouping function; status is grouped below
->select(DB::raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
Is grouping what you really want? Grouping summarises the table, for example by provided totals, or counts, or unique 'names'. If so, you'll need to modify the query manually, like the example above.
If you just want to order them by 'name' (i.e. group them together) then you want the orderBy function. In which case, you'd modify your code as follows:
return Detail::whereIn('id', $array)
->with('master')
->groupBy('name')
->get();
Whilst I don't think it matters here, you may wish to consider whether name is a unique column name between the master and detail tables.
What I want to do is to get all rows related with user_id but in a different way.
First condition is to get all Books that are related with the User via Resources table where user_id is stored (in other words - Books owned by the User). Second condition is to get all Books that are related with the User through the Cities model again which is stored in the Resources table as well (Books that belong to Cities owned by the User).
I tried really a lot of things and I simply cannot make this two conditions work because I use JOIN (tried different combinations of innerJoinWith and leftJoinWith) on the same "end" model (User).
What I've done so far:
$userBooks = $this->Books->find()
->leftJoinWith("Resources.Users")
->leftJoinWith("Cities.Resources.Users")
->where(["Resources.Users" => 1])
->orWhere(["Cities.Resources.Users" => 1])
->all();
This of course does not work, but I hope you get the point about what I'm trying to achieve. The best what I was able to get with trying different approaches is the result of only one JOIN statement what is logical.
Basically, this can be separated into 2 parts which gives expected result (but I do not prefer it because I want it done with one query of course):
$userBooks = $this->Books->find()
->innerJoinWith("Resources.Users", function($q) {
return $q->where(["Users.id" => 1]);
})
->all();
$userBooks2 = $this->Books->find()
->innerJoinWith("Cities.Resources.Users", function($q) {
return $q->where(["Users.id" => 1]);
})
->all();
Also, before this I created an SQL script which works well and result is like expected:
SELECT books.id FROM books, cities, users_resources WHERE
(users_resources.resource_id = books.resource_id AND users_resources.user_id = 1)
OR
(users_resources.resource_id = cities.resource_id AND books.city_id = cities.id AND users_resources.user_id = 1)
This query works and I want to transfer it into ORM styled query in CakePHP to get both Books that are owned by the user and the ones that are connected with the User via Cities. I want somehow to separate these joins to individually filter data like I did in the SQL query.
EDIT
I've tried #ndm solution but the result is the same as where there is only 1 association (User) - I was still able to get data based on only one join statement (second one was ignored). Due to the fact I had to move on, I ended up with
$userBooks = $this->Books->find()
->innerJoinWith("Cities.Resources.Users")
->where(["Users.id" => $userId])
->union($this->Books->find()
->innerJoinWith("Resources.Users")
->where(["Users.id" => $userId])
)
->all();
which outputs correct result but not in very effective way (by union of 2 queries). I would really like to know the best way to approach this as this is a very common case (filtering by related model (user) that is associated with other models).
The ORM (specifically the eager loader) doesn't allow joining the same alias multiple times.
This can be worked around in various ways, the most simple one probaly being creating a separate association with a unique alias. For example in your ResourcesTable, create another association to Users with a different alias, say Users2, like:
$this->belongsToMany('Users2', [
'className' => 'Users'
]);
Then you can use that association in the second leftJoinWith(), and apply the conditions accordingly:
$this->Books
->find()
->leftJoinWith('Resources.Users')
->leftJoinWith('Cities.Resources.Users2')
->where(['Users.id' => 1])
->orWhere(['Users2.id' => 1])
->group('Books.id')
->all();
And don't forget to group your books to avoid duplicate results.
You could also create the joins manually using leftJoin() or join() instead, where you can define the aliases on your own (or don't use any at all) so that there are no conflicts, for more complex queries that can be a tedious task though.
You could also use your two separate queries as subqueries for conditions on Books, or even create a union query from them, which however might perform worse...
See also
Cookbook > Database Access & ORM > Query Builder > Adding Joins
CakePHP Issues > Improve association data fetching
The scenario:
I am building a search system for a given set of data. Most of this is straight forward - where record title contains X, where record date before Y, etc. Where I am running into difficulty is essentially a category search. Each record belongs to zero or more categories (a relationship pivot table exists such that each row contains a record and a category), and when the user searches for Category A, I want to return all of the records that belong to that category. I've gotten this working with a whereHas, but it seems inordinately slow. In this instance, assume $category is a numeric id that is correctly validated as a category, and $records is an eloquent query builder that has not yet been executed by a get, pagination, all, etc (my function checks to see if several $request->input parameters are defined, then attaches a where to $record as required by the specified parameters, only executing it after all parameters have been considered):
if(!empty($category)) {
$records = $records->whereHas('categories', function($query) use ($category)
{
$query->where('category_id', $category);
});
}
This works, but as there are 7000+ records, 7000+ relationships defined in the pivot, and roughly 30 'categories', the search takes longer than I am comfortable leaving it. My unconfirmed thought is that the where query is executing for every record, thus leading to hundreds or thousands of queries.
I've debated approaching this using the raw query builder and just passing the list of record id's that have that category and using a simple where to filter the record collection before it's executed, but it seems counter-intuitive, leading me to believe there must be a better way.
The Question
How do I efficiently limit the records returned by $records->get() to those records with a defined relationship to category $category.
Edit 2018-01-16
To clarify, while I could simply do $category->records to return all records belonging to a category, this is part of a larger search engine. The full structure of the code looks like this:
If($subject_search_term) {
$records->where('subject', $subject_search_term)
}
If(some other search criteria is defined) {
$records->where(someothercriteria);
}
If(Category search criteria is defined) {
$records->whereHas(something);
}
$records->paginate(20);
Furthermore, there are two of these many-to-many relationships that I need to query (in addition to 'categories', lets say there is also a 'subject' that is independent of it, but similar structure and idea). As far as I know, I need to build the query off records and filter it accordingly.
EDIT 2
For anyone else with this problem, it seems the vastly more efficient way (and the most efficient that I've found) is Joel Hinz's comment - use the DB facade to build a raw query, pluck the id's from it, and use that in a whereIn clause.
Try this:
mpyw/eloquent-has-by-non-dependent-subquery: Convert has() and whereHas() constraints to non-dependent subqueries.
mpyw/eloquent-has-by-join: Convert has() and whereHas() constraints to join() ones for single-result relations.
if(!empty($category)) {
$records = $records->hasByNonDependentSubquery('categories', function($query) use ($category)
{
$query->where('category_id', $category);
});
}
That's all. Happy Eloquent Life!
I'm looking for a way to search and filter a database table / Eloquent model through the query string in Laravel 4.
I have a table named houses with the columns named: price, name, hasCoffeeMachineand hasStove
I want the user to be able to do something like: http://example.com?name=test&hasCoffeeMachine=1
Which will get all the rows where the name is "test" and they have a coffee machine.
I want the user to be able to add URL parameters based on the filter they desire. What is the best way to account for all the possible combinations of query strings to return the correct results from the table?
$houses = new House;
if(Input::get('name'))
$houses->where('name', '=', Input::get('name'));
if(Input::get('hasCoffeeMachine'))
$houses->where('hasCoffeeMachine', '=', Input::get('hasCoffeeMachine'));
// Keep adding more for every filter you have
// Don't do this till you are done adding filters.
$houses = $houses->get();
alternatively you could build and pass in an array into the where clause https://laravel.com/docs/5.2/queries#where-clauses
$houses = $houses->where([
['hasCoffeeMachine',1],
['price','<','1000'],
])->get();
You can build your array with any conditional statements.