SQL Join/LeftJoin Statement Returning Null - laravel

I'm trying to join two queries with this statement:
$item = Category::where('type', $item)
->leftJoin('inventory', 'inventory.id', '=', 'categories.id')
->get();
return View::make('general.buy')
->with('items', $item);
This is my view (this returns NULL):
#foreach ($items as $item)
{{ $item->id }}
#endforeach
The problem is that if there is no inventory id value equal to the category id field, the query returns the id as NULL.
Does anyone know why this is happening and how I can solve it? I should also mention that I am new to laravel, so please keep that in mind when rating this question. Thanks in advance!

I think you answered your own question. It's more about what LEFT JOIN means. You have two tables, inventory and category correct? According to your query (or at least what I can tell) this will generate SQL simliar to
SELECT FROM category LEFT JOIN inventory on inventory.id = categories.id
This says, select ALL rows from category and if they have a match in inventory fill in the columns queried (inventory.id) then supply that value, otherwise return null in that column.
Given this seems to be a somewhat unexpected result to you, I can't help but notice that the naming convention seems slightly off (but I'm not as familiar with laravel). I would assume that instead of merging on categories.id you would merge on category.id (which would seem to match the rest of the schema).
Based on what and how you're querying, the results appear to be correct.

Related

Is there a way to select one url among multiple picture urls with "exists" condition in laravel?

I have several picture urls some of them may exists in the table, some of them may not. Columns are "picture, picture_thubnail, company_logo_thumbnail, company_logo". I want to select picture thumbnail as lets say "picture_url". But if "picture_thumbnail" does not exists, I want to pick "picture" as "picture_url". If "picture" does not exists I want to pick company_logo_thumbnail and so on... as a result I will get a picture_url as a string or null.
edit: this should be done in a single query instead of multiple queries with if else. or more elegant solutions are appretiated.
Sounds more like a database thing than a Laravel thing (the easiest way I can think of anyway).
$images = DB::table('images')
->select(DB::raw('COALESCE(picture_thumbnail, picture, company_logo_thumbnail, company_logo) as picture_url'))
->get();
The MySQL COALESCE function returns the first non-null result.
This is also assuming that you are using MySQL as your database engine.
UPDATE
To check a column in another table you could use a join.
$images = DB::table('images')
->select(DB::raw('COALESCE(picture_thumbnail, picture, company_logo_thumbnail, company_logo, company_logos.id) as picture_url'))
->leftJoin('company_logos', 'company_logos.id', '=', 'images.company_logo_id')
->get();
I've used company_logos.id in the select, but guessing it would be something like company_logos.picture or something with the filename, rather than the id of the row.
UPDATE 2
I would change this to be a left join:
->join('cloudfiles', function ($join) {
$join->on('userdetails.company_logo_id', '=', 'cloudfiles.id')->where('company_logo_id', '!=', null);
})
->leftJoin('cloudfiles', function ($join) {
$join->on('userdetails.company_logo_id', '=', 'cloudfiles.id')->where('company_logo_id', '!=', null);
})
A left join would mean you still get all results from the userdetails where as a normal join would mean only userdetails that have an associated cloudfiles record would be returned.
Secondly, I'd change this:
->where('company_logo_id', '!=', null)
to
->whereNotNull('company_logo_id')
Although I think you might be able to remove that condition completely.
Couple of other points, just fyi:
$boothOwner = Booth::where('id', '=', Request()->booth_id)->firstOrFail();
could be (unless there are somehow duplicate ids in the table)
$boothOwner = Booth::findOrFail(Request()->booth_id);
Similarly,
->whereRaw('userdetails.user_id = ?', [$boothOwner->user_id])
could be
->where('userdetails.user_id', $boothOwner->user_id)

Order by relationship column

I have the following query:
$items = UserItems::with('item')
->where('user_id','=',$this->id)
->where('quantity','>',0)
->get();
I need to order it by item.type so I tried:
$items = UserItems::with('item')
->where('user_id','=',$this->id)
->where('quantity','>',0)
->orderBy('item.type')
->get();
but I get Unknown column 'item.type' in 'order clause'
What I am missing?
join() worked fine thanks to #rypskar comment
$items = UserItems
::where('user_id','=',$this->id)
->where('quantity','>',0)
->join('items', 'items.id', '=', 'user_items.item_id')
->orderBy('items.type')
->select('user_items.*') //see PS:
->get();
PS: To avoid the id attribute (or any shared name attribute between the two tables) to overlap and resulting in the wrong value, you should specify the select limit with select('user_items.*').
Well, your eager loading is probably not building the query you're expecting, and you can check it by enabling the query log.
But I would probably just use a collection filter:
$items = UserItems::where('user_id','=',$this->id)
->where('quantity','>',0)
->get()
->sortBy(function($useritem, $key) {
return $useritem->item->type;
});
You can use withAggregate function to solve your problem
UserItems::withAggregate('item','type')
->where('user_id','=',$this->id)
->where('quantity','>',0)
->orderBy('item_type')
->get();
I know it's an old question, but you can still use an
"orderByRaw" without a join.
$items = UserItems
::where('user_id','=',$this->id)
->where('quantity','>',0)
->orderByRaw('(SELECT type FROM items WHERE items.id = user_items.item_id)')
->get();
For a one to many relationship, there is an easier way. Let's say an order has many payments and we want to sort orders by the latest payment date. Payments table has a field called order_id which is FK.
We can write it like below
$orders = Order->orderByDesc(Payment::select('payments.date')->whereColumn('payments.order_id', 'orders.id')->latest()->take(1))->get()
SQL Equivalent of this code:
select * from orders order by (
select date
from payments
where order_id = payments.id
order by date desc
limit 1
) desc
You can adapt it according to your example. If I understood right, order's equivalent is user and payment's equivalent is item in your situation.
Further reading
https://reinink.ca/articles/ordering-database-queries-by-relationship-columns-in-laravel
I found another way of sorting a dataset using a field from a related model, you can get a function in the model that gets a unique relation to the related table(ex: table room related to room category, and the room is related to a category by category id, you can have a function like 'room_category' which returns the related category based on the category id of the Room Model) and after that the code will be the following:
Room::with('room_category')->all()->sortBy('room_category.name',SORT_REGULAR,false);
This will get you the rooms sorted by category name
I did this on a project where i had a DataTable with Server side processing and i had a case where it was required to sort by a field of a related entity, i did it like this and it works. More easier, more proper to MVC standards.
In your case it will be in a similar fashion:
User::with('item')->where('quantity','>',0)->get()->sortBy('item.type',SORT_REGULAR,false);
$users
->whereRole($role)
->join('address', 'users.id', '=', 'address.user_id')
->orderByRaw("address.email $sortType")
->select('users.*')
you can simply do it by
UserItems::with('item')
->where('user_id','=',$this->id)
->where('quantity','>',0)
->orderBy(
Item::select('type')
->whereColumn('items.useritem_id','useritems.id')
->take(1),'desc'
)
->get();

Using Laravel 5, how do I get all() and order by belongsToMany relationship by the pivot table

I have Deals and Faq's. I have functional relationships working and I can reference $deal->faqs() and it returns the right faqs.
The problem I am trying to solve comes up as I administer the faqs related to a deal. In my Deal admin view (new / edit) I am getting all the Faq's.
$faqs = \App\Faq::all();
This works great, and I am even able to check if an faq is related to a deal through my checkbox: in the view:
{!! Form::checkbox('faqlist[]', $faq->id, $deal->faqs->contains($faq->id) ? true : false) !!}
So now we have a list of all the faqs and the correct ones are checked.
I have setup an order column on the pivot table (deal_faq). That table consists of:
deal_id
faq_id
timestamps
order
In my form, I have a drag and drop ordering solution (js) built and working. By working I mean, I can drag/drop and a hidden field value is updated to reflect the correct order.
When creating a deal, this is no problem. Get all the faq's, check a few to associate, drag to order, then save.
When editing a deal, I need to load based on the order column in the deal_faq table. This is my issue.
I have tried a few things but always get an error. An example of what I have tried is:
$faqs = \App\Faq::orderBy('deal_faq.order', 'asc')->get();
This returns an error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'deal_faq.order' in 'order clause' (SQL: select * from `faq` order by `deal_faq`.`order` asc)
I think the issue is that I am trying to get all, but order by a field that only exists for the related faqs since the order field is on the deal_faq. Just not sure how to solve.
In essence you need to join the pivot table and then apply the order
$faqs = \App\Faq::join('deal_faq', 'faqs.id', '=', 'deal_faq.faq_id')
->orderBy('deal_faq.order', 'asc')
->get();
You may need to adjust table and column names to match your schema.
Now you can extract this logic into a scope of the Faq model
class Faq extends Model
{
...
public function scopeOrdered($query)
{
return $query->join('deal_faq', 'faqs.id', '=', 'deal_faq.faq_id')
->orderBy('deal_faq.order', 'asc');
}
}
and then use it like this
$faqs = \App\Faq::ordered()->get();
UPDATE:
This works to get the FAQ's in order but it only get the ones that
have been associated. There will be FAQ's that are not associated and
thus not ordered.
In this case you just need to use an outer join - LEFT JOIN. The scope definition would then look like this
public function scopeOrdered($query)
{
return $query->join('deal_faq', 'faqs.id', '=', 'deal_faq.faq_id', 'left')
->orderBy('deal_faq.order', 'asc');
}
or a bit more expressively
public function scopeOrdered($query)
{
return $query->leftJoin('deal_faq', 'faqs.id', '=', 'deal_faq.faq_id')
->orderBy('deal_faq.order', 'asc');
}
Please consider adding a secondary order (i.e. by id, or any other field(s) in faqs table that make sense). This way you'd have a deterministically ordered resultsets each time regardless of whether you have an explicit order defined in a pivot table or not.

Eloquent query if value in relationship isn't same as value in other relationship

I have four models: Item, Car, ItemLocation and Branch.
An Item has a Car and a ItemLocation via a person_id and a item_location_id field in the DB.
A Car has a branch_id which links to Branch, and an ItemLocation also has a branch_id in the same way.
What I want to do is to select all Items where their Cars's branch is not equal to their ItemLocation's branch.
I tried this statement, though I knew that it wouldn't work:
Item::with('car','item_location')
->where('car.branch_id', '<>', 'item_location.branch_id')
->get();
I'm aware of querying on relationships, but don't understand how to do that across relationships like this.
Any ideas?
Querying relationships won't help you, since you want to compare values from separate tables. You need joins:
$items = Item::select('items.*')
->join('item_locations as il', 'il.id', '=', 'items.item_location_id')
->join('cars', function ($j) {
$j->on('cars.person_id', '=', 'items.id')
->on('cars.branch_id', '<>', 'il.branch_id');
})
->get()
This will fetch all the items having both cars and item_location and matching your criteria. If you want to include also ones that don't have either of the relations, then use leftJoins instead and whereNull('cars.id')
ps. It's hard to read your question. Instead of describing these relationships, better simply show the tables with relevant fields.

Unexpected behavior in Magento collection filters used in loops

I'm seeing an unexpected behavior in collections that maybe someone can enlighten me on (or maybe it's a bit of PHP I don't grok) (sorry for the length of this post, but I had to include sample code and results).
My goal was to write a report where I get an order and it's order items, then go to the invoice and shipment data for the order and get the matching order item data from their items. I know that there is only one invoice and shipment for all orders, so even though Magento uses a 1-M relationship between orders and invoices/shipments, I can take advantage of it as if it were 1-1
I know that the items are all related using the order_item_id fields, so I tried to write a function that used the the following call -
$invoiceItem = $order
->getInvoiceCollection()
->getFirstItem()
->getItemsCollection()
->addFieldToFilter('order_item_id', $orderItem->getItemId())
->getFirstItem();
But that didn't results I expected, what I saw was the same invoice item returned regardless of the order item id used in the filter.
So, to try to understand the problem, I wrote the following small program to see how the queries where created.
<?php
require dirname(__FILE__).'/../app/Mage.php';
umask(0);
Mage::app('default');
$orders = Mage::getResourceModel('sales/order_collection')
->addAttributeToSelect('*')
->addAttributeToFilter('state', 'processing')
->addAttributeToSort('created_at', 'desc')
->addAttributeToSort('status', 'asc')
->load();
foreach ($orders as $order) {
echo "\ngetting data for order id ". $order->getId();
$items = $order->getAllItems();
$invoice = $order->getInvoiceCollection()->getFirstItem();
foreach ($items as $orderItem) {
echo "\n\ngetting data for order item id ". $orderItem->getItemId();
$invoiceItems = $order
->getInvoiceCollection()
->getFirstItem()
->getItemsCollection()
->addFieldToFilter('order_item_id', $orderItem->getItemId());
echo "\n".$invoiceItems->getSelect();
}
die; //just quit after one iteration
}
The output from this program was the following -
getting data for order id 7692
getting data for order item id 20870
SELECT `main_table`.* FROM `sales_flat_invoice_item` AS `main_table` WHERE (parent_id = '7623') AND (order_item_id = '20870')
getting data for order item id 20871
SELECT `main_table`.* FROM `sales_flat_invoice_item` AS `main_table` WHERE (parent_id = '7623') AND (order_item_id = '20870') AND (order_item_id = '20871')
getting data for order item id 20872
SELECT `main_table`.* FROM `sales_flat_invoice_item` AS `main_table` WHERE (parent_id = '7623') AND (order_item_id = '20870') AND (order_item_id = '20871') AND (order_item_id = '20872')
As you can see, every time though the loop another "AND (order_item_id =" was added for each item Id that I was filtering on. I thought that every time though the loop, I'd be getting a fresh version of the collection from using $order->getInvoiceCollection().
So, can anyone tell me what's going wrong in my sample code and educate me on the correct way to do this?
Thanks!
Regarding your business question: need more info. Is the goal to generate a collection with order item objects which are EACH aware of invoice and shipment details? It seems like there are rendering concerns getting pushed into modeling concerns.
Regarding the select statement question: Varien collections have an optimization which prevents them from accessing the storage backend more than once. This standard behavior is achieved in DB collection instances by setting the _isCollectionLoaded property to true.
In your case, the invoice collection instance is created via the order instance stored in a protected property, and immediately load()ed via IteratorAggregate (invoked via foreach). Because you are using the same order object instance in each iteration, you are dealing with this loaded invoice collection instance and are effectively calling addFieldToFilter(/* next order id */) with each iteration, resulting in the ever-expanding WHERE clause. This specific optimization can easily be worked around by calling $order->reset(). This brings us back to the salient issue though, which is the need to better understand the goal and (likely) use a custom collection or to manipulate a collection to join in the specific data that you need.

Resources