将1:n数据库关系转换为代码的最佳实践

时间:2017-05-19 12:28:35

标签: sql oop design-patterns relational-database

想象一个包含两个表的数据库:

persons
---------
|pid|name|
|  0| Tom|
|  1|Hans|
|  2| Ken|
---------

cars
---------------
|cid|pid|  car|
|  0|  1|  BMW|
|  1|  1| Audi|
|  2|  2| Benz|
---------------

人与车之间的1:n关系。

在代码中将这些关联起来的最佳做法是什么?

我们假设我想填写这个简单的课程:

class Person {
  string name;
  List<string> cars;
}

从我的头脑中,我可以采取三种方式:

方式1:

分别执行两个查询并执行嵌套的foreach 关联所有汽车。

两个SQL查询:

SELECT * FROM persons;
SELECT * FROM cars;

使用简单的伪代码:

List<Person> personList;
foreach(row in personQuery) {
  person = new Person(personQuery.name);
  foreach(row in carQuery)
    if(carQuery.pid == personQuery.pid)
      person.AddCar(carQuery.car);
}

方式2:

使用连接执行一个查询并忽略重复信息。

一个SQL查询:

 SELECT * FROM persons JOIN cars ON persons.pid = cars.pid;

使用:

List<Person> personList;
int lastID;
foreach(row in Query) {
  if(Query.pid != lastID) {
    person = new Person(personQuery.name);
    personList.Add(person);
  }
  person.AddCar(Query.car);
}

方式3:

执行一个查询以获取所有人,然后为每个人执行一个查询 得到相关的汽车。

许多SQL查询:

SELECT * FROM persons;
SELECT * FROM cars WHERE pid = @param;

使用:

List<Person> personList;
foreach(row in personQuery) {
  person = new Person(personQuery.name);
  carQuery = DoCarQuery(personQuery.pid);
  foreach(row in carQuery)
    person.AddCar(carQuery.car);
  personList.Add(person);
}

在我的测试中,似乎第一种方式是最快的,但它的速度非常快,因为它是一种n ^ m方法,如果我有多个方法,它会变得更慢 1:n关系就像第三张表与#34;自行车&#34;在上面的例子中。

我在工作中经常遇到这个问题而且无法找到最佳实践,但这可能是因为我无法准确地说出问题,因为我只找到了如何制作1的匹配: n数据库首先:/

2 个答案:

答案 0 :(得分:1)

首先,您的最佳实践(如果您还没有)是在Cars.pid上创建一个参考Persons.pid的FOREIGN KEY。这将确保数据库的完整性。您还可以级联更改(删除是常见的),以便在删除某个人时,也会删除其关联的车辆记录。

回答有关3种情况的具体问题。答案实际上取决于您对数据的处理方式。

  • 数字1只是两个不相关的SQL查询,根本不关联数据。除了显示2个单独的数据列表之外,或者如果您需要在应用程序中分离这2个数据集,您将无法找到许多用例。使用嵌套循环来组合数据会破坏使用 Relational DBMS的目的。看起来这似乎是目前最快的方式,但是您将负载放在应用程序而不是用于处理该过程的数据库管理系统上。正如您所发现的那样,当您将n ^个表添加到混合中时,这将导致指数更慢。

  • 数字2实际上是您通过查询实际关联数据的唯一示例。您会发现这是绝大多数情况下的最佳实践,您将经常使用这种简单的JOIN。如果您要查询特定的pid,则不应该获得重复信息,您将获得与该pid相关的所有汽车。

  • 除了使用变量作为其中一个查询的输入之外,数字3实际上与第一个相同。

检索与人相关的所有汽车的最佳做法是这样的:

SELECT * FROM persons JOIN cars ON persons.pid = cars.pid 
WHERE persons.name = @param

由于您只输入了名称,如果名称不是唯一的,这可能会返回一些重复项,但它会比查询整个人员和汽车表格然后过滤出应用程序中的单个值要好得多。

答案 1 :(得分:1)

不确定我是否可以使用通用的最佳做法,但有些想法假设您需要加载所有人员以及与之相关的所有汽车:

如果您正在显示它们,很可能不想加载所有这些内容。最有可能是某种寻呼方案,以便在数据增长时无法控制。

第一种方法可以改进,如果你首先循环通过人,添加到某种哈希表或字典,然后通过汽车循环(按人员ID排序)并将其添加到指定人员的汽车集合中,如下所示

Dictionary<Guid, Person> personList = new Dictionary<Guid, Person>();
foreach(row in personQuery)  {
  person = new Person(personQuery.name);
  personList.Add(person.pid, person);

}

Person currentPerson = null
foreach(row in carQuery.OrderBy(c=>c.pid) {
    if(currentPerson == null || carQuery.pid != currentPerson.pid)
    {
       currentPerson = personList[carQuery.pid];
    }
    currentPerson.AddCar(carQuery.car);
}

它的编写方式是让每个人循环遍历所有车辆(n * m,其中n是汽车总数,m是总人数)。

第二种方法是最有效的,因为它会循环n次,其中n是汽车的总数。但是,如果将此循环扩展到第三个关系,它是正确的

第三种方法是最糟糕的,因为你将数据库调用n + 1次(n仍然是汽车的总数),查询数据库的效率远低于循环。

我希望有所帮助!