Conditional Select Statement in laravel eloquent - laravel

I have a raw query like that
SELECT IF(`user_group` = '1', `total_score`, `score`) FROM `user`
Now how I can convert this query in laravel eloquent ORM

Convert the MYSQL CASE INTO LARAVEL Query
$query = DB::raw("(CASE WHEN user_group='1' THEN 'Admin' WHEN user_group='2' THEN 'User' ELSE 'Superadmin' END) as name");
and simply execute this query in
DB::table('tablename')->select($query)->get();
or
YourModelClass::select($query)->get();
You will get the result.

Applicable if you need to have conditional join and select :
Using mysql's native conditionals can be a good way. You might be in a situation where if a particular condition is truthy in PHP then you need to join that table otherwise do not join.
For example :
If $loggedInUser is admin, then you want to get student attendence otherwise just show marks.
you can have(PS below is a pseudo code just for reference) :
<?php
// Having the column selection only when a particular condition is true
// Else have its value as NULL(You can have NA also)
if($loggedInUser->role == 'admin'){
$attendanceColumnSelect = DB::raw('attendance.total as total_attendance');
}
else{
$attendanceColumnSelect = DB::raw('NULL as total_attendance');
}
// Students query with joins which must be there always
$studentsQuery= Students::select('name', 'class', 'age', $attendanceColumnSelect)
->join('someothertable', 'someothertable.student_id', '=', 'student.id');
// Adding join of attendance only when required for admin role
if($loggedInUser->role == 'admin'){
$studentsQuery->join('attendance', 'attendance.student_id', '=', 'student.id');
}
// Getting final data
$finalResult = $studentsQuery->get();
?>
If you try to do this way :
<?php
$finalResult = DB::select("
SELECT students.name,
students.class,
students.age,
IF('$loggedInUser->role' = 'admin', attendance.total, NULL) as total_attendance
FROM students
INNER JOIN someothertable on someothertable.student_id = student.id
INNER JOIN attendance on attendance.student_id = student.id
");
?>
Then you have to have the attendance join even when you know the condition is false because otherwise it will have 'unknown column attendance.total' error.
From my perspective, if we know we do not want a particular column, I would just not join that table. If you do an EXPLAIN on above raw query, you will find MySQL will need attendance table even when the If condition in select is false.
Please feel free to comment if you find this incorrect or any better suggestions.

DB::table('users')->select('IF(`user_group` = '1', `total_score`, `score`)')->get();
this will work

Related

How to use order by query both with current table column and relationship table column in laravel?

i am trying to fetch record with order by query but situation is this that i have to use order by both on current table column and relationship table column in laravel . I tried this
$consignments = Consignment::where('delivery_run_id', $id)->whereIn('status', [
'In Warehouse',
'With On Forwarder',
'In Transit (Warehouse->Delivery)',
'Awaiting Pickup'
])->with(['consignment_run_sheet' => function ($query) {
$query->orderBy('run_sheet_id');
}])->orderBy('delivery_date', 'DESC')->get();
$deliveryRuns = DeliveryRun::all();
How I can achieve it?
it will order just the relation items in this way. solution is use subquery or joins. useing Subquery is like this if assume the modal for consignment_run_sheet relation is ConsignmentRunSheet and the relation is belongsTo:
$consignments = Consignment::where('delivery_run_id', $id)->whereIn('status', [
'In Warehouse',
'With On Forwarder',
'In Transit (Warehouse->Delivery)',
'Awaiting Pickup'
])->with('consignment_run_sheet')
->orderBy(ConsignmentRunSheet::select('delivery_date')
->whereColumn('consignments.id', 'consignment_run_sheet.consignment_id'), 'DESC')
->get()
source:
https://reinink.ca/articles/ordering-database-queries-by-relationship-columns-in-laravel

Convert SQL to Laravel Eloquent Statement

I've been working on a few tables where through a rather complex relationship (that I'm trying to clean up, but I still need reports made from the data through my Laravel).
At the moment, I can pull the data using the following SQL query to my MySQL database:
SELECT
customers.id,
customers.customer_name,
SUM(shipments.balance) AS shipmentBalance
FROM customers
LEFT JOIN shipments
ON customers.id = shipments.bill_to
AND balance > (SELECT IFNULL(SUM(payments_distributions.amount),0)
FROM payments_distributions
WHERE payments_distributions.shipment_id = pro_number)
GROUP BY customers.id, customers.customer_name
ORDER BY shipmentBalance DESC
LIMIT 5;
I'm just not sure how to rewrite it properly into the whereRaw or DB::raw statements that Laravel Eloquent requires, as my previous attempts have failed.
Update
Here is the closest solution I have tried:
DB::table('customers')
->select('customers', DB::raw('SUM(shipments.balance) AS shipmentBalance'))
->leftJoin(
DB::raw('
(select shipments
ON customers.id = shipments.bill_to
AND balance > (SELECT IFNULL(SUM(payments_distributions.amount),0)
FROM payments_distributions
WHERE payments_distributions.shipment_id = pro_number)'))
->groupBy('customers.id')
->orderByRaw('shipmentBalance DESC')
->limit(5)
->get();
Update 2
Edit for Dom:
Using everything as it stands with your answer, I get the following response:
SQLSTATE[42S22]: Column not found: 1054 Unknown column '' in 'on clause' (SQL: select customers.id, customers.customer_name,SUM(s.balance) AS shipmentBalance from `customers` left join `shipments` as `s` on `customers`.`id` = `s`.`bill_to` and s.balance > (SELECT IFNULL(SUM(payments_distributions.amount),0) FROM payments_distributions WHERE payments_distributions.shipment_id = s.pro_number) = `` group by `customers`.`id`, `customers`.`customer_name` order by SUM(s.balance) DESC limit 5)
But if I remove this section, it brings up the page and the customers (though in the wrong order as I have removed one of the necessary components:
$join->on(DB::raw('s.balance >
(SELECT IFNULL(SUM(payments_distributions.amount),0)
FROM payments_distributions
WHERE payments_distributions.shipment_id = s.pro_number)
'));
Is there anything I can provide you with to get this specific statement to work with your entire answer?
Use this:
DB::table('customers')
->select('customers.id', 'customers.customer_name', DB::raw('SUM(shipments.balance) AS shipmentBalance'))
->leftJoin('shipments', function($join) {
$join->on('customers.id', 'shipments.bill_to')
->where('balance', '>', function($query) {
$query->selectRaw('IFNULL(SUM(payments_distributions.amount),0)')
->from('payments_distributions')
->where('payments_distributions.shipment_id', DB::raw('pro_number'));
});
})
->groupBy('customers.id', 'customers.customer_name')
->orderByDesc('shipmentBalance')
->limit(5)
->get();
Without the Models containing relationships or being able to test on this specific project, this is the most eloquent way I can think of performing your task.
The benefit of starting with the Customer model is you will have a laravel collection and can paginate as needed. Also review the eloquent docs, they help you understand all the different options. Hope his helps.
P.S. Start by using your model in your controller or wherever you are placing this query with:
use App\Customer
The query
$theQuery = Customer::select(DB::raw('customers.id, customers.customer_name,SUM(s.balance) AS shipmentBalance'))
->leftJoin('shipments as s', function($join)
{
$join->on('customers.id', '=', 's.bill_to');
$join->on(DB::raw('s.balance >
(SELECT IFNULL(SUM(payments_distributions.amount),0)
FROM payments_distributions
WHERE payments_distributions.shipment_id = s.pro_number)
'));
})
->groupBy('customers.id', 'customers.customer_name')
->orderByRaw('SUM(s.balance) DESC')
->limit(5)
->get();

Laravel 4.2 Eloquent using lists() with a join query

I have a query that makes use of multiple joins:
public function scopePurchased($query, $userId)
{
return $query
->join('products','characters.id','=','products.productable_id')
->join('bundle_product','bundle_product.product_id','=','products.id')
->join('bundles','bundles.id','=','bundle_product.bundle_id')
->join('purchases','purchases.bundle_id','=','bundles.id')
->join('users','purchases.user_id','=','users.id')
->whereNull('purchases.deleted_at')
->where('purchases.refunded', false)
->where('products.productable_type', '=', get_class($this))
->where('users.id','=',$userId)
->groupBy('characters.id')
->orderBy('characters.title', 'ASC');
}
And I want to retrieve an array of ID's from this query to use in another scope so:
$query->purchased($userID)->lists('id')
My initial thought was to use lists('id') which complained about an ambiguous query on the ID.
Column 'id' in field list is ambiguous
(
SQL: select `id` from `characters`
inner join `products` on `characters`.`id` = `products`.`productable_id`
inner join `bundle_product` on `bundle_product`.`product_id` = `products`.`id`
inner join `bundles` on `bundles`.`id` = `bundle_product`.`bundle_id`
inner join `purchases` on `purchases`.`bundle_id` = `bundles`.`id`
inner join `users` on `purchases`.`user_id` = `users`.`id`
where `characters`.`deleted_at` is null
and `purchases`.`deleted_at` is null
and `purchases`.`refunded` = 0
and `products`.`productable_type` = Character and `users`.`id` = 1
group by `characters`.`id`
order by `characters`.`title` asc
)
Makes sense, fair enough so I changed the lists to
$query->purchased($userID)->lists('characters.id')
Thinking that naming the table and column should fix it but finding that the lists function drops the 'character.' part and so having the same error.
It appear that lists may not use a dot notation, bring me to my question... Can I escape the dot notation or is there another way to get the list of ID's as an array?
Many thanks
You can alias the column name before using lists:
$query->purchased($userID)->select('characters.id as _id')->lists('_id');
This will avoid any column name conflicts.

Laravel: Improved pivot query

I am successfully querying following and it create 130 queries, I want to optimise it and reduce the number of queries, I have set upped the model and controllers following way.
Post Modal
class Post extends Eloquent {
public function Categories () {
return $this->belongsToMany('Category', 'category_post');
}
}
Category Modal
class Category extends Eloquent {
public function posts () {
return $this->belongsToMany('Post', 'category_post');
}
}
and in the Controller, I am using following query, what following query does is, querying the results based on category id.
$category = Category::with('posts')->where('id','=',$id)->paginate(10)->first();
return Response::json(array('category' => $category));
If anyone can give me a hand to optimise the query, would be really greatful.
You are wrong, it doesn't create 130 queries.
It will create the following 3 queries:
select count(*) as aggregate from `categories` where `id` = '5';
select * from `categories` where `id` = '5' limit 10 offset 0;
select `posts`.*, `posts_categories`.`category_id` as `pivot_category_id`, `posts_categories`.`post_id` as `pivot_post_id` from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` in ('5');
But the question is what exactly you want to paginate. Now you paginate categories and it doesn't make much sense because there's only one category with selected $id.
What you probably want to get is:
$category = Category::where('id','=',$id)->first();
$posts = $category->posts()->paginate(10);
and this will again create 3 queries:
select * from `categories` where `id` = '5' limit 1;
select count(*) as aggregate from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` = '5';
select `posts`.*, `posts_categories`.`category_id` as `pivot_category_id`, `posts_categories`.`post_id` as `pivot_post_id` from `posts` inner join `posts_categories` on `posts`.`id` = `posts_categories`.`post_id` where `posts_categories`.`category_id` = '5' limit 10 offset 0;
If you would like to improve it, you will probably need to not use Eloquent in this case and use join - but is it worth it? You would now need to manually paginate results without paginate() so it would probably won't be want you want to achieve.
EDIT
What you probably do is:
you get all posts that belongs to the category (but in fact you want to paginate only 10 of them)
for each post you want do display all categories it belongs to.
To lower number of queries you should use:
$category = Category::where('id','=',$id)->first();
$posts = $category->posts()->with('categories')->paginate(10);
and to display it you should use:
foreach ($posts as $p) {
echo $p->name.' '."<br />";
foreach ($p->categories as $c) {
echo $c->name."<br />";
}
}
It should lower your number queries to 4 from 130

Getting results form a query to insert in other using active records

I'm needeing to get all the child categories from the given to use in a where_in with active records of codeigniter.
The problem is that the second query get mixed with the main one breaking it completely.
Main Query
$this->db->select('artworks.*, users.id as owner, users.name as user_name');
$this->db->from('artworks');
$this->db->join('users', 'users.id = artworks.user_id');
$category = $this->get_child_categories($this->get_categories(), $matches[1]);
$this->db->where_in('artworks.category', $this->category['child']);
$this->db->group_by('artworks.id');
$query = $this->db->get();
return $query->result_array();
Second Query "get_categories()"
$this->db->select('*');
$this->db->order_by('parent', 'asc');
$this->db->order_by('name', 'asc');
$query = $this->db->get('categories');
return $query->result_array();
get_child_categories
function get_child_categories($categories, $parent){
foreach($categories as $category){
if($category['parent'] == $parent){
array_push($this->category['childs'], $category['id']);
$this->get_child_categories($categories, $category['id']);
}
}
}
But i'm getting this error where clearly displays that the second query is quetting inside the main one.
Error Number: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '* FROM (`artworks`, `categories`) JOIN `users` ON `users`.`id` = `artworks`.`use' at line 1
SELECT `artworks`.*, `users`.`id` as user_id, `users`.`name` as user_name, * FROM (`artworks`, `categories`) JOIN `users` ON `users`.`id` = `artworks`.`user_id` WHERE `artworks`.`rating` IN ('g', 'm', 'a') ORDER BY `artworks`.`id` desc, `parent` asc, `name` asc
Filename: D:\Server\htdocs\gallery\system\database\DB_driver.php
Line Number: 330
I personally think this is an error in CodeIgniter's Active Record approach, if it's supposed to follow the Active Record pattern at all. It should totally enforce either one of these:
queries contained in a single data context
queries specified in a atomic instruction
As neither of these is happening, at the moment you're unwillingly mixing two queries with a structure that CodeIgniter does not support, thus creating that invalid query.
For a simple solution, I would suggest for you to invert the order of the instructions so that the queries are executed separately.
$category = $this->get_child_categories($this->get_categories(), $matches[1]);
# the first query gets executed here, your data context is cleaned up
$this->db->select('artworks.*, users.id as owner, users.name as user_name');
$this->db->from('artworks');
$this->db->join('users', 'users.id = artworks.user_id');
$this->db->where_in('artworks.category', $this->category['child']);
$this->db->group_by('artworks.id');
$query = $this->db->get();
# your second query gets executed here
return $query->result_array();

Resources