文档下载建议反馈入口

  • 简单查询
  • 分组窗口查询
  • 嵌套查询
  • 关联查询
  • 联合查询

SELECT

时序数据库支持使用 SQL 语句执行简单查询、嵌套查询、关联查询、联合查询和插值查询。

简单查询

SELECT 语句是读取和处理现有数据的主要 SQL 语法。当用作独立语句时,SELECT 子句也称为 SELECT 语句。但是,它也是一个选择子句,可以与其他结构组合以形成更复杂的选择查询。

KWDB 支持通过以下集群参数设置时序数据查询的相关配置:

  • SET CLUSTER SETTING ts.parallel_degree = <value>:设置并行查询数目。
  • SET CLUSTER SETTING sql.auto_limit.quantity = <value>:配置 SQL 查询结果的返回行数。
  • SET CLUSTER SETTING ts.ordered_table.enabled:当用户未使用 ORDER BY 子句指定排序时,配置是否按照数据写入的时间戳逆序返回查询结果。当设置为 true 时,对于单设备查询,按照数据写入的时间戳逆序返回查询结果。对于多设备查询,先转换成单设备查询,然后再合并所有的数据。

更多参数设置,参见集群实时参数

KWDB 支持在查询中对列类型为时间戳、时间戳常量以及结果类型为时间戳的函数和表达式按最高精度进行加减运算并返回运算结果。运算结果支持使用大于号(>)、小于号(<)、等号(=)、大于等于号(>=)、小于等于号(<=)进行比较。运算中可以包含 interval 常量、其他时间戳列以及结果类型为 interval、timestamp 或 timstamptz 的函数和表达式。如果运算符两边均为 timestamp 或 timestamptz 类型,则只支持减法运算,差值对应的纳秒数不得超过 INT64 范围,对应的天数不得超过 106751 天。超出范围时,计算结果将取决于实际处理引擎,可能是正确的结果,也可能为 106751 days 23:47:16.854776

加减运算中,interval 常量支持的单位包括纳秒(ns)、微秒(us)、毫秒(ms)、秒(s)、分(m)、小时(h)、天(d)、周(w)、月(mon)、年(y)。目前,KWDB 不支持复合时间格式,如 1d1h

毫秒、秒、分、小时的取值范围受纳秒最大值(INT64)范围限制。下表列出具体支持的取值范围:

单位取值范围
纳秒(ns)[0, 9,214,646,400,000,000,000]
微秒(us)[-62,167,219,200,000, 31,556,995,200,000]
毫秒(ms)[-9,223,372,036,854, 9,223,372,036,854]
秒(s)[-9,223,372,036, 9,223,372,036]
分(m)[-153,722,867, 153,722,867]
小时(h)[-2,562,047, 2,562,047]

天、周、月、年的取值范围受加减计算结果的限制。计算结果对应的毫秒数不得超过 INT64 范围。

说明

时间加减表达式支持出现在以下位置:

  • SELECT 列表:例如 SELECT ts+1h FROM table1; 将返回表中时间戳列加上 1 小时后的结果。
  • WHERE 子句:例如 SELECT * FROM table1 WHERE ts+1h > now(); 将返回表中时间戳列加上 1 小时后大于当前时间的数据。
  • ORDER BY 子句:例如 SELECT * FROM table1 ORDER BY ts+1h; 将按时间戳列加上 1 小时后的值进行排序。
  • HAVING 子句:例如 SELECT MAX(ts) FROM table1 GROUP BY ts HAVING ts+1h > now(); 将筛选出满足条件的分组结果。
  • 参数类型为 timestamp 的函数调用:例如 SELECT CAST(ts+1h AS timestamp) FROM table1; 可以将时间戳列加上 1 小时后的结果转换为 timestamp 类型。
  • 使用比较运算符的表示连接条件:例如 SELECT * FROM table1,table2 WHERE table1.ts+1h > table2.ts; 表示在连接两个表时使用时间加减条件。

所需权限

用户拥有目标表的 SELECT 权限。

语法格式

  • select_clause

  • a_expr

  • target_elem

  • alias_clause

  • as_of_clause

参数说明

参数说明
order_by_clauseORDER BY 子句由一个或多个排序规范组成,每个规范可以是标量表达式。系统通过给定的排序规范对结果集进行排序,可以指定 ASC(升序,默认)或 DESC(降序)关键字来控制排序顺序。
limit_clauseLIMIT 子句指定返回结果的最大行数。例如,LIMIT 10 表示限制查询结果最多为 10 行。支持设置为 LIMIT ALL,表示返回所有行。KWDB 也支持使用 sql.auto_limit.quantity 集群参数配置 SQL 查询结果的返回行数。但是,Limit 子句的优先级大于 sql.auto_limit.quantity 集群参数。
offset_clauseOFFSET 子句用于跳过前面的偏移量行数。OFFSET 子句通常与 LIMIT 组合使用,通过限制结果的数量,实现分页显示结果,避免一次性检索所有数据。
DISTINCT当使用 DISTINCT 关键字时,系统删除返回结果中重复的行。
target_elemtarget_elem 可以是标量表达式,也可以是星号(*)。
- 当 target_elem 是标量表达式时,系统使用标量表达式计算每个结果行的值,然后将计算结果作为结果集中的一列返回。
- 当 target_elem 为星号(*)时,系统自动从 FROM 子句中检索所有列。如果 target_elem 包含聚合函数,可以使用 GROUP BY 子句进一步控制聚合。
alias_clause别名子句,用于为表名或子查询结果集指定别名,使查询更易读和易于理解。
as_of_clause用于检索指定时间点的数据。
说明
由于系统时间返回的是历史数据,读取的结果可能会过时。
WHEREWHERE 子句用于指定过滤条件,筛选出符合条件的行。格式为 WHERE <column> <operator> <value>,其中 <operator> 支持 =<><<=>>=LIKE 操作符。WHERE 语句只检索表达式返回值为 TRUE 的行。列可以是数据列或标签列。
GROUP BYGROUP BY 子句根据表达式或分组窗口函数将数据集划分成小组,然后对这些小组进行数据处理。聚合查询与 GROUP BY 连用时,应避免 GROUP BY 后的结果集行数过大。关于分组窗口函数的详细信息,参见分组窗口查询
HAVINGWHERE 关键字无法与聚合函数一起使用时,HAVING 子句可以用来筛选分组后的各组数据。通常情况下,HAVING 子句与 GROUP BY 子句联用,只检索 a_expr 表达式返回值为 TRUE 的聚合函数组。a_expr 必须是使用聚合函数返回布尔值的标量表达式(例如 <聚合函数> = <value>)。HAVING 子句的作用类似于 WHERE 子句,但适用于聚合函数。

语法示例

  • 查询时序表的数据。

    以下示例假设经创建 ts_db 数据库。以下示例查询 t1 时序表的数据。

    -- 1. 创建 t1 时序表并写入数据。
    
    CREATE TABLE ts_db.t1(ts timestamp not null,a int, b int) tags(tag1 int not null, tag2 int) primary tags(tag1);
    CREATE TABLE
    
    -- 2. 向表中写入数据。
    
    INSERT INTO ts_db.t1 VALUES(now(),11,11,33,44);
    INSERT INTO ts_db.t1 VALUES(now(),22,22,33,44);
    INSERT INTO ts_db.t1 VALUES(now(),11,33,33,44);
    INSERT INTO ts_db.t1 VALUES(now(),22,44,33,44);
    INSERT INTO ts_db.t1 VALUES(now(),33,55,44,44);
    INSERT INTO ts_db.t1 VALUES(now(),22,44,44,44);
    INSERT INTO ts_db.t1 VALUES(now(),33,44,55,44);
    INSERT INTO ts_db.t1 VALUES(now(),null,null,66,66);
    INSERT INTO ts_db.t1 VALUES(now(),null,null,66,77);
    
    -- 3. 查看表的内容。
    
    SELECT * FROM t1;
                  ts               |  a   |  b   | tag1 | tag2
    --------------------------------+------+------+------+-------
      2024-02-26 01:28:28.867+00:00 |   11 |   11 |   33 |   44
      2024-02-26 01:28:28.874+00:00 |   22 |   22 |   33 |   44
      2024-02-26 01:28:28.877+00:00 |   11 |   33 |   33 |   44
      2024-02-26 01:28:28.88+00:00  |   22 |   44 |   33 |   44
      2024-02-26 01:28:28.883+00:00 |   33 |   55 |   44 |   44
      2024-02-26 01:28:28.885+00:00 |   22 |   44 |   44 |   44
      2024-02-26 01:28:28.888+00:00 |   33 |   44 |   55 |   44
      2024-02-26 01:28:28.89+00:00  | NULL | NULL |   66 |   66
      2024-02-26 01:28:28.893+00:00 | NULL | NULL |   66 |   66
    (9 rows)
    
  • 查询指定的数据列。

    以下示例查询 t1 表的 a 列并进行求和。

    SELECT sum(a) FROM ts_db.t1;
      sum
    -------
      154
    (1 row)
    
  • 去重查询。

    以下示例对 t1 表的 a 列进行去重查询。

    SELECT DISTINCT a FROM ts_db.t1;
      a
    --------
        11
        22
        33
      NULL
    (4 rows)
    
  • 使用 WHERE 语句过滤标签列。

    以下示例使用 WHERE 语句过滤 t1 表的 a 列。

    SELECT tag1 FROM ts_db.t1 WHERE a =11;
      tag1
    --------
        33
        33
    (2 rows)
    
  • 使用 GROUP BYORDER BY 语句对数据列进行分类和排序。

    以下示例使用 GROUP BY 语句对 t1 表的 a 列进行分类和排序。

    SELECT a, max(b) FROM ts_db.t1 GROUP BY a ORDER BY a;
      a   | max
    -------+-------
      NULL | NULL
        11 |   33
        22 |   44
        33 |   55
    (4 rows)
    

分组窗口查询

KWDB 支持基于特定条件(如时间间隔、数据行数或状态信息等)对数据进行分组并进行聚合查询。

说明

  • 目前,分组窗口查询必须与 GROUP BY ⼦句搭配使⽤,GROUP BY 子句中支持指定表的主标签。
  • 分组窗口查询目前仅支持单表查询,不支持嵌套查询、关联查询、联合查询和其他分组条件。

具体支持的分组窗口函数如下:

  • 计数窗口:基于固定的数据行数划分窗口,数据按时间戳排序后,划分为指定行数的窗口。如果数据总行数无法整除指定行数,最后一个窗口将包含少于指定行数的数据。

    以下示例中,数据按每 3 行为一组,分为 2 个窗口。

    KWDB 支持用户设置滑动偏移行数,以指定两个窗口起始点间的行数间隔,控制窗口的重叠程度。第⼀个窗⼝为 [1, 1+row_limit), 第⼆个窗⼝为[1+sliding_rows, 1+sliding_rows+row_limit)

    以下示例中,固定数据行数为 3,滑动偏移为 2 行,将数据划分为 3 个窗口,其中第一个窗口为第 [1,4)行数据,第二个窗口为第 [3,6)行数据。

  • 事件窗口:根据开始和结束条件划分窗口。窗口在数据满足开始条件时开启,在满足结束条件时关闭。开始和结束条件可以是任意表达式,并且可以涉及不同的列。

    一条数据同时满足开始和结束条件,且当前不在任何窗口内时,这条数据将构成一个新窗口。如果数据满足开始条件后窗口打开,但后续数据无法满足结束条件,则该窗口无法关闭。这部分数据不构成完整窗口,且不会输出。

    以下示例中,开始条件为速度小于 40,结束条件为车道号为 2,符合条件的数据被划分为 2 个窗口。其中,第五行数据同时满足开始和结束条件,构成一个完整的窗口,最后一行数据只满足开始条件,未满足结束条件,因此不构成完整窗口,不会输出。

  • 会话窗口:根据时间戳列和指定的最大连续时间间隔判断数据是否属于同一窗口。如果相邻数据的时间间隔超过允许的最大时间间隔,将被分配到不同窗口。

    以下示例中,最大时间间隔设置为 5 分钟,第二行数据与第三行数据,以及第五行与第六行数据的时间间隔均超过 5 分钟,数据因此被划分为 3 个窗口。

  • 状态窗口:按设备状态划分窗口,状态相同的数据会分配到同一窗口中。状态变化时窗口结束。

    以下示例中,数据按车道号作为设备状态进行分组,1 号、2 号和 3 号车道上的车辆数据分属于 3 个窗口,并按时间戳进行排序。

  • 时间窗口:基于时间戳列和指定的时间间隔划分窗口。

    数据按时间戳排序后,系统会根据第一个时间戳和给定的时间间隔计算窗口的起始时间(t0)。t0 为第一个时间戳基于时间间隔向下取整的结果,即调整为小于或等于该时间戳的最大时间点。例如,如果第一个时间戳是 2025-1-10 12:01:00.000,时间间隔是 10 分钟,则 t0 会向下取整为 2025-1-10 12:00:00.000。如果时间间隔是 1 年,则 t0 会向下取整为 2025-1-1 00:00:00.000

    以下示例中,时间间隔设置为 10 分钟,数据被划分为 3 个窗口:[2025-1-10 12:00:00.000, 2025-1-10 12:10:00.000), [2025-1-10 12:10:00.000, 2025-1-10 12:20:00.000)[2025-1-10 12:20:00.000, 2025-1-10 12:30:00.000)

    KWDB 支持用户设置滑动偏移间隔,控制窗口的重叠程度,此时,t0 的计算方式为:首先基于第一个时间戳和滑动偏移向上取整,即调整为大于或等于该时间戳的最小时间点,然后减去时间间隔的值。例如,如果第一个时间戳是 2025-1-10 12:01:00.000,时间间隔是 10 分钟,偏移间隔为 5 分钟,则 t0 将根据滑动偏移向上取整后调整为 2025-1-10 12:05:00.000,减去时间间隔后为 2025-1-10 11:55:00.000。如果时间间隔是 1 年,偏移间隔为 1 天,则 t02024-1-11 00:00:00.000

    以下示例中,时间间隔设置为 10 分钟,滑动偏移为 5 分钟,数据被划分为 6 个窗口,分别为 [2025-1-10 11:55:00.000, 2025-1-10 12:05:00.000)[2025-1-10 12:00:00.000, 2025-1-10 12:10:00.000)[2025-1-10 12:05:00.000, 2025-1-10 12:15:00.000)[2025-1-10 12:10:00.000, 2025-1-10 12:20:00.000)[2025-1-10 12:15:00.000, 2025-1-10 12:25:00.000)[2025-1-10 12:20:00.000, 2025-1-10 12:25:00.000)

所需权限

用户拥有目标表的 SELECT 权限。

语法格式

参数说明

参数说明
primary_tag_list可选参数,指定时序表的主标签。
row_limit计数窗口中,指定分组的数据行数。
sliding_rows计数窗口中,指定相邻窗口起始点间的差距,以控制窗口的重叠程度,值必须小于或等于 row_limit
start_condition事件窗口中,指定窗口开始的条件,可以是任意表达式,也可以涉及不同的列。
end_condition事件窗口中,指定窗口结束的条件,可以是任意表达式,也可以涉及不同的列。
ts_column会话窗口和时间窗口中,指定第一列时间戳列。
session_threshold会话窗口中,指定最大连续时间间隔。如果两条相邻数据的时间间隔超过会话允许的最大时间间隔时,则数据分属于不同窗口。支持的单位包括 s(秒)、m (分)、h (时)、d (天)和 w (周) ,不支持复合时间格式,例如 1m2s
column_name状态窗口中,指定表的数据列或标签列。
- 当指定列为数据列时,其数据类型必须为整数、布尔值或除 GEOMETRY 之外的字符类型。
- 当指定列为标签列时,其数据类型必须为整数、布尔值或除 GEOMETRY 和 NVARCHAR 之外的字符类型。
case_when_expr状态窗口中,表示满足指定条件后状态开始或结束的表达式,例如,STATE_WINDOW (CASE WHEN voltage >= 225 AND voltage <= 235 THEN 1 ELSE 0 END) 表示当电压在 225 至 235 之间时,状态为 1,否则为 0。
interval时间窗口中,指定时间间隔, 单位包括毫秒、秒、分、小时、天、周、月、年,不支持复合时间格式,如 1d1h。时间间隔必须不小于 10 毫秒。
各时间单位支持的输入格式如下所示:
- 毫秒:msmsecmsecsmillisecondmilliseconds
- 秒:ssecsecssecondseconds
- 分:mminminsminuteminutes
- 小时:hhrhrshourhours
- 天:ddaydays
- 周:wweekweeks
- 月:monmonsmonthmonths
- 年:yyryrsyearyears
sliding_interval时间窗口函数中,指定滑动偏移间隔。支持的单位包括毫秒、秒、分、小时、天、周,不支持复合时间格式,如 1d1h
各时间单位支持的输入格式如下所示:
- 毫秒:msmsecmsecsmillisecondmilliseconds
- 秒:ssecsecssecondseconds
- 分:mminminsminuteminutes
- 小时:hhrhrshourhours
- 天:ddaydays
- 周:wweekweeks
注意:时间间隔与滑动偏移间隔不宜相差太大,否则可能会影响查询性能,如果窗口过多,还会导致内存不足。

语法示例

以下示例假设已创建时序表 vehicles 并写入了数据:

-- 创建时序表
create table vehicles (ts timestamp not null, vehicle_id varchar, speed float, lane_no int) tags (location int not null) primary tags (location);


-- 写入数据
insert into vehicles values ('2025-1-10 12:01:00.000', 'A11111',35,1,1),('2025-1-10 12:02:00.000','A22222',30,1,1),('2025-1-10
12:09:00.000','A33333',35,2,1),('2025-1-10 12:11:00.000','A44444',40,3,1),('2025-1-10 12:12:00.000','A55555',25,2,1),('2025-1-10 12:21:00.000','A66666',35,1,1);

-- 查询表数据
 select * from vehicles;
             ts             | vehicle_id | speed | lane_no | location
----------------------------+------------+-------+---------+-----------
  2025-01-10 12:01:00+00:00 | A11111     |    35 |       1 |        1
  2025-01-10 12:02:00+00:00 | A22222     |    30 |       1 |        1
  2025-01-10 12:09:00+00:00 | A33333     |    35 |       2 |        1
  2025-01-10 12:11:00+00:00 | A44444     |    40 |       3 |        1
  2025-01-10 12:12:00+00:00 | A55555     |    25 |       2 |        1
  2025-01-10 12:21:00+00:00 | A66666     |    35 |       1 |        1
(6 rows)

  • 使用计数窗口进行聚合查询,窗口间不重叠

    以下示例使用计数窗口将每 3 条记录划分为一个窗口,窗口间不重叠。count(ts) 计算每个窗口中的记录数,avg(speed) 计算每个窗口内的平均速度。

    SELECT count(ts) as records, avg(speed) as avg_speed FROM vehicles GROUP BY COUNT_WINDOW (3);
    
  • 使用计数窗口进行聚合查询,窗口间有重叠

    以下示例使用计数窗口将每 3 条记录划分为一个窗口,每个窗口起始点相差 2 行,形成重叠。count(ts) 计算每个窗口中的记录数,avg(speed) 计算每个窗口内的平均速度。

    SELECT count(ts) as records, avg(speed) as avg_speed FROM vehicles GROUP BY COUNT_WINDOW (3,2);
    
  • 使用事件窗口进行聚合查询

    以下示例使用开始条件 speed<40,结束条件 lane_no=2 来划分数据窗口,count(ts) 计算每个窗口中的记录数,avg(speed) 计算每个窗口内的平均速度。

    SELECT count(ts) as records, avg(speed) as avg_speed FROM vehicles GROUP BY EVENT_WINDOW (speed<40,lane_no=2);
    
  • 使用会话窗口进行聚合查询

    以下示例基于时间戳列 ts 来划分数据窗口。相邻数据的最大时间间隔为 5 分钟,如果时间间隔超过 5 分钟,则开启新的会话窗口。count(ts) 计算每个窗口的记录数,avg(speed) 计算每个窗口内的平均速度。

    SELECT count(ts) as records, avg(speed) as avg_speed FROM vehicles GROUP BY SESSION_WINDOW (ts,'5m');
    
  • 使用状态窗口进行聚合查询

    以下示例使用 lane_no 划分数据窗口,每个不同的 lane_no 值对应一个单独的窗口。count(ts) 计算每个窗口中的记录数,avg(speed) 计算每个窗口内的平均速度。

    SELECT count(ts) as records, avg(speed) as avg_speed FROM vehicles GROUP BY STATE_WINDOW (lane_no);
    
  • 使用时间窗口进行聚合查询,各窗口间不重叠

    以下示例根据时间戳列 ts 划分数据窗口,每个窗口的时间跨度为 10 分钟,且各窗口间不重叠。count(ts) 计算每个窗口内的记录数,avg(speed) 计算每个窗口内的平均速度。

    SELECT count(ts) as records, avg(speed) as avg_speed FROM vehicles GROUP BY TIME_WINDOW (ts,'10m');
    
  • 使用时间窗口进行聚合查询,各窗口间有重叠

    以下示例根据时间戳列 ts 划分数据窗口,每个窗口的时间跨度为 10 分钟,窗口间起始时间相差 5 分钟。count(ts) 计算每个窗口内的记录数,avg(speed) 计算每个窗口内的平均速度。

    SELECT count(ts) as records, avg(speed) as avg_speed FROM vehicles GROUP BY TIME_WINDOW (ts,'10m','5m');
    

嵌套查询

嵌套查询指在一个 SQL 查询中嵌套另一个完整的 SQL 查询,从而实现更复杂的数据检索。

KWDB 支持以下嵌套查询:

  • 相关子查询(Correlated Subquery):内部查询依赖于外部查询的结果,每次外部查询都触发内部查询。
  • 非相关子查询(Non-Correlated Subquery):内部查询独立于外部查询,只执行一次内部查询并返回固定的结果。
  • 相关投影子查询(Correlated Scalar Subquery):内部查询依赖于外部查询的结果,并且只返回一个单一的值作为外部查询的结果。
  • 非相关投影子查询(Non-Correlated Scalar Subquery):内部查询独立于外部查询,并且只返回一个单一的值作为外部查询的结果。
  • FROM 子查询:将一个完整的 SQL 查询嵌套在另一个查询的 FROM 子句中,作为临时表格使用。

所需权限

用户拥有目标表的 SELECT 权限。

语法格式

参数说明

语法示例

  • 非相关子查询

    SELECT e1 FROM ts_stable1 WHERE e1 = (SELECT avg (e1) FROM t1.stable);
    e1
    (0 rows)
    
  • 非相关投影子查询

    SELECT first (e1) = (SELECT e1 FROM ts_stable2 limit 1) FROM ts_stable1;
    ?column?
    -------------
    t
    (1 row)
    
  • 相关子查询

    SELECT e1 FROM t1.stable WHERE e1 in (SELECT e1 FROM t1.stable2 WHERE stable.e2=stable2.e2);
    e1
    -----
    1000
    2000
    3000
    2000
    3000
    4000
    3000
    4000
    5000
    (9 rows)
    
  • 相关投影子查询

    SELECT sum(e1) = (SELECT e1 FROM ts_stable2 WHERE ts_stable2.e1=e1) FROM ts_stable1;
    ?column?
    -------------
    t
    (1 row)
    
  • FROM 子查询

    SELECT avg (a) FROM (SELECT e1 as a FROM t1.ts_stable1);
    avg
    ------
    1000
    (1 row)
    

关联查询

关联查询(JOIN QUERY)从多个表中获取相关联的数据,将其联接成一个结果集,从而得到更丰富的信息。KWDB 支持以下关联类型:

  • 内连接(INNER JOIN)
  • 左连接(LEFT JOIN)
  • 右连接(RIGHT JOIN)
  • 全连接( FULL JOIN)

说明

使用 FULL JOIN 时,避免在连接条件中使用子查询。

所需权限

用户拥有目标表的 SELECT 权限。

语法格式

  • opt_join_hint

参数说明

参数说明
joined_table连接表达式。
table_ref表的表达式。
opt_join_hint可选项,连接提示。
a_exprON 连接条件的标量表达式。
nameUSING 连接条件的列名。

语法示例

SELECT ts_table1.e1, ts_table2.e1 FROM ts_table1 LEFT JOIN ts_table2 ON ts_table1.e1 = ts_table2.e1;
e1  | e1
----+-----
1000|1000
(1 row)

联合查询

联合查询(UNION QUERY)将具有相同列结构的多个查询结果组合成一个结果表。

所需权限

用户拥有目标表的 SELECT 权限。

语法格式

参数说明

参数说明
UNION将两个或多个查询结果合并成一个结果集,并自动去除重复的行。
INTERSECT将两个查询结果的交集作为最终结果集。
EXCEPT返回只存在于第一个查询结果中而不存在于第二个查询结果中的行。

语法示例

SELECT e1 FROM t1.ts_stable1 UNION SELECT e1 FROM t1.ts_stable2;
e1    
------
1000
(1 row)