How to get only one value in the filament table for with hasMany relationship?
I have two DB tables:
products
id
sku
1
SKU_1
2
SKU_2
product_descriptions
id
product_id
translation_id
name
1
1
1
Opel
2
1
2
Vauxhall
In my Product model I have hasMany relationship
public function productDescriptions(): HasMany
{
return $this->hasMany(ProductDescription::class);
}
When I do Tables\Columns\TextColumn::make('productDescriptions.name') it return all values separated by comma. In my example "Opel, Vauxhall"
Is there any way to manipulate/mutate return value using callback? Let say, return only first value "Opel"?
You can use calculated states.
Tables\Columns\TextColumn::make('productDescriptions.name')
->getStateUsing( function (Model $record){
return $record->productDescriptions()->first()?->name;
});
Related
I have two models in my project
Product and Product attributes
The relationship between them is one to many, so i can get product with relative attributes. Now I need to get a list of products and also get common product attributes between selected products. As in ex:
Product
id
Product
1
Apple
2
Pear
3
Ananas
Product attributes
id
attribute
product_id
1
fruit
1
2
fruit
2
3
fruit
3
4
green
1
5
yellow
2
6
brown
3
Now when I'm extracting all 3 products, i want to get their common attributes, in this case the common attribute is "fruit". How can I do this with laravel eloquent?
As per your current schema (which doesn't look good) you can get common attributes as
$products = Product::take(3)->get();
From above products collect product ids
$productIds = $products->pluck('id');
And then query product_attributes matching previously collected $productIds, this query will involve aggregation to satisfy your condition for common attributes.
$attributes = DB::table('product_attributes')
->select(['name',DB::raw('count(distinct product_id) total_count')])
->whereIn('product_id', $productIds)
->groupBy('name')
->havingRaw('total_count = ?', [count($productIds)])
->get();
Above query will return common attributes by checking the result of count() expression with count of $productIds if they match and this means that every products has this attributed attached to it.
At first, as said above, add many-to-many relationships, one-to-many is not suitable for that case. Then I can propose such a variant with two queries:
// select here product ids you need
$products = Product::all(['id']);
//select common attributes
$attibuteBuilder = ProductAttribute::query();
foreach($products as $product){
$attibuteBuilder->whereHas('products', function (Builder $builder) use ($product) {
$builder->where('product_id', $product->id);
})
}
$atributes = $attibuteBuilder->get();
The second variant: get the collection of products with attributes and check the general/
I have category table, attribute table and attribute_value table,
Category:
id | Catgeory Name
Attribute:
id| cat_id | Attribute Name
Attribute values Table:
id | attr_id | attr_value
Now i want to Display it like this :
Category Name
Attribute Name 1
Attribute Val 1
Attribute val n
Attribute Name 2
Attribute Val 1
Attribute Val n
..
..
I'm using following model
Attribute:
public function attr_values()
{
return $this->hasMany(AttributeValue::class);
}
Category:
public function categoryAttributes()
{
return $this->hasMany(Attribute::class, 'category_id');
}
In controller i'm getting data using following:
Category::with(['categoryAttributes','attrValues'])->get()->find($categoryId);
But i'm unable to get the data attributes that are linked to Category and its attributes Values.
Result:
you can join your tables and select the columns you want:
Category::whereIn('id', $ids)->join('attribute', 'attribute.cat_id', '=', 'category.id')
->join('attribute_value', 'attribute_value.attr_id', '=', 'attribute.id')
->select('category.category_name', 'attribute.attribute_name', 'attribute_value.attr_value')->get();
using relations:
Category:: with(['categoryAttributes'=>function($query){
$query->addSelect('attribute.cat_id','attribute.attribute_name')
->with(['attr_values'=>function($query){
$query->addSelect('attribute_value.attr_value','attribute.attr_id');
}]);
}])->whereIn('id', $ids)->get();
please note that adding foreign key relation column to select statement is necessary so laravel get the right object
CONTEXT
I am managing products. This is a shoe store. I would like to offer a view of the other variants.
The database is shaped like this:
For example you can have a leather shoe (id 1), and there is 3 variants of this shoe: a black (id 1), a brown (id 2), and a grey (id 3).
What I try to do is to construct a Laravel relationship to be able, from one variant, to get its siblings. Here is what it looks like in the database according to the example I mentioned.
SHOE
id
====
1
SHOE_VARIANT
id shoeId colorId
===================
1 1 1
2 1 2
3 1 3
...
8 2 5
9 3 2
10 3 4
In this case, if the user is viewing the black variant (id 1), I whish I could show him the 2 others variants (brown, id 2, and grey, id 3).
QUESTION
How can I construct a Laravel relationship in order to retrieve siblings from a parent id, and make sure the current record itself is not included?
EXPERIMENTS
I already tried to construct a relationship like below, but the current record itself is included and I can't figure out how to exclude the record itself because I did not find how to get the current id of the record.
// app/ShoeVariant.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ShoeVariant extends Model {
public function siblings() {
return $this->hasMany("App\ShoeVariant", "shoeId", "shoeId");
}
}
This means:
For the current shoe variant, get the shoe variants that matches knowing that you should match the foreign column named "shoeId" and the local column named "shoeId"
So if 2 shoe variants share the same column "shoeId", this works. Stuck in excluding the current record from these results.
This should do what you want:
public function siblings() {
return $this->hasMany('App\ShoeVariant', 'shoeId', 'shoeId')
->where('id', '!=', $this->id);
}
Just filter out the current variant by id and get all the others.
Alternatively, you can just make a new property:
public function getVariantsAttribute() {
return $this->siblings->reject(function($elem) {
return $elem->id == $this->id;
});
}
And then use it in code like:
$variants = $model->variants; // all except this one
This question already has answers here:
How do I specify unique constraint for multiple columns in MySQL?
(14 answers)
Closed 3 years ago.
I have two table both table are merge in third table like this
**sub_category**
Id
Name
**category**
ID
Name
**sub_cat**
ID
sub_id
cat_id
**category** **Sub_Category**
ID Name ID Name
1 One_Cat 1 one_sub
2 two_Cat 2 two_sub
3 three_Cat 3 three_sub
**Cat_SubCat**
ID Cat_id sub_catId
1 1 1
2 1 2
3 1 3
4 1 1
5 2 1
6 2 2
7 2 2
Do you See under cat_id 1 there is duplicate of sub_CatId,
and what i want is to validate that, under category one there should no duplicate subcategory same for category 2, there should no repeating subcategory under category 2
If you have uniqueness on your names in the category and subcategory tables, then it means that the IDs will be unique always.. So instead of validating if the relationship already exists you can simply use, sync() or syncWithoutDetaching() which will ensure that there will always be one connection between a category and subcategory, and no duplicates. Otherwise you use attach() which produces duplicates.
Take a look at the docs for more
https://laravel.com/docs/master/eloquent-relationships#updating-many-to-many-relationships
The migration should look like this:
Schema::create('sub_cat', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('sub_id');
$table->unsignedBigInteger('cat_id');
$table->unique(['sub_id', 'cat_id']);
});
the unique index ensures the sub_id and cat_id are unique.
And then whenever you want to add a sub-category use sync or syncWithoutDetaching.
But why you're designing the tables like that, you should just use a parent_id on category table
Schema::create('category', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('category');
$table->unsignedBigInteger('parent_id')->nullable;
$table->foreign('parent_id')->references('id')->on('category');
});
The Category model then should look like this:
class Category extends Model
{
public function parentCategory()
{
return $this->belongsTo(self::class, 'parent_id');
}
public function subCategories()
{
return $this->hasMany(slef::class, 'parent_id');
}
}
I am using Laravels Eloquent ORM and i ran into a little problem with a special relationship. Lets assume i have the following table:
Recipe:
id | ... | ingredient1 | ingredient2 | ingredient3 | ingredient4
Every recipe has exactly 4 ingredients and i get the data from an external source in this specific format, thats why i have the ingredients as columns and not as a normal many-to-many relation.
I could set these up as 4 one-to-many relations, but i want to be able to write $ingredient->usedInRecipes()->get().
With 4 one-to-many relations i would have to write $ingredient->usedInRecipesAsIngredient1()->get(), [...], $ingredient->usedInRecipesAsIngredient4()->get() and merge them after afterwards, which would result in 4 queries.
If you know a good way to join these before querying the database or how to make a 4-to-many relation work please answer!
From the question I can't tell if you have already attempted this or not although as I see it you just need to use a single many-to-many relationship.
Each ingredient presumably has a common set of properties that can all be handled in one table ingredients.
id name created_at updated_at
1 Paprika 01/01/1970 00:00:00 01/01/1970 00:00:00
1 Rosemary 01/01/1970 00:00:00 01/01/1970 00:00:00
1 Basil 01/01/1970 00:00:00 01/01/1970 00:00:00
1 Oregano 01/01/1970 00:00:00 01/01/1970 00:00:00
Then your recipes table
id name created_at updated_at
1 Herb Soup 01/01/1970 00:00:00 01/01/1970 00:00:00
To hold the relationships, a pivot table ingredient_recipe
id recipe_id ingredient_id
1 1 1
2 1 2
3 1 3
4 1 4
Now all you require is a belongsToMany relationship on both your Recipe and Ingredient model.
You can code safeguards to make sure one recipe only ever has 4 relationships with ingredient if you wish but to keep it simple:
Recipe::with('ingredients')->get();
Would retrieve all the ingredients along with the recipe.
You can read more about this relationship in the documentation here.
Without Pivots
If you kept the columns ingredient_1, ingredient_2 and so on in the recipes table you could add something like this to your Recipe model.
public function scopeGetWithIngredients($query)
{
$query->leftJoin('ingredients i1', 'recipes.ingredient_1', '=', 'i1.id')
->leftJoin('ingredients i2', 'recipes.ingredient_2', '=', 'i2.id')
->leftJoin('ingredients i3', 'recipes.ingredient_3', '=', 'i3.id')
->leftJoin('ingredients i4', 'recipes.ingredient_4', '=', 'i4.id')
->select('recipes.name', 'i1.name AS ing_1', 'i2.name AS ing_2');
}
You can then just get the ingredients in your model with
Recipe::getWithIngredients();
I found a solution that seems to work in all my use cases.
In the Recipe model i defined the 4 ingredients as One-to-Many relations and made two helper scope functions.
class Recipe extends Eloquent {
public function ingredient1()
{ return $this->belongsTo('Ingredient', 'ingredient1'); }
public function ingredient2()
{ return $this->belongsTo('Ingredient', 'ingredient2'); }
public function ingredient3()
{ return $this->belongsTo('Ingredient', 'ingredient3'); }
public function ingredient4()
{ return $this->belongsTo('Ingredient', 'ingredient4'); }
public function scopeHasIngredient( $query, Ingredient $ingredient ) {
return $query-> where( 'ingredient1', '=', $ingredient->id )
->orWhere( 'ingredient2', '=', $ingredient->id )
->orWhere( 'ingredient3', '=', $ingredient->id )
->orWhere( 'ingredient4', '=', $ingredient->id );
}
public function scopeWithIngredients( $query ) {
return $query->with('ingredient1', 'ingredient2',
'ingredient3', 'ingredient4');
}
}
class Ingredient extends Eloquent {
public function ingredientForRecipes() {
return Recipe::hasIngredient( $this )->withIngredients();
}
}
To get all recipes for an Ingredient i can now call $ingredient->ingredientForRecipes()->get() and use the ingredients without extra queries.