left outer join, from table have been group laravel - php

how to left join table have been group, in my case is, table A have data account number and table B is transaction, i have to calculate and group by account number before joining into table A,
if in sql natife is like bellow
select name.account_no
,amount
from
ci_account_name name left join (
select account_no,currency_id,sum(amount) as amount from
ci_account_transaction
where status <> 'X' and store_id = 62242
group by account_no,currency_id
) as trans
on name.account_no = trans.account_no
that coding is working, but how to implement in laravel eloquent
i have try code bellow but there is error
public function reportShowZerro($data){
$return = $this->accountNameModel->select (['ci_account_name.*'
,'amount'
])
->leftJoin("
$this->model->select(['account_no','currency_id',\DB::raw('SUM(amount) amount')
])
->where('store_id',62242)
->where('status','<>','X')
->where('year',$data['year'])
->where('month',$data['month'])
->groupBy('account_no','currency_id')
) as trans",'ci_account_name.account_no','=','trans.account_no'
->whereIn('ci_account_name.store_id',[0,62242)
->get();
return $return;
}

public function reportWithModel($data){
return $this->accountNameModel->select([
'ci_account_name.*',
'trans.store_id as trans_store_id','currency_id','amount'
])->leftJoin(
\DB::raw('
(select account_no,currency_id,store_id,sum(amount) as amount from
ci_account_transaction
where status <> "X" and store_id = '.store()->id.'
and year = '.$data["year"].' and month = '.$data["month"].'
group by account_no,currency_id
) as trans
'),
'trans.account_no',
'=',
'ci_account_name.account_no'
)
->whereIn('ci_account_name.store_id',[0,store()->id])
->orderBy('ci_account_name.account_no')
->get();
}

Related

Not able to find correct data with whereHas and having count

I have users, conversations, conversation_user and messages table:
Before making a new conversation with $data array of user id's, I am trying to find existing one:
$data = [2,3]; // without auth()->id() -> 1 => total of 3 users per conversation
auth()->user()->conversations()->has('messages')->whereHas('users', function ($query) use ($data) {
$query->whereIn('user_id', $data);
})->whereHas('users', function ($query) use ($data) {
$query->groupBy('conversation_id', 'conversation_user.id')
->havingRaw('count(conversation_id) = ' . count($data) + 1); // total count is 3
})->first()
Now first whereHas returns even if I have conversation between auth()->id() and ID-2, because user_id 2 is in (2,3).. So it would retrieve the wrong conversation, where I need to count for users per conversation as well.
The second whereHas is for counting however if I use $query->groupBy('conversation_id') I get mysql SQL_MODE error for grouping, meaning I need to add $query->groupBy('conversation_id', 'conversation_user.id') as well, but with all that I get no record from database even if there are some.
What am I missing here?
[Updated with generated sql]
select * from `conversations`
inner join `conversation_user` on `conversations`.`id` = `conversation_user`.`conversation_id`
where `conversation_user`.`user_id` = 1 and exists (
select * from `conversation_messages`
where `conversations`.`id` = `conversation_messages`.`conversation_id`
and `conversation_messages`.`deleted_at` is null
) and exists (
select * from `users`
inner join `conversation_user` on `users`.`id` = `conversation_user`.`user_id`
where `conversations`.`id` = `conversation_user`.`conversation_id`
and `user_id` in (2, 3) and `users`.`deleted_at` is null
) and exists (
select * from `users`
inner join `conversation_user` on `users`.`id` = `conversation_user`.`user_id`
where `conversations`.`id` = `conversation_user`.`conversation_id`
and `users`.`deleted_at` is null
group by `conversation_id`, `conversation_user`.`id`
having count(conversation_id) = 3
) and `conversations`.`deleted_at` is null
[Update with table structures]
users -> id, name, email
conversations -> id, slug, subject
conversation_user -> id, user_id, conversation_id
messages -> id, conversation_id, user_id, body
[Another update]
Seems like this works also, in case someone need:
auth()->user()->conversations()->has('messages')->whereHas('users', function ($query) use ($data) {
$query->whereIn('user_id', $data);
})->whereDoesntHave('users', function ($query) use ($data) {
$query->whereNotIn('user_id', $data);
})->first()
I think this is the SQL you want -
SELECT c.*
FROM conversation_user cu
JOIN conversations c
ON cu.conversation_id = c.id
WHERE cu.user_id IN (1, 2, 3)
AND NOT EXISTS (
SELECT *
FROM conversation_user
WHERE conversation_id = cu.conversation_id
AND user_id NOT IN (1, 2, 3)
)
GROUP BY cu.conversation_id
HAVING COUNT(DISTINCT cu.user_id) = 3
Not sure if this is correct as I am not a Laravel user -
$data = [1, 2, 3];
$conv = DB::table('conversation_user cu')
->select('c.*')
->join('conversations c', 'cu.conversation_id', '=', 'c.id')
->whereIn('cu.user_id', $data)
->whereNotExists(function($query) use ($data) {
$query->select(DB::raw(1))
->from('conversation_user')
->whereColumn('conversation_id', 'cu.conversation_id')
->whereNotIn('user_id', $data);
})
->groupBy('cu.conversation_id')
->havingRaw('COUNT(DISTINCT cu.user_id) = ?', count($data))
->get();

query relation based on parent column

i have 2 tables
accounts : id , title , disabled , transaction_amount_limit , transaction_count_limit
account_limits : id , account_id , transaction_amount , transaction_count , date
so each account has bunch of transaction each day ... i want to select the a account that hasn't reached its transactions limit .... current transaction for each account is stored in account_limits table
basically i want to say select account that doesn't have account_limits row or have account_limits but hasn't reached the limits account_limits.transaction_amount < accounts.transaction_amount_limit && account_limits.transaction_count < accounts.transaction_count_limit
something like
select * from `accounts`
( where not exists (select * from `account_limits` where `accounts`.`id` = `account_limits`.`account_id`)
OR
where exists (select * from `account_limits` where `accounts`.`id` = `account_limits`.`account_id` && account_limits.transaction_amount < accounts.transaction_amount_limit && account_limits.transaction_count < accounts.transaction_count_limit)
)
i have this so far
$account = Account::where('disabled' , 0 )->where(function($q){
$q->whereDoesntHave('AccountLimit')->orWhere('....') ;
})->get();
as #Flame suggested i tried
Account::whereHas('accountLimits', function($query) {
$query->where('account_limits.transaction_amount', '<', 'accounts.transaction_amount_limit')
->where('account_limits.transaction_count', '<', 'accounts.transaction_count_limit');
})->orHas('accountLimits', '=', 0);
the problem is for some reason
where('account_limits.transaction_amount', '<', 'accounts.transaction_amount_limit')
in the output will translate to
where `account_limits`.`transaction_amount` < 'accounts.transaction_amount_limit'
and query fails , there's problem with quotations
this
'accounts.transaction_amount_limit'
should be
`accounts`.`transaction_amount_limit`
Here is an example answer in the Eloquent syntax. Note that you need to add the relation:
// Account.php , your eloquent model
public function accountLimits()
{
return $this->hasMany(AccountLimit::class);
}
And for the query:
Account::whereHas('accountLimits', function($query) {
$query->where('account_limits.transaction_amount', '<', 'accounts.transaction_amount_limit')
->where('account_limits.transaction_count', '<', 'accounts.transaction_count_limit');
})->orHas('accountLimits', '=', 0);
This checks for your where-clause in the relation using whereHas, and if it is not a match, it will also add the records that match in the orHas, which finds all Accounts without accountLimits relationships.
As a straight MySQL query, something like this should work:
SELECT a.*
FROM accounts a
JOIN limits l ON l.transaction_amount < a.transaction_amount_limit AND
l.transaction_count < a.transaction_count_limit
The JOIN condition will filter out any accounts that have met or exceeded either their transaction_amount or transaction_count limits.
It is good idea for me to keep transaction_count and transaction_limit in same table (accounts).
Then you can compare this columns.
$accounts = Account::whereRaw("transaction_count < transaction_limit)->get();

Symfony Query Builder how to get last grouped records

I have an entity named "Location" with the following fields:
id (auto increment)
vehicle (foreign key to "vehicle" table)
lat
lng
speed
I need to get the last locations (with last id) grouped by vehicle using doctrine Query Builder.
Here is my function:
// Find locations
public function findLocations($idFleet, $select, $from, $to)
{
$qb = $this->createQueryBuilder('l')
->innerJoin('l.vehicle', 'v')
->where('v.fleet = :fleet')
->setParameter('fleet', $idFleet)
->andWhere('v.gps = :gps')
->setParameter('gps', true);
// Last locations
if ( $select == "last"){
$qb->groupBy('l.vehicle')
->orderBy('l.id', 'ASC');
}
// else Interval locations
else if ( $select == "interval"){
if( $from != ""){
$from = (new \DateTime())->setTimestamp($from);
$qb->andWhere('l.time >= :from')
->setParameter('from', $from);
}
if( $to != ""){
$to = (new \DateTime())->setTimestamp($to);
$qb->andWhere('l.time <= :to')
->setParameter('to', $to);
}
}
$locations = $qb->getQuery()->getResult();
return $locations;
}
thanks for helping.
To pick the latest record from location entity for each vehicle you could do a self left join on location entity with additional join criteria using WITH clause and in where clause check for nulls, the rows with highest id per location will have a null against self joined rows.
// Last locations
if ( $select == "last"){
$qb
->leftJoin( 'YourBundle\Entity\Location', 'l1', 'WITH', 'l.vehicle = l1.vehicle AND l.id < l1.id' )
->andWhere('l1.vehicle IS NULL' )
->orderBy('l.id', 'ASC');
}
See similar solutions
Doctrine Query Language get Max/Latest Row Per Group
doctrine dbal get latest chat message per group
DQL Doctrine query translation
Doctrine DQL greatest-n-per-group
I had a similar problem recently.
This can be done using a subquery to get the location info by max location id for the vehicle, and then left join it to the outer vehicle select.
Unfortunately this cannot be done in DQL since it cannot join an entity with a subquery.
You will have to do this in native SQL
Something like
SELECT * from vehicle LEFT JOIN location ON location.vehicle = vehicle.id WHERE location.id in (SELECT MAX(Id) FROM location GROUP BY vehicle)
You might think at first that you can do this in a DQL subquery, but this query will work only if there is at least one location for each vehicle.
If you want to still get the vehicle data even if there is no location entry for that vehicle, you will have to work on the subquery to get * for it having max id group by vehicle, and then left join it to the main query. This is the part not supported in DQL

Laravel subquery

I want to write this query in laravel 5.2
SELECT b.id,
TotalP,
b.booking_amount
FROM booking b
LEFT JOIN
(SELECT sum(amount) AS TotalP,
booking_id
FROM payment
GROUP BY booking_id) AS T ON b.id = T.booking_id
WHERE COALESCE(TotalP, 0) < b.booking_amount
My Question is related to this post.
I wrote a query after searching and studying but It is not working and need more constraint
$result = DB::table('my_booking')
->select('booking_name')
->leftJoin(DB::raw('(SELECT booking_id,sum(amount) as TotalP FROM `my_payment` GROUP BY booking_id) TotalPayment'), function($join)
{
$join->on('my_booking.id', '=', 'TotalPayment.booking_id');
})
->get();
Sql query to get data diffrence of total in 2 tables
You can try this,
$booking_payments = Booking::with('Payment')->find(1);
$total = 0;
foreach($booking_payments->payment as $booking_payment){
$total += $booking_payment->amount;
}
if($booking_payments->booking_amount == $total){
// if the total and booking_amount is equal
}
This should work in Laravel and give you the same exact result as your MySQL query. I moved COALESCE into the subquery select area so that you don't have to write a raw DB where statement in Laravel.
$sql_subquery = "(SELECT COALESCE(SUM(amount),0) AS TotalP,
booking_id
FROM payment
GROUP BY booking_id) AS T";
$result = DB::table('booking AS b')
->leftJoin(DB::raw($sql_subquery), 'b.id', '=', 'T.booking_id')
->where('T.TotalP','<', 'b.booking_amount')
->select('b.id','T.TotalP','b.booking_amount')
->get();

Yii Activerecord doesn't join with nested relation

I have defined the following criteria in Yii, and tries to use it to fetch an array of customers.
$criteria = new CDbCriteria(array(
"condition"=>"hidden = 0".(Yii::app()->user->GetState('is_admin') ? "" : " AND franchisesMunicipalities.franchise_id=".Yii::app()->user->getState('fid')),
"with" => array('municipality','municipality.franchisesMunicipalities')
));
$customers = Customers::model()->findAll($criteria);
This (i thought) should result in that Yii joined the table Customers with the table Municipalities, and then in the same query join the table Municipalities with the table Franchises_Municipalities. However it doesn't work since it doesn't join franchisesMunicipalities.
The resulting query is this
SELECT `t`.`id` AS `t0_c0`,
`t`.`municipality_id` AS `t0_c1`,
`t`.`personal_code_number` AS `t0_c2`,
`t`.`name` AS `t0_c3`,
`t`.`adress` AS `t0_c4`,
`t`.`zip` AS `t0_c5`,
`t`.`phone` AS `t0_c6`,
`t`.`mobile` AS `t0_c7`,
`t`.`email` AS `t0_c8`,
`t`.`hidden` AS `t0_c9`,
`municipality`.`id` AS `t1_c0`,
`municipality`.`county_id` AS `t1_c1`,
`municipality`.`name` AS `t1_c2`
FROM `customers` `t`
LEFT OUTER JOIN `municipalities` `municipality`
ON ( `t`.`municipality_id` = `municipality`.`id` )
WHERE ( hidden = 0
AND municipality.franchisesmunicipalities.franchise_id = 7 )
LIMIT 30
As you can see it only joins on one relation. The relations in the model should be correctly defined, since i am able to use them in other contexts.
Why doesn't this work?
According to http://www.yiiframework.com/doc/api/1.1/CActiveRecord#with-detail I believe you should be doing it this way:
$criteria = new CDbCriteria(array(
"condition"=>"hidden = 0".(Yii::app()->user->GetState('is_admin') ? "" : " AND franchisesMunicipalities.franchise_id=".Yii::app()->user->getState('fid'))
));
$customers = Customers::model()->with('municipality','municipality.franchisesMunicipalities')->findAll($criteria);
Hopefully that works for you.

Categories