防止Laravel将多个记录添加到数据透视表


97

我已经建立并工作了很多对很多关系,可以将商品添加到我使用的购物车中:

$cart->items()->attach($item);

这会向数据透视表添加一个项目(应如此),但是如果用户再次单击链接以添加他们已经添加的项目,则会在数据透视表中创建一个重复的条目。

是否有一种内置的方式可以将记录添加到数据透视表中,只要该记录尚不存在?

如果没有,如何检查数据透视表以查找是否已存在匹配的记录?

Answers:


76

您可以通过编写这样一个非常简单的条件来检查现有记录的存在:

if (! $cart->items->contains($newItem->id)) {
    $cart->items()->save($newItem);
}

或/并且您可以在数据库中添加唯一性条件,这将在尝试保存doublet期间引发异常。

您还应该看看下面来自Barryvdh的更直接的答案。


1
该方法的$ id参数attach()是混合,它可以是一个int或模型的实例;) -见github.com/laravel/framework/blob/master/src/Illuminate/...
罗布Gordijn

谢谢@RobGordijn。我今天学到了一些东西!答案已编辑。
Alexandre Butynski 2013年

1
@bagusflyer contains语句检查密钥是否存在于集合中的一个对象中。您的代码中应该有一个错误。
亚历山大·布汀斯基

4
我不喜欢这种解决方案,因为它强制执行一个额外的查询($ cart-> items),我一直在执行以下操作: $cart->items()->where('foreign_key', $foreignKey)->count() 嗯,实际上也执行了一个额外的查询'^^但我不需要提取和除非我真的需要,否则就对整个收藏进行补水。
沉默

1
没错,您的解决方案已经过优化。您甚至可以使用该exists()函数代替count()最佳优化。
亚历山德拉·布汀斯基

264

您也可以使用该$model->sync(array $ids, $detaching = true)方法并禁用分离(第二个参数)。

$cart->items()->sync([$item->id], false);

更新:从Laravel 5.3或5.2.44开始,您还可以调用syncWithoutDetaching:

$cart->items()->syncWithoutDetaching([$item->id]);

哪个功能完全相同,但可读性更高:)


2
L5主文档中没有第二个布尔参数,用于防止分离未列出的ID。很高兴知道-这似乎是在单个语句中“如果尚未附加”的唯一方法。
杰森

11
这应该被接受为答案,这是比接受的答案更好的方法
丹尼尔(Daniel)

4
对于新手和我一样:$cart->items()->sync([1, 2, 3])将建设定数组中的ID的许多一对多的关系,12,和3,和删除(或“分离”)的所有其它ID不是在数组中。这样,表中仅存在数组中给定的ID。Brilliant @Barryvdh使用一个未记录的第二个参数来禁用该分离,因此不会删除不在给定数组中的任何关系,而只会附加唯一的ID。查阅“同步便捷”文档。(Laravel 5.2)
identicon

3
仅供参考,我更新了5.2或更高版本的文档:laravel.com/docs/5.2/…并且Taylor立即添加了add syncWithoutDetaching(),它调用了false为第二参数的sync()。
Barryvdh

Laravel 5.5laravel.com/docs/5.5/… syncWithoutDetaching()起作用了!
乔什·拉马尔

2

@alexandre Butynsky方法效果很好,但使用两个sql查询。

一种检查购物车是否包含该物品,另一种保存。

要仅使用一个查询,请使用以下命令:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}

这就是我在项目中所做的。但是问题是您不知道此异常是由重复的条目还是其他原因引起的。
Bagusflyer 2014年

2
为什么会抛出任何异常?如果有一些独特的钥匙的话
Seiji Manoan

1

尽管所有这些答案都很好,因为我已经尝试了所有这些答案,但仍然没有答案或没有照顾到一件事:更新先前检查的值(未选中复选框)的问题。我确实有与上述问题类似的东西,期望我要在我的产品功能表(数据透视表)中检查和取消选中产品的功能。我是新手,但我没有意识到上述任何一项。在添加新功能时都很好,但在我要删除现有功能时(即取消选中)都不好

我将不胜感激。

$features = $request->get('features');

if (isset($features) && Count($features)>0){
    foreach ($features as $feature_id){
        $feature = Feature::whereId($feature_id)->first();
        $product->updateFeatures($feature);
    }
}

//product.php (extract)
public function updateFeatures($feature) {
        return $this->features()->sync($feature, false);
}

要么

public function updateFeatures($feature) {
   if (! $this->features->contains($features))
        return $this->features()->attach($feature);
}
//where my attach() is:
public function addFeatures($feature) {
        return $this->features()->attach($feature);
}

抱歉,我不确定是否应该删除问题,因为自己弄清楚答案,听起来有点愚蠢,那么上述答案就很简单,就像使用@Barryvdh sync()一样;已阅读有关以下内容的更多信息:

$features = $request->get('features');
if (isset($features) && Count($features)>0){
    $product->features()->sync($features);
}

0

你可以用

$cart->items()->sync($items)

从Laravel 5.7开始:

同步关联您还可以使用sync方法构造多对多关联。sync方法接受ID数组放置在中间表上,任何不在给定数组中的ID将从中间表中删除。因此,完成此操作后,中间表中将仅存在给定数组中的ID:


这是最好的解决方案
eifersucht

这甚至没有回答这个问题,那就是如何在避免重复行的同时添加单个关系。
hackel

同步将添加项目,它将避免创建重复的行。
Shreyansh Panchal,

为了完成@hackel的评论,它还将从购物车中删除所有其他物品。
chrissharkman

0

已经发布了一些很好的答案。我也想把这个扔出去。

@AlexandreButynski和@Barryvdh的答案比我的建议更具可读性,这个答案增加了一些效率。

它仅检索当前组合的条目(实际上仅检索ID),如果不存在,则将其附加。sync方法(即使没有分离)也将检索所有当前附加的ID。对于迭代次数较少的较小集合,这几乎没有什么不同,...您明白我的意思。

无论如何,它绝对不那么可读,但是可以解决问题。

if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
    $book->authors()->attach($author);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.