How to group on multiple fields including nested eager columns - php

I have some carts, products and users.
I try to get statistics like number of carts per user. And here are my models:
user.php
class User {
public function products()
{
return $this->hasMany(Product::class, 'user_id');
}
}
cart.php
class Cart {
public function products()
{
return $this->hasMany(Product::class, 'cart_id');
}
}
product.php
class Product {
public function user()
{
return $this->belongsTo('User', 'user_id');
}
public function cart()
{
return $this->belongsTo(Cart::class, 'cart_id');
}
}
Here is my query:
$query = Cart::query()
->whereHas('products', function ($query) use ($filter) {
$query->where('whatever', $filter);
})
->join('product', 'product.cart_id', '=', 'cart.id')
->join('users', 'users.id', '=', 'product.user_id')
->groupBy('users.id')
->select('users.id as user_id')
->select('users.name')
->selectRaw('count(cart.id) as numberOfCarts')
->selectRaw('SUM(price)')
->orderBy('users.name')
->get();
What I get is number of products per user, but I want number of carts per user. when I try to group using cart.id like groupBy(['user.id', 'cart.id']), it is worse: I get several times the same user and at each time the number of products in cart. And this total gives the same as total of number of carts get previously.
I have added jointure because I don't make it work by trying grouping on nested eager relationship. So I make it simplier.
How to fix grouping to count users carts and not their products ?

The issue is that count() counts the number of records returned by your MySQL query, so if you're looking at 20 rows all with the same cart.id, it's still going to count all 20 of them.
You can reduce this to what you want by only counting unique cart.ids with distinct:
$query = Cart::query()
->whereHas('products', function ($query) use ($filter) {
$query->where('whatever', $filter);
})
->join('product', 'product.cart_id', '=', 'cart.id')
->join('users', 'users.id', '=', 'product.user_id')
->groupBy('users.id')
->select('users.id as user_id')
->select('users.name')
->selectRaw('count(distinct cart.id) as numberOfCarts') // << Changed line
->selectRaw('SUM(price)')
->orderBy('users.name')
->get();

Related

Take last 3 records from child. Php/Laravel

Help me please.
I'm trying to write a function where I get all the categories of my forum with the 3 most recently updated Topics in the given categories.
But according to the result, take(3) filters by id (where the id is not higher than 3), and I need to get the last 3 records.
public function index()
{
$forums = Category::with(['posts' => function ($q){
return $q->take(3)->get();
}])->get();
dd($forums);
}
you should order your complete query by update_at descending, only after you can take the first 3.
$q->orderBy('update_at', 'desc')->take(3)->get();
Your Categories table seems to be a different table from posts, so when a post is created or updated you should also set update_at of its category to now.
As far as I know you can not use take() or limit() inside with();
EDIT: solution that was selected by mr.Sardov is to use package staudenmeir/eloquent-eager-limit.
Link is provided below this answer.
So for you need to do is by limit it from model relationship.
For example:
class Category extends Model {
public function posts()
{
return $this->hasMany('App\Models\Post');
}
public function limitPosts()
{
return $this->hasMany('App\Models\Post')
->limit(3);
}
public function limitLatestPosts()
{
return $this->hasMany('App\Models\Post')
->orderBy('created_at', 'desc'). // or use ->latest()
->limit(3);
}
}
And that use it like this:
Category::query()
->with(['limitPosts' => function($query) {
$query->orderBy('created_at', 'desc'); // the last records
}])
->where('id', '<=', 3) // id not higher than 3
->get();
Or
Category::query()
->with('limitLatestPosts')
->where('id', '<=', 3) // id not higher than 3
->get();
Hope this can help you out.

Laravel paginate pivot tables

I have collections which contains custom products I need to paginate those collections products but I receive error.
data
With this query I can get my collection and it's products but I'm not able to paginate products.
$collection = Collection::where('slug', $slug)->where('status',
'active')->with('products')->first();
With this query I receive error
$collection = Product::with('collections')->whereHas('collections',
function($query) use($slug) { $query->where('slug',
$slug)->where('status', 'active')->first(); });
Error
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'shopping.product_id' doesn't exist (SQL: select * from `collection_products` inner join `product_id` on `collection_products`.`id` = `product_id`.`collection_product_id` where `products`.`id` = `product_id`.`product_id` and `slug` = test and `status` = active limit 1)
Code
Product model
public function collections()
{
return $this->belongsToMany(CollectionProduct::class, 'product_id');
}
Collection model
public function collection(){
return $this->belongsToMany(CollectionProduct::class);
}
public function products(){
return $this->belongsToMany(Product::class, 'collection_products', 'collection_id', 'product_id');
}
CollectionProduct model
public function collection()
{
return $this->belongsTo(Collection::class, 'collection_id','id');
}
Controller default query
public function single($slug){
$collection = Collection::where('slug', $slug)->where('status', 'active')->with('products')->first();
return view('front.collections.single', compact('collection'));
}
Question
How can I get my collection products with pagination ability?
Couple things:
You are trying to call the first() method inside of a relationship query in this line:
$collection = Product::with('collections')->whereHas('collections', function($query) use($slug) { $query->where('slug', $slug)->where('status', 'active')->first(); });
The methods first() and get() are used to execute the query, so you should keep them at the end of the chain of eloquent methods:
$collection = Product::with('collections')
->whereHas('collections', function($query) use($slug) {
$query->where('slug', $slug)->where('status', 'active');
})
->get();
https://laravel.com/docs/5.7/eloquent#retrieving-models
However, if you want to paginate the list of products, then what you really want is the paginate() method:
$collection = Product::with('collections')
->whereHas('collections', function($query) use($slug) {
$query->where('slug', $slug)->where('status', 'active');
})
->paginate(20); // will return 20 products per page
https://laravel.com/docs/5.7/pagination
Also, the collections() method on your Product model has product_id listed as the join table, and is joining to the CollectionProduct model instead of the Collection model.
Try this instead for your Product model:
public function collections()
{
return $this->belongsToMany(Collection::class, 'collection_products', 'product_id', 'collection_id');
}
i use following code and working very well
the first step get product
$product = Product()->find($product_id);
than i get collections and pagination
$collections = Product::find($product_id)->collections->paginate(20);
for example i used above code in this site.

How can I pick single record from one to many relation?

I have one to many relation based two tables users and games and there is also bridge table users_games (linking user_id to games).
I want to fetch a single record from games table based on provided game_id for specific user. I did some research and found whereHas() which is returning all games which are belongs to specific user. But I need to fetch one based on game_id. Can some one kindly let me know how can I fix issue in below script
$GameInfo = User::with('games')->whereHas('games', function ($query) use($request)
{
$query->where('game_id', '=', $request->game_id);
})->find(request()->user()->id);
Is this what you're trying to do?
$GameInfo = $request
->user()
->games()
->where('game_id', $request->game_id)
->first();
try this:
$GameInfo = User::with(['games' => function ($query) use($request)
{
$query->where('game_id', $request->game_id);
}])->whereHas('games', function ($query) use($request)
{
$query->where('game_id', '=', $request->game_id);
})->find(request()->user()->id);
If your relation 'games' is a hasMany() with table 'users_games', You can try this code
$GameInfo = User::with(['games' => function ($query) use($request)
{
$query->where('game_id', $request->game_id);
}])
->where('users.id', <user_id_variable>)
->first();
And the relation 'games' in User Model as
public function games()
{
return $this->hasMany('App\Models\UserGames', 'user_id', 'id');
}

Retrieve models that belongsToMany specific models

I've got a standard many-to-many relationship
class User {
public function roles() {
return $this->belongsToMany('Role');
}
}
class Role {
public function users() {
return $this->belongsToMany('User');
}
}
And it works very well.
But I need to select all the users that has exactly two specific roles.
$roleAdmin = Role::where('name', 'admin')->first();
$roleUser = Role::where('name', 'user')->first();
$users = //which users has BOTH $roleAdmin and $roleUser ??
Is it possible to achieve this using eloquent or I need a raw query?
PS the use-case is stupid, I know, it's just an abstraction of my real problem (that doesn't concern users and roles)
The best solution I found is to get admins and users and then use intersect() helper to get only users who are present both in $users and admins collections:
$users = User::whereHas('roles', function ($q) use($otherRoles) {
$q->where('name', 'user')->whereNotIn('name', $otherRoles);
})->get();
$admins = User::whereHas('roles', function ($q) use($otherRoles) {
$q->where('name', 'admin')->whereNotIn('name', $otherRoles);
})->get();
$result = $admins->intersect($users);
If you want to save some memory, you could pluck() only IDs, intersect() these and only then get users with whereIn().
This is not an eloquent solution(by all means)
$users = \DB::table('users')
->select(\DB::raw("GROUP_CONCAT(roles.name SEPARATOR '-') as `role_names`"), 'users.name')
->join('role_user', 'users.id', '=', 'role_user.user_id')
->join('roles', 'roles.id', '=', 'role_user.role_id')
->groupBy('users.id')
->having('role_names', '=', 'admin-user')
->get();
admin-user can be user-admin, which roles names comes first in the database. Please change table and column names as per your requirement

Laravel Eloquent using "with" with conditions

I have two tables, say Products and Biddings where one product can be bid by many users. Naturally I have two models:
class Product extends Model
{
public function biddings()
{
return $this->hasMany('App\Bidding');
}
}
class Bidding extends Model
{
public function product()
{
return $this->belongsTo('App\Product');
}
}
So, say I want to get all products along with the highest priced bidding I did something like this.
$productBidding = DB::table('biddings')
->select('*', DB::raw('max(price) as price'))
->join('products', 'products.id', '=', 'biddings.product_id')
->groupBy('product_id')
->get();
That works well BUT I kinda want to do it Eloquent way. So how do I convert Query Builder way to Eloquent? I am currently on this but do not know how to put the "max" condition in.
$productBidding = Products::with('biddings')
->get();
$productbinding=Bidding:with('product')
->get();
foreach($productbinding as $productbind)
{
echo $productbind->product->name; // example
}
I would extract the highest bid to a separate function on the Product model, like so:
public function highestBid() {
return $this->biddings()->max('price');
}
Then fetch the products and get the highest bid:
$products = Product::get();
foreach ($products AS $product) {
echo $product->highestBid();
}

Categories