數(shù)據(jù)庫已經(jīng)越來越被人們熟知,同時(shí)也在許多企業(yè)中得到了應(yīng)用,但是由于市面上沒有統(tǒng)一的圖查詢語言標(biāo)準(zhǔn),所以有部分開發(fā)者對(duì)于不同圖數(shù)據(jù)庫的用法存在著疑問。因此本文作者對(duì)市面上主流的幾款圖數(shù)據(jù)庫進(jìn)行了一番分析,并以查詢操作為例進(jìn)行深入介紹。
文章的開頭我們先來看下什么是圖數(shù)據(jù)庫,根據(jù)維基百科的定義:圖數(shù)據(jù)庫是使用圖結(jié)構(gòu)進(jìn)行語義查詢的數(shù)據(jù)庫,它使用節(jié)點(diǎn)、邊和屬性來表示和存儲(chǔ)數(shù)據(jù)。
雖然和關(guān)系型數(shù)據(jù)庫存儲(chǔ)的結(jié)構(gòu)不同(關(guān)系型數(shù)據(jù)庫為表結(jié)構(gòu),圖數(shù)據(jù)庫為圖結(jié)構(gòu)),但不計(jì)各自的性能問題,關(guān)系型數(shù)據(jù)庫可以通過遞歸查詢或者組合其他 SQL 語句(Join)完成圖查詢語言查詢節(jié)點(diǎn)關(guān)系操作。得益于 1987 年 SQL 成為國際標(biāo)準(zhǔn)化組織(ISO)標(biāo)準(zhǔn),關(guān)系型數(shù)據(jù)庫行業(yè)得到了很好的發(fā)展。同 60、70 年代的關(guān)系型數(shù)據(jù)庫類似,圖數(shù)據(jù)庫這個(gè)領(lǐng)域的查詢語言目前也沒有統(tǒng)一標(biāo)準(zhǔn),雖然 19 年 9 月經(jīng)過國際 SQL 標(biāo)準(zhǔn)委員會(huì)投票表決,決定將圖查詢語言(Graph Query Language)納為一種新的數(shù)據(jù)庫查詢語言,但 GQL 的制定仍需要一段時(shí)間。
鑒于市面上沒有統(tǒng)一的圖查詢語言標(biāo)準(zhǔn),在本文中我們選取市面上主流的幾款圖查詢語言來分析一波用法,由于篇幅原因本文旨在簡單介紹圖查詢語言和常規(guī)用法,更詳細(xì)的內(nèi)容將在進(jìn)階篇中講述。
Gremlin 是 Apache ThinkerPop 框架下的圖遍歷語言。Gremlin 可以是聲明性的也可以是命令性的。雖然 Gremlin 是基于 Groovy 的,但具有許多語言變體,允許開發(fā)人員以 Java、JavaScript、Python、Scala、Clojure 和 Groovy 等許多現(xiàn)代編程語言原生編寫 Gremlin 查詢。
支持圖數(shù)據(jù)庫:Janus Graph、InfiniteGraph、Cosmos DB、DataStax Enterprise(5.0+) 、Amazon Neptune
Cypher 是一個(gè)描述性的圖形查詢語言,允許不必編寫圖形結(jié)構(gòu)的遍歷代碼對(duì)圖形存儲(chǔ)有表現(xiàn)力和效率的查詢,和 SQL 很相似,Cypher 語言的關(guān)鍵字不區(qū)分大小寫,但是屬性值,標(biāo)簽,關(guān)系類型和變量是區(qū)分大小寫的。
支持圖數(shù)據(jù)庫: Neo4j、RedisGraph、AgensGraph
nGQL 是一種類 SQL 的聲明型的文本查詢語言,nGQL 同樣是關(guān)鍵詞大小寫不敏感的查詢語言,目前支持模式匹配、聚合運(yùn)算、圖計(jì)算,可無嵌入組合語句。
支持圖數(shù)據(jù)庫:Nebula Graph
在比較這 3 個(gè)圖查詢語言之前,我們先來看看他們各自的術(shù)語,如果你翻閱他們的文檔會(huì)經(jīng)常見到下面這些“關(guān)鍵字”,在這里我們不講用法,只看這些圖數(shù)據(jù)庫常用概念在這 3 個(gè)圖數(shù)據(jù)庫文檔中的叫法。
術(shù)語GremlinCyphernGQL點(diǎn)VertexNodeVertex邊EdgeRelationshipEdge點(diǎn)類型LabelLabelTag邊類型labelRelationshipTypeedge type點(diǎn) IDvidid(n)vid邊 IDeidid?無插入addcreateinsert刪除dropdeletedelete / drop更新屬性setPropertysetupdate
我們可以看到大體上對(duì)點(diǎn)和邊的叫法類似,只不過 Cypher 中直接使用了 Relationship 關(guān)系一詞代表邊。其他的術(shù)語基本都非常直觀。
上面說了一通術(shù)語之類的“干貨”之后,是時(shí)候展示真正的技術(shù)了——來個(gè)具體一點(diǎn)的例子,在具體的例子中我們將會(huì)分析 Gremlin、Cypher、nGQL 的用法不同。
實(shí)操示例使用了 Janus Graph 的示例圖 The Graphs of Gods。該圖結(jié)構(gòu)如下圖所示,描述了羅馬萬神話中諸神關(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 直接使用類函數(shù)的 g.addV() 來插入點(diǎn),Cypher 使用 CREATE 這個(gè) SQL 常見關(guān)鍵詞來創(chuàng)建插入的點(diǎn)。在點(diǎn)對(duì)應(yīng)的屬性值方面,nGQL 通過 VALUES 關(guān)鍵詞來賦值,Gremlin 則通過操作 .property() 進(jìn)行對(duì)應(yīng)屬性的賦值,Cypher 更直觀直接在對(duì)應(yīng)的屬性值后面跟上想對(duì)應(yīng)的值。
在邊插入方面,可以看到和點(diǎn)的使用語法類似,只不過在 Cypher 和 nGQL 中分別使用 -[]-> 和 **-> 來表示關(guān)系,而 Gremlin 則用 to() ** 關(guān)鍵詞來標(biāo)識(shí)指向關(guān)系,在使用這 3 種圖查詢語言的圖數(shù)據(jù)庫中的邊均為有向邊,下圖左邊為有向邊,右邊為無向邊。
復(fù)制代碼
# nGQLnebula> DELETE VERTEX hash("prometheus");# Gremlingremlin> g.V(prometheus).drop();# Cyphercypher> MATCH (n:character {name:"prometheus"}) DETACH DELETE n
這里,我們可以看到大家的刪除關(guān)鍵詞都是類似的:Delete 和 Drop,不過這里需要注意的是上面術(shù)語篇中提過 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)鍵詞來設(shè)置點(diǎn)對(duì)應(yīng)的類型值,只不過 nGQL 中多了 UPDATE 關(guān)鍵詞來標(biāo)識(shí)操作,Gremlin 的操作和查看點(diǎn)操作類似,只不過增加了變更 property 值操作,這里我們注意到的是,Cypher 中常見的一個(gè)關(guān)鍵詞便是 MATCH,顧名思義,它是一個(gè)查詢關(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 通過調(diào)取 valueMap() 獲得對(duì)應(yīng)的屬性值,而 Cypher 正如上面更新數(shù)據(jù)所說,依舊是 MATCH 關(guān)鍵詞來進(jìn)行對(duì)應(yīng)的匹配查詢?cè)偻ㄟ^ 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" │ └──────────┘
查詢父親,其實(shí)是一個(gè)查詢關(guān)系 / 邊的操作,這里不做贅述,上面插入邊的時(shí)候簡單介紹了 Gremlin、Cypher、nGQL 這三種圖數(shù)據(jù)庫是各自用來標(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" │ └──────────┘
查詢祖父,其實(shí)是一個(gè)查詢對(duì)應(yīng)點(diǎn)的兩跳關(guān)系,即:父親的父親,我們可以看到 Gremlin 使用了兩次 out() 來表示為祖父,而 nGQL 這里使用了 |(Pipe 管道) 的概念,用于子查詢。在兩跳關(guān)系處理上,上面說到 Gremlin 是用了 2 次 out(),而 Cypher、nGQL 則引入了 step 數(shù)的概念,分別對(duì)應(yīng)到查詢語句的 GO 2 STEP 和 [:father *2],相對(duì)來說 Cypher、nGQL 這樣書寫更優(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è)典型的查詢語句,找尋符合特定條件的點(diǎn)并返回結(jié)果,在 Cypher 和 nGQL 中用 WHRER 進(jìn)行條件判斷,而 Gremlin 延續(xù)了它的“編程風(fēng)”用 gt(100) 表示年大于齡 100 的這個(gè)篩選條件,延伸下 Gremlin 中 eq() 則表示等于這個(gè)查詢條件。
復(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 反向查詢指定邊(居住)的操作,在反向查詢中,Gremlin 使用了 in 來表示反向關(guān)系,而 Cypher 則更直觀的將指向箭頭反向變成 <- 來表示反向關(guān)系,nGQL 則用關(guān)鍵詞 REVERSELY 來標(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è)通過查詢指定點(diǎn) Pluto 查詢指定邊 brother 后再查詢指定邊 live 的查詢,相對(duì)來說不是很復(fù)雜,這里就不做解釋說明了。
最后,本文只是對(duì) Gremlin、Cypher、nGQL 等 3 個(gè)圖查詢語言進(jìn)行了簡單的介紹,更復(fù)雜的語法將在本系列的后續(xù)文章中繼續(xù),歡迎在論壇留言交流。
近在練習(xí)TP3.2制作個(gè)人博客時(shí),在后臺(tái)中使用ueditor發(fā)布文章并保存在數(shù)據(jù)庫中。
如下圖,文章內(nèi)容非常簡單一段文字加一個(gè)圖片,后臺(tái)中可以對(duì)文章進(jìn)行修改:傳入文章ID->讀取數(shù)據(jù)庫->在ueditor中顯示,這些操作都沒有問題。
長沙蒲公英網(wǎng)絡(luò)
在前臺(tái)調(diào)用時(shí)卻發(fā)現(xiàn)文章內(nèi)容未正常顯示--顯示成了HTML實(shí)體的形式。考慮到文章內(nèi)容在后臺(tái)中顯示正常,區(qū)別在于有無有使編程器。所以問題可能是編輯器保存數(shù)據(jù)時(shí)可能與通常形式不一樣。
長沙蒲公英網(wǎng)絡(luò)
通過查看數(shù)據(jù)庫中文章內(nèi)容保存的結(jié)果得知,編輯器將HTML標(biāo)簽進(jìn)行了轉(zhuǎn)義后再保存,而輸出到HTML頁面中時(shí),標(biāo)簽被轉(zhuǎn)義回單獨(dú)的字符直接顯示成了HTML代碼。這樣處理的優(yōu)點(diǎn)是可以避免sql注入;缺點(diǎn)是不能直接讀取數(shù)據(jù)。
長沙蒲公英網(wǎng)絡(luò)
知道了原因,那么問題解決起來就容易多了。1.高手可以對(duì)ueditor數(shù)據(jù)處理方式進(jìn)行修改(時(shí)間成本高,可能導(dǎo)致sql注入的漏洞);2.可以將文章內(nèi)容先讀取在ueditor中,再把ueditor中的內(nèi)容顯示在頁面上(過程復(fù)雜,但簡單有效);3.讀取數(shù)據(jù)時(shí)先處理后,再在模板中進(jìn)行顯示(簡單實(shí)用)。
這里簡單說明下第三種方法:在讀取數(shù)據(jù)后使用html_entity_decode函數(shù)把文章內(nèi)容從 HTML 實(shí)體轉(zhuǎn)換為字符。
public function index() {
$id = I('get.id', '', 'intval');
$field = array('id', 'title', 'time', 'content', 'cid', 'click');
$blog = M('blog')->field($field)->find($id);
$blog['content'] = html_entity_decode($blog['content']);
$blog['click'] += 1;
$where = array('id' => $id);
M('blog')->where($where)->setInc('click');
$this->assign('blog', $blog);
$this->display();
}
通過以上處理后,文章內(nèi)容就可以在前臺(tái)正常使用了。
長沙蒲公英網(wǎng)絡(luò)
原創(chuàng)文章鏈接:http://www.0731pgy.com/a/news/IndustryNews/430.html
Javascript無法直接訪問SqlServer數(shù)據(jù)庫的,但可以使用瀏覽器的“執(zhí)行SQL”功能查詢數(shù)據(jù)庫,將查詢結(jié)果保存在瀏覽器變量中,然后在Javascript代碼中引用這些變量即可。在瀏覽器的項(xiàng)目管理器中,新建執(zhí)行SQL步驟,設(shè)置好SqlServer數(shù)據(jù)庫連接參數(shù),以及Sql查詢語句和引用變量值。
Sqlserver連接參數(shù)及查詢語句
在Javascript代碼輸入框中,點(diǎn)擊鼠標(biāo)右鍵選擇Sql查詢結(jié)果中對(duì)應(yīng)的字段,這樣就把Sql查詢結(jié)果賦值給Javascript變量了。
Javascript引用Sqlserver查詢結(jié)果
在網(wǎng)頁填表步驟中,可直接引用Javascript變量的值。
Javascript變量的應(yīng)用
提示:如果把Sql語句改為Install,則可以構(gòu)建把Javascript變量插入到Sqlserver數(shù)據(jù)庫的方案。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。