一篇文章我給大家說(shuō)明了如何從零開(kāi)始搭建一個(gè)node的服務(wù)端框架,我們用到了Egg框架。Egg框架我不再過(guò)多介紹,如果有小伙伴想了解,可以回去看我以前寫(xiě)的文章,會(huì)有相關(guān)的介紹。這次我將在上次搭建的框架上進(jìn)行延伸,講一下如果用Egg框架連接數(shù)據(jù)庫(kù),并且實(shí)現(xiàn)對(duì)數(shù)據(jù)的增刪查改。接下來(lái)我們直接進(jìn)入主題。
我本次選用的數(shù)據(jù)庫(kù)是MySQL。所以我們安裝Egg官方的數(shù)據(jù)庫(kù)插件即可,首先我們安裝插件 egg-mysql 。我們?cè)陧?xiàng)目根目錄打開(kāi)命令提示符,輸入命令行:npm i --save egg-mysql 。回車(chē)等待插件下載安裝完成。
npm i --save egg-mysql
命令行下載安裝插件完成后,我們下一步的工作就是在項(xiàng)目中開(kāi)啟并配置egg-mysql插件。具體操作如下:
首先我們要在項(xiàng)目中開(kāi)啟數(shù)據(jù)庫(kù)。找到項(xiàng)目中的/config/plugin.js文件我們需要在里面添加幾行代碼,如下所示。
//開(kāi)啟數(shù)據(jù)庫(kù)插件
mysql : {
enable: true,
package: 'egg-mysql',
}
然后我們還要在 config/config.default.js 中配置各個(gè)環(huán)境的數(shù)據(jù)庫(kù)連接信息。具體配置如下。
//添加數(shù)據(jù)庫(kù)連接信息
config.mysql = {
// 單數(shù)據(jù)庫(kù)信息配置
client: {
// host
host: 'localhost',
// 端口號(hào)
port: '3306',
// 用戶(hù)名
user: 'root',
// 密碼
password: '123456',
// 數(shù)據(jù)庫(kù)名
database: 'testdb',
},
// 是否加載到 app 上,默認(rèn)開(kāi)啟
app: true,
// 是否加載到 agent 上,默認(rèn)關(guān)閉
agent: false,
};
到此步驟我們的數(shù)據(jù)庫(kù)插件已經(jīng)安裝完成并且配置好了。那我們?cè)趺磳?shí)現(xiàn)數(shù)據(jù)的增刪查改呢?大家請(qǐng)繼續(xù)往下看。
首先我們看一下怎么新增數(shù)據(jù)。我們?cè)趍ysql的testdb實(shí)例中新建一個(gè)user空表。如下圖所示。
我們的egg框架也遵循MVC的架構(gòu)所以我們一般會(huì)在service層里面寫(xiě)我們邏輯處理的代碼,而controller層則是獲取前端數(shù)據(jù),回傳數(shù)據(jù)的控制層。所以我們操作數(shù)據(jù)庫(kù)的代碼是寫(xiě)在service文件夾里面的。
我們?cè)赼pp/service文件夾里面新建一個(gè)user.js文件。在里面寫(xiě)個(gè)新增用戶(hù)的方法,該方法就是把數(shù)據(jù)存到數(shù)據(jù)庫(kù)中。具體代碼如下。
const Service = require('egg').Service;
class UserService extends Service {
//新增用戶(hù)data是有controller層傳遞過(guò)來(lái)的數(shù)據(jù)記錄。
async addUser(data) {
const {ctx, app} = this;
let result = {};
try {
data.id = 0;//定義id=0,因?yàn)閿?shù)據(jù)庫(kù)已經(jīng)設(shè)置id為主鍵,并且自增。所以只需要賦值0即可。
// 在 user 表中,插入前端提交上來(lái)的數(shù)據(jù)記錄
const info = await app.mysql.insert('user', data);
//插入成功后。
if(info.affectedRows === 1){
//給前端返回一個(gè)Json的對(duì)象
result = {
state: 0, //自定義的狀態(tài)碼
msg: "添加成功", //返回的消息
data: info.insertId, //新增的記錄的id
}
}
} catch (err) {
//插入數(shù)據(jù)失敗的返回結(jié)果
result = {
state: 1,
msg: err,
data: null,
}
}
return result
}
};
module.exports = UserService;
然后我們?cè)赼pp/controller文件夾里新建一個(gè)user.js文件。在這里我們需要獲取前端提交上來(lái)的數(shù)據(jù),并且將數(shù)據(jù)處理的結(jié)果返回給前端。具體代碼如下。
'use strict';
const Controller = require('egg').Controller;
/**
* @Controller 用戶(hù)管理
*/
class UserController extends Controller {
/**
* @summary 新增用戶(hù)
* @router post /user/add
* @request body userAddRequest
* @response 200
*/
async addUser() {
const { ctx } = this;
//通過(guò)ctx.request.body的方式,可以獲取到前端post方式提交上來(lái)的數(shù)據(jù)
const data = ctx.request.body;
//調(diào)用service層的addUser方法。并且返回相應(yīng)的結(jié)果
const userInfo = await ctx.service.user.addUser(data);
//向前端接口響應(yīng)數(shù)據(jù)。
ctx.body = userInfo;
}
}
module.exports = UserController;
最后我們定義一個(gè)路由,讓前端請(qǐng)求訪(fǎng)問(wèn)此路由。框架會(huì)監(jiān)聽(tīng)路由是否被訪(fǎng)問(wèn),如果被訪(fǎng)問(wèn)了則會(huì)調(diào)用我們定義在controller層的新增用戶(hù)的方法。我們?cè)赼pp/router.js文件中添加如下代碼,即可完成路由的定義。
//新增用戶(hù)路由
router.post('/user/add', controller.user.addUser);
完成這步驟后,我們一個(gè)新增用戶(hù)的功能就已經(jīng)完成了。接下里我們就測(cè)試一下它的實(shí)際效果。我們運(yùn)行命令:npm run dev。啟動(dòng)項(xiàng)目,然后打開(kāi)網(wǎng)頁(yè)http://127.0.0.1:7001,可以直接在swagger-ui.html頁(yè)面中進(jìn)行測(cè)試。結(jié)果如下圖所示。
經(jīng)過(guò)測(cè)試,數(shù)據(jù)已經(jīng)添加完成。所以數(shù)據(jù)庫(kù)連接也是正常的。
本次分享暫時(shí)先告一段落。請(qǐng)各位小伙伴抬起你們發(fā)財(cái)?shù)男∈郑c(diǎn)個(gè)贊唄。下次我將會(huì)進(jìn)行和大家分享對(duì)數(shù)據(jù)查改刪的方法。關(guān)注我!!!更多精彩分享不迷路。
日客戶(hù)要求表內(nèi)的數(shù)據(jù)依據(jù)某種分組生成HTML頁(yè)面進(jìn)行展示,一般處理這種需求直接上編程工具就好了,從數(shù)據(jù)庫(kù)里讀取數(shù)據(jù),根據(jù)規(guī)則生成字符串,最后將字符串寫(xiě)出到文件。由于需求比較急,作為數(shù)據(jù)庫(kù)編程系列文章,如果能用SQL實(shí)現(xiàn)首選還是SQL,這樣處理既直接又快速,不過(guò)針對(duì)SQL要真的有耐心和信心寫(xiě)完,調(diào)試更是崩潰。由于要寫(xiě)出文件到硬盤(pán),最后還是選擇MySQL作為數(shù)據(jù)庫(kù)工具,Navicat作為開(kāi)發(fā)工具。
有兩張表計(jì)劃表、市縣表,二者依靠市縣編碼(sxbm)進(jìn)行等值連接,計(jì)劃表內(nèi)含有各個(gè)學(xué)校投放在各個(gè)市縣的專(zhuān)業(yè)代號(hào)(zydh),專(zhuān)業(yè)名稱(chēng)(zymc)、招生備注(bz)、學(xué)制(xz)、要求的學(xué)歷(xl)、計(jì)劃數(shù)(jh)等字段組成的計(jì)劃信息,院校編碼(yxbm)為學(xué)校的兩位數(shù)編碼,院校代號(hào)(yxdh)為院校編碼(yxbm)+市縣編碼(sxbm)組成的四位數(shù)編碼,院校代號(hào)其實(shí)可以區(qū)分出學(xué)校在哪個(gè)市縣的投檔的專(zhuān)業(yè)計(jì)劃。要求以學(xué)校為單位創(chuàng)建HTML頁(yè)面,頁(yè)面首先要以市縣作為表格分割,然后根據(jù)專(zhuān)業(yè)代號(hào)排序。具體實(shí)現(xiàn)過(guò)程如下:
CREATE TABLE `zzjh2019v` ( `YXDH` varchar(9) COMMENT '學(xué)校代號(hào)', `YXMC` varchar(54) COMMENT '學(xué)校名稱(chēng)', `ZYDH` varchar(2) COMMENT '專(zhuān)業(yè)代號(hào)', `ZYMC` varchar(28) COMMENT '專(zhuān)業(yè)名稱(chēng)', `XZ` varchar(3) COMMENT '學(xué)制', `XL` varchar(4) COMMENT '學(xué)歷', `JH` varchar(6) COMMENT '招生計(jì)劃數(shù)', `BZ` varchar(200) COMMENT '備注', `yxbm` char(2) COMMENT '學(xué)校編碼', `sxbm` char(2) COMMENT '市縣編碼' ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;
CREATE TABLE `sx` ( `sxbm` char(2) COMMENT '市縣編碼', `sxmc` varchar(20) COMMENT '市縣名稱(chēng)' ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;
糾結(jié)了很久這個(gè)東西怎么寫(xiě),最后采取游標(biāo)、拼接字符串、字符串聚合,動(dòng)態(tài)SQL,寫(xiě)文件等一些列操作完成需求,創(chuàng)建的存儲(chǔ)過(guò)程如下:
CREATE DEFINER=`root`@`localhost` PROCEDURE `splitjh`() BEGIN declare done INT DEFAULT 0; declare pyxbm char(2); declare psxmc varchar(10); declare pyxmc varchar(50); declare pjhall int; declare pjhrows TEXT; declare yxjh cursor for select yxbm,yxmc,sum(jh) jhall from zzjh2019v a,sx b where a.sxbm=b.sxbm group by yxbm,yxmc order by yxbm; declare CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; open yxjh; fetch yxjh into pyxbm,pyxmc,pjhall; while done !=1 do select group_concat(jhrow separator '') into pjhrows from (select concat('<tr class="subtitle"><td>',yxdh,'</td><td>',yxmc,'在 <span><font color="red">',b.sxmc,'</font></span> 招生計(jì)劃如下</td><td>',sum(jh),'</td><td></td><td></td></tr>',group_concat('<tr class="jhrow"><td>',zydh,'</td><td>',zymc,'(',bz,')</td><td>',jh,'</td><td>',xz,'</td><td>',xl,'</td></tr>' order by zydh separator '')) jhrow from zzjh2019v a,sx b where yxbm=pyxbm and a.sxbm=b.sxbm group by yxdh order by yxdh,zydh) jhs; set @pfilename = concat('''d:/32/1/1/jh11',pyxbm,'.html'''); set @sql =concat('select concat(''<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" type="text/css" href="zsjh.css" ><title>3+2計(jì)劃</title></head><body><h3></h3><table><tr class="subtitle"><th>代號(hào)</th><th>專(zhuān)業(yè)及名稱(chēng)備注</th><th>人數(shù)</th><th>學(xué)制</th><th>學(xué)歷</th></tr>'',''',pjhrows,''',''</body></html>'') from dual into outfile ',@pfilename); prepare execsql from @sql; execute execsql; DEALLOCATE PREPARE execsql; fetch yxjh into pyxbm,pyxmc,pjhall; end while; close yxjh; END;
首先看效果,執(zhí)行過(guò)程
call splitjh();
在磁盤(pán)形成的HTML文件效果如下圖(數(shù)據(jù)有一定的敏感性,進(jìn)行了遮擋處理):
文件展示頁(yè)面
生成的文件列表如下圖:
生成的文件列表
這里一共有87所學(xué)校,所以生成了87的文件,添加CSS樣式文件,讓表格呈現(xiàn)如前圖所示。
技術(shù)點(diǎn)
1)MySQL的游標(biāo),以及循環(huán)讀取游標(biāo)的方法,涉及的語(yǔ)句如下:
declare yxjh cursor for select yxbm,yxmc,sum(jh) jhall from zzjh2019v a,sx b where a.sxbm=b.sxbm group by yxbm,yxmc order by yxbm;#游標(biāo)定義 declare CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;#游標(biāo)循環(huán)條件,注意此句一定要定義在游標(biāo)之后,才起作用 open yxjh;#打開(kāi)游標(biāo) fetch yxjh into pyxbm,pyxmc,pjhall;#將游標(biāo)行內(nèi)容賦值給變量。
2)執(zhí)行動(dòng)態(tài)SQL,由于MySQL into outfile 后接的文件名不能為變量,所以必須使用動(dòng)態(tài)SQL的方法,涉及的語(yǔ)句如下:
prepare execsql from @sql;#從一個(gè)變量準(zhǔn)備一個(gè)動(dòng)態(tài)sql,注意execsql不用提前定義 execute execsql;#執(zhí)行準(zhǔn)備好的語(yǔ)句 DEALLOCATE PREPARE execsql;#銷(xiāo)毀語(yǔ)句
綜上就是使用MySQL數(shù)據(jù)庫(kù),并借用MySQL寫(xiě)文件的方式將數(shù)據(jù)從數(shù)據(jù)庫(kù)內(nèi)按照需求導(dǎo)出文件,為何不用navicat導(dǎo)出呢?因?yàn)闊o(wú)法達(dá)到要求,又是聚合、又是格式,所以只能自己編寫(xiě)過(guò)程通過(guò)SQL語(yǔ)句拼接字符串的方式來(lái)實(shí)現(xiàn)。沒(méi)有太多的技術(shù)難度,主要是想法和調(diào)試難度。后續(xù)在此基礎(chǔ)上又開(kāi)發(fā)了以市縣為單位創(chuàng)建HTML文件,各招生學(xué)校作為分割的過(guò)程。本案例是實(shí)際需求催生出來(lái)的做法,在遇到這樣的需求前你是先想到SQL還是先想到開(kāi)發(fā)工具呢?從實(shí)際效果看使用SQL這種方式更加靈活。這樣的SQL實(shí)現(xiàn)的字符串拼接是不是有點(diǎn)極限呢?
數(shù)據(jù)庫(kù)已經(jīng)越來(lái)越被人們熟知,同時(shí)也在許多企業(yè)中得到了應(yīng)用,但是由于市面上沒(méi)有統(tǒng)一的圖查詢(xún)語(yǔ)言標(biāo)準(zhǔn),所以有部分開(kāi)發(fā)者對(duì)于不同圖數(shù)據(jù)庫(kù)的用法存在著疑問(wèn)。因此本文作者對(duì)市面上主流的幾款圖數(shù)據(jù)庫(kù)進(jìn)行了一番分析,并以查詢(xún)操作為例進(jìn)行深入介紹。
文章的開(kāi)頭我們先來(lái)看下什么是圖數(shù)據(jù)庫(kù),根據(jù)維基百科的定義:圖數(shù)據(jù)庫(kù)是使用圖結(jié)構(gòu)進(jìn)行語(yǔ)義查詢(xún)的數(shù)據(jù)庫(kù),它使用節(jié)點(diǎn)、邊和屬性來(lái)表示和存儲(chǔ)數(shù)據(jù)。
雖然和關(guān)系型數(shù)據(jù)庫(kù)存儲(chǔ)的結(jié)構(gòu)不同(關(guān)系型數(shù)據(jù)庫(kù)為表結(jié)構(gòu),圖數(shù)據(jù)庫(kù)為圖結(jié)構(gòu)),但不計(jì)各自的性能問(wèn)題,關(guān)系型數(shù)據(jù)庫(kù)可以通過(guò)遞歸查詢(xún)或者組合其他 SQL 語(yǔ)句(Join)完成圖查詢(xún)語(yǔ)言查詢(xún)節(jié)點(diǎn)關(guān)系操作。得益于 1987 年 SQL 成為國(guó)際標(biāo)準(zhǔn)化組織(ISO)標(biāo)準(zhǔn),關(guān)系型數(shù)據(jù)庫(kù)行業(yè)得到了很好的發(fā)展。同 60、70 年代的關(guān)系型數(shù)據(jù)庫(kù)類(lèi)似,圖數(shù)據(jù)庫(kù)這個(gè)領(lǐng)域的查詢(xún)語(yǔ)言目前也沒(méi)有統(tǒng)一標(biāo)準(zhǔn),雖然 19 年 9 月經(jīng)過(guò)國(guó)際 SQL 標(biāo)準(zhǔn)委員會(huì)投票表決,決定將圖查詢(xún)語(yǔ)言(Graph Query Language)納為一種新的數(shù)據(jù)庫(kù)查詢(xún)語(yǔ)言,但 GQL 的制定仍需要一段時(shí)間。
鑒于市面上沒(méi)有統(tǒng)一的圖查詢(xún)語(yǔ)言標(biāo)準(zhǔn),在本文中我們選取市面上主流的幾款圖查詢(xún)語(yǔ)言來(lái)分析一波用法,由于篇幅原因本文旨在簡(jiǎn)單介紹圖查詢(xún)語(yǔ)言和常規(guī)用法,更詳細(xì)的內(nèi)容將在進(jìn)階篇中講述。
Gremlin 是 Apache ThinkerPop 框架下的圖遍歷語(yǔ)言。Gremlin 可以是聲明性的也可以是命令性的。雖然 Gremlin 是基于 Groovy 的,但具有許多語(yǔ)言變體,允許開(kāi)發(fā)人員以 Java、JavaScript、Python、Scala、Clojure 和 Groovy 等許多現(xiàn)代編程語(yǔ)言原生編寫(xiě) Gremlin 查詢(xún)。
支持圖數(shù)據(jù)庫(kù):Janus Graph、InfiniteGraph、Cosmos DB、DataStax Enterprise(5.0+) 、Amazon Neptune
Cypher 是一個(gè)描述性的圖形查詢(xún)語(yǔ)言,允許不必編寫(xiě)圖形結(jié)構(gòu)的遍歷代碼對(duì)圖形存儲(chǔ)有表現(xiàn)力和效率的查詢(xún),和 SQL 很相似,Cypher 語(yǔ)言的關(guān)鍵字不區(qū)分大小寫(xiě),但是屬性值,標(biāo)簽,關(guān)系類(lèi)型和變量是區(qū)分大小寫(xiě)的。
支持圖數(shù)據(jù)庫(kù): Neo4j、RedisGraph、AgensGraph
nGQL 是一種類(lèi) SQL 的聲明型的文本查詢(xún)語(yǔ)言,nGQL 同樣是關(guān)鍵詞大小寫(xiě)不敏感的查詢(xún)語(yǔ)言,目前支持模式匹配、聚合運(yùn)算、圖計(jì)算,可無(wú)嵌入組合語(yǔ)句。
支持圖數(shù)據(jù)庫(kù):Nebula Graph
在比較這 3 個(gè)圖查詢(xún)語(yǔ)言之前,我們先來(lái)看看他們各自的術(shù)語(yǔ),如果你翻閱他們的文檔會(huì)經(jīng)常見(jiàn)到下面這些“關(guān)鍵字”,在這里我們不講用法,只看這些圖數(shù)據(jù)庫(kù)常用概念在這 3 個(gè)圖數(shù)據(jù)庫(kù)文檔中的叫法。
術(shù)語(yǔ)GremlinCyphernGQL點(diǎn)VertexNodeVertex邊EdgeRelationshipEdge點(diǎn)類(lèi)型LabelLabelTag邊類(lèi)型labelRelationshipTypeedge type點(diǎn) IDvidid(n)vid邊 IDeidid?無(wú)插入addcreateinsert刪除dropdeletedelete / drop更新屬性setPropertysetupdate
我們可以看到大體上對(duì)點(diǎn)和邊的叫法類(lèi)似,只不過(guò) Cypher 中直接使用了 Relationship 關(guān)系一詞代表邊。其他的術(shù)語(yǔ)基本都非常直觀。
上面說(shuō)了一通術(shù)語(yǔ)之類(lèi)的“干貨”之后,是時(shí)候展示真正的技術(shù)了——來(lái)個(gè)具體一點(diǎn)的例子,在具體的例子中我們將會(huì)分析 Gremlin、Cypher、nGQL 的用法不同。
實(shí)操示例使用了 Janus Graph 的示例圖 The Graphs of Gods。該圖結(jié)構(gòu)如下圖所示,描述了羅馬萬(wàn)神話(huà)中諸神關(guān)系。
復(fù)制代碼
# 插入點(diǎn)## nGQLnebula> INSERT VERTEX character(name, age, type) VALUES hash("saturn"):("saturn", 10000, "titan"), hash("jupiter"):("jupiter", 5000, "god");## Gremlingremlin> saturn = g.addV("character").property(T.id, 1).property('name', 'saturn').property('age', 10000).property('type', 'titan').next();==>v[1]gremlin> jupiter = g.addV("character").property(T.id, 2).property('name', 'jupiter').property('age', 5000).property('type', 'god').next();==>v[2]gremlin> prometheus = g.addV("character").property(T.id, 31).property('name', 'prometheus').property('age', 1000).property('type', 'god').next();==>v[31]gremlin> jesus = g.addV("character").property(T.id, 32).property('name', 'jesus').property('age', 5000).property('type', 'god').next();==>v[32]## Cyphercypher> CREATE (src:character {name:"saturn", age: 10000, type:"titan"})cypher> CREATE (dst:character {name:"jupiter", age: 5000, type:"god"})# 插入邊## nGQLnebula> INSERT EDGE father() VALUES hash("jupiter")->hash("saturn"):();## Gremlingremlin> g.addE("father").from(jupiter).to(saturn).property(T.id, 13);==>e[13][2-father->1]## Cyphercypher> CREATE (src)-[rel:father]->(dst)
在數(shù)據(jù)插入這塊,我們可以看到 nGQL 使用 INSERT VERTEX 插入點(diǎn),而 Gremlin 直接使用類(lèi)函數(shù)的 g.addV() 來(lái)插入點(diǎn),Cypher 使用 CREATE 這個(gè) SQL 常見(jiàn)關(guān)鍵詞來(lái)創(chuàng)建插入的點(diǎn)。在點(diǎn)對(duì)應(yīng)的屬性值方面,nGQL 通過(guò) VALUES 關(guān)鍵詞來(lái)賦值,Gremlin 則通過(guò)操作 .property() 進(jìn)行對(duì)應(yīng)屬性的賦值,Cypher 更直觀直接在對(duì)應(yīng)的屬性值后面跟上想對(duì)應(yīng)的值。
在邊插入方面,可以看到和點(diǎn)的使用語(yǔ)法類(lèi)似,只不過(guò)在 Cypher 和 nGQL 中分別使用 -[]-> 和 **-> 來(lái)表示關(guān)系,而 Gremlin 則用 to() ** 關(guān)鍵詞來(lái)標(biāo)識(shí)指向關(guān)系,在使用這 3 種圖查詢(xún)語(yǔ)言的圖數(shù)據(jù)庫(kù)中的邊均為有向邊,下圖左邊為有向邊,右邊為無(wú)向邊。
復(fù)制代碼
# nGQLnebula> DELETE VERTEX hash("prometheus");# Gremlingremlin> g.V(prometheus).drop();# Cyphercypher> MATCH (n:character {name:"prometheus"}) DETACH DELETE n
這里,我們可以看到大家的刪除關(guān)鍵詞都是類(lèi)似的:Delete 和 Drop,不過(guò)這里需要注意的是上面術(shù)語(yǔ)篇中提過(guò) nGQL 中刪除操作對(duì)應(yīng)單詞有 Delete 和 Drop ,在 nGQL 中 Delete 一般用于點(diǎn)邊,Drop 用于 Schema 刪除,這點(diǎn)和 SQL 的設(shè)計(jì)思路是一樣的。
復(fù)制代碼
# nGQLnebula> UPDATE VERTEX hash("jesus") SET character.type = 'titan';# Gremlingremlin> g.V(jesus).property('age', 6000);==>v[32]# Cyphercypher> MATCH (n:character {name:"jesus"}) SET n.type = 'titan';
可以看到 Cypher 和 nGQL 都使用 SET 關(guān)鍵詞來(lái)設(shè)置點(diǎn)對(duì)應(yīng)的類(lèi)型值,只不過(guò) nGQL 中多了 UPDATE 關(guān)鍵詞來(lái)標(biāo)識(shí)操作,Gremlin 的操作和查看點(diǎn)操作類(lèi)似,只不過(guò)增加了變更 property 值操作,這里我們注意到的是,Cypher 中常見(jiàn)的一個(gè)關(guān)鍵詞便是 MATCH,顧名思義,它是一個(gè)查詢(xún)關(guān)鍵詞,它會(huì)去選擇匹配對(duì)應(yīng)條件下的點(diǎn)邊,再進(jìn)行下一步操作。
復(fù)制代碼
# nGQLnebula> FETCH PROP ON character hash("saturn");===================================================| character.name | character.age | character.type |===================================================| saturn | 10000 | titan |---------------------------------------------------# Gremlingremlin> g.V(saturn).valueMap();==>[name:[saturn],type:[titan],age:[10000]]# Cyphercypher> MATCH (n:character {name:"saturn"}) RETURN properties(n) ╒════════════════════════════════════════════╕ │"properties(n)" │ ╞════════════════════════════════════════════╡ │{"name":"saturn","type":"titan","age":10000}│ └────────────────────────────────────────────┘
在查看數(shù)據(jù)這塊,Gremlin 通過(guò)調(diào)取 valueMap() 獲得對(duì)應(yīng)的屬性值,而 Cypher 正如上面更新數(shù)據(jù)所說(shuō),依舊是 MATCH 關(guān)鍵詞來(lái)進(jìn)行對(duì)應(yīng)的匹配查詢(xún)?cè)偻ㄟ^(guò) RETURN 返回對(duì)應(yīng)的數(shù)值,而 nGQL 則對(duì) saturn 進(jìn)行 hash 運(yùn)算得到對(duì)應(yīng) VID 之后去獲取對(duì)應(yīng) VID 的屬性值。
復(fù)制代碼
# nGQLnebula> LOOKUP ON character WHERE character.name == 'hercules' | \ -> GO FROM $-.VertexID OVER father YIELD $$.character.name;=====================| $$.character.name |=====================| jupiter |---------------------# Gremlingremlin> g.V().hasLabel('character').has('name','hercules').out('father').values('name');==>jupiter# Cyphercypher> MATCH (src:character{name:"hercules"})-[:father]->(dst:character) RETURN dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"jupiter" │ └──────────┘
查詢(xún)父親,其實(shí)是一個(gè)查詢(xún)關(guān)系 / 邊的操作,這里不做贅述,上面插入邊的時(shí)候簡(jiǎn)單介紹了 Gremlin、Cypher、nGQL 這三種圖數(shù)據(jù)庫(kù)是各自用來(lái)標(biāo)識(shí)邊的關(guān)鍵詞和操作符是什么。
復(fù)制代碼
# nGQLnebula> LOOKUP ON character WHERE character.name == 'hercules' | \ -> GO 2 STEPS FROM $-.VertexID OVER father YIELD $$.character.name;=====================| $$.character.name |=====================| saturn |---------------------# Gremlingremlin> g.V().hasLabel('character').has('name','hercules').out('father').out('father').values('name');==>saturn# Cyphercypher> MATCH (src:character{name:"hercules"})-[:father*2]->(dst:character) RETURN dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"saturn" │ └──────────┘
查詢(xún)祖父,其實(shí)是一個(gè)查詢(xún)對(duì)應(yīng)點(diǎn)的兩跳關(guān)系,即:父親的父親,我們可以看到 Gremlin 使用了兩次 out() 來(lái)表示為祖父,而 nGQL 這里使用了 |(Pipe 管道) 的概念,用于子查詢(xún)。在兩跳關(guān)系處理上,上面說(shuō)到 Gremlin 是用了 2 次 out(),而 Cypher、nGQL 則引入了 step 數(shù)的概念,分別對(duì)應(yīng)到查詢(xún)語(yǔ)句的 GO 2 STEP 和 [:father *2],相對(duì)來(lái)說(shuō) Cypher、nGQL 這樣書(shū)寫(xiě)更優(yōu)雅。
復(fù)制代碼
# nGQLnebula> LOOKUP ON character WHERE character.age > 100 YIELD character.name, character.age;=========================================================| VertexID | character.name | character.age |=========================================================| 6761447489613431910 | pluto | 4000 |---------------------------------------------------------| -5860788569139907963 | neptune | 4500 |---------------------------------------------------------| 4863977009196259577 | jupiter | 5000 |---------------------------------------------------------| -4316810810681305233 | saturn | 10000 |---------------------------------------------------------# Gremlingremlin> g.V().hasLabel('character').has('age',gt(100)).values('name');==>saturn==>jupiter==>neptune==>pluto# Cyphercypher> MATCH (src:character) WHERE src.age > 100 RETURN src.name ╒═══════════╕ │"src.name" │ ╞═══════════╡ │ "saturn" │ ├───────────┤ │ "jupiter" │ ├───────────┤ │ "neptune" │ │───────────│ │ "pluto" │ └───────────┘
這個(gè)是一個(gè)典型的查詢(xún)語(yǔ)句,找尋符合特定條件的點(diǎn)并返回結(jié)果,在 Cypher 和 nGQL 中用 WHRER 進(jìn)行條件判斷,而 Gremlin 延續(xù)了它的“編程風(fēng)”用 gt(100) 表示年大于齡 100 的這個(gè)篩選條件,延伸下 Gremlin 中 eq() 則表示等于這個(gè)查詢(xún)條件。
復(fù)制代碼
# nGQLnebula> GO FROM hash("pluto") OVER lives YIELD lives._dst AS place | GO FROM $-.place OVER lives REVERSELY WHERE $$.character.name != "pluto" YIELD $$.character.name AS cohabitants;===============| cohabitants |===============| cerberus |---------------# Gremlingremlin> g.V(pluto).out('lives').in('lives').where(is(neq(pluto))).values('name');==>cerberus# Cyphercypher> MATCH (src:character{name:"pluto"})-[:lives]->()<-[:lives]-(dst:character) RETURN dst.name ╒══════════╕ │"dst.name"│ ╞══════════╡ │"cerberus"│ └──────────┘
這是一個(gè)沿指定點(diǎn) Pluto 反向查詢(xún)指定邊(居住)的操作,在反向查詢(xún)中,Gremlin 使用了 in 來(lái)表示反向關(guān)系,而 Cypher 則更直觀的將指向箭頭反向變成 <- 來(lái)表示反向關(guān)系,nGQL 則用關(guān)鍵詞 REVERSELY 來(lái)標(biāo)識(shí)反向關(guān)系。
復(fù)制代碼
# which brother lives in which place?## nGQLnebula> GO FROM hash("pluto") OVER brother YIELD brother._dst AS god | \GO FROM $-.god OVER lives YIELD $^.character.name AS Brother, $$.location.name AS Habitations;=========================| Brother | Habitations |=========================| jupiter | sky |-------------------------| neptune | sea |-------------------------## Gremlingremlin> g.V(pluto).out('brother').as('god').out('lives').as('place').select('god','place').by('name');==>[god:jupiter, place:sky]==>[god:neptune, place:sea]## Cyphercypher> MATCH (src:Character{name:"pluto"})-[:brother]->(bro:Character)-[:lives]->(dst)RETURN bro.name, dst.name ╒═════════════════════════╕ │"bro.name" │"dst.name"│ ╞═════════════════════════╡ │ "jupiter" │ "sky" │ ├─────────────────────────┤ │ "neptune" │ "sea" │ └─────────────────────────┘
這是一個(gè)通過(guò)查詢(xún)指定點(diǎn) Pluto 查詢(xún)指定邊 brother 后再查詢(xún)指定邊 live 的查詢(xún),相對(duì)來(lái)說(shuō)不是很復(fù)雜,這里就不做解釋說(shuō)明了。
最后,本文只是對(duì) Gremlin、Cypher、nGQL 等 3 個(gè)圖查詢(xún)語(yǔ)言進(jìn)行了簡(jiǎn)單的介紹,更復(fù)雜的語(yǔ)法將在本系列的后續(xù)文章中繼續(xù),歡迎在論壇留言交流。
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。