Search query from joined table in Laravel 5.3 - laravel

I have a books table that contains many subject on my subjects table (one-to-many relationship).
I tried to join my tables like this:
$book = Book::latest()
->leftjoin('subjects', 'books.id', '=', 'subjects.book_id')
->select('books.*', 'subjects.subject')
->where('subject', 'like', '%' .$search. '%')
->paginate(20);
I want a search query that will display the books having subjects matched form the $search variable. However, it keeps displaying a book redundantly depending on how many subjects of a book that matched on the $search variable since a book has many subjects.
I only want to display a book once, regardless of how many subjects the book matched.
This image below was the output of the search query I made, the value of the $search= ""
On the second image notice that I search "a" on the search box:
The book entitled "Special Education assessment: Issues strategies affecting today's classrooms" (see it on the first image; it was being redundant 6 times since the subjects of that book was 6)

To display a book only once you have to group by book id (or any other unique column)
->groupBy('books.id');
Mind you, as mentioned in the MySQL doc here
SQL92 and earlier does not permit queries for which the select list, HAVING condition, or ORDER BY list refer to nonaggregated columns that are not named in the GROUP BY clause.
Hence the error message 'bisu_ccc_library.books.ISBN' isn't in GROUP BY
To bypass this, turn off strict in Laravel and everything will work nicely.
Go to config/database.php and in the mysql configuration array, change strict => true to strict => false

I think you want to use distinct for your select
$book = Book::latest()
->leftjoin('subjects', 'books.id', '=', 'subjects.book_id')
->select('books.*', 'subjects.subject')
->distinct()
->where('subject', 'like', '%' .$search. '%')
->paginate(20);
Just like in regular SQL (which it will translate to) it will "Force the query to only return distinct results." (from laravel api docs)
The SELECT DISTINCT statement is used to return only distinct
(different) values.
Inside a table, a column often contains many duplicate values; and
sometimes you only want to list the different (distinct) values.
The SELECT DISTINCT statement is used to return only distinct
(different) values. - W3Schools

Related

Laravel: query with a different where condition in a different row

I have a classrooms table with a "quota" column that has different values. In one class, there are many students. How to display classrooms data with where condition which total students per row < "quota" ? Here's the table : .
Code :
Classroom::with('subject.teacher')->with('students')->whereHas('subject', fn ($query) => $query->where('grade', $grade))->withCount('students')->having('students_count', '<', 'quota');
when I use this code the result is empty
when "having" is removed this is the result :
The desired result only displays 3 classroom
You can use the has and a DB::raw.
// Simplified version
Classroom::has('students', '<=', DB::raw('classrooms.quota'))->get();
The withCount function appends an additional select column to your query. It does not use GROUP BY which is typically what is used in conjunction with having.
You can't actually filter by subqueries that are added in the select. However fortunately Laravel allows you to select rows based on how many related models they have using has. This query is also added as a subquery, but within the where clauses so you can also use column names within it like below:
Classroom::with('subject.teacher')
->with('students')
->whereHas('subject', fn ($query) => $query->where('grade', $grade))
->has('students', '<', \DB::raw('quota'))
->withCount('students');

Is there a way to select one url among multiple picture urls with "exists" condition in laravel?

I have several picture urls some of them may exists in the table, some of them may not. Columns are "picture, picture_thubnail, company_logo_thumbnail, company_logo". I want to select picture thumbnail as lets say "picture_url". But if "picture_thumbnail" does not exists, I want to pick "picture" as "picture_url". If "picture" does not exists I want to pick company_logo_thumbnail and so on... as a result I will get a picture_url as a string or null.
edit: this should be done in a single query instead of multiple queries with if else. or more elegant solutions are appretiated.
Sounds more like a database thing than a Laravel thing (the easiest way I can think of anyway).
$images = DB::table('images')
->select(DB::raw('COALESCE(picture_thumbnail, picture, company_logo_thumbnail, company_logo) as picture_url'))
->get();
The MySQL COALESCE function returns the first non-null result.
This is also assuming that you are using MySQL as your database engine.
UPDATE
To check a column in another table you could use a join.
$images = DB::table('images')
->select(DB::raw('COALESCE(picture_thumbnail, picture, company_logo_thumbnail, company_logo, company_logos.id) as picture_url'))
->leftJoin('company_logos', 'company_logos.id', '=', 'images.company_logo_id')
->get();
I've used company_logos.id in the select, but guessing it would be something like company_logos.picture or something with the filename, rather than the id of the row.
UPDATE 2
I would change this to be a left join:
->join('cloudfiles', function ($join) {
$join->on('userdetails.company_logo_id', '=', 'cloudfiles.id')->where('company_logo_id', '!=', null);
})
->leftJoin('cloudfiles', function ($join) {
$join->on('userdetails.company_logo_id', '=', 'cloudfiles.id')->where('company_logo_id', '!=', null);
})
A left join would mean you still get all results from the userdetails where as a normal join would mean only userdetails that have an associated cloudfiles record would be returned.
Secondly, I'd change this:
->where('company_logo_id', '!=', null)
to
->whereNotNull('company_logo_id')
Although I think you might be able to remove that condition completely.
Couple of other points, just fyi:
$boothOwner = Booth::where('id', '=', Request()->booth_id)->firstOrFail();
could be (unless there are somehow duplicate ids in the table)
$boothOwner = Booth::findOrFail(Request()->booth_id);
Similarly,
->whereRaw('userdetails.user_id = ?', [$boothOwner->user_id])
could be
->where('userdetails.user_id', $boothOwner->user_id)

Laravel eloquent search and order by best matching

I have a products table in my ecommerce website. When customers search products by name, I execute the following query:
$products = Product::where('name', 'like', '%'.$query.'%')->get();
Now, I see the result contains only matched rows with customer's $query. But the result is not ordered by the best matching as like google. How should I change my query to get products ordered by best matching?
First of all, best match is a bit ambiguous.
However any solution will always include the usage of multiple orderByRaw calls.
Example:
$products = Product::where('name', 'like', '%'.$query.'%')
->orderByRaw('name like ? desc', $query)
->orderByRaw('instr(name,?) asc', $query)
->orderBy('name')
->get();
Basically, it orders on the name match first (still using like to allow wildcards in the search string, if not needed use =).
Then it orders the substring position of the query it found in the name col, make sure to order asc since lower is better. At least in my definition of best match.
Also notice the usage of the ? parameter. Using it like this ->orderByRaw('name like '.$query.' desc') will make it vulnerable to SQL injection!
Take into account that ordering on multiple subqueries can be costly!

Laravel Count and Group By Child Relationship

I have two tables, one called companies and one called leads. Using Laravel's Eloquent I'd like to get a count of leads for each company. Which is pretty straight forward, but I also need to group these by the Leads status.
e.g.
Company A has
3 hot leads
2 cold leads
Company B has
6 cold leads
0 hot leads
Companies table has the following structure:
id
name
Leads table has the following structure
id
company_id
status (hot, cold)
I have tried the following, which gets the count of leads out for each company, but this needs to be further grouped by the status of the leads
DB::table('companies')
->selectRaw('companies.id, companies.name, COUNT(*) as count')
->join('leads', 'leads.company_id', '=', 'companies.id')
->groupBy('id')
->get();
When using aggregates such as count, you need to make sure to group by all selected non-aggregate columns.
MySQL will usually let you get away with out doing this but the query results can come out being non-sensical.
$companies = DB::table('companies')
->select(['companies.name', 'leads.status', \DB::raw('COUNT(*) as count')])
->join('leads', 'leads.company_id', '=', 'companies.id')
->groupBy('companies.name', 'leads.status')
->get();
If you have multiple records in the companies table with the same name (but different ids), this will group all the counts together for each duplicate company name. If that's the case, you should also select and group by companies.id as well.
If you want to key this by the company, and then by the status, the following should work...
$companies = $companies->groupBy('name')->map(function ($company) {
return $company->groupBy('status')->map(function ($company) {
return $company->first()['count'];
});
});
Then you can use it like...
echo $companies['testCompanyName']['hot'];
echo $companies['testCompanyName']['cold'];

Mulitple LIKE db query using associative array- but all from the same column name...?

I'm trying to query my database using CodeIgniter's active record class. I have a number of blog posts stored in a table. The query is for a search function, which will pull out all the posts that have certain categories assigned to them. So the 'category' column of the table will have a list of all the categories for that post in no particular order, separated by commas, like so: Politics, History, Sociology. etc.
If a user selects, say, Politics, and History, The titles of all the posts that have BOTH these categories should be returned.
So, the list of categories queried will be the array $cats. I thought this would work-
foreach ($cats as $cat){
$this->db->like('categories',$cat);
}
By Producing this:
$this->db->like ('categories','Politics');
$this->db->like ('categories','History');
(Which would produce- 'WHERE categories LIKE '%Politics%' AND categories LIKE '%History%')
But it doesn't work, it seems to only produce the first statement. The problem I guess is that the column name is the same for each of the chained queries. There doesn't seem to be anything in the CI user guide about this (http://codeigniter.com/user_guide/database/active_record.html) as they seem to assume that each chained statement is going to be for a different column name.
Of course it is not possible to use an associative array in one statement as it would have to contain duplicate keys- in this case every key would have to be 'categories'...
With regard to MySQL, I just ran the following query on my database and there were no issues.
SELECT *
FROM `TicketUpdates`
WHERE content LIKE '%update%'
AND content LIKE '%footprints%';
Likewise the following code ran as expected:
$this->db->select('*');
$this->db->from('TicketUpdates');
$this->db->like('content', 'update');
$this->db->like('content', 'footprints');
print_r($this->db->get()->result());
If you're using the 'like' function in CI, you have to prefix and postfix them with the proper query functions, example:
$this->db->select('*');
$this->db->from('tablename');
foreach($cats as $cat){
$this->db->like('categories', $cat);
}
$result = $this->db->get();
you can use following code
$this->db->select("*")->from($table);
foreach($cats as $single_name)
$this->db->or_like("categories",$single_name);
$result = $this->db->get();

Resources