从表创建嵌套数组的最佳方式:多个 queries/loops VS 单个 query/loop 样式
Best way to create nested array from tables: multiple queries/loops VS single query/loop style
假设我有 2 张桌子,
我可以 "merge" 并在单个嵌套数组中表示。
我正在考虑最好的方法是什么,考虑到:
- 效率
- 最佳实践
- DB/server-side 使用权衡
- 你在现实生活中应该做什么
- 3 个、4 个或更多表的相同情况可以 "merged" 那样
问题是关于任何 server-side/relational-db。
我想到的 2 个简单方法
(如果你有其他的,请推荐!
注意 我要一个简单的服务器端和关系数据库,
所以请不要浪费你的时间解释为什么我不应该
使用这种DB,使用MVC设计等等……):
- 2 个循环,5 个简单的 "SELECT" 查询
- 1 个循环,1 个"JOIN" 查询
我试着举了一个简单而详细的例子,
为了解释我自己并更好地理解你的答案
(虽然代码怎么写and/or
发现可能的错误不是这里的问题,
所以尽量不要专注于此...)
SQL 用于创建表并将数据插入表的脚本
CREATE TABLE persons
(
id int NOT NULL AUTO_INCREMENT,
fullName varchar(255),
PRIMARY KEY (id)
);
INSERT INTO persons (fullName) VALUES ('Alice'), ('Bob'), ('Carl'), ('Dan');
CREATE TABLE phoneNumbers
(
id int NOT NULL AUTO_INCREMENT,
personId int,
phoneNumber varchar(255),
PRIMARY KEY (id)
);
INSERT INTO phoneNumbers (personId, phoneNumber) VALUES ( 1, '123-456'), ( 1, '234-567'), (1, '345-678'), (2, '456-789'), (2, '567-890'), (3, '678-901'), (4, '789-012');
A JSON 在我 "MERGED" 之后的表格表示:
[
{
"id": 1,
"fullName": "Alice",
"phoneNumbers": [
"123-456",
"234-567",
"345-678"
]
},
{
"id": 2,
"fullName": "Bob",
"phoneNumbers": [
"456-789",
"567-890"
]
},
{
"id": 3,
"fullName": "Carl",
"phoneNumbers": [
"678-901"
]
},
{
"id": 4,
"fullName": "Dan",
"phoneNumbers": [
"789-012"
]
}
]
两种方式的伪代码:
1.
query: "SELECT id, fullName FROM persons"
personList = new List<Person>()
foreach row x in query result:
current = new Person(x.fullName)
"SELECT phoneNumber FROM phoneNumbers WHERE personId = x.id"
foreach row y in query result:
current.phoneNumbers.Push(y.phoneNumber)
personList.Push(current)
print personList
2.
query: "SELECT persons.id, fullName, phoneNumber FROM persons
LEFT JOIN phoneNumbers ON persons.id = phoneNumbers.personId"
personList = new List<Person>()
current = null
previouseId = null
foreach row x in query result:
if ( x.id != previouseId )
if ( current != null )
personList.Push(current)
current = null
current = new Person(x.fullName)
current.phoneNumbers.Push(x.phoneNumber)
print personList
PHP/MYSQL 中的代码实现:
1。
/* get all persons */
$result = mysql_query("SELECT id, fullName FROM persons");
$personsArray = array(); //Create an array
//loop all persons
while ($row = mysql_fetch_assoc($result))
{
//add new person
$current = array();
$current['id'] = $row['id'];
$current['fullName'] = $row['fullName'];
/* add all person phone-numbers */
$id = $current['id'];
$sub_result = mysql_query("SELECT phoneNumber FROM phoneNumbers WHERE personId = {$id}");
$phoneNumbers = array();
while ($sub_row = mysql_fetch_assoc($sub_result))
{
$phoneNumbers[] = $sub_row['phoneNumber']);
}
//add phoneNumbers array to person
$current['phoneNumbers'] = $phoneNumbers;
//add person to final result array
$personsArray[] = $current;
}
echo json_encode($personsArray);
2。
/* get all persons and their phone-numbers in a single query */
$sql = "SELECT persons.id, fullName, phoneNumber FROM persons
LEFT JOIN phoneNumbers ON persons.id = phoneNumbers.personId";
$result = mysql_query($sql);
$personsArray = array();
/* init temp vars to save current person's data */
$current = null;
$previouseId = null;
$phoneNumbers = array();
while ($row = mysql_fetch_assoc($result))
{
/*
if the current id is different from the previous id:
you've got to a new person.
save the previous person (if such exists),
and create a new one
*/
if ($row['id'] != $previouseId )
{
// in the first iteration,
// current (previous person) is null,
// don't add it
if ( !is_null($current) )
{
$current['phoneNumbers'] = $phoneNumbers;
$personsArray[] = $current;
$current = null;
$previouseId = null;
$phoneNumbers = array();
}
// create a new person
$current = array();
$current['id'] = $row['id'];
$current['fullName'] = $row['fullName'];
// set current as previous id
$previouseId = $current['id'];
}
// you always add the phone-number
// to the current phone-number list
$phoneNumbers[] = $row['phoneNumber'];
}
}
// don't forget to add the last person (saved in "current")
if (!is_null($current))
$personsArray[] = $current);
echo json_encode($personsArray);
P.S。
这个 link 是这里另一个问题的一个例子,我在这里尝试建议第二种方式:tables to single json
一般来说,最好的做法是在尽可能少的数据库访问中获取所需的数据,然后将数据映射到适当的对象中。 (选项 2)
但是,为了回答您的问题,我会问您自己您的数据的用例是什么。如果你确定你需要你的人和你的 phone 号码数据,那么我会说第二种方法是你最好的选择。
但是,当连接的数据是 optional.One 时,选项一也可以有它的用例。例如,在 UI 你有一个 table 的所有人如果用户想查看某个人的 phone 号码,他们必须点击那个人。那么它将接受 table 到 "lazy-load" 所有 phone 号码。
初步
首先,感谢您花那么多精力来解释问题和格式化。很高兴看到有人清楚他们在做什么,他们在问什么。
但必须注意,这本身就形成了一个限制:您固执地认为这是正确的解决方案,并且通过一些小的修正或指导,这将起作用。那是不正确的。所以我必须请你放弃这个想法,退后一大步,并在没有那个想法的情况下查看 (a) 整个问题和 (b) 我的答案。
这个回答的上下文是:
您给出的所有明确考虑,非常重要,我不会重复
其中最重要的两个是最佳实践和我在现实生活中会做什么
此答案植根于标准,最佳实践 的更高级别或参考框架。这就是商业 Client/Server 世界所做的,或者应该做的。
这个问题,整个问题 space,正在成为一个普遍的问题。我将在这里进行全面考虑,从而也回答另一个SO问题。因此,它可能包含您需要的更多详细信息。如果是,请见谅。
考虑
数据库是一个server-based资源,由许多用户共享。在在线系统中,数据库是不断变化的。它包含每个事实的一个版本的真相(不同于一个地方的一个事实,这是一个单独的规范化问题)。
- 一些数据库系统没有服务器架构,因此在此类软件中 服务器 的概念是错误和误导的,这是单独但值得注意的要点。
据我了解,"performance reasons"需要JSON和JSON-like结构,正是因为"server"没有,不能,作为服务器执行。这个概念是在每个(每个)客户端上缓存数据,这样您就不会一直从 "server" 获取数据。
这打开了一堆蠕虫。如果您没有正确地设计和实现它,蠕虫就会在应用程序中蔓延。
这样的实现严重违反了Client/Server架构,它允许双方使用简单的代码,并适当部署软件和数据组件,这样实现时间就很短,而且效率高
此外,这样的实现需要大量的实现工作,而且很复杂,由很多部分组成。每个部分都必须适当设计。
Web 和该主题领域的许多书籍提供了令人困惑的方法组合,以假定的简单性为基础进行营销;舒适; anyone-can-do-anything; freeware-can-do-anything;等等。这些提议都没有科学依据。
Non-architecture & Sub-standard
事实证明,您已经了解到某些数据库设计方法是不正确的。您遇到了 一个 问题,一个 实例表明该建议是错误的。一旦你解决了这个问题,下一个你现在还不明显的问题就会暴露出来。这些概念是一组 never-ending 问题。
有时鼓吹的错误观念我就不一一列举了。我相信,随着您逐步完成我的回答,您会发现一个接一个的营销观念是错误的。
两条底线是:
这些概念违反了体系结构和设计标准,即Client/Server体系结构; Open Architecture;工程原理;在这个特定问题中,数据库设计原则较少。
这导致像您这样努力诚实工作的人被骗实施简单的概念,然后变成大规模的实施。永远无法正常工作的实现,因此它们需要大量的持续维护,并且最终会被批量替换。
建筑
被违反的中心原则是,永远不要复制任何东西。当你有一个数据重复的位置(由于缓存或复制或两个单独的整体应用程序等)时,你创建了一个副本,将在在线情况下不同步。所以原则是避免那样做。
- 当然,对于严肃的 third-party 软件,例如 gruntly 报告工具,它们很可能会在客户端缓存 server-based 数据。但请注意,他们已经投入了数百 man-years 来正确实施它,并充分考虑了上述内容。你的不是这样的软件。
本答案的其余部分不是提供关于必须理解的原则或每个错误的弊端和代价的讲座,而是提供所要求的你在现实生活中会做什么,使用正确的架构方法(比最佳实践高出一步)。
架构 1
请勿混淆
- 必须规范化的数据
和
- 结果集,根据定义,它是数据的扁平化视图("de-normalised" 不太正确)。
数据,假设它是规范化的,不会包含重复值;重复组。结果集将包含重复值;重复组。那就是行人。
请注意,嵌套集(或嵌套关系)的概念,在我看来不是很好的建议,正是基于这种混淆。
自 RM 出现以来 forty-five 年以来,他们一直无法区分基础关系(为此归一化 不适用) 来自派生关系(规范化不适用)。
其中两个支持者目前正在质疑第一范式的定义。 1NF是其他NF的基础,如果新的定义被接受,所有的NF都将被渲染value-less。结果将是归一化本身(在数学术语中很少定义,但被专业人士清楚地理解为一门科学)即使没有被摧毁也会受到严重破坏。
架构二
有一个centuries-old科学或工程原理,内容(数据)必须与控制(程序元素)分开。这是因为两者的分析、设计和实现完全不同。这个原则在软件科学中同样重要,它有具体的表述。
为了保持简短(哈哈),我假设您理解以下内容,而不是讨论:
数据和程序元素之间存在科学要求的界限。将它们混合在一起会导致 error-prone 且难以维护的复杂对象。
这一原则的混淆在OO/ORM世界中达到了流行病的程度,其后果影响深远。
只有专业人士才能避免这种情况。对于其余的人,绝大多数人,他们接受新定义 "normal",他们一生都在解决我们根本没有的问题。
根据 E F Codd 博士的关系模型表格形式存储和呈现数据的架构优势和巨大价值=408=]。数据规范化有特定规则。
而且重要的是,您可以确定写作和销售书籍的人何时建议 non-relational 或 anti-relational 方法。
架构 3
如果在客户端缓存数据:
缓存绝对最小值。
也就是说只缓存在线环境中不会发生变化的数据。这意味着仅参考和查找 tables,填充更高级别 classifier 的 tables,drop-downs,等等
货币
对于您缓存的每个 table,您必须有一种方法来 (a) 确定缓存的数据与服务器上存在的真实版本相比已经过时,并且(b) 从服务器刷新它,(c) 在 table-by-table 基础上。
通常,这涉及一个每 (e) 五分钟执行一次的后台进程,查询客户端上每个缓存 table 的最大更新日期时间与服务器上的日期时间,如果更改,则刷新table、 和 所有其子 table,那些依赖于更改的 table.
当然,这要求您在每个 table 上都有一个 UpdatedDateTime
列。这不是负担,因为无论如何你都需要 OLTP ACID 事务(如果你有一个真正的数据库,而不是一堆 sub-standard 文件)。
这真的意味着,永远不要复制,编码负担过重。
架构 4
在sub-commercial、non-server的世界里,我了解到有人建议"everything"的反向缓存。
这是像 PostgreSQL 这样的程序可以在 multi-user 系统中使用的唯一方法。
一分钱一分货:一分钱一分货,一分钱一分货;你支付零,你得到零。
架构 3 的推论是,如果您在客户端缓存数据,请不要缓存经常更改的 tables。这些是交易和历史 tables。在客户端缓存这样的 table 或所有 table 的想法是完全破产的。
在真正的 Client/Server 部署中,由于使用了适用的标准,对于每个数据 window,应用程序应该只查询所需的行,以满足特定的需求,在特定的时间,基于上下文或过滤器值等。应用程序不应加载整个 table.
如果同一用户使用相同的 window 检查其内容,则在第一次检查 15 分钟后,数据将过时 15 分钟。
对于 freeware/shareware/vapourware 平台,它们通过缺少服务器架构来定义自己,因此结果是,性能是 non-existent,当然,你必须缓存超过客户端上的最小 tables。
如果你这样做,你必须考虑到以上所有因素,并正确实施,否则你的应用程序将被破坏,后果将促使用户寻求你的终止。如果不止一个用户,他们就会有相同的原因,很快就会形成一支军队。
架构 5
现在我们来看看如何在客户端缓存那些精心挑选的tables。
请注意,数据库在增长,它们在扩展。
如果系统坏了,一个故障,它会以小增量增长,需要很大的努力。
如果系统即使是小成功,它也会成倍增长。
如果系统(每个数据库,和应用程序,分别)设计和实现得好,变化会很容易,错误会很少。
因此,应用程序中的所有组件都必须经过适当设计,以符合适用标准,并且数据库必须完全规范化。这反过来又最大限度地减少了数据库更改对应用程序的影响,反之亦然。
该应用程序将由简单而不复杂的对象组成,这些对象易于维护和更改。
对于您在客户端缓存的数据,您将使用某种形式的数组:面向对象平台中 class 的多个实例; DataWindows(TM,google for it)或类似的 4GL; PHP.
中的简单数组
(旁白。请注意,在像您这样的情况下,专业提供商使用商业 SQL 平台、商业 4GL 并遵守架构和标准,一年内生产的产品。 )
架构 6
因此,让我们假设您了解以上所有内容,并欣赏其价值,尤其是架构 1 和 2。
- 如果你不这样做,请在这里停下来提问,不要继续下面的内容。
现在我们已经建立了完整的上下文,我们可以解决您问题的症结所在。
在应用程序的那些数组中,到底为什么要存储扁平化的数据视图?
- 因此弄乱并苦恼于问题
而不是存储标准化 table 的副本 ?
回答
永远不要复制任何可以派生的东西。这是一个架构原则,不限于数据库中的规范化。
永远不要合并任何东西。
如果这样做,您将创造:
客户端上的数据重复和大量重复。客户端不仅会又胖又慢,它会被重复数据压舱物固定在地板上。
附加代码,完全没有必要
该代码的复杂性
代码很脆弱,必须不断更改。
这正是您遇到的问题,是您直觉上知道错误的方法的结果,必须有更好的方法。您知道这是一个普遍且常见的问题。
另请注意,该方法、该代码构成了您的精神支柱。看看你如何格式化它并如此精美地呈现它:它对你很重要。我不愿意告诉你这一切。
- 哪一种不情愿很容易克服,因为你认真和直率的态度,而且知道这个方法不是你发明的
在每个代码段中,在演示时,需要时:
一个。在商业 Client/Server 背景下
执行一个 加入 简单的、规范化的、不重复的 table 的查询,并仅检索符合条件的行。从而得到当前数据值。用户永远不会看到陈旧的数据。在这里,经常使用视图(规范化数据的扁平化视图)。
b.在 sub-commercial non-server 上下文中
创建一个临时 result-set 数组,并 加入 简单的、不重复的数组(缓存的 table 的副本),并仅用符合条件的行填充它, 来自源数组。币种由后台进程维护
使用键来形成数组之间的连接,与使用键在数据库中的关系 table 中形成连接的方式完全相同。
当用户关闭时销毁那些组件window。
一个聪明的版本会删除result-set数组,并通过键连接源数组,并将结果限制为符合条件的行。
除了架构不正确之外,根本不需要嵌套数组或嵌套集或 JSON 或 JSON-like 结构。这是混淆架构 1 原则的结果。
- 如果您确实选择使用此类结构,那么仅 将它们用于临时result-set 数组。
最后,我相信这篇文章证明了 n tables 是一个 non-issue。更重要的是,m 层级在数据层次结构的深处,即 "nesting",是一个 non-issue.
答案 2
现在我已经给出了完整的上下文(而不是之前),这消除了你问题中的含义,并使它成为一个通用的内核问题。
The question is about ANY server-side/relational-db. [Which is better]:
2 loops, 5 simple "SELECT" queries
1 loop, 1 "JOIN" query
上面你给出的详细例子描述不准确。准确的描述是:
你的选择 1
2个循环,每个循环用于加载每个数组
每个循环 1 single-table SELECT 个查询
(执行了 n x m 次……只有最外层的循环是单次执行)
你的选择 2
1 Joined SELECT 查询执行一次
接下来是2个循环,每个循环用于加载每个数组
对于商业 SQL 平台,也不是,因为它不适用。
- 商业 SQL 服务器是一个 set-processing 引擎。将一个查询与任何需要的连接一起使用,return 是一个结果集。永远不要使用循环遍历行,这会将 set-processing 引擎缩减为 1970 年代之前的 ISAM 系统。在服务器中使用视图,因为它提供最高性能并且代码位于一个地方。
但是,对于 non-commercial、non-server 平台,其中:
您的 "server" 是 而不是 set-processing 引擎。它 return 是单行,因此您必须手动获取每一行并填充数组 或
您的 "server" 不 提供 Client/Server 绑定,即。它没有在客户端提供将传入结果集绑定到接收数组的功能,因此您必须逐行逐行遍历 returned 结果集,然后手动填充数组,
根据你的例子那么,答案很大程度上是你的选项 2。
请慎重考虑,评论或提问。
回复评论
Say I need to print this json (or other html page) to some STOUT (example: an http response to: GET /allUsersPhoneNumbers. It's just an example to clarify what I'm expecting to get), should return this json. I have a php function that got this 2 result sets (1). now it should print this json - how should I do that? this report could be an employee month salary for a whole year, and so one. one way or anther, I need to gather this information and represent it in a "JOIN"ed representation
可能是我说的不够清楚
基本上,除非万不得已,否则不要使用 JSON。这意味着发送到某个需要它的系统,这意味着接收系统,那个需求是愚蠢的。
确保你的系统不会对其他人提出这样的要求。
保持数据标准化。无论是在数据库中,还是在您编写的任何程序元素中。这意味着(在此示例中)每个 table 或数组使用一个 SELECT。这是为了加载目的,以便您可以在程序中的任何位置引用和检查它们。
当你需要join的时候,理解为:
- 一个result-set;派生关系;一个视图
- 因此是暂时的,它只存在于该元素的执行期间,仅
一个。对于 tables,以通常的方式通过键加入它们。一个查询,加入两个(或更多)tables.
b.对于数组,在程序中连接数组,就像在数据库中连接 tables 一样,通过 Keys.
你举的这个例子,是对某个请求的响应,先理解为类别[4],然后fulfill。
Why even consider JSON?
What has JSON got to do with this?
JSON is misunderstood and people are interested in the wow factor. It is a solution looking for a problem. Unless you have that problem it has no value.
Check these two links:
Copter - What is JSON
Whosebug - What is JSON
Now if you understand that, it is mostly for incoming feeds. Never for outgoing. Further, it requires parsing, deconstructing, etc, before the can be used.
召回:
I need to gather this information and represent it in a "JOIN"ed representation
是的。那是行人。加入 不是 意味着 JSONed.
在你的例子中,接收者期望一个扁平的视图(例如电子表格),所有的单元格都被填满,是的,对于拥有多个电话号码的用户,他们的用户详细信息将在第二个和随后的重复result-set 行。对于任何类型的 print,
例如。为了调试,我想要一个平面视图。它只是一个:
SELECT ... FROM Person JOIN PhoneNumber
和return那个。或者,如果您满足来自数组的请求,加入 Person 和 PhoneNumber 数组,这可能需要一个临时 result-set 数组,并且 return 那。
please don't tell me you should get only 1 user at a time, etc. etc.
正确。如果有人告诉您回归到程序处理(即在 WHILE 循环中逐行),引擎或您的程序已设置处理(即在一个命令中处理整个集合),则将他们标记为应该不听。
我已经说明了,你的选项2是正确的,选项1是错误的。就 GET 或 SELECT 而言。
另一方面,对于不具备 set-processing 能力的编程语言(即不能在单个命令中 print/set/inspect 数组),或 "servers" 不提供client-side 数组绑定,您必须编写循环,数据层次结构的每个深度一个循环(在您的示例中,两个循环,一个用于 Person,一个用于每个用户的 PhoneNumber)。
- 您必须这样做才能解析传入的 JSON 对象。
- 您必须这样做才能从选项 2 中 returned 的结果集中加载每个数组。
- 您必须这样做才能打印在选项 2 中 returned 的结果集中的每个数组。
对评论 2 的回复
I've ment I have to return a result represented in a nested version (let's say I'm printing the report to the page), json was just an example for such representation.
我认为您不理解我在此回答中提供的推理和结论。
- 为了打印和展示,从不嵌套。 打印扁平化视图,根据选项 2 从 SELECT 编辑的行 return。这就是我们一直在做的,当打印或显示关系数据时,为了31岁。它更容易阅读、调试、搜索、查找、折叠、装订、切割。你不能做嵌套数组的任何事情,除了看看它,然后说哎呀,这很有趣。
代码
警告
我更愿意拿你的代码来修改,但实际上,看你的代码,写得不好,结构不好,无法合理修改。其次,如果我使用它,那将是一个糟糕的教学工具。所以我必须给你新鲜的、干净的代码,否则你不会学到正确的方法。
此代码示例遵循我的建议,因此我不会重复。这远远超出了最初的问题。
-
您的请求,使用您的选项 2。一个 SELECT 执行一次。接着是一个循环。如果你愿意,你可以"pretty up"。
这是一个常见问题,特别是如果您正在创建 WebAPI,将那些 table 集转换为嵌套数组是一个大问题..
我总是为你选择第二个选项(虽然方法略有不同),因为第一个是最糟糕的方法......我从我的经验中学到的一件事是永远不要在循环内查询,那就是浪费数据库调用,你知道我想说什么。
虽然我不接受PerformanceDBA说的所有东西,但有两件大事我需要解决,
1.不要有重复数据
2. 只获取你想要的数据
我在加入 table 中看到的唯一问题是,我们最终复制了很多数据,以您的数据为例,加入 Person ans phoneNumber tables我们最终为每个人的每个 phone 号码复制了每个人,对于两个 table 有几百行它很好,想象一下我们需要合并 5 table 有数千行它的巨大。 ..
所以这是我的解决方案:
查询:
SELECT id, fullName From Person;
SELECT personId, phoneNumber FROM phoneNumbers
WHERE personId IN (SELECT id From Person);
所以我在结果集中得到 tables,现在我将 Table[0] 分配给我的人员列表,
并使用 2 个循环将正确的 phone 数字放入正确的人...
代码:
personList = ConvertToEntity<List<Person>>(dataset.Table[0]);
pnoList = ConvertToEntity<List<PhoneNumber>>(dataset.Table[1]);
foreach (person in personList) {
foreach (pno in pnoList) {
if(pno.PersonId = person.Id)
person.PhoneNumer.Add(pno)
}
}
我认为上述方法减少了很多重复,只得到了我想要的,如果上述方法有任何缺点,请告诉我...感谢您提出此类问题...
假设我有 2 张桌子, 我可以 "merge" 并在单个嵌套数组中表示。
我正在考虑最好的方法是什么,考虑到:
- 效率
- 最佳实践
- DB/server-side 使用权衡
- 你在现实生活中应该做什么
- 3 个、4 个或更多表的相同情况可以 "merged" 那样
问题是关于任何 server-side/relational-db。
我想到的 2 个简单方法 (如果你有其他的,请推荐! 注意 我要一个简单的服务器端和关系数据库, 所以请不要浪费你的时间解释为什么我不应该 使用这种DB,使用MVC设计等等……):
- 2 个循环,5 个简单的 "SELECT" 查询
- 1 个循环,1 个"JOIN" 查询
我试着举了一个简单而详细的例子, 为了解释我自己并更好地理解你的答案 (虽然代码怎么写and/or 发现可能的错误不是这里的问题, 所以尽量不要专注于此...)
SQL 用于创建表并将数据插入表的脚本
CREATE TABLE persons
(
id int NOT NULL AUTO_INCREMENT,
fullName varchar(255),
PRIMARY KEY (id)
);
INSERT INTO persons (fullName) VALUES ('Alice'), ('Bob'), ('Carl'), ('Dan');
CREATE TABLE phoneNumbers
(
id int NOT NULL AUTO_INCREMENT,
personId int,
phoneNumber varchar(255),
PRIMARY KEY (id)
);
INSERT INTO phoneNumbers (personId, phoneNumber) VALUES ( 1, '123-456'), ( 1, '234-567'), (1, '345-678'), (2, '456-789'), (2, '567-890'), (3, '678-901'), (4, '789-012');
A JSON 在我 "MERGED" 之后的表格表示:
[
{
"id": 1,
"fullName": "Alice",
"phoneNumbers": [
"123-456",
"234-567",
"345-678"
]
},
{
"id": 2,
"fullName": "Bob",
"phoneNumbers": [
"456-789",
"567-890"
]
},
{
"id": 3,
"fullName": "Carl",
"phoneNumbers": [
"678-901"
]
},
{
"id": 4,
"fullName": "Dan",
"phoneNumbers": [
"789-012"
]
}
]
两种方式的伪代码:
1.
query: "SELECT id, fullName FROM persons"
personList = new List<Person>()
foreach row x in query result:
current = new Person(x.fullName)
"SELECT phoneNumber FROM phoneNumbers WHERE personId = x.id"
foreach row y in query result:
current.phoneNumbers.Push(y.phoneNumber)
personList.Push(current)
print personList
2.
query: "SELECT persons.id, fullName, phoneNumber FROM persons
LEFT JOIN phoneNumbers ON persons.id = phoneNumbers.personId"
personList = new List<Person>()
current = null
previouseId = null
foreach row x in query result:
if ( x.id != previouseId )
if ( current != null )
personList.Push(current)
current = null
current = new Person(x.fullName)
current.phoneNumbers.Push(x.phoneNumber)
print personList
PHP/MYSQL 中的代码实现:
1。
/* get all persons */
$result = mysql_query("SELECT id, fullName FROM persons");
$personsArray = array(); //Create an array
//loop all persons
while ($row = mysql_fetch_assoc($result))
{
//add new person
$current = array();
$current['id'] = $row['id'];
$current['fullName'] = $row['fullName'];
/* add all person phone-numbers */
$id = $current['id'];
$sub_result = mysql_query("SELECT phoneNumber FROM phoneNumbers WHERE personId = {$id}");
$phoneNumbers = array();
while ($sub_row = mysql_fetch_assoc($sub_result))
{
$phoneNumbers[] = $sub_row['phoneNumber']);
}
//add phoneNumbers array to person
$current['phoneNumbers'] = $phoneNumbers;
//add person to final result array
$personsArray[] = $current;
}
echo json_encode($personsArray);
2。
/* get all persons and their phone-numbers in a single query */
$sql = "SELECT persons.id, fullName, phoneNumber FROM persons
LEFT JOIN phoneNumbers ON persons.id = phoneNumbers.personId";
$result = mysql_query($sql);
$personsArray = array();
/* init temp vars to save current person's data */
$current = null;
$previouseId = null;
$phoneNumbers = array();
while ($row = mysql_fetch_assoc($result))
{
/*
if the current id is different from the previous id:
you've got to a new person.
save the previous person (if such exists),
and create a new one
*/
if ($row['id'] != $previouseId )
{
// in the first iteration,
// current (previous person) is null,
// don't add it
if ( !is_null($current) )
{
$current['phoneNumbers'] = $phoneNumbers;
$personsArray[] = $current;
$current = null;
$previouseId = null;
$phoneNumbers = array();
}
// create a new person
$current = array();
$current['id'] = $row['id'];
$current['fullName'] = $row['fullName'];
// set current as previous id
$previouseId = $current['id'];
}
// you always add the phone-number
// to the current phone-number list
$phoneNumbers[] = $row['phoneNumber'];
}
}
// don't forget to add the last person (saved in "current")
if (!is_null($current))
$personsArray[] = $current);
echo json_encode($personsArray);
P.S。 这个 link 是这里另一个问题的一个例子,我在这里尝试建议第二种方式:tables to single json
一般来说,最好的做法是在尽可能少的数据库访问中获取所需的数据,然后将数据映射到适当的对象中。 (选项 2)
但是,为了回答您的问题,我会问您自己您的数据的用例是什么。如果你确定你需要你的人和你的 phone 号码数据,那么我会说第二种方法是你最好的选择。
但是,当连接的数据是 optional.One 时,选项一也可以有它的用例。例如,在 UI 你有一个 table 的所有人如果用户想查看某个人的 phone 号码,他们必须点击那个人。那么它将接受 table 到 "lazy-load" 所有 phone 号码。
初步
首先,感谢您花那么多精力来解释问题和格式化。很高兴看到有人清楚他们在做什么,他们在问什么。
但必须注意,这本身就形成了一个限制:您固执地认为这是正确的解决方案,并且通过一些小的修正或指导,这将起作用。那是不正确的。所以我必须请你放弃这个想法,退后一大步,并在没有那个想法的情况下查看 (a) 整个问题和 (b) 我的答案。
这个回答的上下文是:
您给出的所有明确考虑,非常重要,我不会重复
其中最重要的两个是最佳实践和我在现实生活中会做什么
此答案植根于标准,最佳实践 的更高级别或参考框架。这就是商业 Client/Server 世界所做的,或者应该做的。
这个问题,整个问题 space,正在成为一个普遍的问题。我将在这里进行全面考虑,从而也回答另一个SO问题。因此,它可能包含您需要的更多详细信息。如果是,请见谅。
考虑
数据库是一个server-based资源,由许多用户共享。在在线系统中,数据库是不断变化的。它包含每个事实的一个版本的真相(不同于一个地方的一个事实,这是一个单独的规范化问题)。
- 一些数据库系统没有服务器架构,因此在此类软件中 服务器 的概念是错误和误导的,这是单独但值得注意的要点。
据我了解,"performance reasons"需要JSON和JSON-like结构,正是因为"server"没有,不能,作为服务器执行。这个概念是在每个(每个)客户端上缓存数据,这样您就不会一直从 "server" 获取数据。
这打开了一堆蠕虫。如果您没有正确地设计和实现它,蠕虫就会在应用程序中蔓延。
这样的实现严重违反了Client/Server架构,它允许双方使用简单的代码,并适当部署软件和数据组件,这样实现时间就很短,而且效率高
此外,这样的实现需要大量的实现工作,而且很复杂,由很多部分组成。每个部分都必须适当设计。
Web 和该主题领域的许多书籍提供了令人困惑的方法组合,以假定的简单性为基础进行营销;舒适; anyone-can-do-anything; freeware-can-do-anything;等等。这些提议都没有科学依据。
Non-architecture & Sub-standard
事实证明,您已经了解到某些数据库设计方法是不正确的。您遇到了 一个 问题,一个 实例表明该建议是错误的。一旦你解决了这个问题,下一个你现在还不明显的问题就会暴露出来。这些概念是一组 never-ending 问题。
有时鼓吹的错误观念我就不一一列举了。我相信,随着您逐步完成我的回答,您会发现一个接一个的营销观念是错误的。
两条底线是:
这些概念违反了体系结构和设计标准,即Client/Server体系结构; Open Architecture;工程原理;在这个特定问题中,数据库设计原则较少。
这导致像您这样努力诚实工作的人被骗实施简单的概念,然后变成大规模的实施。永远无法正常工作的实现,因此它们需要大量的持续维护,并且最终会被批量替换。
建筑
被违反的中心原则是,永远不要复制任何东西。当你有一个数据重复的位置(由于缓存或复制或两个单独的整体应用程序等)时,你创建了一个副本,将在在线情况下不同步。所以原则是避免那样做。
- 当然,对于严肃的 third-party 软件,例如 gruntly 报告工具,它们很可能会在客户端缓存 server-based 数据。但请注意,他们已经投入了数百 man-years 来正确实施它,并充分考虑了上述内容。你的不是这样的软件。
本答案的其余部分不是提供关于必须理解的原则或每个错误的弊端和代价的讲座,而是提供所要求的你在现实生活中会做什么,使用正确的架构方法(比最佳实践高出一步)。
架构 1
请勿混淆
- 必须规范化的数据
和
- 结果集,根据定义,它是数据的扁平化视图("de-normalised" 不太正确)。
数据,假设它是规范化的,不会包含重复值;重复组。结果集将包含重复值;重复组。那就是行人。
请注意,嵌套集(或嵌套关系)的概念,在我看来不是很好的建议,正是基于这种混淆。
自 RM 出现以来 forty-five 年以来,他们一直无法区分基础关系(为此归一化 不适用) 来自派生关系(规范化不适用)。
其中两个支持者目前正在质疑第一范式的定义。 1NF是其他NF的基础,如果新的定义被接受,所有的NF都将被渲染value-less。结果将是归一化本身(在数学术语中很少定义,但被专业人士清楚地理解为一门科学)即使没有被摧毁也会受到严重破坏。
架构二
有一个centuries-old科学或工程原理,内容(数据)必须与控制(程序元素)分开。这是因为两者的分析、设计和实现完全不同。这个原则在软件科学中同样重要,它有具体的表述。
为了保持简短(哈哈),我假设您理解以下内容,而不是讨论:
数据和程序元素之间存在科学要求的界限。将它们混合在一起会导致 error-prone 且难以维护的复杂对象。
这一原则的混淆在OO/ORM世界中达到了流行病的程度,其后果影响深远。
只有专业人士才能避免这种情况。对于其余的人,绝大多数人,他们接受新定义 "normal",他们一生都在解决我们根本没有的问题。
根据 E F Codd 博士的关系模型表格形式存储和呈现数据的架构优势和巨大价值=408=]。数据规范化有特定规则。
而且重要的是,您可以确定写作和销售书籍的人何时建议 non-relational 或 anti-relational 方法。
架构 3
如果在客户端缓存数据:
缓存绝对最小值。
也就是说只缓存在线环境中不会发生变化的数据。这意味着仅参考和查找 tables,填充更高级别 classifier 的 tables,drop-downs,等等
货币
对于您缓存的每个 table,您必须有一种方法来 (a) 确定缓存的数据与服务器上存在的真实版本相比已经过时,并且(b) 从服务器刷新它,(c) 在 table-by-table 基础上。
通常,这涉及一个每 (e) 五分钟执行一次的后台进程,查询客户端上每个缓存 table 的最大更新日期时间与服务器上的日期时间,如果更改,则刷新table、 和 所有其子 table,那些依赖于更改的 table.
当然,这要求您在每个 table 上都有一个
UpdatedDateTime
列。这不是负担,因为无论如何你都需要 OLTP ACID 事务(如果你有一个真正的数据库,而不是一堆 sub-standard 文件)。
这真的意味着,永远不要复制,编码负担过重。
架构 4
在sub-commercial、non-server的世界里,我了解到有人建议"everything"的反向缓存。
这是像 PostgreSQL 这样的程序可以在 multi-user 系统中使用的唯一方法。
一分钱一分货:一分钱一分货,一分钱一分货;你支付零,你得到零。
架构 3 的推论是,如果您在客户端缓存数据,请不要缓存经常更改的 tables。这些是交易和历史 tables。在客户端缓存这样的 table 或所有 table 的想法是完全破产的。
在真正的 Client/Server 部署中,由于使用了适用的标准,对于每个数据 window,应用程序应该只查询所需的行,以满足特定的需求,在特定的时间,基于上下文或过滤器值等。应用程序不应加载整个 table.
如果同一用户使用相同的 window 检查其内容,则在第一次检查 15 分钟后,数据将过时 15 分钟。
对于 freeware/shareware/vapourware 平台,它们通过缺少服务器架构来定义自己,因此结果是,性能是 non-existent,当然,你必须缓存超过客户端上的最小 tables。
如果你这样做,你必须考虑到以上所有因素,并正确实施,否则你的应用程序将被破坏,后果将促使用户寻求你的终止。如果不止一个用户,他们就会有相同的原因,很快就会形成一支军队。
架构 5
现在我们来看看如何在客户端缓存那些精心挑选的tables。
请注意,数据库在增长,它们在扩展。
如果系统坏了,一个故障,它会以小增量增长,需要很大的努力。
如果系统即使是小成功,它也会成倍增长。
如果系统(每个数据库,和应用程序,分别)设计和实现得好,变化会很容易,错误会很少。
因此,应用程序中的所有组件都必须经过适当设计,以符合适用标准,并且数据库必须完全规范化。这反过来又最大限度地减少了数据库更改对应用程序的影响,反之亦然。
该应用程序将由简单而不复杂的对象组成,这些对象易于维护和更改。
对于您在客户端缓存的数据,您将使用某种形式的数组:面向对象平台中 class 的多个实例; DataWindows(TM,google for it)或类似的 4GL; PHP.
中的简单数组
(旁白。请注意,在像您这样的情况下,专业提供商使用商业 SQL 平台、商业 4GL 并遵守架构和标准,一年内生产的产品。 )
架构 6
因此,让我们假设您了解以上所有内容,并欣赏其价值,尤其是架构 1 和 2。
- 如果你不这样做,请在这里停下来提问,不要继续下面的内容。
现在我们已经建立了完整的上下文,我们可以解决您问题的症结所在。
在应用程序的那些数组中,到底为什么要存储扁平化的数据视图?
- 因此弄乱并苦恼于问题
而不是存储标准化 table 的副本 ?
回答
永远不要复制任何可以派生的东西。这是一个架构原则,不限于数据库中的规范化。
永远不要合并任何东西。
如果这样做,您将创造:
客户端上的数据重复和大量重复。客户端不仅会又胖又慢,它会被重复数据压舱物固定在地板上。
附加代码,完全没有必要
该代码的复杂性
代码很脆弱,必须不断更改。
这正是您遇到的问题,是您直觉上知道错误的方法的结果,必须有更好的方法。您知道这是一个普遍且常见的问题。
另请注意,该方法、该代码构成了您的精神支柱。看看你如何格式化它并如此精美地呈现它:它对你很重要。我不愿意告诉你这一切。
- 哪一种不情愿很容易克服,因为你认真和直率的态度,而且知道这个方法不是你发明的
在每个代码段中,在演示时,需要时:
一个。在商业 Client/Server 背景下
执行一个 加入 简单的、规范化的、不重复的 table 的查询,并仅检索符合条件的行。从而得到当前数据值。用户永远不会看到陈旧的数据。在这里,经常使用视图(规范化数据的扁平化视图)。b.在 sub-commercial non-server 上下文中
创建一个临时 result-set 数组,并 加入 简单的、不重复的数组(缓存的 table 的副本),并仅用符合条件的行填充它, 来自源数组。币种由后台进程维护使用键来形成数组之间的连接,与使用键在数据库中的关系 table 中形成连接的方式完全相同。
当用户关闭时销毁那些组件window。
一个聪明的版本会删除result-set数组,并通过键连接源数组,并将结果限制为符合条件的行。
除了架构不正确之外,根本不需要嵌套数组或嵌套集或 JSON 或 JSON-like 结构。这是混淆架构 1 原则的结果。
- 如果您确实选择使用此类结构,那么仅 将它们用于临时result-set 数组。
最后,我相信这篇文章证明了 n tables 是一个 non-issue。更重要的是,m 层级在数据层次结构的深处,即 "nesting",是一个 non-issue.
答案 2
现在我已经给出了完整的上下文(而不是之前),这消除了你问题中的含义,并使它成为一个通用的内核问题。
The question is about ANY server-side/relational-db. [Which is better]:
2 loops, 5 simple "SELECT" queries
1 loop, 1 "JOIN" query
上面你给出的详细例子描述不准确。准确的描述是:
你的选择 1 2个循环,每个循环用于加载每个数组 每个循环 1 single-table SELECT 个查询 (执行了 n x m 次……只有最外层的循环是单次执行)
你的选择 2 1 Joined SELECT 查询执行一次 接下来是2个循环,每个循环用于加载每个数组
对于商业 SQL 平台,也不是,因为它不适用。
- 商业 SQL 服务器是一个 set-processing 引擎。将一个查询与任何需要的连接一起使用,return 是一个结果集。永远不要使用循环遍历行,这会将 set-processing 引擎缩减为 1970 年代之前的 ISAM 系统。在服务器中使用视图,因为它提供最高性能并且代码位于一个地方。
但是,对于 non-commercial、non-server 平台,其中:
您的 "server" 是 而不是 set-processing 引擎。它 return 是单行,因此您必须手动获取每一行并填充数组 或
您的 "server" 不 提供 Client/Server 绑定,即。它没有在客户端提供将传入结果集绑定到接收数组的功能,因此您必须逐行逐行遍历 returned 结果集,然后手动填充数组,
根据你的例子那么,答案很大程度上是你的选项 2。
请慎重考虑,评论或提问。
回复评论
Say I need to print this json (or other html page) to some STOUT (example: an http response to: GET /allUsersPhoneNumbers. It's just an example to clarify what I'm expecting to get), should return this json. I have a php function that got this 2 result sets (1). now it should print this json - how should I do that? this report could be an employee month salary for a whole year, and so one. one way or anther, I need to gather this information and represent it in a "JOIN"ed representation
可能是我说的不够清楚
基本上,除非万不得已,否则不要使用 JSON。这意味着发送到某个需要它的系统,这意味着接收系统,那个需求是愚蠢的。
确保你的系统不会对其他人提出这样的要求。
保持数据标准化。无论是在数据库中,还是在您编写的任何程序元素中。这意味着(在此示例中)每个 table 或数组使用一个 SELECT。这是为了加载目的,以便您可以在程序中的任何位置引用和检查它们。
当你需要join的时候,理解为:
- 一个result-set;派生关系;一个视图
- 因此是暂时的,它只存在于该元素的执行期间,仅
一个。对于 tables,以通常的方式通过键加入它们。一个查询,加入两个(或更多)tables.
b.对于数组,在程序中连接数组,就像在数据库中连接 tables 一样,通过 Keys.
你举的这个例子,是对某个请求的响应,先理解为类别[4],然后fulfill。
Why even consider JSON? What has JSON got to do with this?
JSON is misunderstood and people are interested in the wow factor. It is a solution looking for a problem. Unless you have that problem it has no value. Check these two links:
Copter - What is JSON
Whosebug - What is JSONNow if you understand that, it is mostly for incoming feeds. Never for outgoing. Further, it requires parsing, deconstructing, etc, before the can be used.
召回:
I need to gather this information and represent it in a "JOIN"ed representation
是的。那是行人。加入 不是 意味着 JSONed.
在你的例子中,接收者期望一个扁平的视图(例如电子表格),所有的单元格都被填满,是的,对于拥有多个电话号码的用户,他们的用户详细信息将在第二个和随后的重复result-set 行。对于任何类型的 print,
例如。为了调试,我想要一个平面视图。它只是一个:
SELECT ... FROM Person JOIN PhoneNumber
和return那个。或者,如果您满足来自数组的请求,加入 Person 和 PhoneNumber 数组,这可能需要一个临时 result-set 数组,并且 return 那。
please don't tell me you should get only 1 user at a time, etc. etc.
正确。如果有人告诉您回归到程序处理(即在 WHILE 循环中逐行),引擎或您的程序已设置处理(即在一个命令中处理整个集合),则将他们标记为应该不听。
我已经说明了,你的选项2是正确的,选项1是错误的。就 GET 或 SELECT 而言。
另一方面,对于不具备 set-processing 能力的编程语言(即不能在单个命令中 print/set/inspect 数组),或 "servers" 不提供client-side 数组绑定,您必须编写循环,数据层次结构的每个深度一个循环(在您的示例中,两个循环,一个用于 Person,一个用于每个用户的 PhoneNumber)。
- 您必须这样做才能解析传入的 JSON 对象。
- 您必须这样做才能从选项 2 中 returned 的结果集中加载每个数组。
- 您必须这样做才能打印在选项 2 中 returned 的结果集中的每个数组。
对评论 2 的回复
I've ment I have to return a result represented in a nested version (let's say I'm printing the report to the page), json was just an example for such representation.
我认为您不理解我在此回答中提供的推理和结论。
- 为了打印和展示,从不嵌套。 打印扁平化视图,根据选项 2 从 SELECT 编辑的行 return。这就是我们一直在做的,当打印或显示关系数据时,为了31岁。它更容易阅读、调试、搜索、查找、折叠、装订、切割。你不能做嵌套数组的任何事情,除了看看它,然后说哎呀,这很有趣。
代码
警告
我更愿意拿你的代码来修改,但实际上,看你的代码,写得不好,结构不好,无法合理修改。其次,如果我使用它,那将是一个糟糕的教学工具。所以我必须给你新鲜的、干净的代码,否则你不会学到正确的方法。
此代码示例遵循我的建议,因此我不会重复。这远远超出了最初的问题。
-
您的请求,使用您的选项 2。一个 SELECT 执行一次。接着是一个循环。如果你愿意,你可以"pretty up"。
这是一个常见问题,特别是如果您正在创建 WebAPI,将那些 table 集转换为嵌套数组是一个大问题..
我总是为你选择第二个选项(虽然方法略有不同),因为第一个是最糟糕的方法......我从我的经验中学到的一件事是永远不要在循环内查询,那就是浪费数据库调用,你知道我想说什么。
虽然我不接受PerformanceDBA说的所有东西,但有两件大事我需要解决, 1.不要有重复数据 2. 只获取你想要的数据
我在加入 table 中看到的唯一问题是,我们最终复制了很多数据,以您的数据为例,加入 Person ans phoneNumber tables我们最终为每个人的每个 phone 号码复制了每个人,对于两个 table 有几百行它很好,想象一下我们需要合并 5 table 有数千行它的巨大。 ..
所以这是我的解决方案:
查询:
SELECT id, fullName From Person;
SELECT personId, phoneNumber FROM phoneNumbers
WHERE personId IN (SELECT id From Person);
所以我在结果集中得到 tables,现在我将 Table[0] 分配给我的人员列表, 并使用 2 个循环将正确的 phone 数字放入正确的人...
代码:
personList = ConvertToEntity<List<Person>>(dataset.Table[0]);
pnoList = ConvertToEntity<List<PhoneNumber>>(dataset.Table[1]);
foreach (person in personList) {
foreach (pno in pnoList) {
if(pno.PersonId = person.Id)
person.PhoneNumer.Add(pno)
}
}
我认为上述方法减少了很多重复,只得到了我想要的,如果上述方法有任何缺点,请告诉我...感谢您提出此类问题...