Laravel eloquent query order by customized field - laravel

I have this raw mysql query
SELECT campaign_influencers.*,
CASE influencer_status
WHEN 'PENDING' THEN 0
WHEN 'ACTIVE' THEN 1
WHEN 'LIVE' THEN 2
WHEN 'ACCEPTED' THEN 3
ELSE 5
end AS order_field
FROM campaign_influencers
WHERE campaign_id = 612
ORDER BY order_field
How can this be converted to eloquent query builder?
This is what I have done so far.
$sql = "SELECT campaign_influencers.*,
CASE influencer_status
WHEN 'PENDING' THEN 3
WHEN 'ACTIVE' THEN 1
WHEN 'LIVE' THEN 2
WHEN 'ACCEPTED' THEN 0
ELSE 5
end AS order_field
FROM campaign_influencers
WHERE campaign_id = :campaignId
ORDER BY order_field";
$campaignInfluencers = DB::select( DB::raw($sql), array(
'campaignId' => $id
));
Only issues is that the relationship object is gone.
foreach ($campaignInfluencers as $campaignInfluencer) {
$user = $campaignInfluencer->user; //will not work
}

Use this:
CampaignInfluencer::select('*', DB::raw('CAST influencer_status ... AS order_field'))
->where('campaign_id', $campaign_id)
->orderBy('order_field')
->get();

You could shorten your query by using FIND_IN_SET
SELECT campaign_influencers.*,
FIND_IN_SET(influencer_status,'ACCEPTED,LIVE,ACTIVE,PENDING') AS order_field
FROM campaign_influencers
WHERE campaign_id = 612
ORDER BY order_field DESC
Using eloquent as #Jonas Staudenmeir already suggested you need to use raw method for raw sql expressions
YourModel::select('*', DB::raw("FIND_IN_SET(influencer_status,'ACCEPTED,LIVE,ACTIVE,PENDING') AS order_field"))
->where('campaign_id', 612)
->orderBy('order_field', 'desc')
->get();

Related

Refactor Laravel Query

I have a query that I have built, and I am trying to understand how I can achieve the same thing but in one single query. I am fairly new to Laravel and learning. Anyway someone could help me understand how I can achieve what I am after?
$activePlayerRoster = array();
$pickupGames = DB::table('pickup_games')
->where('pickupDate', '>=', Carbon::now()->subDays(30)->format('m/d/Y'))
->orderBy('pickupDate', 'ASC')
->get();
foreach ($pickupGames as $games) {
foreach(DB::table('pickup_results')
->where('pickupRecordLocatorID', $games->recordLocatorID)
->get() as $activePlayers) {
$activePlayerRoster[] = $activePlayers->playerID;
$unique = array_unique($activePlayerRoster);
}
}
$activePlayerList = array();
foreach($unique as $playerID) {
$playerinfo = DB::table('players')
->select('player_name')
->where('player_id', $playerID)
->first();
$activePlayerList[] = $playerinfo;
}
return $activePlayerList;
pickup_games
checkSumID
pickupDate
startTime
endTime
gameDuration
winningTeam
recordLocatorID
pickupID
1546329808471
01/01/2019
08:03 am
08:53 am
50 Minute
2
f47ac0fc775cb5793-0a8a0-ad4789d4
216
pickup_results
id
checkSumID
playerID
team
gameResult
pickOrder
pickupRecordLocatorID
1
1535074728532
425336395712954388
1
Loss
0
be3532dbb7fee8bde-2213c-5c5ce710
First, you should try to write SQL query, and then convert it to Laravel's database code.
If performance is not critical for you, then it could be done in one query like this:
SELECT DISTINCT players.player_name FROM pickup_results
LEFT JOIN players ON players.player_id = pickup_results.playerID
WHERE EXISTS (
SELECT 1 FROM pickup_games
WHERE pickupDate >= DATE_FORMAT(SUBDATE(NOW(), INTERVAL 30 DAY), '%m/%d/%Y')
AND pickup_results.pickupRecordLocatorID = recordLocatorID
)
Here I'm assuming you know what you're doing with this dates comparison, because it looks weird to me.
Now, let's convert it to Laravel's code:
DB::table('pickup_results')
->select('players.player_name')->distinct()
->leftJoin('players', 'players.player_id', '=', 'pickup_results.playerID')
->whereExists(function ($query) {
$query->select(DB::raw(1))
->from('pickup_games')
->where('pickupDate', '>=', Carbon::now()->subDays(30)->format('m/d/Y'))
->whereRaw('pickup_results.pickupRecordLocatorID = recordLocatorID');
})
->get();
Basically, I would reduce the query to its SQL variant to get directly at its core.
The essence of the query is
select `x` FROM foo WHERE id IN (
select distinct bar.id from bar join baz on bar.id = baz.id);
This can be interpreted in Eloquent as:
$thirtyDaysAgo = Carbon::now()->subDays(30)->format('m/d/Y');
$playerIds = DB::table('pickup_games')
->select('pickup_games.player_id')
->join(
'pickup_results',
'pickup_results.pickupRecordLocatorID',
'pickup_games.recordLocatorID')
->where('pickupDate', '>=', $thirtyDaysAgo)
->orderBy('pickupDate', 'ASC')
->distinct('pickup_games.player_id');
$activePlayers = DB::table('players')
->select('player_name')
->whereIn('player_id', $playerIds);
//>>>$activePlayers->toSql();
//select "player_name" from "players" where "player_id" in (
// select distinct * from "pickup_games"
// inner join "pickup_results"
// on "pickup_results"."pickupRecordLocatorID" = "pickup_games"."recordLocatorID"
// where "pickupDate" >= ? order by "pickupDate" asc
//)
From the resulting query, it may be better to refactor the join as relationship between the Eloquent model for pickup_games and pickup_results. This will help to further simplify $playerIds.

Laravel 5.6: how to create a subquery using Query Builder

I would like to reproduce following mySQL query using Laravel query builder:
*SELECT SUM(scores) FROM (SELECT scores FROM player_games WHERE player_id = 1 ORDER BY id DESC LIMIT 2) scores
Any suggestions?
Here the solution:
$sub = playerGame::where('player_id',1)->where('scores','>',0)->limit(2)->orderBy('id','desc');
$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )
->mergeBindings($sub->getQuery())
->sum('scores');
return $count;
Use fromSub():
$sub = playerGame::select('scores')
->where('player_id', 1)
->where('scores', '>', 0)
->limit(2)
->orderBy('id','desc');
$sum = DB::query()->fromSub($sub, 'scores')->sum('scores');
$responce= DB::table('player_games')->where('player_id',1)->sum('amount');
dd(collect($responce)->sortByDesc('id')->take(2));
please cheack this one.....i try it's work....and add use DB;in the top of controller....

Yii2 subquery GROUP BY in active record

can I convert this query
"SELECT * FROM (SELECT * FROM blog_post ORDER BY data DESC) blog_post GROUP BY blog_id LIMIT 2"
into a Yii2 active record query?
Thx
Ms
Yes, you can do this.
Yii2 gave us a wonderful library support.
You can form custom sql query and pass this query in findBySql() like:
$sql = "Some query/nested query";
$result = ModelClass::findBySql($sql);
Visit Yii official documentation.
BlogPost::find()
->orderBy('data DESC')
->groupBy('blog_id')
->limit(2)
->all();
I suppose you can do in this way :
Ⅰ:create a subQuery where you select from.
$blogPostQuery = BlogPostModel::find()->orderBy(['data' => SORT_DESC]);
Ⅱ:Get activeRecord results. The from params is an exist Query.
$models = (new yii\db\Query)
->from(['blog_post ' => $blogPostQuery])
->groupBy(['blog_id'])
->limit(2)
->all();
ps:
see yii\db\query->from()-detail in Yii Api;
(public $this from ( $tables )) $tables can be either a string (e.g. 'user') or an array (e.g. ['user', 'profile']) specifying one or several table names··· or a sub-query or DB expression
Have a try!I hope it`s useful for you:))

Convert raw SQL to use Eloquent Query Builder

How can I convert the following complex SQL query to use the Eloquent query builder? I want to use methods such as join() and where(), get() etc.
The below query returns a list of locations along with counts for vouchers that have been redeemed.
select
a.location_name,
'' as dates,
a.places,
sum(netvalue155),
sum(netvalue135) from
(
select
l.id,
l.location_name,
b.places,
case when v.net_value = 155 then 1 else 0 end as netvalue155,
case when v.net_value = 135 then 1 else 0 end as netvalue135
from locations l
left join bookings b on l.id = b.location_id
left join vouchers v on b.voucher_code = v.voucher_code
) a
right join locations l on l.id = a.id
group by a.location_name
EDIT
I am trying the below code, which throws the error SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sub.id' in on clause
$subQuery = DB::table('locations')
->select(
'locations.id',
'locations.location_name',
DB::raw('"'.$request->get('dates').'" as dates'),
DB::raw('sum(bookings.id) as number'),
DB::raw('round(sum(bookings.final_price/1.2), 2) as paidbycard'),
DB::raw('case when bookings.voucher_value = 155 then round(sum(bookings.voucher_value/1.2), 2) else 0.00 end as voucher155'),
DB::raw('case when bookings.voucher_value = 135 then round(sum(bookings.voucher_value/1.2), 2) else 0.00 end as voucher135'),
DB::raw('case when bookings.transfer_fee = 10 then round(sum(bookings.transfer_fee/1.2), 2) else 0.00 end as transfer_fee'))
->leftJoin('bookings', 'locations.id', '=', 'bookings.location_id');
$meatBookQuery = DB::table('orders')->select(DB::raw('sum(orders_items.price) as total'))
->join('orders_items', 'orders.id', '=', 'orders_items.order_id')
->where('orders_items.item_name', 'The Meat Book');
$booking = DB::table(DB::raw("({$subQuery->toSql()}) as sub, ({$meatBookQuery->toSql()}) as meatBook"))
->mergeBindings($subQuery)
->mergeBindings($meatBookQuery)
->select('sub.location_name', 'sub.dates', 'sub.number', 'sub.paidbycard', 'sub.voucher155', 'sub.voucher135', 'sub.transfer_fee', DB::raw('round(sum(sub.voucher155 + sub.voucher135 + sub.transfer_fee + sub.paidbycard), 2) as total'), 'meatBook.total')
->leftJoin('locations', 'locations.id', '=', 'sub.id')
->leftJoin('bookings', 'bookings.location_id', '=', 'sub.id')
->groupBy('sub.location_name');
First of all
I often see people asking for how to rebuild a complex SQL query in Laravels Query Builder. But not every operation which is possible in SQL or MySQL is implemented as a function in Laravels Query Builder. This means you can't rebuild every SQL query in Query Builder without using raw SQL.
What does this mean for your SQL query?
Some things like sub queries (the from (select ...) part) and the case when ... part is not implemented in Query Builder. At least therefore you will have to use the raw expression with the DB::raw() function. I'm not sure about the sum() if this is already possible but you will surely find that in the docs.
Other things like joins are implemented as a function:
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.id', 'contacts.phone', 'orders.price')
->get();
see Laravel Documentation: Queries - Joins
And you can even mix up Query Builder functions with raw expressions:
$users = DB::table('users')
->select(DB::raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
see Laravel Documentation: Queries - Raw Expression
Example for a sub query:
$subQuery = DB::table('locations')
->leftJoin('bookings', 'locations.id', '=', 'bookings.location_id')
->leftJoin('vouchers', 'bookings.voucher_code', '=', 'vouchers.voucher_code')
->select('locations.id', 'locations.location_name', 'bookings.places');
$query = DB::table(DB::raw("({$subQuery->toSql()} as sub"))
->mergeBindings($subQuery)
->select(...)
->rightJoin(...)
->groupBy('sub.location_name')
->get();
So you can rebuild some parts of the query in Query Builder and use raw expressions wherever you need to.
To debug a query while you build it Laravels query logging function can be very helpful.

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