RelationManager belongsToMany withPivot(['id']) returns Column 'id' in order clause is ambiguous
When attaching an Product item to my Warehouse model which is a manyToMany relationship. I'm getting an SQL error (see below)
the order by should contain the pivot table name ....
i need the "id" because my attachments can be duplicated. I also already have configured:
$table->allowDuplicates()
this is my migration:
Product Model:
Warehouse Model:
51 Replies
you don't need to add
'id'
in the withPivot..@Leandro Ferreira documentations says you should.... https://filamentphp.com/docs/3.x/panels/resources/relation-managers#handling-duplicates
or shouldnt i create an "id" column at the pivot table?
and does laravel this automagically?
$table->id(); + allowDuplicates()
try this without 'id' in the withPivot..
okay, if i remove the "id" column in my "product_warehouse" table. the page loads fine.. but when i click the edit action i get the following error:
@Leandro Ferreira do you mean:
or in the migration the $table->id()
return $table->id() --> Method Filament\Tables\Table::id does not exist.
migration, as you did...
okay
so table "product_warehouse" now has id with auto-increment ,
Warehouse.php model:
Product.php model:
still :
wait.. this "Column not found: 1054 Unknown column 'product_warehouse.id' in 'field list'". was something not correct i used withPivot(["product_warehouse.id", 'quantity', 'location_info']); obviously that doesnt work
if i remove the "id" column from the pivot table "product_warehouse" and use ->withPivot(['id', 'quantity', 'location_info']); the RelationManager view page works.. but then in the edit action i gives me an error:
SQL error when clicking: "edit" on that record row...
so there it does expect that "id" column in the pivot table
i think if I change the "id" column name in the pivot table to perhaps: 'product_warehouse_id' as auto-increment key...
and use that in the with pivot
no then still in editAction the same sql error because it gets the ID of the product ID table
honestly, I think this would work with id and allowDuplicates.. If you want to create a mini repo on Github to reproduce this error, I can take a look after đ
so back to square one... and i think only the order by should add the table name of the products or the warehouse table...
so the ORDER BY would look like this:
or
Shouldnât these be HasMany instead of BelongsToMany? I could be wrong.
Product has many warehouses and warehouse has many products. Neither actually âownsâ the other.
that is true @awcodes i'm going to change it see if it works. thanks for the "detailed eye ;-)"
btw in "CanSortRecords.php" this function:
returns just the "id" not with table name.. although $this->getTable()..
and in CanBeSortable.php"
this function just strips of everything... except behind last dot
HasMany.. is not with an pivot/intermediate table right???
okay i fixed it by adding:
->defaultSort('products.id', 'desc')
but the getDefaultSortColumns() is not correct it does not take in account when multiple tables are joined that it should use one table name in front of the sorting
there goes a lot of things wrong with pivot when you have the same column names in multiple tables. for example in the intermediate/pivot table i have column "quantity" but that column also exists in the "products" table...
when i now edit the record it takes the wrong "quantity"
Many to many most definitely requires a pivot table.
EditAction::make()
->form(fn (EditAction $action): array => [
TextInput::make('quantity')->required(),
TextInput::make('location_info')->required(),
]),
quantity in the editAction form is now the value of products.quantity (table) and not product_warehouse.quantity (table)
Yea, if you need the info from a relationship then the form needs to know itâs a relationship.
By default all properties on the record are assumed to be on the record itself and itâs tied to the state of the form.
Anything in a relationship needs to be define with a form field or layout component that supports relationships.
@awcodes that's why ->withPivot([]) on models... but if the names are the same in all tables you have a problem
Well, yea, but thatâs not necessarily a filament issue. Thatâs an issue of how youâre doing the joins.
yes that i can agee on supplement columns like quantity, but not on the primary keys id
Right but the relationship should handle the keys. The pivot attributes should never need the primary keys
return $table
->defaultSort('products.id', 'desc')
->allowDuplicates()
->columns([
TextColumn::make('id') <-- actually is the products.id
TextColumn::make('products.name') <-- does not work
TextColumn::make('product_warehouse.id'), <-- does not work
TextColumn::make('quantity'), <-- is products.quantity
TextColumn::make('product_warehouse.quantity'), <-- does not work
])
that is what i think i would use: products.name or product_warehouse.quantity but that doesnt work
Sorting on an id is always going to create problems.
well the docs say it should work:
https://filamentphp.com/docs/3.x/panels/resources/relation-managers#handling-duplicates
Youâre going to have to redefine any pivot relationship data that uses the same keys.
yes i will do that for quantity etc...
but for the id it should be handled by laravel/filament correctly
Itâs no different. Filament / Laravel shouldnât do any of that automatically.
because i already tried changing the id column of the intermediate table (product_warehouse) from id to product_warehouse_id and use that column as the withPivot(['product_warehouse_id' ... ])
You can join in primary keys, it defeats the entire point of a primary key.
But Iâm also not fully understanding why you need the id instead of the relationship handling it.
Like, what is the practical use case of sorting on the id?
okay. the purpose is because one and the same product A can have laying around 4 pieces in warehouse A and 6 in warehouse B. so you need to assign the same. product A on warehouse A and on warehouse B
->defaultSort('products.id', 'desc') i need to set because the $query generates otherwise ORDER BY 'id' instead of ORDER BY 'products.id'
it does not matter what i set in the default sort. it could also be ->defaultSort('warehouses.id', 'desc') or ->defaultSort('product_warehouse.id', 'desc')
as long as i provide the prefix
because filament just does not add it correctly in this setup
i would expect that filament adds this product. prefix
because:
so the productRelationManager knows that the relationship is products, and that model has an
relation to warehouses()... public function warehouses(): BelongsToMany { return $this->belongsToMany(Warehouse::class)->withPivot(['id', 'quantity', 'location_info']); } so when filament builds the query it should take in account that when it joins the intermediate table ( INNER JOIN
relation to warehouses()... public function warehouses(): BelongsToMany { return $this->belongsToMany(Warehouse::class)->withPivot(['id', 'quantity', 'location_info']); } so when filament builds the query it should take in account that when it joins the intermediate table ( INNER JOIN
product_warehouse
ON products
.id
= product_warehouse
.product_id
) that sorting also should have the prefix of the base table (in my case 'products'
it does set the "products.id" correctly to the ON so...
why not on the ORDER BY
I just think that in filament/tables/src/Table/Concerns/CanSortRecords.php:71 function protected function applySortingToTableQuery(Builder $query): Builder
returns not the correct / full order by columnOk but your query is joining data and the warehouse id and product id could both be 2. So when thatâs joined one of them has to overridden since they both canât hav the same primary key.
And thatâs why you canât sort on id with joins.
The owning record will always trump it.
that's why i added the id on the pivot table itself...
I think youâre going to have to pick an order her and possible do a tuple sort. ->orderBy([warehouse.id, product.id])
Something like that.
But depending on the model active at the time that can all change.
line 98 in CanSortRecords.php
the $this->getTable() is known so why not prefix the defaultsortcolumn when $query builder has joins on it....
But my point is if it just an integer, so what is the goal of sorting on that? What is the applicable use case of sorting on an id.
nothing
but the SQL is then ambiquous because the SQL has an innerjoin so the sort by should add the table prefix
the result of $sortColumnName should not be by default "id" when joins happen
Well, the use case doesnât make sense to me, but maybe it is a bug. Feel free to submit an issue on the repo.
that's why i added for now: ->defaultSort('products.id', 'desc')
so it overrides that $sortColumName
so that the SQL statement becomes correct when the Builder $query creates inner join
Just seems to me that sorting on a product name, warehouse name or the timestamps would make more sense. đ
okay but what if i actually dont want to sort anything... in the relationsmanager table
Then donât provide a default sort. Just let the records comeback as they are.
yes precisely but then i get the sql error
Like I said. Maybe itâs a bug.
yes it is. đ
Then submit an issue. đ
the ->defaultSort('products.id', 'desc') is a workaround
đ
just to create a correct query builder --> SQL
will do, but i thought that you had the idea i was configuring the RelationManager wrong
in this specific setup
My initial thought was just that the relationship type was wrong.
okay xD
sorry my bad communication
will create issue tomorrow with an example repo
All good. Sorry if Iâm just being dumb too. Itâs hard to reason about the queries without actually being able to step through them.
At least for me. đ
no absolutely not! no excuses plz. it's just hard to explain via text sometimes
No doubt.
Thank you for your time at least!
i really appreciate it đ
I appreciate you choosing filament.
I also found the reason why the EditAction receives the wrong form input when duplicate column names in both tables are used.
in filament/tables/src/Actions/EditAction.php there only is a check for the BelongsToMany relationship when pressing the save button. That is okay, but the same thing should be done in the setUp() function. but there it just converts the $record->attributesToArray() and when using the same columns names it just takes the first columns of the joined tables.
in the $this->action function you can see that the pivot columns get renamed with a prefix of pivot_. which is a good thing not to have duplicates!
so for example: 'quantity' becomes 'pivot_quantity' .
and only those columns will get saved --> $pivotData = Arr::only($data, $pivotColumns);
Will create ticket for that as well .
but for now my workaround is that in my intermediate table i just make sure i dont use duplicate column names