Laravel 5.4 Raw Join Query - mysql

I have a table TBL_POST used to store blog posts. A post can be assigned to multiple categories, there is a column, cat_id that stores category ID's in comma separated pattern like 2,4,6. I want to use FIND_IN_SET() method in this line
->leftJoin(TBL_CAT.' as c', 'p.cat_id', '=', 'c.id')
to show the associated category names. How can I do that?
public static function getPostWithJoin($status="")
{
$query = DB::table(TBL_POST .' as p')
->select('p.id','p.post_title','p.post_status','u.name as author','c.name as cat_name','p.updated_at')
->leftJoin(TBL_ADMINS.' as u', 'p.post_author', '=', 'u.id')
->leftJoin(TBL_CAT.' as c', 'p.cat_id', '=', 'c.id')
->where('p.post_type','post');
if($status!="all") {
$query->where('p.post_status',$status);
}
$query->orderby('p.id','DESC');
$data = $query->paginate(20);
return $data;
}

You can use callback to create more complicated join query.
->leftJoin(TBL_CAT, function($query){
$query->on(TBL_CAT.'id', '=', 'p.cat_id')->where("**", "**", "**");
})
Here is link on laravel doc - https://laravel.com/docs/5.4/queries#joins "Advanced Join Clauses" section.
UPD::
As mentioned in comment it is not good idea to have string for such types of data. Cause search by equality should be much simpler than string check. Even if your amount of data should not have big difference, you never know what will happen with your app in future.
But if you still want to do that i think you can try like this
->leftJoin(TBL_CAT, function($query){
$query->where(DB::raw("FIND_IN_SET(".TBL_CAT.".id, p.cat_id)"), "<>", "0");
})
Join that will check existence of id in cat_id.

Related

Add result of subquery to Eloquent query in Laravel 9

In Laravel 9 I am trying to add the result of a subquery to a query(for lack of better wording) and I am stuck. More concretely, I am trying to load all products and at the same time add information about whether the current user has bought that product.
Why do I want to do this?
I am currently loading all products, then loading all bought products, then comparing the 2 to determine if the user has bought a product, but that means extra queries which I would like to avoid. Pretend for the sake of this question that pagination doesn't exist(because when paginating the impact of those multiple queries is far diminished).
There is a many to many relationship between the 2 tables users and products, so these relationships are defined on the models:
public function products()
{
return $this->belongsToMany(Product::class);
}
and
public function users()
{
return $this->belongsToMany(User::class);
}
What I have tried so far:
I created a model for the join table and tried to use selectRaw to add the extra 'column' I want. This throws a SQL syntax error and I couldn't fix it.
$products = Product::query()
->select('id', 'name')
->selectRaw("ProductUser::where('user_id',$user->id)->where('product_id','products.id')->exists() as is_bought_by_auth_user")
->get();
I tried to use addSelect but that also didn't work.
$products = Product::query()
->select('id', 'name')
->addSelect(['is_bought_by_auth_user' => ProductUser::select('product_id')->where('user_id',$user?->id)->where('product_id','product.id')->first()])
->get();
I don't even need a select, I actually just need ProductUser::where('user_id',$user?->id)->where('product_id','product.id')->exists() but I don't know a method like addSelect for that.
The ProductUser table is defined fine btw, tried ProductUser::where('user_id',$user?->id)->where('product_id','product.id')->exists() with hardcoded product id and that worked as expected.
I tried to create a method on the product model hasBeenBoughtByAuthUser in which I wanted to check if Auth::user() bought the product but Auth wasn't recognized for some reason(and I thought it's not really nice to use Auth in the model anyway so didn't dig super deep with this approach).
$products = Product::query()
->select('id', 'name')
->addSelect(\DB::raw("(EXISTS (SELECT * FROM product_user WHERE product_users.product_id = product.id AND product_users.user_id = " . $user->id . ")) as is_bought_by_auth_user"))
->simplePaginate(40);
For all attempts $user=$request->user().
I don't know if I am missing something easy here but any hints in the right direction would be appreciated(would prefer not to use https://laravel.com/docs/9.x/eloquent-resources but if there is no other option I will try that as well).
Thanks for reading!
This should do,
$id = auth()->user()->id;
$products = Product::select(
'id',
'name',
DB::raw(
'(CASE WHEN EXISTS (
SELECT 1
FROM product_users
WHERE product_users.product_id = products.id
AND product_users.user_id = '.$id.'
) THEN "yes" ELSE "no" END) AS purchased'
)
);
return $products->paginate(10);
the collection will have purchased data which either have yes or no value
EDIT
If you want eloquent way you can try using withExists or withCount
i.e.
withExists the purchased field will have boolean value
$products = Product::select('id', 'name')->withExists(['users as purchased' => function($query) {
$query->where('user_id', auth()->user()->id);
}]);
withCount the purchased field will have count of found relationship rows
$products = Product::select('id', 'name')->withCount(['users as purchased' => function($query) {
$query->where('user_id', auth()->user()->id);
}]);

Laravel Query builder where() for when Product has to have multiple tags (with product_tags pivot table many-to-many)

I am new to Laravel and I got a complicated query to build. I managed to sort it except for when a user asks for multiple tags (tags = 1, 2, 3). Product shown has to have all tags that the user asks (not only one or two but all of them).
I have the query in SQL (this example is two tags, I would switch it to different numbers based on how many tags are passed):
SELECT m.*
FROM meals m
WHERE EXISTS (
SELECT 1
FROM meals_tags t
WHERE m.id = t.meals_id AND
t.tags_id IN (227,25)
HAVING COUNT(1) = 2
);
This one works perfectly, but I have an issue when translating it to an Eloquent query builder where method.
I have another where method included in the same query so I want to attach this one as well.
I have tried this:
DB::table('meals')
->select('id')
->where(function ($query) use ($parameters) {
if (isset($parameters['tags'])) {
$array = explode(',', $parameters['tags']);
$query->select(DB::raw(1))
->from('meals_tags')
->where('meals.id', '=', 'meals_tags.meals_id')
->whereIn('meals_tags.tags_id', $array)
->having(DB::raw('COUNT(1)'), '=', count($parameters['tags']));
}
});
But I can't find a way. New to Laravel and PHP.
Let's say I have table meals and tags with meals_tags to connect them (many to many).
$paramaters are comming from GET (...?tags=1,2,3&lang=en&another=something...), they are an array of key-value pairs (['tags' => '1,2,3', 'lang' => 'en'])
$parameters['tags'] is of type string with comma separated numbers (positive integers greater than 0) so that I have the option to explode it if needed somewhere.
Assuming that you have defined belongsToMany (Many-to-Many) meal_tags relationship on the Meal model, you can try
Meal::select('id')
->when(
$request->has('tags'),
function($query) use ($request) {
$requestedTagIds = explode(',', $request->tags);
return $query->whereHas(
'meal_tags',
fn ($query) => $query->whereIn('tags_id', $requestedTagIds),
'=',
count($requestedTagIds)
);
}
)
->get();

how to add conditional where clause in sql

Looking for improved answer
In Laravel, I am using a raw query. My question is how to add where clause depending on variable value
i.e. if $cid is present then the query should be
select * from user where aid=2 and cid=1;
If it is not present
select * from user where aid=2;
Ideally, I can do it like this
if($cid) {
$query = DB::select("select * from user where aid=2 and cid=1");
} else {
$query = DB::select("select * from user where aid=2");
}
Is there any way to do this without the above method?
This can be achieved with conditional clauses.
$users = DB::table('users')
->where('aid', 2)
->when($cid, function ($query) {
$query->where('cid', 1);
})
->get();
Please contextualize your question well, we don't know what kind of condition you are talking about, nor in what sense your question is asked.
normally the comment above would be enough but it is necessary to specify
Here are some examples from the documentation
$users = DB::table('users')
->where('votes', '=', 100)
->where('age', '>', 35)
->get();
$users = DB::table('users')->where('votes', 100)->get();
You may also pass an array of conditions to the where function. Each element of the array should be an array containing the three arguments typically passed to the where method:
$users = DB::table('users')->where([
['status', '=', '1'],
['subscribed', '<>', '1'],
])->get();
I encourage you to read the documentation which is very clear on this subject and to come back to me in case of misunderstanding
here

How add to collection only models with no record joined - Eloquent

In my model Questions I have simple relation to Standpoint
public function standpoints_byrel()
{
// return $this->hasMany('App\Models\Standpoint');
return $this->hasMany('App\Models\Standpoint', 'question_id');
}
Now,
I have yet another model Userattitude (tableuser_attitudes`) which allow users to upvote and downvote Standpoints.
I am able to list Standpoints, which were voted by a given user:
$user_attitudes = Userattitude::join('entitystandpoints', function ($q) use($questionid,$user) {
$q->where('user_attitudes.item_type', '=', 'entitystandpoint');
$q->on('user_attitudes.item_id', '=', 'entitystandpoints.id');
$q->where('entitystandpoints.question_id', '=', $questionid);
$q->where('user_attitudes.creator_id','=', $user);
})
->select('user_attitudes.*')
->get();
TO DO
Now I try to list all standpoints, which were NOT voted by the given user.
I have no idea how to do it using Eloquent.
Any help appreciated.
edit
condition to meet:
if an user votes up or down, a new model Userattitude is created. Therefore Standpoint models not down- or upvoted have nothing to join. still, in the Userattitude there are two fields for upvoting : 'attitude' and 'importance'. often one of them is null
Try with a left join where the left parameter of the join is null.
Something like this (but please check the syntax out, I'm not an Eloquent expert):
$user_attitudes = Userattitude::leftJoin('entitystandpoints', function ($q) use($questionid,$user) {
$q->where('user_attitudes.item_type', '=', 'entitystandpoint');
$q->on('user_attitudes.item_id', '=', 'entitystandpoints.id');
$q->where('entitystandpoints.question_id', '=', $questionid);
$q->where('user_attitudes.creator_id','=', $user);
})
->whereNull('entitystandpoints.id')
->select('user_attitudes.*')
->get();
Let me know.

Retrieve data from one table and order by related table column using Laravel eloquent

I have two models Category and Transaction the table structures are like this
Categories:
id,category_name,..
and
Transactions:
id,category_id,amount..
The relation is
Category hasMany transactions
public function transactions()
{
return $this->hasMany('App\Transaction');
}
Transactions blongsTo Category
public function category()
{
return $this->belongsTo('App\Category', 'category_id');
}
I want to retrieve all data of transaction table which are sorted by category name.
Most importantly I want to get it using the eloquent method.
I have tried eager load which I think doesn't work on the belongsTo relationship.
Here is the code I have used for the eager load.
$transactions = Transaction::with(['category' => function ($query) {
$query->orderBy('category_name', 'asc');
}])->paginate(10);
So far I can achieve this by writing a query like below, but I'd like to use the eloquent method.
$transactions = Transaction::select(DB::raw('transactions.*'))
->leftJoin(DB::raw('(select id,category_name from categories) as categories'), 'categories.id', '=', 'transactions.category_id')
->orderBy('category_name', 'asc')
->paginate(10);
It'd be nice if someone can help me with this. Thank You.
Note: I am using Laravel 5.1
You have to provide the method name that defines the relationship, in your case this is category.
$transactions = Transaction::all()->with('category')->group_by('category.name')->get();
$transactions = Transaction::with('categories')->group_by('category.name')->get();
$cs = Course::where(['courses.active' => 1])
->whereHas('course_dates', function ($join) use ($now) {
$join->where('course_dates.start_date_time', '>', $now);
$join->orderBy('course_dates.start_date_time', 'asc');
})
->whereHas('category', function ($join) use ($cat_slug) {
$join->where('categories.url_slug', '=', $cat_slug);
})
->whereHas('language', function ($join) use ($cat_slug) {
$join->where('languages.string_id', '=', strtoupper(App::getLocale()));
})
->with(['course_dates' => function($q){
$q->orderBy('course_dates.start_date_time', 'desc');
}])
->join('course_dates' ,'course_dates.course_id', '=', 'courses.id')
->orderby('course_dates.start_date_time')
->limit(7)
->get();
To return Eloquent models ordered by related model (hasMany) column, I had to join the tables and then orderBy, still get the models, but correctly ordered by course_date.start_date_time.
Laravel 5.7, I don't think there is a cleaner solution (at least after few hours of tinkering and searching the web).