timeuuid類型
timeuuid具有唯一索引和日期時間的綜合特性,可以與日期和時間函數聯合使用,常用的關聯函數:
- dateOf()
- now()
- minTimeuuid() and maxTimeuuid()
- toDate(timeuuid)
- toTimestamp(timeuuid)
- toUnixTimestamp(timeuuid)
比如
SELECT * FROM myTable WHERE t > maxTimeuuid('2013-01-01 00:05+0000') AND t < minTimeuuid('2013-02-02 10:00+0000')
DataStax Enterprise 5.0及更高版本支持一些額外的timeuuid和timestamp函數來操作日期。這些函數可以在INSERT、UPDATE和SELECT語句中使用。
CREATE TABLE sample_times (a int, b timestamp, c timeuuid, d bigint, PRIMARY KEY (a,b,c,d)); INSERT INTO sample_times (a,b,c,d) VALUES (1, toUnixTimestamp(now()), 50554d6e-29bb-11e5-b345-feff819cdc9f, toTimestamp(now())); SELECT a, b, toDate(c), toDate(d) FROM sample_times
開工!
-- 開始工作: bin/cqlsh localhost -- 查看所有的鍵空間: DESCRIBE keyspaces -- 使用創建的鍵空間: USE myks; -- 查看已有表: describe tables; -- 查看表結構: describe table users; -- 刪除已有表: drop table users;
user_status_updates表有一個id字段,類型是timeuuid。
username是一個分區key。表的分區key的作用是將行分組到特定的邏輯相關。在程序中,每個用戶的時間線都是一個獨立的數據結構,因此按用戶划分表是一個合理的策略。
我們稱id列為集群(clustering)列。集群列的任務是確定分區中行的順序。這就是為什么我們觀察到在每個用戶的狀態更新中,行是按id的時間戳嚴格按升序返回的。
-- 首先,先建表 CREATE TABLE "user_status_updates" ( "username" text, "id" timeuuid, "body" text, PRIMARY KEY ("username", "id") ); CREATE TABLE "users" ( "username" text, "email" text, "encrypted_password" blob, PRIMARY KEY ("username") ); -- 對主表users進行數據插入 INSERT INTO "users" ("username", "email", "encrypted_password") VALUES ( 'alice', 'alice@gmail.com', 0x8914977ed729792e403da53024c6069a9158b8c4 ); INSERT INTO "users" ("username", "encrypted_password") VALUES ( 'bob', 0x10920941a69549d33aaee6116ed1f47e19b8e713 ); -- 對從表user_status_updates進行數據插入 INSERT INTO "user_status_updates" ("username", "id", "body") VALUES ( 'alice', 76e7a4d0-e796-11e3-90ce-5f98e903bf02, 'Learning Cassandra!' ); INSERT INTO "user_status_updates" ("username", "id", "body") VALUES ( 'bob', 97719c50-e797-11e3-90ce-5f98e903bf02, 'Eating a tasty sandwich.' ); -- 前面我們加了2條從表記錄,現在重點關注下id(即timeuuid)的查詢 SELECT * FROM "user_status_updates"; SELECT "username", "id", "body", DATEOF("id") FROM "user_status_updates"; SELECT "username", "id", "body", UNIXTIMESTAMPOF("id") FROM "user_status_updates"; SELECT * FROM "user_status_updates" WHERE "username" = 'alice' AND "id" = 76e7a4d0-e796-11e3-90ce-5f98e903bf02; -- 我們加了2條自制的uuid,那么從時間函數能自動產生uuid嗎?會不會產生排序異常? -- 再插入6條測試數據 INSERT INTO "user_status_updates" ("username", "id", "body") VALUES ('alice', NOW(), 'Alice Update 1'); INSERT INTO "user_status_updates" ("username", "id", "body") VALUES ('bob', NOW(), 'Bob Update 1'); INSERT INTO "user_status_updates" ("username", "id", "body") VALUES ('alice', NOW(), 'Alice Update 2'); INSERT INTO "user_status_updates" ("username", "id", "body") VALUES ('bob', NOW(), 'Bob Update 2'); INSERT INTO "user_status_updates" ("username", "id", "body") VALUES ('alice', NOW(), 'Alice Update 3'); INSERT INTO "user_status_updates" ("username", "id", "body") VALUES ('bob', NOW(), 'Bob Update 3'); -- 看看時間戳就知道了 SELECT "username", "id", "body",toTimestamp("id"), UNIXTIMESTAMPOF("id") FROM "user_status_updates";
-- 結果:
上面的例子看到一個表的主鍵中有兩列:一個分區鍵(username)和一個集群列(id)。事實證明,這兩種都不僅僅限於一個列。可以定義一個或多個分區鍵列和零個或多個群集列。
多個群集列
群集列不限於前面指定的一個字段。讓我們看看多個集群列是如何工作的,並有助於數據排序。為了說明這一點,我們將重新創建狀態更新表,以便按用戶更新其狀態的日期和時間對其進行群集:
"status_date", "status_time"將是下面的演示多個集群列的例子。
復合主鍵
復合主鍵是由多個列組成的簡單主鍵。雖然乍一看,這似乎是對Cassandra表的一個小小的補充,但實際上,具有復合主鍵的表是一個相當豐富的數據結構,它具有新的數據訪問模式。
我們構建一個user_status_updates表,該表存儲用戶狀態更新的時間線。
-- 下面來看看復合主鍵 -- 建表 CREATE TABLE "user_status_updates_by_datetime" ( "username" text, "status_date" date, "status_time" time, "body" text, PRIMARY KEY ("username", "status_date", "status_time") ); INSERT INTO "user_status_updates_by_datetime" ("username", "status_date", "status_time", "body") VALUES ('alice', '2019-11-18', '08:30:55.123', 'Alice Update 1'); INSERT INTO "user_status_updates_by_datetime" ("username", "status_date", "status_time", "body") VALUES ('alice', '2019-11-18', '14:40:25.123456789', 'Alice Update 2'); INSERT INTO "user_status_updates_by_datetime" ("username", "status_date", "status_time", "body") VALUES ('alice', '2019-11-19', '08:25:25', 'Alice Update 3'); INSERT INTO "user_status_updates_by_datetime" ("username", "status_date", "status_time", "body") VALUES ('alice', '2019-11-21', '08:35:55.123456', 'Alice Update 4'); INSERT INTO "user_status_updates_by_datetime" ("username", "status_date", "status_time", "body") VALUES ('alice', '2019-11-21', '14:30:15.123', 'Alice Update 5'); INSERT INTO "user_status_updates_by_datetime" ("username", "status_date", "status_time", "body") VALUES ('alice', '2019-11-23', '14:50:45.123456', 'Alice Update 6');
-- 試試看插入一些錯誤數據
INSERT INTO "user_status_updates_by_datetime" ("username", "status_date", "status_time", "body") VALUES ('alice', '2019-14-23', '14:50:45.123456', 'Alice Update 7'); INSERT INTO "user_status_updates_by_datetime" ("username", "status_date", "status_time", "body") VALUES ('alice', '2019-11-23', '14:65:45.123456', 'Alice Update 8');
一些組合查詢和傳統數據庫不同的地方
SELECT * FROM "user_status_updates_by_datetime"; -- 試試組合查詢 SELECT * FROM "user_status_updates_by_datetime" WHERE "username" = 'alice' AND "status_date" < '2019-11-20'; -- 試試組合日期和時間查詢,要查大於某天及大於幾點的數據 SELECT * FROM "user_status_updates_by_datetime" WHERE "username" = 'alice' AND "status_date" > '2019-11-20' AND "status_time" > '12:00:00'; -- 報了個錯:InvalidRequest: Error from server: code=2200 [Invalid query] message="Clustering column "status_time" cannot be restricted (preceding column "status_date" is restricted by a non-EQ relation)" -- 原因Cassandra 支持的查詢語句很嚴格,首先 partition key 必須精確查詢,最后一個查詢才能范圍查詢。 -- 改成下面這樣就合規了 SELECT * FROM "user_status_updates_by_datetime" WHERE "username" = 'alice' AND "status_date" = '2019-11-21' AND "status_time" > '12:00:00';
Cassandra沒有內置的不同表中數據之間關系的概念。SELECT語句中沒有外鍵約束,也沒有JOIN子句;
事實上,在同一個查詢中沒有從多個表中讀取的方法,而關系數據庫可以考慮不同表中數據之間的關系,無論它們是一對一、一對多還是多對多。
Cassandra沒有用於描述或遍歷表間關系的內置機制。也就是說,Cassandra的復合主鍵結構為一種特殊的主從關系提供了充足的保障。
在關系數據庫中,我們可以使用外鍵約束使關系在模式中顯式化,但是Cassandra沒有提供這樣的功能。事實上,如果我們想為用戶和用戶狀態更新使用兩個不同的表,我們無法在數據庫模式中顯式地編碼它們的關系。但是,有一種方法可以將用戶和狀態更新合並到一個表中,同時保持它們之間的一對多關系。為了實現這一合並,我們將使用Cassandra表的一個特性,這是我們以前從未見過的靜態列。
其實,歸根結締,用一個詞來形容Cassandra的這種Non Join的做法的核心就是:冗余。
而靜態列就是保持冗余和一致性的重要技術手段。
STATIC靜態字段
-- 創建表 -- 我們將 email 和 encrypted_password 兩個字段設置為 STATIC 了,這意味着同一個 username 只會有一個 email 和 encrypted_password CREATE TABLE "users_with_status_updates" ( "username" text, "id" timeuuid, "email" text STATIC, "encrypted_password" blob STATIC, "body" text, PRIMARY KEY ("username", "id") ); INSERT INTO "users_with_status_updates" ("username", "id", "email", "encrypted_password", "body") VALUES ( 'alice', 76e7a4d0-e796-11e3-90ce-5f98e903bf02, 'alice@gmail.com', 0x8914977ed729792e403da53024c6069a9158b8c4, 'Learning Cassandra!' ); SELECT * FROM "users_with_status_updates"; -- 驗證插入 INSERT INTO "users_with_status_updates" ("username", "id", "body") VALUES ('alice', NOW(), 'Another status update'); SELECT * FROM "users_with_status_updates"; SELECT "username", "email", "encrypted_password" FROM "users_with_status_updates" WHERE "username" = 'alice'; SELECT DISTINCT "username", "email", "encrypted_password" FROM "users_with_status_updates" WHERE "username" = 'alice'; -- 驗證另一個主鍵bob INSERT INTO "users_with_status_updates" ("username", "email", "encrypted_password") VALUES ( 'bob', 'bob@gmail.com', 0x10920941a69549d33aaee6116ed1f47e19b8e713 ); INSERT INTO "users_with_status_updates" ("username", "id", "body") VALUES ('bob', NOW(), 'Bob status update');
結果:
高級課題
以上是聯合主鍵的一些用法,現在進入高級些的課題
- 如何檢索單個分區中的所有行
- 如何檢索集群列值范圍內的行
- 如何對單個分區中的行進行分頁
- 如何更改結果的順序
- 如何按聚類列的降序存儲行
- 如何分頁
SELECT * FROM "user_status_updates" WHERE "username" = 'alice'; -- 以前,我們使用WHERE關鍵字為一個完整的主鍵指定一個確切的值。在前面的查詢中,我們只指定主鍵的分區鍵部分,這允許我們只檢索那些我們要求分區的行; -- 出於唯一性的目的,username列肯定不能保證唯一性;id是一個UUID,我們可以跳過用戶名分區鍵,只通過id clustering列查找行嗎?讓我們試一試: SELECT * FROM "user_status_updates" WHERE id = 3f9b5f00-e8f7-11e3-9211-5f98e903bf02; INSERT INTO "status_update_replies" ("status_update_username", "status_update_id", "id", "body") VALUES( 'alice', 76e7a4d0-e796-11e3-90ce-5f98e903bf02, NOW(), 'Good luck!' ); SELECT * FROM "status_update_replies" WHERE "status_update_username" = 'alice'; -- 檢索特定時間范圍的狀態更新 -- 在探究了CQL中WHERE關鍵字的限制之后,讓我們返回到user_status_updates表。假設我們想為MyStatus構建一個存檔特性,顯示用戶在請求的一個月內的所有更新。 -- 在CQL術語中,我們希望選擇一系列集群列;例如,讓我們返回2014年5月創建的alice的所有狀態更新: SELECT "id", DATEOF("id"), "body" FROM "user_status_updates" WHERE "username" = 'alice' AND "id" >= MINTIMEUUID('2014-05-01') AND "id" <= MAXTIMEUUID('2014-05-31'); -- 在分區中分頁,順序 SELECT "id", DATEOF("id"), "body" FROM "user_status_updates" WHERE "username" = 'alice' LIMIT 3; -- 在分區中篩選后分頁,順序 SELECT "id", DATEOF("id"), "body" FROM "user_status_updates" WHERE "username" = 'alice' AND id > 3f9df710-e8f7-11e3-9211-5f98e903bf02 LIMIT 3; SELECT COUNT(1) FROM "user_status_updates" WHERE "username" = 'alice'; -- 在分區中篩選后分頁,倒序 SELECT "id", DATEOF("id"), "body" FROM "user_status_updates" WHERE "username" = 'alice' ORDER BY "id" DESC; -- 在分區中不使用簇列進行排序,將會報錯 cqlsh:myks> SELECT "id", DATEOF("id"), "body" ... FROM "user_status_updates" ... WHERE "username" = 'alice' ... ORDER BY "body" DESC; InvalidRequest: Error from server: code=2200 [Invalid query] message="Order by is currently only supported on the clustered columns of the PRIMARY KEY, got body" -- 顯式設定特定的倒序排序 CREATE TABLE "reversed_user_status_updates" ( "username" text, "id" timeuuid, "body" text, PRIMARY KEY ("username", "id") ) WITH CLUSTERING ORDER BY ("id" DESC); INSERT INTO "reversed_user_status_updates" ("username", "id", "body") VALUES ('alice', NOW(), 'Reversed status 1'); INSERT INTO "reversed_user_status_updates" ("username", "id", "body") VALUES ('alice', NOW(), 'Reversed status 2'); INSERT INTO "reversed_user_status_updates" ("username", "id", "body") VALUES ('alice', NOW(), 'Reversed status 3'); -- 顯式倒序排序測試 SELECT * FROM "reversed_user_status_updates" WHERE "username" = 'alice'; -- 正確 SELECT * FROM "user_status_updates_by_datetime"; SELECT * FROM "user_status_updates_by_datetime" WHERE "username" = 'alice' ORDER BY "status_date" ASC, "status_time" ASC; SELECT * FROM "user_status_updates_by_datetime" WHERE "username" = 'alice' ORDER BY "status_date" DESC, "status_time" DESC; --錯誤 SELECT * FROM "user_status_updates_by_datetime" WHERE "username" = 'alice' ORDER BY "status_date" ASC, "status_time" DESC; SELECT * FROM "user_status_updates_by_datetime" WHERE "username" = 'alice' ORDER BY "status_date" DESC, "status_time" ASC; -- 顯式設定特定的倒序-正序排序組合 CREATE TABLE "reversed_user_status_updates_by_datetime" ( "username" text, "status_date" date, "status_time" time, "body" text, PRIMARY KEY ("username", "status_date", "status_time") ) WITH CLUSTERING ORDER BY ("status_date" ASC, "status_time" DESC); JSON支持 -- 插入JSON,看起來完全不像傳統數據庫SQL了 INSERT INTO "user_status_updates_by_datetime" JSON '{"username": "alice", "status_date": "2016-11-24", "status_time": "13:35:20.123456", "body": "Alice Update 7"}'; SELECT * FROM "user_status_updates_by_datetime"; -- json查詢 SELECT JSON * FROM "user_status_updates_by_datetime" WHERE "username" = 'alice' AND status_date > '2016-11-20';