如何设计数据库以存储“默认”项目

Posted

技术标签:

【中文标题】如何设计数据库以存储“默认”项目【英文标题】:How to design database in order to store "default" items 【发布时间】:2020-01-08 14:54:56 【问题描述】:

我正在 Laravel 中开发一个应用程序。我有名为 Account 和 ShippingAddress 的模型。一个 Account 可能有一个或多个 ShippingAddress,但一个 ShippingAddress 只能有一个 Account。所以我使用的是一对多关系。

现在我想实现一个功能,将 ShippingAddress 标记为“默认”,并且每个“帐户”只能有一个“默认送货地址”。我已经确定了两种可能的解决方案:

    在 ShippingAddress 表中添加一个“is_default”列。 创建一个“default_shipping_address”数据透视表,我将在其中存储“account_id”“和 shipping_address_id”。

哪一个是最适合您的解决方案,为什么?如果是第二个:如何通过数据透视表在 Laravel 中实现一对一关系?

【问题讨论】:

【参考方案1】:

恕我直言,考虑到一个 ShippingAddress 只属于一个 Account,恕我直言,使用数据透视表似乎有点多余,第一个解决方案对我来说看起来更干净。

顺便说一句,您可以通过这种方式或类似的方式设计Models

class Account extends Model

    public function shipping_addresses()
    
        return $this->hasMany('App\ShippingAddress');
    
    public function default_shipping_address()
    
        return $this->hasOne('App\ShippingAddress')->where('is_default', true);
    


class ShippingAddress extends Model

    public function account()
    
        return $this->belongsTo('App\Account');
    

这样你可以eager load'shipping_addresses'或者只有'default_shipping_address'当你检索Account模型时,例如:

$account = Account::with('shipping_addresses')->find($id);
$account = Account::with('default_shipping_address')->find($id);

显然你不需要eager load这两个关系,因为shipping_addresses已经包含了默认送货地址

您只需通过验证 (unique rule) 或数据库 constraints 或两者都检查代码中是否只有 一个 default shipping address

【讨论】:

【参考方案2】:

我个人的观点是,在单个收藏地址的情况下,使用数据透视表虽然从形式上看可能更正确,但只会增加查询的复杂性。相反,通过在地址中添加一个标志列来确定它何时是收藏夹,这将使查询更容易。

现在,如果你想使用数据透视表,你应该使用如下函数在 Account 模型中建立关系:

public function defaultShippingAddress()
    
      return $this->hasOneThrough('App\Models\AccountShippingAddress', 'App\Models\ShippingAddress');
    

在这里您可以阅读有关它的整个文档https://laravel.com/docs/5.8/eloquent-relationships#has-one-through

问候。

【讨论】:

我想探索这个答案,但我不太明白。我将按如下方式设计我的数据库:accounts [id, ...]shipping_addresses [id, account_id, ...]default_shipping_address [account_id, shipping_address_id]。现在我创建了一个扩展Illuminate\Database\Eloquent\Relations\PivotDefaultShippingAddress,因为通过阅读文档,这听起来是正确的做法。然后我通过反转参数来实现您建议的方法。但是如果我尝试执行它返回一个QueryException的方法:它无法检索shipping_addresses.id。我做错了什么? 让我尝试实现您的模型,我会回来提供更好的解释。【参考方案3】:

我会选择选项 1,因为它更简单且没有缺点。为了确保每个Account 只有一个默认ShippingAddress,您需要监听saving 事件并进行如下验证:

class ShippingAddress extends Model

    // ... other code

    public function boot()
    
        parent::boot();

        static::saving(function (ShippingAddress $shippingAddress) 
            if (! $shippingAddress->is_default) 
                return;
            

            $defaultAlreadyExists = static::where([
                ['account_id', '=', $shippingAddress->account_id],
                ['id', '!=', $shippingAddress->id],
                ['is_default', '=', true],
            ])->exists();

            if ($defaultAlreadyExists) 
                throw new YourCustomException("Account with id $shippingAddress->account_id already has a default shipping address");
            
        );
    

    // ... other code


【讨论】:

以上是关于如何设计数据库以存储“默认”项目的主要内容,如果未能解决你的问题,请参考以下文章

如何在用户应用促销代码时设计订单历史记录表

ElasticSearch中index的设计

数据库设计:如何在数据库中存储 XML/JSON?

如何禁用 Spring Data REST 存储库的默认公开?

如何将详细数据存储在数据仓库中以进行贷款处理?

数据库凭据以纯文本形式存储可以吗?