本节介绍CQL支持插入,更新,删除和查询数据的语句。
查询(SELECT)
从数据查询数据是使用SELECT语句完成的:
select_statement ::= SELECT [ JSON | DISTINCT ] (select_clause
| '*' ) FROMtable_name
[ WHEREwhere_clause
] [ GROUP BYgroup_by_clause
] [ ORDER BYordering_clause
] [ PER PARTITION LIMIT (integer
|bind_marker
) ] [ LIMIT (integer
|bind_marker
) ] [ ALLOW FILTERING ] select_clause ::=selector
[ ASidentifier
] ( ','selector
[ ASidentifier
] ) selector ::=column_name
|term
| CAST '('selector
AScql_type
')' |function_name
'(' [selector
( ','selector
)* ] ')' | COUNT '(' '*' ')' where_clause ::=relation
( ANDrelation
)* 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语句读取表中一个或多个行的一个或多个列。它返回与请求匹配的行的结果集,其中每行包含与查询对应的选择的值。此外,包括聚合函数的函数可以应用于结果。
SELECT语句至少包含选择子句和选择所在的表的名称(请注意,CQL没有连接查询和子查询,因此select语句只适用于单个表)。在大多数情况下,select也将有一个where子句,并且它可以有可选地具有额外的子句来排序或限制结果。最后,如果提供了ALLOW FILTERING标志,则可以允许需要过滤的查询。
选择子句(Selection clause)
选择子句确定在结果集中需要查询和返回哪些列,以及在返回之前应用于此结果的任何转换。它由逗号分隔的选择器列表或通配符(*)组成,以选择表中定义的所有列。
选择器
选择器可以是以下之一:
- 所选表的列名,以检索该列的值。
- 通常使用嵌套在其他选择器(如函数)中的term(如果直接选择term,则结果集的相应列将简单地具有返回的每一行的该term的值)。
- 一个转换,允许将嵌套选择器转换为(兼容)类型。
- 函数调用,其中参数是选择器自身。
- COUNT函数特殊调用COUNT(*),计数所有非空结果。
别名(Aliases)
每个顶级选择器也可以使用别名(使用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
注意:目前,在使用它们的语句中不能识别别名(不是在WHERE子句中,而是在ORDER BY子句中,...)。您必须改用原始列名称。
WRITETIME和TTL功能
查询操作支持两个特殊功能(其他操作中不支持):WRITETIME和TTL。两个函数只需要一个参数,该参数必须是列名(因此,例如TTL(3)无效)。
这些函数允许检索每个列内部存储的元信息,即:
- WRITETIME列的值的时间戳。
- 如果列设置了到期时间TTL(未设置则为空),则列的值代表列的剩余生存时间(以秒为单位)。
WHERE子句(
WHERE
clause)
WHERE子句指定必须查询哪些行。它由作为PRIMARY KEY的一部分的列上的关系和/或具有在其上定义的辅助索引组成。
在查询中不是所有关系都被允许查询。例如,不支持在分区键上的非等关系(其中IN被认为是等同关系,但是看到使用TOKEN方法对分区键执行不相等的查询)。此外,对于给定的分区键,只能按照聚簇列指定的排序连续的进行查询。例如,给定:
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'
当指定关系时,可以在PARTITION KEY列上使用TOKEN函数进行查询。在这种情况下,将基于其PARTITION_KEY的令牌而不是根据值来选择行。密钥的令牌取决于使用中的分区器,并且随机分区器不会产生有意义的顺序。还要注意,排序分区器总是按字节排序令牌值(因此,即使分区键的类型为int,token(-1)> token(0))。 例:
SELECT * FROM posts
WHERE token(userid) > token('tom') AND token(userid) < token('bob')
也可以使用元组符号将关系中的聚簇列组合在一起。 例如:
SELECT * FROM posts
WHERE userid = 'john doe'
AND (blog_title, posted_at) > ('John''s Blog', '2012-01-01')
上诉查询将请求在具有“John's Blog”的行作为blog_tile的排序之后排序的所有行以及在聚簇顺序中的用于posted_at的“2012-01-01”(感觉这句翻译有点怪,原文:will request all rows that sorts after the one having “John’s Blog” asblog_tile
and ‘2012-01-01’ forposted_at
in the clustering order.)。需要特别注意的是只要是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运算符只能在映射列上使用,并适用于map键。
分组结果(Grouping results)
GROUP BY选项允许将一组列中共享相同值的所有选定行压缩为单个行。
使用GROUP BY选项,只能在分区键级别或在集群列级别对行进行分组。因此,GROUP BY选项只接受主键顺序中的主键列名作为参数。如果主键列受到等式限制的限制,则不需要在GROUP BY子句中出现。
聚合函数将为每个组生成单独的值。如果未指定GROUP BY子句,则聚合函数将为所有行生成单个值。
如果选择没有聚合函数的列,则在具有GROUP BY的语句中,将返回每个组中遇到的第一个值。
排序结果(Ordering results)
ORDER BY子句允许选择返回结果的顺序。它使用列名称列表以及列的顺序作为参数(ASC用于升序,DESC用于降序,省略相当于ASC的顺序)。目前,可能的顺序受表上定义的聚类顺序的限制:
- 如果表已经定义没有任何特定的聚簇列排序,那么允许的排序是聚簇原本列顺序和相反的那个排序。
- 否则,允许的排序是聚簇列排序“正序”选项和“倒序”选项的顺序。
限制结果(Limiting results)
SELECT语句的LIMIT选项限制查询返回的行数,而PER PARTITIONLIMIT选项限制查询为给定分区返回的行数。请注意,两种类型的限制可以在同一语句中使用。
允许过滤器(Allowing filtering)
默认情况下,CQL只允许不涉及“过滤”服务器端的选择查询,即我们知道的在结果集中返回实时记录读取的查询。可能是因为这些“非过滤”查询的性能是可预测的,即它们的执行时间与由查询返回的数据量(其可以通过LIMIT控制)成比例。
ALLOW FILTERING选项允许显式地允许(一些)需要过滤的查询。使用ALLOW FILTERING的查询因此可能具有不可预测的性能(对于上述定义),即使选择少数记录的查询也可能表现出取决于存储在集群中的数据总量的性能。
例如,考虑下表持有用户个人资料、出生年份(具有次要指数)和居住国家:
CREATE TABLE users (
username text PRIMARY KEY,
firstname text,
lastname text,
birth_year int,
country text
)
CREATE INDEX ON users(birth_year);
那么以下查询有效:
SELECT * FROM users;
SELECT * FROM users WHERE birth_year = 1981;
因为在这两种情况下,Cassandra保证这些查询的性能将与返回的数据量成比例。如果在1981年没有用户出生,则第二查询性能将不取决于存储在数据库中的用户简档的数量(不直接至少:由于次要索引实现考虑,该查询可能仍然取决于数量的节点,这间接地取决于存储的数据量。然而,节点的数量将总是比存储的用户简档的数量低多个数量级)。当然,两个查询在实践中可能返回非常大的结果集,但是返回的数据量总是可以通过添加LIMIT来控制。
但是,以下查询将被拒绝:
SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR';
因为Cassandra不能保证它不必扫描大量的数据,即使这些查询的结果很小。通常,它将扫描1981年出生的用户的所有索引条目,即使只有少数实际来自法国。但是,如果您“知道您在做什么”,则可以使用ALLOW FILTERING强制执行此查询,因此以下查询有效:
SELECT * FROM users WHERE birth_year = 1981 AND country = 'FR' ALLOW FILTERING;
插入(INSERT)
使用INSERT语句完成插入行的数据:
insert_statement ::= INSERT INTOtable_name
(names_values
|json_clause
) [ IF NOT EXISTS ] [ USINGupdate_parameter
( ANDupdate_parameter
)* ] names_values ::=names
VALUEStuple_literal
json_clause ::= JSONstring
[ 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语法时,它们是可选的。
与SQL不同,INSERT不会默认检查行的先前存在:如果之前没有存在,则创建行,否则更新。此外,去了解哪个创建或更新发生是没有意义的。
然而,可以使用IF NOT EXISTS条件仅在插入之前插入行不存在。但请注意,使用IF NOT EXISTS会产生不可忽略的性能成本(内部使用Paxos),因此应谨慎使用。
INSERT的所有更新具有原子性和隔离性。
有关update_parameter的信息,请参阅UPDATE部分。
还要注意,INSERT不支持计数器,而UPDATE支持。
更新(UPDATE)
使用UPDATE语句完成更新行:
update_statement ::= UPDATEtable_name
[ USINGupdate_parameter
( ANDupdate_parameter
)* ] SETassignment
( ','assignment
)* WHEREwhere_clause
[ IF ( EXISTS |condition
( ANDcondition
)*) ] 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关键字设置非主键列。
注意,与SQL不同,UPDATE不会默认检查行的先前存在(除了通过IF,seebelow):如果之前没有存在,则创建行,否则更新。此外,没有办法知道是否发生创建或更新。然而,可以通过IF在某些列上使用条件,在这种情况下,除非满足条件,否则不会更新该行。但是,请注意,使用IF条件会产生不可忽略的性能成本(内部使用Paxos),因此应谨慎使用。
在UPDATE语句中,同一分区键中的所有更新都具有原子性和隔离性。
关于更新分配语句:
c = c + 3用于递增/递减计数器。“=”符号后面的列名称必须与'='符号之前的列名称相同。注意,增量/减量只能在计数器上使用,并且是计数器上允许的唯一更新操作。
id = id + <some-collection>和id [value1] = value2用于集合
id.field = 3用于设置非冻结用户定义类型上字段的值
更新参数(Update parameters)
UPDATE,INSERT(和TIMESTAMP的DELETE和BATCH)语句支持以下参数:
TIMESTAMP
: 设置操作的时间戳。 如果未指定,协调器将在语句执行开始时使用当前时间(以微秒为单位)作为时间戳。 这通常是一个合适的默认值。TTL
: 为插入的值指定可选的生存时间(以秒为单位)。 如果设置,则在指定时间之后,将自动从数据库中删除插入的值。 请注意,TTL涉及插入的值,而不是列本身。 这意味着该列的任何后续更新也将重置TTL(到该更新中指定的任何TTL)。 默认情况下,值永不过期。 TTL为0相当于没有TTL。 如果表具有adefault_time_to_live,则TTL为0将删除插入或更新的值的TTL。TTL为null等同于在TTL为0时插入。
删除(DELETE)
删除行或截取行使用DELETE语句:
delete_statement ::= DELETE [simple_selection
( ','simple_selection
) ] FROMtable_name
[ USINGupdate_parameter
( ANDupdate_parameter
)* ] WHEREwhere_clause
[ IF ( EXISTS |condition
( ANDcondition
)*) ]
例如:
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);
WHERE子句指定要删除的行。通过使用一个IN运算符,可以使用一个语句删除多个行。可以使用不等式运算符(例如> =)删除一系列行。
DELETE支持与UPDATE中相同的语义的TIMESTAMP选项。
在DELETE语句中,同一分区键中的所有删除都具有原子性和隔离性。
DELETE操作可以通过使用IF子句(类似于UPDATE和INSERT状态)进行条件操作。然而,与INSERT和UPDATE语句一样,这将产生不可忽略的性能成本(内部使用Paxos),因此应谨慎使用。
批量操作(BATCH)
多个INSERT,UPDATE和DELETE可以在一个语句中通过一个BATCH语句进行分组来执行:
batch_statement ::= BEGIN [ UNLOGGED | COUNTER ] BATCH [ USINGupdate_parameter
( ANDupdate_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语句中列出的顺序不同。因此要强制特定操作顺序,则必须指定每操作时间戳。
UNLOGGED批处理(UNLOGGED
batches)
默认情况下,Cassandra使用批处理日志来确保批处理中的所有操作最终完成或没有进行操作(注意,操作只在单个分区中隔离)。
当批处理跨多个分区时,批处理原子性会有性能损失。 如果你不想承担这种惩罚,你可以告诉Cassandra使用UNLOGGED选项跳过批处理。 如果使用UNLOGGED选项,失败的批处理可能会在剩余的部分中使用补丁修复。
计数器批处理(COUNTER
batches)
批量计数器更新使用COUNTER选项。 与Cassandra中的其他更新不同,计数器更新不是幂等的。