精华内容
下载资源
问答
  • 需求文档

    千次阅读 多人点赞 2019-03-29 18:10:18
    信息结构图中关于友情链接功能的信息数据只有“名称”和“链接”两个内容,但是实际功能需求中,友情链接还有两个功能,分别是“显示或隐藏”和“是否新窗口打开”,这两个功能会产品原型和需求文档中详细描述,...

    产品设计是一个由抽象的概念到具体形象化的处理过程,通过文字或图像等方式将我们规划的产品需求展现出来。它将产品的某种目的或需求转换为一个具体的物理或工具的过程,把一种计划、规划设想、问题解决的方法,通过具体的操作,以理想的形式表达出来。

    由于产品设计阶段要全面确定整个产品策略、外观、结构、功能,从而确定整个产品系统的布局,因而,产品设计的意义重大,具有“牵一发而动全局”的重要意义。如果一个产品的设计缺乏具体形象的表述,那么研发时就将耗费大量资源和劳动力来调整需求。相反,好的产品设计,不仅表现在功能上的优越性,而且便于执行时理解,从而使产品的研发效率得以增强。

    1、产品需求文档介绍

    产品设计的最终表述的形式被称为产品需求文档,业界常常称呼为PRD文档,这是英文Product Requirement Document的缩写。产品需求文档是将产品规划和设计的需求具体形象化表述出来的一种展现形式,主要用于产品界面设计和研发使用。

    PRD文档是基于BRD、MRD的延续文档,主要是一份给执行层面的工作人员阅读的文档,这部分人群绝大多数是设计与技术人员。在这类人群中,设计师更多依赖于产品原型进行交互或视觉的设计,因此看这份文档的人主要是技术人员。相对于技术人员,他们不太关注产品的商业需求和市场愿景,因为在进行产品讨论立项时,产品的定义就已经向参与设计和研发的人员宣讲过,因此技术人员更多的是关注界面、功能、交互、元素等等内容,因此产品需求文档是一份详细的产品功能需求说明文档,是产品文档中最底层和最细致的文档。

    因为阅读人类的因素,所以产品需求文档是一份没有闲话,直入主题的功能说明文档。并且产品需求文档是没有标准规范的,也没有统一的模板,每个公司都不一样和每个人也不一样,这个取决于个人习惯和团队要求。虽然产品需求文档没有明确的规范,但是目的都是一样的,必须能够明确产品的功能需求,便执行人员理解任务要求。

    2、产品需求文档写作

    产品需求文档是产品经过规划和设计之后的最终执行文档,因此这份文档的质量好坏直接影响到执行部门是否能够明确产品的功能和性能。

    2.1、罗列信息(信息结构图)

    在写产品需求文档之前,我们需要先罗列出产品功能的信息内容,这一步是将想法逐渐清晰的第一步,也是帮助我们接下来设计功能的辅助信息,同时也可以辅助服务端技术人员创建数据库。因为这是第一步,所以我们不需要罗列的很详细,在之后的步骤里,我们会逐步改进和完善信息内容。

    罗列信息内容的方式有很多种,文本形式、思维导图形式等等都可以,最主要的是能够清晰易懂,我最常用的方法就是使用思维导图软件(MindManager)罗列成结构图,因此我称这一步为“信息结构图”。

    shu-17

    上图是一张以Blog系统为示例的信息结构图。信息结构图是一种接近数据库结构的图表,在罗列信息结构时,更多的是考虑信息数据,但是他并不是真正意义的数据库结构。信息结构图是提供给产品经理自己梳理信息内容的结构图,也是方便产品经理和服务端技术人员沟通数据结构的参考图,技术人员会根据这张图表的内容再结合产品原型或需求文档,然后规划和设计出真正意义上的数据库结构。

    信息结构图中关于友情链接功能的信息数据只有“名称”和“链接”两个内容,但是在实际功能需求中,友情链接还有两个功能,分别是“显示或隐藏”和“是否新窗口打开”,这两个功能会在产品原型和需求文档中详细描述,但是在信息结构中是没有体现的,因为从产品层面上来说,这两个只是功能,并不是信息内容。但是在真正数据库中,友情链接的这两个功能分别也是有字段参数的,程序在读取该参数后便知道友情链接的属性,然后处理友情链接是显示还是隐藏,是新窗口打开还是本窗口打开。通过友情链接这个例子,我们就知道了在实际中数据结构和信息结构是不一样的,信息结构只是产品层面的数据内容。

    无论是什么样的产品类型,无论从哪里入手,我们第一步都是先要罗列信息结构,因为信息结构图不仅是辅助技术人员创建数据库的图表,也是辅助产品人员进行产品功能规划的参考,只有对信息或数据的结构了解了,我们才能更好的设计产品。

    信息结构图是我们将概念想法形成结构化的第一步,也是我们接下来几步工作的辅助文档,同时在接下来的几步工作中,我们还会不断的完善信息的结构。

    2.2、梳理需求(产品结构图)

    当我们对产品的信息结构了解后,我们就需要规整脑海中的产品需求,让想法更加结构化,因此这一步就要梳理产品的需求。在设计产品原型之前,我们首先要罗列出产品的功能结构,包括频道、页面、模块及元素。这一步依然使用思维导图软件,像绘制楼盘鸟瞰图一样将产品的结构绘制成结构图,因此我称这一步为“产品结构图”。

    产品结构图是一种将产品原型以结构化的方式展现的图表,结构内容也如同产品原型一样,从频道到页面,再细化页面功能模块和元素。所以产品结构图是产品经理在设计原型之前的一种思路梳理的方式,并不是给其他工作人员查看的文档,通过类似鸟瞰式的结构图可以让产品经理对产品结构一目了然,也方便思考。

    shu-18 shu-19

    如上图示例,“活动大全”的产品结构依次是:产品 -> 频道 -> 页面 -> 页面元素 -> 操作 -> 元素。我们换一个角度观看示例,产品结构图实际上就是一种结构化的产品原型。这样做的目的就是梳理产品结构逻辑,让我们清楚的知道产品有几个频道,频道下面有没有子频道或者有多少个页面,这些页面里又有哪些功能模块,这些功能模块里又有哪些元素。

    shu-19

    上图以我们第一步的“信息结构图”为基础绘制的“产品结构图”,有了这份结构导图,我们可以对产品进行鸟瞰式考虑和完善,当有问题时,修改起来也比原型和文档方便很多。比如在后续规划中,我们发现文章的图片等附件上传后,管理不太方便,这时就可以在结构图中增加一个“附件管理”频道。如果我们使用产品结构图的方式,那么附件管理的功能增加和修改就会比原型工具更加便捷和效率。

    产品结构图的方法同样适用于移动互联网产品的设计,并且比起Web产品更加容易梳理产品结构。

    产品结构图是一种让产品经理通过思维导图的方式梳理思路的方法,通过这种方法可以明确产品有多少个频道、有多少个页面、页面有多少个功能模块、功能模块有多少个元素,逐步的将脑海里的想法明确梳理成结构。虽然这种方法能够明确产品的结构,但是这样的思维导图也就只有产品经理自己能够看懂,因为对于设计和技术人员这是一个抽象的表述方式,如果没有详细的讲解,是很难理解的。

    产品结构图是将产品原型具体化的一种方式,只是罗列了产品的频道页面和功能,但是没有详细的进行推演,关于细化方面是否符合产品逻辑,是否符合用户体验,这些都是没有深思过的,因此我们接下来就要进行原型设计,开始具体的考虑可行性。

    2.3、原型设计(界面线框图)

    当我们逐渐清晰了产品的需求后,并梳理了产品的各个频道及页面,那么这一步就要开始验证这些想法的具体界面表现和方案的可行性了。

    原型设计是帮助我们更细致的思考,并做各项需求的评估,同时也是将自己脑海里的想法进行输出的一种方式。通过原型设计后,我们就可以进行产品宣讲了,相比较于抽象的文字描述,原型则更加直观的展现产品的需求,设计和技术人员或者老板也能够更加直观的了解到产品意图。

    原型设计是将结构化的需求进行框架化,因此原型也被称为线框图,具体的表现手法有很多种,相关的辅助软件也有很多,例如:Axure RP、Balsamiq Mockups、UIDesigner等等。

    当到了原型设计这一步时,已经不仅仅是构思了,我们需要更加深入的了解每个页面上元素和这些元素的属性。例如按钮元素,我们就需要考虑这个按钮的功能,并且这个功能操作后带给后端和前端的反馈。例如注册会员按钮,用户操作后,第一步逻辑是验证用户输入的信息是否合法,不合法则给出前端反馈;合法则和后端通信验证是否已经存在同样信息,已经存在则给出前端反馈,不存在则进入下一步,注册成功;注册成功后的反馈是跳转页面,还是弹出层提示用户完善资料,这些都是需要更详情的考虑。当然这些更细致的思考是留在需求文档撰写时的,而此时我们需要做的就是把这些元素通过原型表现出来。

    原型设计的表现手法主要有三种:手绘原型、灰模原型、交互原型。从工作效率的角度考虑,我非常建议先通过手绘的形式快速在草纸上绘制出产品的原型,推演和讨论方案的可行性。当方案的可行性被验证之后,我们再根据个人习惯或团队要求,通过软件工具进行更深入的设计。

    ① 手绘原型

    因为原型也被称为线框图,因此手绘是最简单直接的方法,也是最快速的表现产品轮廓的手法。

    手绘原型

    手绘原型在初期验证想法时非常高效,也方便讨论和重构,同时也适合敏捷开发时快速出原型。

    ② 灰模原型

    灰模原型是由图形设计软件制作而成,最常用的软件是Photoshop和Fireworks,相对手绘原型,灰模更加清晰和整洁,也适用于正式场合的PPT形式宣讲。

    灰模原型
    phone

    灰模原型也可以称之为平面原型,所以如果不会使用图形软件也可以使用Axure RP设计,相比交互原型,灰模原型只是缺少交互效果,仅仅是将产品需求以线框结构的方式展示出来,让产品需求更加规整的直观展现。

    shu-23

    ③ 交互原型

    交互原型是使用原型设计软件完成的原型,常用软件是Axure RP,通常情况交互原型的设计要早于产品需求文档,是产品经理想法推演的重要一步。通过Axure RP之类的交互原型软件制作出来的产品原型,在功能需求和交互需求的表现上,几乎和正式产品是一致的,所以有时交互原型也被称为产品Demo版。

    通常情况下交互原型是产品经理与交互设计师共同讨论确定,然后由交互设计师制作,但是绝大多数的公司是没有交互设计师这个职位的,因此这类工作最终是由产品经理来负责的。

    以上三种方法并不是渐进的流程,而是三种原型设计的方法,具体取决于你的产品需求和团队要求。

    对于产品经理来说,原型设计是为了帮助我们细致的考虑方案,并论证方案的可行性,同时也是为了产品宣讲时让听众能够清晰直观的了解产品,避免抽象的语言描述导致听众理解困难和理解偏差。产品原型也是为了确保产品在执行过程中,是按产品经理最初设想的需求和期望完成的,因此产品经理的原型是没有很高的要求的,只要对方能够听懂看懂就可以了,所以使用手绘原型是最高效率的方法。

    2.4、用例模型(产品用例图)

    用例(Use Case)是一种描述产品需求的方法,使用用例的方法来描述产品需求的过程就是用例模型,用例模型是由用例图和每一个用例的详细描述文档所组成的。在技术和产品的工作领域里都有用例模型的技能知识。技术人员的用例主要是为了方便在多名技术人员协同工作,或者技术人员任务交接时,让参与者更好的理解代码的逻辑结构。产品人员的用例主要是为了方便技术研发和功能测试时,让参与者更好的理解功能的逻辑。

    用例起源和发展于软件时代的产品研发,后来被综合到UML规范之中,成为一种标准化的需求表述体系。虽然用例在软件研发和技术工作中应用的非常广泛,但是在互联网产品规划和设计中,并不经常使用,互联网产品的需求表达为了敏捷效率,通常采用原型加产品需求文档。

    UML是英文Unified Modeling Language的缩写,中文称为统一建模语言或标准建模语言,是用例模型的建模语言,常用工具是Microsoft Office Visio。产品用例是一种通过用户的使用场景来获取需求的方式,每个用例提供了一个或多个场景,该场景说明了产品是如何和最终用户或其它产品互动,也就是谁可以用产品做什么,从而获得一个明确的业务目标。

    ① 用例图

    用例图并不是画成了图形的用例。用例图包含一组用例,每一个用例用椭圆表示,放置在矩形框中;矩形框表示整个系统。矩形框外画如图所示的小人,表示参与者。参与者不一定是人,可以是其它产品、软件或硬件等等。某一参与者与某一用例用线连起来,表示该参与者和该用例有交互。

    shu-24

    许多人通过UML认识了用例,UML定义为展现用例的图形符号。UML并不是为描述用例定义书写格式的标准,因此许多人误认为这些图形符号就是用例本身;然而,图形符号只能给出最简单的一个或一组用例的概要。UML是用例图形符号最流行的标准,但是除了UML标准,用例也有其它的可选择的标准。

    ② 用例描述文档

    用例图只是在总体上大致描述了产品所能提供的各种服务,让我们对于产品的功能有一个总体的认识。除此之外,我们还需要描述每一个用例的详细信息,这些信息应该包含以下内容:

    shu-25

    用例名称:本用例的名称或者编号

    行为角色:参与或操作(执行)该用例的角色

    简要说明:简要的描述一下本用例的需求(作用和目的)

    前置条件:参与或操作(执行)本用例的前提条件,或者所处的状态

    后置条件:执行完毕后的结果或者状态

    用例描述文档基本上是用文本方式来表述的,为了更加清晰地描述用例,也可以选择使用状态图、流程图或序列图来辅助说明。只要有助于表达的简洁明了,就可以在用例中任意粘贴用户界面和流程的图形化显示方式,或是其它图形。如流程图有助于描述复杂的决策流程,状态转移图有助于描述与状态相关的系统行为,序列图适合于描述基于时间顺序的消息传递。

    在互联网产品和设计中,用例的使用越来越少,通常有了产品原型再加上功能流程图和功能说明文档就能够将产品需求详细的表述清楚,所以也没有必须撰写用例了。但是在大公司里,往往会追求产品流程的规范性,要求撰写用例,不过在敏捷开发的时候也会采用其它更有效率的方式,不一定非要撰写用例。

    前面几步我们将产品需求逐渐细化并且通过原型的方式将产品需求形象化的展现了出来,但是在产品功能的逻辑细节方面,原型就非常不直观了,所以用例是一个非常重要的描述需求过程的文档。

    但是由于用例文档以文字为主,并且格式复杂,不适用于高效率的产品需求表述,所以展现逻辑流程的“功能流程图”是一个简洁直观的可替代用例文档的方式。

    shu-26

    (请点击查看大图)

    如上图所示,功能流程图是一种使用图形的方式表示算法逻辑的图表,因为千言万语不如一张图,通过流程图将“优惠券”功能模块的逻辑和需求非常形象直观、一目了然的展现了出来。

    流程图的展现方式也不会产生“歧义性”,便于理解,逻辑出错时也非常容易发现,并且可以直接转化为程序需求描述文档。

    2.6、需求文档(PRD文档)

    前面的几个步骤是为了帮助我们梳理需求、验证可行性和明确细节,到了这一步的时候我们已经非常清晰的了解产品需求,此时撰写产品需求文档可以大大减少和避免了撰写文档时容易忽略的细节黑洞。

    产品需求文档是将产品规划和设计的需求具体形象化表述出来的一种展现形式,主要用于产品界面设计和研发使用。因为每个人的习惯和团队要求都是不一样的,所以产品需求文档没有统一的行业规范标准,无论以什么样的格式撰写产品需求文档,最终的目的都是让执行人员能够理解产品需求,根据需求完成产品。

    产品需求文档的表现形式有很多种,常见的有Word、图片和交互原型这三种形式,文档内容通常包含信息结构图、界面线框图、功能流程图、功能说明文档。虽然产品需求文档没有标准的规范,但是有两项是必不可少的,那就是文件标识和修改记录。文档在撰写过程中,我们可以自行不断的修改完善,但是如果正式发布或交给团队其他成员后,一旦有了修改,为了文档的同步,我们就需要标注出文档的修改内容,备注修改记录,这样可以方便大家查看和了解改动的内容。关于文件标识和修改记录,格式都大同小异。

    prd

    ① Word

    这是传统意义上的产品需求文档,主要有四个部分组成(具体根据产品要求进行划分),分别是:结构图、全局说明、频道功能、效果图。

    因为产品需求文档的阅读者主要是偏向于技术人员,因此文档的目的性非常明确,就是要描述产品的功能需求,所有产品需求文档没有关于市场方面的描述。为了保证需求的执行效率,建议大家尽量减少不必要的文字,在能够让阅读者看懂并且了解产品意图的情况下,文字越少越好。这主要是因为绝大多数人是没有足够耐心认真看完产品需求文档的,因此我们要尽量减化文档内容。

    ①-1、结构图:

    ①-1.1、信息结构图:主要是辅助服务端技术人员创建或调整数据结构的参考文件

    ①-1.2、产品结构图:主要是辅助设计和技术开发人员了解产品的全局结构。

    ①-2、全局说明:

    主要讲解产品的全局性功能的说明,例如网站产品的页面编码、用户角色,移动产品的缓存机制、下载机制,这类全局性功能的说明。这里我举一个移动产品的“状态维持与恢复”的例子。示例如下:

    状态的维持与恢复

    当用户退出产品时(误操作、Home键、锁屏、自动关机),产品需要维持用户操作前的状态,当用户返回产品时仍可以恢复到之前状态,并继续使用。

    维持状态包括流程操作、信息浏览、文本输入、文件下载。

    锁屏状态时,如果用户在产品中有下载任务时,仍然保持下载。

    ①-3、频道功能:

    以频道为单位,页面为子项,分别描述产品的频道、页面及页面模块元素的功能需求。示例如下:

    1、频道名:频道介绍及需求说明

    2、页面1:页面介绍及需求说明

    2.1、页面模块1:模块功能需求说明

    2.1.1、页面模块1-元素1:功能说明

    2.1.2、页面模块1-元素2:功能说明

    2.2、页面模块2:模块功能需求说明

    在撰写功能需求时,我们需要考虑用户的流程,例如一个“完成”按钮,我们需要描述他完成后,系统要不要给出反馈提示(反馈提示是什么样的形式反馈,内容显示成什么,有没有内容需要调取数据库),或者要不要跳转页面(跳转到哪个页面,这个页面是其他频道页面,还是这个功能的子页面,如果是子页面就需要再描述这个子页面的模块及元素内容)。

    ①-4、效果图:

    效果图是由设计师完成的产品图,和实际开发完成的产品保真度一致。

    这个示例是一个移动产品(iPad)需求文档,其中部分隐私内容已过滤隐藏,并且只保留了首页和地图找房频道的需求说明。由于工作环境没有交互设计师,所以Word文档中包含了部分交互说明。

    ② 图片

    图片形式的产品需求文档是基于效果图的说明文件,将传统Word形式的功能需求说明标注在效果图上,这种方式经常使用在移动互联网领域,实际上是图文形式的交互需求文件,只是在此基础上更深入的描述出功能需求。

    对于图片形式的产品需求文档,我们只需要另外再描述一下全局说明,其他频道页面的需求直接以图片形式展示,这种方式相对于Word文档的纯文字更加生动易读并且直观,因此有一些产品经理非常喜欢用这种方式代替Word形式的产品需求文档。

    picture_prd

    ③ 交互原型

    这里指的交互原型就是前面篇章讲到的原型设计,使用Axure PR之类的交互原型设计软件制作出来的产品原型非常真实和直观,并且原型软件还支持元素标注和导出Word文档,因此很多产品经理都喜欢使用Axure PR来代替Word完成产品需求文档。

    当我们通过Axure PR制作出产品原型后,实际上他已经是很完善的产品Demo了,因此我们只需要加上元素的标注,在标注中说明功能需求,这样导出的HTML文件相比Word文档更直观易懂,是非常高效的产品需求说明方式。

    shu-31

    无论你采用哪种方式撰写需求文档,最终的目的都是为了方便团队成员理解产品的意图,因此哪种方法能够避免细节黑洞,高效完成产品的设计和研发,那么这种方法就是最有效的方法


    展开全文
  • ES系列之嵌套文档和父子文档

    千次阅读 多人点赞 2020-03-26 19:45:47
    需求背景 很多时候mysql的表之间是一对多的关系,比如订单表和商品表。...索引是独立文档的集合体。不同的索引之间一般是没有关系的。 不过ES目前毕竟发展到7.x版本了, 已经有几种可选的方式能...

    需求背景

    很多时候mysql的表之间是一对多的关系,比如订单表和商品表。一笔订单可以包含多个商品。他们的关系如下图所示。

    在这里插入图片描述

    ElasticsSearch(以下简称ES)处理这种关系虽然不是特别擅长(相对于关系型数据库),因为ES和大多数 NoSQL 数据库类似,是扁平化的存储结构。索引是独立文档的集合体。不同的索引之间一般是没有关系的。

    不过ES目前毕竟发展到7.x版本了, 已经有几种可选的方式能够高效的支持这种一对多关系的映射。

    比较常用的方案是嵌套对象,嵌套文档和父子文档。后两种是我们本文要讲的重点。

    我下面聚合分析使用的数据都是kibana自带的,这样方便有些读者实际测试文中的示例。

    ES处理一对多关系的方案

    普通内部对象

    kibana自带的电商数据就是这种方式,我们来看看它的mapping。

    "kibana_sample_data_ecommerce" : {
        "mappings" : {
          "properties" : {
            "category" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword"
                }
              }
            },
            "currency" : {
              "type" : "keyword"
            },
            "customer_full_name" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            //省略部分
           
            "products" : {
              "properties" : {
                "_id" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword",
                      "ignore_above" : 256
                    }
                  }
                },
                "base_price" : {
                  "type" : "half_float"
                },
                "base_unit_price" : {
                  "type" : "half_float"
                },
                "category" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword"
                    }
                  }
                },
                "created_on" : {
                  "type" : "date"
                },
                "discount_amount" : {
                  "type" : "half_float"
                },
                "discount_percentage" : {
                  "type" : "half_float"
                },
                "manufacturer" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword"
                    }
                  }
                },
                "min_price" : {
                  "type" : "half_float"
                },
                "price" : {
                  "type" : "half_float"
                },
                "product_id" : {
                  "type" : "long"
                },
                "product_name" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword"
                    }
                  },
                  "analyzer" : "english"
                },
                "quantity" : {
                  "type" : "integer"
                },
                "sku" : {
                  "type" : "keyword"
                },
                "tax_amount" : {
                  "type" : "half_float"
                },
                "taxful_price" : {
                  "type" : "half_float"
                },
                "taxless_price" : {
                  "type" : "half_float"
                },
                "unit_discount_amount" : {
                  "type" : "half_float"
                }
              }
            },
            "sku" : {
              "type" : "keyword"
            },
            "taxful_total_price" : {
              "type" : "half_float"
            },
            //省略部分
    

    我们可以看到电商的订单索引里面包含了一个products的字段,它是对象类型,内部有自己的字段属性。这其实就是一个包含关系,表示一个订单可以有多个商品信息。我们可以查询下看看结果,

    查询语句,

    POST kibana_sample_data_ecommerce/_search
    {
      "query": {
        "match_all": {}
      }
    }
    

    返回结果(我去掉了一些内容方便观察),

    "hits" : [
          {
            "_index" : "kibana_sample_data_ecommerce",
            "_type" : "_doc",
            "_id" : "VJz1f28BdseAsPClo7bC",
            "_score" : 1.0,
            "_source" : {
              "customer_first_name" : "Eddie",
              "customer_full_name" : "Eddie Underwood",
              "order_date" : "2020-01-27T09:28:48+00:00",
              "order_id" : 584677,
              "products" : [
                {
                  "base_price" : 11.99,
                  "discount_percentage" : 0,
                  "quantity" : 1,
                  "sku" : "ZO0549605496",
                  "manufacturer" : "Elitelligence",
                  "tax_amount" : 0,
                  "product_id" : 6283,
                },
                {
                  "base_price" : 24.99,
                  "discount_percentage" : 0,
                  "quantity" : 1,
                  "sku" : "ZO0299602996",
                  "manufacturer" : "Oceanavigations",
                  "tax_amount" : 0,
                  "product_id" : 19400,
                }
              ],
              "taxful_total_price" : 36.98,
              "taxless_total_price" : 36.98,
              "total_quantity" : 2,
              "total_unique_products" : 2,
              "type" : "order",
              "user" : "eddie",
                "region_name" : "Cairo Governorate",
                "continent_name" : "Africa",
                "city_name" : "Cairo"
              }
            }
          },
    

    可以看到返回的products其实是个list,包含两个对象。这就表示了一个一对多的关系。

    这种方式的优点很明显,由于所有的信息都在一个文档中,查询时就没有必要去ES内部没有必要再去join别的文档,查询效率很高。那么它优缺点吗?

    当然有,我们还用上面的例子,如下的查询,

    GET kibana_sample_data_ecommerce/_search
    {
      "query": {
        "bool": {
          "must": [
            { "match": { "products.base_price": 24.99 }},
            { "match": { "products.sku":"ZO0549605496"}},
            {"match": { "order_id": "584677"}}
          ]
        }
      }
    }
    

    我这里搜索有三个条件,order_id,商品的价格和sku,事实上同时满足这三个条件的文档并不存在(sku=ZO0549605496的商品价格是11.99)。但是结果却返回了一个文档,这是为什么呢?

    原来在ES中对于json对象数组的处理是压扁了处理的,比如上面的例子在ES存储的结构是这样的:

    {
      "order_id":            [ 584677 ],
      "products.base_price":    [ 11.99, 24.99... ],
      "products.sku": [ ZO0549605496, ZO0299602996 ],
      ...
    }
    

    很明显,这样的结构丢失了商品金额和sku的关联关系。

    如果你的业务场景对这个问题不敏感,就可以选择这种方式,因为它足够简单并且效率也比下面两种方案高。

    嵌套文档

    很明显上面对象数组的方案没有处理好内部对象的边界问题,JSON数组对象被ES强行存储成扁平化的键值对列表。为了解决这个问题,ES推出了一种所谓的嵌套文档的方案,官方对这种方案的介绍是这样的:

    The nested type is a specialised version of the object datatype that allows arrays of objects to be indexed in a way that they can be queried independently of each other.

    可以看到嵌套文档的方案其实是对普通内部对象这种方案的补充。上面那个电商的例子mapping太长了,我换个简单一些的例子,只要能说明问题就行了。

    先设置给索引设置一个mapping,

    PUT test_index
    {
      "mappings": {
        "properties": {
          "user": {
            "type": "nested" 
          }
        }
      }
    }
    

    user属性是nested,表示是个内嵌文档。其它的属性这里没有设置,让es自动mapping就可以了。

    插入两条数据,

    PUT test_index/_doc/1
    {
      "group" : "root",
      "user" : [
        {
          "name" : "John",
          "age" :  30
        },
        {
          "name" : "Alice",
          "age" :  28
        }
      ]
    }
    
    PUT test_index/_doc/2
    {
      "group" : "wheel",
      "user" : [
        {
          "name" : "Tom",
          "age" :  33
        },
        {
          "name" : "Jack",
          "age" :  25
        }
      ]
    }
    

    查询的姿势是这样的,

    GET test_index/_search
    {
      "query": {
        "nested": {
          "path": "user",
          "query": {
            "bool": {
              "must": [
                { "match": { "user.name": "Alice" }},
                { "match": { "user.age":  28 }} 
              ]
            }
          }
        }
      }
    }
    

    注意到nested文档查询有特殊的语法,需要指明nested关键字和路径(path),再来看一个更具代表性的例子,查询的条件在主文档和子文档都有。

    GET test_index/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "group": "root"
              }
            },
            {
              "nested": {
                "path": "user",
                "query": {
                  "bool": {
                    "must": [
                      {
                        "match": {
                          "user.name": "Alice"
                        }
                      },
                      {
                        "match": {
                          "user.age": 28
                        }
                      }
                    ]
                  }
                }
              }
            }
          ]
        }
      }
    }
    

    说了这么多,似乎嵌套文档很好用啊。没有前面那个方案对象边界缺失的问题,用起来似乎也不复杂。那么它有缺点吗?当然,我们先来做个试验。

    先看看当前索的文档数量,

    GET _cat/indices?v
    

    查询结果,

    green  open   test_index                   FJsEIFf_QZW4Q4SlZBsqJg   1   1          6            0     17.7kb          8.8kb
    
    

    你可能已经注意到我这里查看文档数量并不是用的

    GET test_index/_count
    

    而是直接查看的索引信息,他们的区别打算后面专门的文章讲解,现在你只需要知道前者可以看到底层真实的文档数量即可。

    是不是很奇怪问啥文档的数量是6而不是2呢?这是因为nested子文档在ES内部其实也是独立的lucene文档,只是我们在查询的时候,ES内部帮我们做了join处理。最终看起来好像是一个独立的文档一样。

    那可想而知同样的条件下,这个性能肯定不如普通内部对象的方案。在实际的业务应用中要根据实际情况决定是否选择这种方案。

    父子文档

    我们还是看上面那个例子,假如我需要更新文档的group属性的值,需要重新索引这个文档。尽管嵌套的user对象我不需要更新,他也随着主文档一起被重新索引了。

    还有就是如果某个表属于跟多个表有一对多的关系,也就是一个子文档可以属于多个主文档的场景,用nested无法实现。

    下面来看示例。

    首先我们定义mapping,如下,

    PUT my_index
    {
      "mappings": {
        "properties": {
          "my_id": {
            "type": "keyword"
          },
          "my_join_field": { 
            "type": "join",
            "relations": {
              "question": "answer" 
            }
          }
        }
      }
    }
    

    my_join_field是给我们的父子文档关系的名字,这个可以自定义。join关键字表示这是一个父子文档关系,接下来relations里面表示question是父,answer是子。

    插入两个父文档,

    PUT my_index/_doc/1
    {
      "my_id": "1",
      "text": "This is a question",
      "my_join_field": {
        "name": "question" 
      }
    }
    
    
    PUT my_index/_doc/2
    {
      "my_id": "2",
      "text": "This is another question",
      "my_join_field": {
        "name": "question"
      }
    }
    

    "name": "question"表示插入的是父文档。

    然后插入两个子文档

    PUT my_index/_doc/3?routing=1
    {
      "my_id": "3",
      "text": "This is an answer",
      "my_join_field": {
        "name": "answer", 
        "parent": "1" 
      }
    }
    
    PUT my_index/_doc/4?routing=1
    {
      "my_id": "4",
      "text": "This is another answer",
      "my_join_field": {
        "name": "answer",
        "parent": "1"
      }
    }
    

    子文档要解释的东西比较多,首先从文档id我们可以判断子文档都是独立的文档(跟nested不一样)。其次routing关键字指明了路由的id是父文档1, 这个id和下面的parent关键字对应的id是一致的。

    需要强调的是,索引子文档的时候,routing是必须的,因为要确保子文档和父文档在同一个分片上。

    name关键字指明了这是一个子文档。

    现在my_index中有四个独立的文档,我们来父子文档在搜索的时候是什么姿势。

    先来一个无条件查询,

    GET my_index/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": ["my_id"]
    }
    

    返回结果(部分),

    {
            "_index" : "my_index",
            "_type" : "_doc",
            "_id" : "3",
            "_score" : null,
            "_routing" : "1",
            "_source" : {
              "my_id" : "3",
              "text" : "This is an answer",
              "my_join_field" : {
                "name" : "answer",
                "parent" : "1"
              }
            },
    

    可以看到返回的结果带了my_join_field关键字指明这是个父文档还是子文档。

    Has Child 查询,返回父文档

    POST my_index/_search
    {
      "query": {
        "has_child": {
          "type": "answer",
          "query" : {
                    "match": {
                        "text" : "answer"
                    }
                }
        }
      }
    }
    

    返回结果(部分),

    "hits" : [
          {
            "_index" : "my_index",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 1.0,
            "_source" : {
              "my_id" : "1",
              "text" : "This is a question",
              "my_join_field" : {
                "name" : "question"
              }
            }
          }
        ]
    

    Has Parent 查询,返回相关的子文档

    POST my_index/_search
    {
      "query": {
        "has_parent": {
          "parent_type": "question",
          "query" : {
                    "match": {
                        "text" : "question"
                    }
                }
        }
      }
    }
    

    结果(部分),

     "hits" : [
          {
            "_index" : "my_index",
            "_type" : "_doc",
            "_id" : "3",
            "_score" : 1.0,
            "_routing" : "1",
            "_source" : {
              "my_id" : "3",
              "text" : "This is an answer",
              "my_join_field" : {
                "name" : "answer",
                "parent" : "1"
              }
            }
          },
          {
            "_index" : "my_index",
            "_type" : "_doc",
            "_id" : "4",
            "_score" : 1.0,
            "_routing" : "1",
            "_source" : {
              "my_id" : "4",
              "text" : "This is another answer",
              "my_join_field" : {
                "name" : "answer",
                "parent" : "1"
              }
            }
          }
        ]
    

    Parent Id 查询子文档

    POST my_index/_search
    {
      "query": {
        "parent_id": { 
          "type": "answer",
          "id": "1"
        }
      }
    }
    

    返回的结果和上面基本一样,区别在于parent id搜索默认使用相关性算分,而Has Parent默认情况下不使用算分。

    使用父子文档的模式有一些需要特别关注的点:

    • 每一个索引只能定义一个 join field
    • 父子文档必须在同一个分片上,意味着查询,更新操作都需要加上routing
    • 可以向一个已经存在的join field上新增关系

    总的来说,嵌套对象通过冗余数据来提高查询性能,适用于读多写少的场景。父子文档类似关系型数据库中的关联关系,适用于写多的场景,减少了文档修改的范围。

    总结

    1. 普通子对象模式实现一对多关系,会损失子对象的边界,子对象的属性之前关联性丧失。
    2. 嵌套对象可以解决普通子对象存在的问题,但是它有两个缺点,一个是更新主文档的时候要全部更新,另外就是不支持子文档从属多个主文档的场景。
    3. 父子文档能解决前面两个存在的问题,但是它适用于写多读少的场景。

    参考:

    *《elasticsearch 官方文档》

    展开全文
  • 带领大家看看AutoSAR标准文档的大致结构和内容

    AutoSAR系列讲解 - AutoSAR标准文档概览(上)

    AutoSAR标准文档概览(上)

    ->返回总目录<-
    自从实践篇完结以后,好久没有更新了,前段时间确实是太忙了,从今天开始我们逐渐恢复更新,大概5月份可以恢复到一周1-2更的正常频率。深入篇的内容博主还在思考怎么写出来,所以大家还请耐心等待,争取今年年底前将我们的AutoSAR系列讲解全部完结。当然,AutoSAR这里面的内容实在是太多了,想要全部详细讲解出来是不现实的,大家还需要通过自己的项目来摸索一套自己的学习流程。

    一、文档下载

    相信看了入门篇和实践篇,大家都对AutoSAR有了自己的认识,更加深入的内容就需要看文档了。但是看过官方文档的朋友应该知道其内容之多,很多人望而却步。说实话,博主也是最近才开始看标准文档的,结合一些其他博主的观看方法,这里博主就带领大家一起看看,帮大家理出一个观看的头绪来,毕竟授人以鱼不如授人以渔嘛。首先是下载地址:

    展开全文
  • HTML5文档结构标签

    2020-12-13 22:32:08
    里面可以包含:①article:表示这一个分块包含了多篇文章。②section:包含子分块。 article:表示的是独立的完整的文章。①header:表示文章的头部。里面包含了标题等。②section:表示不同的段落和分区。③article...
  • 1、MongoDB集合里面文档,相当于关系型数据库表里面的记录 2、MongoDB集合里面文档的插入 db.c2.insert( { id:"1",name:"zhejiang" } ); 3、利用JavaScript脚本,批量插入文档 for( var i=1; i; i++){ db.c3....

    1、MongoDB集合里面文档,相当于关系型数据库表里面的记录

    2、MongoDB集合里面文档的插入

    db.c2.insert( { id:"1",name:"zhejiang"   }  );


    3、利用JavaScript脚本,批量插入文档

    for( var i=1; i<=1000; i++){
       db.c3.insert( { id:i,name:i }  );
    }


    4、统计一个集合里面的文档数量 (类属于统计关系型数据库,表的记录数)

     db.c3.find().count();

    5、跟新文档里面的键值内容(相当于跟新关系型数据库的字段的内容)

          1、mongodb的基本语法                                                                                        2、关系型数据库的语法                                     

           db.collection.update( condition , objNew,upsert,multi)                 update  tableName  set  xxx = objNew   where   condition ;
           参数说明:

          1、condition,用于设置更新的条件
          2、objNew用于设置更新的内容
          3、默认,只更新符合条件的第一条记录

          4、upsert:如果记录已经存在,更新它,否则新增一个记录,取值为0或1 (取数值1,表示更新一个记录)

          5、multi:如果有多个符合条件的记录,是否全部更新,取值为0或1(取数值1更新全部,取数值0更新第一条)

          6、更新语句的一般用法

    db.collection.update(criteria,objNew,0,1); #表示没有这个记录不插入,更新的时候,更新全部文档内容
       

         2、更新集合中的文档,将集合中name为user1的文档改成name为jack------->注意不加$set的更新数据,其他的键值会丢失
           

    db.c3.find(); 
    db.c3.update( { name:"user1" },{ name:"jack" },0,1 );
    db.c3.find();
       

        3、利用$set来更新文档的键值,不会导致其他的键值丢失------>利用$set更新键值,不会导致其他的键值丢失

       

    db.c3.find(); 
    db.c3.update( { name:"user1" },{ $set:{ name:"jack" }},0,1 );
    db.c3.find();



         4、更新一个集合里面文档的键值时,一定要用$set   ( 存在这个键值就更新,不存在就加入到符合条件的文档里面  )  ----->  相当于给表增加新的字段

              解释:更新集合中的文档, $set 用来指定一个键的值,如果这个键不存在,则创建它,如果这个键存在,则更新它的值
           4.1 给name为user1的文档添加address,
                  命令为:

     db.c1.update({name:”user1”},{$set:{address:”bj”}},0,1)


          4.2 将name为user1的文档修改address为tj, 其它键值对不变,
                 命令为:
    db.c1.update({name:”user1”},{$set:{address:”tj”}},0,1)


       5、更新集合中的文档, $unset 用来删除某个键  ------>相当于删除表里面的某个字段

          5.1 例如删除name为user1的文档中的address键,可以使用命令:
                              

    db.c1.update({name:”user1”},{$unset:{address:1}},0,1)
    


    5、更新集合中的文档,$inc表示使某个键值加减指定的数值

           5.1 使用 $inc 将集合中name为user1的age加1,其它键不变
                             

     db.c1.update( {name:"user1"},{$inc:{age:1}} )

          

         5.2 使用 $inc 将集合中name为user1的age减8,其它键不变               

     db.c1.update({name:"user1"},{$inc:{age:-8}})

    展开全文
  • Elasticsearch:如何写入文档时加上 now 时间标签

    千次阅读 热门讨论 2020-08-08 22:35:04
    在文档里,我们没有项 now 这样的东西可以使用来表示当前的时间。那么我们该怎么办呢? 首先我们创建一个这样的 pipeline: PUT _ingest/pipeline/add-timestamp { "processors": [ { "set": { "field": "@...
  • 如何word文档中添加两个目录

    万次阅读 2019-04-16 09:55:08
    由于需要一个word文档中添加两个目录,第一个目录表示文章前半部分的内容,第二个目录表示后半部分的内容,对于word不太熟悉的我经过一番折腾之后终于搞定了,此记录一下。 原理:将word文本划分成两个域,而每...
  • VS2015中,创建一个基于单文档视图的MFC项目,该项目的名称为MFCApplication。通过向导成功创建项目之后发现,自动生成了很多文件。这些文件中,MFCApplicationDoc.cpp和MFCApplictionDoc.h对应的是项目的文档类...
  • 使用GitBook编写文档书籍

    千次阅读 2017-12-01 11:34:44
    GitBook支持输出以下几种文档格式: 静态站点:GitBook默认输出该种格式 PDF:需要安装gitbook-pdf依赖 eBook:需要安装ebook-convert GitBook可以用来写书、API文档、公共文档,企业手册,论文,研究报告等。一、...
  • 需求文档(PRD文档)

    万次阅读 多人点赞 2018-03-05 22:11:21
    信息结构图中关于友情链接功能的信息数据只有“名称”和“链接”两个内容,但是实际功能需求中,友情链接还有两个功能,分别是“显示或隐藏”和“是否新窗口打开”,这两个功能会产品原型和需求文档中详细描述,...
  • 文件手机?需要现拍相片?那就点击: 这时会弹出一个二维码 手机扫描一下,显示下面的界面 点击上传文件,然后选文件还是拍照摄像就随便了 附件上传之后,有时候我们可能只想让人看,不想...
  • 什么是文档? 程序中大多的实体或对象能够被序列化为包含键值对的JSON对象,键(key)是字段(field)或属性(property)的名字,值(value)可以是字符串、数字、布尔类型、另一个对象、值数组或者其他特殊类型,比如表示...
  • pygame中文文档

    千次阅读 2018-03-01 17:42:03
    注:为了适应语境,display 文档中有时翻译为“显示”,有时翻译为“显示界面”。函数pygame.display.init() — 初始化 display 模块pygame.display.quit() — 结束 display 模块pygame.display.get_ini...
  • 使用Typora编写文档的总结

    千次阅读 2019-02-23 12:33:37
    是一款很实用的 Markdown 编辑软件,通过学习 Markdown 的语法以及 Typora 的使用,对 Typora 中编辑文档的方法进行总结。 Basice Use of Typora 菜单 输入 [toc]+换行键,产生标题,并且编写过程中自动...
  • 需求文档(PRD文档)应该怎么写?

    千次阅读 2019-05-13 19:43:10
    信息结构图中关于友情链接功能的信息数据只有“名称”和“链接”两个内容,但是实际功能需求中,友情链接还有两个功能,分别是“显示或隐藏”和“是否新窗口打开”,这两个功能会产品原型和需求文档中详细描述,...
  • 系统设计文档

    千次阅读 2020-05-15 20:59:21
    系统设计文档1 系统需求分析1.1可行性分析1.2项目要解决的问题1.3项目目标2 功能设计2.1功能划分2.2功能描述2.2.1电影信息查询2.2.2用户偏好推荐2.2.3用户登录功能2.3功能设计2.3.1用例图2.3.2 E-R图3 详细设计3.1...
  • 通过朋友的推荐, 了解到了这款在线项目文档维护工具, 简洁没管. 非常好用. 个人也准备今后的项目中使用该工具进行管理, 希望能够让更多的人看到并使用它.
  • 第1步 首先打开一个WORD文件,点击“视图” →"工具栏"-控件工具箱,然后【控件工具箱】的工具栏点击“其他控件”。第2步 然后再点击[Shockwave Flash Object]项目,表示要插入Flash物件。第3
  • Java导出Word文档的实现

    万次阅读 多人点赞 2020-10-18 16:17:13
    日常的开发工作中,我们时常会遇到导出Word文档报表的需求,比如公司的财务报表、医院的患者统计报表、电商平台的销售报表等等。 导出Word方式多种多样,通常有以下几种方式: 1. 使用第三方Java工具类库Hutool...
  • 传统的SQL数据库中,关系被分为一个个表(table),表中,每个数据项以主键(primary key)标识,而一个表的主键又作为另一个表的外键(reference key),两个表之间引用。当遇上多对多关系的时候,还需要一个额外...
  • XML 文档中引用 Schema

    千次阅读 2017-03-10 23:16:56
    最近看java ee相关的东西,配置xml肯定是经常要做的一个事情,而配置xml当然是有规则的。 xml Schema 的作用是定义 xml 文档的合法构建模块,类似 DTD,所以...下面介绍如何 xml 文档中引用 Schema(也就是关于XM
  • WdatePicker文档

    千次阅读 2017-11-17 10:51:32
    一、WdatePicker常用配置属性(可以WdatePicker方法是配置)   属性 类型 默认值 说明 el Element 或 String null 指定一个控件或控件的ID,必须具有value或innerHTML属性(如input,textarea,span,div,p等...
  • mongodb 子文档查询

    千次阅读 2017-07-18 16:24:22
    mongodb 子文档查询 此...首先我的数据库有两个文档,每个文档里都有2个嵌套的数组:mongodb 子文档查询 如果我想查询commentsscore大于5的记录: testProvider.find({"comments.score":{"$gt":5}},{},function
  • 集合里面的蓝色边框是文档(类似于MySql的行) 即:层级顺序是:数据库 –》 集合 –》 文档 下面先来分享一下对数据库中集合里面文档的操作: 插入文档:db.COLLECTION_NAME.insert(document) ...
  • 如何word文档里面的小方框内打钩

    万次阅读 2011-02-28 10:55:00
    方框打勾 :  单元格输入 : R --> 字型选 Wingdings 2 方框打叉  单元格输入 : Q --> 字型选 Wingdings 2 3、模板中新建专业型传真,里面有框,双击后打勾,co
  • [MFC]文档视图体系结构、单文档界面

    千次阅读 2015-09-20 20:25:03
     2) 数据以文档的形式表现,文档可以存储磁盘中永久保存也可以被程序读出并进行处理;  3) 单文档(SDI,Single Document Interface)和多文档(Multiple Document Interface)的区别:单文档界面程序只支持打开...
  • VS报错:“创建空文档失败”

    千次阅读 2019-05-28 18:43:40
    VS2015上的一个项目中,运行时提示创建空文档失败,为了找错大费周章。 首先可以确定报错是创建界面以前就报的错。所以代码的OnCreate等初始化函数的入口打上断点,一步一步调试。受网上的启发,最后...
  • Chrome 开发者工具官方中文文档

    千次阅读 2018-11-07 12:03:25
    传送门 Chrome开发者工具官方中文文档 front-end-study系列
  • 文档编号与文档版本号的作用

    千次阅读 2017-03-31 13:41:11
    当一个阶段下的文档数目超过50个的时候,一个目录查找某个文档是个痛苦的事。常常有文档的名称很长且类似的情况,这导致找到需要的文档很费时。  这里有一个简单的解决办法。为每一个文档分配一个两位的编号...
  • 摘要: Office Open XML格式文件代替了早期的二进制Office系统文件。本文档向您介绍了包含一个格式化文档中的组件以及展示这些文件功能的一些场景 。 Frank Rice,微软公

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 608,613
精华内容 243,445
关键字:

在文档里面怎么表示选择