开发者社区> 异雀> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

TableStore多元索引复合类型的使用诀窍:array还是nested

简介: 对主表中需要查询的列(Column)建立多元索引,即可通过该列的值查询数据。索引列分为基本类型和复合类型,本文详细介绍两种复合类型——数组类型(array)和嵌套类型(nested)的使用。
+关注继续查看

表格存储是阿里云提供的一个分布式存储系统,可以用来存储海量结构化、半结构化的数据。多元索引(SearchIndex)可以支持基于属性列的丰富查询类型,帮你挖掘出数据的更多潜能。
对主表中需要查询的列(Column)建立多元索引,即可通过该列的值查询数据。索引列可以分为以下几种类型:

  • 基本类型:不可再分的字段类型,包括long, double, boolean, keyword, text, geo_point
  • 复合类型:由基本类型构成,包括数组类型(array)和嵌套类型(nested)。


本文将结合具体使用场景说明两种复合类型的使用——

  • 数组类型(array):数组元素类型必须一致,只能是基本类型,元素个数不固定;
  • 嵌套类型(nested):nested类型本质上也是数组,数组元素内部可以嵌套基本类型,查询时保证各元素的独立性。

数组类型(array)

数组类型是一种复合的索引字段类型。如同C++/Java中的数组,一个数组类型的索引字段由0个或多个相同基本类型组成,只需要在定义基本类型字段时指定IsArray: true即可。常见的使用场景如,为“用户信息表”创建多元索引,为每个用户创建一个“用户标签(tags)”索引列——类型为TEXT类型数组。当好友为该用户打标签时,会作为一个数组元素被追加到tags列中,数量从0个到多个不等,如下所示。

fieldAuthors := &tablestore.FieldSchema{
    FieldName:        proto.String("tags"),                //字段名
    FieldType:        tablestore.FieldType_TEXT,    //定义数组类型的每个元素为TEXT类型
    Index:            proto.Bool(true),               //开启索引
    IsArray:          proto.Bool(true),                        //定义字段类型为数组
}

使用限制

  • 每个数组元素都必须是相同类型。如果无法保证数组元素类型相同,可以考虑使用nested。
  • array类型字段的取值只能以“json字符串”的列表,才能被同步到array类型索引。

形如["Thomas Cormen", "Charles Leiserson"],["Thomas Cormen"],或[]。地理类型的数组类型如["41.12,-71.34", "41.13,71.34"]。

  • 数组元素只能是基本类型。

嵌套类型(nested)

nested类型是嵌套文档类型,指一个文档(一行数据)可以包含多个子文档,多个子文档保存在一个nested类型的列中。对于一个nested类型的列,需要指定其子文档的结构,即指定子文档中需包含哪些字段,以及每个字段的属性。在多元索引底层实现中,文档和子文档是平等独立的。
nested类型本质上也是数组。array类型的数组元素只能是基本类型,有时无法准确描述业务,或者不支持把数组元素视为独立对象进行复杂查询,这时您可以考虑使用nested类型。例如,为“用户信息表”创建多元索引,每个用户都有一个好友列表,列表中每个好友的信息包括名字、性别、年龄等,用一个基本类型的字段无法准确描述。于是我们定义"friends"这个NESTED类型字段——嵌套TEXT类型的name字段(表示好友姓名)和LONG类型的gender字段(表示好友性别)。

fieldFriends := &tablestore.FieldSchema{
    FieldName:        proto.String("friends"),          //好友列表
    FieldType:        tablestore.FieldType_NESTED,       //字段类型: NESTED类型
    FieldSchemas:     [] *tablestore.FieldSchema {
        {
            FieldName:    proto.String("name"),        //内部字段名: 好友姓名
            FieldType:    tablestore.FieldType_TEXT,    //内部字段类型: TEXT类型
        },
        {
            FieldName:    proto.String("gender"),    //内部字段名: 好友性别
            FieldType:    tablestore.FieldType_LONG,    //内部字段类型: LONG类型, 0(男), 1(女), 2(其他)
        },
    },
}


嵌套类型不能直接查询,需要通过NestedQuery包装一下,即可对嵌套字段进行复杂组合查询。NestedQuery中需要指定嵌套类型的字段路径(Path)以及一个子Query(可以是任意Query)。如下面查询条件,将匹配“好友列表中有30岁以下女性好友”的那些用户。

query := &search.NestedQuery{                //nested查询
    Path:        "friends",
    Query:        &search.BoolQuery{
        MustQueries: []search.Query {
            &search.TermQuery{
                FieldName:    "friends.gender",    //查询字段friends.gender(nested内部字段)
                Term:         1,                //查询字段取值: 1 (女)
            },
            &search.RangeQuery{
                FieldName:    "friends.age",    //查询字段friends.age(nested内部字段)
                To:           30,                //年龄<30岁
                IncludeUpper: false,            //不包括上限
            },
        },
    },
    ScoreMode:    search.ScoreMode_Avg,
}

使用限制

  • nested类型字段的取值只能以“json字符串”的列表,才能被同步到array类型索引。形如[{"name": "王二", "gender": 0, "age": 30}, {"name": "小谭", "gender": 1, "age": 25}]。
  • NESTED不允许嵌套,即nested字段的内部字段只能是基本类型。
  • 最多100个NESTED字段。

代码示例

用户信息表及其多元索引,定义了array类型字段"tags",以及nested类型字段"friends",并演示如何进行NestedQuery匹配“好友列表中有30岁以下女性好友”的那些用户。

func putUser(client *tablestore.TableStoreClient, tableName string, uid string, name string, desc string, tags string, friends string) {
    putRowRequest := new(tablestore.PutRowRequest)
    putRowChange := new(tablestore.PutRowChange)
    putRowChange.TableName = tableName                            //设置表名
    putPk := new(tablestore.PrimaryKey)
    putPk.AddPrimaryKeyColumn("uid", uid)        //设置主键

    putRowChange.PrimaryKey = putPk
    putRowChange.AddColumn("name", name)            //用户姓名
    putRowChange.AddColumn("desc", desc)            //用户描述
    putRowChange.AddColumn("tags", tags)            //用户标签
    putRowChange.AddColumn("friends", friends)        //用户描述
    putRowChange.SetCondition(tablestore.RowExistenceExpectation_EXPECT_NOT_EXIST)
    putRowRequest.PutRowChange = putRowChange
    _, err := client.PutRow(putRowRequest)
    if err != nil {
        fmt.Println("PutRow failed with error:", err)
    } else {
        fmt.Println("PutRow ok")
    }
}

func ArrayDemo(client *tablestore.TableStoreClient, tableName string, indexName string) {
    //1. 创建表
    createtableRequest := new(tablestore.CreateTableRequest)

    tableMeta := new(tablestore.TableMeta)
    tableMeta.TableName = tableName                //设置表名
    tableMeta.AddPrimaryKeyColumn("uid", tablestore.PrimaryKeyType_STRING)    //主键列
    tableOption := new(tablestore.TableOption)
    tableOption.TimeToAlive = -1                //TTL无限
    tableOption.MaxVersion = 1                    //每列最多1个数据版本
    reservedThroughput := new(tablestore.ReservedThroughput)
    reservedThroughput.Readcap = 0
    reservedThroughput.Writecap = 0
    createtableRequest.TableMeta = tableMeta
    createtableRequest.TableOption = tableOption
    createtableRequest.ReservedThroughput = reservedThroughput  //设置预留读写吞吐量

    _, err := client.CreateTable(createtableRequest)            //创建主表
    if err != nil {
        fmt.Println("Failed to create table with error:", err)
        return
    } else {
        fmt.Println("Create table finished")
    }

    //2. 创建多元索引
    request := &tablestore.CreateSearchIndexRequest{}
    request.TableName = tableName                            //设置表名
    request.IndexName = indexName                            //设置索引名

    schemas := []*tablestore.FieldSchema{}                    //索引字段列表(根据业务,您还可以添加更多字段)
    fieldTitle := &tablestore.FieldSchema{
        FieldName:        proto.String("name"),            //用户名
        FieldType:        tablestore.FieldType_TEXT,        //字段类型: TEXT类型
        Index:            proto.Bool(true),               //开启索引
    }
    fieldTime := &tablestore.FieldSchema{
        FieldName:        proto.String("desc"),            //用户描述
        FieldType:        tablestore.FieldType_TEXT,           //字段类型: TEXT类型
        Index:            proto.Bool(true),               //开启索引
    }
    fieldTags := &tablestore.FieldSchema{
        FieldName:        proto.String("tags"),          //个人标签
        FieldType:        tablestore.FieldType_TEXT,           //字段类型: TEXT类型
        Index:            proto.Bool(true),               //开启索引
        IsArray:          proto.Bool(true),                //是数组
    }
    fieldFriends := &tablestore.FieldSchema{
        FieldName:        proto.String("friends"),          //好友列表
        FieldType:        tablestore.FieldType_NESTED,       //字段类型: NESTED类型
        FieldSchemas:     [] *tablestore.FieldSchema {
            {
                FieldName:    proto.String("name"),        //内部字段名: 好友姓名
                FieldType:    tablestore.FieldType_TEXT,    //内部字段类型: TEXT类型
            },
            {
                FieldName:    proto.String("gender"),    //内部字段名: 好友性别
                FieldType:    tablestore.FieldType_LONG,    //内部字段类型: LONG类型, 0(男), 1(女), 2(其他)
            },
            {
                FieldName:    proto.String("age"),        //内部字段名: 好友年龄
                FieldType:    tablestore.FieldType_LONG,    //内部字段类型: LONG类型
            },
        },
    }
    schemas = append(schemas, fieldTitle, fieldTime, fieldTags, fieldFriends)

    request.IndexSchema = &tablestore.IndexSchema{
        FieldSchemas: schemas,                                //设置索引字段
    }

    _, err = client.CreateSearchIndex(request)                //创建多元索引
    if err != nil {
        fmt.Println("Failed to create index with error:", err)
        return
    } else {
        fmt.Println("Create index finished")
    }
    time.Sleep(time.Duration(20) * time.Second)               //等待数据表加载

    //3. 插入一些测试数据
    putUser(client, tableName, "0001", "Thomas Corment", "<Introduction to Algorithms>作者", "[\"技术牛人\", scientist]", "[{\"name\": \"老周\", \"gender\": 0}]")
    putUser(client, tableName, "0002", "小明", "资深研发", "[\"资深研发\", \"聪明可爱\"]",
        "[{\"name\": \"王二\", \"gender\": 0, \"age\": 30}, {\"name\": \"小谭\", \"gender\": 1, \"age\": 25}]")

    time.Sleep(time.Duration(20) * time.Second)       //等待数据同步到多元索引(通常,稳定后延迟在1s~10s级别)

    //4. 查询
    searchRequest := &tablestore.SearchRequest{}
    searchRequest.SetTableName(tableName)       //设置主表名
    searchRequest.SetIndexName(indexName)       //设置多元索引名

    query := &search.NestedQuery{                //nested查询
        Path:        "friends",
        Query:        &search.BoolQuery{
            MustQueries: []search.Query {
                &search.TermQuery{
                    FieldName:    "friends.gender",    //查询字段friends.gender(nested内部字段)
                    Term:         1,                //查询字段取值: 1 (女)
                },
                &search.RangeQuery{
                    FieldName:    "friends.age",    //查询字段friends.age(nested内部字段)
                    To:           30,                //年龄<30岁
                    IncludeUpper: false,            //不包括上限
                },
            },
        },
        ScoreMode:    search.ScoreMode_Avg,
    }

    searchQuery := search.NewSearchQuery()
    searchQuery.SetQuery(query)
    searchRequest.SetColumnsToGet(
        &tablestore.ColumnsToGet{
            ReturnAll:false,
            Columns: []string{"name", "friends"},
        })
    searchRequest.SetSearchQuery(searchQuery)

    searchResponse, err := client.Search(searchRequest)   //查询
    if err != nil {
        fmt.Println("Failed to search with error:", err)
        return
    }
    fmt.Println("IsAllSuccess: ", searchResponse.IsAllSuccess)  //查看返回结果是否完整
    fmt.Println("TotalCount: ", searchResponse.TotalCount)    //返回所有匹配到的行数
    fmt.Println("RowCount: ", len(searchResponse.Rows))         //返回的行数
    for _, row := range searchResponse.Rows {           //打印本次返回的行
        jsonBody, err := json.Marshal(row)
        if err != nil {
            panic(err)
        }
        fmt.Println("Row: ", string(jsonBody))
    }
}

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Tablestore:多元索引的统计聚合
# 前言 信息爆炸的浪潮下,单应用的数据量呈指数级增长,对海量数据进行实时分析的场景日趋广泛。从管理大量设备的监控指标,到勾勒目标用户画像,从突发新闻的舆情监控,到可视化呈现业务规律以供BI决策,都对“实时”、“快速”地分析海量数据提出更高的要求。
563 0
只需一步,DLA开启TableStore多元索引查询加速!
Data Lake Analytics(简称DLA)在构建第一天就是支持直接关联分析Table Store(简称OTS)里的数据,实现存储计算分离架构,满足用户基于SQL接口分析Table Store数据需求。
909 0
只需一步,DLA开启TableStore多元索引查询加速!
一、背景介绍 Data Lake Analytics(简称DLA)在构建第一天就是支持直接关联分析Table Store(简称OTS)里的数据,实现存储计算分离架构,满足用户基于SQL接口分析Table Store数据需求。
2 0
TableStore多元索路由探微
多元索引会分布式地将数据打散存储在不同机器上。通过指定路由,您可以有的放矢地定向搜索,在指定的一个数据分区上执行查询,而不是所有数据分区,有效提升了查询吞吐量,减少长尾对延迟的影响。本文抽丝剥茧,介绍分布式模型中路由的原理,如何使用路由来加速查询。
1059 0
基于Tablestore多元索引打造亿量级店铺搜索系统
如何使用TableStore打造店铺搜索系统
6056 0
TableStore发布多元索引功能,打造统一的在线数据平台
TableStore发布多元索引功能,提供多字段ad-hoc查询、模糊查询、全文检索、排序、范围查询、嵌套查询、空间查询等功能,打造统一的在线数据平台
6297 0
表格存储数据多版本介绍| 学习笔记
快速学习表格存储数据多版本介绍。
0 0
基于TableStore的海量气象格点数据解决方案实战 王怀远
基于TableStore的海量气象格点数据解决方案实战 王怀远
0 0
基于Tablestore 实现大规模订单系统海量订单/日志数据分类存储的实践
前言:从最早的互联网高速发展、到移动互联网的爆发式增长,再到今天的产业互联网、物联网的快速崛起,各种各样新应用、新系统产生了众多订单类型的需求,比如电商购物订单、银行流水、运营商话费账单、外卖订单、设备信息等,产生的数据种类和数据量越来越多;其中订单系统就是一个非常广泛、通用的系统。而随着数据规模的快速增长、大数据技术的发展、运营水平的不断提高,包括数据消费的能力要求越来越高,这对支撑订单系统的数据库设计、存储系统也提出了更多的要求。在新的需求下,传统的经典架构面临着诸多挑战,需要进一步思考架构优化,以更好支撑业务发展;
0 0
使用 Data Lake Formation(DLF) 进行 Tablestore 数据实时入湖
本文介绍使用 Data Lake Formation (DLF)服务,实时订阅 Tablestore(原 OTS) 的数据,并以 Delta Lake 的格式投递进入 OSS,构建实时数据湖。 ## 架构介绍 表格存储是一种全托管的云原生数据库,使用表格存储您无需担心软硬件预置、配置、故障、集群扩展、安全等问题。提供高服务可用性的同时极大地减少了管理成本。 表格存储支持多种数据库模型
0 0
+关注
异雀
樽前做剧莫相笑,我死诸君思我狂
文章
问答
来源圈子
更多
阿里云存储基于飞天盘古2.0分布式存储系统,产品包括对象存储OSS、块存储Block Storage、共享文件存储NAS、表格存储、日志存储与分析、归档存储及混合云存储等,充分满足用户数据存储和迁移上云需求,连续三年跻身全球云存储魔力象限四强。
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
玩转 Tablestore 入门与实战
立即下载
TableStore在社交类场景下的应用
立即下载
一站式结构化数据存储Tablestore实战手册
立即下载