Get unique relation rows with distinct in Laravel - php

Let's say I have Category model with hasMany relation to Product model. Product has color column.
I want to return all categories with all colors that exists in this category, so I tried:
return Category::with(['products' => function ($query) {
$query->distinct('color');
}])->get();
Thanks to this I could later foreach or pluck category->products to get unique colors list. I know I can just get all products in every category, and then filter unique colors, but by doing this I would have to query for example 1000 products per category, instead just 5, which is unnecessary resource heavy. That's why I'm trying to do this on SQL level not PHP.
But this code does not work. There are no errors, it just still returns all products with duplicated colors. Why?
Edit:
Not sure why but my code works if I add select() with used columns before discrinct, and then distinct is making unique rows by all choosed columns. No "color" param required in distinct. Not sure why it works that way, need to dive deeper into SQL docs.

Have you tried this code? Somehow, this will reduce your unnecessary query.
$categories = Category::with([
'products'=> fn($q) => $q->select(['category_id', 'color'])->distinct('color')
])
->select('id') // select required column in categories table
->whereHas('products')
->get();
$colors = $categories->map(function($category) {
return $category->products->pluck('color');
})->toArray();
$color = [];
for ($i=0; $i < count($colors); $i++) {
$color = array_merge($color, $colors[$i]);
}
$uniqueColor = array_unique($color);
return $categories;

Related

Laravel pagination with specific foreign key

I need to make pagination in Laravel, and as I read laravel documentation here is a several way to do it. but in my cases it is a little bit complexity. So let say I've two table in my DB, table1 and table2(many to many relationship), I want to paginate table1 with specific ids. So I mean if table1 contains ids 1,2,3,4,5 ... n, I want to paginate only rows which id is 2 and 3
I have tried :
private function check_wear($type){
$products_id = array(); //here is ids which $type is set as 'ON'
$wears = DB::select("SELECT prod_id FROM wears WHERE ".$type." = 'on' "); //Select specific products ids. (in our case it is **table1**)
foreach ($wears as $wr){
array_push($products_id,$wr->prod_id);
}
return $products_id; //return array with ids which should be paginate
}
public function hat($locale){
$hat_prod_ids = $this->check_wear('coat');
$p=12;
$products = DB::table('products')->paginate($p); // 12 per page
dd($products);
my code only paginate one table with all data, is there any built-in function to somehow write a logic condition? for pagination
If you need to filter your pagination with elements which you've in $hat_prod_ids, you're able to use WhereIn() method, that one checks if your elements (in our case ids) exist in your $hat_prod_ids and returns true or false
$products = DB::table('products')
->whereIn('id',$hat_prod_ids)
->paginate($p);
add that code and now you will be able to paginate only that ids which is matched in your array

how to compare two collection and get match records

I am using Laravel and I have two different collections that contain ID of products
First one is colorProduct and second is tagProduct
so I want to compare these two and get only same ID of products so how can I do this?
$colorProducts = Color::where('code', $request->color)->get()->first()->products;
$tagProducts = $tag->products->where('shop_id', $shop->id);
$colorAndTagProducts = collect();
foreach ($colorProducts->toBase()->merge($tagProducts)->unique('id')->groupBy('id') as $allProducts) {
if ($allProducts->count() >= 1) {
$colorAndTagProducts[] = $allProducts->first();
}
}
here
$colorAndTagProducts
gives me all records form both collection but I only want same record
I dont know, if I understand correctly, but maybe like this?
I suppose Color and Product are in many to many relationship. And Product and Shop/tag in one to many.
$colorId = Color::where('code', $request->color)->get()->first()->id;
$shopId = $shop->id;
$products = Product::whereHas('colors', function ($query) use ($colorId) {
$query->where('id', $colorId); //id or color_id
})->where('shop_id', $shopId)->get();
intersect()
The intersect method removes any values from the original collection
that are not present in the given array or collection. The resulting
collection will preserve the original collection's keys:
I did it with this method

Sort elements by a value calculated on the go (not stored)

In my project I'm using Laravel 5.5 with Eloquent and Scout drivers to build a sort of search engine API endpoint.
In my scenario I have a SQL table items that has a price_factor property.
The table is also stored inside an Elasticsearch index.
With this value and with the number of the user related with that item, I can calculate the right price of the object.
An easy example is the item with id: 1 has price_factor: 2 and it is related to 5 users.
The right price of the item is 2 * 5 = 10, of course.
Now, I have to query all results and use where conditions, sorting them by that calcolated property, and return paginated results.
For example: get all items with price between 5 and 10, sort them by price and paginate by 10 elements per page.
In Eloquent I will write:
// Set filters to be applied
$filters = [
['price', '>', 5],
['price', '<', 10],
];
// Sort by "update_at", or "price"
$sort = "price";
// Order by "ASC" mode
$order = "ASC";
// Number of rows per page
$rows = 10;
// Get items
$result = Item::orderBy(
$sort,
$order
// Get related user with item record, where has...
)->with(['users'])->whereHas(
// Take users related models
'users',
// Take the model related ("user") and use filters
function($relation_model) use ($filters) {
// Apply where condition with filters
$relation_model->where($filters);
}
// Paginate for the numbers of row requested
)->paginate($rows);
How to do that if price is not a property of table items?
Should I store price inside the table and update it on every new user relation added? (or every removed relation too).
Is this the correct approach?
I was thinking about website like eBay or other real-time auction that have a similar situation of mine: how do you think they have solved?
Thanks in advance.
Assuming that you have a user_items table that keeps track of the items owned by user, I think something like this might work:
$result = Item::selectRaw('items.*, price_factor * (SELECT COUNT(id) FROM user_items WHERE user_items.item_id = items.id) AS price')
->with(['users'])
->havingRaw('price > ? AND price < ?', [5, 10])
->orderBy('price', 'asc')
->paginate($rows);
You can calculate the price on the fly and alias it. Finally you can apply a havingRaw clause to it that will check if the price is between the range it needs to be. Here's a working example of this in action.
There might be better ways to do this. I am also curious to know :)

Laravel relations, select next raleted row from database.

I have related items in my database. I selected all of items from database by related id:
$next_stock = $this->model->get()->where('part_id', $in_data['part_id'])->all();
and I collection of rows grouped by one specific id, like on the picture. All of them selected by "part_id":
Selection Of Items
Grouped By Same Id
Also with this line of code i can select one of the items from this collection:
$next_stock = $this->model->get()->where('id', $old_stock['id'])->where('part_id', $in_data['part_id'])->first();
But how can I select the following items after this one?
Or, how can I select second or third item from this collect?
I cannot just increase id number by one from first, because sometimes this item ids not following each other.
Having a collection, you can take a specific element in the position with a combination of take() and last().
$collection = $this->model->get()->where('part_id', $in_data['part_id'])->all();
$second = $collection->take(2)->last(); //if this doesnt work, do it in 2 steps
$third = $collection->take(3)->last(); //if this doesnt work, do it in 2 steps
If you don't have a collection, take directly from database like this
$second = $this->model
->where('part_id', $in_data['part_id'])
->skip(1)
->first();
If it doesn't work with first()
$collect = $this->model
->where('part_id', $in_data['part_id'])
->skip(1)
->take(1)
->get();
$second = $collect->first();
Edit
skip() and take() are actually part of the query builder, not eloquent model. So it won't work with Eloquent in Laravel 5.4
Try with
$collect = $this->model
->where('part_id', $in_data['part_id'])
->get(1); //For the second record, 0 being the first
If you aren't doing it yet, you should set your model's relationships.
E.g. If you use "one-to-many", Eloquent will automatically determine the proper foreign key column on the model for you.
$parts = App\Stock::find(1)->partId;
foreach ($parts as $part) {
//
}

Symfony2 using Query builder to load a specific ManyToMany object

My product can have many categories. In one part of the object however, I need to get a specific Category. So instead of getting all the categories and then in a for loop search for specific one, I need to get only this specific category. For that I am using query builder.
public function findProduct($id) {
$qb = $this->createQueryBuilder('p')
->addSelect(array('p', 'cat')) // many to many table
->addSelect(array('p', 'category')) // the category entity
->leftJoin('p.category', 'cat')
->leftJoin('cat.category', 'category')
->andWhere("category.id = 15") // error here
->SetParameter('id', $id);
return $qb->getQuery()->getOneOrNullResult();
}
With this query I am easily able to do $product->getCategory[0]([] since array) and get only the category that I need(in this example category with id=15)
THE PROBLEM:
However if the product doesnt have a category with a specific id.. It returns whole product null..
So If i do:
$product = $em->getRepository('MpShopBundle:Product')->findProduct($id); = null
But instead it should be like this:
$product = $em->getRepository('MpShopBundle:Product')->findProduct($id); = object
$product->getCategory() = null
How can I make this work in query builder? Is that even possible?
This should work. Instead of constraining your whole query (which is what that does) just constrain the join).
leftJoin('cat.category', 'category', 'WITH', 'category.id = 15')
This way you should get your product always & category only if id == 15.

Categories