I have the following database schema:
Person 1 - 1 Contact 1 - * Address
There is also another entity that is a contact, therefore the table contact is necessary.
I access a list of persons like this:
$persons = Person::with('contact.addresses')
->filter($string)
->withoutCompany()
->orderBy('persons.surname', 'ASC')
->get();
The problem is that the eloquent model that is returned overwrites the id field of person with the id of the table contact. This is due to the query scope filter($string) which can be seen below:
public function scopeFilter($query, $parameter) {
return $query
->join('contacts', 'persons.contact_id', '=', 'contacts.id')
->join('addresses', 'addresses.contact_id', '=', 'contacts.id')
->where(function($q) use($parameter) {
$q->where('addresses.city', 'like', $parameter)
->orWhere('persons.surname', 'like', '%' . $parameter . '%')
->orWhere('persons.name', 'like', '%' . $parameter . '%')
->orWhere('addresses.postcode', 'like', $parameter . '%');
});
}
public function scopeWithoutCompany($query) {
return $query->whereRaw('persons.id not in (select company_person.person_id from company_person)');
}
This problem is already addressed here
It states that a select statement should be used, because if the same column name is selected several times, the last one will overwrite the precedents when using joins to fill an eloquent model.
But when I use a select statement in a query scope, e.g. selecting only persons.* and addresses.* - I get an Cardinality violation: 1241 Operand should contain 23 column(s) error from another model where I use the scope in an eager loading constraint (Person and Address together have 23 columns).
I fixed this problem by an ugly workaround, joining again on the Persons table as a last join to overwrite the id with the person id. But this can't be the solution to my problem. Has anyone any idea how I can solve this?
This is the workaround from my Person::filter($string) query scope:
return $query
->select(array('persons.*', 'addresses.*'))
->join('contacts', 'persons.contact_id', '=', 'contacts.id')
->join('addresses', 'addresses.contact_id', '=', 'contacts.id')
->join('persons as p', 'p.id', '=', 'persons.id')
->where(function($q) use($parameter) {
$q->where('addresses.city', 'like', $parameter)
->orWhere('persons.surname', 'like', '%' . $parameter . '%')
->orWhere('persons.name', 'like', '%' . $parameter . '%')
->orWhere('addresses.postcode', 'like', $parameter . '%');
});
For reference:
This is the code that results in the cardinality violation error (database sehcma is Company * - * Person):
$companies = Company::with(array('persons' => function($q) use ($string)
{
$q->filter($string);
})
)->whereHas('persons', function($q) use ($string)
{
$q->filter($string);
})
->orderBy('name', 'ASC')
->get();
Thanks to user Kindari at Laravel.io I have a solution now.
I am not directly putting a condition on my address table, but I have the where clauses in a query scope within my Address model.
Within my Persn model in the query scope, I first retrieve all relevant addresses. I put the IDs in a list and restrict my Persons to only those that live at the addresses. With that I have one more query but have no join at all. See the solution below
public function scopeFilter($query, $parameter) {
$contact_ids = Address::filter($parameter)->lists('contact_id');
$query->with('contact')
->where(function($q) use ($parameter, $contact_ids) {
$q->where('persons.surname', 'like', '%' . $parameter . '%')
->orWhere('persons.name', 'like', '%' . $parameter . '%');
if($contact_ids) {
$q->orWhereIn('contact_id', $contact_ids);
}
});
return $query;
}
This part
$q->where('addresses.city', 'like', $parameter)
->orWhere('addresses.postcode', 'like', $parameter . '%');
is now part of the query scope of the Address model.
Nicely encapsulated. I don't know why I did not think about that one. I was somehow focusing on putting it all in one query. This solves my problem.
Related
I have a problem connecting to the data for searching the customer's name because I only identify the id as a foreign key. How can I get data from the customer table? I tried using a join table but no got no result.
public function render()
{
$search = $this->searchTerm;
$data = Order::with('customer')->where('user_id', Auth::id())
->where(function ($query) use ($search) {
$query->where('customer_id', 'like', '%' . $search . '%') // <- this field linked in customers table to get name of customer
->orWhere('orderNumber', 'like', '%' . $search . '%');
})->orderBy('id', $this->sortDirection)->paginate(9);
return view('livewire.dashboard.orders.list-order', compact('data'));
}
If I am right I think you are trying to search for customer name through customer relationship
You can do it like this:
$search = $this->searchTerm;
$data = Order::where('user_id', Auth::id())->with(['customer' =>
function($query) use($search){
$query->where('name','like', '%' . $search . '%')
->orWhere('orderNumber', 'like', '%' . $search . '%');
}])->orderBy('id', $this->sortDirection)->paginate(9);
I have the following scope in User model
$query->with('country')
->when($filters['search'] ?? null, function ($query, $search) {
return $query->where('name', 'LIKE', '%' . $search . '%')
->orWhereHas('country', function ($query) use ($search) {
$query->where('name', 'LIKE', '%' . $search . '%');
});
});
$query->orderBy('name', 'asc');
}
return $query;
}
I am pretty new to Laravel - I currently the above query is sorting by user name but I would like to sort by country name. I can do this with country_id as there is a relation but not sure how to sort by country name.
Thanks
there are two approaches we can use to order these users by their company. The first is using a join:
$users = User::select('users.*')
->join('countries', 'countries.id', '=', 'users.country_id')
->orderBy('companies.name')
->get();
Here is the generated SQL for this query:
select users.*
from users
inner join countries on countries.id = users.country_id
order by countries.name asc
The second way is using a subquery:
$users = User::orderBy(Country::select('name')
->whereColumn('countries.id', 'users.country_id')
)->get();
And you can see the reference here: ordering database queries by relationship columns in laravel
The items have category_id and location_id which has relation to tables category and location.
The search form has 3 fields, 1 item keyword, 2nd is the category selection and 3rd is its location.
The user has to fill all the fields in order to be able to search.
The problem is that the search works only for the first input field and brings all the items with the same name but location and categories are not filtered during search...
public function search(Request $request) {
$query = $request->input('query');
$cQuery = $request->input('subCategoryId');
$pQuery = $request->input('province');
$subCategories = Business::where('name', 'like', "%$query%")
->where('province_id', 'like', "%$pQuery%")
->where('sub_category_id', 'like', "%$cQuery%")->get();
return view('pages.search-result')
->with('subCategories', $subCategories);
}
I have assumed that you have the relations setup for all the models. If not, please go through Defining Relationships.
Consider the below answer as a skeleton example for what you are looking for.
$businesses = Business::where('name', 'like', '%' . $query . '%')
->with('province', 'subCategory')
->whereHas('province', function ($q) use ($pQuery) {
$q->where('name', 'like', '%' . $pQuery . '%');
})
->whereHas('subCategory', function ($q) use ($cQuery) {
$q->where('name', 'like', '%' . $cQuery . '%');
})
->get();
I have models: Student, Tutor, Country.
Main model is Student with code:
public function studentTutors()
{
return $this->morphedByMany(Tutor::class, 'studentable')
->with('tutorAddresses');
}
Then relations.
Tutor:
public function tutorAddresses()
{
return $this->hasMany(TutorAddress::class, 'tutor_id', 'id')
->with('tutorCountry');
}
TutorAddress:
public function tutorCountry()
{
return $this->hasOne(Country::class, 'country_id', 'country_id')
->where('user_lang', 'en');
}
How do I use it:
$paginator = $student->studentFavouriteTutors()
->getQuery() //for paginate
->where(function ($query) use ($searchPhraze) {
if (strlen(trim($searchPhraze))) {
return $query
->where('username', 'like', '%' . $searchPhraze . '%')
->orWhere('firstname', 'like', '%' . $searchPhraze . '%')
->orWhere('lastname', 'like', '%' . $searchPhraze . '%');
}
})
->paginate($pages, $columns, $pageName, $page);
Question:
I am searching in tutors table (Tutor) for user/first/last names.
Is there are way to search for country name from countries table (Country: tutorCountry)? Lets say, table has 'name' column with country names.
If yes, how should $paginator code look like, to get data from countries table?
Same question goes for relation tutorAddresses. Lets say, table has 'city' column with city names.
Is this possible?
Now, I do not use relations for search, and just do joins.
BTW: I tried hasManyThrough relation, but it does not seem to pass data from that 'through' table, so this is not going to work for me. Also, my 'through' relations go a bit too deep for it (unless I do not understand something as far as this relation is concerned).
EDIT:
Answer by jedrzej.kurylo is perfect!
I just want to add, for all these, who look for a way to search within relation of a relation, like in my case:
studentTutors / tutorAddresses / tutorCountry
... where within model Student, I also want to look for country name inside of Country model, that is deeper in chain of relations and is not directly related to Tutor, but to TutorAddress, which is related to Tutor.
It is just a question of nesting queries:
$searchPhraze = 'France';
$res = $student->studentFavouriteTutors()
//first relation level
->whereHas('tutorAddresses', function($query) use ($searchPhraze) {
//deeper relation
$query->whereHas('tutorCountry', function($query) use ($searchPhraze) {
$query->where('country', 'like', '%' . $searchPhraze . '%');
});
})->get();
Or you can even combine of searches of parent and child relations:
$searchPhraze = 'Hodkiewiczville';
$res = $student->studentFavouriteTutors()
//first relation level
->whereHas('tutorAddresses', function($query) use ($searchPhraze) {
$query
//first level relation search
->where('city', 'like', '%' . $searchPhraze . '%')
//deeper relation
->orWhereHas('tutorCountry', function($query) use ($searchPhraze) {
$query->where('country_label', 'like', '%' . $searchPhraze . '%'));
});
})->get();
Above code found related datasets as expected.
Thou, I am not sure as to benchmark of this.
You can search data in related tables using whereHas() function, e.g.:
$student->studentFavouriteTutors()
->whereHas('tutorAddresses', function($query) use ($searchPhraze) {
$query->where('city', 'like', '%' . $searchPhraze . '%');
})->get();
This will get you all student's favourite tutors that have an address where city column contains given phrase.
I have a table that I am running a search on. Foo. I want to check if the search term is like a bunch of different fields in the table. But I want the results to be forced to be "active".
I am using Laravel 5.0
$foos = DB::table('foo')
->where('name', 'LIKE', '%' . $query . '%')
->orWhere('description', 'LIKE', '%' . $query . '%')
->orWhere('address_city', 'LIKE', '%' . $query . '%')
->orWhere('short_description', 'LIKE', '%' . $query . '%')
->orWhere('interview', 'LIKE', '%' . $query . '%')
->get();
$foos = $foos->where('active', true)->get();
Collection has a where method similar to query builder.
$activeFoos = $foos->where('active', true);
https://laravel.com/docs/5.4/collections#method-where
Edit :
I understand what you need from the comments now. Try the code below, and also note that i changed your original $query to $find. So keep that in mind.
$foos = DB::table('foo')
->where('active', true)
->where(function ($query) use ($find) {
$query->where('name', 'LIKE', "%{$find}%")
->orWhere('description', 'LIKE', "%{$find}%")
->orWhere('address_city', 'LIKE', "%{$find}%")
->orWhere('short_description', 'LIKE', "%{$find}%")
->orWhere('interview', 'LIKE', "%{$find}%");
})
->get();
Is the value of active actually true or just truthy (such as an integer of 1)?
For loose comparisons then you'll need to use whereLoose('active', true), otherwise Laravel's method where() on Collections will perform strict type comparisons.
Like Sandeesh said, you'll need to remove the get() since get() is a query builder method, Collections where() methods return a new collection not a builder instance.