Laravel Multiple WHERE() Operator Precedence - mysql

I have the following query written using Eloquent:
Contact::select(DB::raw("DATE_FORMAT(DATE(`created_at`),'%b %d') as date"))
->addSelect(DB::raw("`created_at`"))
->addSelect(DB::raw("COUNT(*) as `count`"))
->where('created_at', '>', $date)
->ofType($type)
->groupBy('date')
->orderBy('created_at', 'ASC')
->lists('count', 'date');
You can see it uses a query scope method ofType() Here is that method, it just adds a bunch of extra where clauses to the query:
return $query->where('list_name', '=', 'Apples')
->orWhere('list_name', '=', 'Oranges')
->orWhere('list_name', '=', 'Pears')
->orWhere('list_name', '=', 'Plums')
->orWhere('list_name', '=', 'Blueberries');
Ultimately this results in the following real SQL query:
SELECT DATE_FORMAT(DATE(`created_at`),'%b %d') as date,`created_at`, COUNT(*) as `count`
FROM `contacts`
WHERE `created_at` > '2014-10-02 00:00:00'
AND `list_name` = 'Apples'
OR `list_name` = 'Oranges'
OR `list_name` = 'Pears'
OR `list_name` = 'Plums'
OR `list_name` = 'Blueberries'
GROUP BY `date`
ORDER BY `created_at` ASC
The problem is, the WHERE created_at > '2014-10-02 00:00:00' clause is being missed when the OR clauses kick in. Due to operator precendence. I need to wrap all the clauses after the first AND in parentheses, like so:
SELECT DATE_FORMAT(DATE(`created_at`),'%b %d') as date,`created_at`, COUNT(*) as `count`
FROM `contacts`
WHERE `created_at` > '2014-10-02 00:00:00'
AND
(`list_name` = 'Apples'
OR `list_name` = 'Oranges'
OR `list_name` = 'Pears'
OR `list_name` = 'Plums'
OR `list_name` = 'Blueberries')
GROUP BY `date`
ORDER BY `created_at` ASC
So, my question is, how would I achieve this using the eloquent query builder. Thank you.

Thanks to mOrsa I've figured it out, by changing my query scope method to take advantage of advanced where:
return $query->where(function($query){
$query->orWhere('list_name', '=', 'Apples')
->orWhere('list_name', '=', 'Oranges')
->orWhere('list_name', '=', 'Pears')
->orWhere('list_name', '=', 'Plums')
->orWhere('list_name', '=', 'Blueberries');
});
I get the desired SQL.

Related

How to make Laravel eloquent request with 1 filter on many fields

In my Laravel 5.7/ mysql app I make request on a form with 10 filter inputs,
one of them ($filter_search) if non empty must be compared with all fields(string, number,
date, ref to fields of other tables) in resulting listing.
I made scope on this table fields:
public function scopeGetBySearch($query, $search = null)
{
if (empty($search)) {
return $query;
}
$tb= with(new StorageSpace)->getTable();
return $query->where(
$tb.'.number', $search
) ->orWhere(function ($query) use ($search, $tb) {
$query->where( $tb.".notes", 'like', '%'.$search.'%' )
->orWhere($tb.".selling_range", $search)
->orWhere($tb.".actual_storage_rent", $search)
->orWhere($tb.".insurance_vat", $search)
// ->havingRaw("job_ref_no", $search)
})
But I have a problem how can I set filter on job_ref_no field from other table :
$storageSpacesCollection = StorageSpace
::getByStatus($filter_status)
->getById($relatedStorageSpacesArray)
->getByLocationId($filter_location_id, 'warehouses')
->getByCapacityCategoryId($filter_capacity_category_id, 'storage_capacities')
->getByLevel($filter_level)
->getByNumber($filter_number, true)
->orderBy('storage_spaces.id', 'asc')
->getByStorageCapacityId($filter_storage_capacity_id)
->getByClientId($filter_client_id)
->getByColorId($filter_color_id)
->getBySearch($filter_search)
// ->havingRaw("job_ref_no = " . $filter_search)
->leftJoin( 'storage_capacities', 'storage_capacities.id', '=', 'storage_spaces.storage_capacity_id' )
->leftJoin( 'warehouses', 'warehouses.id', '=', 'storage_spaces.warehouse_id' )
->leftJoin( 'clients', 'clients.id', '=', 'storage_spaces.client_id')
->select(
"storage_spaces.*",
\DB::raw( "CONCAT(storage_capacities.count, ' ', storage_capacities.sqft ) as storage_capacity_name" ),
\DB::raw("( SELECT check_ins.job_ref_no FROM check_ins WHERE // I got job_ref_no field in subquesry check_ins.storage_space_id=storage_spaces.id ORDER BY check_ins.id ASC limit 1 ) AS job_ref_no"),
"warehouses.name as warehouse_name",
"clients.full_name as client_full_name")
->get();
havingRaw does not work both in the scope and in the request above if to uncomment it.
I tried to use addSelect, like:
But with request :
$storageSpacesCollection = StorageSpace
::getByStatus($filter_status)
->whereRaw('storage_spaces.id <= 8') // DEBUGGING
->getById($relatedStorageSpacesArray)
->getByLocationId($filter_location_id, 'warehouses')
->getByCapacityCategoryId($filter_capacity_category_id, 'storage_capacities')
->getByLevel($filter_level)
->getByNumber($filter_number, true)
->orderBy('storage_spaces.id', 'asc')
->getByStorageCapacityId($filter_storage_capacity_id)
->getByClientId($filter_client_id)
->getByColorId($filter_color_id)
->getBySearch($filter_search)
// ->havingRaw("job_ref_no = " . $filter_search)
->leftJoin( 'storage_capacities', 'storage_capacities.id', '=', 'storage_spaces.storage_capacity_id' )
->leftJoin( 'warehouses', 'warehouses.id', '=', 'storage_spaces.warehouse_id' )
->leftJoin( 'clients', 'clients.id', '=', 'storage_spaces.client_id')
->select(
"storage_spaces.*",
\DB::raw( "CONCAT(storage_capacities.count, ' ', storage_capacities.sqft ) as storage_capacity_name" ),
"warehouses.name as warehouse_name",
"clients.full_name as client_full_name")
->addSelect([
'job_ref_no' => CheckIn::selectRaw('job_ref_no')->whereColumn('check_ins.storage_space_id', 'storage_spaces.id'),
])
->get();
But I got an error:
local.ERROR: stripos() expects parameter 1 to be string, object given {"userId":11,"email":"nilovsergey#yahoo.com","exception":"[object] (ErrorException(code: 0): stripos() expects parameter 1 to be string, object given at /mnt/_work_sdb8/wwwroot/lar/the-box-booking/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php:1031)
[stacktrace]
#0 [internal function]: Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, 'stripos() expec...', '/mnt/_work_sdb8...', 1031, Array)
#1 /mnt/_work_sdb8/wwwroot/lar/the-box-booking/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php(1031): stripos(Object(Illuminate\\Database\\Eloquent\\Builder), ' as ')
#2 [internal function]: Illuminate\\Database\\Query\\Grammars\\Grammar->wrap(Object(Illuminate\\Database\\Eloquent\\Builder))
Which way is valid ?
MODIFIED BLOCK:
I try to create sql I need manually.
With Laravel request :
$storageSpacesCollection = StorageSpace
::getByStatus($filter_status)
->whereRaw('storage_spaces.id <= 8') // DEBUGGING
->getById($relatedStorageSpacesArray)
->getByLocationId($filter_location_id, 'warehouses')
->getByCapacityCategoryId($filter_capacity_category_id, 'storage_capacities')
->getByLevel($filter_level)
->getByNumber($filter_number, true)
->orderBy('storage_spaces.id', 'asc')
->getByStorageCapacityId($filter_storage_capacity_id)
->getByClientId($filter_client_id)
->getByColorId($filter_color_id)
->getBySearch($filter_search)
->leftJoin( 'storage_capacities', 'storage_capacities.id', '=', 'storage_spaces.storage_capacity_id' )
->leftJoin( 'warehouses', 'warehouses.id', '=', 'storage_spaces.warehouse_id' )
->leftJoin( 'clients', 'clients.id', '=', 'storage_spaces.client_id')
->select(
"storage_spaces.*",
\DB::raw( "CONCAT(storage_capacities.count, ' ', storage_capacities.sqft ) as storage_capacity_name" ),
\DB::raw("( SELECT check_ins.job_ref_no FROM check_ins WHERE check_ins.storage_space_id=storage_spaces.id ORDER BY check_ins.id ASC limit 1 ) AS job_ref_no"),
"warehouses.name as warehouse_name",
"clients.full_name as client_full_name")
->get();
And tracing I see sql statement with job_ref_no column :
SELECT `storage_spaces`.*, CONCAT(storage_capacities.count, ' ', storage_capacities.sqft ) AS storage_capacity_name, ( SELECT check_ins.job_ref_no
FROM check_ins
WHERE check_ins.storage_space_id=storage_spaces.id
ORDER BY check_ins.id ASC limit 1 ) AS job_ref_no, `warehouses`.`name` AS `warehouse_name`, `clients`.`full_name` AS `client_full_name`
FROM `storage_spaces`
LEFT JOIN `storage_capacities` on `storage_capacities`.`id` = `storage_spaces`.`storage_capacity_id`
LEFT JOIN `warehouses` on `warehouses`.`id` = `storage_spaces`.`warehouse_id`
LEFT JOIN `clients` on `clients`.`id` = `storage_spaces`.`client_id`
WHERE storage_spaces.id <= 8 AND (`storage_spaces`.`number` = '999' OR
(`storage_spaces`.`notes` like '%999%' OR
`storage_spaces`.`selling_range` = '999' OR
`storage_spaces`.`actual_storage_rent` = '999' OR
`storage_spaces`.`insurance_vat` = '999') )
ORDER BY `storage_spaces`.`id` asc
I want to set filter on job_ref_no column manually :
SELECT `storage_spaces`.*, CONCAT(storage_capacities.count, ' ', storage_capacities.sqft ) AS storage_capacity_name, ( SELECT check_ins.job_ref_no
FROM check_ins
WHERE check_ins.storage_space_id=storage_spaces.id
ORDER BY check_ins.id ASC limit 1 ) AS job_ref_no, `warehouses`.`name` AS `warehouse_name`, `clients`.`full_name` AS `client_full_name`
FROM `storage_spaces`
LEFT JOIN `storage_capacities` on `storage_capacities`.`id` = `storage_spaces`.`storage_capacity_id`
LEFT JOIN `warehouses` on `warehouses`.`id` = `storage_spaces`.`warehouse_id`
LEFT JOIN `clients` on `clients`.`id` = `storage_spaces`.`client_id`
WHERE storage_spaces.id <= 8 AND (`storage_spaces`.`number` = '999' OR
(`storage_spaces`.`notes` like '%999%' OR
`storage_spaces`.`selling_range` = '999' OR
`storage_spaces`.`actual_storage_rent` = '999' OR
`storage_spaces`.`insurance_vat` = '999' OR
job_ref_no = '999' ) // I added this line #
)
ORDER BY `storage_spaces`.`id` asc
But I got error :
Error in query (1054): Unknown column 'job_ref_no' in 'where clause'
Which is valid way in raw sql and how it can be implemented with eloquent ?
MODIFIED BLOCK # 2:
I try to make with join:
$storageSpacesCollection = StorageSpace
::getByStatus($filter_status)
->getById($relatedStorageSpacesArray)
->getByLocationId($filter_location_id, 'warehouses')
->getByCapacityCategoryId($filter_capacity_category_id, 'storage_capacities')
->getByLevel($filter_level)
->getByNumber($filter_number, true)
->orderBy('storage_spaces.id', 'asc')
->getByStorageCapacityId($filter_storage_capacity_id)
->getByClientId($filter_client_id)
->getByColorId($filter_color_id)
->getBySearch($filter_search)
->leftJoin( 'storage_capacities', 'storage_capacities.id', '=', 'storage_spaces.storage_capacity_id' )
->leftJoin( 'warehouses', 'warehouses.id', '=', 'storage_spaces.warehouse_id' )
->leftJoin( 'clients', 'clients.id', '=', 'storage_spaces.client_id')
->leftJoin( 'check_ins', 'check_ins.storage_space_id', '=', 'storage_spaces.id') // I ADDED THIS LINE
->select(
"storage_spaces.*",
\DB::raw( "CONCAT(storage_capacities.count, ' ', storage_capacities.sqft ) as storage_capacity_name" ),
// \DB::raw("( SELECT check_ins.job_ref_no FROM check_ins WHERE check_ins.storage_space_id=storage_spaces.id ORDER BY check_ins.id ASC limit 1 ) AS job_ref_no"),
"warehouses.name as warehouse_name",
"check_ins.job_ref_no as job_ref_no",
"clients.full_name as client_full_name")
->distinct()
->get();
and I have a sql :
SELECT distinct `storage_spaces`.*, CONCAT(storage_capacities.count, ' ', storage_capacities.sqft ) AS storage_capacity_name,
`warehouses`.`name` AS `warehouse_name`, `check_ins`.`job_ref_no` AS `job_ref_no`, `clients`.`full_name` AS `client_full_name`
FROM `storage_spaces`
LEFT JOIN `storage_capacities` on `storage_capacities`.`id` = `storage_spaces`.`storage_capacity_id`
LEFT JOIN `warehouses` on `warehouses`.`id` = `storage_spaces`.`warehouse_id`
LEFT JOIN `clients` on `clients`.`id` = `storage_spaces`.`client_id`
LEFT JOIN `check_ins` on `check_ins`.`storage_space_id` = `storage_spaces`.`id`
WHERE ( `storage_spaces`.`number` = 'S1-102' OR (`storage_spaces`.`notes` like '%S1-102%' OR `storage_spaces`.`selling_range` = 'S1-102' OR
`storage_spaces`.`actual_storage_rent` = 'S1-102' OR `storage_spaces`.`insurance_vat` = 'S1-102' OR `job_ref_no` = 'S1-102') )
ORDER BY `storage_spaces`.`id` asc
I have have different results
I need to get only last row from check_ins, that is why in my request I have limit 1:
\DB::raw("( SELECT check_ins.job_ref_no FROM check_ins WHERE check_ins.storage_space_id=storage_spaces.id ORDER BY check_ins.id ASC limit 1 ) AS job_ref_no"),
that is why have have several rows of as storage_spaces as all check_ins ae joined
2) I have all rows from storage_spaces and I do not see why
I tried to change leftJoin with Join /rightJoin but it did not help...
Thanks!
You need to set the join into the main query instead of select query because in select query it's not a actual column which you set into the where condition.
I found a decision addin in scope :
->orWhereRaw("(SELECT check_ins.job_ref_no FROM check_ins WHERE
check_ins.storage_space_id=storage_spaces.id ORDER BY check_ins.id ASC LIMIT 1) = '".$search."'")
and that works for me.

How to get votes with results with percent calculating

In my Laravel 5.7/mysql 5 app I have a table with votes results:
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`vote_item_id` INT(10) UNSIGNED NOT NULL,
`user_id` INT(10) UNSIGNED NOT NULL,
`is_correct` TINYINT(1) NOT NULL DEFAULT '0',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
where boolean is_correct field is if answer was correct or incorrect.
I need to get data on percents of correct answers.
Creating such request
$voteItemUsersResultsCorrect = VoteItemUsersResult:: // Grouped by vote name
getByIsCorrect(true)->
getByCreatedAt($filter_voted_at_from, ' > ')->
getByCreatedAt($filter_voted_at_till, ' <= ')->
getByUserId($filterSelectedUsers)->
getByVote($filterSelectedVotes)->
getByVoteCategories($filterSelectedVoteCategories)->
getByVoteIsQuiz(true)->
getByVoteStatus('A')->
select( \DB::raw('count(vote_item_users_result.id) as count, votes.id, votes.name as vote_name') )->
orderBy('vote_name', 'asc')->
groupBy( 'votes.id' )->
groupBy( 'vote_name' )->
join(\DB::raw('vote_items'), \DB::raw('vote_items.id'), '=', \DB::raw('vote_item_users_result.vote_item_id'))->
join(\DB::raw('votes '), \DB::raw('votes.id'), '=', \DB::raw('vote_items.vote_id'))->
get();
I can get number of correct votes with sql request.
SELECT count(vote_item_users_result.id) AS count, votes.id, votes.name AS vote_name
FROM `vote_item_users_result`
INNER JOIN vote_items on vote_items.id = vote_item_users_result.vote_item_id
INNER JOIN votes on votes.id = vote_items.vote_id
WHERE `vote_item_users_result`.`is_correct` = '1' AND vote_item_users_result.created_at > '2018-08-01' AND vote_item_users_result.created_at <= '2018-09-22 23:59:59' AND `votes`.`is_quiz` = '1' AND `votes`.`status` = 'A'
GROUP BY `votes`.`id`, `vote_name`
ORDER BY `vote_name` asc
I know a way to get 2nd similar request with is_correct = '0' and on php side to combine results with percent calculating,
but I wonder if that could be done with eloquent in 1 request?
If yes, how ?
Thanks!
One correct raw MySQL would use conditional aggregation:
SELECT
v.id,
100.0 * COUNT(CASE WHEN vir.is_correct = 1 THEN 1 END) / COUNT(*) AS pct_correct,
100.0 * COUNT(CASE WHEN vir.is_correct = 0 THEN 1 END) / COUNT(*) AS pct_incorrect
FROM votes v
INNER JOIN vote_items vi
ON v.id = vi.vote_id
INNER JOIN vote_item_users_result vir
ON vi.id = vir.vote_item_id
WHERE
vir.created_at > '2018-08-01' AND vir.created_at < '2018-09-23' AND
v.is_quiz = '1' AND
v.status = 'A'
GROUP BY
v.id;
Now we can try writing Laravel code for this:
DB::table('vote')
->select('vote.id',
DB::raw('100.0 * COUNT(CASE WHEN vir.is_correct = 1 THEN 1 END) / COUNT(*) AS pct_correct'),
DB::raw('100.0 * COUNT(CASE WHEN vir.is_correct = 0 THEN 1 END) / COUNT(*) AS pct_incorrect'))
->join('vote_items', 'votes.id', '=', 'vote_items.vote_id')
->join('vote_item_users_result', 'vote_items.id', '=', 'vote_item_users_result.vote_item_id ')
->where([
['vote_item_users_result.created_at', '>', '2018-08-01'],
['vote_item_users_result.created_at', '<', '2018-09-23'],
['vote.is_quiz', '=', '1'],
['vote.status', '=', 'A']
])
->groupBy('vote.id')
->get();

Laravel DB query group by output is different

I have the following implemented query in my Laravel project, the query is working if I copy paste in the console, but the DB object sets on subquery group by zeit_von to group by zeit_von is null
$data = DB::table('e_supply AS s')
->select(
DB::raw('count(s.employee) as anzahl_employee'),
DB::raw('group_concat(a.account_lastname order by a.account_lastname ) AS employee_names'),
DB::raw('date_format(s.zeit_von, "%Y") as y'),
DB::raw('date_format(s.zeit_von, "%m") as m'),
DB::raw('date_format(s.zeit_von, "%d") as d'),
DB::raw('date_format(s.zeit_von, "%h") as h')
)
->leftJoin('phpgw_accounts AS a', 'a.account_id', '=', 's.employee')
->where('deleted', 0)
->whereIn('supply_status', [1,3])
->where(
DB::raw('( select count(cal_id) from phpgw_cal_utf8 as c
where c.owner=s.employee
and deleted=0
and date_format(s.zeit_von, "%d.%m.%y")=date_format(from_unixtime(c.mdatetime), "%d.%m.%y")
and c.category="Krank"
)<1 and date_format((s.zeit_von), "%Y")="'.$year.'" group by zeit_von'
)
)
->get();
What i'm missing in this case?
using whereRaw for a subquery suppress the problem what I faced
$data = DB::table('e_supply AS s')
->select(
'zeit_von as raw_time',
DB::raw('count(distinct s.employee) as anzahl_employee'),
DB::raw('group_concat(a.account_lastname order by a.account_lastname ) AS employee_names'),
DB::raw('date_format(s.zeit_von, "%Y") as y'), DB::raw('date_format(s.zeit_von, "%m") as m'),
DB::raw('date_format(s.zeit_von, "%d") as d'), DB::raw('date_format(s.zeit_von, "%h") as h')
)
->leftJoin('phpgw_accounts AS a', 'a.account_id', '=', 's.employee')
->where('deleted', 0)
->whereIn('supply_status', [1, 3])
->whereRaw(
DB::raw('( select count(cal_id) from phpgw_cal_utf8 as c
where c.owner=s.employee
and deleted=0
and date_format(s.zeit_von, "%d.%m.%y")=date_format(from_unixtime(c.mdatetime), "%d.%m.%y")
and c.category="Krank"
)<1'
)
)
->whereYear('s.zeit_von', $year)
->groupBy('zeit_von')
->get();

Laravel 5: whereRaw escapes integer to string

What is the Laravel eloquent query for this:
select * from `jobs` where (
400000 between min_salary and max_salary
or
600000 between min_salary and max_salary
);
I tried the below eloquent query which encapsulates the integer to string
$min = 400000;
$max = 600000;
Job::whereRaw('
? between min_salary and max_salary
or
? between min_salary and max_salary',
[$min,$max]
)->get();
Also tried Casting and DB::Raw none of the options were worked as expected.
protected $casts = [
'min_salary' => 'integer',
'max_salary' => 'integer',
];
$min = 400000;
$max = 600000;
Job::whereRaw('
? between min_salary and max_salary
or
? between min_salary and max_salary',
[DB::Raw($min),DB::Raw($max)]
)->get();
I tried the below eloquent query works as expected but i have hard coded the query directly(Unsafe)
$min = 400000;
$max = 600000;
Job::whereRaw(
$min.' between min_salary and max_salary
or
'.$max.' between min_salary and max_salary'
)->get();
Try this:
Job::where(function($query){
$query->where('min_salary','<',400000);
$query->where('max_salary','>',400000);
})->orWhere(function($query){
$query->where('min_salary','<',600000);
$query->where('max_salary','>',600000);
})->get();

Nested select eloquent

Is it possible creating nested select in Eloquent using query builder? I have problem where i need to group and use sum on one column of my table but that column is displayed as debit or credit and depends on another column value. I need to group one more time to get both of the sums in one row.
$columns=array( DB::raw('left(archive_ledgers.account,3) as account'),
DB::raw('(case when archive_ledgers.booking_type=1 then sum(archive_ledgers.amount) else 0 end) as debit'),
DB::raw('(case when archive_ledgers.booking_type!=1 then sum(archive_ledgers.amount) else 0 end) as credit')
);
ArchiveLedger::where('accounts.account_type', '<=', $accounts['to'])
->join('accounts',function($join){
$join->on('archive_ledgers.account','=','accounts.account');
})
->join('orders',function($join){
$join->on('orders.id','=','archive_ledgers.order_id');
})
->where('archive_ledgers.account', '>=', $accounts['from'])
->where('document_date', '<=', Input::get('dateTo'))
->where('document_date', '>=', Input::get('dateFrom'))
->select($columns)
->orderBy('archive_ledgers.booking_type',DB::raw('left(archive_ledgers.account,3)'))
->groupBy('archive_ledgers.booking_type',DB::raw('left(archive_ledgers.account,3)'));
I need to do select on result given from this query
select `account`, sum(s1.owes) as owes, sum(s1.asks) as asks, s1.owes - s1.asks as total from (select left(archive_ledgers.account,3) as account, (case when archive_ledgers.booking_type=1 then sum(archive_ledgers.amount) else 0 end) as owes, (case when archive_ledgers.booking_type!=1 then sum(archive_ledgers.amount) else 0 end) as asks from `archive_ledgers` inner join `accounts` on `archive_ledgers`.`account` = `accounts`.`account` inner join `orders` on `orders`.`id` = `archive_ledgers`.`order_id` where `accounts`.`account_type` <= ? and `archive_ledgers`.`account` >= ? and `document_date` <= ? and `document_date` >= ? group by `archive_ledgers`.`booking_type`, left(archive_ledgers.account,3) order by `archive_ledgers`.`booking_type` desc) s1 group by `s1`.`account`)