Laravel eloquent search and order by best matching - laravel

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!

Related

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)

How to count two related tables using Laravel Query Builder?

I have two tables in my database
My first table
And my second table
I would like to count how many gold members exists and how many silver members exists... I want to do a single count for each category... The rif can be repeated in the column but is the key to join the two tables.. I'm working with Query Builder and I would like to continue work with that. Someone can help me?
I tried with this code but didn't work
$count = DB::table('table1')
->join('table2', 'table1.rif', '=', 'table2.rif')
->select(DB::raw('category')
->count();
Try this:
use Illuminate\Support\Facades\DB;
DB::table('table2')
->join('table1', 'table2.rif', '=', 'table1.rif')
->select(DB::raw('count(*) as count'), 'table1.category as category')
->groupBy('category')
->get();
If you want to count only a specific category:
DB::table('table2')
->join('table1', 'table2.rif', '=', 'table1.rif')
->where('table1.category', 'Silver')
->count()
See Laravel docs for more info.

Efficient way to query database with multiple conditions in laravel

Is it possible to make this a single query?
$yl_min = DB::connection($this->db2)->table('historical')
->where([['slug','=',$crypto_id],['low_usd','!=', null]])
->whereBetween('created_time',[$this->range_1y,$this->hislatest])
->min('low_usd');
$yl = DB::connection($this->db2)->table('historical')
->select('id','coin','low_usd','created_time','created_at')
->where([['slug','=',$crypto_id],['low_usd',$yl_min]])
->whereBetween('created_time',[$this->range_1y,$this->hislatest])
->first();
I've tried this but no luck:
$yl = DB::connection($this->db2)->table('historical')
->select('id','coin','created_time','created_at',DB::raw('SELECT MIN(low_usd) as low_usd'))
->where([['slug','=',$crypto_id],['low_usd','!=', null]])
->whereBetween('created_time',[$this->range_1y,$this->hislatest])
->first();
After looking at your query code, I found the two query condition is same, and you just want to get min low_usd record,
I think you can just use the multiple condition and ORDER BY low_usd ASC, then take the first one:
$yl = DB::connection($this->db2)->table('historical')
->where([['slug','=',$crypto_id],['low_usd','!=', null]])
->whereBetween('created_time',[$this->range_1y,$this->hislatest])
->orderBy('low_usd','asc')
->select('id','coin','low_usd','created_time','created_at')
->first();
After this, if you want to make this query more efficient,
you need to add index on slug, low_usd, created_time

Laravel Eloquent query model which has exactly a number of association in a n-to-m relationship

I have a project where I have the following Models :
Products
FeatureTypes
Features
Variants
And the tables and relationships are the following :
I have a request where I want to retrieve the Variants from a Product with some specific Features.
A Variant can have 1 or many Features but I want to have all Variants if I give one Feature, and have a more specific search on the Variants if I gave more Features, until I have only one Variant which can have only one set of specific Feature.
Here's the code I have for now :
$variants = $product
->variants()
->whereHas('features', function($query) {
$query->whereIn('id', json_decode(request('features')));
})
->with('features', 'features.featureType')
->get();
The request('features') contains a stringified array with ids of Features.
I used Eloquent's whereIn method thinking that it would give me only the variants that have exactly all the Features given in the request.
But while checking the documentation again, I see that it will return any Variants that has at least one Feature given in the request which is not what I need here.
How can I make that Eloquent's query to return me only the Variants that are associated with all the Features given by the request ?
Thanks in advance for you help ! ;)
whereHas() accepts a fourth argument with the number of rows:
$variants = $product
->variants()
->whereHas('features', function($query) use ($features) {
$query->whereIn('id', $features);
}, '=', count($features))
->with('features', 'features.featureType')
->get();

Search query from joined table in Laravel 5.3

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

Resources