Laravel - query to join tables in Many to many relation - php

I have the below tables
posts
- id
- name
categories
- id
- name
category_post
- post_id
- category_id
Post.php
public function Category()
{
return $this->belongsToMany(Category::class);
}
Category.php
public function posts()
{
return $this->belongsToMany(Post::class);
}
A post may contain many categories.
I want to query all posts related to the categories of any given post in the least number of database queries.
For example, If a post belongs to three categories, I want to get all the posts related to that three categories. I could achieve this in 4 DB queries as below.
$post = Post::find(1);
$categoryIds = $post->category()->pluck('id');
$postIds = DB::table('category_post')
->whereIn('category_id', $categoryIds)
->pluck('post_id')
->unique();
$relatedPosts = DB::table('posts')
->whereIn('id', $postIds)
->get();
Any help would be highly appreciated to reduce DB queries or refactor the code in laravel way.
Thanks

Your given example can be written like:
$postWithRelatedPosts = Post::whereHas('category.post', function ($q) {
$q->where('id', 1);
})->get();
This is a single query but has 2 sub-queries within it. It generates something like:
select *
from `posts`
where exists (
select *
from `categories` inner join `category_post` on `categories`.`id` = `category_post`.`category_id`
where `posts`.`id` = `category_post`.`post_id` and exists (
select *
from `posts` inner join `category_post` on `posts`.`id` = `category_post`.`post_id`
where `categories`.`id` = `category_post`.`category_id` and `id` = ?
)
)

Related

withExists() or withCount() for nested relationship (Many To Many and One To Many) in Laravel Eloquent

I have the following relationships in models:
Product.php
public function skus()
{
return $this->belongsToMany(Sku::class);
}
Sku.php
public function prices()
{
return $this->hasMany(Price::class);
}
I need to get an attribute indicating whether a product has at least one price or not (in the extreme case, just the number of prices).
Product::withExists('sku.prices') or Product::withCount('sku.prices')
I know about this repository https://github.com/staudenmeir/belongs-to-through, but I prefer to use complex query once
UPDATE: I have already written a sql query for this purpose, but I don't know how to do it in Laravel:
SELECT
*,
EXISTS (SELECT
*
FROM prices
INNER JOIN skus
ON prices.sku_id = skus.id
INNER JOIN product_sku
ON skus.id = product_sku.sku_id
WHERE products.id = product_sku.product_id
) AS prices_exists
FROM products
Here you can get at least one record
$skuPrice = Sku::with('prices')
->has('prices', '>=', 1)
->withCount('prices')
->get();

How to fetch data with pivot table using where condition on pivot in laravel4

How can I fetch data with belongsToMany relation models.
Table tags
- id
- name
...
Table photo_entries
- id
...
Table photo_entry_tag
- id
- tag_id
- photo_entry_id
Tag.php
public function photo_entry()
{
return $this->belongsToMany('PhotoEntry');
}
PhotoEntry.php
public function tags()
{
return $this->belongsToMany('Tag');
}
Now I need to fetch photo entries from photo_entries table with their tags where tag_id=1.
I have tried this:
$photos = PhotoEntry::orderBy('votes', 'desc')->with(array('tags' => function($query)
{
$query->where('tag_id', 1);
}))->paginate(50);
But its not giving me the proper result, its returning all photos. I am not getting what is the issue.
Can any one please help me.
You need to select all records form photo_entries table that has a relation in photo_entry_tag table so to do that your query should be like this
$tag_id = 1;
$query = "SELECT * FROM `photo_entries` WHERE `id` IN (SELECT `photo_entry_id` FROM `photo_entry_tag` WHERE `tag_id` = {$tag_id})";
Also the equivalence code in eloquent will be like the code below
$tag_id = 1;
$photos = PhotoEntry::whereIn('id', function($query) use ($tag_id) {
$query->select('photo_entry_id')->from('photo_entry_tag')->whereTagId($tag_id
);
})->get();
Also you can add orderBy('votes', 'desc') just before get() to sort the result

laravel: convert many to many (but only one related item) mysql query to a laravel query

Hi I'm having trouble converting a mysql query that I've been working on into a laravel eloquent query and need some help.
I have a reservations table which links to a product table with a many to many relationship. I want to pull all the reservations and just the first product it finds regardless of how many products are related to the reservation.
Here's my sql:
SELECT reservations.id,
reservations.play_date,
reservations.group_size,
reservations.status,
reservations.event_title,
t4.product_id,
t4.id AS link_id,
p1.name,
CONCAT_WS(" ", customers.first_name, customers.last_name, customers.group_name) AS customerName,
reservations.event_type
FROM reservations
LEFT JOIN customers ON reservations.customer_id = customers.id
LEFT JOIN
(SELECT *
FROM product_reservation AS t3
GROUP BY t3.reservation_id ) AS t4 ON t4.reservation_id = reservations.id
LEFT JOIN products AS p1 ON t4.product_id = p1.id
I can place this as a raw query but that produces an array with the result - I need to be able to create a query object so I can work with another module on the results
Is there an eloquent way of doing this - or how can I get this query to work in laravel?
Thank you
Yeah, you can use Eloquent relationships. They would look something like this...
class Reservation extends Eloquent
{
public function products()
{
return $this->belongsToMany('Product');
}
}
class Product
{
public function reservations()
{
return $this->belongsToMany('Reservation');
}
}
$reservations = Reservation::with(array('products', function($q) {
$q->take(1);
}))->get();
foreach($reservations as $reservation) {
echo $reservation->name;
foreach($reservation->products as $product) {
echo $product->description;
}
}

Laravel many to many loading related models with count

I am trying to link 4 tables and also add a custom field calculated by counting the ids of some related tables using laravel.
I have this in SQL which does what I want, but I think it can be made more efficient:
DB::select('SELECT
posts.*,
users.id AS users_id, users.email,users.username,
GROUP_CONCAT(tags.tag ORDER BY posts_tags.id) AS tags,
COUNT(DISTINCT comments.id) AS NumComments,
COUNT(DISTINCT vote.id) AS NumVotes
FROM
posts
LEFT JOIN comments ON comments.posts_id = posts.id
LEFT JOIN users ON users.id = posts.author_id
LEFT JOIN vote ON vote.posts_id = posts.id
LEFT JOIN posts_tags ON posts_tags.posts_id = posts.id
LEFT JOIN tags ON tags.id = posts_tags.tags_id
GROUP BY
posts.id,
posts.post_title');
I tried to implement it using eloquent by doing this:
$trending=Posts::with(array('comments' => function($query)
{
$query->select(DB::raw('COUNT(DISTINCT comments.id) AS NumComments'));
},'user','vote','tags'))->get();
However the NumComments value is not showing up in the query results.
Any clue how else to go about it?
You can't do that using with, because it executes separate query.
What you need is simple join. Just translate the query you have to something like:
Posts::join('comments as c', 'posts.id', '=', 'c.id')
->selectRaw('posts.*, count(distinct c.id) as numComments')
->groupBy('posts.id', 'posts.post_title')
->with('user', 'vote', 'tags')
->get();
then each post in the collection will have count attribute:
$post->numComments;
However you can make it easier with relations like below:
Though first solution is better in terms of performance (might not be noticeable unless you have big data)
// helper relation
public function commentsCount()
{
return $this->hasOne('Comment')->selectRaw('posts_id, count(*) as aggregate')->groupBy('posts_id');
}
// accessor for convenience
public function getCommentsCountAttribute()
{
// if relation not loaded already, let's load it now
if ( ! array_key_exists('commentsCount', $this->relations)) $this->load('commentsCount');
return $this->getRelation('commentsCount')->aggregate;
}
This will allow you to do this:
$posts = Posts::with('commentsCount', 'tags', ....)->get();
// then each post:
$post->commentsCount;
And for many to many relations:
public function tagsCount()
{
return $this->belongsToMany('Tag')->selectRaw('count(tags.id) as aggregate')->groupBy('pivot_posts_id');
}
public function getTagsCountAttribute()
{
if ( ! array_key_exists('tagsCount', $this->relations)) $this->load('tagsCount');
$related = $this->getRelation('tagsCount')->first();
return ($related) ? $related->aggregate : 0;
}
More examples like this can be found here http://softonsofa.com/tweaking-eloquent-relations-how-to-get-hasmany-relation-count-efficiently/
as of laravel 5.3 you can do this
withCount('comments','tags');
and call it like this
$post->comments_count;
laravel 5.3 added withCount

Can I query relations using an INNER JOIN instead of two queries in Eloquent?

Can I write something like this:
$post = Post::join(['author'])->find($postId);
$authorName = $post->author->name;
To produce only ONE select with inner join (no 2 selects) and without using DB query builder
SELECT
post.*,
author.*
FROM post
INNER JOIN author
ON author.id = post.author_id
WHERE post.id = ?
You can do it in Eloquent using the join method:
$post = Post::join('author', function($join)
{
$join->on('author.id', '=', 'post.author_id');
})
->where('post.id', '=', $postId)
->select('post.*', 'author.*')
->first();
Please note that your results in $post will be an object where their attributes will correspond to the result set, if two columns has the same name it will be merged. This happen when using:
->select('post.*', 'author.*')
To avoid this, you should create alias to those columns in the select clause as shown below:
->select('post.id AS post_id', 'author.id AS author_id')
Try
Post::join('author',function($join){
$join->on('author.id','=','post.author_id');
})->where('post.id','=',$postId)->select('post.*','author.*');

Categories