Laravel using eloquent Count with Having - php

I have two variables $customers (that holds all the rows) and $total that holds the total rows of the query.
I usually do the following query:
$customers = Customers::select
(
'customer.id',
'customer.name',
'customer.min_tolerance',
DB::raw('(SELECT MAX(tolerance) FROM customers_tolerances WHERE customer_id = customer.id) AS tolerance')
)
->from('customers AS customer')
->whereIn('customer.id', $request->customers);
$total = $customers->count();
$customers = $customers->limit($request->limit)
->offset($request->offset)
->get();
This works great. I get all the rows limited (usually 20 per page) plus the total rows.
My problem is that I added a having clause to my query, so it looks like this now:
$customers = Customers::select
(
'customer.id',
'customer.name',
'customer.min_tolerance',
DB::raw('(SELECT MAX(tolerance) FROM customers_tolerances WHERE customer_id = customer.id) AS tolerance')
)
->from('customers AS customer')
->whereIn('customer.id', $request->customers)
->havingRaw('tolerance >= customer.min_tolerance');
And the $count stopped working as it triggers an error:
Column not found: 1054 Unknown column 'tolerance' in 'having clause'
select count(*) as aggregate from customers as customer having tolerance >= customer.min_tolerance)
So how can I use count with having clause?

Solved.
Before creating this post I tried to create a subquery, as follow:
SELECT COUNT(*) FROM (SELECT ...)
But the slowness of the query was too much, so I tried to look for answers here. The slowness was due to the lack of index in tables.
By adding ALTER TABLE customers_tolerances ADD INDEX(customer_id); I'm now able to retrieve fast the total results.

Related

Laravel OrderBy by related table column

I have a table (A) that has a One to Many relation with another table (B).
I want to query Table A and eager load Table B with the Table A results - but I also want to sort Table A by a value in Table B.
I have tried using OrderBy in the query and also trying SortBy on the resultant collection but cannot get the Table A data to be sorted by the value found in Table B.
Example of what I have tried:
$query = ModelA::with("ModelB"])->get()->sortByDesc('ModelB.sortValue');
Keep in mind, I am only interested in the LATEST record from Table B. So I need to query Table A and sort by a value in the LATEST records of Table B.
How can I achieve this?
EDIT:
The below (as suggested by #ljubadr) works pretty close, but the issue is that there are many record in Table B which means that it doesn't reliably sort as it doesn't seem to sortby the latest records in Table B. Can I have the join return ONLY the latest record for each ID?
$query = ModelA::select('TableA.*')
->join('TableB', 'TableA.id', '=', 'TableB.col_id')
->groupBy('TableA.id')->orderBy('TableB.sortCol', 'desc')
->with(['x'])
->get();
EDIT 2:
#Neku80 answer has gotten me closest but it seems to not sort the column with the greatest accuracy.. I'm sorting a Decimal column and for the most part it is in order but in some places the items are out of order..
$latestTableB = ModelB::select(['TableA_id', 'sortByColumnName'], DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.sortByColumnName')
->get();
For example, the ordering is like:
0.0437
0.0389
0.0247 <-- -1
0.025 <-- +1
0.0127
When I delete all rows except for the 'latest' rows, then it orders correctly, so it still must be ordering with old data...
I have found a solution:
ModelA::select('TableA.*', 'TableB.sortByCol as sortByCol')
->leftJoin('TableB', function ($query) {
$query->on('TableB.TableA_id', '=', 'TableA.id')
->whereRaw('TableB.id IN (select MAX(a2.id) from TableB as a2 join TableA as u2 on u2.id = a2.TableA_id group by u2.id)');
})
->orderBy('TableB.sortByCol')
->get();
Another alternative to order is like this:
$users = User::orderBy(
Company::select('name')
->whereColumn('companies.user_id', 'users.id'),
'asc'
)->get();
Here we are ordering in asc order by company name field.
In this article it is explained in detail.
You can simply execute a left join query:
ModelA::query()->leftJoin('model_b_table', 'model_a_table.primary_key', '=', 'model_b_table.foreign_key')->orderBy('model_a_table.target_column')->get();
This should work if you only need TableB's ID and created_at columns:
$latestTableB = ModelB::select('TableA_id', DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.created_at')
->get();

WhereHas with WhereRaw on one to many relationship (Laravel)

I have an order table and an order_details table in my system.
Relationship between order table and order details table is one to many, means One order has many order details.
Now the problem is i am trying to filter the order with the quantity of items a that are stored in order_details table.
what i doing right know trying to access with whereHas
if ($request->has('quantity') && $request->quantity != null){
$query = $query->whereHas('orderDetails',function ($q) use ($request){
$q->whereRaw('SUM(Quantity) >= '.$request->quantity);
});
}
$orders = $query->orderBy('OrderID','desc')->get();
But it throws an error
General error: 1111 Invalid use of group function (SQL: select * from `orders` where `AddedToCart` = 0 and `PaymentSucceeded` = 1 and exists (select * from `order_details` where `orders`.`OrderID` = `order_details`.`OrderID` and SUM(Quantity) >= 12) order by `OrderID` desc)
I will be vary thankful if i get the solution
To be able to use sum function you need to group by data and as I see you are trying to group them by orderID.
An approach like this might help:
$ordersIDs = DB::table('orderDetails')
->groupBy('OrderID')
->havingRaw('SUM(Quantity)', '>=', 12)
->pluck('orderID')->toArray();
$orders = DB::table('orders')
->whereIn($ordersIDs)
->get();
The above code executes two SQL queries, you can mix them easily to make one.
Hope it helps.

GROUP BY clause and contains nonaggregated column when attempting to order by multiple records in relationship

Introduction
So the idea is that I have a players table, all we need to know about this table is that it has an id and username fields.
I also have a financials table, all we need to know about this table is that it references the players table through the player_id foreign key. It also has another two fields, staked and won.
A player can have many financials. So for example, a player could have 5 financials records associated with themselves.
The issue
I'm attempting to return a list of players which are sorted by their financial records. So for example, I'd like to retrieve the players who have made the most net revenue for the company. Here is a query I have written against Laravel's
Query Builder (Illuminate\Support\Facades\DB).
DB::table('financials AS f')
->select(['*', DB::raw('SUM(staked - (won)) AS net_revenue')])
->join('players AS p', function ($join) use ($param) {
$join->on('f.player_id', '=', 'p.id')
->where('p.id', 'like', '%' . $param . '%')
->orWhere('p.username', 'like', '%' . $param . '%');
})
->orderBy('net_revenue')
->groupBy(['f.id'])
->paginate($perPage);
The Error
SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #9 of
SELECT list is not in GROUP BY clause and contains nonaggregated column
'master.p.id' which is not functionally dependent on columns in GROUP BY
clause; this is incompatible with sql_mode=only_full_group_by (SQL: select
*, SUM(staked - (won)) AS net_revenue from `financials` as `f` inner join
`players` as `p` on `f`.`player_id` = `p`.`id` and `p`.`id` like %% or
`p`.`username` like %% group by `f`.`id` order by `net_revenue` asc limit 15
offset 0)
Could someone please point me in the right direction to fixing this issue? I'd greatly appreciate it.
EDIT (08/06/2017):
$query = DB::table('financials AS f1')
->select(['f1.id', 'f1.plays', 'f1.game_id', 'f1.staked', 'f1.won', 'f1.player_id', 'f1.date', 'f1.type', DB::raw('SUM(f1.staked - (f1.won)) AS net_revenue')])
->leftJoin('financials AS f2', 'f1.player_id', '=', 'f2.player_id')
->groupBy(['f1.id', 'f1.player_id', 'f1.game_id'])
->orderBy($orderBy)
->paginate($perPage);
So my query now looks like this, and it works, it generates the net_revenue for each record. However it returns me multiple records for the same player_id.
To elaborate, if I have two records for the same player, it calculates the net_revenue for both records and orders them from highest to lowest, however what I want is only a single record for the player containing the calculation of all net_revenue.
Change the order by as SUM(staked - (won)) instead of the field name.

query builder using 'whereNotIn' throws error

I'm trying to fetch records with an array of exceptions, here's what I tried (refer below)
$users_nowishlist = DB::table('employee')
->join('users', 'users.employee_id', '=', 'employee.employee_id')
->where('has_wishlist', '=', "0")
->whereNotIn('employee_id', ['MMMFLB003', 'guest_01', 'guest_02', 'guest_03'])
->where('employment_status', '=', 'ACTIVE')
->get();
so in this line was my records filter, means only records that does not equal to any of those 'employee_id' from the exceptions array will be return (refer below)
->whereNotIn('employee_id', ['MMMFLB003', 'guest_01', 'guest_02', 'guest_03'])
but instead I got this error (refer below):
SQLSTATE[23000]: Integrity constraint violation: 1052 Column
'employee_id' in where clause is ambiguous (SQL: select * from
employee inner join users on users.employee_id =
employee.employee_id where has_wishlist = 0 and employee_id
not in (MMMFLB003, guest_01, guest_02, guest_03) and
employment_status = ACTIVE)
any ideas, help please?
This happens because when you are doing the join there are two columns with the same name.
That's why on your join you prefix the employee_id with users. and employee.
Now on your whereNotIn you also have to prefix it, so the query engine knows which table column you are trying to reference. So you only have to add the prefix in your whereNotIn clause:
->whereNotIn('employee.employee_id', ['MMMFLB003', 'guest_01', 'guest_02', 'guest_03'])
->whereNotIn('employee.employee_id', ['MMMFLB003', 'guest_01', 'guest_02'])
when using join , these errors are expected if you have two fields have the same name in the tables you join between, so always try to fetch them like this
table_name.field_name

Laravel STDDEV with LIMIT

I'm trying to get the AVG() and the STDDEV_SAMP() on subset of data using Laravel.
So I've tried
//Data from which I want to calculate the AVG and STDDEV_SAMP()
//Limit the query to certain values
$subquery = TableAModel::join('TableB','TableA.TableB_id','=','TableB.id')
->select('Factor1')
->whereraw('conditionA <= 10')
->orderBy('DateTimeCode', 'desc')
->take('5')
->toSql();
//My aggregate functions
$aggregates = 'AVG(TableA.Factor1) as Factor1,
STDDEV_SAMP(TableA.Factor1) as Factor1_SD';
//My final query that should return the average and SD
$AVG_SD = \DB::table(\DB::raw(" ($subquery) as sub "))->select(\DB::raw("$aggregates"))->first()->toArray();
//it should return something like array([Factor1] => The_average, [Factor1_SD] => The_SD)
However, it throws me "Column not found: 1054 Unknown column 'TableA.Factor1' in 'field list'". If I try to select('*'), it throws me "Duplicate columns id".
I don't really have any experience with this but, it looks to me like your creating a view and from that view you want to find the STD and AVG of Factor1. But you say AVG(TableA.Factor1) as Factor1, I don't think TableA would be the table name for DB::table(\DB::raw(" ($subquery) as sub ")) so its not finding Factor1 because its can't find the table. would the table not be called sub.
I have no idea if this is the case or not, but hope it helps, also would've left a comment but I don't have the rep yet :(.

Categories