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.
Related
I have the following function that lists the Leads and the pages, I am asked to sort it by the activities but I have the problem that this is from another table if I do it in SQL mode I get the result, but with the actual writing of the code I don't get it.
This is my query, it has join with contacts and a left join with activities, the problem is that I only need to be able to sort it by activities.
SELECT l.id,l.status_id,l.user_id,l.created_at,l.ticket_id,l.last_lead, c.full_name, COUNT(a.lead_id) as total FROM `leads` l join contacts c on l.contact_id = c.id left join activities a on a.lead_id =l.id where active=1 and status_id=7 and project_id in (8,9,10,11) GROUP by l.id order by total desc;
And this is my eloquent query:
Order by comments.
if ($sortField == 'last_comment') {
$user = User::withRole(['salesman'])->pluck('id');
$sortField = Comment::select('created_at')
->whereColumn('comments.lead_id','leads.id')
->where('comments.user_id',implode(',',$user->toArray()))
->latest()
->take(1);
}
$leads_ = $this->leads
->openedStage() // where status_id = 7
->ticketActive() // where active = 1
->searchTickets($projects)
->orderBy($sortField,'desc')
->paginate((int)$per_page);
Where the following methods mean:
**openedStage()**: where status_id = 7
**ticketActive()**: where active = 1
**searchTickets($projects):
whereHas('contacts', function ($q) use ($projects){
$q->whereIn('project_id',$projects->toArray());
});
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
I have these models
I want to make a query that shows me all the products whose stock quantity> 0 and that does not repeat the products.
My query:
$stock_products_limit = Stock::distinct('product_id')->where('quantity', '!=', 0)->get();
This would be much easier using a size chart relating it to stocks ... but for now I don't have it
I need the model to return me, and then do a foreach:
#foreach($stock_products_limit as $stock_product)
#foreach($stock_product->product->product_images as $i=>$product_image)
...
#endforeach
...
#enforeach
In my models I have the hasMany and belongsTo relations made
How could I make the query? I've been trying the distinct, group by ... but nothing works for me. It only removes the ones with quantity 0 and repeats the product ID ...
Example of the query I want:
SELECT DISTINCT(stocks.product_id)
FROM stocks
INNER JOIN products ON stocks.product_id = products.id
WHERE quantity != 0
ORDER BY product_id
LIMIT 10;
Another example query (but LIMIT doesn't work with IN)
SELECT * from products where id in (SELECT DISTINCT(product_id)
FROM stocks
INNER JOIN products ON stocks.product_id = products.id
WHERE quantity != 0
ORDER BY product_id)
Instead of making the Stock model as the starting point, you might want to use the Product model. Then you don't even have to think about using DISTINCT. Let's use whereHas
return Product::whereHas('stocks', function ($query) {
$query->where('quantity', '>', 0);
})
->limit(10)
->get();
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
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();