Small. Fast. Reliable.
Choose any three.
行值

1.定义

“值”是单个数字,字符串,BLOB或NULL。有时使用限定名称“标量值”来强调仅涉及一个数量。

“行值”是两个或多个标量值的有序列表。换句话说,“行值”是向量或元组。

行值的“大小”是该行值包含的标量值的数量。行值的大小始终至少为2。只有一列的行值只是一个标量值。没有列的行值是语法错误。

2.语法

SQLite允许以两种方式表示行值:

  1. 用括号括起来的逗号分隔的标量值列表。
  2. 具有两个或更多结果列的子查询表达式。

SQLite可以在两个上下文中使用行值:

  1. 可以使用运算符<,<=,>,> =,=,<>,IS,IS NOT,IN,IN,NOT IN,BETWEEN或CASE比较具有相同大小的两个行值。
  2. UPDATE语句中,可以将列名列表设置为相同大小的行值。

下面的示例说明了行值的语法以及可以使用行值的环境。

2.1。行值比较

通过从左到右查看组成标量值来比较两行值。NULL表示“未知”。如果可以通过用替代值代替组成NULL来使结果为真或假,则比较的总体结果为NULL。以下查询演示了一些行值比较:

选择
  (1,2,3)=(1,2,3)-1
  (1,2,3)=(1,NULL,3),-NULL
  (1,2,3)=(1,NULL,4),-0
  (1,2,3)<(2,3,4),-1
  (1,2,3)<(1,2,4),-1
  (1,2,3)<(1,3,NULL),-1
  (1,2,3)<(1,2,NULL),-NULL
  (1,3,5)<(1,2,NULL),-0
  (1,2,NULL)IS(1,2,NULL); -1

“(1,2,3)=(1,NULL,3)”的结果为NULL,因为如果替换为NULL→2,则结果可能为true;如果替换为NULL→9,则结果为false。“(1,2,3)=(1,NULL,4)”的结果不是NULL,因为没有组成NULL的替换将使表达式为true,因为在第三列中3永远不会等于4。

上一个示例中的任何行值都可以由返回三列的子查询替换,并且将得到相同的答案。例如:

创建表t1(a,b,c);
插入t1(a,b,c)值(1,2,3);
SELECT(1,2,3)=(选择*来自t1); -1

对于行值IN运算符,左侧(以下称“ LHS”)可以是带括号的值列表,也可以是具有多列的子查询。但是右侧(以下简称“ RHS”)必须是子查询表达式。

创建表t2(x,y,z);
插入t2(x,y,z)值(1,2,3),(2,3,4),(1,NULL,5);
选择
   (1,2,3)IN(选择*来自t2),-1
   (7,8,9)IN(SELECT * FROM t2),-0
   (1,3,5)IN(选择*来自t2);  -  空值

2.2。UPDATE语句中的行值

行值也可以在UPDATE语句的SET子句中使用。LHS必须是列名的列表。RHS可以是任何行值。例如:

更新选项卡3 
   SET(a,b,c)=(选择x,y,z
                    从选项卡4
                   在哪里tab4.w = tab3.d)
 tab3.e在55和66之间;

3.行值的示例用法

3.1。滚动窗口查询

假设应用程序要在一个滚动窗口中按姓氏,名字的字母顺序显示联系人列表,一次只能显示7个联系人。将滚动窗口初始化为前7个条目很容易:

选择*从联系人
 ORDER BY姓氏,名字
 LIMIT 7;

当用户向下滚动时,应用程序需要找到第二组7个条目。一种方法是使用OFFSET子句:

选择*从联系人
 ORDER BY姓氏,名字
 LIMIT 7 OFFSET 7;

偏移量给出正确的答案。但是,偏移需要的时间与偏移值成正比。“ LIMIT x OFFSET y”真正发生的是,SQLite将查询计算为“ LIMIT x + y”,并丢弃了前y个值,而没有将它们返回给应用程序。因此,当窗口向下滚动到长列表的底部,并且y值越来越大时,连续的偏移量计算将花费越来越多的时间。

一种更有效的方法是记住当前显示的最后一个条目,然后在WHERE子句中使用行值比较:

选择*从联系人
 哪里(姓,名)>(?1,?2)
 ORDER BY姓氏,名字
 LIMIT 7;

如果前一个屏幕的底部行上的姓和名绑定到?1和?2,则上面的查询将计算接下来的7行。而且,假设有一个合适的索引,它会非常高效地执行-比OFFSET效率要高得多。

3.2。比较存储为单独字段的日期

在数据库表中存储日期的通常方法是作为单个字段,如UNIX时间戳,朱利安日数或ISO-8601日期字符串。但是某些应用程序商店的日期分别作为年,月和日的三个字段。

创建表信息(
  年INT,-4位数字的年份
  INT月,-1到12
  第INT天,-1到31
  other_stuff BLOB-等等等等
);

以这种方式存储日期时,行值比较提供了一种比较日期的便捷方法:

选择*从信息
 (2015,9,12)和(2016,9,12)之间的(年,月,日);

3.3。针对多列键进行搜索

假设我们想知道任何产品的订单号,产品编号和数量,其中产品编号和数量与订单编号365中任何项目的产品编号和数量相匹配:

SELECT ordid,prodid,qty
  来自项目
 WHERE(prodid,qty)IN(SELECT prodid,qty)
                           来自项目
                          在哪里ordid = 365);

上面的查询可以重写为联接,而无需使用行值:

选择t1.ordid,t1.prodid,t1.qty
  从项目AS t1,项目AS t2
 在哪里t1.prodid = t2.prodid
   AND t1.qty = t2.qty
   AND t2.ordid = 365;

因为可以在不使用行值的情况下编写相同的查询,所以行值不提供新功能。但是,许多开发人员表示,行值格式更易于读取,写入和调试。

即使采用JOIN形式,也可以通过使用行值来使查询更清晰:

选择t1.ordid,t1.prodid,t1.qty
  从项目AS t1,项目AS t2
 其中(t1.prodid,t1.qty)=(t2.prodid,t2.qty)
   AND t2.ordid = 365;

此后一个查询生成与以前的标量公式完全相同的字节码,但使用的语法更清晰,更易于阅读。

3.4。根据查询更新表的多列

行值表示法对于从单个查询的结果更新表的两个或更多列很有用。例如,Fossil版本控制系统的全文本搜索功能 。

在化石全文检索系统,参与的全文检索的文献(wiki页面,门票,检查插件,文档文件等)是由一个名为“ftsdocs”(表跟踪˚F ULL牛逼分机小号目录操作搜索文档ument小号)。将新文档添加到资源库后,不会立即将它们编入索引。索引将推迟到有搜索请求为止。ftsdocs表包含一个“ idxed”字段,如果文档已被索引,则为true;否则为false。

发生搜索请求并首次索引待处理文档时,必须通过将idxed列设置为true并用与搜索有关的信息填充其他几个列来更新ftsdocs表。其他信息是从联接中获得的。查询是这样的:

更新ftsdocs SET
  idxed = 1,
  名称= NULL,
  (label,url,mtime)= 
      (选择printf('%% s上的“签入[%%。16s]”,blob.uuid,
                     datetime(event.mtime)),
              printf('/ timeline?y = ci&c = %%。20s',blob.uuid),
              event.mtime
         FROM事件,blob
        在哪里event.objid = ftsdocs.rid
          AND blob.rid = ftsdocs.rid)
ftsdocs.type ='c'而不是ftsdocs.idxed

(有关 更多详细信息,请参见 源代码此处此处的其他示例 。)

ftsdocs表中的9列中的5列已更新。修改后的两个列“ idxed”和“ name”可以独立于查询进行更新。但是三列“ label”,“ url”和“ mtime”都需要针对“ event”和“ blob”表的联接查询。如果没有行值,等效的UPDATE将要求连接重复三遍,每列更新一次。

3.5。介绍的清晰度

有时,使用行值只会使SQL更易于读写。考虑以下两个UPDATE语句:

UPDATE tab1 SET(a,b)=(b,a);
UPDATE tab1 SET a = b,b = a;

这两个UPDATE语句执行的操作完全相同。(它们生成相同的字节码。)但是第一种形式,即行值形式,似乎更清楚了该语句的意图是交换A和B列中的值。

或者考虑以下相同的查询:

SELECT * FROM tab1其中a =?1和b =?2;
SELECT * FROM tab1 WHERE(a,b)=(?1,?2);

SQL语句再次生成相同的字节码,从而以完全相同的方式执行完全相同的工作。但是,通过将查询参数组合到一个单独的行值中,而不是将它们分散在WHERE子句中,可以使人类更容易阅读第二种形式。

4.向后兼容性

行值已添加到SQLite 版本3.15.0(2016-10-14)。尝试在早期版本的SQLite中使用行值将生成语法错误。