Nov 222014
 
This entry is part 22 of 25 in the series Matlab数据库基础班

昨天,Postgresql 9.4(RC1)出来了, 正如在前面的一个博文中提到的:Postgresql 9.4发布及操作备忘   这个版本的Postgresql对Json的支持得到很大的加强。

1. 研究的出发点

最近一直利用碎片研究Json的使用。越研究越觉得它的方便之处。当然,如果你的数据结构非常简单,那么普通的关系型数据库就够了,但是一旦数据开始复杂,特别是结构性数据,例如比较接近于OOP(对象编程)的数据类型,那么Json(当然还有XML,不过XML更复杂一些)这样的数据格式可以给应用带来很大的方便。

Json与Matlab中什么对应最好呢?答案就是Matlab的Struct格式的数据。假设现在要维护一个顾客信息数据库,假设我们有如下三个顾客的信息,用Matlab来表述如下面的代码:

client1.name='张三';
client1.id='123';
client1.province='江西';
client1.jingdong={'华硕笔记本',8000,'2014/1/2'};
client2.name='李四';
client2.id='124';
client2.province='湖北';
client2.jingdong={'Dell笔记本',7000,'2014/2/3';'华为手机',2000,'2014/6/1'};
client2.taobao={'无线键鼠',200,'2014/2/4'};
client3.name='王五';
client3.id='125';
client3.province='湖南';
client3.amazon={'ThinkPad笔记本',10000,'2014/5/1'};

每个客户的信息就是一个Struct,需要添加或者修改其中的内容,就非常方便。例如,客户李四在淘宝上又买了一个硬盘,那么只要给client2.taobao后面添加一行数据即可。对于这样的数据,要直接保存到数据库中,会比较麻烦,特别是会将数据库的结构弄得太乱。如果借助Json等数据格式,可以很方便地将这样得异质数据保存进去,还能够利用Json的相关函数对内部的数据做寻址等操作。

2. 在Matlab中:从Struct到Json

要在Matlab中做Json类型数据的操作,需要借助第三方的Toolbox,我找到的、而且很好用的工具叫做: JsonLab(点击下载)。 下载、解压缩到某个目录,然后将其目录加入到Matlab的Search Path中。

工具箱中的loadjson()用于将Json格式的文本转换为Struct, 而savejson则用于将Struct格式的数据转换为Json格式的文本内容。

用如下的这个命令,可以将一个客户的Struct类型数据转换为Json文本格式,结果如下所示:

>> jsonstr=savejson('client',client1)
jsonstr =
{
"client": {
"name": "张三",
"id": "123",
"province": "江西",
"jingdong": [
"华硕笔记本",
[8000],
"2014/1/2"
]
}
}

>> whos jsonstr
Name Size Bytes Class Attributes

jsonstr 1x128 256 char

注意,这里指定了Json的根节点名称为“client”。因为每个Json都需要有一个根节点名称,所以我个人的习惯是用这个变量的类别名称(client),而不是用这个变量的具体名称(client1)来表示。

得到的Jsonstr,它的类型是字符串。

3. 写入数据库:从Matlab写入到Postgresql中

9.4版本支持两种Json数据类型: Json和Jsonb, 区别在于前者是以明文的形式将数据储存在数据库中,后者是以二进制内码的形式储存,前者类型所有的操作,后者都提供支持,而且后者还有很多其他的操作。所以,我直接就只用jsonb类型了。

首先要在Postgresql中新建一个用于测试的数据库,新建的SQL命令可以是:CREATE TABLE jsontest(id serial NOT NULL, clientinfo jsonb);  可以在PgAdmin的命令工具中输入,也可以在Matlab中用exec()函数输入到数据库。

然后可以直接将上面的jsonstr写入到数据库。但是不能直接用fastinsert()函数,因为数据库那边的类型是jsonb。

(说明:下文中你从Matlab中读取时就会发现,如果你也用我这的JDBC驱动,那么jsonb格式返回到Matlab的数据类型是“org.postgresql.util.PGobject”,因此你如果要用fastinsert(),那么需要先将这个jsonstr转换为org.postgresql.util.PGobject,再插入。因为深入了解这样的自定义数据类型的学习成本实在太高,我就不去尝试,而是直接越过这个障碍。)

那么我们可以用SQL语句直接将数据写入到数据库中,命令格式是:  INSERT INTO 数据表名称("列名称") VALUES(直接将文本类型贴入)

>> sql=sprintf('INSERT INTO jsontest("clientinfo") VALUES(''%s'')',jsonstr)
sql =
INSERT INTO jsontest("clientinfo") VALUES('{
"client": {
"name": "张三",
"id": "123",
"province": "江西",
"jingdong": [
"华硕笔记本",
[8000],
"2014/1/2"
]
}
}
')
>> cur=exec(wdb,sql);

从pgAdmin中打开这个数据库直接看内部数据,就可以看到这条信息了。

为了后面的测试,我们对剩下两条客户信息数据如法炮制:

>> jsonstr=savejson('client',client2);
>> sql=sprintf('INSERT INTO jsontest("clientinfo") VALUES(''%s'')',jsonstr);
>> cur=exec(wdb,sql);
>> jsonstr=savejson('client',client3);
>> sql=sprintf('INSERT INTO jsontest("clientinfo") VALUES(''%s'')',jsonstr);
>> cur=exec(wdb,sql);

此时,数据表中应该有三条记录了。

4. 从Postgresql读取数据到Matlab,并转换为Struct

先不管寻址的问题,我们先从其中获取任意一个客户数据,

>> sql='select clientinfo from jsontest limit 1';
>> tmpclient=fetch(wdb,sql)
tmpclient =
[1x1 org.postgresql.util.PGobject]

可见返回的是"org.postgresql.util.PGobject" 特殊类型。当然,我相信这个类型有对应的操作功能,但是学习成本太高,依旧不去探究,直接用char()函数将其转换为文本格式:

>> tmpstr=char(tmpclient{1})
tmpstr =
{"client": {"id": "123", "name": "张三", "jingdong": ["华硕笔记本", [8000], "2014/1/2"], "province": "江西"}}

然后用 loadjson() 函数将其转化回 struct 格式即可。

>> tmp=loadjson(tmpstr)
tmp =
client: [1x1 struct]

注意一点,这里转换回来,会将client本身作为struct中的一个域,我们一般用不上,那么可以将其往外面提取一层:

>> tmpclient=tmp.client
tmpclient =
id: '123'
name: '张三'
jingdong: {'华硕笔记本' [8000] '2014/1/2'}
province: '江西'

这就是我们之前所储存的client1数据,对比:

>> client1
client1 =
name: '张三'
id: '123'
province: '江西'
jingdong: {'华硕笔记本' [8000] '2014/1/2'}

发现二者唯一差异是struct的域次序发生变化,但这个没有关系,因为它本来就没有先后次序的问题。什么时候次序变化的呢?是在保存为jsonb数据时,就对域次序做了调整。

5. Jsonb的寻址(初步)

如果仅仅是简单储存数据,那么根本没有特别的优势,优势在于,我们从数据库中直接就可以对数据作寻址和筛选。如下都是SQL语句,可以在pgAdmin的命令行窗口中直接运行,当然也可以在Matlab中用exec()函数运行。此处例子中,为了简单起见,仅仅是pgAdmin中运行。

例子1. 已知名字找到记录,例如找出张三的记录

SELECT * FROM jsontest WHERE clientinfo->'client' @> '{"name":"张三"}';

格式上面, clientinfo是字段名, 然后 ->'client' 表示这个字段里面的那个根名称为 client 的数据树, @> 表示寻找对应数据树下面存在此数据对, name=张三 的结果

例子2. 提取所有客户的名称

SELECT clientinfo->'client'->'name' FROM jsontest;

用-> 来表示层次, 这里的客户名称是在 clientinfo(数据字段) 下面的 client(根名称), 下面的 name(对应的一级名称)

例子3. 提取张三在京东的交易记录

SELECT clientinfo->'client'->'jingdong' FROM jsontest WHERE clientinfo->'client' @> '{"name":"张三"}';

如果要直接提取消费金额?那么就进一步寻址即可:

SELECT clientinfo->'client'->'jingdong'->1 FROM jsontest WHERE clientinfo->'client' @> '{"name":"张三"}';

注意,数字寻址是从0开始的,这里消费金额是第二个数据,那么寻址的就是1

当然,如果要搜索一个不存在的数据,那么会返回空:

SELECT clientinfo->'client'->'taobao' FROM jsontest WHERE clientinfo->'client' @> '{"name":"张三"}';

当然,到这里还远没有完,在未来应用过程中,会遇到各种新的需求,合适的时候,将补充新的内容。

 

最后总结:我倒不纠结 Postgresql 和MongoDB等非关系型数据库在处理Json等数据类型时的优劣,因为对于我的使用而言,学习成本、已有平台的重复使用相比运行效率更为重要。

No related posts.

Series Navigation<< Postgresql-导入XMLMatlab连接Access数据库文件的JDBC方法2-Caigen Driver >>
Bookmark/FavoritesSina WeiboGoogle+FacebookQQTwitterYahoo BookmarksBaiduDiggEmailGoogle GmailOutlook.comEvernotePrintAIMLinkedInBlogger PostKindle ItShare

Related Posts:

 Leave a Reply

(required)

(required)


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>