I have 4 tables, items, listings, catitem_item, and item_listing.
items and listing is many to many relationship.
items and catitems is also many to many relationship.
catitems contains list of item's categories.
listings is like location where items located.
example listing shop A can have item chair and item chair have multiple catitem categories.
My goal is to get items which under list of categories such as category 1 AND 2 AND 3 ($cats
) and with listing information where this item located.
So if there are 6 listings for chair, it will return 6 chair results.
This is the query I have so far.
$items = DB::table('items')
->join('catitem_item', 'catitem_item.item_id', '=', 'items.id')
->join('item_listing', 'item_listing.item_id', '=', 'items.id')
->join('listings', 'item_listing.listing_id', '=', 'listings.id')
->whereIn('catitem_item.catitem_id', $cats)
//->groupBy('items.id')
//->having(DB::raw('count(*)'), '=', count($cats))
->select('items.id', 'items.name', 'items.price', 'items.slug', 'item_listing.listing_id', 'listings.name as listing_name', 'listings.slug as listing_slug')
->get();
Note that the way you are trying to do it, you might get multiple rows per item (once per related listing). A better way would be to have an array of listings per item.
If you use eloquent models and you have setup the relations correctly, you could try the following:
$cats = [1, 2, 3];
$query = Item::with('listings');
foreach ($cats as $cat) {
$query->whereHas('catitems', function($q) use($cat) {
$q->where('id', $cat);
});
}
$items = $query->get();
Now every item shoud have a listings
property. For example for the first item you can access the listings the following way:
$item1 = $items[0];
$listings1 = $item1->listings;
Note that whereHas()
will probably create a correlated EXISTS
subquery for every entry in the $cats
array. If that is to slow, you can use a JOIN query like:
$items = Item::with('listings')
->join('catitem_item', 'catitem_item.item_id', '=', 'items.id')
->whereIn('catitem_item.catitem_id', $cats)
->groupBy('items.id')
->having(DB::raw('count(*)'), '=', count($cats))
->select('items.*')
->get();
If you don't use eloquent, you can also do the "eager loading" yourself.
$items = DB::table('items')
->join('catitem_item', 'catitem_item.item_id', '=', 'items.id')
->whereIn('catitem_item.catitem_id', $cats)
->groupBy('items.id')
->having(DB::raw('count(*)'), '=', count($cats))
->select('items.*')
->get()
->keyBy('id');
foreach ($items as $item) {
$item->listings = [];
}
$itemIds = $items->pluck('id');
$listings = DB::table('listings')
->join('item_listing', 'item_listing.listing_id', '=', 'listings.id')
->whereIn('item_listing.item_id', $itemIds)
->groupBy('listings.id')
->select('listings.*', DB::raw('group_concat(item_listing.item_id) as item_ids'))
->get();
foreach ($listings as $listing) {
$itemIds = explode(',', $listing->item_ids);
foreach ($itemIds as $itemId) {
$items[$itemId]->listings[] = $listing;
}
$listing->forget('item_ids');
}