Laravel: Sorting a column in the Left Join - laravel

Lets say I have a simple table of users
id | userName
3 Michael
4 Mike
5 George
And another table of their cars and prices
id | belongsToUser | carPrice
1 4 5000
2 4 6000
3 4 8000
I would like to do a left join that would return the highest or lowest carPrice
At the moment, the query would return the last/first instance of that users carPrice.
I've tried entering the orderBy in various join queries but to no avail.
I have a helper function that would return the highest/lowest price on demand but I'm not sure how that would fit within this query as I would like to use laravels inbuilt paginate

This is the aggregate problem so here is the solvation:
DB::table('users')
->leftJoin('carPrices', 'belongsToUser', '=', 'users.id')
->select('users.*', DB::raw('MAX(carPrice) as highestCarPrice'), DB::raw('MIN(carPrice) as lowestCarPrice'))
->groupBy('users.id')
->get();

Related

Laravel join 3 table and select raw sum

I have a 3 tables in Laravel project
First table "offers"
id
client
numer_offer
id_user
1
123
211/2022
11
2.
145
212/2022
23
Second table "clients"
id
name
adres
123
Mark
211/2022
145
Ben
212/2022.
A the last table "offer_items"
id
id_offer
product
amount
1
2
bags
14.56
2
2
bags2
16.50
And have a query:
$id_user = '11';
$offers = Offer::join('clients', 'clients.id', '=', 'offers.client')
->join('offer_items','.offer_items.id_offer', '=', 'offers.id')
->selectRaw(' sum(offer_items.amount) as suma, clients.name, offers.*')
->where('offers.id_user', $id_user)
->groupBy('offer_items.id_offer')
->Orderby('offers.id_offer')
->get();
the query works fine if I have a record in "offer_items", but if I have no record in the table, nothing shows, and I would like everything to be displayed and amount = 0.
any idea because yesterday I was up all day :(
use leftJoin instead of join at joining with offer_items, to retrieve data whether has records on offer_items or not , also i added IFNULL to treat null as 0
$id_user = '11';
$offers = Offer::join('clients', 'clients.id', '=', 'offers.client')
->leftJoin('offer_items','offer_items.id_offer', '=', 'offers.id')
->selectRaw(' sum(IFNULL(offer_items.amount,0)) as suma, clients.name, offers.*')
->where('offers.id_user', $id_user)
->groupBy('offers.id')
->Orderby('offers.id')
->get();
more details about different type of join , Mysql IFNULL

How can I do join with whereDoesntHave in Laravel

I'm trying to run a query in my Laravel 8.0 application. I've a problem doing join (circular) using the concept of whereDoesntHave() in Laravel. Since I'm using DB::table('...') and it has other conditions implemented, I'm having a brands table, projects table and project_associate_brand table, brand is in many-to-many relation with project, I want to find out the project counts in which brand and projects are not associated with each other, I'm trying to join the table twice one for getting projects associated with and other one to remove where it is not in relationship, so my query looks like:
$innerQuery = DB::table('brands as b')
->join('project_associate_brand as pabdoesnthave', 'b.id', '=', 'pabdoesnthave.brand_id')
->join('project_associate_brand as pab', function ($join) {
$join->on('b.id', '!=', 'pab.brand_id')
->where('pabdoesnthave.project_id', '!=', 'pab.project_id');
})
->join('projects as p', function ($join) {
$join->on('pab.project_id', '=', 'p.id')
->where('pabdoesnthave.project_id', '!=', 'p.id');
})
->select(DB::raw('b.id, b.title, COUNT(DISTINCT p.id) as projects_count'))
->whereNull('b.deleted_at')
->whereNull('p.deleted_at')
->groupByRaw('b.id');
return ProductBrandFilterResource::collection(DB::query()->fromSub($innerQuery, 't')
->select(DB::raw("
id,
`title`,
projects_count
"))
->orderBy('title', 'asc')
->groupBy('t.id')->get());
I'm unable get the desired results. To explain better I've table porject_associate_brand as:
brand_id project_id
1 1
1 2
1 3
2 1
2 3
If I want to get the list of brand where projects are not associated I get
id title project_count
1 ABC 3
2 PQR 3
For id 2 it is giving me 3 but actually it should be 1 as result.
Any better approach is appreciated. Thanks

Laravel Sum transactions amount grouped by category

I'm trying to sum all transactions and group these records by category with the summed value of transactions for a particular month.
Example (Current month):
Groceries => 500
Subscriptions => 100
Coffee => 0
But the current code I have only groups categories when a transaction has occurred for that month and will return the following:
Groceries => 500
Subscriptions => 100
Here are my the following database tables:
Transactions table:
id
date
category_id
amount
1
2021-01-01
1
200
2
2021-01-01
1
300
3
2021-01-01
2
100
3
2020-06-06
3
100
Categories table:
id
display_name
description
1
Groceries
Food
2
Subscriptions
Subscriptions
3
Coffee
Coffee
And my code that selects transactions for the current month but doesn't return a full list of categories even if there are no transactions.
$currentMonth = date('m');
$grouped = DB::table('categories')
->join('transactions', function ($join) use ($currentMonth) {
$join->on('transactions.category_id', '=', 'categories.id')
->whereRaw('MONTH(date) = ?',[$currentMonth]);
})
->groupBy('categories.display_name')
->selectRaw('sum(transactions.amount) as sum, display_name as name')
->pluck('sum', 'name');
Move the criteria in the WHERE clause to the ON clause of the join, and use a left join:
$grouped = DB::table('categories c')
->leftJoin('transactions t', function ($join) {
$join->on('t.category_id', '=', 'c.id')
$join->on(DB::raw('MONTH(t.date) = ?'))
})
->groupBy('c.display_name')
->selectRaw('SUM(t.amount) AS sum, c.display_name AS name')
->setBindings([$currentMonth])
->pluck('sum', 'name');
The raw MySQL query I am suggesting here is:
SELECT
c.display_name,
SUM(t.amount) AS sum
FROM categories c
LEFT JOIN transactions t
ON c.id = t.category_id AND
MONTH(t.date) = ?
GROUP BY
c.display_name;
The structure used above in the left join is critical here, because it ensures that every category on the left side of the join would appear in the result set, even if it have no matching transactions for a given month.

laravel group by fetch latest record from table

I have one table where I want to retrieve data group by the user but I want the latest entry in result how can I do that by using eloquent.
here is an eloquent query I am using.
Product::whereNotNull('user_id')
->orderBy('id','desc')
->groupBy('user_id')
->get();
here is my table
Id Name user_id
-------------------------
1 A 1
2 b 1
3 c 2
4 d 2
5 e 3
6 f 3
result my query is giving me
Id Name user_id
-------------------------
1 A 1
3 c 2
5 e 3
result i want
Id Name user_id
-------------------------
2 b 1
4 d 2
6 f 3
Product::whereRaw('id IN (select MAX(id) FROM products GROUP BY user_id)')
->whereNotNull('user_id')
->orderBy('id','desc')
->get();
You will need a nested query for that, i don't think you can avoid it but this solution should work.
GroupBy happens before OrderBy so you have to get your last record before you do your ordering
Try this :
$subquery = Product::orderBy('id','DESC');
$products = DB::table(DB::raw("({$subquery->toSql()}) as subquery"))
->whereNotNull('user_id')
->groupBy('user_id')
->get();
2nd way : use unique() method in collection (The unique method returns all of the unique models in the collection):
$products = Product::whereNotNull('user_id')
->orderBy('id','desc')
->get()
->unique('user_id');
Check out Laravel Docs
latest / oldest
The latest and oldest methods allow you to easily order results by date. By default, result will be ordered by the created_at column. Or, you may pass the column name that you wish to sort by:
$product = Product::latest()->first();

Laravel eloquent get the latest rows of grouped rows

Using Eloquent, trying to find a way to get the latest rows of every row grouped by: exchange, base, quote
Data
exchange base quote price value created_at
bittrex BTC USD 10000 10000 2018-01-05
bittrex BTC USD 9000 9000 2018-01-01
poloniex BTC USD 10001 10001 2018-01-05
poloniex BTC USD 9000 9000 2018-01-01
binance BTC USD 10002 10002 2018-01-05
binance BTC USD 9000 9000 2018-01-01
binance ETH USD 800 800 2018-01-05
binance ETH USD 700 700 2018-01-01
Result:
bittrex BTC USD 10000 10000 2018-01-05
poloniex BTC USD 10001 10001 2018-01-05
binance BTC USD 10002 10002 2018-01-05
binance ETH USD 800 800 2018-01-05
UPDATE
I went with #Cryode solution, raw SQL instead of Eloquent (if anyone can come up with one Eloquent query to replicate the results of the query below, feel free to post).
I've also changed the structure of the table to add id (increments) as the primary key. I also added the following index $table->index(['exchange', 'base', 'quote', 'created_at']);
Here is the solution:
$currencies = DB::select('SELECT *
FROM (
SELECT DISTINCT exchange, base, quote
FROM tickers
) AS t1
JOIN tickers
ON tickers.id =
(
SELECT id
FROM tickers AS t2
WHERE t2.exchange = t1.exchange
AND t2.base = t1.base
AND t2.quote = t1.quote
ORDER BY created_at DESC
LIMIT 1
)
');
Thanks
Let's first determine what this SQL query would actually look like.
This DBA answer provides some great insight into the "greatest-n-per-group" problem, as well as PostgreSQL and MySQL examples. Inspired by this answer, here's what I've come up with for your single table (assuming MySQL as your DB):
SELECT ticker.*
FROM (
SELECT DISTINCT exchange, base, quote
FROM ticker
) AS exchanges
JOIN ticker
ON ticker.id =
(
SELECT id
FROM ticker
WHERE ticker.exchange = exchanges.exchange
AND ticker.base = exchanges.base
AND ticker.quote = exchanges.quote
ORDER BY created_at DESC
LIMIT 1
);
Oh dear. Getting that into Laravel-speak doesn't look easy.
Personally, I wouldn't even try. Complicated SQL queries are just that because they take advantage of your database to do reporting, data gathering, etc. Trying to shove this into a query builder is tedious and likely comes with little to no benefit.
That said, if you'd like to achieve the same result in a simple way using Laravel's query builder and Eloquent, here's an option:
// Get the unique sets of tickers we need to fetch.
$exchanges = DB::table('ticker')
->select('exchange, base, quote')
->distinct()
->get();
// Create an empty collection to hold our latest ticker rows,
// because we're going to fetch them one at a time. This could be
// an array or however you want to hold the results.
$latest = new Collection();
foreach ($exchanges as $exchange) {
$latest->add(
// Find each group's latest row using Eloquent + standard modifiers.
Ticker::where([
'exchange' => $exchange->exchange,
'base' => $exchange->base,
'quote' => $exchange->quote,
])
->latest()
->first()
);
}
Pros: You can use the query builder and Eloquent abstractions; allows you to maintain your Ticker model which may have additional logic needed during the request.
Cons: Requires multiple queries.
Another option could be to use a MySQL View that encapsulates the complicated query, and create a separate Eloquent model which would fetch from that view. That way, your app code could be as simple as TickerLatest::all().
You may pass multiple arguments to the groupBy method to group by multiple columns
Please refer to documentation https://laravel.com/docs/5.6/queries#ordering-grouping-limit-and-offset
$users = DB::table('users')
->groupBy('first_name', 'status')
->having('account_id', '>', 100)
->get();
Since Laravel 5.6.17 you can use joinSub() so a possible Eloqunish solution could maybe be something like this:
Group and find the ticket with the last date
$latest = Ticker::select('exchange', 'base', 'quote', DB::raw('MAX(created_at) as created_at'))
->groupBy('exchange', 'base', 'quote');
And join the latest of each group again all records with joinSub()
$posts = DB::table('tickets')
->joinSub($latest, 'latest_tickets', function ($join) {
$join->on('tickets.exchange', '=', 'latest_tickets.exchange')
->on('tickets.base', '=', 'latest_tickets.base')
->on('tickets.quote', '=', 'latest_tickets.quote')
->on('tickets.created_at', '=', 'latest_posts. created_at');
})->get();
You can fetch the latest rows first then group the collection later.
$items = Ticker::latest()->get();
// exchange, base, quote
$groupedByExchange = $items->groupBy('exchange');
$groupedByBase = $items->groupBy('base');
$groupedByQoute = $items->groupBy('qoute');
UPDATE:
You can get the single item by each group by simple adding ->first() after the groupBy() function.
$latestByExchange= Ticker::latest()->groupBy('exchange')->first(); // and so on
Here is another way to get latest record per group by using a self left join and this query can be easily transformed to laravel's query builder.
It doesn't require any specific version of laravel to work, it can work on older versions of laravel too
No need for N+1 queries (overhead) as suggested in other answer
In plain SQL it can be written as
select a.*
from tickers a
left join tickers b on a.exchange = b.exchange
and a.base = b.base
and a.quote = b.quote
and a.created_at < b.created_at
where b.created_at is null
And in query builder it would look like
DB::table('tickers as a')
->select('a.*')
->leftJoin('tickers as b', function ($join) {
$join->on('a.exchange', '=', 'b.exchange')
->whereRaw(DB::raw('a.base = b.base'))
->whereRaw(DB::raw('a.quote = b.quote'))
->whereRaw(DB::raw('a.created_at < b.created_at'))
;
})
->whereNull('b.created_at')
->get();
Laravel Eloquent select all rows with max created_at
Or you use a correlated sub query to choose latest row
SQL
select a.*
from tickers a
where exists (
select 1
from tickers b
where a.exchange = b.exchange
and a.base = b.base
and a.quote = b.quote
group by b.exchange,b.base,b.quote
having max(b.created_at) = a.created_at
);
Query Builder
DB::table('tickers as a')
->whereExists(function ($query) {
$query->select(DB::raw(1))
->from('tickers as b')
->whereRaw(DB::raw('a.exchange = b.base'))
->whereRaw(DB::raw('a.base = b.base'))
->whereRaw(DB::raw('a.quote = b.quote'))
->groupBy(['b.exchange','b.base','b.quote'])
->havingRaw('max(b.created_at) = a.created_at')
;
})
->get();
DEMO

Resources