数据操作
本节介绍 CQL 支持的用于插入、更新、删除和查询数据的语句。
SELECT
使用 SELECT
语句从数据中查询数据
select_statement::= SELECT [ JSON | DISTINCT ] ( select_clause | '*' )
FROM `table_name`
[ WHERE `where_clause` ]
[ GROUP BY `group_by_clause` ]
[ ORDER BY `ordering_clause` ]
[ PER PARTITION LIMIT (`integer` | `bind_marker`) ]
[ LIMIT (`integer` | `bind_marker`) ]
[ ALLOW FILTERING ]
select_clause::= `selector` [ AS `identifier` ] ( ',' `selector` [ AS `identifier` ] )
selector::== `column_name`
| `term`
| CAST '(' `selector` AS `cql_type` ')'
| `function_name` '(' [ `selector` ( ',' `selector` )_ ] ')'
| COUNT '(' '_' ')'
where_clause::= `relation` ( AND `relation` )*
relation::= column_name operator term
'(' column_name ( ',' column_name )* ')' operator tuple_literal
TOKEN '(' column_name# ( ',' column_name )* ')' operator term
operator::= '=' | '<' | '>' | '<=' | '>=' | '!=' | IN | CONTAINS | CONTAINS KEY
group_by_clause::= column_name ( ',' column_name )*
ordering_clause::= column_name [ ASC | DESC ] ( ',' column_name [ ASC | DESC ] )*
例如
SELECT name, occupation FROM users WHERE userid IN (199, 200, 207);
SELECT JSON name, occupation FROM users WHERE userid = 199;
SELECT name AS user_name, occupation AS user_occupation FROM users;
SELECT time, value
FROM events
WHERE event_type = 'myEvent'
AND time > '2011-02-03'
AND time <= '2012-01-01'
SELECT COUNT (*) AS user_count FROM users;
SELECT
语句至少包含一个选择子句和执行选择的表的名称。CQL 不执行联接或子查询,并且选择语句仅应用于单个表。选择语句还可以包含一个where 子句,该子句可以进一步缩小查询结果。其他子句可以排序或限制结果。最后,需要完整集群过滤的查询可以在任何查询中追加 ALLOW FILTERING
。对于虚拟表,从CASSANDRA-18238开始,当查询通常需要 ALLOW FILTERING
时,无需指定它。请参阅虚拟表的文档以了解更多信息。
选择子句
select_clause
确定将在结果集中查询和返回哪些列。此子句还可以应用转换以在返回之前应用于结果。选择子句由一个逗号分隔的特定选择器列表组成,或者,可以使用通配符字符 (*
) 来选择表中定义的所有列。
别名
每个顶层选择器也可以使用 AS 赋予别名。如果是这样,结果集中相应列的名称将是别名的名称。例如
// Without alias
SELECT int_as_blob(4) FROM t;
// int_as_blob(4)
// ----------------
// 0x00000004
// With alias
SELECT int_as_blob(4) AS four FROM t;
// four
// ------------
// 0x00000004
目前,别名在语句的 |
WRITETIME
、MAXWRITETIME
和 TTL
函数
选择支持三个在其他地方不允许的特殊函数:WRITETIME
、MAXWRITETIME
和 TTL
。所有函数都只接受一个参数,即列名。如果列是集合或 UDT,则可以添加元素选择器,例如 WRITETTIME(phones[2..4])
或 WRITETTIME(user.name)
。这些函数检索为每个列内部存储的元信息
-
WRITETIME
存储列值的 timestamp。 -
MAXWRITETIME
存储列值的 largest timestamp。对于非集合和非 UDT 列,MAXWRITETIME
等效于WRITETIME
。在其他情况下,它返回列中值的 largest timestamp。 -
TTL
存储列值的剩余生存时间(以秒为单位),如果它被设置为过期;否则值为null
。
WRITETIME
和 TTL
函数可用于多单元格列,例如非冻结集合或非冻结用户定义类型。在这种情况下,这些函数将返回每个选定单元格的 timestamp 或 TTL 列表。
WHERE
子句
WHERE
子句指定查询哪些行。它为 PRIMARY KEY
列或已定义二级索引的列指定一个关系,以及一个设置值。
查询中并非所有关系都是允许的。例如,分区键上只允许相等性。IN
子句被认为是一个或多个值的相等性。TOKEN
子句可用于查询分区键非相等性。分区键必须在 WHERE
子句中指定在聚簇列之前。聚簇列的关系必须指定一个连续的行集以进行排序。
例如,给定
CREATE TABLE posts (
userid text,
blog_title text,
posted_at timestamp,
entry_title text,
content text,
category int,
PRIMARY KEY (userid, blog_title, posted_at)
);
以下查询是允许的
SELECT entry_title, content FROM posts
WHERE userid = 'john doe'
AND blog_title='John''s Blog'
AND posted_at >= '2012-01-01' AND posted_at < '2012-01-31';
但以下查询是不允许的,因为它没有选择一个连续的行集(我们假设没有设置二级索引)
// Needs a blog_title to be set to select ranges of posted_at
SELECT entry_title, content FROM posts
WHERE userid = 'john doe'
AND posted_at >= '2012-01-01' AND posted_at < '2012-01-31';
在指定关系时,可以将 TOKEN
函数应用于 PARTITION KEY
列以进行查询。将根据 PARTITION_KEY
的 token 选择行,而不是根据值。
键的 token 取决于使用的分区器,特别是 |
例如
SELECT * FROM posts
WHERE token(userid) > token('tom') AND token(userid) < token('bob');
IN
关系仅允许在分区键的最后一列或完整主键的最后一列上使用。
还可以使用元组表示法将 CLUSTERING COLUMNS
“分组”到一个关系中。
例如
SELECT * FROM posts
WHERE userid = 'john doe'
AND (blog_title, posted_at) > ('John''s Blog', '2012-01-01');
此查询将返回所有在聚簇排序中具有 “John’s Blog” 作为 blog_tile
和 '2012-01-01' 作为 posted_at
的行之后的行。特别是,只要它们的 blog_title > 'John''s Blog'
,具有 post_at ⇐ '2012-01-01'
的行将被返回。
对于以下示例,情况并非如此
SELECT * FROM posts
WHERE userid = 'john doe'
AND blog_title > 'John''s Blog'
AND posted_at > '2012-01-01';
元组表示法也可用于聚簇列上的 IN
子句
SELECT * FROM posts
WHERE userid = 'john doe'
AND (blog_title, posted_at) IN (('John''s Blog', '2012-01-01'), ('Extreme Chess', '2014-06-01'));
CONTAINS
运算符只能用于集合列(列表、集合和映射)。在映射的情况下,CONTAINS
应用于映射值。CONTAINS KEY
运算符只能用于映射列,并应用于映射键。
分组结果
GROUP BY
选项可以将共享一组列的相同值的选定所有行压缩成一行。
使用 GROUP BY
选项,可以按分区键或聚簇列级别对行进行分组。因此,GROUP BY
选项仅接受按定义顺序排列的主键列作为参数。如果主键列受到相等性限制,则不将其包含在 GROUP BY
子句中。
聚合函数将为每个组生成一个单独的值。如果没有指定 GROUP BY
子句,聚合函数将为所有行生成一个单一值。
如果在包含 GROUP BY
的语句中选择一个列而没有聚合函数,则将返回每个组中遇到的第一个值。
排序结果
ORDER BY
子句选择返回结果的顺序。参数是一个列名列表,以及每列的顺序(ASC
表示升序,DESC
表示降序,可能的排序受表上定义的聚簇排序限制
-
如果表已定义为没有任何特定的
CLUSTERING ORDER
,则顺序按聚簇列定义,或反之 -
否则,顺序由
CLUSTERING ORDER
选项及其反向定义。
允许过滤
默认情况下,CQL 仅允许不涉及对所有分区的完整扫描的选择查询。如果扫描了所有分区,则返回结果可能会遇到与表中数据量成正比的显着延迟。ALLOW FILTERING
选项显式执行完整扫描。因此,查询的性能可能不可预测。
例如,考虑以下包含用户资料、出生年份和居住国的表格。出生年份定义了二级索引。
CREATE TABLE users (
username text PRIMARY KEY,
firstname text,
lastname text,
birth_year int,
country text
);
CREATE INDEX ON users(birth_year);
以下查询有效
// All users are returned
SELECT * FROM users;
// All users with a particular birth year are returned
SELECT * FROM users WHERE birth_year = 1981;
在这两种情况下,查询性能与返回的数据量成正比。第一个查询返回所有行,因为选择了所有用户。第二个查询仅返回由二级索引定义的行,这是一个每个节点的实现;结果将取决于集群中的节点数量,并且与存储的数据量成反比。节点数量始终比存储的用户资料数量低几个数量级。这两个查询都可能返回非常大的结果集,但添加 LIMIT
子句可以减少延迟。
以下查询将被拒绝
SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR';
Cassandra 无法保证即使结果很小,也不会扫描大量数据。如果您知道数据集很小,并且性能将是合理的,请添加 ALLOW FILTERING
以允许查询执行
SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR' ALLOW FILTERING;
INSERT
使用 INSERT
语句插入行的數據。
insert_statement::= INSERT INTO table_name ( names_values | json_clause )
[ IF NOT EXISTS ]
[ USING update_parameter ( AND update_parameter )* ]
names_values::= names VALUES tuple_literal
json_clause::= JSON string [ DEFAULT ( NULL | UNSET ) ]
names::= '(' column_name ( ',' column_name )* ')'
例如
INSERT INTO NerdMovies (movie, director, main_actor, year)
VALUES ('Serenity', 'Joss Whedon', 'Nathan Fillion', 2005)
USING TTL 86400;
INSERT INTO NerdMovies JSON '{"movie": "Serenity", "director": "Joss Whedon", "year": 2005}';
INSERT
语句为表中给定行的一个或多个列写入数据。由于行由其 PRIMARY KEY
标识,因此必须至少指定一个列。要插入的列列表必须使用 VALUES
语法提供。使用 JSON
语法时,VALUES
是可选的。有关更多详细信息,请参阅有关 JSON 支持 的部分。INSERT
的所有更新都是原子地且隔离地应用的。
与 SQL 不同,INSERT
默认情况下不会检查行的先前存在。如果之前不存在,则创建该行,否则更新该行。此外,没有办法知道发生了哪种操作。
IF NOT EXISTS
条件可以在行不存在的情况下限制插入。但是,请注意,使用 IF NOT EXISTS
会产生不可忽略的性能成本,因为使用了 Paxos,因此应谨慎使用。
有关 update_parameter
的信息,请参阅 UPDATE 部分。另请注意,INSERT
不支持计数器,而 UPDATE
支持。
UPDATE
使用 UPDATE
语句更新行。
update_statement ::= UPDATE table_name
[ USING update_parameter ( AND update_parameter )* ]
SET assignment( ',' assignment )*
WHERE where_clause
[ IF ( EXISTS | condition ( AND condition)*) ]
update_parameter ::= ( TIMESTAMP | TTL ) ( integer | bind_marker )
assignment: simple_selection'=' term
`| column_name'=' column_name ( '+' | '-' ) term
| column_name'=' list_literal'+' column_name
simple_selection ::= column_name
| column_name '[' term']'
| column_name'.' field_name
condition ::= `simple_selection operator term
例如
UPDATE NerdMovies USING TTL 400
SET director = 'Joss Whedon',
main_actor = 'Nathan Fillion',
year = 2005
WHERE movie = 'Serenity';
UPDATE UserActions
SET total = total + 2
WHERE user = B70DE1D0-9908-4AE3-BE34-5573E5B09F14
AND action = 'click';
UPDATE
语句为表中给定行的一个或多个列写入数据。WHERE
子句用于选择要更新的行,并且必须包含 PRIMARY KEY
的所有列。非主键列使用 SET
关键字设置。在 UPDATE
语句中,同一分区键内的所有更新都是原子地且隔离地应用的。
与 SQL 不同,UPDATE
默认情况下不会检查行的先前存在。如果之前不存在,则创建该行,否则更新该行。此外,没有办法知道发生了哪种操作。
IF
条件可用于在满足特定条件时选择是否更新行。但是,与 IF NOT EXISTS
条件一样,可能会产生不可忽略的性能成本。
关于 SET
赋值
更新参数
UPDATE
和 INSERT
语句支持以下参数
-
TTL
:指定插入值的可选生存时间(以秒为单位)。如果设置,插入的值将在指定时间后自动从数据库中删除。请注意,TTL 涉及插入的值,而不是列本身。这意味着对列的任何后续更新也将重置 TTL(到该更新中指定的任何 TTL)。默认情况下,值永远不会过期。TTL 为 0 等效于没有 TTL。如果表具有 default_time_to_live,则 TTL 为 0 将删除插入或更新值的 TTL。TTL 为null
等效于插入 TTL 为 0 的值。
UPDATE
、INSERT
、DELETE
和 BATCH
语句支持以下参数
-
TIMESTAMP
:设置操作的时间戳。如果未指定,协调器将在语句执行开始时使用当前时间(以微秒为单位)作为时间戳。这通常是一个合适的默认值。
DELETE
删除行或行的一部分使用 DELETE
语句。
delete_statement::= DELETE [ simple_selection ( ',' simple_selection ) ]
FROM table_name
[ USING update_parameter ( AND update_parameter# )* ]
WHERE where_clause
[ IF ( EXISTS | condition ( AND condition)*) ]
例如
DELETE FROM NerdMovies USING TIMESTAMP 1240003134
WHERE movie = 'Serenity';
DELETE phone FROM Users
WHERE userid IN (C73DE1D3-AF08-40F3-B124-3FF3E5109F22, B70DE1D0-9908-4AE3-BE34-5573E5B09F14);
DELETE
语句删除列和行。如果在 DELETE
关键字之后直接提供列名,则仅从 WHERE
子句指示的行中删除这些列。否则,将删除整个行。
WHERE
子句指定要删除哪些行。可以使用 IN
运算符删除多行。可以使用不等式运算符(如 >=
)删除行范围。
DELETE
支持 TIMESTAMP
选项,其语义与 更新 中的语义相同。
在 DELETE
语句中,同一分区键内的所有删除都是原子地且隔离地应用的。
DELETE
操作可以通过使用 IF
子句来实现条件,类似于 UPDATE
和 INSERT
语句。但是,与 INSERT
和 UPDATE
语句一样,这会产生不可忽略的性能成本,因为使用了 Paxos,因此应谨慎使用。
BATCH
通过 BATCH
语句将多个 INSERT
、UPDATE
和 DELETE
分组,可以在单个语句中执行。
batch_statement ::= BEGIN [ UNLOGGED | COUNTER ] BATCH
[ USING update_parameter( AND update_parameter)* ]
modification_statement ( ';' modification_statement )*
APPLY BATCH
modification_statement ::= insert_statement | update_statement | delete_statement
例如
BEGIN BATCH
INSERT INTO users (userid, password, name) VALUES ('user2', 'ch@ngem3b', 'second user');
UPDATE users SET password = 'ps22dhds' WHERE userid = 'user3';
INSERT INTO users (userid, password) VALUES ('user4', 'ch@ngem3c');
DELETE name FROM users WHERE userid = 'user1';
APPLY BATCH;
BATCH
语句将多个修改语句(插入/更新和删除)分组到单个语句中。它有几个目的
-
在对多个更新进行批处理时,它可以节省客户端和服务器之间(以及有时服务器协调器和副本之间)的网络往返次数。
-
属于给定分区键的
BATCH
中的所有更新都以隔离方式执行。 -
默认情况下,批次中的所有操作都以日志方式执行,以确保所有变异最终完成(或者没有一个会完成)。有关更多详细信息,请参阅有关 UNLOGGED 批次 的说明。
请注意
-
BATCH
语句只能包含UPDATE
、INSERT
和DELETE
语句(例如,不能包含其他批次)。 -
批次不是 SQL 事务的完全模拟。
-
如果未为每个操作指定时间戳,则所有操作都将使用相同的时间戳应用(要么是自动生成的时间戳,要么是批次级别提供的时间戳)。由于 Cassandra 在 时间戳绑定 情况下冲突解决过程,操作可能以与它们在
BATCH
语句中列出的顺序不同的顺序应用。要强制执行特定的操作顺序,您必须指定每个操作的时间戳。 -
对单个分区的 LOGGED 批次将作为优化转换为 UNLOGGED 批次。