Conditional table joins - laravel

I have following scope in my eloquent model and I want to add two conditions to it. I need help in doing it.
public function scopeImages($query) {
$query->join('images as i', function ($join) {
$join->on('i.vessel_id', '=', 'vessel.id')
->where('i.sort', '=', 'min(i.sort)');
})
->leftjoin('users', 'users.id', '=', 'vessel.user_id')
->select('vessel.*', 'i.image as image', 'users.name as brokername');
}
images table has featured and sort columns. I want to select one row where images.featured is 1 and min sort of the returned results. If there are no images.featured=1 then I want to select min of sort.
Currently the above scope selects image of min sort for each vessel_id

If you order by featured (if featured is boolean) in desc and sort in asc, it will list featured 1 in highest priority and then list sort from min to max. Now, if you take 1st row, you will get what you want.
$query->join('images', 'images.vessel_id', '=', 'vessel.id')
->leftjoin('users', 'users.id' ,'=', 'vessel.user_id')
->select('vessel.*', 'images.image as image', 'users.name as brokername')
->orderBy('images.featured', 'DESC')
->orderBy('images.sort', 'ASC')
->take(1);

You need a correlated subquery
https://dev.mysql.com/doc/refman/5.5/en/correlated-subqueries.html
SELECT v.*,
(SELECT `image` FROM images WHERE vessel_id = v.id ORDER BY featured DESC, sort LIMIT 1) AS image
FROM `vessel` AS v
#EDIT
You Can give this a try:
public function scopeImages($query) {
return $query
->leftjoin('users', 'users.id', '=', 'vessel.user_id')
->select(\DB::raw("vessel.*, (SELECT `image` FROM images WHERE vessel_id = vessel.id ORDER BY featured DESC, sort LIMIT 1) AS image, users.name as brokername"));
}

Related

Convert DB::Select to Query Builder

i has raw query in laravel like this
public function getPopularBook(){
$book = DB::select("
with totalReview as(
SELECT r.book_id , count(r.id)
FROM review r
GROUP BY r.book_id
)
SELECT *
from totalReview x
left JOIN (
SELECT b.*,
case when ((now() >= d.discount_start_date and now() <= d.discount_end_date) or (now() >= d.discount_start_date and d.discount_end_date is null)) then (b.book_price-d.discount_price)
ELSE b.book_price
end as final_price
FROM discount d
right JOIN book b
on d.book_id = b.id
) as y
on x.book_id = y.id
ORDER BY x.count DESC, y.final_price ASC
LIMIT 8"
);
return $book;
}
so when i want to return a paginate, it doesn't work so can i convert this to query build to use paginate
This is a very un-optimized raw query in itself. You are performing too many Join in Subquery just to sort by price
i'm assuming the database table:
books[ id, name, price ]
reviews[ id, book_id ]
discounts[ id, book_id, start_date, end_date, discount_price]
Look how easy it is if you just use Eloquent:
Book::withCount('reviews')->orderBy('reviews_count')->get();
this will give you all the Books order by number of reviews
now with the final price, this can be a bit tricky, let's take a look at a query when we don't consider discount time
Book::withCount('reviews')
->withSum('discounts', 'discount_price') //i'm assuming a book can have many discount at the same time, so i just sum them all
->addSelect(
DB::raw('final_price AS (books.price - discounts_sum_discount_price)')
)
->orderBy('reviews_count', 'asc') // =you can specify ascending or descending
->orderBy('final_price', 'desc') //in laravel chaining multiple orderBy to order multiple column
->get();
I dont even need to use Subquery!! But how do we actually only add the "active" discount?, just need to modify the withSum a bit:
Book::withCount('reviews')
->withSum(
[
'discounts' => function($query) {
$query->where('start_date', '<=', Carbon::now())
->where('end_date', '>=', Carbon::now())
}
],
'discount_price'
)
->addSelect(
DB::raw('final_price AS (books.price - discounts_sum_discount_price)')
)
->orderBy('reviews_count', 'asc') // =you can specify ascending or descending
->orderBy('final_price', 'desc') //in laravel chaining multiple orderBy to order multiple column
->get();
and it is done
What about pagination? just replace the get() method with paginate():
Book::withCount('reviews')
->withSum(['discounts' => function($query) {
$query->where('start_date', '<=', Carbon::now())->where('end_date', '>=', Carbon::now())
}],'discount_price')
->addSelect(DB::raw('final_price AS (books.price - discounts_sum_discount_price)')) //just format to be a bit cleaner, nothing had changed
->orderBy('reviews_count', 'asc')
->orderBy('final_price', 'desc')
->paginate(10); //10 books per page
WARNING: this is written with ELoquent ORM, not QueryBuilder, so you must define your relationship first

Nested Select in Eloquent

I want to recreate below SQL in Eloquent (Laravel 6 LTS)
I want to avoid DB::raw as I have logics behind the PartnerPrice::class (model)
SELECT *
FROM (SELECT *,
ROW_NUMBER ()
OVER (PARTITION BY group, TYPE
ORDER BY effective_at DESC, created_at DESC)
r
FROM partner_prices
WHERE group = 'premier'
and partner_id = 8
AND TYPE = 'premium'
AND effective_at <= '2020-10-31') a
WHERE r = 1
ORDER BY group;
Here's my working inner query.
I just need help wrapping this with another select and add a where('r', 1)
$sub = PartnerPrice::select('*')
->selectRaw('ROW_NUMBER () OVER (PARTITION BY mccmnc, TYPE ORDER BY effective_at DESC, created_at DESC) r')
->where('type', $type)
->where('partner_id', $partnerId)
->where('group', $group)
->where('effective_at', '<=', now()->subMonth()->lastOfMonth())
->get();
You could use the toSql method with mergeBindings like this
$queryBuilder = PartnerPrice::select('*')
->selectRaw('ROW_NUMBER () OVER (PARTITION BY mccmnc, TYPE ORDER BY effective_at DESC, created_at DESC) r')
->where('type', $type)
->where('partner_id', $partnerId)
->where('group', $group)
->where('effective_at', '<=', now()->subMonth()->lastOfMonth());
$result = DB::table(DB::raw('(' . $queryBuilder->toSql() . ') as a'))
->mergeBindings($queryBuilder->getQuery())
->where('a.r', 1)
->get();
Note that I omitted the get() on the first builder
Also I think you can use
DB::select('*')
->fromSub($queryBuilder, 'a')
->where('a.r', 1)
->get();
But never used it. try this too.

Laravel join two tables with where that compares two date time columns

I have two tables that I want to run joint query on using the value of two date time columns, I have products table and sync_status tables, I want to return all products with updated_at date time greater than synced_at date time.
DB::table('products')
->join('sync_statuses', function ($join) {
$join->on('products.product_number', '=', 'sync_statuses.product_number')
->where('products.updated_at', '>', 'sync_statuses.synced_at')
->whereNull('products.deleted_at');
})
->select('products.product_number');
This SQL represents what I am trying to achieve using Eloquent:
SELECT products.product_number
FROM products
JOIN push_statuses
ON products.product_number = statuses.product_number
AND (
statuses.synced_at IS NULL
OR products.updated_at > statuses.synced_at
)
You have to use on() instead of where() to compare columns:
->on('products.updated_at', '>', 'sync_statuses.synced_at')
This worked for me:
DB::table('products')
->join('statuses', function ($join) {
$join->on('products.product_number', '=', 'statuses.product_number')
->where(DB::raw('products.updated_at'), '>=', DB::raw('statuses.synced_at'))
->whereNull('products.deleted_at');
})->select('products.product_number');
I needed to use DB::raw('products.updated_at') to reference each date time column in the where() clause.

Query builder isn't execute where = on another column

Sorry, my question title isn't very clear but I couldn't figure a way to word this question.
I have the following query builder code.
return self::select(DB::raw('sum(user_points.points) AS points, users.id AS user_id, users.username, users.avatar, users.firstname, users.lastname'))
->join('users', 'users.id', '=', 'user_points.user_id')
$query->where('user_points.artist_id', 0)->orWhere('user_points.artist_id', 'users.favourite_artist_id');
})
->where('user_points.created_at', '>=', $startDate)
->where('user_points.created_at', '<=', $endDate)
->groupBy('users.id')
->orderBy('points', 'DESC')
->orderBy('user_id', 'ASC')
->simplePaginate(100);
It runs ok but is ignore the inner where query, specifically it's ignoring part of this;
$query->where('user_points.artist_id', 0)->orWhere('user_points.artist_id', 'users.favourite_artist_id');
})
It's matching 'user_points.artist_id = 0', but it's not matching the 'user_points.artist_id = users.favourite_artist_id', presumable it's got something to do with the way it's handling the bindings? But I can't seem to find a way to get it to work.
The complete query should end up like this;
SELECT SUM(user_points.points) AS points, users.id AS user_id, users.username, users.avatar, users.firstname, users.lastname
FROM user_points
INNER JOIN users ON users.id = user_points.user_id
WHERE (user_points.artist_id = 0 OR user_points.artist_id = users.favourite_artist_id)
AND user_points.created_at >= '$startDate' AND user_points.created_at <= '$endDate'
GROUP BY user_id
ORDER BY points DESC, user_id ASC
I updated the query builder code to this.
return self::select(DB::raw('sum(user_points.points) AS points, users.id AS user_id, users.username, users.avatar, users.firstname, users.lastname'))
->join('users', 'users.id', '=', 'user_points.user_id')
->where(function($query) {
$query->Where('user_points.artist_id', 0)->orWhere(DB::raw('user_points.artist_id = users.favourite_artist_id'));
})
->where('user_points.created_at', '>=', $startDate)
->where('user_points.created_at', '<=', $endDate)
->groupBy('users.id')
->orderBy('points', 'DESC')
->orderBy('user_id', 'ASC')
->simplePaginate(100);
That didn't work as the final query ended up looking like this.
select sum(user_points.points) AS points, users.id AS user_id, users.username, users.avatar, users.firstname, users.lastname
from `user_points` inner join `users` on `users`.`id` = `user_points`.`user_id` where (`user_points`.`artist_id` = ? or user_points.artist_id = users.favourite_artist_id is null)
and `user_points`.`created_at` >= ?
and `user_points`.`created_at` <= ?
group by `users`.`id`
order by `points` desc, `user_id` asc
You need to add the second where (or) as a raw query. The where method will add the second column as a value so you are actually trying the following:
user_points.artist_id = 'users.favourite_artist_id'
Try the following:
whereRaw("user_points.artist_id = users.favourite_artist_id")

Laravel ordering results of a left join

I am trying to replicate the below SQL in Laravels Eloquent query builder.
select a.name, b.note from projects a
left join (select note, project_id from projectnotes order by created_at desc) as b on (b.project_id = a.id)
where projectphase_id = 10 group by a.id;
So far I have:
$projects = Project::leftjoin('projectnotes', function($join)
{
$join->on('projectnotes.project_id', '=', 'projects.id');
})
->where('projectphase_id', '=', '10')
->select(array('projects.*', 'projectnotes.note as note'))
->groupBy('projects.id')
->get()
which works for everything except getting the most recent projectnotes, it just returns the first one entered into the projectnotes table for each project.
I need to get the order by 'created_at' desc into the left join but I don't know how to achieve this.
Any help would be much appreciated.
Your subquery is unnecessary and is just making the entire thing inefficient. To be sure though, make sure this query returns the same results...
SELECT
projects.name,
notes.note
FROM
projects
LEFT JOIN
projectnotes notes on projects.id = notes.project_id
WHERE
projects.projectphase_id = 10
ORDER BY
notes.created_at desc
If it does, that query translated to the query builder looks like this...
$projects = DB::table('projects')
->select('projects.name', 'notes.note')
->join('projectnotes as notes', 'projects.id', '=', 'notes.project_id', 'left')
->where('projects.projectphase_id', '=', '10')
->orderBy('notes.created_at', 'desc')
->get();

Resources