外键的存储方式——存ID而非对象

在数据库设计中,订单表中通常会保存客户的 id 作为外键,而不是保存客户的对象

  1. 原因分析

    • 数据冗余和维护成本 :如果在订单表中保存客户的对象(如客户的姓名、联系方式、地址等所有属性),会导致数据冗余。例如,一个客户可能会有多个订单,每个订单都存储客户的完整信息,当客户的某个信息(如电话号码)发生变化时,需要在多个订单记录中更新这个信息,这增加了数据维护的复杂度和出错的可能性。

    • 数据完整性 :通过外键(客户 id)建立订单表和客户表之间的关系,可以更好地维护数据完整性。数据库管理系统可以利用外键约束来确保订单中的客户 id 必须对应客户表中存在的客户记录。如果试图插入一个不存在的客户 id 到订单表中,数据库系统会阻止这种操作,从而保证了数据的准确性和一致性。

    • 查询效率和灵活性 :在需要获取订单相关客户信息时,可以通过连接查询(如 SQL 中的 JOIN 操作)来获取。这种方式在不存储冗余数据的情况下,能够灵活地获取所需信息。例如,可以通过订单表和客户表的连接查询,获取某个订单的客户姓名、联系方式等信息,同时还能利用数据库的索引等优化机制来提高查询效率。

  2. 举例说明

    • 假设有一个客户表(Customers),其结构为: CustomerID(主键)、CustomerName、ContactName、Address、City、PostalCode、Country

    • 订单表(Orders)的结构为: OrderID(主键)、CustomerID(外键)、EmployeeID、OrderDate、ShipperID

    • 在订单表中,CustomerID 字段作为外键引用客户表的 CustomerID 字段。当需要查询某个订单的客户信息时,可以通过 SQL 语句如 “SELECT Customers.CustomerName, Customers.ContactName, Customers.Address FROM Orders JOIN Customers ON Orders.CustomerID = Customers.CustomerID WHERE Orders.OrderID = 某个指定订单号” 来获取该订单对应客户的姓名、联系人和地址等信息。这种设计方式使得数据结构清晰,便于维护和查询。

    • 使用ORM:

      在使用 Tortoise ORM(一个用于 Python 的异步 ORM)进行数据库操作时,可以通过以下步骤使用订单表中的客户 ID 获取客户的其他信息:

      1. 定义模型

      首先,你需要定义你的模型。假设你有以下两个模型:OrderCustomer,其中 Order 模型中有一个外键引用到 Customer 模型。

      from tortoise import Model, fields
      
      class Customer(Model):
          id = fields.IntField(pk=True)
          name = fields.CharField(max_length=100)
          contact_name = fields.CharField(max_length=100)
          address = fields.CharField(max_length=255)
          city = fields.CharField(max_length=100)
          postal_code = fields.CharField(max_length=20)
          country = fields.CharField(max_length=100)
      
          class Meta:
              table = "customers"
      
      class Order(Model):
          id = fields.IntField(pk=True)
          order_date = fields.DatetimeField()
          customer = fields.ForeignKeyField("models.Customer", related_name="orders")
      
          class Meta:
              table = "orders"
      

      2. 查询

      要通过订单表中的客户 ID 获取客户的其他信息,可以使用 Tortoise ORM 的查询方法。

      方法 1:通过 get 方法获取订单,然后访问客户信息

      如果你已经知道订单的 ID,并且想要通过这个订单获取客户的其他信息,可以这样做:

      async def get_customer_info_by_order(order_id: int):
          # 获取指定订单
          order = await Order.get(id=order_id)
          # 通过订单的 customer 关联获取客户信息
          customer = await order.customer
          return customer
      

      在上面的代码中:

    • Order.get(id=order_id) 用于获取指定 ID 的订单。

    • order.customer 是一个异步属性,用于获取与订单关联的客户对象。

    • 方法 2:使用 prefetch_related 预加载客户信息

      如果要获取多个订单以及它们关联的客户信息,可以使用 prefetch_related 方法来预加载客户数据,以避免多次查询数据库:

      async def get_orders_with_customer_info():
          # 获取所有订单,并预加载客户信息
          orders = await Order.all().prefetch_related("customer")
          for order in orders:
              # 访问每个订单的客户信息
              customer = order.customer
              print(f"Order ID: {order.id}, Customer Name: {customer.name}")
      

      在这个例子中:

    • Order.all().prefetch_related("customer") 会预先加载所有订单及其关联的客户信息。

    • 这样可以减少数据库查询的次数,提高效率。

    • 方法 3:使用 select_related 获取关联数据

      如果你只需要在查询结果中包含关联的客户数据,可以使用 select_related 方法:

      async def get_order_with_customer(order_id: int):
          # 获取订单及其关联的客户信息
          order = await Order.get(id=order_id).select_related("customer")
          return order
      

      在这个例子中:

    • Order.get(id=order_id).select_related("customer") 会返回订单对象,并且订单对象的 customer 属性已经被加载,可以直接访问。

    • get 方法:用于获取单个订单及其关联的客户信息。

    • prefetch_related:用于预加载多个订单及其关联的客户信息,适合批量操作

    • select_related:用于在查询时直接包含关联的客户信息,适合单个查询。

    • 根据你的具体需求选择合适的方法。Tortoise ORM 提供了灵活的查询方式,可以方便地处理数据库中的关系数据。

      3. 使用查询结果

      无论你使用哪种方法获取客户信息,都可以通过访问客户对象的属性来获取需要的信息。例如:

      async def main():
          # 获取客户信息
          customer = await get_customer_info_by_order(order_id=123)
          print(f"Customer ID: {customer.id}")
          print(f"Customer Name: {customer.name}")
          print(f"Customer Address: {customer.address}")
          print(f"Customer City: {customer.city}")
          print(f"Customer Postal Code: {customer.postal_code}")
          print(f"Customer Country: {customer.country}")
      
      # 运行异步函数
      import asyncio
      asyncio.run(main())
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值