Eloquent wherein with join multiple tables

2019-08-20 03:05发布

问题:

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();

回答1:

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');
}