Laravel query builder strange result with join - laravel

I have a query
$orders = DB::table('orders')->where('user_id', $user->id)->get();
And this is what I have in view:
#foreach ($orders as $order)
{{ $order->id }}
#endforeach
It prints out 1 2 3, because the table has these three IDs.
But if I try to join, I get a rather unpredicted result.
$orders = DB::table('orders')->where('user_id', $user->id)->
leftJoin('status', 'orders.status_id', '=', 'status.id')
->get();
It gives 2 1 1. Result is the same with rightJoin() and join().
I thought this command would append row from status table to every corresponding row of orders table. Join on orders.status_id = status.id.
Can I get an intended result?

I have tried ->select('orders.*') but it did not change the result.
But still, I needed to alias everything out, as suggested in the comments here. So here is my final query.
$orders = User::find($user->id)->orders()->
select('orders.id as order_id', 'status.id as status_id',
'status.label as label', 'orders.ordered_at as ordered_at')
->leftJoin('status', 'orders.status_id', '=', 'status.id')
->get();
Probably this could be done in more pretty way, but this works.
Thank you who commented the post.
UPD: Now, I have fixed relations between my models and I can do this much simpler. As described here Laravel Many-to-one relationship
$orders = Order::with('status')->where('user_id', '=', $user->id)->get();

Related

Laravel JOIN with JSON string

I have two tables in Laravel, one is the comment table, and the second is the users table. In the comment table, I have this type of data.
For this comment table, I want to match the tags column's userid in JSON, so how can we join that userid with the user's table? here is what I tried, but that is not working as expected.
$messages = TopicComment::where('user_id', $currentUserId)
->join("users", "users.id", "=", "users.id")
->(function ($query) {
$query->whereJsonContains('tags.userid', users.id);
})
->
->get()->toArray();
with this package you can create a relation via a json field
https://github.com/staudenmeir/eloquent-json-relations
First, there seem to be a number of errors in your code.
Judging from the DB schema, there is no user_id column in your comments table and so, ::where('user_id', $currentUserId) will not work.
A similar issue occurs in your join statement. You're joining on "users.id","=","users.id" which is the same column in the same table.
There's no method called in the line with function($query). Ideally, should be a where clause.
Correct usage of whereJsonContains would be:
$query->whereJsonContains('tags', ['userid' => $currentUserId]);
Rouge arrow after that line.
And so, your final result after correcting the changes should look like:
use Illuminate\Support\Facades\DB;
...
$messages = TopicComment::join('users', 'users.id', DB::Raw("CAST(comments.tags->'$.userid' AS UNSIGNED)"))
->where(function ($query) use ($currentUserId) {
$query->whereJsonContains('tags', ['userid' => $currentUserId]);
})
->get()
->toArray();
I think the only way to extract is to use json tables.
DB::select(DB::raw("
SELECT document_types.*, jst.name
FROM document_types,
JSON_TABLE(settings, '$[*]' COLUMNS (
`userid` int(11) PATH '$.userid',
`name` varchar(255) PATH '$.name'
)) jst
inner join users on jst.userid = users.id WHERE users.id = :id"
,['id' => $currentUserId]))->get()
credit: https://dba.stackexchange.com/questions/306938/extract-json-object-inside-of-an-json-array-then-search-base-on-id
Unfortunately I don't have json functions on my mysql so the code will probably fail, maybe at least it'll help get you on the right track.
You can try this way...
eg:
<?php
$comments = DB::table('topic_comments')
->join('users', 'users.id', '=', 'topic_comments.tags->userid')
->select('topic_comments.*', 'users.name')
->get();

Laravel gives me a duplicate collection data

I want to sort my data by created date with this code :
$comments = Instacomment::where('postid',$query->post['id'])->orderBy('comment.created_at', 'ASC')->paginate(10);
These are the results and Laravel gives me duplicate data! I don't know what to do !
please try join table
$comments = Instacomment::join('comments', 'instacomment.id', '=', 'comments.postid')->where('postid',$query->post['id'])->orderBy('comment.created_at', 'ASC')->paginate(10);

Counting columns with if conidition in query

Counting only columns in which, Buy_Rate is more than Sell_Rate.
My query is not resulting as per expected, it is resulting me wrong.
$user_id = Auth::user()->id;
$losing_trades_count = FinalTrade::where('user_id', '=', $user_id)->where('buy_rate', '<', 'sell_rate')->get()->count();
Inverse: If sell_rate is more than buy_rate then only, count the columns.
You can use whereColumn
$losing_trades_count = FinalTrade::where('user_id', '=', $user_id)
->whereColumn('buy_rate', '<', 'sell_rate')
->count();
Also there is no need to call get() when you need count() from query builder
laravel eloquent where don't support comparing columns. so you need use raw SQL in order to compair two columns. you can do something like,
$user_id = Auth::user()->id;
$losing_trades_count = FinalTrade::where('user_id', '=', $user_id)->whereRaw('buy_rate < sell_rate')->get()->count();
hope this helps!

Querying related table data with Eloquent

i have a problem trying to get records from a model based on a related table.
I have two tables one called leads and one called recycle_logs, my app will basically send the same record in the leads table like once a month, and when it does so i'll store a new record in recycle_logs.
The problem is that i need to select all leads with a specific campaign_id value and that have a specific status that is different from invalid, so far so good, now the problem is i need to get them only if they don't have any recycleLogs associated with them OR if the last recycleLog associated with that particular lead is older than 30 days ago for instance.
What i currently have looks somewhat like this.
$leads = $this->leadModel->where(Lead::CAMPAIGN_ID, $this->campaignID)
->where(Lead::DUPLICATED, Lead::DUPLICATED_NO)
->where(Lead::LEAD_STATUS, "!=" ,Lead::LEAD_STATUS_INVALID)
->orderBy(Lead::CREATED_AT, 'desc')
->with(
['leadRecyclingLog' => function($query) {
$query->where(LeadRecyclingLog::CREATED_AT, '<', (new Carbon())->subDays($this->configRecyclingDays))
->orWhere(LeadRecyclingLog::ID, null);
}]
)
->get();
What exactly am i doing wrong? It always selects the same number of records regardless of me adding or removing recycleLogs
I've managed to get it done through a raw SQL query which i'll post below in case it helps anyone, i'd still like to know how to do it in Eloquent/Query Builder.
SELECT * FROM `leads` LEFT JOIN `lead_recycling_logs` ON `leads`.`guid` = `lead_recycling_logs`.`original_lead_guid` WHERE `leads`.`campaign_id` = :campaignID AND `leads`.`duplicated` = 0 AND `leads`.`lead_status` != :invalidStatus AND (`lead_recycling_logs`.`id` IS NULL OR `lead_recycling_logs`.`created_at` < :recyclingDate) ORDER BY `leads`.`created_at` DESC
Try this:
$leads = $this->leadModel->where(Lead::CAMPAIGN_ID, $this->campaignID)
->where(Lead::DUPLICATED, Lead::DUPLICATED_NO)
->where(Lead::LEAD_STATUS, "!=" ,Lead::LEAD_STATUS_INVALID)
->orderBy(Lead::CREATED_AT, 'desc')
->where(function($q) {
$q->whereHas('leadRecyclingLog', function($q) {
$q->where(LeadRecyclingLog::CREATED_AT, '<', (new Carbon())->subDays($this->configRecyclingDays));
})
->orWhereHas('leadRecyclingLog', '<', 1); // Where count of the relationship is smaller than 1
})->get();
I assumed the first part of the query is working well (up until the relationship).
What you're looking for is ->whereHas(relationship), not ->with(relationship). ->with(relationship) will attach the associated results to the original model (the query for the original model will not be affected by ->with()). ->whereHas(relationship) filters the original model by the condition.
Got it to work through #devk 's help
$leads = $this->leadModel->where(Lead::CAMPAIGN_ID, $this->campaignID)
->where(Lead::DUPLICATED, Lead::DUPLICATED_NO)
->where(Lead::LEAD_STATUS, "!=" ,Lead::LEAD_STATUS_INVALID)
->orderBy(Lead::CREATED_AT, 'desc')
->where(function($q) {
$q->whereHas('leadRecyclingLog', function($q) {
$q->where(LeadRecyclingLog::CREATED_AT, '<', (new Carbon())->subDays($this->configRecyclingDays));
})
->doesntHave('leadRecyclingLog', 'or');
})->get();

Laravel Eloquent groupBy() AND also return count of each group

I have a table that contains, amongst other columns, a column of browser versions. And I simply want to know from the record-set, how many of each type of browser there are. So, I need to end up with something like this: Total Records: 10; Internet Explorer 8: 2; Chrome 25: 4; Firefox 20: 4. (All adding up to 10)
Here's my two pence:
$user_info = Usermeta::groupBy('browser')->get();
Of course that just contains the 3 browsers and not the number of each. How can I do this?
This is working for me:
$user_info = DB::table('usermetas')
->select('browser', DB::raw('count(*) as total'))
->groupBy('browser')
->get();
This works for me (Laravel 5.1):
$user_info = Usermeta::groupBy('browser')->select('browser', DB::raw('count(*) as total'))->get();
Thanks Antonio,
I've just added the lists command at the end so it will only return one array with key and count:
Laravel 4
$user_info = DB::table('usermetas')
->select('browser', DB::raw('count(*) as total'))
->groupBy('browser')
->lists('total','browser');
Laravel 5.1
$user_info = DB::table('usermetas')
->select('browser', DB::raw('count(*) as total'))
->groupBy('browser')
->lists('total','browser')->all();
Laravel 5.2+
$user_info = DB::table('usermetas')
->select('browser', DB::raw('count(*) as total'))
->groupBy('browser')
->pluck('total','browser');
If you want to get collection, groupBy and count:
$collection = ModelName::groupBy('group_id')
->selectRaw('count(*) as total, group_id')
->get();
Cheers!
Open config/database.php
Find strict key inside mysql connection settings
Set the value to false
Works that way as well, a bit more tidy.
getQuery() just returns the underlying builder, which already contains the table reference.
$browser_total_raw = DB::raw('count(*) as total');
$user_info = Usermeta::getQuery()
->select('browser', $browser_total_raw)
->groupBy('browser')
->pluck('total','browser');
Try with this
->groupBy('state_id','locality')
->havingRaw('count > 1 ')
->having('items.name','LIKE',"%$keyword%")
->orHavingRaw('brand LIKE ?',array("%$keyword%"))
$post = Post::select(DB::raw('count(*) as user_count, category_id'))
->groupBy('category_id')
->get();
This is an example which results count of post by category.
Laravel Version 8
Removed the dependency of DB
$counts = Model::whereIn('agent_id', $agents)
->orderBy('total', 'asc')
->selectRaw('agent_id, count(*) as total')
->groupBy('agent_id')
->pluck('total','agent_id')->all();
Another way would be this:
$data = Usermeta::orderBy('browser')->selectRaw('browser, count(*) as total')->get()
Since this is the top result when i search for eloquent count with groupby returns only first
using "illuminate/database": "^9.38" in composer. so "should" be the latest at the time of this post
I honestly have no idea why they think that returning the first record is the right option.. ie
IMHO the current implementation doesn't make sense for queries including groupBy statements.
Why should be N_1 be the "right" result?
Since #taylorotwell rightly pointed out some performance issues with counting the subquery results, why don't we fix that on the php side, by checking if there are any group statements, and if so, performing a N_1 + N_2 + .... + N_M ?
https://github.com/laravel/ideas/issues/1693#issuecomment-621167890
Wrapping the query and doing a count seems to work for me
$records = ...
$record_count = DB::table( "fake" );
$record_count->fromSub($records->select(DB::raw(1)),"query");
$record_count->count();
create a "fake" query builder
add a sub "from" from (...) query set the "sub query" to select 1 instead of returning huge column data. not sure if this is needed but in my mind it seems like a good idea
do the normal ->count()
returns the "expected" result since it executes:
select count(*) as aggregate from (select 1 from ... group by ...) as `query`
The sub is the "query" that $records would normaly execute
My use-case is for pagination (not using laravel). so i get the record count then pass it to the paginator then call ->forPage()->get()
https://github.com/laravel/framework/issues/44081#issuecomment-1301816710
Laravel Eloquent query that uses the GROUP BY clause with advanced aggregation functions and conditional statements.
GroupBy on one column.
$data = Employee::select(
'department',
DB::raw('SUM(salary) as total_salary'),
DB::raw('COUNT(*) as total_employees'),
DB::raw('SUM(IF(bonus > 13000, 1, 0)) as employees_with_bonus')
)
->groupBy('department')
->havingRaw('total_employees > 5 AND total_salary > 10000')
->orHavingRaw('department_rank LIKE ?', array("%$keyword%"))
->get();
In the above query, if the bonus is greater than 13000, the IF function returns 1 otherwise it returns 0.
GroupBy on two columns: The groupBy method takes multiple arguments
$data = Employee::select(
'department',
'location',
DB::raw('SUM(salary) as total_salary'),
DB::raw('COUNT(*) as total_employees'),
DB::raw('SUM(IF(bonus > 1000, 1, 0)) as employees_with_bonus')
)
->groupBy('department', 'location')
->havingRaw('total_employees > 5 AND total_salary > 10000')
->get();
GroupBy and JOIN: Laravel Eloquent query that joins two tables and uses grouping and aggregate functions. (inner join)
$data = Employee::select(
'employees.department',
'employees.location',
DB::raw('SUM(employees.salary) as total_salary'),
DB::raw('COUNT(*) as total_employees'),
DB::raw('SUM(IF(employees.bonus > 1000, 1, 0)) as employees_with_bonus')
)
->join('departments', 'employees.department', '=', 'departments.name')
->groupBy('employees.department', 'employees.location')
->havingRaw('total_employees > 5 AND total_salary > 10000')
->get();
Raw MySQL Query:
SELECT
department,
SUM(salary) AS total_salary,
COUNT(*) AS total_employees,
SUM(IF(bonus > 1000, 1, 0)) AS employees_with_bonus
FROM
employees
GROUP BY
department
HAVING
total_employees > 5 AND total_salary > 13000;
If you want to get sorted data use this also
$category_id = Post::orderBy('count', 'desc')
->select(DB::raw('category_id,count(*) as count'))
->groupBy('category_id')
->get();
In Laravel 8 you can use countBy() to get the total count of a group.
Check the documentation on the same.
https://laravel.com/docs/8.x/collections#method-countBy
Simple solution(tested with Laravel 9 and Spatie/Permissions).
Controller:
//Get permissions group by guard name(3 in my case: web, admin and api)
$permissions = Permission::get()->groupBy('guard_name');
View:
#foreach($permissions as $guard => $perm)
<div class="form-group">
<label for="permission">Permissions ({{ ucfirst($guard) }}) {{ count($perm) }}</label>
<select name="permission[]" id="permission" class="form-control #error('permission') is-invalid #enderror" multiple>
#foreach($perm as $value)
<option value="{{ $value->id }}">{{ $value->name }}</option>
#endforeach
</select>
#error('permission')
<div class="invalid-feedback">
{{ $message }}
</div>
#enderror
</div>
#endforeach
Here is a more Laravel way to handle group by without the need to use raw statements.
$sources = $sources->where('age','>', 31)->groupBy('age');
$output = null;
foreach($sources as $key => $source) {
foreach($source as $item) {
//get each item in the group
}
$output[$key] = $source->count();
}

Resources