这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

数据分析

本指南说明了如何在 Vertica 数据库中查询和分析数据。

1 - 查询

查询是一种数据库操作,可从一个或多个表或视图中检索数据。在 Vertica 中,查询是最高级别的 SELECT 语句,嵌套在其他 SQL 语句中的查询称为子查询。

Vertica 的设计目的是运行在其他数据库中运行的相同 SQL 标准查询。但是,Vertica 查询与在其他关系数据库管理系统中使用的查询存在一些区别。

Vertica 事务模型不同于 SQL 标准,它对查询性能会产生深远影响。可以:

  • 对在任何特定日期和时间生成的数据库静态备份运行查询。这样做可避免持有锁或阻止其他数据库操作。

  • 使用标准 SQL 隔离级别子集,并访问用户 会话模式(读取/写入或只读)。

在 Vertica 中,语句是 SQL 查询的主要结构。每个语句以分号结束,您可以编写以分号分隔的多个查询;例如:

=> CREATE TABLE t1( ..., date_col date NOT NULL, ...);
=> CREATE TABLE t2( ..., state VARCHAR NOT NULL, ...);

1.1 - 历史查询

Vertica 可以执行历史查询,这些查询针对在特定时间戳或时期获取的数据库快照执行。历史查询可用于评估并可能恢复已删除但尚未清除的数据。

您可以通过限定包含 AT epoch 子句的 SELECT 语句来指定历史查询,其中 epoch 为以下内容之一:

  • EPOCH LATEST:返回数据直到当前时期(但不包括当前时期)。结果集包括来自最新提交的 DML 事务的数据。

  • EPOCH integer:返回数据直到 integer 指定的时期(包括该指定时期)。

  • TIME 'timestamp':从 timestamp 指定的时期返回数据。

有关 Vertica 如何使用时期的详细信息,请参阅时期

历史查询仅返回指定时期的数据。由于它们不返回最新数据,因此历史查询不包含锁定或阻塞写操作。

查询结果对于事务而言是私有的,并且只在会话时长内有效。无论事务隔离级别如何,查询执行都是相同的。

限制

  • 指定时期或指定时间戳的时期不能小于 Ancient History Mark 时期。

  • Vertica 不支持对临时表运行历史查询。

1.2 - 临时表

您可以使用 CREATE TEMPORARY TABLE 语句实施某些查询,其步骤如下:

  1. 创建一个或多个临时表。

  2. 执行查询,然后将结果集存储在临时表中。

  3. 使用临时表执行主要查询,就好像临时表是 逻辑架构的正常组成部分一样。

有关详细信息,请参阅《SQL 参考手册》中的CREATE TEMPORARY TABLE

1.3 - SQL 查询

所有 DML(数据操作语言)语句都可包含查询。此部分介绍 Vertica 中的部分查询类型,其他详细信息将在后续几节介绍。

简单查询

简单查询包含对一个表执行的查询。以下查询会查找产品表中的产品键和 SKU 编号,处理它只需要很少的工作量。

=> SELECT product_key, sku_number FROM public.product_dimension;
product_key  | sku_number
-------------+-----------
43           | SKU-#129
87           | SKU-#250
42           | SKU-#125
49           | SKU-#154
37           | SKU-#107
36           | SKU-#106
86           | SKU-#248
41           | SKU-#121
88           | SKU-#257
40           | SKU-#120
(10 rows)

表可以包含数组。您可以选择整个数组列、其索引或应用于数组的函数的结果。有关详细信息,请参阅数组和集(集合)

联接

联接使用关系运算符合并两个或两个以上表中的信息。查询的 ON 子句会指定表的合并方式,例如通过将外键与主键相匹配。在以下示例中,查询通过联接商店架构的销售事实表和销售表中的商店键 ID,请求交易量大于 70 的商店的名称。

=> SELECT store_name, COUNT(*) FROM store.store_sales_fact
   JOIN store.store_dimension ON store.store_sales_fact.store_key = store.store_dimension.store_key
   GROUP BY store_name HAVING COUNT(*) > 70 ORDER BY store_name;

 store_name | count
------------+-------
 Store49    |    72
 Store83    |    78
(2 rows)

有关更多详细信息,请参阅联接。另请参阅子查询示例中的“多列子查询”部分。

交叉联接

交叉联接又称为笛卡儿积,它会将一个表中所有记录与另一个表中所有记录联接在一起。当表之间没有联接键来限制记录时,则会产生交叉联接。例如,以下查询返回了供应商表和商店表中供应商和商店名称的所有实例。

=> SELECT vendor_name, store_name FROM public.vendor_dimension
    CROSS JOIN store.store_dimension;
vendor_name         | store_name
--------------------+------------
Deal Warehouse      | Store41
Deal Warehouse      | Store12
Deal Warehouse      | Store46
Deal Warehouse      | Store50
Deal Warehouse      | Store15
Deal Warehouse      | Store48
Deal Warehouse      | Store39
Sundry Wholesale    | Store41
Sundry Wholesale    | Store12
Sundry Wholesale    | Store46
Sundry Wholesale    | Store50
Sundry Wholesale    | Store15
Sundry Wholesale    | Store48
Sundry Wholesale    | Store39
Market Discounters  | Store41
Market Discounters  | Store12
Market Discounters  | Store46
Market Discounters  | Store50
Market Discounters  | Store15
Market Discounters  | Store48
Market Discounters  | Store39
Market Suppliers    | Store41
Market Suppliers    | Store12
Market Suppliers    | Store46
Market Suppliers    | Store50
Market Suppliers    | Store15
Market Suppliers    | Store48
Market Suppliers    | Store39
...                 | ...
(4000 rows)

此示例的输出已截断,因为这个特殊的交叉联接返回了几千行。另请参阅交叉联接

子查询

子查询是一种嵌套在其他查询中的查询。在以下示例中,我们希望列出脂肪含量最高的所有产品。内查询(子查询)向外查询块(包含查询)返回了所有食品产品中脂肪含量最高的产品。然后,外查询使用这些信息返回脂肪含量最高的产品的名称。

=> SELECT product_description, fat_content FROM public.product_dimension
   WHERE fat_content IN
     (SELECT MAX(fat_content) FROM public.product_dimension
      WHERE category_description = 'Food' AND department_description = 'Bakery')
   LIMIT 10;
         product_description         | fat_content
-------------------------------------+-------------
 Brand #59110 hotdog buns            |          90
 Brand #58107 english muffins        |          90
 Brand #57135 english muffins        |          90
 Brand #54870 cinnamon buns          |          90
 Brand #53690 english muffins        |          90
 Brand #53096 bagels                 |          90
 Brand #50678 chocolate chip cookies |          90
 Brand #49269 wheat bread            |          90
 Brand #47156 coffee cake            |          90
 Brand #43844 corn muffins           |          90
(10 rows)

有关详细信息,请参阅子查询

对查询排序

使用 ORDER BY 子句可以对查询返回的行进行排序。

有关查询结果的特殊说明

在不同计算机上运行某些查询得到的结果可能不同,其原因如下:

  • 由于精确度原因,在 FLOAT 类型上分区会返回具有不确定性的结果,尤其当数量接近于另一种类型时,例如输出范围非常小的 RADIANS() 函数的结果。

    如果您必须通过不属于 INTEGER 类型的数据进行分区,要想获得具有确定性的结果,请使用 NUMERIC

  • 大多数分析(和分析聚合,例如作为例外的 MIN()/MAX()/SUM()/COUNT()/AVG())依赖于输入数据的唯一顺序来获取具有确定性的结果。如果分析 window-order 子句无法解析数据中的关系,每次运行查询时结果都不相同。

    例如,在以下查询中,分析 ORDER BY 没有在查询中包含第一列 promotion_key。因此,对于 AVG(RADIANS(cost_dollar_amount)), product_version 的关系,同一个 promotion_key 在分析分区中的位置可能不同,从而产生不同的 NTILE() 编号。因此,DISTINCT 也可能具有不同的结果:

    => SELECT COUNT(*) FROM
          (SELECT DISTINCT SIN(FLOOR(MAX(store.store_sales_fact.promotion_key))),
       NTILE(79) OVER(PARTITION BY AVG (RADIANS
          (store.store_sales_fact.cost_dollar_amount ))
       ORDER BY store.store_sales_fact.product_version)
       FROM store.store_sales_fact
       GROUP BY store.store_sales_fact.product_version,
             store.store_sales_fact.sales_dollar_amount ) AS store;
     count
    -------
      1425
    (1 row)
    

    如果将 MAX(promotion_key) 添加到分析 ORDER BY,则结果在任何计算机上都是相同的:

    => SELECT COUNT(*) FROM (SELECT DISTINCT MAX(store.store_sales_fact.promotion_key),
        NTILE(79) OVER(PARTITION BY MAX(store.store_sales_fact.cost_dollar_amount)
       ORDER BY store.store_sales_fact.product_version,
       MAX(store.store_sales_fact.promotion_key))
       FROM store.store_sales_fact
       GROUP BY store.store_sales_fact.product_version,
         store.store_sales_fact.sales_dollar_amount) AS store;
    

1.4 - 数组和集(集合)

表可以包含集合(数组或集)。数组是允许重复值的元素的有序集合,而集是唯一值的无序集合。

考虑一个订单表,其中包含产品密钥、客户密钥、订单价格和订单日期列,以及一些包含数组。Vertica 中的基本查询结果如下:

=> SELECT * from orders LIMIT 5;
 orderkey | custkey |        prodkey         |         orderprices         | orderdate
----------+---------+------------------------+-----------------------------+------------
    19626 |      91 | ["P1262","P68","P101"] | ["192.59","49.99","137.49"] | 2021-03-14
    25646 |     716 | ["P997","P31","P101"]  | ["91.39","29.99","147.49"]  | 2021-03-14
    25647 |     716 | ["P12"]                | ["8.99"]                    | 2021-03-14
    19743 |     161 | ["P68","P101"]         | ["49.99","137.49"]          | 2021-03-15
    19888 |     241 | ["P1262","P101"]       | ["197.59","142.49"]         | 2021-03-15
(5 rows)

如本例所示,数组值以 JSON 格式返回。

集的值也以 JSON 数组格式返回:

=> SELECT custkey,email_addrs FROM customers LIMIT 4;
 custkey |                           email_addrs
---------+------------------------------------------------------------------------
 342176  | ["joe.smith@example.com"]
 342799  | ["bob@example,com","robert.jones@example.com"]
 342845  | ["br92@cs.example.edu"]
 342321  | ["789123@example-isp.com","sjohnson@eng.example.com","sara@johnson.example.name"]

您可以访问具有多个索引的嵌套数组(多维数组)的元素,如以下示例所示:

=> SELECT host, pingtimes FROM network_tests;
 host |                 pingtimes
------+-------------------------------------------------------
 eng1 | [[24.24,25.27,27.16,24.97], [23.97,25.01,28.12,29.50]]
 eng2 | [[27.12,27.91,28.11,26.95], [29.01,28.99,30.11,31.56]]
 qa1  | [[23.15,25.11,24.63,23.91], [22.85,22.86,23.91,31.52]]
(3 rows)

=> SELECT pingtimes[0] FROM network_tests;
      pingtimes
-------------------------
[24.24,25.27,27.16,24.97]
[27.12,27.91,28.11,26.95]
[23.15,25.11,24.63,23.91]
(3 rows)

=> SELECT pingtimes[0][0] FROM network_tests;
 pingtimes
-----------
24.24
27.12
23.15
(3 rows)

Vertica 支持多个函数来操作数组和集。

考虑同一订单表,其中包含在单个订单中购买的所有商品的产品密钥数组。您可以使用 APPLY_COUNT_ELEMENTS 函数来找出每个订单包含的商品数量。该函数标识 prodkey 数组中非 null 元素的数量:

=> SELECT apply_count_elements(prodkey) FROM orders LIMIT 5;
apply_count_elements
--------------------
3
2
2
3
1
(5 rows)

Vertica 还支持集合元素的聚合函数。现在,考虑同一表中的一列,其中包含在单个订单中购买的每件商品的价格数组。您可以使用 APPLY_SUM 函数来查找每个订单的总花费:

=> SELECT apply_sum(orderprices) from orders LIMIT 5;
apply_sum
-----------
380.07
187.48
340.08
268.87
  8.99
(5 rows)

大部分数组函数只对一维数组进行操作。要将它们与多维数组一起使用,首先要取消引用一维:

=> SELECT apply_max(pingtimes[0]) FROM network_tests;
 apply_max
-----------
 27.16
 28.11
 25.11
(3 rows)

有关完整的函数列表,请参阅集合函数

您可以在查询中同时包含列名和字面量值。以下示例返回每个订单中商品数量大于三个的订单的产品密钥:

=> SELECT prodkey FROM orders WHERE apply_count_elements(prodkey)>2;
      prodkey
------------------------
 ["P1262","P68","P101"]
 ["P997","P31","P101"]
(2 rows)

考虑一个较复杂的查询,该查询通过联接两个表 custorders 来返回客户密钥、姓名、电子邮件、订单密钥和产品密钥,以获取满足总和大于 150 这一条件的订单:

=> SELECT custkey, cust_custname, cust_email, orderkey, prodkey, orderprices from orders
 JOIN cust ON custkey = cust_custkey
 WHERE apply_sum(orderprices)>150 ;
custkey|  cust_custname   |        cust_email         |   orderkey   |                  prodkey                  |        orderprices
-------+------------------+---------------------------+--------------+--------------------------------========---+---------------------------
342799 | "Ananya Patel"   | "ananyapatel98@gmail.com" | "113-341987" | ["MG-7190","VA-4028","EH-1247","MS-7018"] | [60.00,67.00,22.00,14.99]
342845 | "Molly Benton"   | "molly_benton@gmail.com"  | "111-952000" | ["ID-2586","IC-9010","MH-2401","JC-1905"] | [22.00,35.00,90.00,12.00]
342989 | "Natasha Abbasi" | "natsabbasi@live.com"     | "111-685238" | ["HP-4024"]                               | [650.00]
342176 | "Jose Martinez"  | "jmartinez@hotmail.com"   | "113-672238" | ["HP-4768","IC-9010"]                     | [899.00,60.00]
342845 | "Molly Benton"   | "molly_benton@gmail.com"  | "113-864153" | ["AE-7064","VA-4028","GW-1808"]           | [72.00,99.00,185.00]
(5 rows)

具有复杂元素的数组

数组可以包含任意组合的数组和结构,如下例所示:

=> CREATE TABLE orders(
  orderid INT,
  accountid INT,
  shipments ARRAY[
    ROW(
      shipid INT,
      address ROW(
        street VARCHAR,
        city VARCHAR,
        zip INT
        ),
      shipdate DATE
    )
  ]
 );

一些订单包含多批货物。为了便于阅读,已在以下输出中插入换行符:

=> SELECT * FROM orders;
 orderid | accountid |                          shipments
---------+-----------+---------------------------------------------------------------------------------------------------------------
   99123 |        17 | [{"shipid":1,"address":{"street":"911 San Marcos St","city":"Austin","zip":73344},"shipdate":"2020-11-05"},
            {"shipid":2,"address":{"street":"100 main St Apt 4B","city":"Pasadena","zip":91001},"shipdate":"2020-11-06"}]
   99149 |       139 | [{"shipid":3,"address":{"street":"100 main St Apt 4B","city":"Pasadena","zip":91001},"shipdate":"2020-11-06"}]
   99162 |       139 | [{"shipid":4,"address":{"street":"100 main St Apt 4B","city":"Pasadena","zip":91001},"shipdate":"2020-11-04"},
            {"shipid":5,"address":{"street":"100 Main St Apt 4A","city":"Pasadena","zip":91001},"shipdate":"2020-11-11"}]
(3 rows)

您可以在查询中同时使用数组索引和结构字段选择:

=> SELECT orderid, shipments[0].shipdate AS ship1, shipments[1].shipdate AS ship2 FROM orders;
 orderid |   ship1    |   ship2
---------+------------+------------
   99123 | 2020-11-05 | 2020-11-06
   99149 | 2020-11-06 |
   99162 | 2020-11-04 | 2020-11-11
(3 rows)

此示例选择特定数组索引。要访问所有条目,请使用 EXPLODE

某些数据格式具有映射类型,它是一组键/值对。Vertica 不直接支持查询映射,但您可以将映射列定义为结构数组并对其进行查询。在以下示例中,数据中的 "prods" 列是一个映射:

=> CREATE EXTERNAL TABLE orders
 (orderkey INT,
  custkey INT,
  prods ARRAY[ROW(key VARCHAR(10), value DECIMAL(12,2))],
  orderdate DATE
 ) AS COPY FROM '...' PARQUET;

=> SELECT orderkey, prods FROM orders;
 orderkey |                                              prods
----------+--------------------------------------------------------------------------------------------------
    19626 | [{"key":"P68","value":"49.99"},{"key":"P1262","value":"192.59"},{"key":"P101","value":"137.49"}]
    25646 | [{"key":"P997","value":"91.39"},{"key":"P101","value":"147.49"},{"key":"P31","value":"29.99"}]
    25647 | [{"key":"P12","value":"8.99"}]
    19743 | [{"key":"P68","value":"49.99"},{"key":"P101","value":"137.49"}]
    19888 | [{"key":"P1262","value":"197.59"},{"key":"P101","value":"142.49"}]
(5 rows)

您不能在 CREATE TABLE AS SELECT (CTAS) 或视图中使用混合列。此限制适用于整个列或其中的字段选择。

排序和分组

您可以将 比较运算符 用于集合。null 集合排在最后。否则,对集合中的元素逐个比较,直到出现不匹配,然后根据不匹配的元素对集合进行排序。如果所有元素的长度都依次等于较短元素的长度,则首先对较短的元素进行排序。

您可以在查询的 ORDER BYGROUP BY 子句中使用集合。以下示例演示如何按数组列对查询结果进行排序:

=> CREATE TABLE employees (id INT, department VARCHAR(50), grants ARRAY[VARCHAR], grant_values ARRAY[INT]);

=> COPY employees FROM STDIN;
42|Physics|[US-7376,DARPA-1567]|[65000,135000]
36|Physics|[US-7376,DARPA-1567]|[10000,25000]
33|Physics|[US-7376]|[30000]
36|Astronomy|[US-7376,DARPA-1567]|[5000,4000]
\.

=> SELECT * FROM employees ORDER BY grant_values;
 id | department |          grants          |  grant_values
----+------------+--------------------------+----------------
 36 | Astronomy  | ["US-7376","DARPA-1567"] | [5000,4000]
 36 | Physics    | ["US-7376","DARPA-1567"] | [10000,25000]
 33 | Physics    | ["US-7376"]              | [30000]
 42 | Physics    | ["US-7376","DARPA-1567"] | [65000,135000]
(4 rows)

以下示例使用 GROUP BY 查询同一个表:

=> CREATE TABLE employees (id INT, department VARCHAR(50), grants ARRAY[VARCHAR], grant_values ARRAY[INT]);

=> COPY employees FROM STDIN;
42|Physics|[US-7376,DARPA-1567]|[65000,135000]
36|Physics|[US-7376,DARPA-1567]|[10000,25000]
33|Physics|[US-7376]|[30000]
36|Astronomy|[US-7376,DARPA-1567]|[5000,4000]
\.

=> SELECT department, grants, SUM(apply_sum(grant_values)) FROM employees GROUP BY grants, department;
 department |          grants          |  SUM
------------+--------------------------+--------
 Physics    | ["US-7376","DARPA-1567"] | 235000
 Astronomy  | ["US-7376","DARPA-1567"] |   9000
 Physics    | ["US-7376"]              |  30000
(3 rows)

有关 Vertica 如何对集合进行排序的信息,请参阅 ARRAY 参考页面上的“函数和运算符”部分。(SET 参考页面上也提供了同样的信息。)

NULL 处理

集合的空语义在大多数方面与普通列一致。有关空处理的更多信息,请参阅 NULL 排序顺序

当集合为 null 而不是空时,空安全相等运算符 (<=>) 的行为与相等 (=) 不同。将集合严格地与 NULL 进行比较是未定义的。

=> SELECT ARRAY[1,3] = NULL;
?column?
----------

(1 row)

=> SELECT ARRAY[1,3] <=> NULL;
 ?column?
----------
 f
(1 row)

在以下示例中,表中的授予列对于员工 99 为 null。

=> SELECT grants = NULL FROM employees WHERE id=99;
 ?column?
----------

(1 row)

=> SELECT grants <=> NULL FROM employees WHERE id=99;
 ?column?
----------
 t
(1 row)

空集合不为 null 并且按预期运行。

=> SELECT ARRAY[]::ARRAY[INT] = ARRAY[]::ARRAY[INT];
 ?column?
----------
 t
(1 row)

集合逐个元素进行比较。如果比较依赖于 null 元素,则结果是未知的 (null),而不是 false。例如,ARRAY[1,2,null]=ARRAY[1,2,null]ARRAY[1,2,null]=ARRAY[1,2,3] 都返回 null,但 ARRAY[1,2,null]=ARRAY[1,4,null] 因为第二个元素不匹配而返回 false。

集合中的越界索引返回 NULL。

=> SELECT prodkey[2] from orders LIMIT 4;
prodkey
---------

"EH-1247"
"MH-2401"

(4 rows)

由于指定的索引大于这些数组的大小,因此四行中的两行(第一行和第四行)的查询结果返回 NULL。

强制转换

当表达式值的数据类型明确时,会隐式地强制转换,以与预期数据类型相匹配。但是,表达式的数据类型可能不明确。例如,日期可能解释为字符串或时间戳。编写显式强制转换以避免使用默认值:

=> SELECT apply_count_elements(ARRAY['2019-01-20','2019-02-12','2019-03-23']::ARRAY[TIMESTAMP]);
apply_count_elements
--------------------
 3
(1 row)

您可以按照与强制转换标量值相同的规则,将一种标量类型的数组或集强制转换为其他(兼容)类型的数组或集。强制转换一个集合会强制转换该集合的每个元素。将数组强制转换为集也会移除任何重复项。

您可以使用作为数组或结构(或二者的组合)的元素来强制转换数组(而不是集):

=> SELECT shipments::ARRAY[ROW(id INT,addr ROW(VARCHAR,VARCHAR,INT),shipped DATE)]
FROM orders;
                  shipments
---------------------------------------------------------------------------
 [{"id":1,"addr":{"street":"911 San Marcos St","city":"Austin","zip":73344},"shipped":"2020-11-05"},
  {"id":2,"addr":{"street":"100 main St Apt 4B","city":"Pasadena","zip":91001},"shipped":"2020-11-06"}]
 [{"id":3,"addr":{"street":"100 main St Apt 4B","city":"Pasadena","zip":91001},"shipped":"2020-11-06"}]
 [{"id":4,"addr":{"street":"100 main St Apt 4B","city":"Pasadena","zip":91001},"shipped":"2020-11-04"},
  {"id":5,"addr":{"street":"100 Main St Apt 4A","city":"Pasadena","zip":91001},"shipped":"2020-11-11"}]
(3 rows)

您可以通过强制转换来更改数组或集的边界。当强制转换为有界原生数组时,会截断太长的输入。强制转换为多维数组时,如果新边界对于数据而言太小,则强制转换失败:

=> SELECT ARRAY[1,2,3]::ARRAY[VARCHAR,2];
   array
-----------
 ["1","2"]
(1 row)

=> SELECT ARRAY[ARRAY[1,2,3],ARRAY[4,5,6]]::ARRAY[ARRAY[VARCHAR,2],2];
ERROR 9227:  Output array isn't big enough
DETAIL:  Type limit is 4 elements, but value has 6 elements

如果强制转换为有界多维数组,则必须指定所有级别的边界。

必须显式强制转换具有单个 null 元素的数组或集,因为无法推断出任何类型。

有关数据类型强制转换的详细信息,请参阅数据类型强制转换

分割数组列

您可以使用 EXPLODE 简化对存储在数组中的元素的查询,该函数从表中获取数组列并展开它们。对于每个分割的数组,结果包括两列,一列用于数组元素索引,另一列用于该位置的值。如果该函数分割单个数组,这些列默认命名为 positionvalue。如果该函数分割两个或更多数组,则每个数组的列命名为 pos_column-nameval_column-name

该函数分割参数列表中的前 N 个数组列(N 默认值为 1),并传递所有其他列。

以下示例说明了将 EXPLODE()OVER(PARTITION BEST) 子句一起使用。

考虑一个订单表,其中包含订单键、客户键、产品键、订单价格和电子邮件地址的列,其中一些包含数组。Vertica 中的基本查询结果如下:

=> SELECT orderkey, custkey, prodkey, orderprices, email_addrs FROM orders LIMIT 5;
  orderkey  | custkey |                    prodkey                    |            orderprices            |                                                  email_addrs
------------+---------+-----------------------------------------------+-----------------------------------+----------------------------------------------------------------------------------------------------------------
 113-341987 |  342799 | ["MG-7190 ","VA-4028 ","EH-1247 ","MS-7018 "] | ["60.00","67.00","22.00","14.99"] | ["bob@example,com","robert.jones@example.com"]
 111-952000 |  342845 | ["ID-2586 ","IC-9010 ","MH-2401 ","JC-1905 "] | ["22.00","35.00",null,"12.00"]    | ["br92@cs.example.edu"]
 111-345634 |  342536 | ["RS-0731 ","SJ-2021 "]                       | ["50.00",null]                    | [null]
 113-965086 |  342176 | ["GW-1808 "]                                  | ["108.00"]                        | ["joe.smith@example.com"]
 111-335121 |  342321 | ["TF-3556 "]                                  | ["50.00"]                         | ["789123@example-isp.com","alexjohnson@example.com","monica@eng.example.com","sara@johnson.example.name",null]
(5 rows)

此示例按升序扩展指定客户的 orderprices 列。custkeyemail_addrs 列对每个数组元素重复。

=> SELECT EXPLODE(orderprices, custkey, email_addrs) OVER(PARTITION BEST) AS (position, orderprices, custkey, email_addrs)
   FROM orders WHERE custkey='342845' ORDER BY orderprices;
 position | orderprices | custkey |         email_addrs
----------+-------------+---------+------------------------------
        2 |             |  342845 | ["br92@cs.example.edu",null]
        3 |       12.00 |  342845 | ["br92@cs.example.edu",null]
        0 |       22.00 |  342845 | ["br92@cs.example.edu",null]
        1 |       35.00 |  342845 | ["br92@cs.example.edu",null]
(4 rows)

展开包含空值的列时,null 值显示为空。

您可以通过指定 explode_count 参数来展开多个列。

=> SELECT EXPLODE(orderkey, prodkey, orderprices USING PARAMETERS explode_count=2)
OVER(PARTITION BEST)
AS (orderkey,pk_idx,pk_val,ord_idx,ord_val)
FROM orders
WHERE orderkey='113-341987';
  orderkey  | pk_idx |  pk_val  | ord_idx | ord_val
------------+--------+----------+---------+---------
 113-341987 |      0 | MG-7190  |       0 |   60.00
 113-341987 |      0 | MG-7190  |       1 |   67.00
 113-341987 |      0 | MG-7190  |       2 |   22.00
 113-341987 |      0 | MG-7190  |       3 |   14.99
 113-341987 |      1 | VA-4028  |       0 |   60.00
 113-341987 |      1 | VA-4028  |       1 |   67.00
 113-341987 |      1 | VA-4028  |       2 |   22.00
 113-341987 |      1 | VA-4028  |       3 |   14.99
 113-341987 |      2 | EH-1247  |       0 |   60.00
 113-341987 |      2 | EH-1247  |       1 |   67.00
 113-341987 |      2 | EH-1247  |       2 |   22.00
 113-341987 |      2 | EH-1247  |       3 |   14.99
 113-341987 |      3 | MS-7018  |       0 |   60.00
 113-341987 |      3 | MS-7018  |       1 |   67.00
 113-341987 |      3 | MS-7018  |       2 |   22.00
 113-341987 |      3 | MS-7018  |       3 |   14.99
(16 rows)

以下示例使用多维数组:

=> SELECT name, pingtimes FROM network_tests;
 name |                       pingtimes
------+-------------------------------------------------------
 eng1 | [[24.24,25.27,27.16,24.97],[23.97,25.01,28.12,29.5]]
 eng2 | [[27.12,27.91,28.11,26.95],[29.01,28.99,30.11,31.56]]
 qa1  | [[23.15,25.11,24.63,23.91],[22.85,22.86,23.91,31.52]]
(3 rows)

=> SELECT EXPLODE(name, pingtimes USING PARAMETERS explode_count=1) OVER()
FROM network_tests;
 name | position |           value
------+----------+---------------------------
 eng1 |        0 | [24.24,25.27,27.16,24.97]
 eng1 |        1 | [23.97,25.01,28.12,29.5]
 eng2 |        0 | [27.12,27.91,28.11,26.95]
 eng2 |        1 | [29.01,28.99,30.11,31.56]
 qa1  |        0 | [23.15,25.11,24.63,23.91]
 qa1  |        1 | [22.85,22.86,23.91,31.52]
(6 rows)

有关在 Vertica 中实施这些数据类型的详细信息,请参阅 ARRAYSET

压缩和筛选数组

IMPLODE 函数是 EXPLODE 的反函数:它接受一列,并生成一个包含该列值的数组。与 GROUP BY 结合使用,可用于反转分割操作。

您可以同时使用 EXPLODEIMPLODE 来筛选数组值。例如,在一组价格为数组值的订单中,您可能只想查询价格低于某个阈值的订单。请考虑下表:

=> SELECT * FROM orders;

 key |      prices
-----+-------------------
 567 | [27.99,18.99]
 789 | [108.0]
 345 | [14.99,35.99]
 123 | [60.0,67.0,14.99]
(4 rows)

您可以使用 EXPLODE 来展开数组。为清楚起见,此示例创建一个新表来保存结果。更典型的做法是,您在子查询中使用 EXPLODEIMPLODE 而不是创建中间表。

=> CREATE TABLE exploded AS
SELECT EXPLODE(prices,key) OVER (PARTITION BEST)
AS (position, itemprice, itemkey) FROM orders;

=> SELECT * FROM exploded;

 position | itemprice | itemkey
----------+-----------+---------
        0 |       108 |     789
        1 |     35.99 |     345
        0 |     14.99 |     345
        0 |     27.99 |     567
        0 |        60 |     123
        1 |     18.99 |     567
        1 |        67 |     123
        2 |     14.99 |     123
(8 rows)

您现在可以筛选已分割的价格:

=> CREATE TABLE filtered AS
    SELECT position, itemprice, itemkey FROM orders WHERE itemprice < 50.00;

=> SELECT * FROM filtered;

 position | itemprice | itemkey
----------+-----------+---------
        0 |     14.99 |     345
        0 |     27.99 |     567
        1 |     18.99 |     567
        1 |     35.99 |     345
        2 |     14.99 |     123
(5 rows)

最后,您可以使用 IMPLODE 来重构数组:

=> SELECT itemkey AS key, IMPLODE(itemprice) AS prices
    FROM filtered GROUP BY itemkey ORDER BY itemkey;
 key |      prices
-----+-------------------
 123 | ["14.99"]
 345 | ["35.99","14.99"]
 567 | ["27.99","18.99"]
(3 rows)

您可以通过包括 WITHIN GROUP ORDER BY 子句来修改此查询,该子句指定如何对每个组中的数组元素进行排序:

=> SELECT itemkey AS key, IMPLODE(itemprice) WITHIN GROUP (ORDER BY itemprice) AS prices
    FROM filtered GROUP BY itemkey ORDER BY itemkey;
 key |      prices
-----+-------------------
 123 | ["14.99"]
 345 | ["14.99","35.99"]
 567 | ["18.99","27.99"]
(3 rows)

如果 IMPLODE 要返回的数组对于该列来说太大,则该函数将返回一个错误。为避免这种情况,您可以将 allow_truncate 参数设置为在结果中省略一些元素。截断永远不会应用于单个元素;例如,该函数不会缩短字符串。

1.5 - 行(结构)

表可以包含 ROW 数据类型的列。ROW(有时称为结构)是一组类型化的属性值对。

考虑一个包含 name、address 和 ID 列的 customers 表。地址是一个 ROW,其中包含地址元素的字段(街道、城市和邮政编码)。如本例所示,ROW 值以 JSON 格式返回:

=> SELECT * FROM customers ORDER BY accountID;
        name        |                              address                               | accountID
--------------------+--------------------------------------------------------------------+-----------
 Missy Cooper       | {"street":"911 San Marcos St","city":"Austin","zipcode":73344}     |        17
 Sheldon Cooper     | {"street":"100 Main St Apt 4B","city":"Pasadena","zipcode":91001}  |       139
 Leonard Hofstadter | {"street":"100 Main St Apt 4A","city":"Pasadena","zipcode":91001}  |       142
 Leslie Winkle      | {"street":"23 Fifth Ave Apt 8C","city":"Pasadena","zipcode":91001} |       198
 Raj Koothrappali   | {"street":null,"city":"Pasadena","zipcode":91001}                  |       294
 Stuart Bloom       |                                                                    |       482
(6 rows)

大部分的值都强制转换为 UTF-8 字符串,如此处的 street 和 city 所示。整数和布尔值强制转换为 JSON 数字,因此不被引用。

使用点表示法 (column.field) 访问各个字段:

=> SELECT address.city FROM customers;
   city
----------
 Pasadena
 Pasadena
 Pasadena
 Pasadena
 Austin

(6 rows)

在以下示例中,customers 表中的联系信息有一个 email 字段,该字段是地址数组:

=> SELECT name, contact.email FROM customers;
        name        |                    email
--------------------+---------------------------------------------
 Missy Cooper       | ["missy@mit.edu","mcooper@cern.gov"]
 Sheldon Cooper     | ["shelly@meemaw.name","cooper@caltech.edu"]
 Leonard Hofstadter | ["hofstadter@caltech.edu"]
 Leslie Winkle      | []
 Raj Koothrappali   | ["raj@available.com"]
 Stuart Bloom       |
(6 rows)

您可以使用 ROW 列或特定字段来限制查询,如下例所示:


=> SELECT address FROM customers WHERE address.city ='Pasadena';
                              address
--------------------------------------------------------------------
 {"street":"100 Main St Apt 4B","city":"Pasadena","zipcode":91001}
 {"street":"100 Main St Apt 4A","city":"Pasadena","zipcode":91001}
 {"street":"23 Fifth Ave Apt 8C","city":"Pasadena","zipcode":91001}
 {"street":null,"city":"Pasadena","zipcode":91001}
(4 rows)

您可以使用 ROW 语法来指定字面量值,例如以下示例中 WHERE 子句中的地址:


=> SELECT name,address FROM customers
   WHERE address = ROW('100 Main St Apt 4A','Pasadena',91001);
        name        |                              address
--------------------+-------------------------------------------------------------------
 Leonard Hofstadter | {"street":"100 Main St Apt 4A","city":"Pasadena","zipcode":91001}
(1 row)

您可以像从任何其他列中一样联接字段值:

=> SELECT accountID,department from customers JOIN employees
   ON customers.name=employees.personal.name;
 accountID | department
-----------+------------
       139 | Physics
       142 | Physics
       294 | Astronomy

您可以联接完整的结构。以下示例联接 employees 和 customers 表中的地址:

=> SELECT employees.personal.name,customers.accountID FROM employees
JOIN customers ON employees.personal.address=customers.address;
        name        | accountID
--------------------+-----------
 Sheldon Cooper     |       139
 Leonard Hofstadter |       142
(2 rows)

您可以强制转换结构,且可选择指定新的字段名称:

=> SELECT contact::ROW(str VARCHAR, city VARCHAR, zip VARCHAR, email ARRAY[VARCHAR,
20]) FROM customers;
                                                     contact

--------------------------------------------------------------------------------
----------------------------------
 {"str":"911 San Marcos St","city":"Austin","zip":"73344","email":["missy@mit.ed
u","mcooper@cern.gov"]}
 {"str":"100 Main St Apt 4B","city":"Pasadena","zip":"91001","email":["shelly@me
emaw.name","cooper@caltech.edu"]}
 {"str":"100 Main St Apt 4A","city":"Pasadena","zip":"91001","email":["hofstadte
r@caltech.edu"]}
 {"str":"23 Fifth Ave Apt 8C","city":"Pasadena","zip":"91001","email":[]}
 {"str":null,"city":"Pasadena","zip":"91001","email":["raj@available.com"]}

(6 rows)

您可以在视图和子查询中使用结构,如下例所示:

=> CREATE VIEW neighbors (num_neighbors, area(city, zipcode))
AS SELECT count(*), ROW(address.city, address.zipcode)
FROM customers GROUP BY address.city, address.zipcode;
CREATE VIEW

=> SELECT employees.personal.name, neighbors.area FROM neighbors, employees
WHERE employees.personal.address.zipcode=neighbors.area.zipcode AND neighbors.nu
m_neighbors > 1;
        name        |                area
--------------------+-------------------------------------
 Sheldon Cooper     | {"city":"Pasadena","zipcode":91001}
 Leonard Hofstadter | {"city":"Pasadena","zipcode":91001}
(2 rows)

如果引用不明确,Vertica 会优先选择列名而不是字段名。

您可以对 ROW 列使用许多运算符和谓词,包括 JOIN、GROUP BY、ORDER BY、IS [NOT] NULL 以及可 null 筛选器中的比较操作。某些运算符在逻辑上不适用于结构化数据并且不受支持。有关完整列表,请参阅 ROW 参考页面。

1.6 - 子查询

子查询是一种嵌套在其他 SELECT 语句中的 SELECT 语句。嵌套子查询通常称为查询内语句,而包含查询通常称为查询语句或外查询块。子查询返回外查询用作条件的数据,以确定需要检索哪些数据。您可以创建的嵌套子查询的数量没有限制。

与任何查询一样,子查询返回(单列单记录、单列多记录或多列多记录)表中的记录。查询可以是非相关或相关查询。您甚至可以使用它们基于存储在其他数据库表中的值来更新或删除表中的记录。

1.6.1 - 搜索条件中使用的子查询

子查询需要用作搜索条件才能过滤结果。它们指定了从包含查询的 select-list、查询表达式或子查询自身返回行的条件。此操作评估为 TRUE、FALSE 或 UNKNOWN (NULL)。

语法

search‑condition {
    [ { AND | OR | NOT } {  predicate | ( search‑condition ) } ]
   }[,... ]
 predicate
     { expression comparison‑operator expression
         | string‑expression [ NOT ] { LIKE | ILIKE | LIKEB | ILIKEB } string-expression
         | expression IS [ NOT ] NULL
         | expression [ NOT ] IN ( subquery | expression[,... ] )
         | expression comparison‑operator [ ANY | SOME ] ( subquery )
         | expression comparison‑operator ALL ( subquery )
         | expression OR ( subquery )
         | [ NOT ] EXISTS ( subquery )
         | [ NOT ] IN ( subquery )
     }

参数

1.6.1.1 - 逻辑运算符 AND 和 OR

AND 和 OR 逻辑运算符会组合两个条件。当由 AND 关键字联接的两个条件都有匹配时,AND 评估为 TRUE,当由 OR 关键字联接的一个条件有匹配时,则 OR 评估为 TRUE。

OR 子查询(复杂表达式)

Vertica 支持使用 OR 的更复杂的表达式中的子查询,例如:

  • 连接表达式中有一个以上子查询:

    (SELECT MAX(b) FROM t1) + SELECT (MAX FROM t2) a IN (SELECT a FROM t1) OR b IN (SELECT x FROM t2)
    
  • 连接表达式中的 OR 子句至少包含一个子查询:

    a IN (SELECT a FROM t1) OR b IN (SELECT x FROM t2) a IN (SELECT a from t1) OR b = 5
    a = (SELECT MAX FROM t2) OR b = 5
    
  • 只有一个子查询,而且它包含在另一个表达式中:

    x IN (SELECT a FROM t1) = (x = (SELECT MAX FROM t2) (x IN (SELECT a FROM t1) IS NULL
    

如何评估 AND 查询

Vertica 会分别对待 AND(连接)运算符分隔的表达式。例如,如果 WHERE 子句为:

  WHERE (a IN (SELECT a FROM t1) OR b IN (SELECT x FROM t2)) AND (c IN (SELECT a FROM t1))

则将查询解释为两个连接表达式:

  1. (a IN (SELECT a FROM t1) OR b IN (SELECT x FROM t2))

  2. (c IN (SELECT a FROM t1))

第一个表达式会被认为是一个复杂的子查询,而第二个表达式则不会。

示例

以下列表显示了过滤 WHERE 子句中复杂条件的几种方法:

  • 子查询和非子查询条件之间的 OR 表达式:

    => SELECT x FROM t WHERE x > (SELECT SUM(DISTINCT x) FROM t GROUP BY y) OR x < 9;
    
  • 两个子查询之间的 OR 表达式:

    => SELECT * FROM t WHERE x=(SELECT x FROM t) OR EXISTS(SELECT x FROM tt);
    
  • 子查询表达式:

    => SELECT * FROM t WHERE x=(SELECT x FROM t)+1 OR x<>(SELECT x FROM t)+1;
    
  • 包含 [NOT] IN 子查询的 OR 表达式:

    => SELECT * FROM t WHERE NOT (EXISTS (SELECT x FROM t)) OR x >9;
    
  • 包含 IS [NOT] NULL 子查询的 OR 表达式:

    => SELECT * FROM t WHERE (SELECT * FROM t)IS NULL OR (SELECT * FROM tt)IS NULL;
    
  • 包含 boolean 列和返回 Boolean 数据类型的子查询的 OR 表达式:

    => SELECT * FROM t2 WHERE x = (SELECT x FROM t2) OR x;
    
  • CASE 语句中的 OR 表达式:

    => SELECT * FROM t WHERE CASE WHEN x=1 THEN x > (SELECT * FROM t)
           OR x < (SELECT * FROM t2) END ;
    
  • 分析函数、NULL 处理函数、字符串函数、数学函数等等:

    => SELECT x FROM t WHERE x > (SELECT COALESCE (x,y) FROM t GROUP BY x,y) OR
           x < 9;
    
  • 在用户定义的函数(假设 f() 为 1)中:

    => SELECT * FROM t WHERE x > 5 OR x = (SELECT f(x) FROM t);
    
  • 在不同的位置使用圆括号重建查询:

    => SELECT x FROM t WHERE (x = (SELECT x FROM t) AND y = (SELECT y FROM t))
           OR (SELECT x FROM t) =1;
    
  • 多列子查询:

    => SELECT * FROM t WHERE (x,y) = (SELECT x,y FROM t) OR x > 5;
    
  • 子查询左侧的常数/NULL:

    => SELECT * FROM t WHERE x > 5 OR 5 = (SELECT x FROM t);
    

另请参阅

1.6.1.2 - 替代表达式

返回单值(与 IN 子查询返回的值列表不同)的子查询可用于在 SQL 中允许使用表达式的任何位置。它可以是列名称、常数、函数、标量子查询或由运算符或子查询连接的列名称、常数和函数的组合。

例如:

=> SELECT c1 FROM t1 WHERE c1 = ANY (SELECT c1 FROM t2) ORDER BY c1;
=> SELECT c1 FROM t1 WHERE COALESCE((t1.c1 > ANY (SELECT c1 FROM t2)), TRUE);
=> SELECT c1 FROM t1 GROUP BY c1 HAVING
     COALESCE((t1.c1 <> ALL (SELECT c1 FROM t2)), TRUE);

多列表达式也受支持:

=> SELECT c1 FROM t1 WHERE (t1.c1, t1.c2) = ALL (SELECT c1, c2 FROM t2);
=> SELECT c1 FROM t1 WHERE (t1.c1, t1.c2) <> ANY (SELECT c1, c2 FROM t2);

如果用作表达式的任何子查询返回了一个以上的行,则 Vertica 将返回查询错误:

=> SELECT c1 FROM t1 WHERE c1 = (SELECT c1 FROM t2) ORDER BY c1;
   ERROR:  more than one row returned by a subquery used as an expression

另请参阅

1.6.1.3 - 比较运算符

Vertica 支持包含以下任何运算符的 WHERE 子句中的 Boolean 子查询表达式:

> < >= <= = <> <=>

WHERE 子句子查询会筛选结果,并采用以下格式:

SELECT <column, ...> FROM <table>
WHERE <condition> (SELECT <column, ...> FROM <table> WHERE <condition>);

只要比较有意义,这些条件可适用于所有数据类型。所有比较运算符均为二元运算符,可返回 TRUE、FALSE 或 UNKNOWN (NULL) 这些值。

表达式可以仅与外查询块中的一个外表相关,而且这些相关表达式可以是比较运算符。

支持以下子查询场景:

SELECT * FROM T1 WHERE T1.x =  (SELECT MAX(c1) FROM T2);
SELECT * FROM T1 WHERE T1.x >= (SELECT MAX(c1) FROM T2 WHERE T1.y = T2.c2);
SELECT * FROM T1 WHERE T1.x <= (SELECT MAX(c1) FROM T2 WHERE T1.y = T2.c2);

另请参阅

子查询限制

1.6.1.4 - LIKE 模式匹配

Vertica 支持子查询中的 LIKE 模式匹配条件,并采用以下格式:

string-expression [ NOT ] { LIKE | ILIKE | LIKEB | ILIKEB } string-expression

以下命令搜索公司名称以“Ev”开头的客户,然后返回总计数:

=> SELECT COUNT(*) FROM customer_dimension WHERE customer_name LIKE
      (SELECT 'Ev%' FROM customer_dimension LIMIT 1);
 count
-------
   153
(1 row)

Vertica 还支持将单行子查询用作 LIKEB 谓词和 ILIKEB 谓词的模式实参,例如:

=> SELECT * FROM t1 WHERE t1.x LIKEB (SELECT t2.x FROM t2);

以下符号可替代 LIKE 关键字:

~~    LIKE
~#    LIKEB
~~*   ILIKE
~#*   ILIKEB
!~~   NOT LIKE
!~#   NOT LIKEB
!~~*  NOT ILIKE
!~#*  NOT IILIKEB

有关其他示例,请参阅 LIKE 谓词

1.6.1.5 - ANY 和 ALL

您通常只对返回一行的子查询使用比较运算符(=>< 等)。使用 ANYALL 运算符,可在返回多个行的子查询中进行比较。

这些子查询采用以下格式:

expression comparison-operator { ANY | ALL } (subquery)

ANYALL 评估子查询返回的任何或所有值是否与左侧表达式匹配。

等效运算符

可以使用以下运算符代替 ANYALL

Example data

以下示例使用以下表和数据:

ANY 子查询

当在子查询中检索的任何值与左侧表达式的值相匹配时,使用 ANY 关键字的子查询返回 true。

示例

表达式中的 ANY 子查询:

=> SELECT c1, c2 FROM t1 WHERE COALESCE((t1.c1 > ANY (SELECT c1 FROM t2)));
 c1 | c2
----+-----
  2 | fed
  2 | def
  3 | ihg
  3 | ghi
  4 | jkl
  5 | mno
(6 rows)

ANY 不带有聚合的非相关子查询:

=> SELECT c1 FROM t1 WHERE c1 = ANY (SELECT c1 FROM t2) ORDER BY c1;
 c1
----
  1
  1
  2
  2
  3
  3
(6 rows)

ANY 带有聚合的非相关子查询:


=> SELECT c1, c2 FROM t1 WHERE c1 <> ANY (SELECT MAX(c1) FROM t2) ORDER BY c1;
 c1 | c2
----+-----
  1 | cab
  1 | abc
  2 | fed
  2 | def
  4 | jkl
  5 | mno
(6 rows)

=> SELECT c1 FROM t1 GROUP BY c1 HAVING c1 <> ANY (SELECT MAX(c1) FROM t2) ORDER BY c1;
 c1
----
  1
  2
  4
  5
(4 rows)

ANY 带有聚合和 GROUP BY 子句的非相关子查询:


=> SELECT c1, c2 FROM t1 WHERE c1 <> ANY (SELECT MAX(c1) FROM t2 GROUP BY c2) ORDER BY c1;
 c1 | c2
----+-----
  1 | cab
  1 | abc
  2 | fed
  2 | def
  3 | ihg
  3 | ghi
  4 | jkl
  5 | mno
(8 rows)

ANY 带有 GROUP BY 子句的非相关子查询:

=> SELECT c1, c2 FROM t1 WHERE c1 <=> ANY (SELECT c1 FROM t2 GROUP BY c1) ORDER BY c1;
 c1 | c2
----+-----
  1 | cab
  1 | abc
  2 | fed
  2 | def
  3 | ihg
  3 | ghi
(6 rows)

ANY 不带有聚合或 GROUP BY 子句的相关子查询:

=> SELECT c1, c2 FROM t1 WHERE c1 >= ANY (SELECT c1 FROM t2 WHERE t2.c2 = t1.c2) ORDER BY c1;
 c1 | c2
----+-----
  1 | abc
  2 | fed
  4 | jkl
(3 rows)

ALL 子句

当子查询检索到的所有值都与左侧表达式匹配时,使用 ALL 关键字的子查询返回 true,否则返回 false。

示例

ALL 不带有聚合的非相关子查询:

=> SELECT c1, c2 FROM t1 WHERE c1 >= ALL (SELECT c1 FROM t2) ORDER BY c1;
 c1 | c2
----+-----
  3 | ihg
  3 | ghi
  4 | jkl
  5 | mno
(4 rows)

ALL 带有聚合的非相关子查询:

=> SELECT c1, c2 FROM t1 WHERE c1 = ALL (SELECT MAX(c1) FROM t2) ORDER BY c1;
 c1 | c2
----+-----
  3 | ihg
  3 | ghi
(2 rows)

=> SELECT c1 FROM t1 GROUP BY c1 HAVING c1 <> ALL (SELECT MAX(c1) FROM t2) ORDER BY c1;
 c1
----
  1
  2
  4
  5
(4 rows)

ALL 带有聚合和 GROUP BY 子句的非相关子查询:


=> SELECT c1, c2 FROM t1 WHERE c1 <= ALL (SELECT MAX(c1) FROM t2 GROUP BY c2) ORDER BY c1;
 c1 | c2
----+-----
  1 | cab
  1 | abc
(2 rows)

ALL 带有 GROUP BY 子句的非相关子查询:

=> SELECT c1, c2 FROM t1 WHERE c1 <> ALL (SELECT c1 FROM t2 GROUP BY c1) ORDER BY c1;
 c1 | c2
----+-----
  4 | jkl
  5 | mno
(2 rows)

NULL 处理

如果列没有标记为 NOT NULL,则 Vertica 支持多列 <> ALL 子查询。如果任何列包含 NULL 值,Vertica 将返回运行时错误。

如果任何列值为 NULL,则 Vertica 不支持嵌套在另一个表达式中的 = ANY 子查询。

另请参阅

子查询限制

1.6.1.6 - EXISTS 和 NOT EXISTS

EXISTS 谓词是最常见的谓词之一,可用于构建使用非相关和相关子查询的条件。使用 EXISTS 可以不考虑数量就能识别是否存在关系。例如,如果子查询返回任何行,EXISTS 返回 true,如果子查询没有返回行,[NOT] EXISTS 返回 true。

[NOT] EXISTS 子查询采用以下格式:

expression [ NOT ] EXISTS ( subquery )

如果子查询至少返回了一个行,则认为符合 EXISTS 条件。由于结果仅取决于是否返回了记录,而不是取决于这些记录的内容,因此子查询的输出列表通常没有吸引力。常见的编码规范是按照如下方式编写所有 EXISTS 测试:

EXISTS (SELECT 1 WHERE ...)

在上述片段中,SELECT 1 为查询中所有记录返回了值 1。例如,查询返回了五个记录,它会返回 5 个一。系统不会考虑记录中的实际值;它只要知道是否返回了行。

或者,子查询使用 EXISTS 的选择列表可能包含星号 (*)。您不需要指定列名称,因为查询会测试是否有符合子查询中指定条件的记录。

EXISTS (SELECT * WHERE ...)

注意

  • 如果 EXISTS (subquery) 返回至少 1 行,则结果为 TRUE。

  • 如果 EXISTS (subquery) 不返回任何行,则结果为 FALSE。

  • 如果 NOT EXISTS (subquery) 返回至少 1 行,则结果为 FALSE。

  • 如果 NOT EXISTS (subquery) 不返回任何行,则结果为 TRUE。

示例

以下查询检索从任何一家商店购买金额超过 550 美元商品的所有客户的列表:

=> SELECT customer_key, customer_name, customer_state
   FROM public.customer_dimension WHERE EXISTS
     (SELECT 1 FROM store.store_sales_fact
      WHERE customer_key = public.customer_dimension.customer_key
      AND sales_dollar_amount > 550)
   AND customer_state = 'MA' ORDER BY customer_key;
 customer_key |   customer_name    | customer_state
--------------+--------------------+----------------
        14818 | William X. Nielson | MA
        18705 | James J. Goldberg  | MA
        30231 | Sarah N. McCabe    | MA
        48353 | Mark L. Brown      | MA
(4 rows)

使用 EXISTS 子查询还是 IN 子查询,取决于您在外查询块和内查询块中所选择的谓词。例如,要为供应商表中有记录的供应商获取所有商店在 2003 年 1 月 2 日下的所有订单的列表:

=> SELECT store_key, order_number, date_ordered
   FROM store.store_orders_fact WHERE EXISTS
     (SELECT 1 FROM public.vendor_dimension
      WHERE public.vendor_dimension.vendor_key = store.store_orders_fact.vendor_key)
   AND date_ordered = '2012-01-02';
 store_key | order_number | date_ordered
-----------+--------------+--------------
        37 |         2559 | 2012-01-02
        16 |          552 | 2012-01-02
        35 |         1156 | 2012-01-02
        13 |         3885 | 2012-01-02
        25 |          554 | 2012-01-02
        21 |         2687 | 2012-01-02
        49 |         3251 | 2012-01-02
        19 |         2922 | 2012-01-02
        26 |         1329 | 2012-01-02
        40 |         1183 | 2012-01-02
(10 rows)

上述查询查找是否存在下单的供应商和日期。要返回特定的值,而不是简单地确定是否存在,查询需要查找在 2004 年 1 月 4 日达成最佳交易的供应商所下的订单。

=> SELECT store_key, order_number, date_ordered
   FROM store.store_orders_fact ord, public.vendor_dimension vd
   WHERE ord.vendor_key = vd.vendor_key AND vd.deal_size IN
      (SELECT MAX(deal_size) FROM public.vendor_dimension)
    AND date_ordered = '2013-01-04';
 store_key | order_number | date_ordered
-----------+--------------+--------------
       166 |        36008 | 2013-01-04
       113 |        66017 | 2013-01-04
       198 |        75716 | 2013-01-04
        27 |       150241 | 2013-01-04
       148 |       182207 | 2013-01-04
         9 |       188567 | 2013-01-04
        45 |       202416 | 2013-01-04
        24 |       250295 | 2013-01-04
       121 |       251417 | 2013-01-04
(9 rows)

另请参阅

1.6.1.7 - IN 和 NOT IN

尽管无法使一个单值与一个值集相等,但您可以查看单值是否已在此值集中。对多记录单列子查询使用 IN 子句。子查询返回 INNOT IN 产生的结果之后,外查询将利用这些结果来返回最终结果。

[NOT] IN 子查询采用以下格式:

{ expression [ NOT ] IN ( subquery )| expression [ NOT ] IN ( expression ) }

传递给 SELECT 语句的 IN 子句的参数数量不受限制;例如:

=> SELECT * FROM tablename WHERE column IN (a, b, c, d, e, ...);

Vertica 还支持两个或多个外表达式同时引用不同内表达式的查询。

=> SELECT * FROM A WHERE (A.x,A.x) IN (SELECT B.x, B.y FROM B);

示例

以下查询使用 VMart 架构显示了同时引用不同内表达式的外表达式的使用情况:

=> SELECT product_description, product_price FROM product_dimension
   WHERE (product_dimension.product_key, product_dimension.product_key) IN
      (SELECT store.store_orders_fact.order_number,
         store.store_orders_fact.quantity_ordered
       FROM store.store_orders_fact);
 product_description         | product_price
-----------------------------+---------------
 Brand #72 box of candy      |           326
 Brand #71 vanilla ice cream |           270
(2 rows)

要查找马萨诸塞州的商店提供的所有产品,请首先创建内查询,然后运行它以确保它可以正常运行。以下查询返回了马萨诸塞州的所有商店:

=> SELECT store_key FROM store.store_dimension WHERE store_state = 'MA';
 store_key
-----------
        13
        31
(2 rows)

然后创建外查询或主查询,指定在马萨诸塞州的商店售出的所有不同产品。此语句使用 IN 谓词将内查询和外查询组合在一起:

=> SELECT DISTINCT s.product_key, p.product_description
   FROM store.store_sales_fact s, public.product_dimension p
   WHERE s.product_key = p.product_key
       AND s.product_version = p.product_version
       AND s.store_key IN
         (SELECT store_key
          FROM store.store_dimension
          WHERE store_state = 'MA')
   ORDER BY s.product_key;
 product_key |          product_description
-------------+---------------------------------------
           1 | Brand #1 white bread
           1 | Brand #4 vegetable soup
           3 | Brand #9 wheelchair
           5 | Brand #15 cheddar cheese
           5 | Brand #19 bleach
           7 | Brand #22 canned green beans
           7 | Brand #23 canned tomatoes
           8 | Brand #24 champagne
           8 | Brand #25 chicken nuggets
          11 | Brand #32 sausage
         ...   ...
(281 rows)

使用 NOT IN 时,子查询在外查询中返回了一列零值或更多值,其中比较列与子查询返回的任何值都不匹配。使用上一个示例时,NOT IN 返回不是来自马萨诸塞州的所有产品。

注意

如果列没有标记为 NOT NULL,则 Vertica 支持多列 NOT IN 子查询。如果在查询执行期间发现某列中包含 NULL 值,则 Vertica 会返回运行时错误。

同样地,如果任何一个列值为 NULL,则不支持嵌套在另一个表达式中的 IN 子查询。例如,如果在以下语句中,任何一个表的 x 列包含 NULL 值,Vertica 会返回运行时错误:

=> SELECT * FROM t1 WHERE (x IN (SELECT x FROM t2)) IS FALSE;
   ERROR: NULL value found in a column used by a subquery

另请参阅

1.6.2 - SELECT 列表中的子查询

子查询可以在包含查询的 select 列表中使用。以下语句的结果按第一列 (customer_name) 进行排序。您也可以写入 ORDER BY 2,然后指定结果按 select 列表中的子查询进行排序。

=> SELECT c.customer_name, (SELECT AVG(annual_income) FROM customer_dimension
    WHERE deal_size = c.deal_size) AVG_SAL_DEAL FROM customer_dimension c
     ORDER BY 1;
 customer_name | AVG_SAL_DEAL
---------------+--------------
 Goldstar      |       603429
 Metatech      |       628086
 Metadata      |       666728
 Foodstar      |       695962
 Verihope      |       715683
 Veridata      |       868252
 Bettercare    |       879156
 Foodgen       |       958954
 Virtacom      |       991551
 Inicorp       |      1098835
...

注意

  • select 列表中的标量子查询返回单个行/列值。这些子查询使用 Boolean 比较运算符:=、>、<、<>、<=、>=。

    如果查询是相关查询,则当相关生成 0 行时,它将返回 NULL。如果查询返回多行,则查询将在运行时出错,并且 Vertica 会显示错误消息,说明标量子查询必须只能返回 1 行。

  • 子查询表达式(例如 [NOT] IN、[NOT] EXISTS、ANY/SOME 或 ALL)总是返回单个评估为 TRUE、FALSE 或 UNKNOWN 的 Boolean 值;子查询自身可以拥有多个行。大多数查询可以为相关或非相关查询。

  • ORDER BY 和 GROUP BY 子句中的子查询受支持,例如,以下语句表示按第一列进行排序,该列是 select-list 子查询:

    => SELECT (SELECT MAX(x) FROM t2 WHERE y=t1.b) FROM t1 ORDER BY 1;
    

另请参阅

1.6.3 - WITH 子句

WITH 子句是较大的主要查询中的伴随查询。Vertica 可以通过两种方式评估 WITH 子句:

  • 内联扩展(默认):当主要查询每次引用每个 WITH 子句时,Vertica 才会对它进行评估。

  • 实体化:Vertica 对每个 WITH 子句进行一次评估,将结果存储在临时表中,然后在查询需要时引用此表。

有关语法选项和要求的详细信息,请参阅 WITH 子句

1.6.3.1 - WITH 子句的内联展开

默认情况下,Vertica 使用内联展开评估 WITH 子句。当主要查询每次引用每个 WITH 子句时,Vertica 才会对它进行评估。内联展开通常在查询没有多次引用同一个 WITH 子句时或在内联展开之后可以开展某些本地优化时运行效果最好。

示例

以下示例显示适合用于内联展开的 WITH 子句。在获取 2007 年 12 月 1 日至 7 日发货的所有订单的订单信息的查询中,使用 WITH 子句。


-- Begin WITH
WITH store_orders_fact_new AS(
   SELECT * FROM store.store_orders_fact WHERE date_shipped between '2007-12-01' and '2007-12-07')
-- End WITH
-- Begin primary query
SELECT store_key, product_key, product_version, SUM(quantity_ordered*unit_price) AS total_price
FROM store_orders_fact_new
GROUP BY store_key, product_key, product_version
ORDER BY total_price DESC;

 store_key | product_key | product_version | total_price
-----------+-------------+-----------------+-------------
       232 |        1855 |               2 |       29008
       125 |        8500 |               4 |       28812
       139 |        3707 |               2 |       28812
       212 |        3203 |               1 |       28000
       236 |        8023 |               4 |       27548
       123 |       10598 |               2 |       27146
        34 |        8888 |               4 |       27100
       203 |        2243 |               1 |       27027
       117 |       13932 |               2 |       27000
        84 |         768 |               1 |       26936
       123 |        1038 |               1 |       26885
       106 |       18932 |               1 |       26864
        93 |       10395 |               3 |       26790
       162 |       13073 |               1 |       26754
        15 |        3679 |               1 |       26675
        52 |        5957 |               5 |       26656
       190 |        8114 |               3 |       26611
         5 |        7772 |               1 |       26588
       139 |        6953 |               3 |       26572
       202 |       14735 |               1 |       26404
       133 |        2740 |               1 |       26312
       198 |        8545 |               3 |       26287
       221 |        7582 |               2 |       26280
       127 |        9468 |               3 |       26224
        63 |        8115 |               4 |       25960
       171 |        2088 |               1 |       25650
       250 |       11210 |               3 |       25608
...

Vertica 处理查询,如下所示:

  1. 在主要查询中展开对 store_orders_fact_new 的 WITH 子句引用。

  2. 展开 WITH 子句后,评估主要查询。

1.6.3.2 - WITH 子句的实体化

实体化启用之后,Vertica 会对每个 WITH 子句进行一次评估,将结果存储在临时表中,然后在查询需要时引用此表。主要查询执行完成之后,Vertica 会将临时表删除。

如果 WITH 子句非常复杂,例如当 WITH 子句包含 JOIN 和 GROUP BY 子句,并且在主要查询中多次被引用时,实体化可帮助提高性能。

如果实体化已启用,WITH 语句会自动提交用户事务。即使将 EXPLAIN 与 WITH 语句一起使用,也是如此。

启用 WITH 子句实体化

WITH 实体化由配置参数 WithClauseMaterialization 设置,默认设置为 0(禁用)。您可以分别使用 ALTER DATABASEALTER SESSION,通过在数据库和会话级别设置 WithClauseMaterialization 来启用和禁用实体化:

  • 数据库:

    => ALTER DATABASE db-spec SET PARAMETER WithClauseMaterialization={ 0 | 1 };
    => ALTER DATABASE db-spec CLEAR PARAMETER WithClauseMaterialization;
    
  • 会话:参数设置将继续有效,直到以显式方式清除它,或者会话结束。

    => ALTER SESSION SET PARAMETER WithClauseMaterialization={ 0 | 1 };
    => ALTER SESSION CLEAR PARAMETER WithClauseMaterialization;
    

您还可以使用提示 ENABLE_WITH_CLAUSE_MATERIALIZATION 为单个查询启用 WITH 实体化。如果查询返回,则会自动清除实体化。例如:


=> WITH /*+ENABLE_WITH_CLAUSE_MATERIALIZATION */ revenue AS (
      SELECT vendor_key, SUM(total_order_cost) AS total_revenue
      FROM store.store_orders_fact
      GROUP BY vendor_key ORDER BY 1)
     ...

使用 EE5 临时关系处理 WITH 子句

默认情况下,当重用 WITH 子句查询时,Vertica 将这些 WITH 子句查询输出保存在 EE5 临时关系中。不过,可以更改此选项。可以通过以下设置方式,使用配置参数 EnableWITHTempRelReuseLimit 来设置 EE5 临时关系支持:

  • 0:禁用此功能。

  • 1:将所有 WITH 子句查询强制保存到 EE5 临时关系中,无论它们是否被重用。

  • 2(默认值):仅将重用的 WITH 子句查询保存到 EE5 临时关系中。

  • 3 及 3 以上:仅当使用 WITH 子句查询至少达到此次数时才将其保存到 EE5 临时关系中。

可以分别使用 ALTER DATABASEALTER SESSION,在数据库和会话级别设置 EnableWITHTempRelReuseLimit。当 WithClauseMaterialization 设置为 1 时,该设置将覆盖任何 EnableWITHTempRelReuseLimit 设置。

请注意,对于具有复杂类型的 WITH 查询,禁用临时关系。

示例

以下示例显示适合用于实体化的 WITH 子句。查询获取在所有订单中拥有最高合并订单成本的供应商的数据:

-- Enable materialization
=> ALTER SESSION SET PARAMETER WithClauseMaterialization=1;

-- Define WITH clause
=> WITH revenue AS (
      SELECT vendor_key, SUM(total_order_cost) AS total_revenue
      FROM store.store_orders_fact
      GROUP BY vendor_key ORDER BY 1)
-- End WITH clause

-- Primary query
=> SELECT vendor_name, vendor_address, vendor_city, total_revenue
FROM vendor_dimension v, revenue r
WHERE v.vendor_key = r.vendor_key AND total_revenue = (SELECT MAX(total_revenue) FROM revenue )
ORDER BY vendor_name;
   vendor_name    | vendor_address | vendor_city | total_revenue
------------------+----------------+-------------+---------------
 Frozen Suppliers | 471 Mission St | Peoria      |      49877044
(1 row)

Vertica 按以下方式处理此查询:

  1. WITH 子句 revenue 通过 store.store_orders_fact 表评估 SELECT 语句。

  2. revenue 子句的结果存储在本地临时表中。

  3. 无论何时引用 revenue 子句,都会使用表中存储的结果。

  4. 查询执行完成之后,临时表会删除。

1.6.3.3 - WITH 子句递归

包含 RECURSIVE 选项的 WITH 子句可以重复执行 UNION 或 UNION ALL 查询,从而迭代其自身的输出。递归查询在处理分层结构(例如,经理下属关系)或树状结构数据(如分类法)等自引用数据时十分有用。

配置参数 WithClauseRecursionLimit(默认设置为 8)将设置递归的最大深度。您可以分别使用 ALTER DATABASE 和 ALTER SESSION 在数据库和会话范围内设置此参数。递归将会继续,直到达到配置的最大深度为止,或者直到最后一次迭代返回没有数据为止。

可以按如下方式指定递归 WITH 子句:

WITH [ /*+ENABLE_WITH_CLAUSE_MATERIALIZATION*/ ] RECURSIVE
   cte‑identifier [ ( column-aliases ) ] AS (
     non-recursive-term
     UNION [ ALL ]
     recursive-term
   )

非递归项和递归项由 UNION 或 UNION ALL 分隔:

  • non-recursive-term 查询将其结果集设置在 cte-identifier,在 recursive-term 中递归。

  • UNION 语句的 recursive-term 以递归方式迭代其自身输出。当递归完成时,所有迭代的结果均会编译并在 cte-identifier 中设置。

例如:


=> ALTER SESSION SET PARAMETER WithClauseRecursionLimit=4; -- maximum recursion depth = 4
=> WITH RECURSIVE nums (n) AS (
   SELECT 1 -- non-recursive (base) term
   UNION ALL
     SELECT n+1 FROM nums -- recursive term
  )
SELECT n FROM nums; -- primary query

此简单查询按如下方式执行:

  1. 执行 WITH RECURSIVE 子句:

    • 评估非递归项 SELECT 1,并将结果集 (1) 放入 nums

    • 迭代 UNION ALL 查询 (SELECT n+1),直到迭代次数大于配置参数 WithClauseRecursionLimit。

    • 合并所有 UNION 查询的结果,并将结果集设置在 nums 中,然后退出到主要查询。

  2. 执行主要查询 SELECT n FROM nums

    
     n
    ---
     1
     2
     3
     4
     5
    (5 rows)
    

在本例中,根据 WithClauseRecursionLimit,WITH RECURSIVE 子句在四次迭代后退出。如果将 WithClauseRecursionLimit 还原为其默认值 8,则子句在八次迭代后退出:


=> ALTER SESSION CLEAR PARAMETER WithClauseRecursionLimit;
=> WITH RECURSIVE nums (n) AS (
   SELECT 1
   UNION ALL
     SELECT n+1 FROM nums
  )
SELECT n FROM nums;
 n
---
 1
 2
 3
 4
 5
 6
 7
 8
 9
(9 rows)

限制

存在以下限制:

  • 非递归项的 SELECT 列表不能包含通配符 *(星号)或函数 MATCH_COLUMNS

  • 递归项只能引用目标 CTE 一次。

  • 递归引用不能出现在外联接中。

  • 递归引用不能出现在子查询中。

  • WITH 子句不支持 UNION 选项 ORDER BY、LIMIT 和 OFFSET。

示例

一家小型软件公司维护以下有关员工及其经理的数据:

=> SELECT * FROM personnel.employees ORDER BY emp_id;
 emp_id |   fname   |   lname   | section_id |    section_name     |  section_leader  | leader_id
--------+-----------+-----------+------------+---------------------+------------------+-----------
      0 | Stephen   | Mulligan  |          0 |                     |                  |
      1 | Michael   | North     |        201 | Development         | Zoe Black        |         3
      2 | Megan     | Berry     |        202 | QA                  | Richard Chan     |        18
      3 | Zoe       | Black     |        101 | Product Development | Renuka Patil     |        24
      4 | Tim       | James     |        203 | IT                  | Ebuka Udechukwu  |        17
      5 | Bella     | Tucker    |        201 | Development         | Zoe Black        |         3
      6 | Alexandra | Climo     |        202 | QA                  | Richard Chan     |        18
      7 | Leonard   | Gray      |        203 | IT                  | Ebuka Udechukwu  |        17
      8 | Carolyn   | Henderson |        201 | Development         | Zoe Black        |         3
      9 | Ryan      | Henderson |        201 | Development         | Zoe Black        |         3
     10 | Frank     | Tucker    |        205 | Sales               | Benjamin Glover  |        29
     11 | Nathan    | Ferguson  |        102 | Sales Marketing     | Eric Redfield    |        28
     12 | Kevin     | Rampling  |        101 | Product Development | Renuka Patil     |        24
     13 | Tuy Kim   | Duong     |        201 | Development         | Zoe Black        |         3
     14 | Dwipendra | Sing      |        204 | Tech Support        | Sarah Feldman    |        26
     15 | Dylan     | Wijman    |        206 | Documentation       | Kevin Rampling   |        12
     16 | Tamar     | Sasson    |        207 | Marketing           | Nathan Ferguson  |        11
     17 | Ebuka     | Udechukwu |        101 | Product Development | Renuka Patil     |        24
     18 | Richard   | Chan      |        101 | Product Development | Renuka Patil     |        24
     19 | Maria     | del Rio   |        201 | Development         | Zoe Black        |         3
     20 | Hua       | Song      |        204 | Tech Support        | Sarah Feldman    |        26
     21 | Carmen    | Lopez     |        204 | Tech Support        | Sarah Feldman    |        26
     22 | Edgar     | Mejia     |        206 | Documentation       | Kevin Rampling   |        12
     23 | Riad      | Salim     |        201 | Development         | Zoe Black        |         3
     24 | Renuka    | Patil     |        100 | Executive Office    | Stephen Mulligan |         0
     25 | Rina      | Dsouza    |        202 | QA                  | Richard Chan     |        18
     26 | Sarah     | Feldman   |        101 | Product Development | Renuka Patil     |        24
     27 | Max       | Mills     |        102 | Sales Marketing     | Eric Redfield    |        28
     28 | Eric      | Redfield  |        100 | Executive Office    | Stephen Mulligan |         0
     29 | Benjamin  | Glover    |        102 | Sales Marketing     | Eric Redfield    |        28
     30 | Dominic   | King      |        205 | Sales               | Benjamin Glover  |        29
     32 | Ryan      | Metcalfe  |        206 | Documentation       | Kevin Rampling   |        12
     33 | Piers     | Paige     |        201 | Development         | Zoe Black        |         3
     34 | Nicola    | Kelly     |        207 | Marketing           | Nathan Ferguson  |        11
(34 rows)

您可以通过 WITH RECURSIVE 查询此数据以了解员工与经理的关系。例如,以下查询的 WITH RECURSIVE 子句获取员工 Eric Redfield(包括他的所有直接下属和间接下属员工)的员工-经理关系:

WITH RECURSIVE managers (employeeID, employeeName, sectionID, section, lead, leadID)
 AS (SELECT emp_id, fname||' '||lname, section_id, section_name, section_leader, leader_id
      FROM personnel.employees WHERE fname||' '||lname = 'Eric Redfield'
 UNION
    SELECT emp_id, fname||' '||lname AS employee_name, section_id, section_name, section_leader, leader_id FROM personnel.employees e
      JOIN managers m ON m.employeeID = e.leader_id)
 SELECT employeeID, employeeName, lead AS 'Reports to', section, leadID from managers ORDER BY sectionID, employeeName;

WITH RECURSIVE 子句定义了 CTE managers,然后分两个阶段执行:

  1. 非递归项使用从 personnel.employees 查询的数据填充 managers

  2. 递归项的 UNION 查询迭代它自己的输出,直到在第四个循环中找不到更多数据。然后,所有迭代结果进行编译并设置在 managers 中,WITH CLAUSE 退出到主要查询。

主要查询从 managers 返回三个级别的数据(每个递归迭代一个级别):

类似地,以下查询遍历相同的数据以获取员工 Richard Chan(他在公司管理层中比 Eric Redfield 低一级)的所有员工-经理关系:

WITH RECURSIVE managers (employeeID, employeeName, sectionID, section, lead, leadID)
 AS (SELECT emp_id, fname||' '||lname, section_id, section_name, section_leader, leader_id
      FROM personnel.employees WHERE fname||' '||lname = 'Richard Chan'
 UNION
    SELECT emp_id, fname||' '||lname AS employee_name, section_id, section_name, section_leader, leader_id FROM personnel.employees e
      JOIN managers m ON m.employeeID = e.leader_id)
 SELECT employeeID, employeeName, lead AS 'Reports to', section, leadID from managers ORDER BY sectionID, employeeName;

WITH RECURSIVE 子句像以前一样执行,但这次它在两次迭代后找不到更多数据而退出。因此,主要查询从 managers 返回两个级别的数据:

WITH RECURSIVE 实体化

默认情况下,实体化处于禁用状态。在这种情况下,Vertica 将 WITH RECURSIVE 查询重写为子查询,其数量与所需的递归级别一样多。

如果递归非常深,则大量查询重写可能会产生相当大的开销,从而对性能产生不利影响并耗尽系统资源。在这种情况下,请考虑使用配置参数 WithClauseMaterialization 或提示 ENABLE_WITH_CLAUSE_MATERIALIZATION 来启用实体化。在任何一种情况下,来自所有递归级别的中间结果集都将写入本地临时表。当递归完成时,所有临时表中的中间结果都进行编译并传递给主要查询。

1.6.4 - 非相关和相关的子查询

子查询可以分为以下两种类型:

  • 非相关(简单)子查询获取结果不需要依赖其包含(外)语句。

  • 相关子查询需要来自其外查询的值才能执行。

非相关子查询

非相关子查询的执行不需要依赖外查询。子查询首先执行,然后将结果传递给外查询,例如:

=> SELECT name, street, city, state FROM addresses WHERE state IN (SELECT state FROM states);

Vertica 按以下方式执行此查询:

  1. 执行子查询 SELECT state FROM states(粗体)。

  2. 将子查询结果传递给外查询。

如果子查询解析为一个单行,查询的 WHEREHAVING 子句可以指定非相关子句,如下所示:

在 WHERE 子句中

=> SELECT COUNT(*) FROM SubQ1 WHERE SubQ1.a = (SELECT y from SubQ2);

在 HAVING 子句中

=> SELECT COUNT(*) FROM SubQ1 GROUP BY SubQ1.a HAVING SubQ1.a = (SubQ1.a & (SELECT y from SubQ2)

相关子查询

相关子查询通常在执行之前从外查询获取值。当子查询返回时,它将其结果传递给外查询。

在以下示例中,子查询需要外查询中 addresses.state 列的值。

=> SELECT name, street, city, state FROM addresses
     WHERE EXISTS (SELECT * FROM states WHERE states.state = addresses.state);

Vertica 按以下方式执行此查询:

  1. 子查询提取并评估外子查询记录中的每个 addresses.state 值。

  2. 然后,查询(使用 EXISTS 谓词)检查内部(相关)子查询中的地址。

  3. 由于它使用 EXISTS 谓词,因此查询会在找到第一个匹配项时停止处理。

Vertica 执行此查询时,会将完整查询转换为 JOIN WITH SIPS。

1.6.5 - 修整 FROM 子句子查询

FROM 子句子查询的评估通常早于它们的包含查询。在某些情况下,优化器修整FROM子句查询,使查询可以更高效地执行。

例如,为了针对以下语句创建查询计划,Vertica 查询优化器会先评估表 t1 中的所有记录,然后再评估表 t0 中的记录:

=> SELECT * FROM (SELECT a, MAX(a) AS max FROM (SELECT * FROM t1) AS t0 GROUP BY a);

使用上述查询,优化器可以在内部修整它,如下所示:

=> SELECT * FROM (SELECT a, MAX(a) FROM t1 GROUP BY a) AS t0;

两个查询返回了相同的结果,但修整后的查询的运行速度更快。

修整视图

当查询的 FROM 子句指定视图时,优化器通过将视图替换为视图封装的查询来扩展视图。如果视图包含符合修整条件的子查询,则优化器会生成一个查询计划来修整这些子查询。

修整限制

如果子查询或视图包含以下元素之一,则优化器无法创建修整查询计划:

  • 聚合函数

  • 分析函数

  • 外联接(左外联接、右外联接或全外联接)

  • GROUP BYORDER BYHAVING 子句

  • DISTINCT 关键字

  • LIMITOFFSET 子句

  • UNIONEXCEPTINTERSECT 子句

  • EXISTS 子查询

示例

如果谓词适用于视图或子查询,只要在修整之前评估谓词,即可对修整操作进行优化。下面举两个示例。

视图修整

在本例中,视图 v1 定义如下:

=> CREATE VIEW v1 AS SELECT * FROM a;

以下查询指定此视图:

=> SELECT * FROM v1 JOIN b ON x=y WHERE x > 10;

在没有修整的情况下,优化器按如下方式评估查询:

  1. 评估子查询。

  2. 应用谓词 WHERE x > 10

相反,优化器可以通过在评估子查询之前应用谓词来创建修整查询计划。这减少了优化器的工作,因为它只将记录 WHERE x > 10 返回到包含查询。

Vertica 在内部转换了上述查询,如下所示:

=> SELECT * FROM (SELECT * FROM a) AS t1 JOIN b ON x=y WHERE x > 10;

然后,优化器修整查询:

=> SELECT * FROM a JOIN b ON x=y WHERE x > 10;

子查询修整

以下示例演示 Vertica 如何在 WHERE 子句 IN 子查询中转换 FROM 子句子查询。给定以下查询:

=> SELECT * FROM a
   WHERE b IN (SELECT b FROM (SELECT * FROM t2)) AS D WHERE x=1;

优化器将其修整如下:

=> SELECT * FROM a
   WHERE b IN (SELECT b FROM t2) AS D WHERE x=1;

另请参阅

子查询限制

1.6.6 - UPDATE 和 DELETE 语句中的子查询

您可以在 UPDATEDELETE 语句中嵌套子查询。

UPDATE 子查询

您可以通过在 UPDATE 语句中嵌套子查询,根据其他表中的值更新一个表中的记录。下面的示例通过几个非相关子查询说明了这一点。您可以使用下表重现此示例:

=> CREATE TABLE addresses(cust_id INTEGER, address VARCHAR(2000));
CREATE TABLE
dbadmin=> INSERT INTO addresses VALUES(20,'Lincoln Street'),(30,'Booth Hill Road'),(30,'Beach Avenue'),(40,'Mt. Vernon Street'),(50,'Hillside Avenue');
 OUTPUT
--------
      5
(1 row)

=> CREATE TABLE new_addresses(new_cust_id integer, new_address Boolean DEFAULT 'T');
CREATE TABLE
dbadmin=> INSERT INTO new_addresses VALUES (20),(30),(80);
 OUTPUT
--------
      3
(1 row)

=> INSERT INTO new_addresses VALUES (60,'F');
 OUTPUT
--------
      1

=> COMMIT;
COMMIT

对这些表的查询会返回以下结果:

=> SELECT * FROM addresses;
 cust_id |      address
---------+-------------------
      20 | Lincoln Street
      30 | Beach Avenue
      30 | Booth Hill Road
      40 | Mt. Vernon Street
      50 | Hillside Avenue
(5 rows)

=> SELECT * FROM new_addresses;
 new_cust_id | new_address
-------------+-------------
          20 | t
          30 | t
          80 | t
          60 | f
(4 rows)
  1. 以下 UPDATE 语句使用非相关子查询来联接 customer ID 上的 new_addressesaddresses 记录。UPDATE 在联接的 addresses 记录中设置 'New Address' 值。语句输出表明更新了三行:

    => UPDATE addresses SET address='New Address'
       WHERE cust_id IN (SELECT new_cust_id FROM new_addresses WHERE new_address='T');
     OUTPUT
    --------
    3
    (1 row)
    
  2. 查询 addresses 表,以查看匹配客户 ID 20 和 30 的更改。客户 ID 40 和 50 的地址未更新:

    => SELECT * FROM addresses;
     cust_id |      address
    ---------+-------------------
          40 | Mt. Vernon Street
          50 | Hillside Avenue
          20 | New Address
          30 | New Address
          30 | New Address
    (5 rows)
    
    =>COMMIT;
    COMMIT
    

DELETE 子查询

您可以通过在 DELETE 语句中嵌套子查询,根据其他表中的值删除一个表中的记录。

例如,您想从之前用于更新 addresses 中的记录的 new_addresses 中移除记录。以下 DELETE 语句使用非相关子查询来联接 customer ID 上的 new_addressesaddresses 记录。然后它从表 new_addresses 中删除联接的记录:

=> DELETE FROM new_addresses
    WHERE new_cust_id IN (SELECT cust_id FROM addresses WHERE address='New Address');
 OUTPUT
--------
      2
(1 row)
=> COMMIT;
COMMIT

查询 new_addresses 以确认记录已被删除:

=> SELECT * FROM new_addresses;
 new_cust_id | new_address
-------------+-------------
          60 | f
          80 | t
(2 rows)

1.6.7 - 子查询示例

此主题显示了一些可以编写的子查询。示例使用 VMart 示例数据库。

单行子查询

单行子查询将与单行比较运算符(=、>=、<=、<> 和 <=>)一起使用,而且正好返回一行。

例如,以下查询在 Vmart 数据库中检索工作时间最长的员工的姓名和雇佣日期:

=> SELECT employee_key, employee_first_name, employee_last_name, hire_date
   FROM employee_dimension
   WHERE hire_date = (SELECT MIN(hire_date) FROM employee_dimension);
 employee_key | employee_first_name | employee_last_name | hire_date
--------------+---------------------+--------------------+------------
         2292 | Mary                | Bauer              | 1956-01-11
(1 row)

多行子查询

多行子查询返回多个记录。

例如,以下 IN 子句子查询返回了六个地区薪水最高的员工的姓名:

=> SELECT employee_first_name, employee_last_name, annual_salary, employee_region
    FROM employee_dimension WHERE annual_salary IN
     (SELECT MAX(annual_salary) FROM employee_dimension GROUP BY employee_region)
   ORDER BY annual_salary DESC;
 employee_first_name | employee_last_name | annual_salary |  employee_region
---------------------+--------------------+---------------+-------------------
 Alexandra           | Sanchez            |        992363 | West
 Mark                | Vogel              |        983634 | South
 Tiffany             | Vu                 |        977716 | SouthWest
 Barbara             | Lewis              |        957949 | MidWest
 Sally               | Gauthier           |        927335 | East
 Wendy               | Nielson            |        777037 | NorthWest
(6 rows)

多列子查询

多列子查询返回一个或多个列。有时,子查询的结果集在列对列比较和行对行比较的包含查询中进行评估。

您可以将一些多列子查询替换为联接,也可以将联接替换为多列子查询。例如,以下两个查询会检索向马萨诸塞州客户在线售出的所有产品的销售事务,然后返回了相同的结果集。唯一的区别在于,第一个查询编写为联接,第二个查询编写为子查询。

以下查询返回各地区薪水超过平均薪水的所有员工:

=> SELECT e.employee_first_name, e.employee_last_name, e.annual_salary,
      e.employee_region, s.average
   FROM employee_dimension e,
     (SELECT employee_region, AVG(annual_salary) AS average
      FROM employee_dimension GROUP BY employee_region) AS s
   WHERE  e.employee_region = s.employee_region AND e.annual_salary > s.average
   ORDER BY annual_salary DESC;
 employee_first_name | employee_last_name | annual_salary | employee_region |     average
---------------------+--------------------+---------------+-----------------+------------------
 Doug                | Overstreet         |        995533 | East            |  61192.786013986
 Matt                | Gauthier           |        988807 | South           | 57337.8638902996
 Lauren              | Nguyen             |        968625 | West            | 56848.4274914089
 Jack                | Campbell           |        963914 | West            | 56848.4274914089
 William             | Martin             |        943477 | NorthWest       | 58928.2276119403
 Luigi               | Campbell           |        939255 | MidWest         | 59614.9170454545
 Sarah               | Brown              |        901619 | South           | 57337.8638902996
 Craig               | Goldberg           |        895836 | East            |  61192.786013986
 Sam                 | Vu                 |        889841 | MidWest         | 59614.9170454545
 Luigi               | Sanchez            |        885078 | MidWest         | 59614.9170454545
 Michael             | Weaver             |        882685 | South           | 57337.8638902996
 Doug                | Pavlov             |        881443 | SouthWest       | 57187.2510548523
 Ruth                | McNulty            |        874897 | East            |  61192.786013986
 Luigi               | Dobisz             |        868213 | West            | 56848.4274914089
 Laura               | Lang               |        865829 | East            |  61192.786013986
 ...

您也可以在 FROM、WHERE 和 HAVING 子句中使用 EXCEPTINTERSECT UNION [ALL] 关键字。

以下子查询返回了有关通过商店或在线销售渠道购买商品且购买金额超过 500 美元的所有康涅狄格州客户的信息:

=> SELECT DISTINCT customer_key, customer_name FROM public.customer_dimension
   WHERE customer_key IN (SELECT customer_key FROM store.store_sales_fact
      WHERE sales_dollar_amount > 500
      UNION ALL
      SELECT customer_key FROM online_sales.online_sales_fact
      WHERE sales_dollar_amount > 500)
   AND customer_state = 'CT';
 customer_key |  customer_name
--------------+------------------
          200 | Carla Y. Kramer
          733 | Mary Z. Vogel
          931 | Lauren X. Roy
         1533 | James C. Vu
         2948 | Infocare
         4909 | Matt Z. Winkler
         5311 | John Z. Goldberg
         5520 | Laura M. Martin
         5623 | Daniel R. Kramer
         6759 | Daniel Q. Nguyen
 ...

HAVING 子句子查询

HAVING 子句与 GROUP BY 子句一起使用可过滤 GROUP BY 返回的 select-list 记录。HAVING 子句子查询必须使用布尔比较运算符:=, >, <, <>, <=, >= 并采用以下格式:

SELECT <column, ...>
FROM <table>
GROUP BY <expression>
HAVING <expression>
  (SELECT <column, ...>
   FROM <table>
   HAVING <expression>);

例如,以下语句使用 VMart 数据库,然后返回购买低脂产品的客户数量。请注意,GROUP BY 子句必不可少,因为查询使用了聚合 (COUNT)。

=> SELECT s.product_key, COUNT(s.customer_key) FROM store.store_sales_fact s
   GROUP BY s.product_key HAVING s.product_key IN
     (SELECT product_key FROM product_dimension WHERE diet_type = 'Low Fat');

子查询首先返回所有低脂产品的产品键,然后由外查询统计购买这些产品的客户总数量。

 product_key | count
 -------------+-------
          15 |     2
          41 |     1
          66 |     1
         106 |     1
         118 |     1
         169 |     1
         181 |     2
         184 |     2
         186 |     2
         211 |     1
         229 |     1
         267 |     1
         289 |     1
         334 |     2
         336 |     1
(15 rows)

1.6.8 - 子查询限制

以下限制适用于 Vertica 子查询:

  • 子查询不可在 CREATE PROJECTION 语句的定义查询中使用。

  • 子查询可以在 SELECT 列表中使用,但如果子查询不是包含查询的 GROUP BY 子句的组成部分,则 GROUP BY 或聚合函数不可在查询中使用。例如,以下两个语句会返回错误消息:

    => SELECT y, (SELECT MAX(a) FROM t1) FROM t2 GROUP BY y;
       ERROR:  subqueries in the SELECT or ORDER BY are not supported if the
       subquery is not part of the GROUP BY
    => SELECT MAX(y), (SELECT MAX(a) FROM t1) FROM t2;
       ERROR:  subqueries in the SELECT or ORDER BY are not supported if the
       query has aggregates and the subquery is not part of the GROUP BY
    
  • 支持在 UPDATE 语句中使用子查询,但存在以下例外:

    • 不能使用 SET column = {expression} 来指定子查询。

    • UPDATE 列表中指定的表也不能出现在 FROM 子句中(没有自联接)。

  • FROM 子句子查询需要别名,但表不需要别名。如果表没有别名,查询必须将其中的列命名为 table-name.column-name。但是,在查询中的所有表之间均保持唯一的列名不需要由其表名来限定。

  • 如果 ORDER BY 子句包含在 FROM 子句子查询中,而不是包含在包含查询中,则查询可能会返回意外的排序结果。发生这种情况是因为 Vertica 数据来自多个节点,因此无法保证排序顺序,除非外部查询块指定 ORDER BY 子句。这种行为符合 SQL 标准,但它有别于其他数据库。

  • 多列子查询不能使用 <、>、<=、>= 比较运算符。它们可以使用 <>、!= 和 = 比较运算符。

  • WHERE HAVING 子句子查询必须使用布尔比较运算符:=、>、<、<>、<=、>=。这些子查询可以是非相关和相关子查询。

  • [NOT] INANY 子查询(嵌套在另一个表达式中)在以下情况下不受支持:任一列值为 NULL。例如,在以下语句中,如果表 t1t2 中的列 x 包含 NULL 值,Vertica 将返回运行时错误:

    => SELECT * FROM t1 WHERE (x IN (SELECT x FROM t2)) IS FALSE;
    ERROR:  NULL value found in a column used by a subquery
    
  • Vertica 将在标量子查询(返回多行)的子查询运行期间返回错误消息。

  • 只要子查询不是相关子查询,聚合和 GROUP BY 子句即可在这些子查询中使用。

  • ALL[NOT] IN 下的相关表达式不受支持。

  • OR 下的相关表达式不受支持。

  • 对于使用等式 (=) 谓词联接的子查询,才仅允许使用多重相关。但是,不允许在相关子查询中使用 IN/NOT INEXISTS/NOT EXISTS 谓词:

    => SELECT t2.x, t2.y, t2.z FROM t2 WHERE t2.z NOT IN
           (SELECT t1.z FROM t1 WHERE t1.x = t2.x);
       ERROR: Correlated subquery with NOT IN is not supported
    
  • 如果子查询引用了当前外部查询块中的列,则 WHERE 子句中最多只能使用一个级别的相关子查询。例如,以下查询不受支持,因为 t2.x = t3.x 子查询只能在外部查询中引用表 t1,使其成为相关表达式,因为 t3.x 具有两个以上的级别:

    => SELECT t3.x, t3.y, t3.z FROM t3 WHERE t3.z IN (
         SELECT t1.z FROM t1 WHERE EXISTS (
            SELECT 'x' FROM t2 WHERE t2.x = t3.x) AND t1.x = t3.x);
    ERROR:  More than one level correlated subqueries are not supported
    

    如果按如下所示进行重写,则支持该查询:

    => SELECT t3.x, t3.y, t3.z FROM t3 WHERE t3.z IN
           (SELECT t1.z FROM t1 WHERE EXISTS
             (SELECT 'x' FROM t2 WHERE t2.x = t1.x)
       AND t1.x = t3.x);
    

1.7 - 联接

查询可以合并多个表中的记录或同一个表的多个实例。合并一个或多个表中的记录的查询称为联接。SELECT 语句和子查询中允许联接。

支持的联接类型

Vertica 支持以下联接类型:

  • 内联接(包括自然联接、交叉联接)

  • 左外联接、右外联接和全外联接

  • 等式和范围联接谓词的优化

Vertica 不支持嵌套循环联接。

联接算法

Vertica 的查询优化器使用哈希联接或合并联接算法来实施联接。有关详细信息,请参阅哈希联接与合并联接

1.7.1 - 联接语法

Vertica 支持用于联接表的 ANSI SQL-92 标准,如下所示:

table‑reference [join-type] JOIN table-reference [ ON join-predicate ]

其中,join-type 可以是以下类型之一:

例如:

=> SELECT * FROM T1 INNER JOIN T2 ON T1.id = T2.id;

替代语法选项

Vertica 还支持两种较旧的联接语法约定:

由 WHERE 子句联接谓词指定的联接

INNER JOIN 等效于在 WHERE 子句中指定其联接谓词的查询。例如,此示例和上一个示例返回相同的结果。它们都分别在 T1.idT2.id 列上指定 T1T2 表之间的内部联接。

=> SELECT * FROM T1, T2 WHERE T1.id = T2.id;

JOIN USING 子句

可以使用 JOIN USING 子句在同名列上联接两个表。例如:

=> SELECT * FROM T1 JOIN T2 USING(id);

默认情况下,由 JOIN USING 指定的联接始终是内联接。

SQL-92 联接语法的好处

Vertica 建议您使用 SQL-92 联接语法,原因如下:

  • SQL-92 外联接语法可跨数据库移植;旧语法不能在数据库之间保持一致。

  • SQL-92 语法可更好地控制是在外联接期间还是之后评估谓词。如果使用旧语法,这同样无法在数据库之间保持一致。

  • SQL-92 语法消除了在通过外联接联接两个以上的表这种情况下,联接评估顺序的不确定性。

1.7.2 - 联接条件与筛选条件

如果不使用 SQL-92 语法,联接条件(联接过程中评估的谓词)很难与筛选条件(联接之后评估的谓词)区分开来,而且在某些情况下根本无法表达。使用 SQL-92 时,联接条件和筛选条件会分为两个不同的子句,即 ON 子句和 WHERE 子句,这样查询更易于理解。

  • ON 子句 包含关系运算符(例如 <、<=、>、>=、<>、=、<=>)或用于指定合并左输入关系和右输入关系中哪些记录(例如通过匹配外键与主键)的其他谓词。ON 可用于内联接、左外联接、右外联接和全外联接。交叉联接和联合联接不能使用 ON 子句。

    内联接返回经 ON 子句评估为 TRUE 的左关系和右关系中的所有行对。在左联接中,联接中左关系的所有行出现在结果中;左关系与右关系中不匹配的行也会出现在结果中,但包含在右关系中获取的任何列中的 NULL。同样地,右联接会保留右关系的所有行,而全联接保留左右两个关系的所有行。

  • WHERE 子句 在联接执行之后进行评估。它会筛选 FROM 子句返回的记录,清除掉不符合 WHERE 子句条件的所有记录。

Vertica 自动将外联接转换为内联接(如果合适),允许优化器在更广泛的查询计划集中选择计划,从而实现更好的性能。

1.7.3 - 内联接

内联接基于联接谓词将两个表中的记录组合在一起,并要求第一个表中每个记录在第二个表中都具有匹配的记录。因此,内联接只会返回两个已联接表中符合联接条件的记录。不包含匹配项的记录将从结果集中排除。

内联接采用以下格式:

SELECT column‑list FROM left-join-table
  [INNER] JOIN right-join-table ON join-predicate

如果您省略 INNER 关键字,则 Vertica 假定内联接。内联接是可交换的和关联的。您可以按任何顺序指定表而不更改结果。

示例

以下示例指定表 store.store_dimensionpublic.employee_dimension 之间的内联接,其记录分别在 store_regionemployee_region 列中具有匹配值。

=> SELECT s.store_region, SUM(e.vacation_days) TotalVacationDays
   FROM public.employee_dimension e
   JOIN store.store_dimension s ON s.store_region=e.employee_region
   GROUP BY s.store_region ORDER BY TotalVacationDays;

这种联接也可以表示如下:

=> SELECT s.store_region, SUM(e.vacation_days) TotalVacationDays
    FROM public.employee_dimension e, store.store_dimension s
    WHERE s.store_region=e.employee_region
    GROUP BY s.store_region ORDER BY TotalVacationDays;

两个查询返回相同的结果集:

 store_region | TotalVacationDays
--------------+-------------------
 NorthWest    |             23280
 SouthWest    |            367250
 MidWest      |            925938
 South        |           1280468
 East         |           1952854
 West         |           2849976
(6 rows)

如果联接的内表 store.store_dimension 有任何行的 store_region 值与表 public.employee_dimension 中的 employee_region 值不匹配,则从结果集中排除这些行。要包含该行,您可以指定外联接

1.7.3.1 - 等联接和非等联接

Vertica 支持含有匹配列值和非匹配列值的任意联接表达式。例如:

SELECT * FROM fact JOIN dim ON fact.x = dim.x;
SELECT * FROM fact JOIN dim ON fact.x > dim.y;
SELECT * FROM fact JOIN dim ON fact.x <= dim.y;
SELECT * FROM fact JOIN dim ON fact.x <> dim.y;
SELECT * FROM fact JOIN dim ON fact.x <=> dim.y;

等联接以等式(匹配列值)为基础。此等式以等于号 (=) 表示,它在使用 SQL-92 语法的 ON 子句或使用更旧的联接语法的 WHERE 子句中用作比较运算符。

下文中第一个示例使用 SQL-92 和 ON 子句将线上销售额表与使用呼叫中心键的呼叫中心表联接在一起,然后查询返回等于 156 的销售日期键:

=> SELECT sale_date_key, cc_open_date FROM online_sales.online_sales_fact
   INNER JOIN online_sales.call_center_dimension
   ON (online_sales.online_sales_fact.call_center_key =
    online_sales.call_center_dimension.call_center_key
   AND sale_date_key = 156);
 sale_date_key | cc_open_date
---------------+--------------
           156 | 2005-08-12
(1 row)

第二个示例使用更旧的联接语法和 WHERE 子句联接上述表,获得了相同的结果:

=> SELECT sale_date_key, cc_open_date
    FROM online_sales.online_sales_fact, online_sales.call_center_dimension
   WHERE online_sales.online_sales_fact.call_center_key =
      online_sales.call_center_dimension.call_center_key
   AND sale_date_key = 156;
 sale_date_key | cc_open_date
---------------+--------------
           156 | 2005-08-12
(1 row)

Vertica 还允许使用包含复合(多列)主键和外键的表。例如,要创建一对包含多列键的表:

=> CREATE TABLE dimension(pk1 INTEGER NOT NULL, pk2 INTEGER NOT NULL);=> ALTER TABLE dimension ADD PRIMARY KEY (pk1, pk2);
=> CREATE TABLE fact (fk1 INTEGER NOT NULL, fk2 INTEGER NOT NULL);
=> ALTER TABLE fact ADD FOREIGN KEY (fk1, fk2) REFERENCES dimension (pk1, pk2);

要联接使用复合键的表,您必须用 Boolean AND 运算符连接两个联接谓词。例如:

=> SELECT * FROM fact f JOIN dimension d ON f.fk1 = d.pk1 AND f.fk2 = d.pk2;

您可以通过含有 NULL=NULL 联接的 <=> 运算符的表达式编写查询。

=> SELECT * FROM fact JOIN dim ON fact.x <=> dim.y;

<=> 运算符与 = 运算符一样,执行等式比较,但它在两个操作数都为 NULL 时返回 true(而不是 NULL),并在一个操作数为 NULL 时返回 false(而不是 NULL)。

=> SELECT 1 <=> 1, NULL <=> NULL, 1 <=> NULL;
 ?column? | ?column? | ?column?
----------+----------+----------
 t        | t        | f
(1 row)

比较 <=> 运算符和 = 运算符:

=> SELECT 1 = 1, NULL = NULL, 1 = NULL;
 ?column? | ?column? | ?column?
----------+----------+----------
 t        |          |
(1 row)

编写联接时,它可帮助您提前知道哪些列包含 NULL 值。例如,员工的雇佣日期就不是一个好选择,因为雇佣日期不可能被忽略。但是,如果一些员工按小时领工资,而一些员工按薪水领工资,这种情况下,可以使用每小时工资列。如果您不确定给定表中的列值,而且希望检查一下,请键入以下命令:

=> SELECT COUNT(*) FROM tablename WHERE columnname IS NULL;

1.7.3.2 - 自然联接

自然联接只是一种包含隐式联接谓词的联接。自然联接可以是内联接、左外联接、右外联接或全外联接,它采用以下格式:

SELECT column‑list FROM left-join-table
NATURAL [ INNER | LEFT OUTER | RIGHT OUTER | FULL OUTER ] JOIN right-join-table

默认情况下,自然联接为自然内联接;但自然联接也可以为自然左/右/全外联接。内联接和自然联接的主要区别在于,内联接具有显式联接条件,而自然联接的条件的形成方式是,对具有相同名称和兼容数据类型的表中所有列对进行匹配,使自然联接变为等联接,因为联接条件在普通的列之间是相等的。(如数据类型不兼容,Vertica 将返回错误。)

当 T2 列 val 大于 5 时,以下查询是 T1 表和 T2 表之间的一个简单的自然联接:

=> SELECT * FROM T1 NATURAL JOIN T2 WHERE T2.val > 5;

store_sales_fact 表和 product_dimension 表有两列共享相同的名称和数据类型:product_key product_version。以下示例在这两个表的共享列处创建自然联接:

=> SELECT product_description, sales_quantity FROM store.store_sales_fact
   NATURAL JOIN public.product_dimension;

以下三个查询返回相同的结果,分别表示为基本查询、内联接和自然联接。仅当 store_sales_fact 表和 store_dimension 表中的公共属性为 store_key 时,表的表达式才等效。如果两个表都具有名为 store_key 的列,则自然联接也会具有 store_sales_fact.store_key = store_dimension.store_key 联接条件。由于所有三个实例的结果都相同,因此它们仅显示在第一个(基本)查询中:

=> SELECT store_name FROM store.store_sales_fact, store.store_dimension
   WHERE store.store_sales_fact.store_key = store.store_dimension.store_key
   AND store.store_dimension.store_state = 'MA' ORDER BY store_name;
 store_name
------------
 Store11
 Store128
 Store178
 Store66
 Store8
 Store90
(6 rows)

作为内联接编写的查询:

=> SELECT store_name FROM store.store_sales_fact
   INNER JOIN store.store_dimension
   ON (store.store_sales_fact.store_key = store.store_dimension.store_key)
   WHERE store.store_dimension.store_state = 'MA' ORDER BY store_name;

对于自然联接,通过对由相同的列名称联接的两个表中所有列进行比较,隐式显示联接谓词。结果集对表示一对命名相同的列仅保留一列。

=> SELECT store_name FROM store.store_sales_fact
   NATURAL JOIN store.store_dimension
   WHERE store.store_dimension.store_state = 'MA' ORDER BY store_name;

1.7.3.3 - 交叉联接

交叉联接是一种编写起来最简单的联接,但运行速度通常不是最快的,因为它们包含两个表中记录的所有可能组合。交叉联接不包含任何联接条件,它会返回笛卡尔积,其中结果集中的行数等于第一个表中的行数与第二个表中的行数之乘积。

以下查询返回了促销表和商店销售表的所有可能组合:

=> SELECT * FROM promotion_dimension CROSS JOIN store.store_sales_fact;

由于此示例返回了超过 6 亿个记录,因此,许多交叉联接的结果非常大,不易管理。但是,交叉联接非常有用,例如当您想返回一个单行结果集时。

隐式联接与显式联接

Vertica 建议您不要编写隐式交叉联接(FROM 子句中以逗号分隔的表)。这些查询可能会意外忽略了某个联接谓词。

以下查询隐式交叉联接 promotion_dimensionstore.store_sales_fact 表:

=> SELECT * FROM promotion_dimension, store.store_sales_fact;

更好的做法是明确表达此交叉联接,如下所示:

=> SELECT * FROM promotion_dimension CROSS JOIN store.store_sales_fact;

示例

以下示例创建了两个小表格及其超投影,然后对这两个表运行交叉联接:

=> CREATE TABLE employee(employee_id INT, employee_fname VARCHAR(50));
=> CREATE TABLE department(dept_id INT, dept_name VARCHAR(50));
=> INSERT INTO employee VALUES (1, 'Andrew');
=> INSERT INTO employee VALUES (2, 'Priya');
=> INSERT INTO employee VALUES (3, 'Michelle');
=> INSERT INTO department VALUES (1, 'Engineering');
=> INSERT INTO department VALUES (2, 'QA');
=> SELECT * FROM employee CROSS JOIN department;

在结果集中,交叉联接检索了第一个表中的记录,然后为第二个表中每个行创建一个新行。接着,它对第一个表中的下一个记录重复此操作,依此类推。

 employee_id | employee_name | dept_id | dept_name
 -------------+---------------+---------+-----------
           1 | Andrew        |       1 |  Engineering
           2 | Priya         |       1 |  Engineering
           3 | Michelle      |       1 |  Engineering
           1 | Andrew        |       2 |  QA
           2 | Priya         |       2 |  QA
           3 | Michelle      |       2 |  QA
(6 rows)

1.7.4 - 外联接

外联接扩展了内联接的功能。通过外联接,您可以保留一个或两个表中在非保留表中没有匹配行的行。外联接采用以下格式:

SELECT column‑list FROM left-join-table
[ LEFT | RIGHT | FULL ] OUTER JOIN right-join-table ON join-predicate

左外联接

左外联接返回左联接(保留)表 T1 的完整记录集以及右联接(非保留)表 T2 中的匹配记录(如适用)。如果 Vertica 未找到匹配项,它会使用 null 值扩展右侧列 (T2)。

=> SELECT * FROM T1 LEFT OUTER JOIN T2 ON T1.x = T2.x;

要排除 T2 中不匹配的值,请编写相同的左外联接,但使用 WHERE 子句过滤掉右表中不需要的记录:

=> SELECT * FROM T1 LEFT OUTER JOIN T2
   ON T1.x = T2.x WHERE T2.x IS NOT NULL;

以下示例使用左外联接扩充含有不完整电话号码维度的电话呼叫详细记录。然后,它过滤掉已知的不是来自马萨诸塞州的结果:

=> SELECT COUNT(*) FROM calls LEFT OUTER JOIN numbers
   ON calls.to_phone = numbers.phone WHERE NVL(numbers.state, '') <> 'MA';

右外联接

右外联接返回右联接(保留)表的完整记录集以及左联接(非保留)表中的匹配值。如果 Vertica 从左联接表 (T1) 中未找到匹配记录,则对于 T1 中没有匹配值的任何记录,将在 T1 列中显示 NULL 值。因此,右联接与左联接相似,只是用于联接的表的顺序相反而已。

=> SELECT * FROM T1 RIGHT OUTER JOIN T2 ON T1.x = T2.x;

上述查询等同于以下查询,其中 T1 RIGHT OUTER JOIN T2 = T2 LEFT OUTER JOIN T1

=> SELECT * FROM T2 LEFT OUTER JOIN T1 ON T2.x = T1.x;

以下示例会标识 没有 下单的客户:

=> SELECT customers.customer_id FROM orders RIGHT OUTER JOIN customers
   ON orders.customer_id = customers.customer_id
   GROUP BY customers.customer_id HAVING COUNT(orders.customer_id) = 0;

全外联接

全外联接返回左右两个外联接的结果。联接表包含两个表的所有记录,包括联接任何一侧中的 NULL(缺少匹配)。它非常有用,例如,如果您想查看哪些员工已分配到某个特定部门以及已有一名员工的所有部门,但是您还想查看哪些员工没有分配到某个特定部门以及没有员工的所有部门:

=> SELECT employee_last_name, hire_date FROM  employee_dimension emp
   FULL OUTER JOIN department dept ON emp.employee_key = dept.department_key;

注意

Vertica 还支持以下联接,即外部(保留)表或子查询在一个以上节点中进行复制,而内部(非保留)表或子查询跨多个节点进行分段。例如,在以下查询中,事实表(大多数情况下已分段)出现在联接的非保留表中,这是允许的:

=> SELECT sales_dollar_amount, transaction_type, customer_name
    FROM store.store_sales_fact f RIGHT JOIN customer_dimension d
   ON f.customer_key = d.customer_key;
 sales_dollar_amount | transaction_type | customer_name
---------------------+------------------+---------------
                 252 | purchase         | Inistar
                 363 | purchase         | Inistar
                 510 | purchase         | Inistar
                -276 | return           | Foodcorp
                 252 | purchase         | Foodcorp
                 195 | purchase         | Foodcorp
                 290 | purchase         | Foodcorp
                 222 | purchase         | Foodcorp
                     |                  | Foodgen
                     |                  | Goldcare
(10 rows)

1.7.5 - 控制联接输入

默认情况下,优化器使用自己的内部逻辑来确定将一个表作为内部输入还是外部输入联接到另一个表。有时,优化器可能选择将更大的表作为联接的内部输入。但这样做会导致性能和并发问题。

如果配置参数 EnableForceOuter 设置为 1,则您可以通过 ALTER TABLE..FORCE OUTER 控制特定表的联接输入。FORCE OUTER 选项会在 TABLES 系统表中修改表的 force_outer 设置。实施联接时,Vertica 会比较参与联接的表的 force_outer 设置:

  • 如果表设置不同,Vertica 会使用它们来设置联接输入:

    • 相比其他表具有较低 force_outer 设置的表会作为内部输入联接到这些表。

    • 相比其他表具有较高 force_outer 设置的表会作为外部输入联接到这些表。

  • 如果所有表设置都相同,Vertica 会忽略它们,然后自行决定如何构建联接。

对于所有新定义的表,force_outer 列最初都设置为 5。您可以使用 ALTER TABLE..FORCE OUTERforce_outer 重置为等于或大于 0 的值。例如,您可以将 abcxyz 表的 force_outer 设置分别改为 3 和 8:

=> ALTER TABLE abc FORCE OUTER 3;
=> ALTER TABLE xyz FORCE OUTER 8;

根据这些设置,优化器会将 abc 作为内部输入联接到 force_outer 值大于 3 的任何表。优化器会将 xyz 作为外部输入联接到 force_outer 值小于 8 的任何表。

投影继承

直接查询投影时,它会继承其锚表的 force_outer 设置。然后查询会在联接到另一个投影后使用此设置。

启用强制联接输入

配置参数 EnableForceOuter 决定了 Vertica 是否使用表的 force_outer 值来实施联接。默认情况下,此参数设置为 0,并且强制联接输入处于禁用状态。您可以通过 ALTER SESSIONALTER DATABASE 分别在会话和数据库范围中启用强制联接输入:

=> ALTER SESSION SET EnableForceOuter = { 0 | 1 };
=> ALTER DATABASE db-name SET EnableForceOuter =  { 0 | 1 };

如果 EnableForceOuter 设置为 0,则 ALTER TABLE..FORCE OUTER 语句会返回以下警告:

查看强制联接输入

EXPLAIN 生成的查询计划会指示 EnableForceOuter 配置参数是否已启用。联接查询可能会包含 force_outer 设置小于或大于默认值 5 的表。在这种情况下,查询计划会包含相关联接查询的 Force outer level 字段。

例如,以下查询联接了 store.store_sales 表和 public.products 表,但这两个表具有相同的 force_outer 设置 (5)。EnableForceOuter已启用,如生成的查询计划中所示:

=> EXPLAIN SELECT s.store_key, p.product_description, s.sales_quantity, s.sale_date
   FROM store.store_sales s JOIN public.products p ON s.product_key=p.product_key
   WHERE s.sale_date='2014-12-01' ORDER BY s.store_key, s.sale_date;

 EnableForceOuter is on
 Access Path:
 +-SORT [Cost: 7K, Rows: 100K (NO STATISTICS)] (PATH ID: 1)
 |  Order: sales.store_key ASC, sales.sale_date ASC
 |  Execute on: All Nodes
 | +---> JOIN HASH [Cost: 5K, Rows: 100K (NO STATISTICS)] (PATH ID: 2) Outer (BROADCAST)(LOCAL ROUND ROBIN)
 | |      Join Cond: (sales.product_key = products.product_key)
 | |      Execute on: All Nodes
 | | +-- Outer -> STORAGE ACCESS for sales [Cost: 2K, Rows: 100K (NO STATISTICS)] (PATH ID: 3)
 | | |      Projection: store.store_sales_b0
 | | |      Materialize: sales.sale_date, sales.store_key, sales.product_key, sales.sales_quantity
 | | |      Filter: (sales.sale_date = '2014-12-01'::date)
 | | |      Execute on: All Nodes
 | | +-- Inner -> STORAGE ACCESS for products [Cost: 177, Rows: 60K (NO STATISTICS)] (PATH ID: 4)
 | | |      Projection: public.products_b0
 | | |      Materialize: products.product_key, products.product_description
 | | |      Execute on: All Nodes

以下 ALTER TABLE 语句将 public.productsforce_outer 设置重置为 1:

=> ALTER TABLE public.products FORCE OUTER 1;
ALTER TABLE

此时,为上述联接重新生成的查询包含 Force outer level 字段,并将 public.products 指定为内部输入:

=> EXPLAIN SELECT s.store_key, p.product_description, s.sales_quantity, s.sale_date
   FROM store.store_sales s JOIN public.products p ON s.product_key=p.product_key
   WHERE s.sale_date='2014-12-01' ORDER BY s.store_key, s.sale_date;

 EnableForceOuter is on
 Access Path:
 +-SORT [Cost: 7K, Rows: 100K (NO STATISTICS)] (PATH ID: 1)
 |  Order: sales.store_key ASC, sales.sale_date ASC
 |  Execute on: All Nodes
 | +---> JOIN HASH [Cost: 5K, Rows: 100K (NO STATISTICS)] (PATH ID: 2) Outer (BROADCAST)(LOCAL ROUND ROBIN)
 | |      Join Cond: (sales.product_key = products.product_key)
 | |      Execute on: All Nodes
 | | +-- Outer -> STORAGE ACCESS for sales [Cost: 2K, Rows: 100K (NO STATISTICS)] (PATH ID: 3)
 | | |      Projection: store.store_sales_b0
 | | |      Materialize: sales.sale_date, sales.store_key, sales.product_key, sales.sales_quantity
 | | |      Filter: (sales.sale_date = '2014-12-01'::date)
 | | |      Execute on: All Nodes
 | | +-- Inner -> STORAGE ACCESS for products [Cost: 177, Rows: 60K (NO STATISTICS)] (PATH ID: 4)
 | | |      Projection: public.products_b0
 | | |      Force outer level: 1
 | | |      Materialize: products.product_key, products.product_description
 | | |      Execute on: All Nodes

如果将 public.productsforce_outer 设置更改为 8,Vertica 会创建将 public.products 指定为外部输入的另一个查询计划:

=> ALTER TABLE public.products FORCE OUTER 8;
ALTER TABLE

=> EXPLAIN SELECT s.store_key, p.product_description, s.sales_quantity, s.sale_date
   FROM store.store_sales s JOIN public.products p ON s.product_key=p.product_key
   WHERE s.sale_date='2014-12-01' ORDER BY s.store_key, s.sale_date;


EnableForceOuter is on
 Access Path:
 +-SORT [Cost: 7K, Rows: 100K (NO STATISTICS)] (PATH ID: 1)
 |  Order: sales.store_key ASC, sales.sale_date ASC
 |  Execute on: All Nodes
 | +---> JOIN HASH [Cost: 5K, Rows: 100K (NO STATISTICS)] (PATH ID: 2) Inner (BROADCAST)
 | |      Join Cond: (sales.product_key = products.product_key)
 | |      Materialize at Output: products.product_description
 | |      Execute on: All Nodes
 | | +-- Outer -> STORAGE ACCESS for products [Cost: 20, Rows: 60K (NO STATISTICS)] (PATH ID: 3)
 | | |      Projection: public.products_b0
 | | |      Force outer level: 8
 | | |      Materialize: products.product_key
 | | |      Execute on: All Nodes
 | | |      Runtime Filter: (SIP1(HashJoin): products.product_key)
 | | +-- Inner -> STORAGE ACCESS for sales [Cost: 2K, Rows: 100K (NO STATISTICS)] (PATH ID: 4)
 | | |      Projection: store.store_sales_b0
 | | |      Materialize: sales.sale_date, sales.store_key, sales.product_key, sales.sales_quantity
 | | |      Filter: (sales.sale_date = '2014-12-01'::date)
 | | |      Execute on: All Nodes

限制

Vertica 在执行以下操作时会忽略 force_outer 设置:

  • 外联接:无论已联接表的 force_outer 设置如何,Vertica 通常都会遵循 OUTER JOIN 子句。

  • MERGE 语句联接。

  • 包含 SYNTACTIC_JOIN 提示的查询。

  • 半联接查询,例如 SEMI JOIN

  • 联接到子查询,其中有一个子查询总是按 force_outer 设置为 5 进行处理,而不论在此子查询中已联接表的 force_outer 设置如何。此设置决定了相对于其他联接输入,将子查询指定为内部还是外部输入。如果联接两个子查询,优化器会决定哪个是内部输入,哪个是外部输入。

1.7.6 - 范围联接

Vertica 为联接 ON 子句中的 <、<=、>、>= 和 BETWEEN 谓词进行了性能优化。如果一个表的列限定在另一个表的两个列所指定的范围内,这些优化将特别有用。

键范围

多个连续的键值可映射到相同的维度值。例如,以 IPv4 地址表及其所有者为例。因为大型 IP 地址子网(范围)可属于同一个所有者,所以此维度可表示为:

=> CREATE TABLE ip_owners(
      ip_start INTEGER,
      ip_end INTEGER,
      owner_id INTEGER);
=> CREATE TABLE clicks(
      ip_owners INTEGER,
      dest_ip INTEGER);

将点击流与其目标相关联的查询可以使用与以下使用范围优化的联接类似的联接:

=> SELECT owner_id, COUNT(*) FROM clicks JOIN ip_owners
   ON clicks.dest_ip BETWEEN ip_start AND ip_end
   GROUP BY owner_id;

要求

<、<=、>、>= 或 BETWEEN 运算符必须作为最高级别的连接谓词显示,才能使范围联接优化起作用,如以下示例所示:

`BETWEEN `作为唯一的谓词:

```
=> SELECT COUNT(*) FROM fact JOIN dim
    ON fact.point BETWEEN dim.start AND dim.end;

```

比较运算符作为最高级别的谓词(在 `AND` 内):

```
=> SELECT COUNT(*) FROM fact JOIN dim
    ON fact.point > dim.start AND fact.point < dim.end;

```

`BETWEEN `作为最高级别的谓词(在 `AND` 内):

```
=> SELECT COUNT(*) FROM fact JOIN dim
   ON (fact.point BETWEEN dim.start AND dim.end) AND fact.c <> dim.c;

```

查询未优化,因为 `OR` 是最高级别的谓词(反意连接词):

```
=> SELECT COUNT(*) FROM fact JOIN dim
   ON (fact.point BETWEEN dim.start AND dim.end) OR dim.end IS NULL;

```

注意

  • 在许多用例中,范围联接查询中的表达式都得到了优化。

  • 如果范围列可以包含 NULL 值(指示它们是开放式的),则将 NULL 替换为非常大或非常小的值,即可使用范围联接优化:

    => SELECT COUNT(*) FROM fact JOIN dim
       ON fact.point BETWEEN NVL(dim.start, -1) AND NVL(dim.end, 1000000000000);
    
  • 如果同一个 ON 子句中有一个以上范围谓词集,谓词的指定顺序可能会影响优化的效果:

    => SELECT COUNT(*) FROM fact JOIN dim ON fact.point1 BETWEEN dim.start1 AND dim.end1
       AND fact.point2 BETWEEN dim.start2 AND dim.end2;
    

    优化器会选择第一个范围进行优化,因此编写查询,确保让您最想优化的范围最先出现在语句中。

  • 物理架构的任何特性都不会直接影响范围联接优化的使用;不需要对架构进行调试即可受益于优化。

  • 范围联接优化可应用于不包含任何其他谓词的联接,也可应用于 HASH MERGE 联接。

  • 要确定优化是否正在使用,请在 EXPLAIN 计划中搜索 RANGE

1.7.7 - 事件序列联接

事件序列联接是一个 Vertica SQL 扩展,它可以在两个系列的度量间隔并不完全一致(例如时间戳不匹配)时分析这两个系列。您可以直接比较两个系列的值,而不是将系列都标准化为相同的度量间隔。

事件序列联接是 外联接 的扩展,但它不会在存在不匹配时用 NULL 值填充非保留侧,而是用根据以前值获得的插值填充非保留侧。

编写常规联接与事件序列联接的不同之处在于 ON 子句中使用的 INTERPOLATE 谓词。例如,以下两个语句就说明了这一不同之处,更多详情请参阅编写事件系列联接

与常规联接类似,事件序列联接具有内部和外部联接模式,这在之后的主题中会进行介绍。

有关完整的语法(包括注释和限制)请参阅 INTERPOLATE

1.7.7.1 - 事件系列联接示例的示例架构

如果您不打算运行查询,只想查看示例,则可以跳过此主题,直接转至编写事件系列联接

hTicks 表和 aTicks 表的架构

后面的示例使用以下 hTicks 表和 aTicks 表的架构:

尽管 TIMESTAMP 更常用于事件序列列,但为了让输出更简单,此主题中的示例使用了 TIME。

这两个表的输出:

显示出间隙的示例查询

完全外部联接显示了时间戳中的间隙:

=> SELECT * FROM hTicks h FULL OUTER JOIN aTicks a ON h.time = a.time;
 stock |   time   | price | stock |   time   | price
-------+----------+-------+-------+----------+--------
 HPQ   | 12:00:00 | 50.00 | ACME  | 12:00:00 | 340.00
 HPQ   | 12:01:00 | 51.00 |       |          |
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 333.80
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 340.20
 HPQ   | 12:06:00 | 52.00 |       |          |
       |          |       | ACME  | 12:03:00 | 340.10
(6 rows)

Bid 表和 Asks 表的架构

后面的示例使用以下 hTicks 表和 aTicks 表。

这两个表的输出:

显示出间隙的示例查询

完全外部联接显示了时间戳中的间隙:

=> SELECT * FROM bid b FULL OUTER JOIN ask a ON b.time = a.time;
 stock |   time   | price  | stock |   time   | price
-------+----------+--------+-------+----------+--------
 HPQ   | 12:00:00 | 100.10 | ACME  | 12:00:00 |  80.00
 HPQ   | 12:01:00 | 100.00 | HPQ   | 12:01:00 | 101.00
 ACME  | 12:00:00 |  80.00 | ACME  | 12:00:00 |  80.00
 ACME  | 12:03:00 |  79.80 |       |          |
 ACME  | 12:05:00 |  79.90 |       |          |
       |          |        | ACME  | 12:02:00 |  75.00
(6 rows)

1.7.7.2 - 编写事件系列联接

此主题中的示例包含时间戳不匹配项,就像在现实情形中可能会遇到的一样;例如,在没有交易时,股票在一段时间内可能会处于非活动状态,这时要想比较时间戳不匹配的两支股票会有一定挑战。

hTicks 表和 aTicks 表

示例 ticks 架构所述,表 hTicks 在 12:02、12:03 和 12:04 缺少输入行,表 aTicks 在 12:01、12:02 和 12:04 缺少输入行。

通过全外联接查询事件系列

此查询使用传统的全外联接,在 hTicks 表和 aTicks 表之间找到了位于 12:00 和 12:05 的匹配项,然后用 NULL 值填充缺失的数据点。

=> SELECT * FROM hTicks h FULL OUTER JOIN aTicks a ON (h.time = a.time);
 stock |   time   | price | stock |   time   | price
-------+----------+-------+-------+----------+--------
 HPQ   | 12:00:00 | 50.00 | ACME  | 12:00:00 | 340.00
 HPQ   | 12:01:00 | 51.00 |       |          |
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 333.80
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 340.20
 HPQ   | 12:06:00 | 52.00 |       |          |
       |          |       | ACME  | 12:03:00 | 340.10
(6 rows)

要为缺失的数据点将空白替换为内插值,可使用 INTERPOLATE 谓词创建 事件序列联接。联接条件仅限于 ON 子句,该子句会在两个输入表的时间戳列中对等同谓词求值。换句话说,对于外表 hTicks 中的每个行,为内表 aTicks 中每个行的每个组合对 ON 子句谓词进行求值。

简单地重写全外联接可将 INTERPOLATE 谓词与所需的 PREVIOUS VALUE 关键字结合使用。请注意,对于事件序列数据,在事件序列数据上执行全外联接是最常见的场景,在此场景中,您可以同时保留两个表中的所有行。

=> SELECT * FROM hTicks h FULL OUTER JOIN aTicks a
   ON (h.time INTERPOLATE PREVIOUS VALUE a.time);

Vertica 使用此表以前的值内插缺失数据(在全外联接中显示为 NULL):

![event series join](/images/hticks1.png)

如果查看普通全外联接的输出,您可以发现这两个表在 12:00 和 12:05 时间列中存在一个匹配,但在 12:01,则不存在 ACME 的条目记录。因此,相关操作会根据 aTicks 表中以前的值为 ACME (ACME,12:00,340) 内插一个值。

通过左外联接查询事件系列

您也可以使用左外联接和右外联接。例如,您可能决定仅保留 hTicks 的值。因此,您将写入左外联接:

=> SELECT * FROM hTicks h LEFT OUTER JOIN aTicks a
   ON (h.time INTERPOLATE PREVIOUS VALUE a.time);
 stock |   time   | price | stock |   time   | price
-------+----------+-------+-------+----------+--------
 HPQ   | 12:00:00 | 50.00 | ACME  | 12:00:00 | 340.00
 HPQ   | 12:01:00 | 51.00 | ACME  | 12:00:00 | 340.00
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 333.80
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 340.20
 HPQ   | 12:06:00 | 52.00 | ACME  | 12:05:00 | 340.20
(5 rows)

以下数据与使用传统的左外联接得到的数据相似:

=> SELECT * FROM hTicks h LEFT OUTER JOIN aTicks a ON h.time = a.time;
 stock |   time   | price | stock |   time   | price
-------+----------+-------+-------+----------+--------
 HPQ   | 12:00:00 | 50.00 | ACME  | 12:00:00 | 340.00
 HPQ   | 12:01:00 | 51.00 |       |          |
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 333.80
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 340.20
 HPQ   | 12:06:00 | 52.00 |       |          |
(5 rows)

请注意,右外联接与保存表的行为相同,只是顺序相反。

通过内联接查询事件系列

请注意,如果忽视所有空白,INNER 事件序列联接的行为方式与普通的 ANSI SQL-99 联接相同。因此,这时不会内插任何内容,而且以下两个查询等效,都会返回相同的结果集。

普通的内联接:

=> SELECT * FROM HTicks h JOIN aTicks a
    ON (h.time INTERPOLATE PREVIOUS VALUE a.time);
 stock |   time   | price | stock |   time   | price
-------+----------+-------+-------+----------+--------
 HPQ   | 12:00:00 | 50.00 | ACME  | 12:00:00 | 340.00
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 333.80
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 340.20
(3 rows)

事件序列内联接:

=> SELECT * FROM hTicks h INNER JOIN aTicks a ON (h.time = a.time);
 stock |   time   | price | stock |   time   | price
-------+----------+-------+-------+----------+--------
 HPQ   | 12:00:00 | 50.00 | ACME  | 12:00:00 | 340.00
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 333.80
 HPQ   | 12:05:00 | 51.00 | ACME  | 12:05:00 | 340.20
(3 rows)

Bid 表和 Ask 表

使用 bid 表和 ask 表的示例架构,编写全外联接以内插缺失的数据点:

=> SELECT * FROM bid b FULL OUTER JOIN ask a
    ON (b.stock = a.stock AND b.time INTERPOLATE PREVIOUS VALUE a.time);

在下列输出中,股票 HPQ 的第一行显示了 NULL,因为在 12:01 之前不存在 HPQ 的条目记录。

 stock |   time   | price  | stock |   time   | price
-------+----------+--------+-------+----------+--------
 ACME  | 12:00:00 |  80.00 | ACME  | 12:00:00 |  80.00
 ACME  | 12:00:00 |  80.00 | ACME  | 12:02:00 |  75.00
 ACME  | 12:03:00 |  79.80 | ACME  | 12:02:00 |  75.00
 ACME  | 12:05:00 |  79.90 | ACME  | 12:02:00 |  75.00
 HPQ   | 12:00:00 | 100.10 |       |          |
 HPQ   | 12:01:00 | 100.00 | HPQ   | 12:01:00 | 101.00
(6 rows)

另外,请注意 ask 表的同一个行 (ACME,12:02,75) 出现了三次。第一次出现是因为 bid 表中没有 ask 中的行,因此 Vertica 使用 12:02 的 ACME 值 (75.00) 插入了缺失值。第二次出现是因为 bid 中的行 (ACME,12:05,79.9) 在 ask 中没有相应的匹配项。ask 中包含 (ACME,12:02,75) 的行是最接近的行,因此使用它来插入值。

如果编写普通的全外联接,则可以发现哪些地方出现了不匹配的时间戳:

=> SELECT * FROM bid b FULL OUTER JOIN ask a ON (b.time = a.time);
 stock |   time   | price  | stock |   time   | price
-------+----------+--------+-------+----------+--------
 ACME  | 12:00:00 |  80.00 | ACME  | 12:00:00 |  80.00
 ACME  | 12:03:00 |  79.80 |       |          |
 ACME  | 12:05:00 |  79.90 |       |          |
 HPQ   | 12:00:00 | 100.10 | ACME  | 12:00:00 |  80.00
 HPQ   | 12:01:00 | 100.00 | HPQ   | 12:01:00 | 101.00
       |          |        | ACME  | 12:02:00 |  75.00
(6 rows)

2 - 查询优化

将查询发送到 Vertica 进行处理时,Vertica 查询优化器会自动选择一系列操作来计算请求的结果。这些操作的总称就是查询计划。选择的操作会显著影响计算查询结果所需的资源数量以及整体运行时性能。最佳性能在很大程度上取决于可用于给定查询的投影。

此部分介绍优化器使用的不同操作以及如何提高优化器性能。

2.1 - 提高查询性能的初始过程

要优化查询性能,首先要执行以下任务:

  1. 运行 Database Designer。

  2. 主动检查查询事件。

  3. 查看查询计划。

运行 Database Designer

Database Designer 会为数据库创建可提供最优查询性能的物理架构。首次运行 Database Designer 时,您应该创建全面设计,其中包括作为设计起点的相关示例查询和数据。如果以后出现性能问题,请考虑加载您经常运行的其他查询,并重新运行 Database Designer 来创建增量设计。

运行 Database Designer 时,请选择“更新统计数据 (Update Statistics)”选项。Vertica 查询优化器使用数据的统计数据来创建查询计划。统计数据可以帮助优化器确定:

  • 多个合格的投影以响应查询

  • 执行联接所采用的最佳顺序

  • 数据分布算法,例如广播和重新分段

如果您的统计数据已过时,请运行 Vertica 函数 ANALYZE_STATISTICS 来更新特定架构、表或列的统计数据。有关详细信息,请参阅收集数据库统计信息

主动检查查询事件

QUERY_EVENTS 系统表返回有关查询计划、优化和执行事件的信息。

EVENT_TYPE 列提供了各种事件类型:

查看查询计划

查询计划由一系列类似步骤的 路径组成,而 Vertica 查询优化器会选择这些路径来访问或修改 Vertical 数据库中的信息。获取查询计划信息的方式有两种:

  • 运行 EXPLAIN 命令。每个步骤(路径)都代表了优化器为其执行策略使用的一个操作。

  • 查询 QUERY_PLAN_PROFILES 系统表:此表详细介绍了当前运行的查询的执行状态。QUERY_PLAN_PROFILES 表的输出显示了实时数据流以及每个查询计划中的每个路径使用的时间和资源。

另请参阅

2.2 - 列编码

更改列编码也有可能会提高查询速度。编码可减少数据在磁盘上的大小,因此会减少查询所需的 I/O,进而提高查询执行速度。确保查询中包括的所有列和投影都使用了正确的数据编码。为此,请执行以下步骤:

  1. 运行 Database Designer 来创建增量设计。Database Designer 会实施最佳编码和投影设计。

  2. 创建增量设计后,使用 ANALYZE_STATISTICS 函数更新统计数据。

  3. 使用您向设计提交的一个或多个查询运行 EXPLAIN,确保它正在使用新投影。

或者,运行 DESIGNER_DESIGN_PROJECTION_ENCODINGS 重新评估当前编码并在必要时对其进行更新。

2.2.1 - 改善列压缩

如果发现性能较慢,或者 FLOAT 数据占用了大量存储空间,请评估数据和业务需要,确认数据能否放入精度在 18 位及以下的 NUMERIC 列中。将 FLOAT 列转换为 NUMERIC 列可以提升数据压缩率,降低数据库的磁盘大小,并提升该列的查询性能。

定义 NUMERIC 数据类型时,您要指定精度和小数位数;NUMERIC 数据是数据的精确表示。FLOAT 数据类型代表了可变精度和近似值;它们在数据库中占用的空间更多。

在以下情况下,将 FLOAT 列转换为 NUMERIC 列最有效:

  • NUMERIC 精度为 18 位及以下。NUMERIC 数据的性能针对最常见的 18 位精度进行了优化。仅当需要 18 位及以下精度时,Vertica 才会建议将 FLOAT 列转换为 NUMERIC 列。

  • FLOAT 精度会受到限制,并且值将全部限定在 NUMERIC 列的指定精度范围内。一个示例是货币值,例如产品价格或财务交易金额。例如,定义为 NUMERIC(11,2) 的列可以包括从 0 到几百万美元的价格,可以存储分币,而且压缩效率也比 FLOAT 列更高。

如果您尝试将值加载到超过指定精度的 NUMERIC 列,Vertica 会返回错误,并且不会加载数据。如果您分配的值的小数位数大于指定小数位数,那么该值会进行四舍五入以匹配指定的小数位数,然后再存储到该列中。

另请参阅

数字数据类型

2.2.2 - 使用运行长度编码

当您运行 Database Designer 时,您可以选择对加载进行优化,以最大限度地减少数据库占用空间。在这种情况下,Database Designer 会对列应用编码,以最大限度地提供查询的性能。编码选项包括运行长度编码 (RLE),此编码将列中唯一值的序列(运行长度)替换为一组值对,其中每个值对表示给定值连续出现的次数:(出现次数, )。

RLE 一般适用于低基数列,并且其中的唯一值通常是连续的,因为在此列中对表数据进行了排序。例如,客户个人资料表通常包含性别列,而此列仅包含 F 值和 M 值。对性别进行排序可确保 F 值或 M 值的运行长度可以表示为一组两个值对:(出现次数, F)和(出现次数, M)。因此,假设 F 出现 8,147 次,M 出现 7,956 次,而投影主要根据性别排序,Vertica 可以应用 RLE,将这些值存储为一组两个值对:(8147, F) 和 (7956, M)。这样可以减少投影占用空间,提高查询性能。

2.3 - 具有谓词的查询的投影

如果查询包含一个或多个谓词,您可以修改投影来提升查询的性能,如以下两个示例所述。

使用日期范围的查询

此示例显示了如何使用 RLE 对数据进行编码,以及更改投影排序顺序以提升检索给定日期范围内所有数据的查询的性能。

假设您的查询类似于如下内容:

=> SELECT * FROM trades
   WHERE trade_date BETWEEN '2016-11-01' AND '2016-12-01';

要优化此查询,请确定所有投影是否可以及时执行 SELECT 操作。为每个投影运行指定了日期范围的 SELECT COUNT(*) 语句,并记下响应时间。例如:

=> SELECT COUNT(*) FROM [ projection_name ]
   WHERE trade_date BETWEEN '2016-11-01' AND '2016-12-01;

如果一个或多个查询很慢,请检查 trade_date 列的唯一性,并确定它是否需要位于投影的 ORDER BY 子句中和/或是否可以使用 RLE 进行编码。RLE 会将列中的相同数据值的序列替换为代表值和计数的配对。要获得最佳效果,请从最低基数到最高基数对投影中的列进行排序,并使用 RLE 对低基数列中的数据进行编码。

如果唯一列的数量未经过排序,或者重复行的平均数量小于 10,那么 trade_date 会因为太过于接近而难以变成唯一,而且不能使用 RLE 进行编码。在这种情况下,请添加新列以最大程度缩小搜索范围。

以下示例会添加一个新列 trade_year

  1. 确定新列 trade_year 是否返回了可管理的结果集。以下查询返回了按 trade_year 进行分组的数据:

    => SELECT DATE_TRUNC('trade_year', trade_date), COUNT(*)
       FROM trades
       GROUP BY DATE_TRUNC('trade_year',trade_date);
    
  2. 假设 trade_year = 2007 接近 8k,请将 trade_year 的列添加到 trades 表。然后,SELECT 语句就会变为:

    => SELECT * FROM trades
       WHERE trade_year = 2007
       AND trade_date BETWEEN '2016-11-01' AND '2016-12-01';
    

    因此,您会具有对 trade_year 进行排序的投影,而且它可以使用 RLE 进行编码。

具有高基数主键的表的查询

此示例说明了您可以如何修改投影来提升从具有高基数主键的表中选择数据的查询的性能。

假设您有这样的一个查询:

=> SELECT FROM [table]
   WHERE pk IN (12345, 12346, 12347,...);

由于主键是高基数列,因此 Vertica 必须搜索大量数据。

要优化此查询的架构,请创建名为 buckets 的新列,并向其分配主键除以 10000 的值。在此示例中,buckets=(int) pk/10000。请按如下所示使用 buckets 列限制搜索范围:

=> SELECT FROM [table]
   WHERE buckets IN (1,...)
   AND pk IN (12345, 12346, 12347,...);

创建较低基数的列并将其添加到查询会限制搜索范围并提升查询性能。此外,如果您创建 buckets 在排序顺序中位于第一位的投影,查询可能会运行地更快。

2.4 - GROUP BY 查询

以下章节中包含的示例展示了如何设计投影以优化 GROUP BY 查询的性能。

2.4.1 - GROUP BY 实施选项

Vertica 将使用以下算法之一来实施查询 GROUP BY 子句:GROUPBY PIPELINED 或 GROUPBY HASH。这两种算法会返回相同的结果。对于返回少量不同组(通常每个节点一千个)的查询,两者的性能基本相似。

您可以使用 EXPLAIN 来确定查询优化器为给定查询选择的算法。以下条件通常决定选择哪种算法:

  • GROUPBY PIPELINED 要求在投影的 ORDER BY 子句中指定所有 GROUP BY 数据。有关详细信息,请参阅下面的 GROUPBY PIPELINED 要求

    由于 GROUPBY PIPELINED 只需在内存中保留当前组数据,因此相比 GROUPBY HASH,该算法通常需要的内存更少且执行速度更快。对于聚合大量不同组的查询,性能改进尤为显著。

  • GROUPBY HASH 将用于任何不符合 GROUPBY PIPELINED 排序顺序要求的查询。在这种情况下,Vertica 必须先对各个 GROUP BY 列构建哈希表,然后才能开始对数据进行分组。

GROUPBY PIPELINED 要求

通过确保查询及其投影之一符合 GROUPBY PIPELINED 要求,可以启用 GROUPBY PIPELINED 算法。以下条件适用于 GROUPBY PIPELINED。如果查询不满足任何条件,则 Vertica 使用 GROUPBY HASH。

后续所有示例均假定使用此架构:

CREATE TABLE sortopt (
    a INT NOT NULL,
    b INT NOT NULL,
    c INT,
    d INT
);
CREATE PROJECTION sortopt_p (
   a_proj,
   b_proj,
   c_proj,
   d_proj )
AS SELECT * FROM sortopt
ORDER BY a,b,c
UNSEGMENTED ALL NODES;
INSERT INTO sortopt VALUES(5,2,13,84);
INSERT INTO sortopt VALUES(14,22,8,115);
INSERT INTO sortopt VALUES(79,9,401,33);

条件 1

所有 GROUP BY 列也包含在投影 ORDER BY 子句中。例如:

条件 2

如果查询的 GROUP BY 子句比投影的 ORDER BY 子句拥有的列数少,则 GROUP BY 列必须:

  • 是连续的 ORDER BY 列的子集。

  • 包含第一个 ORDER BY 列。

例如:

条件 3

如果查询的 GROUP BY 列不是先出现在投影的 GROUP BY 子句中,则在查询的 GROUP BY 子句中缺失的任何先出现的投影排序列必须在查询的 WHERE 子句中显示为单列恒定相等谓词。

例如:

控制选择的 GROUPBY 算法

通常最好让 Vertica 来确定最适合用于给定查询的 GROUP BY 算法。有时,您可能希望使用某种算法,而不是另一种算法。在这种情况下,您可以使用 GBYTYPE 提示来限定 GROUP BY 子句:

GROUP BY /*+ GBYTYPE( HASH | PIPE ) */

例如,指定以下查询后,查询优化器将使用 GROUPBY PIPELINED 算法:

=> EXPLAIN SELECT SUM(a) FROM sortopt GROUP BY a,b;
 ------------------------------
 QUERY PLAN DESCRIPTION:
 ------------------------------

 EXPLAIN SELECT SUM(a) FROM sortopt GROUP BY a,b;

 Access Path:
 +-GROUPBY PIPELINED [Cost: 11, Rows: 3 (NO STATISTICS)] (PATH ID: 1)
 |  Aggregates: sum(sortopt.a)
 |  Group By: sortopt.a, sortopt.b

...

您可以使用 GBYTYPE 提示来强制查询优化器使用 GROUPBY HASH 算法:

=> EXPLAIN SELECT SUM(a) FROM sortopt GROUP BY /*+GBYTYPE(HASH) */ a,b;
 ------------------------------
 QUERY PLAN DESCRIPTION:
 ------------------------------

 EXPLAIN SELECT SUM(a) FROM sortopt GROUP BY /*+GBYTYPE(HASH) */ a,b;

 Access Path:
 +-GROUPBY HASH (LOCAL RESEGMENT GROUPS) [Cost: 11, Rows: 3 (NO STATISTICS)] (PATH ID: 1)
 |  Aggregates: sum(sortopt.a)
 |  Group By: sortopt.a, sortopt.b

...

仅当查询及其投影之一符合 GROUPBY PIPELINED 要求时,GBYTYPE 提示才能指定 PIPE(GROUPBY PIPELINED 算法)实参。否则,Vertica 会发出警告并使用 GROUPBY HASH。

例如,以下查询不能使用 GROUPBY PIPELINED 算法,因为 GROUP BY 列 {b,c} 不包含投影的第一个 ORDER BY 列 a

=> SELECT SUM(a) FROM sortopt GROUP BY /*+GBYTYPE(PIPE) */ b,c;
WARNING 7765:  Cannot apply Group By Pipe algorithm. Proceeding with Group By Hash and hint will be ignored
 SUM
-----
  79
  14
   5
(3 rows)

2.4.2 - 使用投影设计避免在 GROUP BY 优化期间重新分段

要计算包含 GROUP BY 子句的查询的正确结果,Vertica 必须确保 GROUP BY 表达式中具有相同值的所有行最终都会在相同节点上以便于最终计算。如果投影设计已经确保数据按 GROUP BY 列进行分段,那么运行时则不需要重新分段。

要避免重新分段,GROUP BY 子句必须包含投影的所有分段列,但是它也可以包含其他列。

当查询包括 GROUP BY 子句和联接时,如果联接取决于以下示例所示的 GROUP BY 的结果,那么 Vertica 会首先执行 GROUP BY

=> EXPLAIN SELECT * FROM (SELECT b from foo GROUP BY b) AS F, foo WHERE foo.a = F.b;
Access Path:
+-JOIN MERGEJOIN(inputs presorted) [Cost: 649, Rows: 10K (NO STATISTICS)] (PATH ID: 1)
|  Join Cond: (foo.a = F.b)
|  Materialize at Output: foo.b
|  Execute on: All Nodes
| +-- Outer -> STORAGE ACCESS for foo [Cost: 202, Rows: 10K (NO STATISTICS)] (PATH ID: 2)
| |      Projection: public.foo_super
| |      Materialize: foo.a
| |      Execute on: All Nodes
| |      Runtime Filter: (SIP1(MergeJoin): foo.a)
| +-- Inner -> SELECT [Cost: 245, Rows: 10K (NO STATISTICS)] (PATH ID: 3)
| |      Execute on: All Nodes
| | +---> GROUPBY HASH (SORT OUTPUT) (GLOBAL RESEGMENT GROUPS) (LOCAL RESEGMENT GROUPS) [Cost: 245, Rows: 10K (NO STATISTICS)] (PATH ID:
4)
| | |      Group By: foo.b
| | |      Execute on: All Nodes
| | | +---> STORAGE ACCESS for foo [Cost: 202, Rows: 10K (NO STATISTICS)] (PATH ID: 5)
| | | |      Projection: public.foo_super
| | | |      Materialize: foo.b
| | | |      Execute on: All Nodes

如果联接操作的结果是 GROUP BY 子句的输入,Vertica 会首先执行联接,如以下示例所示。这些中间结果的分段可能不会与查询的 GROUP BY 子句保持一致,从而导致运行时要重新分段。

=> EXPLAIN SELECT * FROM foo AS F, foo WHERE foo.a = F.b GROUP BY 1,2,3,4;
Access Path:
+-GROUPBY HASH (LOCAL RESEGMENT GROUPS) [Cost: 869, Rows: 10K (NO STATISTICS)] (PATH ID: 1)
|  Group By: F.a, F.b, foo.a, foo.b
|  Execute on: All Nodes
| +---> JOIN HASH [Cost: 853, Rows: 10K (NO STATISTICS)] (PATH ID: 2) Outer (RESEGMENT)(LOCAL ROUND ROBIN)
| |      Join Cond: (foo.a = F.b)
| |      Execute on: All Nodes
| | +-- Outer -> STORAGE ACCESS for F [Cost: 403, Rows: 10K (NO STATISTICS)] (PUSHED GROUPING) (PATH ID: 3)
| | |      Projection: public.foo_super
| | |      Materialize: F.a, F.b
| | |      Execute on: All Nodes
| | +-- Inner -> STORAGE ACCESS for foo [Cost: 403, Rows: 10K (NO STATISTICS)] (PATH ID: 4)
| | |      Projection: public.foo_super
| | |      Materialize: foo.a, foo.b
| | |      Execute on: All Nodes

如果查询不包括联接,GROUP BY 子句会使用现有数据库投影进行处理。

示例

假设有以下投影:

CREATE PROJECTION ... SEGMENTED BY HASH(a,b) ALL NODES

下表说明了运行时是否会发生重新分段以及相关原因。

要确定在 GROUP BY 查询期间是否会发生重新分段,请查看 EXPLAIN 生成的查询计划。

例如,以下计划使用了 GROUPBY PIPELINED 排序优化,并需要重新分段以执行 GROUP BY 计算:

+-GROUPBY PIPELINEDRESEGMENT GROUPS[Cost: 194, Rows: 10K (NO STATISTICS)] (PATH ID: 1)

以下计划使用了 GROUPBY PIPELINED 排序优化,但是不需要重新分段:

+-GROUPBY PIPELINED [Cost: 459, Rows: 10K (NO STATISTICS)] (PATH ID: 1)

2.5 - SELECT 查询列表中的 DISTINCT

本节介绍了如何优化在其 SELECT 列表中具有 DISTINCT 关键字的查询。优化 DISTINCT 查询的技术类似于优化 GROUP BY 查询的技术,因为处理使用 DISTINCT 的查询时,Vertica 优化器会将查询重写为 GROUP BY 查询。

本页下的各节介绍了各种具体情况:

这几节中的示例使用了以下表:

=> CREATE TABLE table1 (
    a INT,
    b INT,
    c INT
);

2.5.1 - 查询在 SELECT 列表中没有聚合

如果查询内部的 SELECT 列表中没有聚合,Vertica 会将查询视为使用了 GROUP BY 的查询进行处理。

例如,您可以将以下查询:

SELECT DISTINCT a, b, c FROM table1;

重写为:

SELECT a, b, c FROM table1 GROUP BY a, b, c;

为了最大限度提高执行速度,请为 GROUP BY 查询应用GROUP BY 查询中所述的优化技术。

2.5.2 - COUNT (DISTINCT) 和其他 DISTINCT 聚合

计算 DISTINCT 聚合通常需要比其他聚合更多的工作。另外,与具有多个 DISTINCT 聚合的查询相比,使用单个 DISTINCT 聚合的查询使用的资源较少。

以下查询会返回 date_dimension 表的 date_key 列中不同值的数量:

=> SELECT COUNT (DISTINCT date_key) FROM date_dimension;

 COUNT
-------
  1826
(1 row)

此示例会返回对所有 inventory_fact 记录计算表达式 x+y 得到的所有不同值。

=> SELECT COUNT (DISTINCT date_key + product_key) FROM inventory_fact;

 COUNT
-------
 21560
(1 row)

可以使用 LIMIT 关键字限制返回的行数来创建等同的查询:

=> SELECT COUNT(date_key + product_key) FROM inventory_fact GROUP BY date_key LIMIT 10;

 COUNT
-------
   173
    31
   321
   113
   286
    84
   244
   238
   145
   202
(10 rows)

此查询会返回具有特定不同 date_key 值的所有记录中不同 product_key 值的数量。

=> SELECT product_key, COUNT (DISTINCT date_key)  FROM  inventory_fact
   GROUP BY product_key LIMIT 10;

 product_key | count
-------------+-------
           1 |    12
           2 |    18
           3 |    13
           4 |    17
           5 |    11
           6 |    14
           7 |    13
           8 |    17
           9 |    15
          10 |    12
(10 rows)

该查询通过常数 1 对 product_key 表中每个不同的 inventory_fact 值进行计数。

=> SELECT product_key, COUNT (DISTINCT product_key) FROM inventory_fact
   GROUP BY product_key LIMIT 10;

 product_key | count
-------------+-------
           1 |     1
           2 |     1
           3 |     1
           4 |     1
           5 |     1
           6 |     1
           7 |     1
           8 |     1
           9 |     1
          10 |     1
(10 rows)

该查询会选择每个不同的 date_key 值,并针对具有特定 product_key 值的所有记录为不同的product_key 值进行计数。然后,它会对具有特定 qty_in_stock 值的所有记录中的 product_key 值进行求和,并按照 date_key 对结果进行分组。

=> SELECT date_key, COUNT (DISTINCT product_key), SUM(qty_in_stock) FROM inventory_fact
   GROUP BY date_key LIMIT 10;

 date_key | count |  sum
----------+-------+--------
        1 |   173 |  88953
        2 |    31 |  16315
        3 |   318 | 156003
        4 |   113 |  53341
        5 |   285 | 148380
        6 |    84 |  42421
        7 |   241 | 119315
        8 |   238 | 122380
        9 |   142 |  70151
       10 |   202 |  95274
(10 rows)

该查询会选择每个不同的 product_key 值,然后针对具有特定 date_key 值的所有记录为不同的product_key 值进行计数。它还会对具有特定 warehouse_key 值的所有记录中的不同 product_key 值进行计数。

=> SELECT product_key, COUNT (DISTINCT date_key), COUNT (DISTINCT warehouse_key) FROM inventory_fact
   GROUP BY product_key LIMIT 15;

 product_key | count | count
-------------+-------+-------
           1 |    12 |    12
           2 |    18 |    18
           3 |    13 |    12
           4 |    17 |    18
           5 |    11 |     9
           6 |    14 |    13
           7 |    13 |    13
           8 |    17 |    15
           9 |    15 |    14
          10 |    12 |    12
          11 |    11 |    11
          12 |    13 |    12
          13 |     9 |     7
          14 |    13 |    13
          15 |    18 |    17
(15 rows)

该查询会选择每个不同的 product_key 值,为具有特定 date_key 值的所有记录的不同 warehouse_keyproduct_key 值进行计数,然后对具有特定 qty_in_stock 值的记录中的所有 product_key 值进行求和。然后,它会返回具有特定 product_version 值的记录中 product_key 值的数量。

=> SELECT product_key, COUNT (DISTINCT date_key),
      COUNT (DISTINCT warehouse_key),
      SUM (qty_in_stock),
      COUNT (product_version)
      FROM inventory_fact GROUP BY product_key LIMIT 15;

 product_key | count | count |  sum  | count
-------------+-------+-------+-------+-------
           1 |    12 |    12 |  5530 |    12
           2 |    18 |    18 |  9605 |    18
           3 |    13 |    12 |  8404 |    13
           4 |    17 |    18 | 10006 |    18
           5 |    11 |     9 |  4794 |    11
           6 |    14 |    13 |  7359 |    14
           7 |    13 |    13 |  7828 |    13
           8 |    17 |    15 |  9074 |    17
           9 |    15 |    14 |  7032 |    15
          10 |    12 |    12 |  5359 |    12
          11 |    11 |    11 |  6049 |    11
          12 |    13 |    12 |  6075 |    13
          13 |     9 |     7 |  3470 |     9
          14 |    13 |    13 |  5125 |    13
          15 |    18 |    17 |  9277 |    18
(15 rows)

以下示例会返回 warehouse 维度表中的仓库数。

=> SELECT COUNT(warehouse_name) FROM warehouse_dimension;

 COUNT
-------
   100
(1 row)

下一个示例会返回供应商总数:

=> SELECT COUNT(*) FROM vendor_dimension;

 COUNT
-------
    50
(1 row)

2.5.3 - 近似计数区分函数

聚合函数 COUNT(DISTINCT) 计算数据集中非重复值的确切数量。COUNT(DISTINCT) 在使用 GROUPBY PIPELINED 算法执行计算时表现良好。

当满足以下条件时,聚合 COUNT 操作在数据集上表现良好:

  • 目标表投影之一包含 ORDER BY 子句,便于对聚合进行排序。

  • 非重复值的数量非常小。

  • 需要使用哈希聚合来执行查询。

或者,当满足以下条件时,考虑使用 APPROXIMATE_COUNT_DISTINCT 函数而不是 COUNT(DISTINCT):

  • 您有一个大型数据集,而且您不需要获得非重复值的确切数量。

  • COUNT(DISTINCT) 对于给定数据集的性能是不足的。

  • 您在同一个查询中计算几个非重复值计数。

  • COUNT(DISTINCT) 的计划使用哈希聚合。

APPROXIMATE_COUNT_DISTINCT 返回的预期值等于 COUNT(DISTINCT),误差呈对数正态分布,标准差为 s。您可以通过设置函数的可选误差容限实参来控制标准偏差(默认情况下为 1.25%)。

其他 APPROXIMATE_COUNT_DISTINCT 函数

下面是 Vertica 支持您一起使用的其他两个函数(而不是 APPROXIMATE_COUNT_DISTINCT): APPROXIMATE_COUNT_DISTINCT_SYNOPSISAPPROXIMATE_COUNT_DISTINCT_OF_SYNOPSIS。当满足以下条件时使用这些函数:

  • 您有一个大型数据集,而且您不需要获得非重复值的确切数量。

  • COUNT(DISTINCT) 对于给定数据集的性能是不足的。

  • 您希望预先计算非重复值计数,并在之后使用不同的方法合并它们。

将这两个函数一起使用,如下所示:

  1. 向 APPROXIMATE_COUNT_DISTINCT_SYNOPSIS 传递数据集以及正态分布的置信区间。此函数会返回称为“二进制概要对象”的数据子集。

  2. 将概要传递给 APPROXIMATE_COUNT_DISTINCT_OF_SYNOPSIS 函数,然后对概要计算非重复值近似计数。

您还可以使用 APPROXIMATE_COUNT_DISTINCT_SYNOPSIS_MERGE,它将多个概要合并为一个概要。使用此功能,您可以通过合并一个或多个涵盖最近、较短时间段的概要来不断更新“主”概要。

示例

以下示例显示如何使用 APPROXIMATE_COUNT_DISTINCT 函数来保持给定时间段内单击给定网页的用户的大致计数。

  1. 创建 pviews 表来存储有关网站访问的数据(访问时间、访问的网页和访问者):

    
    => CREATE TABLE pviews(
    visit_time TIMESTAMP NOT NULL,
    page_id INTEGER NOT NULL,
    user_id INTEGER NOT NULL)
    ORDER BY page_id, visit_time
    SEGMENTED BY HASH(user_id) ALL NODES KSAFE
    PARTITION BY visit_time::DATE GROUP BY CALENDAR_HIERARCHY_DAY(visit_time::DATE, 2, 2);
    

    pviews 通过散列 user_id 数据进行分段,因此给定用户的所有访问都存储在同一节点的同一段上。这可以防止稍后执行 COUNT (DISTINCT user_id) 时数据低效跨节点传输。

    该表还在访问时使用分层分区来优化 ROS 存储。这样做可以提高按时间筛选数据时的性能。

  2. 将数据加载到 pviews 中:

    => INSERT INTO pviews VALUES
         ('2022-02-01 10:00:02',1002,1),
         ('2022-02-01 10:00:03',1002,2),
         ('2022-02-01 10:00:04',1002,1),
         ('2022-02-01 10:00:05',1002,3),
         ('2022-02-01 10:00:01',1000,1),
         ('2022-02-01 10:00:06',1002,1),
         ('2022-02-01 10:00:07',1002,3),
         ('2022-02-01 10:00:08',1002,1),
         ('2022-02-01 10:00:09',1002,3),
         ('2022-02-01 10:00:12',1002,2),
         ('2022-02-02 10:00:01',1000,1),
         ('2022-02-02 10:00:02',1002,4),
         ('2022-02-02 10:00:03',1002,2),
         ('2022-02-02 10:00:04',1002,1),
         ('2022-02-02 10:00:05',1002,3),
         ('2022-02-02 10:00:06',1002,4),
         ('2022-02-02 10:00:07',1002,3),
         ('2022-02-02 10:00:08',1002,4),
         ('2022-02-02 10:00:09',1002,3),
         ('2022-02-02 10:00:12',1002,2),
         ('2022-03-02 10:00:01',1000,1),
         ('2022-03-02 10:00:02',1002,1),
         ('2022-03-02 10:00:03',1002,2),
         ('2022-03-02 10:00:04',1002,1),
         ('2022-03-02 10:00:05',1002,3),
         ('2022-03-02 10:00:06',1002,4),
         ('2022-03-02 10:00:07',1002,3),
         ('2022-03-02 10:00:08',1002,6),
         ('2022-03-02 10:00:09',1002,5),
         ('2022-03-02 10:00:12',1002,2),
         ('2022-03-02 11:00:01',1000,5),
         ('2022-03-02 11:00:02',1002,6),
         ('2022-03-02 11:00:03',1002,7),
         ('2022-03-02 11:00:04',1002,4),
         ('2022-03-02 11:00:05',1002,1),
         ('2022-03-02 11:00:06',1002,6),
         ('2022-03-02 11:00:07',1002,8),
         ('2022-03-02 11:00:08',1002,6),
         ('2022-03-02 11:00:09',1002,7),
         ('2022-03-02 11:00:12',1002,1),
         ('2022-03-03 10:00:01',1000,1),
         ('2022-03-03 10:00:02',1002,2),
         ('2022-03-03 10:00:03',1002,4),
         ('2022-03-03 10:00:04',1002,1),
         ('2022-03-03 10:00:05',1002,2),
         ('2022-03-03 10:00:06',1002,6),
         ('2022-03-03 10:00:07',1002,9),
         ('2022-03-03 10:00:08',1002,10),
         ('2022-03-03 10:00:09',1002,7),
         ('2022-03-03 10:00:12',1002,1);
     OUTPUT
    --------
         50
    (1 row)
    
    => COMMIT;
    COMMIT
    
  3. 通过使用 CREATE TABLE...AS SELECT 查询 pviews 来创建 pview_summary 表。此表的每一行汇总了从 pviews 中选择的给定日期的数据:

    • partial_visit_countpviews 中存储具有该日期的行数(网站访问)。

    • daily_users_acdp 使用 APPROXIMATE_COUNT_DISTINCT_SYNOPSIS 构建一个概要,该概要近似计算在该日期访问该网站的不同用户 (user_id) 的数量。

    
    => CREATE TABLE pview_summary AS SELECT
          visit_time::DATE "date",
          COUNT(*) partial_visit_count,
          APPROXIMATE_COUNT_DISTINCT_SYNOPSIS(user_id) AS daily_users_acdp
       FROM pviews GROUP BY 1;
    CREATE TABLE
    => ALTER TABLE pview_summary ALTER COLUMN "date" SET NOT NULL;
    
  4. 更新 pview_summary 表,使其像 pviews 一样进行分区。REORGANIZE 关键字强制表数据的立即重新分区:

    => ALTER TABLE pview_summary
         PARTITION BY "date"
         GROUP BY CALENDAR_HIERARCHY_DAY("date", 2, 2) REORGANIZE;
    vsql:/home/ale/acd_ex4.sql:93: NOTICE 8364:  The new partitioning scheme will produce partitions in 2 physical storage containers per projection
    vsql:/home/ale/acd_ex4.sql:93: NOTICE 4785:  Started background repartition table task
    ALTER TABLE
    
  5. 使用 CREATE TABLE...LIKE 创建两个 ETL 表 pviews_etlpview_summary_etl,它们分别具有与 pviewspview_summary 相同的 DDL。这些表用于处理传入数据:

    => CREATE TABLE pviews_etl LIKE pviews INCLUDING PROJECTIONS;
    CREATE TABLE
    => CREATE TABLE pview_summary_etl LIKE pview_summary INCLUDING PROJECTIONS;
    CREATE TABLE
    
  6. 将新数据加载到 pviews_etl

    => INSERT INTO pviews_etl VALUES
         ('2022-03-03 11:00:01',1000,8),
         ('2022-03-03 11:00:02',1002,9),
         ('2022-03-03 11:00:03',1002,1),
         ('2022-03-03 11:00:04',1002,11),
         ('2022-03-03 11:00:05',1002,10),
         ('2022-03-03 11:00:06',1002,12),
         ('2022-03-03 11:00:07',1002,3),
         ('2022-03-03 11:00:08',1002,10),
         ('2022-03-03 11:00:09',1002,1),
         ('2022-03-03 11:00:12',1002,1);
     OUTPUT
    --------
         10
    (1 row)
    
    => COMMIT;
    COMMIT
    
  7. pview_summary_etl 中汇总新数据:

    => INSERT INTO pview_summary_etl SELECT
          visit_time::DATE visit_date,
          COUNT(*) partial_visit_count,
          APPROXIMATE_COUNT_DISTINCT_SYNOPSIS(user_id) AS daily_users_acdp
        FROM pviews_etl GROUP BY visit_date;
     OUTPUT
    --------
          1
    (1 row)
    
  8. 使用 COPY_PARTITIONS_TO_TABLEpviews_etl 数据附加到 pviews

    => SELECT COPY_PARTITIONS_TO_TABLE('pviews_etl', '01-01-0000'::DATE, '01-01-9999'::DATE, 'pviews');
                  COPY_PARTITIONS_TO_TABLE
    ----------------------------------------------------
     1 distinct partition values copied at epoch 1403.
    
    (1 row)
    
    => SELECT COPY_PARTITIONS_TO_TABLE('pview_summary_etl', '01-01-0000'::DATE, '01-01-9999'::DATE, 'pview_summary');
                  COPY_PARTITIONS_TO_TABLE
    ----------------------------------------------------
     1 distinct partition values copied at epoch 1404.
    
    (1 row)
    
  9. 为所有数据(包括刚刚从 pviews_etl 复制的分区)创建视图和不同(近似)视图:

    => SELECT
         "date" visit_date,
         SUM(partial_visit_count) visit_count,
         APPROXIMATE_COUNT_DISTINCT_OF_SYNOPSIS(daily_users_acdp) AS daily_users_acd
       FROM pview_summary GROUP BY visit_date ORDER BY visit_date;
     visit_date | visit_count | daily_users_acd
    ------------+-------------+-----------------
     2022-02-01 |          10 |               3
     2022-02-02 |          10 |               4
     2022-03-02 |          20 |               8
     2022-03-03 |          20 |              11
    (4 rows)
    
  10. 按月创建视图和不同(近似)视图:

    => SELECT
         DATE_TRUNC('MONTH', "date")::DATE "month",
         SUM(partial_visit_count) visit_count,
         APPROXIMATE_COUNT_DISTINCT_OF_SYNOPSIS(daily_users_acdp) AS monthly_users_acd
       FROM pview_summary GROUP BY month ORDER BY month;
       month    | visit_count | monthly_users_acd
    ------------+-------------+-------------------
     2022-02-01 |          20 |                 4
     2022-03-01 |          40 |                12
    (2 rows)
    
  11. 将每日概要合并到每月概要:

    => CREATE TABLE pview_monthly_summary AS SELECT
         DATE_TRUNC('MONTH', "date")::DATE "month",
         SUM(partial_visit_count) partial_visit_count,
         APPROXIMATE_COUNT_DISTINCT_SYNOPSIS_MERGE(daily_users_acdp) AS monthly_users_acdp
       FROM pview_summary GROUP BY month ORDER BY month;
    CREATE TABLE
    
  12. 按月创建根据合并的概要生成的视图和不同的视图:

    => SELECT
         month,
         SUM(partial_visit_count) monthly_visit_count,
         APPROXIMATE_COUNT_DISTINCT_OF_SYNOPSIS(monthly_users_acdp) AS monthly_users_acd
       FROM pview_monthly_summary GROUP BY month ORDER BY month;
       month    | monthly_visit_count | monthly_users_acd
    ------------+---------------------+-------------------
     2019-02-01 |                  20 |                 4
     2019-03-01 |                  40 |                12
    (2 rows)
    
  13. 您可以使用每月摘要生成年度摘要。当需要处理大量数据时,这种方法可能比使用每日摘要快:

    => SELECT
         DATE_TRUNC('YEAR', "month")::DATE "year",
         SUM(partial_visit_count) yearly_visit_count,
         APPROXIMATE_COUNT_DISTINCT_OF_SYNOPSIS(monthly_users_acdp) AS yearly_users_acd
       FROM pview_monthly_summary GROUP BY year ORDER BY year;
        year    | yearly_visit_count | yearly_users_acd
    ------------+--------------------+------------------
     2022-01-01 |                 60 |               12
    (1 row)
    
  14. 删除 ETL 表:

    => DROP TABLE IF EXISTS pviews_etl, pview_summary_etl;
    DROP TABLE
    

另请参阅

2.5.4 - 单个 DISTINCT 聚合

计算 DISTINCT 聚合时,Vertica 首先会删除聚合参数中的所有重复值,从而找到非重复值。然后,它会计算聚合。

例如,您可以将以下查询:

SELECT a, b, COUNT(DISTINCT c) AS dcnt FROM table1 GROUP BY a, b;

重写为:

SELECT a, b, COUNT(dcnt) FROM
  (SELECT a, b, c AS dcnt FROM table1 GROUP BY a, b, c)
GROUP BY a, b;

为了最大限度提高执行速度,请为 GROUP BY 查询应用优化技术。

2.5.5 - 多个 DISTINCT 聚合

如果查询具有多个 DISTINCT 聚合,则无法通过简单地重写 SQL 来计算它们。以下查询就无法通过简单地重写来提高性能:

SELECT a, COUNT(DISTINCT b), COUNT(DISTINCT c) AS dcnt FROM table1 GROUP BY a;

对于具有多个 DISTINCT 聚合的查询,没有一种投影设计可以避免使用 GROUPBY HASH 以及对数据进行重新分段。要提升此查询的性能,请确保它具有大量内存可用。有关为查询分配内存的详细信息,请参阅资源管理器

2.6 - JOIN 查询

通常,可以通过以下几种方式优化联接多个表的查询的执行:

其他最佳实践

如果满足以下条件,Vertica 还会更高效地执行联接:

  • 查询构造使查询优化器能够创建计划,其中较大的表被定义为外部输入。

  • 等式谓词两边的列来自同一个表。例如,在以下查询中,等式谓词的左侧和右侧分别仅包含表 T 和 X 中的列:

    => SELECT * FROM T JOIN X ON T.a + T.b = X.x1 - X.x2;
    

    相反,以下查询需要处理更多工作,因为谓词的右侧包含表 T 和 X 中的列:

    => SELECT * FROM T JOIN X WHERE T.a = X.x1 + T.b
    

2.6.1 - 哈希联接与合并联接

Vertica 优化器使用以下算法之一实施联接:

  • 当联接表的投影已在联接列中排序时,使用合并联接。与哈希联接相比,合并联接较快并且使用较少的内存。

  • 当联接表的投影尚未在联接列上排序时,使用哈希联接。在这种情况下,优化器会在内表的联接列上构建一个内存中的哈希表。优化器然后扫描外部表以查找与哈希表的匹配,并相应地联接两个表中的数据。如果整个哈希表可以拟合到内存中,则执行哈希联接的成本将非常低。如果哈希表必须写入磁盘,成本会显着增加。

在给定可用的投影的情况下,优化器会自动选择最合适的算法来执行查询。

促进合并联接

为了促进合并联接,请为联接表创建在联接谓词列上排序的投影。联接谓词列应该是 ORDER BY 子句中的第一列。

例如,表 firstsecond 定义如下,分别带有投影 first_p1second_p1。投影在 data_firstdata_second 上排序:

CREATE TABLE first ( id INT, data_first INT );
CREATE PROJECTION first_p1 AS SELECT * FROM first ORDER BY data_first;

CREATE TABLE second ( id INT, data_second INT );
CREATE PROJECTION second_p1 AS SELECT * FROM second ORDER BY data_second;

当您在未排序的列 first.idsecond.id 上联接这些表时,Vertica 使用哈希联接算法:

 EXPLAIN SELECT first.data_first, second.data_second FROM first JOIN second ON first.id = second.id;

 Access Path:
 +-JOIN HASH [Cost: 752, Rows: 300K] (PATH ID: 1) Inner (BROADCAST)

您可以创建投影 first_p2 和 second_p2(它们分别在联接列 first_p2.idsecond_p2.id 上排序),从而使用合并联接算法促进执行此查询:


CREATE PROJECTION first_p2 AS SELECT id, data_first FROM first ORDER BY id SEGMENTED BY hash(id, data_first) ALL NODES;
CREATE PROJECTION second_p2 AS SELECT id, data_second FROM second ORDER BY id SEGMENTED BY hash(id, data_second) ALL NODES;

如果查询联接了大量数据,则查询优化器使用合并算法:

EXPLAIN SELECT first.data_first, second.data_second FROM first JOIN second ON first.id = second.id;

 Access Path:
 +-JOIN MERGEJOIN(inputs presorted) [Cost: 731, Rows: 300K] (PATH ID: 1) Inner (BROADCAST)

您还可以使用子查询对联接谓词列进行预排序,从而促进合并联接。例如:

SELECT first.id, first.data_first, second.data_second FROM
  (SELECT * FROM first ORDER BY id ) first JOIN (SELECT * FROM second ORDER BY id) second ON first.id = second.id;

2.6.2 - 相同分段

要在联接多个表时提升查询性能,请创建在联接键上进行相同分段的投影。使用相同分段的投影时,联接可以在每个节点本地进行,因此有助于在查询处理期间减少整个网络中的数据移动。

要确定投影是否在查询联接键中进行了相同分段,请创建具有 EXPLAIN 的查询计划。如果查询计划包括 RESEGMENTBROADCAST,则表示投影没有进行相同分段。

Vertica 优化器会选择投影为查询中的每个表提供行。如果要联接的投影进行了分段,那么优化器则会根据查询联接表达式评估其分段。因此,它会确定行是否放置在每个节点上,以便于它联接行,而无需从其他节点获取数据。

相同分段的投影的联接条件

如果 p 的分段表达式的所有列引用是联接表达式中的列的子集,那么投影 p 会在联接列上进行分段。

如果表 t1 的分段投影 p1 和表 t2 的分段投影 p2 这两个分段投影要使 t1 联接到 t2,则必须满足以下条件:

  • 联接条件必须采用以下形式:

    t1.j1 = t2.j1 AND t1.j2 = t2.j2 AND ... t1.jN = t2.jN
    
  • 联接列必须共享相同的基本数据类型。例如:

    • 如果 t1.j1 是 INTEGER,则 t2.j1 可以是 INTEGER,但它不能是 FLOAT。

    • 如果 t1.j1 是 CHAR(10),则 t2.j1 可以是任何 CHAR 或 VARCHAR(例如,CHAR(10)、VARCHAR(10)、VARCHAR(20)),但是 t2.j1 不能是 INTEGER。

  • 如果 p1 按列 {t1.s1, t1.s2, ... t1.sN} 上的表达式分段,则每个分段列 t1.sX 必须位于联接列集 {t1.jX} 中。

  • 如果 p2 按列 {t2.s1, t2.s2, ... t2.sN} 上的表达式分段,则每个分段列 t2.sX 必须位于联接列集 {t2.jX} 中。

  • p1p2 的分段表达式必须在结构上等效。例如:

    • 如果 p1SEGMENTED BY hash(t1.x) 并且 p2SEGMENTED BY hash(t2.x),那么 p1p2 进行了相同分段。

    • 如果 p1SEGMENTED BY hash(t1.x) 并且 p2SEGMENTED BY hash(t2.x + 1),那么 p1p2 没有进行相同分段。

  • p1p2 必须有相同的分段计数。

  • 向节点分配的分段必须匹配。例如,如果 p1p2 使用了 OFFSET 子句,它们的偏移必须匹配。

  • 如果 Vertica 发现 t1t2 的投影没有进行相同分段,必要时数据会在查询运行时在整个网络中重新分布。

示例

以下语句创建了两个表,并指定要创建相同分段:

=> CREATE TABLE t1 (id INT, x1 INT, y1 INT) SEGMENTED BY HASH(id, x1) ALL NODES;
=> CREATE TABLE t2 (id INT, x1 INT, y1 INT) SEGMENTED BY HASH(id, x1) ALL NODES;

以此设计为例,以下查询中的联接条件可以利用相同分段:

=> SELECT * FROM t1 JOIN t2 ON t1.id = t2.id;
=> SELECT * FROM t1 JOIN t2 ON t1.id = t2.id AND t1.x1 = t2.x1;

相反,以下查询中的联接条件需要重新分段:

=> SELECT * FROM t1 JOIN t2 ON t1.x1 = t2.x1;
=> SELECT * FROM t1 JOIN t2 ON t1.id = t2.x1;

另请参阅

2.6.3 - 联接可变长度字符串数据

当您在 VARCHAR 列上联接表时,Vertica 会计算缓冲联接列数据所需的存储空间。它通过采用以下两种方式之一设置列数据格式来完成此操作:

  • 使用联接列元数据将列数据的大小调整为固定长度,并相应地缓冲。例如,给定一个定义为 VARCHAR(1000) 的列,Vertica 始终缓冲 1000 个字符。

  • 使用联接列数据的实际长度,因此每个联接的缓冲区大小不同。例如,给定字符串 Xi、John 和 Amrita 的联接,Vertica 仅缓冲每个联接所需的存储空间 — 在本例中,分别为 2、4 和 6 个字节。

第二种方法可以提高联接查询性能。它还可以减少内存消耗,这有助于防止联接溢出并最大限度地减少从资源管理器借用内存的频率。通常,在联接列的定义大小显著超过其数据的平均长度的情况下,这些好处尤其显著。

设置和验证可变长度格式

您可以通过配置参数 JoinDefaultTupleFormat 控制 Vertica 如何在会话或数据库级别实施联接,或者通过 JFMT 提示控制针对单个查询实施联接。Vertica 支持除 mergeevent series 联接之外的所有联接的可变长度格式。

使用 EXPLAIN VERBOSE 通过检查以下标志来验证给定查询是否使用可变字符格式:

  • JF_EE_VARIABLE_FORMAT

  • JF_EE_FIXED_FORMAT

2.7 - ORDER BY 查询

如果投影的 ORDER BY 子句中的列与查询中的列相同,那么您可以提升仅包含 ORDER BY 子句的查询的性能。

如果在 CREATE PROJECTION 语句中定义投影排序顺序,则 Vertica 查询优化器并不一定要在执行特定的 ORDER BY 查询之前对投影数据进行排序。

下面的 sortopt 表中包含 abcd 列。投影 sortopt_p 指定按 abc 列排序。

CREATE TABLE sortopt (
    a INT NOT NULL,
    b INT NOT NULL,
    c INT,
    d INT
);
CREATE PROJECTION sortopt_p (
   a_proj,
   b_proj,
   c_proj,
   d_proj )
AS SELECT * FROM sortopt
ORDER BY a,b,c
UNSEGMENTED ALL NODES;
INSERT INTO sortopt VALUES(5,2,13,84);
INSERT INTO sortopt VALUES(14,22,8,115);
INSERT INTO sortopt VALUES(79,9,401,33);

根据这一排序顺序,如果 SELECT * FROM sortopt 查询中包含以下 ORDER BY 子句之一,则该查询不必对投影重新排序:

  • ORDER BY a

  • ORDER BY a, b

  • ORDER BY a, b, c

例如,在以下查询中,Vertica 不必对投影重新排序,因为其排序顺序包含了 CREATE PROJECTION..ORDER BY a, b, c 子句中指定的列,该子句镜像了该查询的 ORDER BY a, b, c 子句:

=> SELECT * FROM sortopt ORDER BY a, b, c;
 a  | b  |  c  |  d
----+----+-----+-----
  5 |  2 |  13 |  84
 14 | 22 |   8 | 115
 79 |  9 | 401 |  33
(3 rows)

如果在查询中包含 d 列,则 Vertica 必须对投影重新排序,因为列 d 并未在 CREATE PROJECTION..ORDER BY 子句中定义。因此,该 ORDER BY d 查询不会受益于任何排序优化。

您不能在 CREATE PROJECTION 语句的 ORDER BY 子句中指定 ASC 或 DESC 子句。Vertica 总是在物理存储中使用升序排序顺序,因此如果您的查询对其任何列指定了降序顺序,该查询仍然会导致 Vertica 对投影数据重新排序。例如,以下查询需要 Vertica 对结果进行排序:

=> SELECT * FROM sortopt ORDER BY a DESC, b, c;
 a  | b  |  c  |  d
----+----+-----+-----
 79 |  9 | 401 |  33
 14 | 22 |   8 | 115
  5 |  2 |  13 |  84
(3 rows)

另请参阅

CREATE PROJECTION

2.8 - 分析函数

以下几节介绍了如何优化 Vertica 支持的 SQL-99 分析函数。

2.8.1 - 空的 OVER 子句

OVER() 子句不需要窗口化子句。如果您的查询使用类似 SUM(x) 的分析函数,而且您指定了一个空 OVER() 子句,分析函数则会用作报告函数,其中整个输入会被视为单个分区;聚合会为结果集的每行返回相同的聚合值。查询在单个节点上执行,有可能导致性能低下。

如果您将 PARTITION BY 子句添加到 OVER() 子句,查询会在多个节点上执行,从而提升其性能。

2.8.2 - NULL 排序顺序

默认情况下,投影列值按升序存储,但是 NULL 值的位置取决于列的数据类型。

ORDER BY 子句的 NULL 位置差异

分析 OVER(window-order-clause) 与 SQL ORDER BY 子句的语义稍有不同:

OVER(ORDER BY ...)

分析窗口排序子句使用 ASCDESC 排序顺序来确定分析函数结果的 NULLS FIRSTNULLS LAST 位置。NULL 值按如下方式放置:

  • ASC, NULLS LAST — NULL 值出现在排序结果的末尾。

  • DESC, NULLS FIRST — NULL 值出现在排序结果的开头。

(SQL) ORDER BY

SQL 和 Vertica ORDER BY 子句产生不同的结果。SQL ORDER BY 子句只指定升序或降序排序顺序。Vertica ORDER BY 子句根据列数据类型确定 NULL 位置:

  • NUMERIC、INTEGER、DATE、TIME、TIMESTAMP 和 INTERVAL 列: NULLS FIRST (NULL 值出现在排序投影的开头。)

  • FLOAT、STRING 和 BOOLEAN 列: NULLS LAST (NULL 值出现在排序投影的末尾。)

NULL 排序选项

在执行分析计算的查询中,如果不在意 NULL 的位置,或者您知道列中不包含任何 NULL 值,请指定 NULLS AUTO,而不考虑数据类型。Vertica 选择性能最快的放置,如以下查询所示。否则,请指定 NULLS FIRSTNULLS LAST

=> SELECT x, RANK() OVER (ORDER BY x NULLS AUTO) FROM t;

如以下示例所示,您可以仔细构建查询,使 Vertica 避免对数据排序并提高查询速度。如 OVER(ORDER BY) 子句指定的那样,Vertica 会在列 x 上对来自表 t 的输入进行排序,然后对 RANK() 求值:

=> CREATE TABLE t (
    x FLOAT,
    y FLOAT );
=> CREATE PROJECTION t_p (x, y) AS SELECT * FROM t
   ORDER BY x, y UNSEGMENTED ALL NODES;
=> SELECT x, RANK() OVER (ORDER BY x) FROM t;

在之前的 SELECT 语句中,Vertica 不再使用 ORDER BY 子句,而且能迅速执行查询,因为列 x 是 FLOAT 数据类型。因此,投影排序顺序与分析默认排序 (ASC + NULLS LAST) 相匹配。当底层投影已经经过排序后,Vertica 也可以避免对数据进行排序。

但是,如果列 x 是 INTEGER 数据类型,则 Vertica 必须对数据进行排序,因为 INTEGER 数据类型的投影排序顺序 (ASC + NULLS FIRST) 与默认分析排序 (ASC + NULLS LAST) 不匹配。要帮助 Vertica 消除排序,请指定 NULL 的位置以与默认排序匹配:

=> SELECT x, RANK() OVER (ORDER BY x NULLS FIRST) FROM t;

如果列 x 是 STRING,以下查询就会消除排序:

=> SELECT x, RANK() OVER (ORDER BY x NULLS LAST) FROM t;

如果在之前的查询中忽略了 NULLS LAST,Vertica 会消除排序,因为 ASC + NULLS LAST 对于分析 ORDER BY 子句和 Vertica 中字符串相关的列来说是默认排序规范。

另请参阅

2.8.3 - 分析函数中 NULL 值的运行时排序

通过认真编写查询或创建设计(或同时开展这两项工作),您可以帮助 Vertica 查询优化器在执行分析函数时跳过对表中的所有列进行排序这项操作,从而提升查询性能。

要最大程度降低 Vertica 在查询执行期间对投影进行排序的需要,请重新定义 employee 表并指定排序字段不允许出现 NULL 值:

=> DROP TABLE employee CASCADE;
=> CREATE TABLE employee
   (empno INT,
    deptno INT NOT NULL,
    sal INT NOT NULL);
CREATE TABLE
=> CREATE PROJECTION employee_p AS
   SELECT * FROM employee
   ORDER BY deptno, sal;
CREATE PROJECTION
=> INSERT INTO employee VALUES(101,10,50000);
=> INSERT INTO employee VALUES(103,10,43000);
=> INSERT INTO employee VALUES(104,10,45000);
=> INSERT INTO employee VALUES(105,20,97000);
=> INSERT INTO employee VALUES(108,20,33000);
=> INSERT INTO employee VALUES(109,20,51000);
=> COMMIT;
COMMIT
=> SELECT * FROM employee;
 empno | deptno |  sal
-------+--------+-------
   101 |     10 | 50000
   103 |     10 | 43000
   104 |     10 | 45000
   105 |     20 | 97000
   108 |     20 | 33000
   109 |     20 | 51000
(6 rows)
=> SELECT deptno, sal, empno, RANK() OVER
     (PARTITION BY deptno ORDER BY sal)
   FROM employee;
 deptno |  sal  | empno | ?column?
--------+-------+-------+----------
     10 | 43000 |   103 |        1
     10 | 45000 |   104 |        2
     10 | 50000 |   101 |        3
     20 | 33000 |   108 |        1
     20 | 51000 |   109 |        2
     20 | 97000 |   105 |        3
(6 rows)

2.9 - LIMIT 查询

查询可以包含一个 LIMIT 子句,采用以下两种方式限制其结果集:

  • 从整个结果集返回行的子集。

  • 在结果集上设置窗口分区并限制每个窗口中的行数。

限制查询结果集

查询使用具有 ORDER BYLIMIT 子句时,它会从查询数据集返回特定行子集。Vertica 使用数据库查询排名流程 Top-K 优化高效地处理这些查询。Top-K 优化避免对整个数据集进行排序(以及可能的磁盘写入)来找到少量的行。这可以极大地提升查询性能。

例如,以下查询返回表 customer_dimension 中的前 20 行数据,按 number_of_employees 排序:

=> SELECT store_region, store_city||', '||store_state location, store_name, number_of_employees
     FROM store.store_dimension ORDER BY number_of_employees DESC LIMIT 20;
 store_region |       location       | store_name | number_of_employees
--------------+----------------------+------------+---------------------
 East         | Nashville, TN        | Store141   |                  50
 East         | Manchester, NH       | Store225   |                  50
 East         | Portsmouth, VA       | Store169   |                  50
 SouthWest    | Fort Collins, CO     | Store116   |                  50
 SouthWest    | Phoenix, AZ          | Store232   |                  50
 South        | Savannah, GA         | Store201   |                  50
 South        | Carrollton, TX       | Store8     |                  50
 West         | Rancho Cucamonga, CA | Store102   |                  50
 MidWest      | Lansing, MI          | Store105   |                  50
 West         | Provo, UT            | Store73    |                  50
 East         | Washington, DC       | Store180   |                  49
 MidWest      | Sioux Falls, SD      | Store45    |                  49
 NorthWest    | Seattle, WA          | Store241   |                  49
 SouthWest    | Las Vegas, NV        | Store104   |                  49
 West         | El Monte, CA         | Store100   |                  49
 SouthWest    | Fort Collins, CO     | Store20    |                  49
 East         | Lowell, MA           | Store57    |                  48
 SouthWest    | Arvada, CO           | Store188   |                  48
 MidWest      | Joliet, IL           | Store82    |                  48
 West         | Berkeley, CA         | Store248   |                  48
(20 rows)

限制窗口分区结果

您可以使用 LIMIT 对查询结果设置窗口分区,并限制每个窗口中返回的行数:

SELECT ... FROM dataset LIMIT num‑rows OVER ( PARTITION BY column‑expr‑x, ORDER BY column‑expr‑y [ASC | DESC] )

其中查询 dataset 在每个 column‑expr‑x 分区中返回具有最高或最低 column‑expr‑y 值的 num‑rows 个行。

例如,以下语句查询表 store.store_dimension 并包含一个指定窗口分区的 LIMIT 子句。在本例中,Vertica 按 store_region 对结果集进行分区,其中每个分区窗口显示一个区域中两个员工最少的商店:

=> SELECT store_region, store_city||', '||store_state location, store_name, number_of_employees FROM store.store_dimension
     LIMIT 2 OVER (PARTITION BY store_region ORDER BY number_of_employees ASC);
 store_region |      location       | store_name | number_of_employees
--------------+---------------------+------------+---------------------
 West         | Norwalk, CA         | Store43    |                  10
 West         | Lancaster, CA       | Store95    |                  11
 East         | Stamford, CT        | Store219   |                  12
 East         | New York, NY        | Store122   |                  12
 SouthWest    | North Las Vegas, NV | Store170   |                  10
 SouthWest    | Phoenix, AZ         | Store228   |                  11
 NorthWest    | Bellevue, WA        | Store200   |                  19
 NorthWest    | Portland, OR        | Store39    |                  22
 MidWest      | South Bend, IN      | Store134   |                  10
 MidWest      | Evansville, IN      | Store30    |                  11
 South        | Mesquite, TX        | Store124   |                  10
 South        | Beaumont, TX        | Store226   |                  11
(12 rows)

2.10 - INSERT-SELECT 操作

优化具有以下格式的 INSERT-SELECT 查询的方法有以下几种:

INSERT /*+direct*/ INTO destination SELECT * FROM source;

2.10.1 - 匹配排序顺序

在执行 INSERT-SELECT 操作时,要避免 INSERT 的排序阶段,请确保 SELECT 查询的排序顺序与目标表的投影排序顺序相匹配。

例如,在单节点数据库中:

=> CREATE TABLE source (col1 INT, col2 INT, col3 INT);
=> CREATE PROJECTION source_p (col1, col2, col3)
     AS SELECT col1, col2, col3 FROM source
     ORDER BY col1, col2, col3
     SEGMENTED BY HASH(col3)
     ALL NODES;
=> CREATE TABLE destination (col1 INT, col2 INT, col3 INT);
=> CREATE PROJECTION destination_p (col1, col2, col3)
     AS SELECT col1, col2, col3 FROM destination
     ORDER BY col1, col2, col3
     SEGMENTED BY HASH(col3)
     ALL NODES;

以下 INSERT 不需要排序,因为查询结果具有投影的列顺序:

=> INSERT /*+direct*/ INTO destination SELECT * FROM source;

以下 INSERT 需要排序,因为 SELECT 语句中的列顺序与投影顺序不匹配:

=> INSERT /*+direct*/ INTO destination SELECT col1, col3, col2 FROM source;

以下 INSERT 不需要排序。列的顺序不匹配,但是显式 ORDER BY 可以使输出在 Vertica 中按 c1c3c2 排序:

=> INSERT /*+direct*/ INTO destination SELECT col1, col3, col2 FROM source
      GROUP BY col1, col3, col2
      ORDER BY col1, col2, col3 ;

2.10.2 - 相同分段

执行从分段的源表到分段的目标表的 INSERT-SELECT 操作时,请在相同列上对两个投影都进行分段以避免重新分段数据,如以下示例所示:

CREATE TABLE source (col1 INT, col2 INT, col3 INT);
CREATE PROJECTION source_p (col1, col2, col3) AS
   SELECT col1, col2, col3 FROM source
   SEGMENTED BY HASH(col3) ALL NODES;
CREATE TABLE destination (col1 INT, col2 INT, col3 INT);
CREATE PROJECTION destination_p (col1, col2, col3) AS
    SELECT col1, col2, col3 FROM destination
    SEGMENTED BY HASH(col3) ALL NODES;
INSERT /*+direct*/ INTO destination SELECT * FROM source;

2.11 - DELETE 和 UPDATE 查询

Vertica 已针对查询密集型工作负载进行了优化,因此,DELETE 和 UPDATE 查询可能无法达到与其他查询相同的性能水平。DELETE 和 UPDATE 操作必须更新所有投影,因此这些操作可能比最慢的投影还慢。有关详细信息,请参阅优化 DELETE 和 UPDATE

2.12 - 数据收集器表查询

Vertica 数据收集器可收集和保留有关数据库群集的信息,从而扩展了系统表功能。数据收集器会在系统表中提供这些信息。

Vertica Analytic Database 会将数据收集数据存储在 Vertica 或编录路径下的数据收集器目录中。使用数据收集器信息可以查询系统表的过去状态并提取聚合信息。

一般来说,如果数据收集器表只有包含所需数据的列,查询这些表的效率会更高。在以下情况下,查询也会更加高效:

避免重新分段

session_idtransaction_id 上联接以下 DC 表时,您可以避免重新分段,因为所有数据都是本地数据:

  • dc_session_starts

  • dc_session_ends

  • dc_requests_issued

  • dc_requests_completed

查询包括 node_name 列时,不需要重新分段。例如:

=> SELECT dri.transaction_id, dri.request, drc.processed_row_count
    FROM dc_requests_issued dri
    JOIN dc_requests_completed drc
    USING (node_name, session_id, request_id)     WHERE dri.time between 'April 7,2015'::timestamptz and 'April 8,2015'::timestamptz
    AND drc.time between 'April 7,2015'::timestamptz and 'April 8,2015'::timestamptz;

此查询会高效运行,因为:

  • 启动程序节点仅写入到 dc_requests_issued dc_requests_completed

  • session_idnode_name 彼此相关。

使用时间谓词

为时间范围谓词使用非易变函数以及 TIMESTAMP。Vertica Analytic Database 会优化使用时间谓词的 DC 表的 SQL 性能。

每个 DC 表都有一个 time 列。使用此列可将时间范围作为查询谓词输入。

例如,此查询会返回日期在 9 月 1 日到 9 月 10 日之间的数据: select * from dc_foo where time > 'Sept 1, 2015::timestamptz and time < 'Sept 10 2015':: timestamptz; 您可以更改最小或最大时间值,调整要检索数据的时间范围。

您必须将非可变函数用作时间谓词。 可变函数 导致查询运行效率低下。此示例返回了在 2015 年 4 月 7 日开始和结束的所有查询。但是,查询并未以最佳性能运行,因为 trunctimestamp 是可变的:

=> SELECT dri.transaction_id, dri.request, drc.processed_row_count
    FROM dc_requests_issued dri
    LEFT JOIN dc_requests_completed drc
    USING (session_id, request_id)
    WHERE trunc(dri.time, ‘DDD’) > 'April 7,2015'::timestamp
    AND trunc(drc.time, ‘DDD’) < 'April 8,2015'::timestamp;

3 - 视图

视图是封装一个或多个 SELECT 语句的存储查询。视图在执行时动态访问并计算数据库数据。视图是只读的,可以引用表、临时表和其他视图的任意组合。

您可以使用视图实现以下用途:

  • 出于支持或安全目的,让用户看不到复杂的 SELECT 语句。例如,您可以创建仅显示用户所需的各表中数据的视图,同时隐藏这些表中的敏感数据。

  • 将可能随时间而改变的表结构的详细信息封装在一致的用户界面后。

与投影不同,视图不实体化 — 也就是说,它们不在磁盘上存储数据。因此,存在以下限制:

  • 当底层表数据发生变更时,Vertica 不需要刷新视图数据。但是,访问和计算数据时,视图也会产生一些开销。

  • 视图不支持插入、删除或更新操作。

3.1 - 创建视图

您可以创建两种类型的视图:

  • CREATE VIEW 会创建一个可以跨所有会话持续存在的视图,直到显式删除了该视图 DROP VIEW

  • CREATE LOCAL TEMPORARY VIEW 会创建一个仅在当前 Vertica 会话期间可供访问且仅可供其创建者访问的视图。当前会话结束时,视图将会自动删除。

创建视图之后,您无法更改它的定义。您可以将它替换为具有相同名称的视图;或者可以删除它,然后对它重新定义。

权限

要创建视图,非超级用户必须具有以下权限:

有关允许用户访问视图的信息,请参阅查看访问权限

3.2 - 使用视图

视图可用于任何 SQL 查询或子查询的 FROM 子句中。执行期间,Vertica 会在内部将查询中使用的视图名称替换为视图定义中使用的实际查询。

创建视图示例

以下 CREATE VIEW 语句创建 myview 视图,它会将 store.store_sales_fact 表中所列的各个客户的所有收入相加,并按州对结果进行分组:

=> CREATE VIEW myview AS
   SELECT SUM(annual_income), customer_state FROM public.customer_dimension
     WHERE customer_key IN (SELECT customer_key FROM store.store_sales_fact)
     GROUP BY customer_state
     ORDER BY customer_state ASC;

您可以使用此视图查找所有大于 20 亿美元的合并收入:

=> SELECT * FROM myview where sum > 2000000000 ORDER BY sum DESC;
     SUM     | customer_state
-------------+----------------
 29253817091 | CA
 14215397659 | TX
  5225333668 | MI
  4907216137 | CO
  4581840709 | IL
  3769455689 | CT
  3330524215 | FL
  3310667307 | IN
  2832710696 | TN
  2806150503 | PA
  2793284639 | MA
  2723441590 | AZ
  2642551509 | UT
  2128169759 | NV
(14 rows)

启用视图访问

您可以查询自己创建的任何视图。要使其他非超级用户能够访问视图,您必须:

  • 对视图的基表具有 SELECT...WITH GRANT OPTION 权限

  • 授予用户对视图架构的 USAGE 权限

  • 授予用户对视图本身的 SELECT 权限

下面的示例向 user2 授予视图 schema1.view1 的访问权限。

=> GRANT USAGE ON schema schema1 TO user2;
=> GRANT SELECT ON schema1.view1 TO user2;

有关详细信息,请参阅GRANT(视图)

3.3 - 查看执行

当 Vertica 处理包含视图的查询时,它会将此视图视为子查询。Vertica 通过将查询扩展为包含视图定义中的查询来执行查询。例如,Vertica 会扩展使用视图所示的 myview 视图中的查询,以包含该视图封装的查询,如下所示:


=> SELECT * FROM
   (SELECT SUM(annual_income), customer_state FROM public.customer_dimension
    WHERE customer_key IN
      (SELECT customer_key FROM store.store_sales_fact)
    GROUP BY customer_state
    ORDER BY customer_state ASC)
    AS ship where sum > 2000000000;

视图优化

如果您查询一个视图,而您的查询仅包含该视图中已联接的表子集中的列,那么 Vertica 在执行该查询时会将该视图扩展为仅包含这些表。此优化要求满足以下条件之一:

  • 联接列为外键和主键。

  • 联接在包含唯一值的列中是左外联接或右外联接。

投影排序顺序

处理视图中的查询时,Vertica 仅考虑位于最外侧查询中的 ORDER BY 子句。如果视图定义包含了 ORDER BY 子句,Vertica 会忽视它。因此,要对视图返回的结果进行排序,您必须在最外侧查询中指定 ORDER BY 子句。

=> SELECT * FROM view‑name ORDER BY view‑column;

例如,以下视图定义包含一个位于 FROM 子查询中的 ORDER BY 子句。

=> CREATE VIEW myview AS SELECT SUM(annual_income), customer_state FROM public.customer_dimension
   WHERE customer_key IN
     (SELECT customer_key FROM store.store_sales_fact)
   GROUP BY customer_state
   ORDER BY customer_state ASC;

查询视图时,Vertica 不会对数据排序:

=> SELECT * FROM myview WHERE SUM > 2000000000;
     SUM     | customer_state
-------------+----------------
  5225333668 | MI
  2832710696 | TN
 14215397659 | TX
  4907216137 | CO
  2793284639 | MA
  3769455689 | CT
  3310667307 | IN
  2723441590 | AZ
  2642551509 | UT
  3330524215 | FL
  2128169759 | NV
 29253817091 | CA
  4581840709 | IL
  2806150503 | PA
(14 rows)

要返回经过排序的结果,外部查询必须包含 ORDER BY 子句:

=> SELECT * FROM myview WHERE SUM > 2000000000 ORDER BY customer_state ASC;
     SUM     | customer_state
-------------+----------------
  2723441590 | AZ
 29253817091 | CA
  4907216137 | CO
  3769455689 | CT
  3330524215 | FL
  4581840709 | IL
  3310667307 | IN
  2793284639 | MA
  5225333668 | MI
  2128169759 | NV
  2806150503 | PA
  2832710696 | TN
 14215397659 | TX
  2642551509 | UT
(14 rows)

运行时错误

如果 Vertica 不需要对生成运行时错误的表达式求值也能响应查询,则可能不会显示运行时错误。

例如,以下查询返回了一个错误,因为 TO_DATE 无法将字符串 F 转换为指定的日期格式:

=> SELECT TO_DATE('F','dd mm yyyy') FROM customer_dimension;
   ERROR: Invalid input for DD: "F"

现在使用相同的查询创建一个视图:

=> CREATE VIEW temp AS SELECT TO_DATE('F','dd mm yyyy')
   FROM customer_dimension;
   CREATE VIEW

在许多情况下,此视图会生成相同的错误消息。例如:

=> SELECT * FROM temp;
   ERROR: Invalid input for DD: "F"

但是,如果使用 COUNT 函数查询该视图,Vertica 将返回所需的结果。

=> SELECT COUNT(*) FROM temp;
 COUNT
-------
   100
(1 row)

此行为会按预期进行。您可以创建包含子查询的视图,而且在这些子查询中,并非每个行都打算用来传递谓词。

3.4 - 管理视图

获取视图信息

您可以查询系统表 VIEWSVIEW_COLUMNS 以获取有关现有视图的信息 — 例如,视图的定义和组成该视图的列的属性。您还可以查询系统表 VIEW_TABLES 以检查与视图相关的依赖关系 — 例如,确定在您删除某个表之前有多少视图引用了该表。

重命名视图

使用 ALTER VIEW 重命名视图。

删除视图

使用 DROP VIEW 删除视图。它仅会删除指定的视图。Vertica 不支持为视图使用 CASCADE 功能,也不检查依赖关系。删除视图会导致引用该视图的所有视图失败。

禁用和重新启用视图

如果您删除由视图引用的表,Vertica 不会删除该视图。但是,尝试从系统表 VIEW_COLUMNS 使用该视图或访问有关它的信息会返回一个错误,指出被引用的表不存在。如果您还原该表,Vertica 还会重新启用该视图。

4 - 修整表

高度标准化的数据库设计通常使用星形或雪花架构模型,包含多个大型事实表和多个较小的维度表。查询通常涉及大型事实表和多个维度表之间的联接。根据联接的表数和数据量,这些查询可能会产生大量开销。

为避免此问题,一些用户创建了对查询所需的所有事实表和维度表列进行组合的宽表。这些表可以显著加快查询执行速度。但是,维护冗余的标准化和非标准化数据集有其自身的管理成本。

非标准化或修整表可以最大限度地减少这些问题。修整表可以包含通过查询其他表来获取其值的列。对源表和修整表的操作是解耦的;一个表的更改不会自动传播到另一个表。这最大限度地减少了非标准化表的典型开销。

修整表使用以下一个或两个列约束子句来定义派生列:

  • 使用 `CREATE TABLE` 或 `ALTER TABLE...ADD COLUMN` 创建列时,DEFAULT query-expression 设置列值。

  • 调用函数 REFRESH_COLUMNS 时,SET USING query-expression 设置列值。

在这两种情况下,query-expression 必须只返回一个行和列值,或者不返回。如果查询未返回任何行,则列值设置为 NULL。

与 Vertica 中定义的其他表一样,您可以随时在修整表中添加和移除 DEFAULT 和 SET USING 列。Vertica 强制实施修整表与其查询的表之间的依赖关系。有关详细信息,请参阅修改 SET USING 和 DEFAULT 列

4.1 - 修整表示例

在以下示例中,列 orderFact.cust_nameorderFact.cust_gender 分别定义为 SET USING 和 DEFAULT 列。两列都通过查询表 custDim 来获取它们的值:

=> CREATE TABLE public.custDim(
       cid int PRIMARY KEY NOT NULL,
       name varchar(20),
       age int,
      gender varchar(1)
);

=> CREATE TABLE public.orderFact(
       order_id int PRIMARY KEY NOT NULL,
       order_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
       cid int REFERENCES public.custDim(cid),
       cust_name varchar(20) SET USING (SELECT name FROM public.custDim WHERE (custDim.cid = orderFact.cid)),
       cust_gender varchar(1) DEFAULT (SELECT gender FROM public.custDim WHERE (custDim.cid = orderFact.cid)),
       amount numeric(12,2)
)
PARTITION BY order_date::DATE GROUP BY CALENDAR_HIERARCHY_DAY(order_date::DATE, 2, 2);

以下 INSERT 命令将数据加载到两个表中:

=> INSERT INTO custDim VALUES(1, 'Alice', 25, 'F');
=> INSERT INTO custDim VALUES(2, 'Boz', 30, 'M');
=> INSERT INTO custDim VALUES(3, 'Eva', 32, 'F');
=>
=> INSERT INTO orderFact (order_id, cid, amount) VALUES(100, 1, 15);
=> INSERT INTO orderFact (order_id, cid, amount) VALUES(200, 1, 1000);
=> INSERT INTO orderFact (order_id, cid, amount) VALUES(300, 2, -50);
=> INSERT INTO orderFact (order_id, cid, amount) VALUES(400, 3, 100);
=> INSERT INTO orderFact (order_id, cid, amount) VALUES(500, 2, 200);
=> COMMIT;

查询此表时,Vertica 返回以下结果集:

=> SELECT * FROM custDim;
 cid | name  | age | gender
-----+-------+-----+--------
   1 | Alice |  25 | F
   2 | Boz   |  30 | M
   3 | Eva   |  32 | F
(3 rows)

=> SELECT order_id, order_date::date, cid, cust_name, cust_gender, amount FROM orderFact ORDER BY cid;
 order_id | order_date | cid | cust_name | cust_gender | amount
----------+------------+-----+-----------+-------------+---------
      100 | 2018-12-31 |   1 |           | F           |   15.00
      200 | 2018-12-31 |   1 |           | F           | 1000.00
      300 | 2018-12-31 |   2 |           | M           |  -50.00
      500 | 2018-12-31 |   2 |           | M           |  200.00
      400 | 2018-12-31 |   3 |           | F           |  100.00
(5 rows)

Vertica 自动填充DEFAULT 列 orderFact.cust_gender,但 SET USING 列 orderFact.cust_name 保持为 NULL。您可以通过在修整表 orderFact 上调用函数 REFRESH_COLUMNS 来自动填充此列。此函数调用列 orderFact.cust_name 的 SET USING 查询并从结果集中填充该列:

=> SELECT REFRESH_COLUMNS('orderFact', 'cust_name', 'REBUILD');

    REFRESH_COLUMNS
-------------------------------
refresh_columns completed
(1 row)

=> SELECT order_id, order_date::date, cid, cust_name, cust_gender, amount FROM orderFact ORDER BY cid;
 order_id | order_date | cid | cust_name | cust_gender | amount
----------+------------+-----+-----------+-------------+---------
      100 | 2018-12-31 |   1 | Alice     | F           |   15.00
      200 | 2018-12-31 |   1 | Alice     | F           | 1000.00
      300 | 2018-12-31 |   2 | Boz       | M           |  -50.00
      500 | 2018-12-31 |   2 | Boz       | M           |  200.00
      400 | 2018-12-31 |   3 | Eva       | F           |  100.00
(5 rows)

4.2 - 创建修整表

修整表通常是一个事实表,其中一个或多个列通过 DEFAULT 或 SET USING 约束查询其他表的值。DEFAULT 和 SET USING 约束可用于所有数据类型的列。与其他列一样,您可以在创建修整表时设置这些约束,或者在之后的任何时间通过修改表 DDL 设置这些约束:

在所有情况下,您为这些约束设置的表达式都存储在系统表 COLUMNS 的 COLUMN_DEFAULT 和 COLUMN_SET_USING 列中。

通常,DEFAULT 和 SET USING 支持相同的表达式。这些包括:

有关 DEFAULT 和 SET USING 表达式的详细信息(包括限制),请参阅定义列值

4.3 - 必需权限

修整表上的以下操作需要权限,如下所示:

4.4 - DEFAULT 与 SET USING

修整表中的列可以查询具有 DEFAULT 和 SET USING 约束的其他表。在这两种情况下,查询表中的更改都不会自动传播到修整表。这两个约束的区别如下:

DEFAULT 列

Vertica 通常在将新行添加到修整表时通过 INSERT 和 COPY 等加载操作,或者当您使用指定 DEFAULT 表达式的新列创建或更改表时,对新行执行 DEFAULT 查询。在所有情况下,现有行中的值(包括具有 DEFAULT 表达式的其他列)保持不变。

要刷新列的默认值,您必须在该列上显式调用 UPDATE,如下所示:

=> UPDATE table-name SET column-name=DEFAULT;

SET USING 列

仅当您调用函数 REFRESH_COLUMNS 时,Vertica 才会执行 SET USING 查询。加载操作将新行中的 SET USING 列设置为 NULL。加载后,您必须调用 REFRESH_COLUMNS 从查询表中填充这些列。这在两个方面很有用:您可以将更新修整表的开销推迟到任何方便的时间;您可以重复查询源表以获取新数据。

SET USING 对于引用多个维度表中的数据的大型修整表特别有用。通常,只有一小部分 SET USING 列会发生更改,并且对修整表的查询并不总是需要最新数据。在这种情况下,您可以定期或仅在非高峰时段刷新表的内容。这些策略中的一种或两种可以最大限度地减少开销,并在查询大型数据集时提高性能。

您可以通过在以下两种模式之一中调用 REFRESH _COLUMNS 来控制刷新操作的范围:

  • UPDATE :将原始行标记为已删除并用新行替换它们。为了保存这些更新,您必须发出 COMMIT 语句。

  • REBUILD:替换指定列中的所有数据。重建操作是自动提交的。

如果对修整表进行了分区,则可以通过指定一个或多个分区键来减少在 REBUILD 模式下调用 REFRESH_COLUMNS 的开销。这样做会将重建操作限制到指定的分区。有关详细信息,请参阅基于分区的 REBUILD 操作

示例

以下 UPDATE 语句更新 custDim 表:

=> UPDATE custDim SET name='Roz', gender='F' WHERE cid=2;
 OUTPUT
--------
      1
(1 row)

=> COMMIT;
COMMIT

更改不会传播到修整表 orderFact(其中分别包含 SET USING 和 DEFAULT 列 cust_namecust_gender):


=> SELECT * FROM custDim ORDER BY cid;
 cid | name  | age | gender
-----+-------+-----+--------
   1 | Alice |  25 | F
   2 | Roz   |  30 | F
   3 | Eva   |  32 | F
(3 rows)

=> SELECT order_id, order_date::date, cid, cust_name, cust_gender, amount FROM orderFact ORDER BY cid;
 order_id | order_date | cid | cust_name | cust_gender | amount
----------+------------+-----+-----------+-------------+---------
      100 | 2018-12-31 |   1 | Alice     | F           |   15.00
      200 | 2018-12-31 |   1 | Alice     | F           | 1000.00
      300 | 2018-12-31 |   2 | Boz       | M           |  -50.00
      500 | 2018-12-31 |   2 | Boz       | M           |  200.00
      400 | 2018-12-31 |   3 | Eva       | F           |  100.00
(5 rows)

以下 INSERT 语句调用 cust_gender 列的 DEFAULT 查询并将该列设置为 F。加载操作不会调用 cust_name 列的 SET USING 查询,因此 cust_name 设置为 null:

=> INSERT INTO orderFact(order_id, cid, amount)  VALUES(500, 3, 750);
 OUTPUT
--------
      1
(1 row)

=> COMMIT;
COMMIT
=>  SELECT order_id, order_date::date, cid, cust_name, cust_gender, amount FROM orderFact ORDER BY cid;
 order_id | order_date | cid | cust_name | cust_gender | amount
----------+------------+-----+-----------+-------------+---------
      100 | 2018-12-31 |   1 | Alice     | F           |   15.00
      200 | 2018-12-31 |   1 | Alice     | F           | 1000.00
      300 | 2018-12-31 |   2 | Boz       | M           |  -50.00
      500 | 2018-12-31 |   2 | Boz       | M           |  200.00
      400 | 2018-12-31 |   3 | Eva       | F           |  100.00
      500 | 2018-12-31 |   3 |           | F           |  750.00
(6 rows)

要更新 cust_name 中的值,请通过调用 REFRESH_COLUMNS 来调用其 SET USING 查询。REFRESH_COLUMNS 执行 cust_name 的 SET USING 查询:它查询表 custDim 中的 name 列并使用以下值更新 cust_name

  • 将新行中的 cust_name 设置为 Eva

  • 返回 cid=2 的更新值,并将 Boz 更改为 Roz

=> SELECT REFRESH_COLUMNS ('orderFact','');
      REFRESH_COLUMNS
---------------------------
 refresh_columns completed
(1 row)

=> COMMIT;
COMMIT
=>  SELECT order_id, order_date::date, cid, cust_name, cust_gender, amount FROM orderFact ORDER BY cid;
 order_id | order_date | cid | cust_name | cust_gender | amount
----------+------------+-----+-----------+-------------+---------
      100 | 2018-12-31 |   1 | Alice     | F           |   15.00
      200 | 2018-12-31 |   1 | Alice     | F           | 1000.00
      300 | 2018-12-31 |   2 | Roz       | M           |  -50.00
      500 | 2018-12-31 |   2 | Roz       | M           |  200.00
      400 | 2018-12-31 |   3 | Eva       | F           |  100.00
      500 | 2018-12-31 |   3 | Eva       | F           |  750.00
(6 rows)

REFRESH_COLUMNS 仅影响列 cust_name 中的值。gender 列中的值未更改,因此 cid=2 (Roz) 的行设置仍设置为 M。要使用 custDim.gender 中的默认值重新填充 orderFact.cust_gender,请在 orderFact 上调用 UPDATE:

=> UPDATE orderFact SET cust_gender=DEFAULT WHERE cust_name='Roz';
 OUTPUT
--------
      2
(1 row)

=> COMMIT;
COMMIT
=> SELECT order_id, order_date::date, cid, cust_name, cust_gender, amount FROM orderFact ORDER BY cid;
 order_id | order_date | cid | cust_name | cust_gender | amount
----------+------------+-----+-----------+-------------+---------
      100 | 2018-12-31 |   1 | Alice     | F           |   15.00
      200 | 2018-12-31 |   1 | Alice     | F           | 1000.00
      300 | 2018-12-31 |   2 | Roz       | F           |  -50.00
      500 | 2018-12-31 |   2 | Roz       | F           |  200.00
      400 | 2018-12-31 |   3 | Eva       | F           |  100.00
      500 | 2018-12-31 |   3 | Eva       | F           |  750.00
(6 rows)

4.5 - 修改 SET USING 和 DEFAULT 列

修改 SET USING 和 DEFAULT 表达式

ALTER TABLE...ALTER COLUMN 可以将现有列设置为 SET USING 或 DEFAULT,或者更改现有 SET USING 或 DEFAULT 列的查询表达式。在这两种情况下,现有值都保持不变。Vertica 在以下情况下会刷新列值:

  • DEFAULT 列:仅在加载新行或调用 UPDATE 将列值设置为 DEFAULT 时刷新。

  • SET USING 列:仅当您在表上调用 REFRESH_COLUMNS 时才刷新。

例如,可以将整个列设置为 NULL,如下所示:

=> ALTER TABLE orderFact ALTER COLUMN cust_name SET USING NULL;
ALTER TABLE
=> SELECT REFRESH_COLUMNS('orderFact', 'cust_name', 'REBUILD');
REFRESH_COLUMNS
---------------------------
refresh_columns completed
(1 row)

=> SELECT order_id, order_date::date, cid, cust_name, cust_gender, amount FROM orderFact ORDER BY cid;
  order_id | order_date | cid | cust_name | cust_gender | amount
 ----------+------------+-----+-----------+-------------+---------
    100 | 2018-12-31 |   1 |           | F           |   15.00
    200 | 2018-12-31 |   1 |           | F           | 1000.00
    300 | 2018-12-31 |   2 |           | M           |  -50.00
    500 | 2018-12-31 |   2 |           | M           |  200.00
    400 | 2018-12-31 |   3 |           | F           |  100.00
(5 rows)

有关详细信息,请参阅 定义列值

移除 SET USING 和 DEFAULT 约束

使用 ALTER TABLE...ALTER COLUMN 移除列的 SET USINGDEFAULT 约束,如下所示:

ALTER TABLE table-name ALTER COLUMN column-name DROP { SET USING | DEFAULT };

Vertica 会从指定列中移除约束,但该列及其数据不受影响。例如:

=> ALTER TABLE orderFact ALTER COLUMN cust_name DROP SET USING;
ALTER TABLE

删除由 SET USING 或 DEFAULT 查询的列

Vertica 强制实施修整表与其查询的表之间的依赖关系。除非删除操作还包含 CASCADE 选项,否则尝试删除查询的列或其表会返回错误。Vertica 通过从修整表中移除 SET USING 或 DEFAULT 约束来实施 CASCADE。表列及其数据不受影响。

例如,尝试删除表 custDim 中的列 name 会返回回退错误,因为该列由 SET USING 列 orderFact.cust_gender 引用:

=> ALTER TABLE custDim DROP COLUMN gender;
ROLLBACK 7301:  Cannot drop column "gender" since it is referenced in the default expression of table "public.orderFact", column "cust_gender"

要删除此列,请使用 CASCADE 选项:

=> ALTER TABLE custDim DROP COLUMN gender CASCADE;
ALTER TABLE

在执行操作过程中,Vertica 从 orderFact.cust_gender 中移除 DEFAULT 约束。不过,cust_gender 会保留先前从删除的 custDim.gender 列中查询的数据:

=> SELECT EXPORT_TABLES('','orderFact');
                                                EXPORT_TABLES
------------------------------------------------------------------------------------------------------------
CREATE TABLE public.orderFact
(
    order_id int NOT NULL,
    order_date timestamp NOT NULL DEFAULT (now())::timestamptz(6),
    cid int,
    cust_name varchar(20),
    cust_gender varchar(1) SET USING NULL,
    amount numeric(12,2),
    CONSTRAINT C_PRIMARY PRIMARY KEY (order_id) DISABLED
)
PARTITION BY ((orderFact.order_date)::date) GROUP BY (CASE WHEN ("datediff"('year', (orderFact.order_date)::date, ((now())::timestamptz(6))::date) >= 2) THEN (date_trunc('year', (orderFact.order_date)::date))::date WHEN ("datediff"('month', (orderFact.order_date)::date, ((now())::timestamptz(6))::date) >= 2) THEN (date_trunc('month', (orderFact.order_date)::date))::date ELSE (orderFact.order_date)::date END);
ALTER TABLE public.orderFact ADD CONSTRAINT C_FOREIGN FOREIGN KEY (cid) references public.custDim (cid);

(1 row)

=> SELECT * FROM orderFact;
 order_id |         order_date         | cid | cust_name | cust_gender | amount
----------+----------------------------+-----+-----------+-------------+---------
      400 | 2021-01-05 13:27:56.026115 |   3 |           | F           |  100.00
      300 | 2021-01-05 13:27:56.026115 |   2 |           | F           |  -50.00
      200 | 2021-01-05 13:27:56.026115 |   1 |           | F           | 1000.00
      500 | 2021-01-05 13:30:18.138386 |   3 |           | F           |  750.00
      100 | 2021-01-05 13:27:56.026115 |   1 |           | F           |   15.00
      500 | 2021-01-05 13:27:56.026115 |   2 |           | F           |  200.00
(6 rows)

4.6 - 重写 SET USING 查询

当您在平展表SET USING(或 DEFAULT USING)列中调用 REFRESH_COLUMNS 时,它将通过联接目标表和源表来执行 SET USING 查询。默认情况下,源表始终为联接的内表。大多数情况下,源表的基数小于目标表,因此 REFRESH_COLUMNS 有效地执行联接。

有时,特别是当您在分区表上调用 REFRESH_COLUMNS 时,源表有可能比目标表更大。在这种情况下,联接操作的性能可能欠佳。

您可以通过启用配置参数 RewriteQueryForLargeDim 来解决此问题。启用 (1) 后,Vertica 将反转目标表和源表之间的内部和外部联接,以此重写查询。

4.7 - SET USING 列对许可证限制的影响

Vertica 不会将非标准化列中的数据计入原始数据许可证限制。SET USING 列通过查询其他表中的列来获取它们的数据。只有源表中的数据才计入原始数据许可证限制。

有关 SET USING 限制的列表,请参阅定义列值

您可以使用以下命令移除 SET USING 列,使其计入许可证限制:

=> ALTER TABLE table1 ALTER COLUMN column1 DROP SET USING;

5 - SQL 分析

Vertica 分析是基于 ANSI 99 标准的 SQL 函数。这些函数用于处理复杂的分析和报告任务,例如:

  • 对处于特殊状态的合作时间最长的客户进行排序。

  • 计算指定的一段时间内零售额的移动平均值。

  • 查找同一年级所有学生的最高分数。

  • 对销售人员当前收到的销售奖金与其以前收到的销售奖金进行比较。

分析函数将返回聚合结果,但并不对结果集进行分组。它们会多次返回组值,每个记录一个组值。可以使用 window ORDER BY 子句排序组值或分区,但此顺序仅影响函数结果集,而不影响整个查询结果集。

有关受支持函数的详细信息,请参阅分析函数

5.1 - 调用分析函数

您可以调用如下分析函数:

analytic‑function(arguments) OVER(
  [ window-partition-clause ]
  [ window-order-clause [ window-frame-clause ] ]
)

分析函数的 OVER 子句最多可包含三个分子句,这些分子句指定如何对函数输入进行分区和排序,以及指定就当前行而言,如何构建输入框架。函数输出是查询在评估 FROMWHEREGROUP BYHAVING 子句之后返回的结果集。

有关语法的详细信息,请参阅分析函数

函数运行

分析函数按以下方式运行:

  1. 获取查询在执行所有联接和评估 FROMWHEREGROUP BY 以及 HAVING 子句之后返回的输入行。
  2. 根据窗口分区 (PARTITION BY) 子句对输入行进行分组。如果忽略此子句,所有输入行都将视为一个分区。
  3. 根据窗口顺序 (ORDER BY) 子句对每个分区中的行进行排序。
  4. 如果 OVER 子句包含窗口顺序子句,函数会检查窗口框架子句,并在此子句处理每个输入行时运行它。如果 OVER 子句忽略窗口框架子句,函数会将整个分区视为一个窗口框架。

限制

  • 分析函数仅允许在查询的 SELECT 子句和 ORDER BY 子句中使用。

  • 分析函数不可嵌套。例如,以下查询会抛出一条错误:

    => SELECT MEDIAN(RANK() OVER(ORDER BY sal) OVER()).
    

5.2 - 分析函数与聚合函数

与聚合函数一样,分析函数返回聚合结果,但分析函数不会对结果集进行分组。不过,它们会为每个记录多次返回组值,从而实现进一步分析。

通常,分析查询比聚合查询的运行速度更快,占用的资源更少。

示例

以下示例比较了 COUNT 聚合函数和其对应的分析函数 COUNT。这些示例使用如下方所定义的 employees 表:

CREATE TABLE employees(emp_no INT, dept_no INT);
INSERT INTO employees VALUES(1, 10);
INSERT INTO employees VALUES(2, 30);
INSERT INTO employees VALUES(3, 30);
INSERT INTO employees VALUES(4, 10);
INSERT INTO employees VALUES(5, 30);
INSERT INTO employees VALUES(6, 20);
INSERT INTO employees VALUES(7, 20);
INSERT INTO employees VALUES(8, 20);
INSERT INTO employees VALUES(9, 20);
INSERT INTO employees VALUES(10, 20);
INSERT INTO employees VALUES(11, 20);
COMMIT;

查询此表时将返回以下结果集:

=> SELECT * FROM employees ORDER BY emp_no;
 emp_no | dept_no
--------+---------
      1 |      10
      2 |      30
      3 |      30
      4 |      10
      5 |      30
      6 |      20
      7 |      20
      8 |      20
      9 |      20
     10 |      20
     11 |      20
(11 rows)

以下两个查询使用 COUNT 函数统计每个部门的员工人数。左侧的查询使用 COUNT 聚合函数;右侧的查询使用 COUNT 分析函数:

另请参阅

5.3 - 窗口分区

分区 (PARTITION BY) 子句组可在分析函数的 OVER 子句中进行指定(可选),用来在函数处理行之前输入这些行。窗口分区与聚合函数的 GROUP BY 子句相似,区别在于它仅为每个输入行返回一个结果行。如果您省略窗口分区子句,函数会将所有输入行视为一个分区。

指定窗口分区

您可以在分析函数的 OVER 子句中指定窗口分区,如下所示:

{ PARTITION BY expression[,...] | PARTITION BEST | PARTITION NODES }

有关语法的详细信息,请参阅窗口分区子句

示例

此主题中的示例使用 调用分析函数 中定义的 allsales 架构:

CREATE TABLE allsales(state VARCHAR(20), name VARCHAR(20), sales INT);
INSERT INTO allsales VALUES('MA', 'A', 60);
INSERT INTO allsales VALUES('NY', 'B', 20);
INSERT INTO allsales VALUES('NY', 'C', 15);
INSERT INTO allsales VALUES('MA', 'D', 20);
INSERT INTO allsales VALUES('MA', 'E', 50);
INSERT INTO allsales VALUES('NY', 'F', 40);
INSERT INTO allsales VALUES('MA', 'G', 10);
COMMIT;

每个州的销售中间值
以下查询使用分析 window-partition-clause 计算每个州的销售中间值。分析函数根据分区进行计算,并在下一个分区开始时重新开始。

=> SELECT state, name, sales, MEDIAN(sales)
      OVER (PARTITION BY state) AS median from allsales;

在中间值列中,结果分组到 MA (35) 和 NY (20) 的分区中。

 state | name | sales | median
-------+------+-------+--------
 NY    | C    |    15 |     20
 NY    | B    |    20 |     20
 NY    | F    |    40 |     20
-------------------------------
 MA    | G    |    10 |     35
 MA    | D    |    20 |     35
 MA    | E    |    50 |     35
 MA    | A    |    60 |     35
(7 rows)

所有州的销售中间值
以下查询计算了所有州的全部销售的中间值。如果您不结合任何参数使用 OVER(),则只有一个分区,即整个输入:

=> SELECT state, sum(sales), median(SUM(sales))
      OVER () AS median FROM allsales GROUP BY state;
 state | sum | median
-------+-----+--------
 NY    |  75 |  107.5
 MA    | 140 |  107.5
(2 rows)

大于中间值的销售(评估顺序)
除查询的最后一个 SQL ORDER BY 子句之外,分析函数的评估在所有其他子句之后进行。因此,如果查询检索销售大于中间值的所有行,它会返回错误,因为在分析函数之前应用了 WHERE 子句,这时 m 列尚不存在:

=> SELECT name, sales,  MEDIAN(sales) OVER () AS m
   FROM allsales WHERE sales > m;
   ERROR 2624:  Column "m" does not exist

您可以通过在子查询中放入谓词 WHERE sales > m 来解决此问题:

=> SELECT * FROM
   (SELECT name, sales, MEDIAN(sales) OVER () AS m FROM allsales) sq
   WHERE sales > m;
 name | sales | m
------+-------+----
 F    |    40 | 20
 E    |    50 | 20
 A    |    60 | 20
(3 rows)

有关更多示例,请参阅分析查询示例

5.4 - 窗口排序

窗口排序指定如何对提供给分析函数的行进行排序。如下所示,您可以通过函数的 OVER 子句中的 ORDER BY 子句指定窗口排序。如果 OVER 子句包含窗口分区子句,则在每个分区中对行进行排序。如果没有显式指定任何窗口框架,窗口顺序子句还会创建默认窗口框架。

窗口顺序子句仅在窗口结果集中指定顺序。除 OVER 子句以外,查询可以拥有自己的 ORDER BY 子句。它优先于窗口顺序子句,并对最终结果集进行排序。

指定窗口顺序

您可以在分析函数的 OVER 子句中指定窗口框架,如下所示。

ORDER BY { expression [ ASC | DESC [ NULLS { FIRST | LAST | AUTO } ] ]
  }[,...]

有关语法的详细信息,请参阅窗口顺序子句

分析函数用法

分析聚合函数(例如 SUM)支持窗口顺序子句。

必需用法
以下函数需要窗口顺序子句:

无效用法
窗口顺序子句不可与以下函数结合使用:

示例

以下示例使用 allsales 表架构:

CREATE TABLE allsales(state VARCHAR(20), name VARCHAR(20), sales INT);
INSERT INTO allsales VALUES('MA', 'A', 60);
INSERT INTO allsales VALUES('NY', 'B', 20);
INSERT INTO allsales VALUES('NY', 'C', 15);
INSERT INTO allsales VALUES('MA', 'D', 20);
INSERT INTO allsales VALUES('MA', 'E', 50);
INSERT INTO allsales VALUES('NY', 'F', 40);
INSERT INTO allsales VALUES('MA', 'G', 10);
COMMIT;

5.5 - 窗口框架

分析函数的窗口框架由一组与函数当前正在评估的行相对的行组成。分析函数处理此行及其窗口框架之后,Vertica 会将当前行向前移动,并相应地调整框架边界。如果 OVER 子句还指定了分区,Vertica 也会检查框架边界是否跨越了分区边界。此过程将不断重复,直到函数评估了最后一个分区的最后一个行。

指定窗口框架

您可以在分析函数的 OVER 子句中指定窗口框架,如下所示。

{ ROWS | RANGE } { BETWEEN start‑point AND end‑point } | start‑point

start-point / end‑point

{ UNBOUNDED {PRECEDING | FOLLOWING}
  | CURRENT ROW
  | constant-value  {PRECEDING | FOLLOWING}}

start‑pointend‑point 指定相对于当前行的窗口框架偏移。ROWSRANGE 关键字指定偏移是物理偏移还是逻辑偏移。如果仅指定起始点,Vertica 将创建一个从此点到当前行的窗口。

有关语法的详细信息,请参阅窗口框架子句

要求

要指定窗口框架,OVER 还必须指定窗口顺序 (ORDER BY) 子句。如果 OVER 子句包含窗口顺序子句,但没有指定窗口框架,则函数会创建从当前分区的第一行扩展到当前行的默认框架。它等同于以下子句:

RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

窗口聚合函数

支持窗口框架的分析函数称为窗口聚合函数。它们返回移动平均数和累计结果等信息。要将以下函数用作窗口(分析)聚合函数,而不是基本聚合函数,OVER 子句必须指定窗口顺序子句和(可选)窗口框架子句。如果 OVER 子句没有指定窗口框架,则函数将如前所述创建默认窗口框架。

以下分析函数支持窗口框架:

包含空 OVER 子句的窗口聚合函数不会创建窗口框架。如果所有输入都视为一个分区,则函数将用作报告函数。

5.5.1 - 具有物理偏移的窗口 (ROWS)

窗口物理框架子句中的 ROWS 关键字将窗口大小指定为相对于当前行的行数。值只能为 INTEGER 数据类型。

示例

此页面中的示例使用 emp 表架构:

CREATE TABLE emp(deptno INT, sal INT, empno INT);
INSERT INTO emp VALUES(10,101,1);
INSERT INTO emp VALUES(10,104,4);
INSERT INTO emp VALUES(20,100,11);
INSERT INTO emp VALUES(20,109,7);
INSERT INTO emp VALUES(20,109,6);
INSERT INTO emp VALUES(20,109,8);
INSERT INTO emp VALUES(20,110,10);
INSERT INTO emp VALUES(20,110,9);
INSERT INTO emp VALUES(30,102,2);
INSERT INTO emp VALUES(30,103,3);
INSERT INTO emp VALUES(30,105,5);
COMMIT;

以下查询调用 COUNT 来对当前行及其前面的行(最多两行)进行计数:

SELECT deptno, sal, empno,  COUNT(*) OVER
         (PARTITION BY deptno ORDER BY sal ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
        AS count FROM emp;

OVER 子句包含三个组件:

  • 窗口分区子句 PARTITION BY deptno

  • 排序依据子句 ORDER BY sal

  • 窗口框架子句ROWS BETWEEN 2 PRECEDING AND CURRENT ROW。此子句将窗口大小定义为从当前行扩展到当前行前面的两个行。

查询返回的结果分为三个分区,在下文中以红线表示。在第二个分区 (deptno=20) 中,COUNT 按以下方式处理窗口框架子句:

  1. 创建第一个窗口(绿方框)。此窗口包含一个行,因为当前行(蓝方框)也是此分区的第一个行。因此,count 列中的值显示了当前窗口中的行数,即 1:

  2. COUNT 处理此分区的第一行之后,它会将当前行重置为此分区的第二个行。窗口此时跨越了当前行和前面的一个行,因此,COUNT 返回了值 2:

  3. COUNT 处理此分区的第二行之后,它会将当前行重置为此分区的第三个行。窗口此时跨越了当前行和前面的两个行,因此,COUNT 返回了值 3:

  4. 此后,COUNT 继续处理剩余的分区行,并相应地移动窗口,但窗口大小 (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) 不变,仍为三行。因此,count 列中的值也不变 (3):




5.5.2 - 具有逻辑偏移的窗口 (RANGE)

RANGE 关键字将分析窗口框架定义为当前行的逻辑偏移。

对于每个行,分析函数使用窗口顺序子句 (ORDER_BY) 列或表达式来计算窗口框架大小,具体如下所述:

  1. 在当前分区中,根据连续行的 ORDER_BY 值评估当前行的 ORDER_BY 值。

  2. 确定这些行中有哪些行符合相对于当前行的指定范围要求。

  3. 创建仅包含这些行的窗口框架。

  4. 在当前窗口中执行。

示例

此示例使用 property_sales 表,此表包含街区的住宅销售数据:

=> SELECT property_key, neighborhood, sell_price FROM property_sales ORDER BY neighborhood, sell_price;
 property_key | neighborhood  | sell_price
--------------+---------------+------------
        10918 | Jamaica Plain |     353000
        10921 | Jamaica Plain |     450000
        10927 | Jamaica Plain |     450000
        10922 | Jamaica Plain |     474000
        10919 | Jamaica Plain |     515000
        10917 | Jamaica Plain |     675000
        10924 | Jamaica Plain |     675000
        10920 | Jamaica Plain |     705000
        10923 | Jamaica Plain |     710000
        10926 | Jamaica Plain |     875000
        10925 | Jamaica Plain |     900000
        10930 | Roslindale    |     300000
        10928 | Roslindale    |     422000
        10932 | Roslindale    |     450000
        10929 | Roslindale    |     485000
        10931 | Roslindale    |     519000
        10938 | West Roxbury  |     479000
        10933 | West Roxbury  |     550000
        10937 | West Roxbury  |     550000
        10934 | West Roxbury  |     574000
        10935 | West Roxbury  |     598000
        10936 | West Roxbury  |     615000
        10939 | West Roxbury  |     720000
(23 rows)

AVG 分析函数可以获取每个街区的近似销售价格的平均值。对于每个住宅,下列查询计算了销售价格比该住宅高或低 50000 美元的所有其他街区住宅的平均销售价格:

=> SELECT property_key, neighborhood, sell_price, AVG(sell_price) OVER(
     PARTITION BY neighborhood ORDER BY sell_price
     RANGE BETWEEN 50000 PRECEDING and 50000 FOLLOWING)::int AS comp_sales
     FROM property_sales ORDER BY neighborhood;
 property_key | neighborhood  | sell_price | comp_sales
--------------+---------------+------------+------------
        10918 | Jamaica Plain |     353000 |     353000
        10927 | Jamaica Plain |     450000 |     458000
        10921 | Jamaica Plain |     450000 |     458000
        10922 | Jamaica Plain |     474000 |     472250
        10919 | Jamaica Plain |     515000 |     494500
        10917 | Jamaica Plain |     675000 |     691250
        10924 | Jamaica Plain |     675000 |     691250
        10920 | Jamaica Plain |     705000 |     691250
        10923 | Jamaica Plain |     710000 |     691250
        10926 | Jamaica Plain |     875000 |     887500
        10925 | Jamaica Plain |     900000 |     887500
        10930 | Roslindale    |     300000 |     300000
        10928 | Roslindale    |     422000 |     436000
        10932 | Roslindale    |     450000 |     452333
        10929 | Roslindale    |     485000 |     484667
        10931 | Roslindale    |     519000 |     502000
        10938 | West Roxbury  |     479000 |     479000
        10933 | West Roxbury  |     550000 |     568000
        10937 | West Roxbury  |     550000 |     568000
        10934 | West Roxbury  |     574000 |     577400
        10935 | West Roxbury  |     598000 |     577400
        10936 | West Roxbury  |     615000 |     595667
        10939 | West Roxbury  |     720000 |     720000
(23 rows)

AVG 按以下方式处理此查询:

  1. AVG 评估了第一个分区 (Jamaica Plain) 的第 1 行,但没有发现比此行的 sell_price(353000 美元)高或低 50000 美元范围内的销售价格。 AVG 创建了一个仅包含此行的窗口,并为第 1 行返回了平均值 353000 美元:

  2. AVG 评估了第 2 行,发现了有三个 sell_price 值与当前行的相差幅度在 50000 美元范围内。 AVG 创建了一个包含了这三个行的窗口,并为第 2 行返回了平均值 458000 美元:

  3. AVG 评估了第 3 行,同样发现了有三个 sell_price 值与当前行的相差幅度在 50000 美元范围内。 AVG 创建了一个窗口,它与前面的窗口相同,并为第 3 行返回了相同的平均值 458000 美元:

  4. AVG 评估了第 4 行,发现了有四个 sell_price 值与当前行的相差幅度在 50000 美元范围内。 AVG 将窗口扩展到可包含第 2 行至第 5 行,并为第 4 行返回了平均值 472250 美元:

  5. 同样地,AVG 还评估了此分区的其他行。当函数评估第二个分区 (Roslindale) 的第一行时,它会如下所述重置窗口:

限制

如果 RANGE 指定一个常数值,此值的数据类型与窗口的 ORDER BY 数据类型必须相同。以下情况除外:

  • RANGE 如果窗口顺序子句数据类型为以下之一,可以指定 INTERVAL Year to MonthTIMESTAMPTIMESTAMP WITH TIMEZONEDATETIMETIME WITH TIMEZONE 均不受支持。

  • RANGE 如果窗口顺序子句数据为以下之一,可以指定 INTERVAL Day to SecondTIMESTAMPTIMESTAMP WITH TIMEZONEDATETIMETIME WITH TIMEZONE

窗口顺序子句必须指定以下数据类型之一:NUMERICDATE/TIMEFLOAT INTEGER。如果窗口指定了以下框架之一,则会忽视此要求:

  • RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

  • RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING

  • RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING

5.5.3 - 报告聚合

采用 window-frame-clause 的某些分析函数是报告聚合函数。通过这些函数,您可以比较分区的聚合值与明细行,而不使用相关子查询或联接。

如果结合空 OVER() 子句使用窗口聚合,分析函数将用作报告函数,而且整个输入会被视为一个分区。

关于标准差函数和方差函数

使用标准差函数时,较小的标准差表示数据点通常非常接近于平均值,而较大的标准差表示数据点分散在一个较大的值范围内。

标准差通常用图形表示,而分布式标准差为典型的钟形曲线。

方差函数会衡量一组数值的分散距离。

示例

将报告聚合的窗口看作是定义为 UNBOUNDED PRECEDING 和 UNBOUNDED FOLLOWING 的窗口。忽略 window-order-clause 会使分区中的所有行也位于窗口中(报告聚合)。

=> SELECT deptno, sal, empno, COUNT(sal)
  OVER (PARTITION BY deptno) AS COUNT FROM emp;
 deptno | sal | empno | count
--------+-----+-------+-------
     10 | 101 |     1 |     2
     10 | 104 |     4 |     2
------------------------------
     20 | 110 |    10 |     6
     20 | 110 |     9 |     6
     20 | 109 |     7 |     6
     20 | 109 |     6 |     6
     20 | 109 |     8 |     6
     20 | 100 |    11 |     6
------------------------------
     30 | 105 |     5 |     3
     30 | 103 |     3 |     3
     30 | 102 |     2 |     3
(11 rows)

如果上述查询中的 OVER() 子句包含 window-order-clause (例如 ORDER BY sal),它将变为包含 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 默认窗口的移动窗口(窗口聚合)查询:

=> SELECT deptno, sal, empno, COUNT(sal)  OVER (PARTITION BY deptno ORDER BY sal) AS COUNT FROM emp;
 deptno | sal | empno | count
--------+-----+-------+-------
     10 | 101 |     1 |     1
     10 | 104 |     4 |     2
------------------------------
     20 | 100 |    11 |     1
     20 | 109 |     7 |     4
     20 | 109 |     6 |     4
     20 | 109 |     8 |     4
     20 | 110 |    10 |     6
     20 | 110 |     9 |     6
------------------------------
     30 | 102 |     2 |     1
     30 | 103 |     3 |     2
     30 | 105 |     5 |     3
(11 rows)

LAST_VALUE 怎么样?

您可能会想为什么您不能只使用 LAST_VALUE() 分析函数。

例如,对于每个员工,获取部门中最高薪水:

=> SELECT deptno, sal, empno,LAST_VALUE(empno) OVER (PARTITION BY deptno ORDER BY sal) AS lv FROM emp;
![](/images/reporting-aggregates2.png)

受默认窗口语义影响,LAST_VALUE 并非总是会返回分区的最后一个值。如果在分析子句中忽略 window-frame-clause,则 LAST_VALUE 会在默认窗口中运行。因此,结果似乎并不直观,因为函数没有返回当前分区的最后一个值。它只返回了窗口的最后一个值,并且此值随当前正在处理的输入行而不断变化。

请记住默认窗口:

OVER (PARTITION BY deptno ORDER BY sal)

等同于:

OVER(PARTITION BY deptno ORDER BY salROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

如果您要返回分区的最后一个值,请使用 UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。

=> SELECT deptno, sal, empno, LAST_VALUE(empno)
OVER (PARTITION BY deptno ORDER BY sal
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lv
FROM emp;
![](/images/reporting-aggregates9.png)

Vertica 建议结合 window-order-clause 使用 LAST_VALUE 以生成具有确定性的结果。

在以下示例中,empno 6、7 和 8 的薪水相同,因此它们位于相邻行中。在本例中,empno 8 首先出现,但不能保证该顺序。

请注意,在上述输出中,最后一个值是 7,它是分区 deptno = 20 的最后一个行。如果各行的顺序不同,则函数会返回不同的值:

此时,最后一个值是 6,它是分区 deptno = 20 的最后一个行。解决方案是将唯一键添加到排序顺序中。即使查询的顺序发生变化,结果也始终相同,因此非常确定。

=> SELECT deptno, sal, empno, LAST_VALUE(empno)
OVER (PARTITION BY deptno ORDER BY sal, empno
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as lv
FROM emp;

请注意行现在如何按 empno 进行排序,这时最后一个值一直是 8,而且无论查询顺序如何都不会造成影响。

5.6 - 命名窗口

分析函数的 OVER 子句可以引用命名窗口,此窗口封装了一个或多个窗口子句:窗口分区 (PARTITION BY) 子句和(可选)窗口顺序 (ORDER BY) 子句。编写通过类似的 OVER 子句语法调用多个分析函数的查询时,例如,它们使用相同的分区子句,则命名窗口非常有用。

查询按如下方式命名窗口:

WINDOW window‑name AS ( window-partition-clause [window-order-clause] );

同一个查询可以命名和引用多个窗口。在同一个查询中,所有窗口名称都必须是唯一的。

示例

以下查询调用了两个分析函数,即 RANKDENSE_RANK。因为两个函数使用同一个分区和顺序子句,查询对指定这两个子句的 w 窗口进行了命名。两个函数引用了此窗口,如下所示:

=> SELECT employee_region region, employee_key, annual_salary,
     RANK() OVER w Rank,
     DENSE_RANK() OVER w "Dense Rank"
     FROM employee_dimension WINDOW w AS (PARTITION BY employee_region ORDER BY annual_salary);
              region              | employee_key | annual_salary | Rank | Dense Rank
----------------------------------+--------------+---------------+------+------------
 West                             |         5248 |          1200 |    1 |          1
 West                             |         6880 |          1204 |    2 |          2
 West                             |         5700 |          1214 |    3 |          3
 West                             |         9857 |          1218 |    4 |          4
 West                             |         6014 |          1218 |    4 |          4
 West                             |         9221 |          1220 |    6 |          5
 West                             |         7646 |          1222 |    7 |          6
 West                             |         6621 |          1222 |    7 |          6
 West                             |         6488 |          1224 |    9 |          7
 West                             |         7659 |          1226 |   10 |          8
 West                             |         7432 |          1226 |   10 |          8
 West                             |         9905 |          1226 |   10 |          8
 West                             |         9021 |          1228 |   13 |          9
 West                             |         7855 |          1228 |   13 |          9
 West                             |         7119 |          1230 |   15 |         10
 ...

如果命名窗口忽略了顺序子句,查询的 OVER 子句可以指定自己的顺序子句。例如,您可以修改上一个查询,让每个函数使用不同的顺序子句。命名窗口在定义后仅包括一个分区子句:

=> SELECT employee_region region, employee_key, annual_salary,
    RANK() OVER (w ORDER BY annual_salary DESC) Rank,
    DENSE_RANK() OVER (w ORDER BY annual_salary ASC) "Dense Rank"
    FROM employee_dimension WINDOW w AS (PARTITION BY employee_region);
              region              | employee_key | annual_salary | Rank | Dense Rank
----------------------------------+--------------+---------------+------+------------
 West                             |         5248 |          1200 | 2795 |          1
 West                             |         6880 |          1204 | 2794 |          2
 West                             |         5700 |          1214 | 2793 |          3
 West                             |         6014 |          1218 | 2791 |          4
 West                             |         9857 |          1218 | 2791 |          4
 West                             |         9221 |          1220 | 2790 |          5
 West                             |         6621 |          1222 | 2788 |          6
 West                             |         7646 |          1222 | 2788 |          6
 West                             |         6488 |          1224 | 2787 |          7
 West                             |         7432 |          1226 | 2784 |          8
 West                             |         9905 |          1226 | 2784 |          8
 West                             |         7659 |          1226 | 2784 |          8
 West                             |         7855 |          1228 | 2782 |          9
 West                             |         9021 |          1228 | 2782 |          9
 West                             |         7119 |          1230 | 2781 |         10
 ...

同样地,如果命名窗口包含一个顺序子句,OVER 子句可指定命名窗口也指定一个窗口框架子句。如果无法将命名窗口定义为包含一个窗口框架子句,上述用法会非常有用。

例如,以下查询定义了一个封装分区和顺序子句的窗口。OVER 子句调用此窗口,还包含一个窗口框架子句:

=> SELECT deptno, sal, empno,  COUNT(*) OVER (w ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS count
 FROM emp WINDOW w AS (PARTITION BY deptno ORDER BY sal);
 deptno | sal | empno | count
--------+-----+-------+-------
     10 | 101 |     1 |     1
     10 | 104 |     4 |     2
     20 | 100 |    11 |     1
     20 | 109 |     8 |     2
     20 | 109 |     6 |     3
     20 | 109 |     7 |     3
     20 | 110 |    10 |     3
     20 | 110 |     9 |     3
     30 | 102 |     2 |     1
     30 | 103 |     3 |     2
     30 | 105 |     5 |     3
(11 rows)

递归窗口引用

WINDOW 子句可引用另一个已经命名的窗口。例如,由于命名窗口 w1 是在 w2 之前定义的,因此定义 w2WINDOW 子句可以引用 w1

=> SELECT RANK() OVER(w1 ORDER BY sal DESC), RANK() OVER w2
   FROM EMP WINDOW w1 AS (PARTITION BY deptno), w2 AS (w1 ORDER BY sal);

限制

  • OVER 子句只能引用一个命名窗口。

  • 同一个查询中的每个 WINDOW 子句都必须拥有唯一的名称。

5.7 - 分析查询示例

此部分中的主题演示了如何使用分析查询进行计算。

5.7.1 - 计算中间值

中间值是一种数值,它将样本分为上下两部分。例如,您可以将所有观察值从最小值递增排序到最大值,然后选择居于正中间的值,即可获得有限数值列表的中间值。

如果观察值的数目为偶数,则不存在单个中间值;此时中间值是两个中间值的平均值。

以下示例使用此表:

CREATE TABLE allsales(state VARCHAR(20), name VARCHAR(20), sales INT);
INSERT INTO allsales VALUES('MA', 'A', 60);
INSERT INTO allsales VALUES('NY', 'B', 20);
INSERT INTO allsales VALUES('NY', 'C', 15);
INSERT INTO allsales VALUES('MA', 'D', 20);
INSERT INTO allsales VALUES('MA', 'E', 50);
INSERT INTO allsales VALUES('NY', 'F', 40);
INSERT INTO allsales VALUES('MA', 'G', 10);
COMMIT;

您可以使用分析函数 MEDIAN 计算此表中所有销售的中间值。在下述查询中,函数的 OVER 子句为空,因此查询为结果集的每个行返回了相同的聚合值。

=> SELECT name, sales, MEDIAN(sales) OVER() AS median FROM allsales;
 name | sales | median
------+-------+--------
 G    |    10 |     20
 C    |    15 |     20
 D    |    20 |     20
 B    |    20 |     20
 F    |    40 |     20
 E    |    50 |     20
 A    |    60 |     20
(7 rows)

您可以修改此查询,按州对销售进行分组,然后获取每组的中间值。为此,请在 OVER 子句中包含一个窗口分区子句:

=> SELECT state, name, sales, MEDIAN(sales) OVER(partition by state) AS median FROM allsales;
 state | name | sales | median
-------+------+-------+--------
 MA    | G    |    10 |     35
 MA    | D    |    20 |     35
 MA    | E    |    50 |     35
 MA    | A    |    60 |     35
 NY    | C    |    15 |     20
 NY    | B    |    20 |     20
 NY    | F    |    40 |     20
(7 rows)

5.7.2 - 获取两支股票的价格差异

以下子查询选择了两支感兴趣的股票。外部查询使用分析函数的 LAST_VALUE() 和 OVER() 组件以及 IGNORE NULLS。

架构

DROP TABLE Ticks CASCADE;


CREATE TABLE Ticks (ts TIMESTAMP, Stock varchar(10), Bid float);
INSERT INTO Ticks VALUES('2011-07-12 10:23:54', 'abc', 10.12);
INSERT INTO Ticks VALUES('2011-07-12 10:23:58', 'abc', 10.34);
INSERT INTO Ticks VALUES('2011-07-12 10:23:59', 'abc', 10.75);
INSERT INTO Ticks VALUES('2011-07-12 10:25:15', 'abc', 11.98);
INSERT INTO Ticks VALUES('2011-07-12 10:25:16', 'abc');
INSERT INTO Ticks VALUES('2011-07-12 10:25:22', 'xyz', 45.16);
INSERT INTO Ticks VALUES('2011-07-12 10:25:27', 'xyz', 49.33);
INSERT INTO Ticks VALUES('2011-07-12 10:31:12', 'xyz', 65.25);
INSERT INTO Ticks VALUES('2011-07-12 10:31:15', 'xyz');

COMMIT;

Ticks 表

=> SELECT * FROM ticks;
         ts          | stock |  bid
---------------------+-------+-------
 2011-07-12 10:23:59 | abc   | 10.75
 2011-07-12 10:25:22 | xyz   | 45.16
 2011-07-12 10:23:58 | abc   | 10.34
 2011-07-12 10:25:27 | xyz   | 49.33
 2011-07-12 10:23:54 | abc   | 10.12
 2011-07-12 10:31:15 | xyz   |
 2011-07-12 10:25:15 | abc   | 11.98
 2011-07-12 10:25:16 | abc   |
 2011-07-12 10:31:12 | xyz   | 65.25
(9 rows)

查询

 => SELECT ts, stock, bid, last_value(price1 IGNORE NULLS)
   OVER(ORDER BY ts) - last_value(price2 IGNORE NULLS)
   OVER(ORDER BY ts)   as price_diff
 FROM
  (SELECT ts, stock, bid,
    CASE WHEN stock = 'abc' THEN bid ELSE NULL END AS price1,
    CASE WHEN stock = 'xyz' then bid ELSE NULL END AS price2
    FROM ticks
    WHERE stock IN ('abc','xyz')
  ) v1
 ORDER BY ts;
         ts          | stock |  bid  | price_diff
---------------------+-------+-------+------------
 2011-07-12 10:23:54 | abc   | 10.12 |
 2011-07-12 10:23:58 | abc   | 10.34 |
 2011-07-12 10:23:59 | abc   | 10.75 |
 2011-07-12 10:25:15 | abc   | 11.98 |
 2011-07-12 10:25:16 | abc   |       |
 2011-07-12 10:25:22 | xyz   | 45.16 |     -33.18
 2011-07-12 10:25:27 | xyz   | 49.33 |     -37.35
 2011-07-12 10:31:12 | xyz   | 65.25 |     -53.27
 2011-07-12 10:31:15 | xyz   |       |     -53.27
(9 rows)

5.7.3 - 计算移动平均数

计算移动平均数有助于估计数据集中的趋势。移动平均数是一段时间内任何数字子集的平均值。例如,如果您拥有跨十年的零售数据,您可以计算三年移动平均数、四年移动平均数等。此示例计算某支股票的出价在 40 秒内的移动平均数。此示例使用 ticks 表架构。

查询

=> SELECT ts, bid, AVG(bid)
   OVER(ORDER BY ts
       RANGE BETWEEN INTERVAL '40 seconds'
       PRECEDING AND CURRENT ROW)
FROM ticks
WHERE stock = 'abc'
GROUP BY bid, ts
ORDER BY ts;
         ts          |  bid  |     ?column?
---------------------+-------+------------------
 2011-07-12 10:23:54 | 10.12 |            10.12
 2011-07-12 10:23:58 | 10.34 |            10.23
 2011-07-12 10:23:59 | 10.75 | 10.4033333333333
 2011-07-12 10:25:15 | 11.98 |            11.98
 2011-07-12 10:25:16 |       |            11.98
(5 rows)
DROP TABLE Ticks CASCADE;


CREATE TABLE Ticks (ts TIMESTAMP, Stock varchar(10), Bid float);
INSERT INTO Ticks VALUES('2011-07-12 10:23:54', 'abc', 10.12);
INSERT INTO Ticks VALUES('2011-07-12 10:23:58', 'abc', 10.34);
INSERT INTO Ticks VALUES('2011-07-12 10:23:59', 'abc', 10.75);
INSERT INTO Ticks VALUES('2011-07-12 10:25:15', 'abc', 11.98);
INSERT INTO Ticks VALUES('2011-07-12 10:25:16', 'abc');
INSERT INTO Ticks VALUES('2011-07-12 10:25:22', 'xyz', 45.16);
INSERT INTO Ticks VALUES('2011-07-12 10:25:27', 'xyz', 49.33);
INSERT INTO Ticks VALUES('2011-07-12 10:31:12', 'xyz', 65.25);
INSERT INTO Ticks VALUES('2011-07-12 10:31:15', 'xyz');

COMMIT;

5.7.4 - 获取最新出价和询价结果

以下查询填充缺失的 NULL 值以创建一个完整的预订订单,按供应商 ID 显示最新的出价和询价以及大小。原始行(通常)具有一个价格和一个大小值,因此每输入一个 ID 条目,就会使用带有“忽略 null 值”的 last_value 来查找另一对的最新非 null 值。Sequenceno 提供唯一的全顺序。

架构:

=> CREATE TABLE bookorders(
    vendorid VARCHAR(100),
    date TIMESTAMP,
    sequenceno INT,
    askprice FLOAT,
    asksize INT,
    bidprice FLOAT,
    bidsize INT);
=> INSERT INTO bookorders VALUES('3325XPK','2011-07-12 10:23:54', 1, 10.12, 55, 10.23, 59);
=> INSERT INTO bookorders VALUES('3345XPZ','2011-07-12 10:23:55', 2, 10.55, 58, 10.75, 57);
=> INSERT INTO bookorders VALUES('445XPKF','2011-07-12 10:23:56', 3, 10.22, 43, 54);
=> INSERT INTO bookorders VALUES('445XPKF','2011-07-12 10:23:57', 3, 10.22, 59, 10.25, 61);
=> INSERT INTO bookorders VALUES('3425XPY','2011-07-12 10:23:58', 4, 11.87, 66, 11.90, 66);
=> INSERT INTO bookorders VALUES('3727XVK','2011-07-12 10:23:59', 5, 11.66, 51, 11.67, 62);
=> INSERT INTO bookorders VALUES('5325XYZ','2011-07-12 10:24:01', 6, 15.05, 44, 15.10, 59);
=> INSERT INTO bookorders VALUES('3675XVS','2011-07-12 10:24:05', 7, 15.43, 47, 58);
=> INSERT INTO bookorders VALUES('8972VUG','2011-07-12 10:25:15', 8, 14.95, 52, 15.11, 57);
COMMIT;

查询:

=> SELECT
    sequenceno Seq,
    date "Time",
    vendorid ID,
    LAST_VALUE (bidprice IGNORE NULLS)
     OVER (PARTITION BY vendorid ORDER BY sequenceno
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
    AS "Bid Price",
    LAST_VALUE (bidsize IGNORE NULLS)
     OVER (PARTITION BY vendorid ORDER BY sequenceno
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
    AS "Bid Size",
    LAST_VALUE (askprice IGNORE NULLS)
     OVER (PARTITION BY vendorid ORDER BY sequenceno
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
    AS "Ask Price",
    LAST_VALUE (asksize IGNORE NULLS)
     OVER (PARTITION BY vendorid order by sequenceno
           ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
    AS  "Ask Size"
 FROM bookorders
ORDER BY sequenceno;
 Seq |        Time         |   ID    | Bid Price | Bid Size | Ask Price | Ask Size
-----+---------------------+---------+-----------+----------+-----------+----------
   1 | 2011-07-12 10:23:54 | 3325XPK |     10.23 |       59 |     10.12 |       55
   2 | 2011-07-12 10:23:55 | 3345XPZ |     10.75 |       57 |     10.55 |       58
   3 | 2011-07-12 10:23:57 | 445XPKF |     10.25 |       61 |     10.22 |       59
   3 | 2011-07-12 10:23:56 | 445XPKF |        54 |          |     10.22 |       43
   4 | 2011-07-12 10:23:58 | 3425XPY |      11.9 |       66 |     11.87 |       66
   5 | 2011-07-12 10:23:59 | 3727XVK |     11.67 |       62 |     11.66 |       51
   6 | 2011-07-12 10:24:01 | 5325XYZ |      15.1 |       59 |     15.05 |       44
   7 | 2011-07-12 10:24:05 | 3675XVS |        58 |          |     15.43 |       47
   8 | 2011-07-12 10:25:15 | 8972VUG |     15.11 |       57 |     14.95 |       52
(9 rows)

5.8 - 事件窗口

通过基于事件的窗口,可以将时序数据拆分为与数据中的重大事件相邻的窗口。这在财务数据中尤其重要,因为分析通常可以侧重于触发了其他活动的特定事件。

Vertica 提供了两个不属于 SQL-99 标准的基于事件的窗口函数:

  • CONDITIONAL_CHANGE_EVENT 从 0 开始向每个行分配一个事件窗口编号,并在当前行中评估实参表达式的结果与上一行不同时,以 1 为增量递增事件窗口编号。此函数与 ROW_NUMBER 分析函数类似,该分析函数从 1 开始按顺序向分区中每个行分配一个唯一编号。

  • CONDITIONAL_TRUE_EVENT 从 0 开始向每个行分配一个事件窗口编号,并在当 boolean 实参表达式的结果评估为 true 时,以 1 为增量递增事件窗口编号。

下文中更详细地介绍了这两种函数。

示例架构

此页面中的示例使用以下架构:

CREATE TABLE TickStore3 (ts TIMESTAMP, symbol VARCHAR(8), bid FLOAT);
CREATE PROJECTION TickStore3_p (ts, symbol, bid) AS SELECT * FROM TickStore3 ORDER BY ts, symbol, bid UNSEGMENTED ALL NODES;
INSERT INTO TickStore3 VALUES ('2009-01-01 03:00:00', 'XYZ', 10.0);
INSERT INTO TickStore3 VALUES ('2009-01-01 03:00:03', 'XYZ', 11.0);
INSERT INTO TickStore3 VALUES ('2009-01-01 03:00:06', 'XYZ', 10.5);
INSERT INTO TickStore3 VALUES ('2009-01-01 03:00:09', 'XYZ', 11.0);
COMMIT;

使用 CONDITIONAL_CHANGE_EVENT

CONDITIONAL_CHANGE_EVENT 分析函数会返回一系列表示从 0 开始的事件窗口编号的整数。当在当前行中评估函数表达式的结果与上一行不同时,函数会递增事件窗口编号。

在以下示例中,第一个查询会返回 TickStore3 表中的所有记录。第二个查询在出价列中使用 CONDITIONAL_CHANGE_EVENT 函数。由于每个出价行值与上一个值不同,函数会将窗口 ID 从 0 递增到 3。

下图以图表形式说明了出价的变化情况。每个值都不同于其前面的值,因此递增了每个时间片的窗口 ID:

条件更改事件

窗口 ID 从 0 开始,并在值每次与前一个值不同时递增。

在此示例中,出价在第二行中从 $10 变为 $11,然后保持不变。 CONDITIONAL_CHANGE_EVENT 递增了第 2 行的事件窗口 ID,但之后一直没有递增:

下图以图表形式说明了出价仅在 3:00:03 发生了变化。出价在 3:00:06 和 3:00:09 时没有变化,因此在上述变化之后,每个时间片的窗口 ID 仍为 1。

![CCE 2](/images/cce2.png)

使用 CONDITIONAL_TRUE_EVENT

CONDITIONAL_CHANGE_EVENT 一样,CONDITIONAL_TRUE_EVENT 分析函数也会返回一系列表示从 0 开始的事件窗口编号的整数。这两种函数的区别如下:

  • CONDITIONAL_TRUE_EVENT 每次窗口 ID 的表达式计算结果为 true 时,均会增加此 ID。

  • CONDITIONAL_CHANGE_EVENT 使用上一个值在比较表达式中递增。

在以下示例中,第一个查询会返回 TickStore3 表中的所有记录。第二个查询使用CONDITIONAL_TRUE_EVENT 测试当前出价是否大于给定值 (10.6)。每次表达式测试为 true 时,函数都会递增窗口 ID。函数第一次递增窗口 ID 是在第 2 行,这时的值为 11。如果在下一行中,表达式测试为 false(值不大于 10.6),函数不会递增事件窗口 ID。在最后一行中,对于给定的条件,表达式为 true,因此函数将递增窗口:

下图以图形方式显示了 bid 值和窗口 ID 的变化。因为出价值仅在第二个和第四个时间片(3:00:03 和 3:00:09)中大于 $10.6,窗口 ID 返回了 <0,1,1,2>:

CTE

在以下示例中,第一个查询返回了 TickStore3 表中所有的记录,且这些记录按 tickstore 值 (ts) 进行排序。每次出价值大于 10.6 时,第二个查询使用 CONDITIONAL_TRUE_EVENT 递增窗口 ID。函数第一次递增事件窗口 ID 是在第 2 行,这时的值为 11。此后,窗口 ID 每次都会递增,因为对于每个时间片,表达式 (bid > 10.6) 测试都为 true:

下图以图形方式显示了 bid 值和窗口 ID 的变化。出价值在第二个时间片 (3:00:03) 中大于 10.6,在后面两个时间片中也是如此。函数每次都会递增事件窗口 ID,因为表达式测试为 true:

cte2

基于事件的窗口的高级用法

在基于事件的窗口函数中,条件表达式只能访问当前行中的值。要访问前一个值,您可以使用更强大的基于事件的窗口,使窗口事件条件可以包含前一个数据点。例如,LAG(x, n) 分析函数会从上一个输入记录中检索列 x 中排在第 n 位的值。在这种情况下,LAG 共享 CONDITIONAL_CHANGE_EVENTCONDITIONAL_TRUE_EVENT 函数表达式的 OVER 规范。

在以下示例中,第一个查询会返回 TickStore3 表中的所有记录。第二个查询在其 boolean 表达式中结合 LAG 函数使用了 CONDITIONAL_TRUE_EVENT。在这种情况下,如果当前行的出价值小于前一个值,CONDITIONAL_TRUE_EVENT 每次会递增事件窗口 ID。CONDITIONAL_TRUE_EVENT 第一次递增窗口 ID 是在第三个时间片,这时的表达式测试为 ture。当前值 (10.5) 小于前一个值。由于最后一个值大于前一行,所以在最后一行中没有递增窗口 ID:

下图显示了上述第二个查询。当出价小于前一个值时,函数会递增窗口 ID,这仅会在第三个时间片 (3:00:06) 中发生:

cte3

另请参阅

5.9 - 基于事件的窗口的会话化

会话化是基于事件的窗口的特殊用例,它通常用于分析点击流,例如根据记录的 Web 点击识别 Web 浏览会话。

在 Vertica 中,假设有一个输入点击流表,其中每行记录了特定用户(或 IP 地址)的一次网页点击,会话化计算将根据两次点击之间的时间间隔对每个用户执行的点击操作进行分组,来尝试从已记录的点击中识别 Web 浏览会话。如果同一个用户的两次点击间隔的时间很长,超出了超时阈值,则会将这两个点击视为来自不同的浏览会话。

示例架构 此主题中的示例使用以下 WebClicks 架构表示一个简单的点击流表:

CREATE TABLE WebClicks(userId INT, timestamp TIMESTAMP);
INSERT INTO WebClicks VALUES (1, '2009-12-08 3:00:00 pm');
INSERT INTO WebClicks VALUES (1, '2009-12-08 3:00:25 pm');
INSERT INTO WebClicks VALUES (1, '2009-12-08 3:00:45 pm');
INSERT INTO WebClicks VALUES (1, '2009-12-08 3:01:45 pm');
INSERT INTO WebClicks VALUES (2, '2009-12-08 3:02:45 pm');
INSERT INTO WebClicks VALUES (2, '2009-12-08 3:02:55 pm');
INSERT INTO WebClicks VALUES (2, '2009-12-08 3:03:55 pm');
COMMIT;

WebClicks 输入表包含以下行:

=> SELECT * FROM WebClicks;
 userId |      timestamp
--------+---------------------
      1 | 2009-12-08 15:00:00
      1 | 2009-12-08 15:00:25
      1 | 2009-12-08 15:00:45
      1 | 2009-12-08 15:01:45
      2 | 2009-12-08 15:02:45
      2 | 2009-12-08 15:02:55
      2 | 2009-12-08 15:03:55
(7 rows)

在下列查询中,会话化在 SELECT 列表列中执行计算,显示使用 LAG() 的当前时间戳值和以前时间戳值之间的差值。当差值大于 30 秒时,它评估为 true,并递增窗口 ID。

=> SELECT userId, timestamp,
     CONDITIONAL_TRUE_EVENT(timestamp - LAG(timestamp) > '30 seconds')
     OVER(PARTITION BY userId ORDER BY timestamp) AS session FROM WebClicks;
 userId |      timestamp      | session
--------+---------------------+---------
      1 | 2009-12-08 15:00:00 |       0
      1 | 2009-12-08 15:00:25 |       0
      1 | 2009-12-08 15:00:45 |       0
      1 | 2009-12-08 15:01:45 |       1
      2 | 2009-12-08 15:02:45 |       0
      2 | 2009-12-08 15:02:55 |       0
      2 | 2009-12-08 15:03:55 |       1
(7 rows)

在输出中,会话列包含来自 CONDITIONAL_TRUE_EVENT 函数的窗口 ID。窗口 ID 在第 4 行(时间戳 15:01:45)中评估为 true,且第 4 行后面的 ID 是零,因为它是新分区的起点(对于用户 ID 2),即第 4 行后面的行没有评估为 ture,直到输出的最后一行。

您可能要为用户设置不同的超时阈值。例如,一个用户的网络连接较慢或者它可能是多任务用户,而另一用户的网络连接较快,主要访问一个网站,执行一项任务。

要基于最后 2 次点击计算自适应的超时阈值,请结合 LAG 使用 CONDITIONAL_TRUE_EVENT 以返回最后 2 次点击之间的平均时间(宽限期为 3 秒):

=> SELECT userId, timestamp, CONDITIONAL_TRUE_EVENT(timestamp - LAG(timestamp) >
(LAG(timestamp, 1) - LAG(timestamp, 3)) / 2 + '3 seconds')
OVER(PARTITION BY userId ORDER BY timestamp) AS session
FROM WebClicks;
 userId |      timestamp      | session
--------+---------------------+---------
      2 | 2009-12-08 15:02:45 |       0
      2 | 2009-12-08 15:02:55 |       0
      2 | 2009-12-08 15:03:55 |       0
      1 | 2009-12-08 15:00:00 |       0
      1 | 2009-12-08 15:00:25 |       0
      1 | 2009-12-08 15:00:45 |       0
      1 | 2009-12-08 15:01:45 |       1
(7 rows)

另请参阅

6 - 用于预测分析的机器学习

Vertica 提供了许多用于执行数据库内分析的机器学习函数。这些函数执行数据准备、模型训练和预测任务。Vertica 支持下面的数据库内机器学习算法:

有关直接与 Vertica 数据库中的数据集成的类似 scikit 的机器学习库,请参阅 VerticaPy

有关特定机器学习函数的详细信息,请参阅机器学习函数

6.1 - 下载机器学习示例数据

您需要多个数据集来运行机器学习示例。可以从 Vertica GitHub 存储库下载这些数据集。

您可以通过以下两种方式之一下载示例数据:

  • 下载 ZIP 文件。将文件内容解压缩到目录中。

  • 克隆 Vertica 机器学习 GitHub 存储库。使用终端窗口,运行以下命令:

    $ git clone https://github.com/vertica/Machine-Learning-Examples
    

加载示例数据

您可以通过执行以下操作之一加载示例数据。请注意,模型不会自动删除。您必须重新运行 load_ml_data.sql 脚本来删除模型,或者手动删除模型。

  • 在 vsql 提示符或其他 Vertica 客户端中,使用 load_ml_data.sql 复制并粘贴 DDL 和 DML 操作。

  • 从 Machine-Learning-Examples 目录的 data 文件夹中的终端窗口运行以下命令:

    $ /opt/vertica/bin/vsql -d <name of your database> -f load_ml_data.sql
    

您还必须在 Machine-Learning-Examples 目录中加载 naive_bayes_data_prepration.sql 脚本:

$ /opt/vertica/bin/vsql -d <name of your database> -f ./naive_bayes/naive_bayes_data_preparation.sql

示例数据描述

存储库包含以下数据集。

6.2 - 数据准备

在分析数据之前,必须进行数据准备。您可以在 Vertica 中执行以下数据准备任务:

6.2.1 - 对不平衡的数据进行平衡

当数据中类的分布不均匀时,就会出现不平衡的数据。如果在不平衡的数据集上构建预测模型,则会导致模型看起来可产生高准确度,但不能很好地推广到少数类中的新数据。为了防止创建具有错误准确度的模型,您应该在创建预测模型之前对不平衡的数据进行重新平衡。

开始示例之前,请加载机器学习示例数据

您会在金融交易数据中看到很多不平衡的数据,其中大多数交易不是欺诈性的,少数交易是欺诈性的,如下例所示。

  1. 查看类的分布。

    => SELECT fraud, COUNT(fraud) FROM transaction_data GROUP BY fraud;
     fraud | COUNT
    -------+-------
     TRUE  |    19
     FALSE |   981
    (2 rows)
    
  2. 使用 BALANCE 函数创建更平衡的数据集。

    => SELECT BALANCE('balance_fin_data', 'transaction_data', 'fraud', 'under_sampling'
                      USING PARAMETERS sampling_ratio = 0.2);
             BALANCE
    --------------------------
     Finished in 1 iteration
    
    (1 row)
    
  3. 查看分类器的新分布。

    => SELECT fraud, COUNT(fraud) FROM balance_fin_data GROUP BY fraud;
     fraud | COUNT
    -------+-------
     t     |    19
     f     |   236
    (2 rows)
    

另请参阅

6.2.2 - 检测异常值

异常值是与数据集中的其他数据点有很大区别的数据点。您可以将异常值检测用于欺诈检测和系统运行状况监控等应用程序,或者您可以检测异常值并将其从数据中移除。如果在训练机器学习模型时在数据中留下异常值,则生成的模型存在偏差和偏斜预测的风险。Vertica 支持两种检测异常值的方法:DETECT_OUTLIERS 函数和 IFOREST 算法。

隔离森林

隔离森林 (iForest) 是一种无监督算法,它在异常值很少且不同的假设下运行。这种假设使异常值容易受到称为隔离的分离机制的影响。隔离不是将数据实例与每个数据特征的构造正态分布进行比较,而是关注异常值本身。

为了直接隔离异常值,iForest 构建了名为隔离树 (iTree) 的二叉树结构来对特征空间进行建模。这些 iTree 以随机和递归方式拆分特征空间,使得树的每个节点代表一个特征子空间。例如,第一次拆分将整个特征空间分成两个子空间,由根节点的两个子节点表示。当数据实例是特征子空间的唯一成员时,它被认为是孤立的。由于假设异常值很少且不同,因此异常值可能比正常数据实例更早被隔离。

为了提高算法的强健性,iForest 构建了一个 iTree 集合,每个 iTree 以不同的方式分隔特征空间。该算法计算跨所有 iTree 隔离数据实例所需的平均路径长度。此平均路径长度有助于确定数据集中每个数据实例的 anomaly_scoreanomaly_score 高于给定阈值的数据实例被视为异常值。

您不需要大型数据集来训练 iForest,即使是小样本也足以训练准确的模型。数据可以具有 CHAR、VARCHAR、BOOL、INT 或 FLOAT 类型的列。

训练好 iForest 模型后,您可以使用 APPLY_IFOREST 函数检测向数据集中添加的任何新数据中的异常值。

以下示例演示了如何训练 iForest 模型和检测棒球数据集上的异常值。

要构建和训练 iForest 模型,请调用 IFOREST

=> SELECT IFOREST('baseball_outliers','baseball','hr, hits, salary' USING PARAMETERS max_depth=30, nbins=100);
IFOREST
----------
Finished
(1 row)

您可以使用 GET_MODEL_SUMMARY 查看训练模型的摘要:

=> SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='baseball_outliers');
GET_MODEL_SUMMARY
---------------------------------------------------------------------------------------------------------------------------------------------

===========
call_string
===========
SELECT iforest('public.baseball_outliers', 'baseball', 'hr, hits, salary' USING PARAMETERS exclude_columns='', ntree=100, sampling_size=0.632,
col_sample_by_tree=1, max_depth=30, nbins=100);

=======
details
=======
predictor|      type
---------+----------------
   hr    |      int
  hits   |      int
 salary  |float or numeric

===============
Additional Info
===============
       Name       |Value
------------------+-----
tree_count        | 100
rejected_row_count|  0
accepted_row_count|1000

(1 row)

您可以使用 APPLY_IFOREST 将经过训练的 iForest 模型应用于棒球数据集。要仅查看标识为异常值的数据实例,可以运行以下查询:

=> SELECT * FROM (SELECT first_name, last_name, APPLY_IFOREST(hr, hits, salary USING PARAMETERS model_name='baseball_outliers', threshold=0.6)
   AS predictions FROM baseball) AS outliers WHERE predictions.is_anomaly IS true;
 first_name | last_name |                      predictions
------------+-----------+--------------------------------------------------------
 Jacqueline | Richards  | {"anomaly_score":0.8572338674053986,"is_anomaly":true}
 Debra      | Hall      | {"anomaly_score":0.6007846156043213,"is_anomaly":true}
 Gerald     | Fuller    | {"anomaly_score":0.6813650107767862,"is_anomaly":true}
(3 rows)

您可以设置 contamination 参数,而不是为 APPLY_IFOREST 指定 threshold 值。此参数设置一个阈值,以便标记为异常值的训练数据点的比率大约等于 contamination 的值:

=> SELECT * FROM (SELECT first_name, last_name, APPLY_IFOREST(team, hr, hits, avg, salary USING PARAMETERS model_name='baseball_anomalies',
   contamination = 0.1) AS predictions FROM baseball) AS outliers WHERE predictions.is_anomaly IS true;
 first_name | last_name |                      predictions
------------+-----------+--------------------------------------------------------
 Marie      | Fields    | {"anomaly_score":0.5307715717521868,"is_anomaly":true}
 Jacqueline | Richards  | {"anomaly_score":0.777757463074347,"is_anomaly":true}
 Debra      | Hall      | {"anomaly_score":0.5714649698133808,"is_anomaly":true}
 Gerald     | Fuller    | {"anomaly_score":0.5980549926114661,"is_anomaly":true}
(4 rows)

DETECT_OUTLIERS

DETECT_OUTLIERS 函数假定每个数据维度的正态分布,然后标识与任何维度的正态分布有很大差异的数据实例。该函数使用稳健的 Z-score 检测方法对每个输入列进行标准化。如果数据实例包含大于指定阈值的标准化值,则将其标识为异常值。该函数输出一个包含所有异常值的表。

该函数接受仅具有数字输入列的数据,独立处理每一列,并假定每一列上的高斯分布。如果要检测添加到数据集的新数据中的异常值,则必须重新运行 DETECT_OUTLIERS

以下示例演示了如何根据 hr、hits 和 salary 列检测棒球数据集中的异常值。DETECT_OUTLIERS 函数创建一个包含异常值的表,其中包含输入列和键列:

=> SELECT DETECT_OUTLIERS('baseball_hr_hits_salary_outliers', 'baseball', 'hr, hits, salary', 'robust_zscore'
                         USING PARAMETERS outlier_threshold=3.0);
     DETECT_OUTLIERS
--------------------------
 Detected 5 outliers

(1 row)

要查看异常值,请查询包含异常值的输出表:

=> SELECT * FROM baseball_hr_hits_salary_outliers;
id  | first_name | last_name |    dob     |   team    |   hr    |  hits   |  avg  |        salary
----+------------+-----------+------------+-----------+---------+---------+-------+----------------------
73  | Marie      | Fields    | 1985-11-23 | Mauv      |    8888 |      34 | 0.283 | 9.99999999341471e+16
89  | Jacqueline | Richards  | 1975-10-06 | Pink      |  273333 | 4490260 | 0.324 |  4.4444444444828e+17
87  | Jose       | Stephens  | 1991-07-20 | Green     |      80 |   64253 |  0.69 |          16032567.12
222 | Gerald     | Fuller    | 1991-02-13 | Goldenrod | 3200000 |     216 | 0.299 |          37008899.76
147 | Debra      | Hall      | 1980-12-31 | Maroon    | 1100037 |     230 | 0.431 |           9000101403
(5 rows)

您可以创建一个省略表中异常值的视图:

=> CREATE VIEW clean_baseball AS
   SELECT * FROM baseball WHERE id NOT IN (SELECT id FROM baseball_hr_hits_salary_outliers);
CREATE VIEW

另请参阅

6.2.3 - 分类列编码

许多机器学习算法无法处理分类数据。为了适应这种算法,必须在训练之前将分类数据转换为数值数据。将分类值直接映射到索引是不够的。例如,如果您的分类特征具有“红色”、“绿色”和“蓝色”三个不同的值,将它们替换为 1、2 和 3 可能会对训练过程产生负面影响,因为算法通常依赖于值之间的某种数字距离进行区分。在这种情况下,从 1 到 3 的欧几里得距离是从 1 到 2 的距离的两倍,这意味着训练过程会认为“红色”与“蓝色”不同,而与“绿色”更相似。或者,独热编码将每个分类值映射到一个二进制向量以避免这个问题。例如,“红色”可以映射到 [1,0,0],“绿色”可以映射到 [0,1,0],“蓝色”可以映射到 [0,0,1]。现在,这三个类别之间的成对距离都是相同的。独热编码允许您将分类变量转换为二进制值,以便您可以使用不同的机器学习算法来计算您的数据。

以下示例演示如何将独热编码应用于 Titanic 数据集。如果您想了解有关此数据集的详细信息,请参阅 Kaggle 站点

假设您想使用逻辑回归分类器来预测哪些乘客在泰坦尼克号沉没中幸存下来。如果没有一种热编码,则无法将分类特征用于逻辑回归。该数据集有两个可以使用的分类特征。"sex"(性别)特征可以是男性也可以是女性。"embarkation_point"(上船地点)可以是以下几项之一:

  • S 代表南安普顿

  • Q 代表皇后镇

  • C 代表瑟堡

开始示例之前,请加载机器学习示例数据
  1. 对训练数据运行 ONE_HOT_ENCODER_FIT 函数:
=> SELECT ONE_HOT_ENCODER_FIT('titanic_encoder', 'titanic_training', 'sex, embarkation_point');
ONE_HOT_ENCODER_FIT
---------------------
Success

(1 row)

  1. 查看 titanic_encoder 模型摘要:
=> SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='titanic_encoder');
GET_MODEL_SUMMARY
------------------------------------------------------------------------------------------
===========
call_string
===========
SELECT one_hot_encoder_fit('public.titanic_encoder','titanic_training','sex, embarkation_point'
USING PARAMETERS exclude_columns='', output_view='', extra_levels='{}');
==================
varchar_categories
==================
category_name    |category_level|category_level_index
-----------------+--------------+--------------------
embarkation_point|      C       |         0
embarkation_point|      Q       |         1
embarkation_point|      S       |         2
embarkation_point|              |         3
sex              |    female    |         0
sex              |     male     |         1
(1 row)
  1. 运行 GET_MODEL_ATTRIBUTE 函数。此函数返回其原生数据类型中的分类级别,因此可以轻松地将它们与原始表进行比较:
=> SELECT * FROM (SELECT GET_MODEL_ATTRIBUTE(USING PARAMETERS model_name='titanic_encoder',
attr_name='varchar_categories')) AS attrs INNER JOIN (SELECT passenger_id, name, sex, age,
embarkation_point FROM titanic_training) AS original_data ON attrs.category_level
ILIKE original_data.embarkation_point ORDER BY original_data.passenger_id LIMIT 10;
category_name     | category_level | category_level_index | passenger_id |name
|  sex   | age | embarkation_point
------------------+----------------+----------------------+--------------+-----------------------------
-----------------------+--------+-----+-------------------
embarkation_point | S              |                    2 |            1 | Braund, Mr. Owen Harris
| male   |  22 | S
embarkation_point | C              |                    0 |            2 | Cumings, Mrs. John Bradley
(Florence Briggs Thayer | female |  38 | C
embarkation_point | S              |                    2 |            3 | Heikkinen, Miss. Laina
| female |  26 | S
embarkation_point | S              |                    2 |            4 | Futrelle, Mrs. Jacques Heath
(Lily May Peel)       | female |  35 | S
embarkation_point | S              |                    2 |            5 | Allen, Mr. William Henry
| male   |  35 | S
embarkation_point | Q              |                    1 |            6 | Moran, Mr. James
| male   |     | Q
embarkation_point | S              |                    2 |            7 | McCarthy, Mr. Timothy J
| male   |  54 | S
embarkation_point | S              |                    2 |            8 | Palsson, Master. Gosta Leonard
| male   |   2 | S
embarkation_point | S              |                    2 |            9 | Johnson, Mrs. Oscar W
(Elisabeth Vilhelmina Berg)  | female |  27 | S
embarkation_point | C              |                    0 |           10 | Nasser, Mrs. Nicholas
(Adele Achem)                | female |  14 | C
(10 rows)
  1. 对训练和测试数据运行 APPLY_ONE_HOT_ENCODER 函数:
=> CREATE VIEW titanic_training_encoded AS SELECT passenger_id, survived, pclass, sex_1, age,
sibling_and_spouse_count, parent_and_child_count, fare, embarkation_point_1, embarkation_point_2
FROM (SELECT APPLY_ONE_HOT_ENCODER(* USING PARAMETERS model_name='titanic_encoder')
FROM titanic_training) AS sq;

CREATE VIEW

=> CREATE VIEW titanic_testing_encoded AS SELECT passenger_id, name, pclass, sex_1, age,
sibling_and_spouse_count, parent_and_child_count, fare, embarkation_point_1, embarkation_point_2
FROM (SELECT APPLY_ONE_HOT_ENCODER(* USING PARAMETERS model_name='titanic_encoder')
FROM titanic_testing) AS sq;
CREATE VIEW
  1. 然后,对训练数据训练一个逻辑回归分类器,并对测试数据执行该模型:
=> SELECT LOGISTIC_REG('titanic_log_reg', 'titanic_training_encoded', 'survived', '*'
USING PARAMETERS exclude_columns='passenger_id, survived');
LOGISTIC_REG
---------------------------
Finished in 5 iterations
(1 row)

=> SELECT passenger_id, name, PREDICT_LOGISTIC_REG(pclass, sex_1, age, sibling_and_spouse_count,
parent_and_child_count, fare, embarkation_point_1, embarkation_point_2 USING PARAMETERS
model_name='titanic_log_reg') FROM titanic_testing_encoded ORDER BY passenger_id LIMIT 10;
passenger_id |                     name                     | PREDICT_LOGISTIC_REG
-------------+----------------------------------------------+----------------------
893          | Wilkes, Mrs. James (Ellen Needs)             |                    0
894          | Myles, Mr. Thomas Francis                    |                    0
895          | Wirz, Mr. Albert                             |                    0
896          | Hirvonen, Mrs. Alexander (Helga E Lindqvist) |                    1
897          | Svensson, Mr. Johan Cervin                   |                    0
898          | Connolly, Miss. Kate                         |                    1
899          | Caldwell, Mr. Albert Francis                 |                    0
900          | Abrahim, Mrs. Joseph (Sophie Halaut Easu)    |                    1
901          | Davies, Mr. John Samuel                      |                    0
902          | Ilieff, Mr. Ylio                             |
(10 rows)

6.2.4 - 估算缺失值

您可以使用 IMPUTE 函数将缺失数据替换为最频繁的值或同一列中的平均值。此估算示例使用 small_input_impute 表。使用该函数,您可以指定均值法或众数法。

这些示例展示了如何对 small_input_impute 表使用 IMPUTE 函数。

开始示例之前,请加载机器学习示例数据

首先,查询表,以便查看缺失值:

=> SELECT * FROM small_input_impute;
pid | pclass | gender |    x1     |    x2     |    x3     | x4 | x5 | x6
----+--------+--------+-----------+-----------+-----------+----+----+----
5   |      0 |      1 | -2.590837 | -2.892819 |  -2.70296 |  2 | t  | C
7   |      1 |      1 |  3.829239 |   3.08765 |  Infinity |    | f  | C
13  |      0 |      0 | -9.060605 | -9.390844 | -9.559848 |  6 | t  | C
15  |      0 |      1 | -2.590837 | -2.892819 |  -2.70296 |  2 | f  | A
16  |      0 |      1 | -2.264599 | -2.615146 |  -2.10729 | 11 | f  | A
19  |      1 |      1 |           |  3.841606 |  3.754375 | 20 | t  |
1   |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | A
1   |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | A
2   |      0 |      0 | -9.618292 | -9.308881 | -9.562255 |  4 | t  | A
3   |      0 |      0 | -9.060605 | -9.390844 | -9.559848 |  6 | t  | B
4   |      0 |      0 | -2.264599 | -2.615146 |  -2.10729 | 15 | t  | B
6   |      0 |      1 | -2.264599 | -2.615146 |  -2.10729 | 11 | t  | C
8   |      1 |      1 |  3.273592 |           |  3.477332 | 18 | f  | B
10  |      1 |      1 |           |  3.841606 |  3.754375 | 20 | t  | A
18  |      1 |      1 |  3.273592 |           |  3.477332 | 18 | t  | B
20  |      1 |      1 |           |  3.841606 |  3.754375 | 20 |    | C
9   |      1 |      1 |           |  3.841606 |  3.754375 | 20 | f  | B
11  |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | B
12  |      0 |      0 | -9.618292 | -9.308881 | -9.562255 |  4 | t  | C
14  |      0 |      0 | -2.264599 | -2.615146 |  -2.10729 | 15 | f  | A
17  |      1 |      1 |  3.829239 |   3.08765 |  Infinity |    | f  | B
(21 rows)

指定均值法

执行 IMPUTE 函数,指定均值法:

=> SELECT IMPUTE('output_view','small_input_impute', 'pid, x1,x2,x3,x4','mean'
                  USING PARAMETERS exclude_columns='pid');
IMPUTE
--------------------------
Finished in 1 iteration
(1 row)

查看 output_view 以查看估算值:

=> SELECT * FROM output_view;
pid | pclass | gender |        x1         |        x2         |        x3         | x4 | x5 | x6
----+--------+--------+-------------------+-------------------+-------------------+----+----+----
5   |      0 |      1 |         -2.590837 |         -2.892819 |          -2.70296 |  2 | t  | C
7   |      1 |      1 |          3.829239 |           3.08765 | -3.12989705263158 | 11 | f  | C
13  |      0 |      0 |         -9.060605 |         -9.390844 |         -9.559848 |  6 | t  | C
15  |      0 |      1 |         -2.590837 |         -2.892819 |          -2.70296 |  2 | f  | A
16  |      0 |      1 |         -2.264599 |         -2.615146 |          -2.10729 | 11 | f  | A
19  |      1 |      1 | -3.86645035294118 |          3.841606 |          3.754375 | 20 | t  |
9   |      1 |      1 | -3.86645035294118 |          3.841606 |          3.754375 | 20 | f  | B
11  |      0 |      0 |         -9.445818 |         -9.740541 |         -9.786974 |  3 | t  | B
12  |      0 |      0 |         -9.618292 |         -9.308881 |         -9.562255 |  4 | t  | C
14  |      0 |      0 |         -2.264599 |         -2.615146 |          -2.10729 | 15 | f  | A
17  |      1 |      1 |          3.829239 |           3.08765 | -3.12989705263158 | 11 | f  | B
1   |      0 |      0 |         -9.445818 |         -9.740541 |         -9.786974 |  3 | t  | A
1   |      0 |      0 |         -9.445818 |         -9.740541 |         -9.786974 |  3 | t  | A
2   |      0 |      0 |         -9.618292 |         -9.308881 |         -9.562255 |  4 | t  | A
3   |      0 |      0 |         -9.060605 |         -9.390844 |         -9.559848 |  6 | t  | B
4   |      0 |      0 |         -2.264599 |         -2.615146 |          -2.10729 | 15 | t  | B
6   |      0 |      1 |         -2.264599 |         -2.615146 |          -2.10729 | 11 | t  | C
8   |      1 |      1 |          3.273592 | -3.22766163157895 |          3.477332 | 18 | f  | B
10  |      1 |      1 | -3.86645035294118 |          3.841606 |          3.754375 | 20 | t  | A
18  |      1 |      1 |          3.273592 | -3.22766163157895 |          3.477332 | 18 | t  | B
20  |      1 |      1 | -3.86645035294118 |          3.841606 |          3.754375 | 20 |    | C
(21 rows)

您还可以执行 IMPUTE 函数,指定均值法并使用 partition_columns 参数。此参数的工作方式类似于 GROUP_BY 子句:

=> SELECT IMPUTE('output_view_group','small_input_impute', 'pid, x1,x2,x3,x4','mean'
USING PARAMETERS exclude_columns='pid', partition_columns='pclass,gender');
impute
--------------------------
Finished in 1 iteration
(1 row)

查看 output_view_group 以查看估算值:

=> SELECT * FROM output_view_group;
pid | pclass | gender |    x1     |        x2        |        x3        | x4 | x5 | x6
----+--------+--------+-----------+------------------+------------------+----+----+----
5   |      0 |      1 | -2.590837 |        -2.892819 |         -2.70296 |  2 | t  | C
7   |      1 |      1 |  3.829239 |          3.08765 | 3.66202733333333 | 19 | f  | C
13  |      0 |      0 | -9.060605 |        -9.390844 |        -9.559848 |  6 | t  | C
15  |      0 |      1 | -2.590837 |        -2.892819 |         -2.70296 |  2 | f  | A
16  |      0 |      1 | -2.264599 |        -2.615146 |         -2.10729 | 11 | f  | A
19  |      1 |      1 | 3.5514155 |         3.841606 |         3.754375 | 20 | t  |
1   |      0 |      0 | -9.445818 |        -9.740541 |        -9.786974 |  3 | t  | A
1   |      0 |      0 | -9.445818 |        -9.740541 |        -9.786974 |  3 | t  | A
2   |      0 |      0 | -9.618292 |        -9.308881 |        -9.562255 |  4 | t  | A
3   |      0 |      0 | -9.060605 |        -9.390844 |        -9.559848 |  6 | t  | B
4   |      0 |      0 | -2.264599 |        -2.615146 |         -2.10729 | 15 | t  | B
6   |      0 |      1 | -2.264599 |        -2.615146 |         -2.10729 | 11 | t  | C
8   |      1 |      1 |  3.273592 | 3.59028733333333 |         3.477332 | 18 | f  | B
10  |      1 |      1 | 3.5514155 |         3.841606 |         3.754375 | 20 | t  | A
18  |      1 |      1 |  3.273592 | 3.59028733333333 |         3.477332 | 18 | t  | B
20  |      1 |      1 | 3.5514155 |         3.841606 |         3.754375 | 20 |    | C
9   |      1 |      1 | 3.5514155 |         3.841606 |         3.754375 | 20 | f  | B
11  |      0 |      0 | -9.445818 |        -9.740541 |        -9.786974 |  3 | t  | B
12  |      0 |      0 | -9.618292 |        -9.308881 |        -9.562255 |  4 | t  | C
14  |      0 |      0 | -2.264599 |        -2.615146 |         -2.10729 | 15 | f  | A
17  |      1 |      1 |  3.829239 |          3.08765 | 3.66202733333333 | 19 | f  | B
(21 rows)

指定众数法

执行 IMPUTE 函数,指定众数法:

=> SELECT impute('output_view_mode','small_input_impute', 'pid, x5,x6','mode'
                  USING PARAMETERS exclude_columns='pid');
impute
--------------------------
Finished in 1 iteration
(1 row)

查看 output_view_mode 以查看估算值:

=> SELECT * FROM output_view_mode;
pid | pclass | gender |    x1     |    x2     |    x3     | x4 | x5 | x6
----+--------+--------+-----------+-----------+-----------+----+----+----
5   |      0 |      1 | -2.590837 | -2.892819 |  -2.70296 |  2 | t  | C
7   |      1 |      1 |  3.829239 |   3.08765 |  Infinity |    | f  | C
13  |      0 |      0 | -9.060605 | -9.390844 | -9.559848 |  6 | t  | C
15  |      0 |      1 | -2.590837 | -2.892819 |  -2.70296 |  2 | f  | A
16  |      0 |      1 | -2.264599 | -2.615146 |  -2.10729 | 11 | f  | A
19  |      1 |      1 |           |  3.841606 |  3.754375 | 20 | t  | B
1   |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | A
1   |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | A
2   |      0 |      0 | -9.618292 | -9.308881 | -9.562255 |  4 | t  | A
3   |      0 |      0 | -9.060605 | -9.390844 | -9.559848 |  6 | t  | B
4   |      0 |      0 | -2.264599 | -2.615146 |  -2.10729 | 15 | t  | B
6   |      0 |      1 | -2.264599 | -2.615146 |  -2.10729 | 11 | t  | C
8   |      1 |      1 |  3.273592 |           |  3.477332 | 18 | f  | B
10  |      1 |      1 |           |  3.841606 |  3.754375 | 20 | t  | A
18  |      1 |      1 |  3.273592 |           |  3.477332 | 18 | t  | B
20  |      1 |      1 |           |  3.841606 |  3.754375 | 20 | t  | C
9   |      1 |      1 |           |  3.841606 |  3.754375 | 20 | f  | B
11  |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | B
12  |      0 |      0 | -9.618292 | -9.308881 | -9.562255 |  4 | t  | C
14  |      0 |      0 | -2.264599 | -2.615146 |  -2.10729 | 15 | f  | A
17  |      1 |      1 |  3.829239 |   3.08765 |  Infinity |    | f  | B
(21 rows)

您还可以执行 IMPUTE 函数,指定众数法并使用 partition_columns 参数。此参数的工作方式类似于 GROUP_BY 子句:

=> SELECT impute('output_view_mode_group','small_input_impute', 'pid, x5,x6','mode'
                   USING PARAMETERS exclude_columns='pid',partition_columns='pclass,gender');
impute
--------------------------
Finished in 1 iteration
(1 row)

查看 output_view_mode_group 以查看估算值:

=> SELECT * FROM output_view_mode_group;
pid | pclass | gender |    x1     |    x2     |    x3     | x4 | x5 | x6
----+--------+--------+-----------+-----------+-----------+----+----+----
1   |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | A
1   |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | A
2   |      0 |      0 | -9.618292 | -9.308881 | -9.562255 |  4 | t  | A
3   |      0 |      0 | -9.060605 | -9.390844 | -9.559848 |  6 | t  | B
4   |      0 |      0 | -2.264599 | -2.615146 |  -2.10729 | 15 | t  | B
13  |      0 |      0 | -9.060605 | -9.390844 | -9.559848 |  6 | t  | C
11  |      0 |      0 | -9.445818 | -9.740541 | -9.786974 |  3 | t  | B
12  |      0 |      0 | -9.618292 | -9.308881 | -9.562255 |  4 | t  | C
14  |      0 |      0 | -2.264599 | -2.615146 |  -2.10729 | 15 | f  | A
5   |      0 |      1 | -2.590837 | -2.892819 |  -2.70296 |  2 | t  | C
15  |      0 |      1 | -2.590837 | -2.892819 |  -2.70296 |  2 | f  | A
16  |      0 |      1 | -2.264599 | -2.615146 |  -2.10729 | 11 | f  | A
6   |      0 |      1 | -2.264599 | -2.615146 |  -2.10729 | 11 | t  | C
7   |      1 |      1 |  3.829239 |   3.08765 |  Infinity |    | f  | C
19  |      1 |      1 |           |  3.841606 |  3.754375 | 20 | t  | B
9   |      1 |      1 |           |  3.841606 |  3.754375 | 20 | f  | B
17  |      1 |      1 |  3.829239 |   3.08765 |  Infinity |    | f  | B
8   |      1 |      1 |  3.273592 |           |  3.477332 | 18 | f  | B
10  |      1 |      1 |           |  3.841606 |  3.754375 | 20 | t  | A
18  |      1 |      1 |  3.273592 |           |  3.477332 | 18 | t  | B
20  |      1 |      1 |           |  3.841606 |  3.754375 | 20 | f  | C
(21 rows)

另请参阅

IMPUTE

6.2.5 - 标准化数据

标准化的目的主要是把来自不同列的数字数据缩小到等效的比例。例如,假设您对具有两个特征列的数据集执行 LINEAR_REG 函数,这两个特征列为 current_salaryyears_worked。您试图预测的输出值是一名工人的未来工资。current_salary 列的值可能具有很大的范围,且数值可能比 years_worked 列的值大很多。因此,current_salary 列的值可能会超过 years_worked 列的值,使模型偏斜。

Vertica 提供以下使用标准化的数据准备方法。这些方法是:

  • MinMax
    使用 MinMax 标准化方法,您可以将这两列的数值全部标准化,使之分布在 0 至 1 的范围之内。使用这种方法,您可以通过减少一列对于另一列的支配控制,比较不同标度的值。

  • Z-score
    使用 Z-score 标准化方法,您可以将这两列中的值标准化为观测值与每列平均值的标准差数。这使您可以将数据与正态分布的随机变量进行比较。

  • 稳健 Z-score
    使用稳健 Z-score 标准化方法,您可以减少异常值对 Z-score 计算的影响。稳健 Z-score 标准化使用中位数,而不是 Z-score 中使用的平均值。通过使用中位数而不是平均值,它有助于消除数据中异常值的一些影响。

标准化数据会导致创建一个保存标准化数据的视图。NORMALIZE 函数中的 output_view 选项确定视图的名称。

使用 MinMax 标准化工资数据

以下示例演示如何使用 MinMax 标准化方法标准化 salary_data 表。

开始示例之前,请加载机器学习示例数据
=> SELECT NORMALIZE('normalized_salary_data', 'salary_data', 'current_salary, years_worked', 'minmax');
        NORMALIZE
--------------------------
 Finished in 1 iteration

(1 row)

=> SELECT * FROM normalized_salary_data;
employee_id | first_name  | last_name  |     years_worked     |    current_salary
------------+-------------+------------+----------------------+----------------------
189         | Shawn       | Moore      | 0.350000000000000000 | 0.437246565765357217
518         | Earl        | Shaw       | 0.100000000000000000 | 0.978867411144492943
1126        | Susan       | Alexander  | 0.250000000000000000 | 0.909048995710749580
1157        | Jack        | Stone      | 0.100000000000000000 | 0.601863084103319918
1277        | Scott       | Wagner     | 0.050000000000000000 | 0.455949209228501786
3188        | Shirley     | Flores     | 0.400000000000000000 | 0.538816771536005140
3196        | Andrew      | Holmes     | 0.900000000000000000 | 0.183954046444834949
3430        | Philip      | Little     | 0.100000000000000000 | 0.735279557092379495
3522        | Jerry       | Ross       | 0.800000000000000000 | 0.671828883472214349
3892        | Barbara     | Flores     | 0.350000000000000000 | 0.092901007123556866

.
.
.
(1000 rows)

使用 Z-score 标准化工资数据

以下示例演示如何使用 Z-score 标准化方法标准化 salary_data 表。

开始示例之前,请加载机器学习示例数据
=> SELECT NORMALIZE('normalized_z_salary_data', 'salary_data', 'current_salary, years_worked',
                     'zscore');
        NORMALIZE
--------------------------
 Finished in 1 iteration

(1 row)

=> SELECT * FROM normalized_z_salary_data;
employee_id | first_name  | last_name  |    years_worked     |    current_salary
------------+-------------+------------+---------------------+----------------------
189         | Shawn       | Moore      |  -0.524447274157005 |   -0.221041249770669
518         | Earl        | Shaw       |   -1.35743214416495 |     1.66054215981221
1126        | Susan       | Alexander  |  -0.857641222160185 |     1.41799393943946
1157        | Jack        | Stone      |   -1.35743214416495 |    0.350834283622416
1277        | Scott       | Wagner     |   -1.52402911816654 |   -0.156068522159045
3188        | Shirley     | Flores     |  -0.357850300155415 |    0.131812255991634
3196        | Andrew      | Holmes     |    1.30811943986048 |    -1.10097599783475
3430        | Philip      | Little     |   -1.35743214416495 |    0.814321286168547
3522        | Jerry       | Ross       |   0.974925491857304 |    0.593894513770248
3892        | Barbara     | Flores     |  -0.524447274157005 |    -1.41729301118583

.
.
.
(1000 rows)

使用稳健 Z-score 标准化工资数据

以下示例演示如何使用稳健 Z-score 标准化方法标准化 salary_data 表。

开始示例之前,请加载机器学习示例数据
=> SELECT NORMALIZE('normalized_robustz_salary_data', 'salary_data', 'current_salary, years_worked', 'robust_zscore');
        NORMALIZE
--------------------------
 Finished in 1 iteration

(1 row)

=> SELECT * FROM normalized_robustz_salary_data;
employee_id | first_name  | last_name  |    years_worked    |    current_salary
------------+-------------+------------+--------------------+-----------------------
189         | Shawn       | Moore      | -0.404694455685957 | -0.158933849655499140
518         | Earl        | Shaw       | -1.079185215162552 |  1.317126172796275889
1126        | Susan       | Alexander  | -0.674490759476595 |  1.126852528914384584
1157        | Jack        | Stone      | -1.079185215162552 |  0.289689691751547422
1277        | Scott       | Wagner     | -1.214083367057871 | -0.107964200747705902
3188        | Shirley     | Flores     | -0.269796303790638 |  0.117871818902746738
3196        | Andrew      | Holmes     |  1.079185215162552 | -0.849222942006447161
3430        | Philip      | Little     | -1.079185215162552 |  0.653284859470426481
3522        | Jerry       | Ross       |  0.809388911371914 |  0.480364995828913355
3892        | Barbara     | Flores     | -0.404694455685957 | -1.097366550974798397
3939        | Anna        | Walker     | -0.944287063267233 |  0.414956177842775781
4165        | Martha      | Reyes      |  0.269796303790638 |  0.773947701782753329
4335        | Phillip     | Wright     | -1.214083367057871 |  1.218843012657445647
4534        | Roger       | Harris     |  1.079185215162552 |  1.155185021164402608
4806        | John        | Robinson   |  0.809388911371914 | -0.494320112876813908
4881        | Kelly       | Welch      |  0.134898151895319 | -0.540778808820045933
4889        | Jennifer    | Arnold     |  1.214083367057871 | -0.299762093576526566
5067        | Martha      | Parker     |  0.000000000000000 |  0.719991348857328239
5523        | John        | Martin     | -0.269796303790638 | -0.411248545269163826
6004        | Nicole      | Sullivan   |  0.269796303790638 |  1.065141044522487821
6013        | Harry       | Woods      | -0.944287063267233 |  1.005664438654129376
6240        | Norma       | Martinez   |  1.214083367057871 |  0.762412844887071691

.
.
.
(1000 rows)

另请参阅

6.2.6 - PCA(主分量分析)

主分量分析 (PCA) 是一种技术,可以在保留数据中所存在变异的同时降低数据维数。本质上,相当于构建了一个新的坐标系,使数据变化沿第一个轴最强,沿第二个轴减弱,以此类推。然后,数据点转换到这个新的坐标系中。轴的方向称为主分量。

如果输入数据是具有 p 列的表,则可能最多存在 p 个主分量。但是,通常情况下,沿第 k 个主分量方向的数据变化几乎可以忽略不计,这使我们只保留前 k 个分量。因此,新坐标系的轴较少。因此,转换后的数据表只有 k 列而不是 p 列。一定要记住,k 个输出列不仅仅是 p 个输入列的子集。相反,k 个输出列中的每个列都是所有 p 个输入列的组合。

可以使用以下函数来训练和应用 PCA 模型:

有关完整示例,请参阅使用 PCA 降维

6.2.6.1 - 使用 PCA 降维

该 PCA 示例使用一个包含大量列的名为 world 的数据集。该示例展示了如何将 PCA 应用于数据集中的所有列(HDI 除外)并将它们降为二维。

开始示例之前,请加载机器学习示例数据
  1. 创建名为 pcamodel 的 PCA 模型。

    => SELECT PCA ('pcamodel', 'world','country,HDI,em1970,em1971,em1972,em1973,em1974,em1975,em1976,em1977,
    em1978,em1979,em1980,em1981,em1982,em1983,em1984 ,em1985,em1986,em1987,em1988,em1989,em1990,em1991,em1992,
    em1993,em1994,em1995,em1996,em1997,em1998,em1999,em2000,em2001,em2002,em2003,em2004,em2005,em2006,em2007,
    em2008,em2009,em2010,gdp1970,gdp1971,gdp1972,gdp1973,gdp1974,gdp1975,gdp1976,gdp1977,gdp1978,gdp1979,gdp1980,
    gdp1981,gdp1982,gdp1983,gdp1984,gdp1985,gdp1986,gdp1987,gdp1988,gdp1989,gdp1990,gdp1991,gdp1992,gdp1993,
    gdp1994,gdp1995,gdp1996,gdp1997,gdp1998,gdp1999,gdp2000,gdp2001,gdp2002,gdp2003,gdp2004,gdp2005,gdp2006,
    gdp2007,gdp2008,gdp2009,gdp2010' USING PARAMETERS exclude_columns='HDI, country');
    
    
    PCA
    ---------------------------------------------------------------
    Finished in 1 iterations.
    Accepted Rows: 96  Rejected Rows: 0
    (1 row)
    
  2. 查看 pcamodel 的摘要输出。

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='pcamodel');
    GET_MODEL_SUMMARY
    --------------------------------------------------------------------------------
    
  3. 接下来,将 PCA 应用于选定的几个列(HDI 和 country 列除外)。

    => SELECT APPLY_PCA (HDI,country,em1970,em2010,gdp1970,gdp2010 USING PARAMETERS model_name='pcamodel',
    exclude_columns='HDI,country', key_columns='HDI,country',cutoff=.3) OVER () FROM world;
    HDI   |       country       |       col1
    ------+---------------------+-------------------
    0.886 | Belgium             | -36288.1191849017
    0.699 | Belize              |   -81740.32711562
    0.427 | Benin               | -122666.882708325
    0.805 | Chile               | -161356.484748602
    0.687 | China               | -202634.254216416
    0.744 | Costa Rica          | -242043.080125449
    0.4   | Cote d'Ivoire       | -283330.394428932
    0.776 | Cuba                | -322625.857541772
    0.895 | Denmark             | -356086.311721071
    0.644 | Egypt               | -403634.743992772
    .
    .
    .
    (96 rows)
    
  4. 然后,可以选择应用反函数以将数据转换回其原始状态。此示例显示了仅针对第一条记录的缩小版输出。总共有 96 条记录。

    => SELECT APPLY_INVERSE_PCA (HDI,country,em1970,em2010,gdp1970,gdp2010 USING PARAMETERS model_name='pcamodel',
    exclude_columns='HDI,country', key_columns='HDI,country') OVER () FROM world limit 1;
    -[ RECORD 1 ]--------------
    HDI     | 0.886
    country | Belgium
    em1970  | 3.74891915022521
    em1971  | 26.091852917619
    em1972  | 22.0262860721982
    em1973  | 24.8214492074202
    em1974  | 20.9486650320945
    em1975  | 29.5717692117088
    em1976  | 17.4373459783249
    em1977  | 33.1895610966146
    em1978  | 15.6251407781098
    em1979  | 14.9560299812815
    em1980  | 18.0870223053504
    em1981  | -6.23151505146251
    em1982  | -7.12300504708672
    em1983  | -7.52627957856581
    em1984  | -7.17428622245234
    em1985  | -9.04899186621455
    em1986  | -10.5098581697156
    em1987  | -7.97146984849547
    em1988  | -8.85458031319287
    em1989  | -8.78422101747477
    em1990  | -9.61931854722004
    em1991  | -11.6411235452067
    em1992  | -12.8882752879355
    em1993  | -15.0647523842803
    em1994  | -14.3266175918398
    em1995  | -9.07603254825782
    em1996  | -9.32002671928241
    em1997  | -10.0209028262361
    em1998  | -6.70882735196004
    em1999  | -7.32575918131333
    em2000  | -10.3113551933996
    em2001  | -11.0162573094354
    em2002  | -10.886264397431
    em2003  | -8.96078372850612
    em2004  | -11.5157129257881
    em2005  | -12.5048269019293
    em2006  | -12.2345161132594
    em2007  | -8.92504587601715
    em2008  | -12.1136551375247
    em2009  | -10.1144380511421
    em2010  | -7.72468307053519
    gdp1970 | 10502.1047183969
    gdp1971 | 9259.97560190599
    gdp1972 | 6593.98178532712
    gdp1973 | 5325.33813328068
    gdp1974 | -899.029529832931
    gdp1975 | -3184.93671107899
    gdp1976 | -4517.68204331439
    gdp1977 | -3322.9509067019
    gdp1978 | -33.8221923368737
    gdp1979 | 2882.50573071066
    gdp1980 | 3638.74436577365
    gdp1981 | 2211.77365027338
    gdp1982 | 5811.44631880621
    gdp1983 | 7365.75180165581
    gdp1984 | 10465.1797058904
    gdp1985 | 12312.7219748196
    gdp1986 | 12309.0418293413
    gdp1987 | 13695.5173269466
    gdp1988 | 12531.9995299889
    gdp1989 | 13009.2244205049
    gdp1990 | 10697.6839797576
    gdp1991 | 6835.94651304181
    gdp1992 | 4275.67753277099
    gdp1993 | 3382.29408813394
    gdp1994 | 3703.65406726311
    gdp1995 | 4238.17659535371
    gdp1996 | 4692.48744219914
    gdp1997 | 4539.23538342266
    gdp1998 | 5886.78983381162
    gdp1999 | 7527.72448728762
    gdp2000 | 7646.05563584361
    gdp2001 | 9053.22077886667
    gdp2002 | 9914.82548013531
    gdp2003 | 9201.64413455221
    gdp2004 | 9234.70123279344
    gdp2005 | 9565.5457350936
    gdp2006 | 9569.86316415438
    gdp2007 | 9104.60260145907
    gdp2008 | 8182.8163827425
    gdp2009 | 6279.93197775805
    gdp2010 | 4274.40397281553
    

另请参阅

6.2.7 - 对数据采样

数据采样的目标是从较大的数据集中获取较小、较易于管理的样本。使用示例数据集,您可以生成预测模型或使用它来帮助您调整数据库。以下示例演示如何使用 TABLESAMPLE 子句创建数据样本。

对表中数据采样

开始示例之前,请加载机器学习示例数据

使用 baseball 表,创建一个名为 baseball_sample 的新表,其中包含 25% 的 baseball 样本。请记住,TABLESAMPLE 不保证可返回子句中定义的记录的确切百分比。

=> CREATE TABLE baseball_sample AS SELECT * FROM baseball TABLESAMPLE(25);
CREATE TABLE
=> SELECT * FROM baseball_sample;
 id  | first_name | last_name  |    dob     |    team    | hr  | hits  |  avg  |   salary
-----+------------+------------+------------+------------+-----+-------+-------+-------------
   4 | Amanda     | Turner     | 1997-12-22 | Maroon     |  58 |   177 | 0.187 |     8047721
  20 | Jesse      | Cooper     | 1983-04-13 | Yellow     |  97 |    39 | 0.523 |     4252837
  22 | Randy      | Peterson   | 1980-05-28 | Orange     |  14 |    16 | 0.141 |  11827728.1
  24 | Carol      | Harris     | 1991-04-02 | Fuscia     |  96 |    12 | 0.456 |  40572253.6
  32 | Rose       | Morrison   | 1977-07-26 | Goldenrod  |  27 |   153 | 0.442 | 14510752.49
  50 | Helen      | Medina     | 1987-12-26 | Maroon     |  12 |   150 |  0.54 | 32169267.91
  70 | Richard    | Gilbert    | 1983-07-13 | Khaki      |   1 |   250 | 0.213 | 40518422.76
  81 | Angela     | Cole       | 1991-08-16 | Violet     |  87 |   136 | 0.706 | 42875181.51
  82 | Elizabeth  | Foster     | 1994-04-30 | Indigo     |  46 |   163 | 0.481 | 33896975.53
  98 | Philip     | Gardner    | 1992-05-06 | Puce       |  39 |   239 | 0.697 | 20967480.67
 102 | Ernest     | Freeman    | 1983-10-05 | Turquoise  |  46 |    77 | 0.564 | 21444463.92
.
.
.
(227 rows)

就您的示例而言,您可以创建预测模型,或调整您的数据库。

另请参阅

  • FROM 子句 (有关 TABLESAMPLE 子句的详细信息)

6.2.8 - SVD(奇异值分解)

奇异值分解 (SVD) 是一种矩阵分解方法,它允许您将 n×p 维矩阵 X 近似为 3 个矩阵的乘积:X(nxp) = U(nxk).S(kxk).VT(kxp),其中 k 是从 1 到 p 的整数,S 是对角矩阵。其对角线上的值为非负值(称为奇异值),按左上角最大、右下角最小的顺序排序。S 的所有其他元素为零。

在实践中,矩阵 V(pxk)(即 VT 的转置版本)更受欢迎。

如果 k(分解算法的输入参数,也称为要保留在输出中的组件数)等于 p,则分解是精确的。如果 k 小于 p,则分解变为近似值。

SVD 的一个应用是有损数据压缩。例如,存储 X 需要 n.p 个元素,而存储三个矩阵 U、S 和 VT 需要存储 n.k + k + k.p 个元素。如果 n=1000、p=10 和 k=2,存储 X 需要 10,000 个元素,而存储近似值需要 2,000+4+20 = 2,024 个元素。较小的 k 值更加节省存储空间,而较大的 k 值会提供更准确的近似值。

根据您的数据,奇异值可能会迅速减少,从而允许您选择一个远小于 p 值的 k 值。

SVD 的另一个常见应用是执行主分量分析。

可以使用以下函数来训练和应用 SVD 模型:

有关完整示例,请参阅计算 SVD

6.2.8.1 - 计算 SVD

该 SVD 示例使用名为 small_svd 的小数据集。该示例向您展示了如何使用给定的数据集计算 SVD。该表是一个数字矩阵。奇异值分解是使用 SVD 函数计算的。该示例计算表矩阵的 SVD 并将其赋给一个新对象,该对象包含一个向量以及两个矩阵 U 和 V。向量包含奇异值。第一个矩阵 U 包含左奇异向量,V 包含右奇异向量。

开始示例之前,请加载机器学习示例数据
  1. 创建名为 svdmodel 的 SVD 模型。

    => SELECT SVD ('svdmodel', 'small_svd', 'x1,x2,x3,x4');
    SVD
    --------------------------------------------------------------
    
    Finished in 1 iterations.
    Accepted Rows: 8  Rejected Rows: 0
    (1 row)
    
  2. 查看 svdmodel 的摘要输出。

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='svdmodel');
    GET_MODEL_SUMMARY
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ----------------------------------------------------------------------------------------------------------
    ---------------------------------------------------------------------------------------
    =======
    columns
    =======
    index|name
    -----+----
    1    | x1
    2    | x2
    3    | x3
    4    | x4
    ===============
    singular_values
    ===============
    index| value  |explained_variance|accumulated_explained_variance
    -----+--------+------------------+------------------------------
    1    |22.58748|      0.95542     |            0.95542
    2    | 3.79176|      0.02692     |            0.98234
    3    | 2.55864|      0.01226     |            0.99460
    4    | 1.69756|      0.00540     |            1.00000
    ======================
    right_singular_vectors
    ======================
    index|vector1 |vector2 |vector3 |vector4
    -----+--------+--------+--------+--------
    1    | 0.58736| 0.08033| 0.74288|-0.31094
    2    | 0.26661| 0.78275|-0.06148| 0.55896
    3    | 0.71779|-0.13672|-0.64563|-0.22193
    4    | 0.26211|-0.60179| 0.16587| 0.73596
    ========
    counters
    ========
    counter_name      |counter_value
    ------------------+-------------
    accepted_row_count|      8
    rejected_row_count|      0
    iteration_count   |      1
    ===========
    call_string
    ===========
    SELECT SVD('public.svdmodel', 'small_svd', 'x1,x2,x3,x4');
    (1 row)
    
  3. 创建名为 Umat 的新表以获取 U 的值。

    => CREATE TABLE Umat AS SELECT APPLY_SVD(id, x1, x2, x3, x4 USING PARAMETERS model_name='svdmodel',
    exclude_columns='id', key_columns='id') OVER() FROM small_svd;
    CREATE TABLE
    
  4. Umat 表中查看结果。该表将矩阵转换为新坐标系。

    => SELECT * FROM Umat ORDER BY id;
      id |        col1        |        col2        |        col3         |        col4
    -----+--------------------+--------------------+---------------------+--------------------
    1    | -0.494871802886819 | -0.161721379259287 |  0.0712816417153664 | -0.473145877877408
    2    |  -0.17652411036246 | 0.0753183783382909 |  -0.678196192333598 | 0.0567124770173372
    3    | -0.150974762654569 | -0.589561842046029 | 0.00392654610109522 |  0.360011163271921
    4    |  -0.44849499240202 |  0.347260956311326 |   0.186958376368345 |  0.378561270493651
    5    | -0.494871802886819 | -0.161721379259287 |  0.0712816417153664 | -0.473145877877408
    6    |  -0.17652411036246 | 0.0753183783382909 |  -0.678196192333598 | 0.0567124770173372
    7    | -0.150974762654569 | -0.589561842046029 | 0.00392654610109522 |  0.360011163271921
    8    |  -0.44849499240202 |  0.347260956311326 |   0.186958376368345 |  0.378561270493651
    (8 rows)
    
  5. 然后,我们可以选择将数据从 Umat 转换回 Xmat。首先,我们必须创建 Xmat 表,然后将 APPLY_INVERSE_SVD 函数应用于该表:

    => CREATE TABLE Xmat AS SELECT APPLY_INVERSE_SVD(* USING PARAMETERS model_name='svdmodel',
    exclude_columns='id', key_columns='id') OVER() FROM Umat;
    CREATE TABLE
    
  6. 然后,查看所创建的 Xmat 表中的数据:

    => SELECT id, x1::NUMERIC(5,1), x2::NUMERIC(5,1), x3::NUMERIC(5,1), x4::NUMERIC(5,1) FROM Xmat
    ORDER BY id;
    id | x1  | x2  | x3  | x4
    ---+-----+-----+-----+-----
    1  | 7.0 | 3.0 | 8.0 | 2.0
    2  | 1.0 | 1.0 | 4.0 | 1.0
    3  | 2.0 | 3.0 | 2.0 | 0.0
    4  | 6.0 | 2.0 | 7.0 | 4.0
    5  | 7.0 | 3.0 | 8.0 | 2.0
    6  | 1.0 | 1.0 | 4.0 | 1.0
    7  | 2.0 | 3.0 | 2.0 | 0.0
    8  | 6.0 | 2.0 | 7.0 | 4.0
    (8 rows)
    

另请参阅

6.3 - 回归算法

回归是一种重要且流行的机器学习工具,它通过学习数据的某些特征与观察值响应之间的关系来根据数据进行预测。回归用于对利润、销售额、温度、库存等项目进行预测。例如,您可以使用回归来根据位置、平方英尺、地块大小等来预测房屋的价格。在此示例中,房屋的价值是响应,其他因素(例如位置)是特征。

为回归方程找到的最佳系数集称为模型。结果与特征之间的关系在模型中进行了总结,然后可以应用于结果值未知的不同数据集。

6.3.1 - 自回归

请参阅时间序列模型下的自回归模型示例

6.3.2 - 线性回归

使用线性回归,可以为自变量或特性,以及因变量或结果之间的线性关系进行建模。建立线性回归模型用于:

  • 为自变量和因变量的数据集建立可预测模型。这样做允许您使用特征变量值来预测结果。例如,您可以预测一年中某一天的降雨量。

  • 决定自变量和一些结果变量之间的关系强弱。例如,加入你想要通过降雨量这个结果来决定不同天气变量下的重要性。可通过观察天气和降雨来建立线性回归模型进行求解。

与用来确定二进制分类结果的 逻辑回归 不同的是,线性回归主要用于预测线性关系中的连续数字结果。

可以使用下列函数进行线性回归建模,观察这个模型,并且使用模型对一组测试数据进行预测。

有关如何在 Vertica 表中使用线性回归的完整示例,请参阅构建线性回归模型

6.3.2.1 - 构建线性回归模型

线性回归示例使用名为 faithful 的小数据集。该数据集包含黄石国家公园老忠实间歇泉的喷发间隔和喷发持续时间。每次喷发的持续时间在 1.5 到 5 分钟之间。喷发之间的间隔长度和每次喷发的间隔长度各不相同。不过,您可以根据上一次喷发的持续时间来估计下一次喷发的时间。该示例展示了如何构建模型来预测 eruptions 的值(给定 waiting 特征的值)。

开始示例之前,请加载机器学习示例数据
  1. 使用 linear_reg_faithful 样本数据创建名为 faithful_training 的线性回归模型。

    => SELECT LINEAR_REG('linear_reg_faithful', 'faithful_training', 'eruptions', 'waiting'
       USING PARAMETERS optimizer='BFGS');
            LINEAR_REG
    ---------------------------
     Finished in 6 iterations
    
    (1 row)
    
  2. 查看 linear_reg_faithful 的摘要输出:

    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='linear_reg_faithful');
    --------------------------------------------------------------------------------
    =======
    details
    =======
    predictor|coefficient|std_err |t_value |p_value
    ---------+-----------+--------+--------+--------
    Intercept| -2.06795  | 0.21063|-9.81782| 0.00000
    waiting  |  0.07876  | 0.00292|26.96925| 0.00000
    
    ==============
    regularization
    ==============
    type| lambda
    ----+--------
    none| 1.00000
    
    ===========
    call_string
    ===========
    linear_reg('public.linear_reg_faithful', 'faithful_training', '"eruptions"', 'waiting'
    USING PARAMETERS optimizer='bfgs', epsilon=1e-06, max_iterations=100,
    regularization='none', lambda=1)
    
    ===============
    Additional Info
    ===============
    Name              |Value
    ------------------+-----
    iteration_count   |  3
    rejected_row_count|  0
    accepted_row_count| 162
    (1 row)
    
  3. 通过在测试数据中运行 PREDICT_LINEAR_REG 函数,创建包含响应值的表。将该表命名为 pred_faithful_results:在 pred_faithful_results 表中查看结果:

    => CREATE TABLE pred_faithful_results AS
       (SELECT id, eruptions, PREDICT_LINEAR_REG(waiting USING PARAMETERS model_name='linear_reg_faithful')
       AS pred FROM faithful_testing);
    CREATE TABLE
    
    => SELECT * FROM pred_faithful_results ORDER BY id;
     id  | eruptions |       pred
    -----+-----------+------------------
       4 |     2.283 |  2.8151271587036
       5 |     4.533 | 4.62659045686076
       8 |       3.6 | 4.62659045686076
       9 |      1.95 | 1.94877514654148
      11 |     1.833 | 2.18505296804024
      12 |     3.917 | 4.54783118302784
      14 |      1.75 |  1.6337380512098
      20 |      4.25 | 4.15403481386324
      22 |      1.75 |  1.6337380512098
    .
    .
    .
    (110 rows)
    

计算均方误差 (MSE)

您可以使用 MSE 函数计算模型与数据的拟合程度。MSE 返回实际值与预测值之间的平方差的平均值。

=> SELECT MSE (eruptions::float, pred::float) OVER() FROM
   (SELECT eruptions, pred FROM pred_faithful_results) AS prediction_output;
        mse        |                   Comments
-------------------+-----------------------------------------------
 0.252925741352641 | Of 110 rows, 110 were used and 0 were ignored
(1 row)

另请参阅

6.3.3 - 用于回归的随机森林

回归算法的随机森林创建回归树的集成模型。每棵树都对随机选择的训练数据子集进行训练。该算法预测的值是单个树的平均预测值。

您可以使用下列函数训练随机森林模型,并使用该模型对一组测试数据进行预测:

有关如何在 Vertica 中将随机森林用于回归算法的完整示例,请参阅构建随机森林回归模型

6.3.3.1 - 构建随机森林回归模型

此示例使用 "mtcars" 数据集创建随机森林模型来预测 carb 的值(化油器的数量)。

开始示例之前,请加载机器学习示例数据
  1. 使用 RF_REGRESSORmtcars 训练数据创建随机森林模型 myRFRegressorModel。使用 GET_MODEL_SUMMARY 查看模型的摘要输出:

    => SELECT RF_REGRESSOR ('myRFRegressorModel', 'mtcars', 'carb', 'mpg, cyl, hp, drat, wt' USING PARAMETERS
    ntree=100, sampling_size=0.3);
    RF_REGRESSOR
    --------------
    Finished
    (1 row)
    
    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='myRFRegressorModel');
    --------------------------------------------------------------------------------
    ===========
    call_string
    ===========
    SELECT rf_regressor('public.myRFRegressorModel', 'mtcars', '"carb"', 'mpg, cyl, hp, drat, wt'
    USING PARAMETERS exclude_columns='', ntree=100, mtry=1, sampling_size=0.3, max_depth=5, max_breadth=32,
    min_leaf_size=5, min_info_gain=0, nbins=32);
    
    
    =======
    details
    =======
    predictor|type
    ---------+-----
    mpg      |float
    cyl      | int
    hp       | int
    drat     |float
    wt       |float
    ===============
    Additional Info
    ===============
    Name              |Value
    ------------------+-----
    tree_count        | 100
    rejected_row_count|  0
    accepted_row_count| 32
    (1 row)
    
  2. 使用 PREDICT_RF_REGRESSOR 预测化油器数量:

    => SELECT PREDICT_RF_REGRESSOR (mpg,cyl,hp,drat,wt
    USING PARAMETERS model_name='myRFRegressorModel') FROM mtcars;
    PREDICT_RF_REGRESSOR
    ----------------------
    2.94774203574204
    2.6954087024087
    2.6954087024087
    2.89906346431346
    2.97688489288489
    2.97688489288489
    2.7086587024087
    2.92078965478965
    2.97688489288489
    2.7086587024087
    2.95621822621823
    2.82255155955156
    2.7086587024087
    2.7086587024087
    2.85650394050394
    2.85650394050394
    2.97688489288489
    2.95621822621823
    2.6954087024087
    2.6954087024087
    2.84493251193251
    2.97688489288489
    2.97688489288489
    2.8856467976468
    2.6954087024087
    2.92078965478965
    2.97688489288489
    2.97688489288489
    2.7934087024087
    2.7934087024087
    2.7086587024087
    2.72469441669442
    (32 rows)
    

6.3.4 - 用于回归的 SVM(支持向量机)

用于回归的支持向量机 (SVM) 根据训练数据预测连续有序变量。

与用来确定二进制分类结果的 逻辑回归 不同的是,用于回归的 SVM 主要用来预测连续数字结果。

您可以使用下列函数构建用于回归的 SVM 模型、查看模型,并使用该模型对一组测试数据进行预测:

有关如何在 Vertica 中使用 SVM 算法的完整示例,请参阅构建用于回归的 SVM 模型

6.3.4.1 - 构建用于回归的 SVM 模型

该用于回归的 SVM 示例使用了一个名为 faithful 的小型数据集,该数据集基于黄石国家公园的老忠实间歇泉。该数据集包含有关间歇泉喷发之间的等待时间和喷发持续时间的值。该示例展示了如何构建模型来预测 eruptions 的值(给定 waiting 特征的值)。

开始示例之前,请加载机器学习示例数据
  1. 使用 faithful_training 训练数据创建名为 svm_faithful 的 SVM 模型。

    => SELECT SVM_REGRESSOR('svm_faithful', 'faithful_training', 'eruptions', 'waiting'
                          USING PARAMETERS error_tolerance=0.1, max_iterations=100);
            SVM_REGRESSOR
    ---------------------------
     Finished in 5 iterations
    
    Accepted Rows: 162   Rejected Rows: 0
    (1 row)
    
  2. 查看 svm_faithful 的摘要输出:

    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='svm_faithful');
    
    ------------------------------------------------------------------
    =======
    
    details
    
    =======
    
    
    ===========================
    Predictors and Coefficients
    ===========================
             |Coefficients
    ---------+------------
    Intercept|  -1.59007
    waiting  |   0.07217
    ===========
    call_string
    ===========
    Call string:
    SELECT svm_regressor('public.svm_faithful', 'faithful_training', '"eruptions"',
    'waiting'USING PARAMETERS error_tolerance = 0.1, C=1, max_iterations=100,
    epsilon=0.001);
    
    ===============
    Additional Info
    ===============
    Name              |Value
    ------------------+-----
    accepted_row_count| 162
    rejected_row_count|  0
    iteration_count  |  5
    (1 row)
    
  3. 在测试数据中运行 PREDICT_SVM_REGRESSOR 函数创建包含响应值的新表。将此表命名为 pred_faithful_results. 。在 pred_faithful_results 表中查看结果:

    => CREATE TABLE pred_faithful AS
           (SELECT id, eruptions, PREDICT_SVM_REGRESSOR(waiting USING PARAMETERS model_name='svm_faithful')
            AS pred FROM faithful_testing);
    CREATE TABLE
    
    => SELECT * FROM pred_faithful ORDER BY id;
     id  | eruptions |       pred
    -----+-----------+------------------
       4 |     2.283 | 2.88444568755189
       5 |     4.533 | 4.54434581879796
       8 |       3.6 | 4.54434581879796
       9 |      1.95 | 2.09058040739072
      11 |     1.833 | 2.30708912016195
      12 |     3.917 | 4.47217624787422
      14 |      1.75 | 1.80190212369576
      20 |      4.25 | 4.11132839325551
      22 |      1.75 | 1.80190212369576
    .
    .
    .
    (110 rows)
    

计算均方误差 (MSE)

您可以使用 MSE 函数计算模型与数据的拟合程度。MSE 返回实际值与预测值之间的平方差的平均值。

=> SELECT MSE(obs::float, prediction::float) OVER()
   FROM (SELECT eruptions AS obs, pred AS prediction
         FROM pred_faithful) AS prediction_output;
        mse        |                   Comments
-------------------+-----------------------------------------------
 0.254499811834235 | Of 110 rows, 110 were used and 0 were ignored
(1 row)

另请参阅

6.3.5 - 用于回归的 XGBoost

XGBoost (eXtreme Gradient Boosting) 是一种很受欢迎的监督式学习算法,用于对大型数据集进行回归和分类。它使用顺序构建的浅层决策树来提供准确的结果和高度可扩展的定型方法,以避免过度拟合。

以下 XGBoost 函数使用回归模型创建和执行预测:

示例

此示例使用名为 "mtcars" 的小型数据集(其中包含 1973-1974 年 32 辆汽车的设计和性能数据),并创建 XGBoost 回归模型来预测变量 carb 的值(化油器的数量)。

开始示例之前,请加载机器学习示例数据
  1. 使用 XGB_REGRESSORmtcars 数据集创建 XGBoost 回归模型 xgb_cars

    => SELECT XGB_REGRESSOR ('xgb_cars', 'mtcars', 'carb', 'mpg, cyl, hp, drat, wt'
        USING PARAMETERS learning_rate=0.5);
     XGB_REGRESSOR
    ---------------
     Finished
    (1 row)
    

    然后,您可以使用 GET_MODEL_SUMMARY 查看模型的摘要:

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='xgb_cars');
                      GET_MODEL_SUMMARY
    ------------------------------------------------------
    ===========
    call_string
    ===========
    xgb_regressor('public.xgb_cars', 'mtcars', '"carb"', 'mpg, cyl, hp, drat, wt'
    USING PARAMETERS exclude_columns='', max_ntree=10, max_depth=5, nbins=32, objective=squarederror,
    split_proposal_method=global, epsilon=0.001, learning_rate=0.5, min_split_loss=0, weight_reg=0, sampling_size=1)
    
    =======
    details
    =======
    predictor|      type
    ---------+----------------
       mpg   |float or numeric
       cyl   |      int
       hp    |      int
      drat   |float or numeric
       wt    |float or numeric
    
    ===============
    Additional Info
    ===============
           Name       |Value
    ------------------+-----
        tree_count    | 10
    rejected_row_count|  0
    accepted_row_count| 32
    
    (1 row)
    
  2. 使用 PREDICT_XGB_REGRESSOR 预测化油器数量:

    => SELECT carb, PREDICT_XGB_REGRESSOR (mpg,cyl,hp,drat,wt USING PARAMETERS model_name='xgb_cars') FROM mtcars;
     carb | PREDICT_XGB_REGRESSOR
    ------+-----------------------
        4 |      4.00335213618023
        2 |       2.0038188946536
        6 |      5.98866003194438
        1 |      1.01774386191546
        2 |       1.9959801016274
        2 |       2.0038188946536
        4 |      3.99545403625739
        8 |      7.99211056556231
        2 |      1.99291901733151
        3 |       2.9975688946536
        3 |       2.9975688946536
        1 |      1.00320357711227
        2 |       2.0038188946536
        4 |      3.99545403625739
        4 |      4.00124134679445
        1 |      1.00759516721382
        4 |      3.99700517763435
        4 |      3.99580193056138
        4 |      4.00009088187525
        3 |       2.9975688946536
        2 |      1.98625064560888
        1 |      1.00355294416998
        2 |      2.00666247039502
        1 |      1.01682931210169
        4 |      4.00124134679445
        1 |      1.01007809485918
        2 |      1.98438405824605
        4 |      3.99580193056138
        2 |      1.99291901733151
        4 |      4.00009088187525
        2 |       2.0038188946536
        1 |      1.00759516721382
    (32 rows)
    

6.4 - 分类算法

分类是一种重要且流行的机器学习工具,可将数据集中的项分配给不同的类别。分类用于在欺诈检测、文本分类等方面预测随时间变化的风险。分类函数从具有不同的已知类别的数据集开始。例如,假设您想根据学生进入研究生院的可能性对他们进行分类。除了入学考试分数和等级等因素外,您还可以跟踪工作经验。

二进制分类意味着结果(本例中的结果为“录取情况”)只有两个可能的值:录取或不录取。多类结果有两个以上的值。例如,低、中或高录取机会。在训练过程中,分类算法查找结果和特征之间的关系。这种关系在模型中进行了总结,然后可以应用到类别未知的不同数据集。

6.4.1 - 逻辑回归

使用逻辑回归,您可以模拟独立变量之间的关系或功能,以及一些相关变量或结果。逻辑回归的结果始终是二进制值。

您可以构建逻辑回归模型:

  • 将预测模型拟合到独立变量和一些二进制变量的训练数据集。通过这种方法,您可以对结果进行预测,例如判断一个电子邮件是否是垃圾邮件。

  • 确定一个独立变量和一些二进制结果变量之间的关系强度。例如,假设您想要确定一个电子邮件是否是垃圾邮件。您可以基于对电子邮件属性的观测值来构建逻辑回归模型。然后,您可以确定电子邮件的各种属性对该结果的重要性。

您可以使用下列函数构建逻辑回归模型、查看模型,并使用该模型对一组测试数据进行预测:

有关如何在 Vertica 表中使用逻辑回归的完整编程实例,请参阅构建逻辑回归模型

6.4.1.1 - 构建逻辑回归模型

该逻辑回归示例使用了名为 mtcars 的小数据集。该示例展示了如何构建预测 am 值的模型(表示汽车是自动档还是手动档)。使用数据集中所有其他特征给定的值。

在该示例中,使用约 60% 的数据作为样本数据来创建模型。余下 40% 的数据用于针对测试逻辑回归模型的测试数据。

开始示例之前,请加载机器学习示例数据
  1. 使用 mtcars_train 训练数据创建名为 logistic_reg_mtcars 的逻辑回归模型。

    => SELECT LOGISTIC_REG('logistic_reg_mtcars', 'mtcars_train', 'am', 'cyl, wt'
       USING PARAMETERS exclude_columns='hp');
            LOGISTIC_REG
    ----------------------------
     Finished in 15 iterations
    
    (1 row)
    
  2. 查看 logistic_reg_mtcars 的摘要输出。

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='logistic_reg_mtcars');
    --------------------------------------------------------------------------------
    =======
    details
    =======
    predictor|coefficient|  std_err  |z_value |p_value
    ---------+-----------+-----------+--------+--------
    Intercept| 262.39898 |44745.77338| 0.00586| 0.99532
    cyl      | 16.75892  |5987.23236 | 0.00280| 0.99777
    wt       |-119.92116 |17237.03154|-0.00696| 0.99445
    
    ==============
    regularization
    ==============
    type| lambda
    ----+--------
    none| 1.00000
    
    ===========
    call_string
    ===========
    logistic_reg('public.logistic_reg_mtcars', 'mtcars_train', '"am"', 'cyl, wt'
    USING PARAMETERS exclude_columns='hp', optimizer='newton', epsilon=1e-06,
    max_iterations=100, regularization='none', lambda=1)
    
    
    ===============
    Additional Info
    ===============
    Name              |Value
    ------------------+-----
    iteration_count   | 20
    rejected_row_count|  0
    accepted_row_count| 20
    (1 row)
    
  3. 创建名为 mtcars_predict_results 的表。使用通过在测试数据上运行 PREDICT_LOGISTIC_REG 函数获得的预测结果来填充表。在 mtcars_predict_results 表中查看结果。

    => CREATE TABLE mtcars_predict_results AS
       (SELECT car_model, am, PREDICT_LOGISTIC_REG(cyl, wt
       USING PARAMETERS model_name='logistic_reg_mtcars')
       AS Prediction FROM mtcars_test);
    CREATE TABLE
    
    
    => SELECT * FROM mtcars_predict_results;
       car_model    | am | Prediction
    ----------------+----+------------
     AMC Javelin    |  0 |          0
     Hornet 4 Drive |  0 |          0
     Maserati Bora  |  1 |          0
     Merc 280       |  0 |          0
     Merc 450SL     |  0 |          0
     Toyota Corona  |  0 |          1
     Volvo 142E     |  1 |          1
     Camaro Z28     |  0 |          0
     Datsun 710     |  1 |          1
     Honda Civic    |  1 |          1
     Porsche 914-2  |  1 |          1
     Valiant        |  0 |          0
    (12 rows)
    
  4. 使用 PREDICT_LOGISTIC_REG 评估函数来评估 CONFUSION_MATRIX 函数的准确性。

    => SELECT CONFUSION_MATRIX(obs::int, pred::int USING PARAMETERS num_classes=2) OVER()
       FROM (SELECT am AS obs, Prediction AS pred FROM mtcars_predict_results) AS prediction_output;
     class | 0 | 1 |                   comment
    -------+---+---+---------------------------------------------
         0 | 6 | 1 |
         1 | 1 | 4 | Of 12 rows, 12 were used and 0 were ignored
    (2 rows)
    

    在本例中,PREDICT_LOGISTIC_REG 正确预测在 1 列中值为 am 的五辆汽车中的四辆具有 1 值。在 0 列中值为 am 的七辆汽车中,正确预测了六辆具有值 0。将一辆汽车错误地分类为具有值 1

另请参阅

6.4.2 - 朴素贝叶斯

当可以假设特征独立时,您可以使用朴素贝叶斯算法对数据进行分类。该算法使用独立的特征来计算特定类的概率。例如,您可能想要预测电子邮件是垃圾邮件的概率。在该例中,您将使用与垃圾邮件相关的词库来计算电子邮件内容是垃圾邮件的概率。

您可以使用下列函数构建朴素贝叶斯模型、查看模型,并使用该模型对一组测试数据进行预测:

有关如何在 Vertica 中使用朴素贝叶斯算法的完整示例,请参阅使用朴素贝叶斯对数据进行分类

6.4.2.1 - 使用朴素贝叶斯对数据进行分类

该朴素贝叶斯示例使用 HouseVotes84 数据集向您展示如何构建模型。使用此模型,您可以根据投票记录预测美国国会议员隶属于哪个政党。为了帮助对已清理的数据进行分类,替换了所有缺投的投票。已清理的数据将缺投的投票替换为选民所在政党的大多数投票。例如,假设一名民主党成员未对 vote1 投票,而大多数民主党人投了赞成票。此示例将所有民主党人对 vote1 的缺投投票替换为赞成票。

在此示例中,大约 75% 的已清理 HouseVotes84 数据被随机选择并复制到训练表中。剩余的已清理 HouseVotes84 数据用作测试表。

开始示例之前,请加载机器学习示例数据

您还必须加载 naive_bayes_data_prepration.sql 脚本:

$ /opt/vertica/bin/vsql -d <name of your database> -f naive_bayes_data_preparation.sql
  1. 使用 house84_train 训练数据创建名为 naive_house84_model 的朴素贝叶斯模型。

    => SELECT NAIVE_BAYES('naive_house84_model', 'house84_train', 'party',
                          '*' USING PARAMETERS exclude_columns='party, id');
                      NAIVE_BAYES
    ------------------------------------------------
     Finished. Accepted Rows: 315  Rejected Rows: 0
    (1 row)
    
  2. 创建名为 predicted_party_naive 的新表。使用从测试数据上的 PREDICT_NAIVE_BAYES 函数得到的预测结果来填充此表。

    => CREATE TABLE predicted_party_naive
         AS SELECT party,
              PREDICT_NAIVE_BAYES (vote1, vote2, vote3, vote4, vote5,
                                   vote6, vote7, vote8, vote9, vote10,
                                   vote11, vote12, vote13, vote14,
                                   vote15, vote16
                                     USING PARAMETERS model_name = 'naive_house84_model',
                                                      type = 'response') AS Predicted_Party
           FROM house84_test;
    CREATE TABLE
    
  3. 计算模型预测的准确性。

    
    => SELECT  (Predictions.Num_Correct_Predictions / Count.Total_Count) AS Percent_Accuracy
        FROM (  SELECT COUNT(Predicted_Party) AS Num_Correct_Predictions
            FROM predicted_party_naive
            WHERE party = Predicted_Party
             ) AS Predictions,
             (  SELECT COUNT(party) AS Total_Count
                   FROM predicted_party_naive
                ) AS Count;
       Percent_Accuracy
    ----------------------
     0.933333333333333333
    (1 row)
    

该模型根据国会议员的投票模式正确预测了他们的政党,准确率为 93%。

查看每个类别的概率

您还可以查看每个类的概率。使用 PREDICT_NAIVE_BAYES_CLASSES 查看每个类的概率。

=> SELECT PREDICT_NAIVE_BAYES_CLASSES (id, vote1, vote2, vote3, vote4, vote5,
                                       vote6, vote7, vote8, vote9, vote10,
                                       vote11, vote12, vote13, vote14,
                                       vote15, vote16
                                       USING PARAMETERS model_name = 'naive_house84_model',
                                                        key_columns = 'id', exclude_columns = 'id',
                                                        classes = 'democrat, republican')
        OVER() FROM house84_test;
 id  | Predicted  |    Probability    |       democrat       |      republican
-----+------------+-------------------+----------------------+----------------------
 368 | democrat   |                 1 |                    1 |                    0
 372 | democrat   |                 1 |                    1 |                    0
 374 | democrat   |                 1 |                    1 |                    0
 378 | republican | 0.999999962214987 | 3.77850125111219e-08 |    0.999999962214987
 384 | democrat   |                 1 |                    1 |                    0
 387 | democrat   |                 1 |                    1 |                    0
 406 | republican | 0.999999945980143 | 5.40198564592332e-08 |    0.999999945980143
 419 | democrat   |                 1 |                    1 |                    0
 421 | republican | 0.922808855631005 |   0.0771911443689949 |    0.922808855631005
.
.
.
(109 rows)

另请参阅

6.4.3 - 用于分类的随机森林

随机森林算法创建决策树的集成模型。每棵树都对随机选择的训练数据子集进行训练。

您可以使用下列函数训练随机森林模型,并使用该模型对一组测试数据进行预测:

有关如何在 Vertica 中使用随机森林算法的完整示例,请参阅使用随机森林对数据进行分类

6.4.3.1 - 使用随机森林对数据进行分类

该随机森林示例使用了一个名为 iris 的数据集。该示例包含四个变量,用于测量 iris 花的各个部分以预测其种属。

在开始该示例之前,请确保您已按照下载机器学习示例数据中的步骤进行操作。

  1. 使用 RF_CLASSIFIERiris 数据创建名为 rf_iris 的随机森林模型。使用 GET_MODEL_SUMMARY 查看模型的摘要输出:

    => SELECT RF_CLASSIFIER ('rf_iris', 'iris', 'Species', 'Sepal_Length, Sepal_Width, Petal_Length, Petal_Width'
    USING PARAMETERS ntree=100, sampling_size=0.5);
    
    
            RF_CLASSIFIER
    ----------------------------
    Finished training
    
    (1 row)
    
    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='rf_iris');
    ------------------------------------------------------------------------
    ===========
    call_string
    ===========
    SELECT rf_classifier('public.rf_iris', 'iris', '"species"', 'Sepal_Length, Sepal_Width, Petal_Length,
    Petal_Width' USING PARAMETERS exclude_columns='', ntree=100, mtry=2, sampling_size=0.5, max_depth=5,
    max_breadth=32, min_leaf_size=1, min_info_gain=0, nbins=32);
    
    =======
    details
    =======
    predictor   |type
    ------------+-----
    sepal_length|float
    sepal_width |float
    petal_length|float
    petal_width |float
    
    ===============
    Additional Info
    ===============
    Name              |Value
    ------------------+-----
    tree_count        | 100
    rejected_row_count|  0
    accepted_row_count| 150
    (1 row)
    
  2. 使用 PREDICT_RF_CLASSIFIER 将分类器应用于测试数据:

    => SELECT PREDICT_RF_CLASSIFIER (Sepal_Length, Sepal_Width, Petal_Length, Petal_Width
                                      USING PARAMETERS model_name='rf_iris') FROM iris1;
    
    PREDICT_RF_CLASSIFIER
    -----------------------
    setosa
    setosa
    setosa
    .
    .
    .
    versicolor
    versicolor
    versicolor
    .
    .
    .
    virginica
    virginica
    virginica
    .
    .
    .
    (90 rows)
    
  3. 使用 PREDICT_RF_CLASSIFIER_CLASSES 查看每个类的概率:

    => SELECT PREDICT_RF_CLASSIFIER_CLASSES(Sepal_Length, Sepal_Width, Petal_Length, Petal_Width
                                   USING PARAMETERS model_name='rf_iris') OVER () FROM iris1;
    predicted  |    probability
    -----------+-------------------
    setosa     |                 1
    setosa     |                 1
    setosa     |                 1
    setosa     |                 1
    setosa     |                 1
    setosa     |                 1
    setosa     |                 1
    setosa     |                 1
    setosa     |                 1
    setosa     |                 1
    setosa     |              0.99
    .
    .
    .
    (90 rows)
    

6.4.4 - 用于分类的 SVM(支持向量机)

支持向量机 (SVM) 是一种分类算法,它根据训练数据将数据分配到一个类别或另一个类别。该算法实施了可高度扩展的线性 SVM。

您可以使用下列函数训练 SVM 模型,并使用该模型对一组测试数据进行预测:

您还可以使用以下评估函数来获得进一步的见解:

有关如何在 Vertica 中使用 SVM 算法的完整示例,请参阅使用 SVM(支持向量机)对数据进行分类

Vertica 中 SVM 算法的实施基于论文 Distributed Newton Methods for Regularized Logistic Regression(正则逻辑回归的分布式牛顿法)。

6.4.4.1 - 使用 SVM(支持向量机)对数据进行分类

该 SVM 示例使用名为 mtcars 的小数据集。该示例展示了如何使用 SVM_CLASSIFIER 函数训练模型以使用 PREDICT_SVM_CLASSIFIER 函数预测 am 的值(挡位类型,其中 0 = 自动挡,1 = 手动挡)。

开始示例之前,请加载机器学习示例数据
  1. 使用 mtcars_train 训练数据创建名为 svm_class 的 SVM 模型。

    
    => SELECT SVM_CLASSIFIER('svm_class', 'mtcars_train', 'am', 'cyl, mpg, wt, hp, gear'
                              USING PARAMETERS exclude_columns='gear');
    
    SVM_CLASSIFIER
    
    ----------------------------------------------------------------
    Finished in 12 iterations.
    Accepted Rows: 20  Rejected Rows: 0
    (1 row)
    
  2. 查看 svm_class 的摘要输出。

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='svm_class');
    ------------------------------------------------------------------------
    
    =======
    details
    =======
    predictor|coefficient
    ---------+-----------
    Intercept| -0.02006
    cyl      |  0.15367
    mpg      |  0.15698
    wt       | -1.78157
    hp       |  0.00957
    
    ===========
    call_string
    ===========
    SELECT svm_classifier('public.svm_class', 'mtcars_train', '"am"', 'cyl, mpg, wt, hp, gear'
    USING PARAMETERS exclude_columns='gear', C=1, max_iterations=100, epsilon=0.001);
    
    ===============
    Additional Info
    ===============
    Name              |Value
    ------------------+-----
    accepted_row_count| 20
    rejected_row_count|  0
    iteration_count   | 12
    (1 row)
    
  3. 创建名为 svm_mtcars_predict 的新表。使用通过在测试数据上运行 PREDICT_SVM_CLASSIFIER 函数获得的预测结果来填充表。

    => CREATE TABLE svm_mtcars_predict AS
       (SELECT car_model, am, PREDICT_SVM_CLASSIFIER(cyl, mpg, wt, hp
                                                USING PARAMETERS model_name='svm_class')
                                                AS Prediction FROM mtcars_test);
    CREATE TABLE
    
  4. svm_mtcars_predict 表中查看结果。

    => SELECT * FROM svm_mtcars_predict;
    car_model     | am | Prediction
    ------------- +----+------------
    
    Toyota Corona |  0 |          1
    Camaro Z28    |  0 |          0
    Datsun 710    |  1 |          1
    Valiant       |  0 |          0
    Volvo 142E    |  1 |          1
    AMC Javelin   |  0 |          0
    Honda Civic   |  1 |          1
    Hornet 4 Drive|  0 |          0
    Maserati Bora |  1 |          1
    Merc 280      |  0 |          0
    Merc 450SL    |  0 |          0
    Porsche 914-2 |  1 |          1
    (12 rows)
    
  5. 使用 PREDICT_SVM_CLASSIFIER 评估函数来评估 CONFUSION_MATRIX 函数的准确性。

    => SELECT CONFUSION_MATRIX(obs::int, pred::int USING PARAMETERS num_classes=2) OVER()
            FROM (SELECT am AS obs, Prediction AS pred FROM svm_mtcars_predict) AS prediction_output;
     class | 0 | 1 |                   comment
    -------+---+---+---------------------------------------------
         0 | 6 | 1 |
         1 | 0 | 5 | Of 12 rows, 12 were used and 0 were ignored
    (2 rows)
    

    在本例中,PREDICT_SVM_CLASSIFIER 正确预测在 am 列中值为 1 的汽车具有 1 值。没有汽车被错误分类。在 0 列中值为 am 的七辆汽车中,正确预测了六辆具有值 0。将一辆汽车错误地分类为具有值 1

另请参阅

6.4.5 - XGBoost 用于分类

XGBoost (eXtreme Gradient Boosting) 是一种很受欢迎的监督式学习算法,用于对大型数据集进行回归和分类。它使用顺序构建的浅层决策树来提供准确的结果和高度可扩展的定型方法,以避免过度拟合。

以下 XGBoost 函数使用分类模型创建和执行预测:

示例

此示例使用 "iris" 数据集(其中包含对花的各个部分的测量值),可用于预测其种属并创建 XGBoost 分类器模型来对每朵花的种属进行分类。

开始示例之前,请加载机器学习示例数据
  1. 使用 XGB_CLASSIFIERiris 数据集创建名为 xgb_iris 的 XGBoost 分类器模型。

    => SELECT XGB_CLASSIFIER ('xgb_iris', 'iris', 'Species', 'Sepal_Length, Sepal_Width, Petal_Length, Petal_Width'
        USING PARAMETERS max_ntree=10, max_depth=5, weight_reg=0.1, learning_rate=1);
     XGB_CLASSIFIER
    ----------------
     Finished
    (1 row)
    

    然后,您可以使用 GET_MODEL_SUMMARY 查看模型的摘要:

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='xgb_iris');
                                                                                                                                                                           GET_MODEL_SUMMARY
    ------------------------------------------------------
    ===========
    call_string
    ===========
    xgb_classifier('public.xgb_iris', 'iris', '"species"', 'Sepal_Length, Sepal_Width, Petal_Length, Petal_Width'
    USING PARAMETERS exclude_columns='', max_ntree=10, max_depth=5, nbins=32, objective=crossentropy,
    split_proposal_method=global, epsilon=0.001, learning_rate=1, min_split_loss=0, weight_reg=0.1, sampling_size=1)
    
    =======
    details
    =======
     predictor  |      type
    ------------+----------------
    sepal_length|float or numeric
    sepal_width |float or numeric
    petal_length|float or numeric
    petal_width |float or numeric
    
    
    ===============
    Additional Info
    ===============
           Name       |Value
    ------------------+-----
        tree_count    |  10
    rejected_row_count|  0
    accepted_row_count| 150
    
    (1 row)
    
  2. 使用 PREDICT_XGB_CLASSIFIER 将分类器应用于测试数据:

    => SELECT PREDICT_XGB_CLASSIFIER (Sepal_Length, Sepal_Width, Petal_Length, Petal_Width
        USING PARAMETERS model_name='xgb_iris') FROM iris1;
     PREDICT_XGB_CLASSIFIER
    ------------------------
     setosa
     setosa
     setosa
     .
     .
     .
     versicolor
     versicolor
     versicolor
     .
     .
     .
     virginica
     virginica
     virginica
     .
     .
     .
    
    (90 rows)
    
  3. 使用 PREDICT_XGB_CLASSIFIER_CLASSES 查看每个类的概率:

    => SELECT PREDICT_XGB_CLASSIFIER_CLASSES(Sepal_Length, Sepal_Width, Petal_Length, Petal_Width
        USING PARAMETERS model_name='xgb_iris') OVER (PARTITION BEST) FROM iris1;
      predicted  |    probability
    ------------+-------------------
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     setosa     | 0.999911552783011
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     setosa     |   0.9999650465368
     versicolor |  0.99991871763563
     .
     .
     .
    (90 rows)
    

6.5 - 聚类算法

聚类是一种重要且流行的机器学习工具,用于在数据集中查找彼此相似的项目群集。聚类的目标是创建具有大量相似对象的群集。与分类类似,聚类对数据进行分段。不过,在聚类中,不定义分类组。

将数据聚类成相关的组有许多有用的应用场合。如果您已经知道数据包含多少个群集,则 K-均值 算法可能足以训练您的模型并使用该模型来预测新数据点的群集成员资格。

但是,在更常见的情况下,您在分析数据之前不知道它包含多少个群集。在这些情况下,二等分 k-means 算法可以更有效地在您的数据中找到正确的群集。

k-means 和二等分 k-means 都预测给定数据集的群集。然后,可以使用借助于任一算法训练的模型来预测分配新数据点的群集。

聚类可用于发现数据中的异常并找到自然的数据组。例如,您可以使用聚类来分析地理区域并确定该区域中最有可能遭受地震袭击的区域。有关完整示例,请参阅使用 KMeans 方法进行地震群集分析

在 Vertica 中,基于欧几里德距离计算聚类。通过这种计算,数据点被分配到具有最近中心的群集。

6.5.1 - K-均值

您可以使用聚类算法(k 均值聚类)根据数据点之间的相似性将数据点分为 k 个不同的组。

k 均值的目的在于将 n 个数据对象分为 k 个群集。通过这种分法,将每个数据对象分配给最小距离的聚类。最小距离也被称为聚类中心对象:

有关如何对 Vertica 中的表使用 k 均值的完整示例,请参阅使用 k-means 对数据进行聚类

6.5.1.1 - 使用 k-means 对数据进行聚类

该 k-means 示例使用了名为 agar_dish_1agar_dish_2 的两个小数据集。使用 agar_dish_1 数据集中的数字数据,可以将数据聚类到 k 群集。然后,可以使用所创建的 k-means 模型,在 agar_dish_2 上运行 APPLY_KMEANS,并将其分配到原始模型中创建的群集。

开始示例之前,请加载机器学习示例数据

将训练数据聚类到 k 群集

  1. 使用 agar_dish_1 表数据创建名为 agar_dish_kmeans 的 k-means 模型。

    => SELECT KMEANS('agar_dish_kmeans', 'agar_dish_1', '*', 5
                      USING PARAMETERS exclude_columns ='id', max_iterations=20, output_view='agar_1_view',
                      key_columns='id');
               KMEANS
    ---------------------------
     Finished in 7 iterations
    
    (1 row)
    

    该示例创建了一个名为 agar_dish_kmeans 的模型和一个包含模型结果的名为 agar_1_view 的视图。运行聚类算法时,您可能会得到不同的结果。这是因为 KMEANS 默认随机选择初始中心。

  2. 查看 agar_1_view 输出。

    => SELECT * FROM agar_1_view;
     id  | cluster_id
    -----+------------
       2 |          4
       5 |          4
       7 |          4
       9 |          4
      13 |          4
    .
    .
    .
    (375 rows)
    
  3. 由于指定了群集的数量为 5,验证函数创建了五个群集。计算每个群集内的数据点数。

    => SELECT cluster_id, COUNT(cluster_id) as Total_count
       FROM agar_1_view
       GROUP BY cluster_id;
     cluster_id | Total_count
    ------------+-------------
              0 |          76
              2 |          80
              1 |          74
              3 |          73
              4 |          72
    (5 rows)
    

    从输出可以看出创建了五个群集:01234

    您现在已成功将 agar_dish_1.csv 中的数据聚类为五个不同的群集。

生成您的模型的摘要

使用 GET_MODEL_SUMMARY 函数查看 agar_dish_means 的摘要输出。

=> SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='agar_dish_kmeans');
----------------------------------------------------------------------------------
=======
centers
=======
x       |   y
--------+--------
0.49708 | 0.51116
-7.48119|-7.52577
-1.56238|-1.50561
-3.50616|-3.55703
-5.52057|-5.49197

=======
metrics
=======
Evaluation metrics:
  Total Sum of Squares: 6008.4619
  Within-Cluster Sum of Squares:
      Cluster 0: 12.083548
      Cluster 1: 12.389038
      Cluster 2: 12.639238
      Cluster 3: 11.210146
      Cluster 4: 12.994356
  Total Within-Cluster Sum of Squares: 61.316326
  Between-Cluster Sum of Squares: 5947.1456
  Between-Cluster SS / Total SS: 98.98%
Number of iterations performed: 2
Converged: True
Call:
kmeans('public.agar_dish_kmeans', 'agar_dish_1', '*', 5
USING PARAMETERS exclude_columns='id', max_iterations=20, epsilon=0.0001, init_method='kmeanspp',
distance_method='euclidean', output_view='agar_view_1', key_columns='id')
(1 row)

使用 k-means 模型对数据进行聚类

使用刚刚创建的 k-means 模型 agar_dish_kmeans,您可以将 agar_dish_2 中的点分配给群集中心。

使用 agar_dish_2 表作为输入表,使用 agar_dish_kmeans 模型作为初始群集中心,创建一个名为 kmeans_results 的表。

仅向 APPLY_KMEANS 函数中的参数添加相关特征列。

=> CREATE TABLE kmeans_results AS
        (SELECT id,
                APPLY_KMEANS(x, y
                             USING PARAMETERS
                                              model_name='agar_dish_kmeans') AS cluster_id
         FROM agar_dish_2);

kmeans_results 表显示 agar_dish_kmeans 模型对 agar_dish_2 数据进行了正确聚类。

另请参阅

6.5.2 - 二等分 k-means

“二等分 k-means”聚类算法将 k-means 聚类与划分层次聚类相结合。使用二等分 k-means,您不仅可以获得群集,还可以获得数据点群集的层次结构。

该层次结构比 K-均值 返回的非结构化平面群集集合提供更多信息。该层次结构显示聚类结果如何查看群集二等分过程的每个步骤以查找新群集。利用群集的层次结构,可以更轻松地确定数据集中的群集数量。

给定由二等分 k-means 产生的 k 群集的层次结构,您可以轻松计算以下形式的任何预测:假设数据只包含 k' 个群集,其中 k' 是一个数字,它小于或等于用于训练模型的 k

有关如何使用二等分 k-means 在 Vertica 中分析表的完整示例,请参阅使用二等分 k-means 对数据进行分层聚类

6.5.2.1 - 使用二等分 k-means 对数据进行分层聚类

此二等分 k-means 示例使用名为 agar_dish_training 和 agar_dish_testing 的两个小数据集。使用 agar_dish_training 数据集中的数字数据,可以将数据聚类到 k 群集。然后,使用生成的二等分 k-means 模型,您可以在 agar_dish_testing 上运行 APPLY_BISECTING_KMEANS,并将数据分配给在您的训练模型中创建的群集。与常规 k-means(Vertica 中也提供)不同,二等分 k-means 允许您使用小于或等于 k 的任意数量的群集进行预测。因此,如果您使用 k=5 训练模型,但后来决定使用 k=2 进行预测,则无需重新训练该模型;只需运行 k=2 的 APPLY_BISECTING_KMEANS。

开始示例之前,请加载机器学习示例数据。 对于这个示例,加载 agar_dish_training.csvagar_dish_testing.csv

将训练数据聚类成 k 个群集来训练模型

  1. 使用 agar_dish_training 表数据创建名为 agar_dish_bkmeans 的二等分 k-means 模型。

    => SELECT BISECTING_KMEANS('agar_dish_bkmeans', 'agar_dish_training', '*', 5 USING PARAMETERS exclude_columns='id', key_columns='id', output_view='agar_1_view');
     BISECTING_KMEANS
    ------------------
     Finished.
    (1 row)
    

    此示例创建一个名为 agar_dish_bkmeans 的模型和一个包含模型结果的名为 agar_1_view 的视图。运行聚类算法时,您可能会得到略有不同的结果。这是因为 BISECTING_KMEANS 使用随机数来生成最佳群集。

  2. 查看 agar_1_view 的输出:

    => SELECT * FROM agar_1_view;
     id  | cluster_id
    -----+------------
       2 |          4
       5 |          4
       7 |          4
       9 |          4
    
    ...
    

    在这里,我们可以看到 agar_dish_training 表中每个点的 ID 以及它被分配到了哪个群集。

  3. 由于我们将群集数量指定为 5,因此通过计算每个群集中的数据点数来验证该函数是否创建了五个群集。

    => SELECT cluster_id, COUNT(cluster_id) as Total_count FROM agar_1_view GROUP BY cluster_id;
     cluster_id | Total_count
    ------------+-------------
              5 |          76
              7 |          73
              8 |          74
              4 |          72
              6 |          80
    (5 rows)
    

    您可能想知道为什么 cluster_id 不从 0 或 1 开始。这是因为二等分 k-means 算法生成的群集比 k-means 多很多,然后输出 k 的指定值所需的群集。我们稍后会看到为什么这很有用。

    您现在已成功将 agar_dish_training.csv 中的数据聚类为五个不同的群集。

生成您的模型的摘要

使用 GET_MODEL_SUMMARY 函数查看 agar_dish_bkmeans 的摘要输出。

```
=> SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='agar_dish_bkmeans');
======
BKTree
======

```

```
center_id|   x    |   y    | withinss |totWithinss|bisection_level|cluster_size|parent|left_child|right_child
---------+--------+--------+----------+-----------+---------------+------------+------+----------+-----------
    0    |-3.59450|-3.59371|6008.46192|6008.46192 |       0       |    375     |      |    1     |     2
    1    |-6.47574|-6.48280|336.41161 |1561.29110 |       1       |    156     |  0   |    5     |     6
    2    |-1.54210|-1.53574|1224.87949|1561.29110 |       1       |    219     |  0   |    3     |     4
    3    |-2.54088|-2.53830|317.34228 | 665.83744 |       2       |    147     |  2   |    7     |     8
    4    | 0.49708| 0.51116| 12.08355 | 665.83744 |       2       |     72     |  2   |          |
    5    |-7.48119|-7.52577| 12.38904 | 354.80922 |       3       |     76     |  1   |          |
    6    |-5.52057|-5.49197| 12.99436 | 354.80922 |       3       |     80     |  1   |          |
    7    |-1.56238|-1.50561| 12.63924 | 61.31633  |       4       |     73     |  3   |          |
    8    |-3.50616|-3.55703| 11.21015 | 61.31633  |       4       |     74     |  3   |          |
```

```


=======
Metrics
=======
                       Measure                       |  Value
-----------------------------------------------------+----------
                Total sum of squares                 |6008.46192
         Total within-cluster sum of squares         | 61.31633
           Between-cluster sum of squares            |5947.14559
Between-cluster sum of squares / Total sum of squares| 98.97950
      Sum of squares for cluster 1, center_id 5      | 12.38904
      Sum of squares for cluster 2, center_id 6      | 12.99436
      Sum of squares for cluster 3, center_id 7      | 12.63924
      Sum of squares for cluster 4, center_id 8      | 11.21015
      Sum of squares for cluster 5, center_id 4      | 12.08355


===========
call_string
===========
bisecting_kmeans('agar_dish_bkmeans', 'agar_dish_training', '*', 5
USING PARAMETERS exclude_columns='id', bisection_iterations=1, split_method='SUM_SQUARES', min_divisible_cluster_size=2, distance_method='euclidean', kmeans_center_init_method='kmeanspp', kmeans_epsilon=0.0001, kmeans_max_iterations=10, output_view=''agar_1_view'', key_columns=''id'')

===============
Additional Info
===============
        Name         |Value
---------------------+-----
   num_of_clusters   |  5
dimensions_of_dataset|  2
num_of_clusters_found|  5
  height_of_BKTree   |  4

(1 row)
```

在这里,我们可以看到训练过程中通过对 k-means 进行二等分创建的所有中间群集的详细信息、一些用于评估聚类质量的指标(平方和越低越好)、训练算法的具体参数,以及有关数据算法的一些常规信息。

使用二等分 k-means 模型对测试数据进行聚类

使用刚刚创建的二等分 k-means 模型 agar_dish_bkmeans,您可以将 agar_dish_testing 中的点分配给群集中心。

  1. 使用 agar_dish_testing 表作为输入表,使用 agar_dish_bkmeans 模型作为群集中心,创建一个名为 bkmeans_results 的表。仅向 APPLY_BISECTING_KMEANS 函数中的实参添加相关特征列。

    => CREATE TABLE bkmeans_results_k5 AS
            (SELECT id,
                    APPLY_BISECTING_KMEANS(x, y
                                 USING PARAMETERS
                                                  model_name='agar_dish_bkmeans', number_clusters=5) AS cluster_id
             FROM agar_dish_testing);
    => SELECT cluster_id, COUNT(cluster_id) as Total_count FROM bkmeans_results_k5 GROUP BY cluster_id;
     cluster_id | Total_count
    ------------+-------------
              5 |          24
              4 |          28
              6 |          20
              8 |          26
              7 |          27
    (5 rows)
    

    bkmeans_results_k5 表显示 agar_dish_bkmeans 模型对 agar_dish_testing 数据进行了正确聚类。

  2. 使用二等分 k-means 的真正优势在于,它创建的模型可以将数据聚类到小于或等于训练它的 k 的任意数量的群集中。现在,无需重新训练模型,您可以将上述测试数据聚类为 3 个群集而不是 5 个群集:

    => CREATE TABLE bkmeans_results_k3 AS
            (SELECT id,
                    APPLY_BISECTING_KMEANS(x, y
                                 USING PARAMETERS
                                                  model_name='agar_dish_bkmeans', number_clusters=3) AS cluster_id
             FROM agar_dish_testing);
    => SELECT cluster_id, COUNT(cluster_id) as Total_count FROM bkmeans_results_k3 GROUP BY cluster_id;
     cluster_id | Total_count
    ------------+-------------
              4 |          28
              3 |          53
              1 |          44
    (3 rows)
    

使用经过训练的二等分 k-means 模型进行预测

为了使用经过训练的模型对数据进行聚类,二等分 k-means 算法首先将传入数据点与根群集节点的子群集中心进行比较。该算法找出数据点与这些中心中的哪个中心离得最近。然后,将该数据点与离根最近的子节点的子群集中心进行比较。预测过程继续迭代,直到到达叶群集节点。最后,将该点分配给最近的叶群集。下图简单说明了二等分 k-means 算法的训练过程和预测过程。使用二等分 k-means 的一个优点是,您可以使用从 2 到训练模型的最大 k 值范围内的任何 k 值来进行预测。

下图中的模型使用 k=5 训练。中间的图显示如何使用模型预测 k=5 的情况,换句话说,在有 5 个叶群集的层次结构级别中,将传入的数据点匹配到具有最接近值的中心。右图显示如何使用模型预测 k=2 的情况,也就是说,先在只有两个群集的级别将传入的数据点与叶群集进行比较,然后将数据点匹配到这两个群集中心中较近的一个。这种方法比使用 k-means 进行预测快。

另请参阅

6.6 - 时间序列预测

时间序列模型在具有一致时间步长的随机过程的平稳时间序列(即平均值不随时间变化的时间序列)上进行训练。这些算法通过考虑一些先前时间步长(滞后)的值的影响来预测未来值。

适用数据集的示例包括温度、股票价格、地震、产品销售等数据集。

要标准化时间步长不一致的数据集,请参阅空白填充和插值 (GFI)

6.6.1 - 自回归模型示例

自动回归模型将根据先前的值来预测时间序列的未来值。更具体地说,用户指定的 lag 决定了该模型在计算过程中要考虑多少个先前的时间段,且预测值为每个 lag 的线性组合。

使用自回归模型进行训练和预测时使用以下函数。请注意,这些函数需要具有一致时间步长的数据集。

要标准化时间步长不一致的数据集,请参阅空白填充和插值 (GFI)

示例

  1. Machine-Learning-Examples 存储库加载数据集。

    此示例使用 daily-min-temperatures 数据集,其中包含 1981 年至 1990 年澳大利亚墨尔本每天的最低气温数据:

    => SELECT * FROM temp_data;
            time         | Temperature
    ---------------------+-------------
     1981-01-01 00:00:00 |        20.7
     1981-01-02 00:00:00 |        17.9
     1981-01-03 00:00:00 |        18.8
     1981-01-04 00:00:00 |        14.6
     1981-01-05 00:00:00 |        15.8
    ...
     1990-12-27 00:00:00 |          14
     1990-12-28 00:00:00 |        13.6
     1990-12-29 00:00:00 |        13.5
     1990-12-30 00:00:00 |        15.7
     1990-12-31 00:00:00 |          13
    (3650 rows)
    
  2. 使用 AUTOREGRESSORtemp_data 数据集创建自回归模型 AR_temperature。在本例中,模型以 p=3 的滞后进行训练,每次估计都要考虑前 3 个条目:

    => SELECT AUTOREGRESSOR('AR_temperature', 'temp_data', 'Temperature', 'time' USING PARAMETERS p=3);
                        AUTOREGRESSOR
    ---------------------------------------------------------
     Finished. 3650 elements accepted, 0 elements rejected.
    (1 row)
    

    您可以使用 GET_MODEL_SUMMARY 查看模型的摘要:

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='AR_temperature');
    
     GET_MODEL_SUMMARY
    -------------------
    
    ============
    coefficients
    ============
    parameter| value
    ---------+--------
      alpha  | 1.88817
    phi_(t-1)| 0.70004
    phi_(t-2)|-0.05940
    phi_(t-3)| 0.19018
    
    
    ==================
    mean_squared_error
    ==================
    not evaluated
    
    ===========
    call_string
    ===========
    autoregressor('public.AR_temperature', 'temp_data', 'temperature', 'time'
    USING PARAMETERS p=3, missing=linear_interpolation, regularization='none', lambda=1, compute_mse=false);
    
    ===============
    Additional Info
    ===============
           Name       |Value
    ------------------+-----
        lag_order     |  3
    rejected_row_count|  0
    accepted_row_count|3650
    (1 row)
    
  3. 使用 PREDICT_AUTOREGRESSOR 预测未来温度。以下查询在数据集末尾start预测,并返回 10 个预测。

    => SELECT PREDICT_AUTOREGRESSOR(Temperature USING PARAMETERS model_name='AR_temperature', npredictions=10) OVER(ORDER BY time) FROM temp_data;
    
        prediction
    ------------------
     12.6235419917807
     12.9387860506032
     12.6683380680058
     12.3886937385419
     12.2689506237424
     12.1503023330142
     12.0211734746741
     11.9150531529328
     11.825870404008
     11.7451846722395
    (10 rows)
    

6.6.2 - 移动平均模型示例

移动平均值模型使用早期预测误差进行未来预测。更具体地说,用户指定的 lag 决定了它在计算过程中要考虑多少先前的预测和错误。

使用移动平均模型进行训练和预测时使用以下函数。请注意,这些函数需要具有一致时间步长的数据集。

要标准化时间步长不一致的数据集,请参阅空白填充和插值 (GFI)

示例

  1. Machine-Learning-Examples 存储库加载数据集。

    此示例使用 daily-min-temperatures 数据集,其中包含 1981 年至 1990 年澳大利亚墨尔本每天的最低气温数据:

    => SELECT * FROM temp_data;
            time         | Temperature
    ---------------------+-------------
     1981-01-01 00:00:00 |        20.7
     1981-01-02 00:00:00 |        17.9
     1981-01-03 00:00:00 |        18.8
     1981-01-04 00:00:00 |        14.6
     1981-01-05 00:00:00 |        15.8
    ...
     1990-12-27 00:00:00 |          14
     1990-12-28 00:00:00 |        13.6
     1990-12-29 00:00:00 |        13.5
     1990-12-30 00:00:00 |        15.7
     1990-12-31 00:00:00 |          13
    (3650 rows)
    
  2. 使用 MOVING_AVERAGEtemp_data 数据集创建移动平均模型 MA_temperature。在本例中,模型以 p=3 的滞后进行训练,每次估计都要考虑 3 个以前预测的错误:

    => SELECT MOVING_AVERAGE('MA_temperature', 'temp_data', 'temperature', 'time' USING PARAMETERS q=3, missing='linear_interpolation', regularization='none', lambda=1);
                        MOVING_AVERAGE
    ---------------------------------------------------------
     Finished. 3650 elements accepted, 0 elements rejected.
    (1 row)
    

    您可以使用 GET_MODEL_SUMMARY 查看模型的摘要:

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='MA_temperature');
    
     GET_MODEL_SUMMARY
    -------------------
    
    ============
    coefficients
    ============
    parameter| value
    ---------+--------
    phi_(t-0)|-0.90051
    phi_(t-1)|-0.10621
    phi_(t-2)| 0.07173
    
    
    ===============
    timeseries_name
    ===============
    temperature
    
    ==============
    timestamp_name
    ==============
    time
    
    ===========
    call_string
    ===========
    moving_average('public.MA_temperature', 'temp_data', 'temperature', 'time'
    USING PARAMETERS q=3, missing=linear_interpolation, regularization='none', lambda=1);
    
    ===============
    Additional Info
    ===============
           Name       | Value
    ------------------+--------
           mean       |11.17780
        lag_order     |   3
          lambda      | 1.00000
    rejected_row_count|   0
    accepted_row_count|  3650
    
    (1 row)
    
  3. 使用 PREDICT_MOVING_AVERAGE 预测未来温度。以下查询在数据集末尾start预测,并返回 10 个预测。

    => SELECT PREDICT_MOVING_AVERAGE(Temperature USING PARAMETERS model_name='MA_temperature', npredictions=10) OVER(ORDER BY time) FROM temp_data;
    
        prediction
    ------------------
     13.1324365636272
     12.8071086272833
     12.7218966671721
     12.6011086656032
     12.506624729879
     12.4148247026733
     12.3307873804812
     12.2521385975133
     12.1789741993396
     12.1107640076638
    (10 rows)
    

6.7 - 模型管理

此部分中的主题描述了如何管理模型。

6.7.1 - 更改模型

您可以使用 ALTER MODEL 修改模型,以响应模型的需要。您可以通过重命名模型、更改所有者和更改架构来更改模型。

您可以删除或更改您创建的任何模型。

6.7.1.1 - 更改模型所有权

作为超级用户或模型所有者,您可以使用 ALTER MODEL 重新分配模型所有权,如下所示:

ALTER MODEL model-name OWNER TO owner-name

当模型所有者离职或更改职责时,更改模型所有权很有用。由于您可以更改所有者,因此不需要重写模型。

示例

以下示例演示了如何使用 ALTER_MODEL 更改模型所有者:

  1. 查找您要更改的模型。作为 dbadmin,您拥有模型。

    => SELECT * FROM V_CATALOG.MODELS WHERE model_name='mykmeansmodel';
    -[ RECORD 1 ]--+------------------------------------------
    model_id       | 45035996273816618
    model_name     | mykmeansmodel
    schema_id      | 45035996273704978
    schema_name    | public
    owner_id       | 45035996273704962
    owner_name     | dbadmin
    category       | VERTICA_MODELS
    model_type     | kmeans
    is_complete    | t
    create_time    | 2017-03-02 11:16:04.990626-05
    size           | 964
    
  2. 将模型所有者从 dbadmin 更改为 user1。

    => ALTER MODEL mykmeansmodel OWNER TO user1;
         ALTER MODEL
    
  3. 查看 V_CATALOG.MODELS 以验证所有者是否已更改。

    => SELECT * FROM V_CATALOG.MODELS WHERE model_name='mykmeansmodel';
    
    
    -[ RECORD 1 ]--+------------------------------------------
    model_id       | 45035996273816618
    model_name     | mykmeansmodel
    schema_id      | 45035996273704978
    schema_name    | public
    owner_id       | 45035996273704962
    owner_name     | user1
    category       | VERTICA_MODELS
    model_type     | kmeans
    is_complete    | t
    create_time    | 2017-03-02 11:16:04.990626-05
    size           | 964
    

6.7.1.2 - 将模型移到另一个架构

您可以使用 ALTER MODEL 将模型从一个架构移动到另一个架构。您可以以超级用户或对当前架构拥有 USAGE 权限且对目标架构拥有 CREATE 权限的用户的身份来移动模型。

示例

以下示例演示了如何使用 ALTER MODEL 更改模型架构:

  1. 查找您要更改的模型。

    => SELECT * FROM V_CATALOG.MODELS WHERE model_name='mykmeansmodel';
    -[ RECORD 1 ]--+------------------------------------------
    model_id       | 45035996273816618
    model_name     | mykmeansmodel
    schema_id      | 45035996273704978
    schema_name    | public
    owner_id       | 45035996273704962
    owner_name     | dbadmin
    category       | VERTICA_MODELS
    model_type     | kmeans
    is_complete    | t
    create_time    | 2017-03-02 11:16:04.990626-05
    size           | 964
    
  2. 更改模型架构。

    => ALTER MODEL mykmeansmodel SET SCHEMA test;
         ALTER MODEL
    
  3. 查看 V_CATALOG.MODELS 以验证所有者是否已更改。

    => SELECT * FROM V_CATALOG.MODELS WHERE model_name='mykmeansmodel';
    
    
    -[ RECORD 1 ]--+------------------------------------------
    model_id       | 45035996273816618
    model_name     | mykmeansmodel
    schema_id      | 45035996273704978
    schema_name    | test
    owner_id       | 45035996273704962
    owner_name     | dbadmin
    category       | VERTICA_MODELS
    model_type     | kmeans
    is_complete    | t
    create_time    | 2017-03-02 11:16:04.990626-05
    size           | 964
    

6.7.1.3 - 重命名模型

ALTER MODEL 允许您重命名模型。例如:

  1. 查找您要更改的模型。

    => SELECT * FROM V_CATALOG.MODELS WHERE model_name='mymodel';
    -[ RECORD 1 ]--+------------------------------------------
    model_id       | 45035996273816618
    model_name     | mymodel
    schema_id      | 45035996273704978
    schema_name    | public
    owner_id       | 45035996273704962
    owner_name     | dbadmin
    category       | VERTICA_MODELS
    model_type     | kmeans
    is_complete    | t
    create_time    | 2017-03-02 11:16:04.990626-05
    size           | 964
    
  2. 重命名模型。

    => ALTER MODEL mymodel RENAME TO mykmeansmodel;
         ALTER MODEL
    
  3. 查看 V_CATALOG.MODELS 以验证模型名称是否已更改。

    => SELECT * FROM V_CATALOG.MODELS WHERE model_name='mykmeansmodel';
    
    
    -[ RECORD 1 ]--+------------------------------------------
    model_id       | 45035996273816618
    model_name     | mykmeansmodel
    schema_id      | 45035996273704978
    schema_name    | public
    owner_id       | 45035996273704962
    owner_name     | dbadmin
    category       | VERTICA_MODELS
    model_type     | kmeans
    is_complete    | t
    create_time    | 2017-03-02 11:16:04.990626-05
    size           | 964
    

6.7.2 - 删除模型

DROP MODEL 从数据库中移除一个或多个模型。例如:

  1. 查找您要删除的模型。

    => SELECT * FROM V_CATALOG.MODELS WHERE model_name='mySvmClassModel';
    -[ RECORD 1 ]--+--------------------------------
    model_id       | 45035996273765414
    model_name     | mySvmClassModel
    schema_id      | 45035996273704978
    schema_name    | public
    owner_id       | 45035996273704962
    owner_name     | dbadmin
    category       | VERTICA_MODELS
    model_type     | SVM_CLASSIFIER
    is_complete    | t
    create_time    | 2017-02-14 10:30:44.903946-05
    size           | 525
    
  2. 删除模型。

    => DROP MODEL mySvmClassModel;
         DROP MODEL
    
  3. 查看 V_CATALOG.MODELS 以验证模型是否已删除。

    => SELECT * FROM V_CATALOG.MODELS WHERE model_name='mySvmClassModel';
    (0 rows)
    

6.7.3 - 管理模型安全

您可以使用 GRANT 和 REVOKE 语句来管理模型的安全权限。以下示例展示了如何使用 faithful 表和 linearReg 模型更改 user1 和 user2 的权限。

  • 在以下示例中,dbadmin 将 SELECT 权限授予 user1:

    => GRANT SELECT ON TABLE faithful TO user1;
    
    GRANT PRIVILEGE
    
  • 然后,dbadmin 将公共架构的 CREATE 权限授予 user1:

    => GRANT CREATE ON SCHEMA public TO user1;
    GRANT PRIVILEGE
    
  • 以 user1 身份连接到数据库:

    => \c - user1
    
  • 以 user1 身份构建 linearReg 模型:

=> SELECT LINEAR_REG('linearReg', 'faithful', 'waiting', 'eruptions');
LINEAR_REG
---------------------------
Finished in 1 iterations
(1 row)
  • 以 user1 身份将 USAGE 权限授予 user2:
=> GRANT USAGE ON MODEL linearReg TO user2;
GRANT PRIVILEGE
  • 以 user2 身份连接到数据库:
=> \c - user2
  • 要确认权限已授予 user2,请运行 GET_MODEL_SUMMARY 函数。对模型具有 USAGE 权限的用户可以在该模型上运行 GET_MODEL_SUMMARY:
=> SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='linearReg');

=======
details
=======
predictor|coefficient|std_err |t_value |p_value
---------+-----------+--------+--------+--------
Intercept| 33.47440  | 1.15487|28.98533| 0.00000
eruptions| 10.72964  | 0.31475|34.08903| 0.00000

==============
regularization
==============
type| lambda
----+--------
none| 1.00000

===========
call_string
===========
linear_reg('public.linearReg', 'faithful', '"waiting"', 'eruptions'
USING PARAMETERS optimizer='newton', epsilon=1e-06, max_iterations=100, regularization='none', lambda=1)

===============
Additional Info
===============
Name              |Value
------------------+-----
iteration_count   |  1
rejected_row_count|  0
accepted_row_count| 272
(1 row)
  • 以 user1 身份连接到数据库:
=> \c - user1
  • 然后,您可以使用 REVOKE 语句撤销 user2 的权限:
=> REVOKE USAGE ON MODEL linearReg FROM user2;
REVOKE PRIVILEGE
  • 要确认权限已撤销,请以用户 2 身份连接并运行 GET_MODEL_SUMMARY 函数:
=> \c - user2
=>SELECT GET_MODEL_SUMMARY('linearReg');
ERROR 7523:  Problem in get_model_summary.
Detail: Permission denied for model linearReg

另请参阅

6.7.4 - 查看模型属性

以下主题介绍了 Vertica 机器学习算法的模型属性。这些属性描述了特定模型的内部结构:

6.7.5 - 总结模型

  1. 查找您要总结的模型。

    => SELECT * FROM v_catalog.models WHERE model_name='svm_class';
    model_id      | model_name |     schema_id   | schema_name |     owner_id      | owner_name |    category    |
    model_type   | is_complete |     create_time          | size
    -------------------+------------+-------------------+-------------+-------------------+------------+--------
    --------+----------------+-------------+-------------------------------+------
    45035996273715226 | svm_class  | 45035996273704980 | public      | 45035996273704962 | dbadmin    | VERTICA_MODELS
    | SVM_CLASSIFIER | t           | 2017-08-28 09:49:00.082166-04 | 1427
    (1 row)
    
  2. 查看模型摘要。

    
    => SELECT GET_MODEL_SUMMARY(USING PARAMETERS model_name='svm_class');
    ------------------------------------------------------------------------
    
    =======
    details
    =======
    predictor|coefficient
    ---------+-----------
    Intercept| -0.02006
    cyl      |  0.15367
    mpg      |  0.15698
    wt       | -1.78157
    hp       |  0.00957
    
    ===========
    call_string
    ===========
    SELECT svm_classifier('public.svm_class', 'mtcars_train', '"am"', 'cyl, mpg, wt, hp, gear'
    USING PARAMETERS exclude_columns='gear', C=1, max_iterations=100, epsilon=0.001);
    
    ===============
    Additional Info
    ===============
    Name              |Value
    ------------------+-----
    accepted_row_count| 20
    rejected_row_count|  0
    iteration_count   | 12
    (1 row)
    

另请参阅

6.7.6 - 查看模型

Vertica 将您创建的模型存储在 V_CATALOG.MODELS 系统表中。

您可以查询 V_CATALOG.MODELS 来查看您创建的模型的信息:

=> SELECT * FROM V_CATALOG.MODELS;
-[ RECORD 1 ]--+------------------------------------------
model_id       | 45035996273765414
model_name     | mySvmClassModel
schema_id      | 45035996273704978
schema_name    | public
owner_id       | 45035996273704962
owner_name     | dbadmin
category       | VERTICA_MODELS
model_type     | SVM_CLASSIFIER
is_complete    | t
create_time    | 2017-02-14 10:30:44.903946-05
size           | 525
-[ RECORD 2 ]--+------------------------------------------

model_id       | 45035996273711466
model_name     | mtcars_normfit
schema_id      | 45035996273704978
schema_name    | public
owner_id       | 45035996273704962
owner_name     | dbadmin
category       | VERTICA_MODELS
model_type     | SVM_CLASSIFIER
is_complete    | t
create_time    | 2017-02-06 15:03:05.651941-05
size           | 288

另请参阅

6.8 - 在 Vertica 中使用外部模型

为了让您在机器学习方面获得最大的灵活性和可扩展性,Vertica 支持使用 PMML 和 TensorFlow 模型进行导入、导出和预测。

机器学习配置参数 MaxModelSizeKB 设置可以导入 Vertica 的模型的最大大小。

对 PMML 模型的支持

Vertica 支持以预测模型标记语言 (PMML) 格式导入和导出 K-means、线性回归和逻辑回归机器学习模型。借助对这种独立于平台的模型格式的支持,您可以使用其他平台上训练的模型来预测 Vertica 数据库中存储的数据。您还可以使用 Vertica 作为模型存储库。Vertica 支持 PMML 4.4 版

通过 PREDICT_PMML 函数,您可以使用在 Vertica 中存档的 PMML 模型对 Vertica 数据库中存储的数据运行预测。

有关详细信息,请参阅使用 PMML 模型

有关 Vertica 当前支持和不支持的 PMML 属性的详细信息,请参阅PMML 特征和属性

对 TensorFlow 模型的支持

Vertica 现在支持导入经过训练的 TensorFlow 模型,并使用这些模型在 Vertica 中对 Vertica 数据库中存储的数据进行预测。Vertica 支持在 TensorFlow 1.15 版中训练的 TensorFlow 模型。

PREDICT_TENSORFLOW 函数可让您使用任何 TensorFlow 模型预测 Vertica 中的数据。

有关其他信息,请参阅TensorFlow 模型

支持外部模型的其他函数

以下函数同时支持 PMML 和 TensorFlow 模型:

IMPORT_MODELS EXPORT_MODELS GET_MODEL_ATTRIBUTE GET_MODEL_SUMMARY

6.8.1 - TensorFlow 模型

Tensorflow 是一个用于创建神经网络的框架。它以可扩展的方式实现基本的线性代数和多变量微积分运算,使用户可以轻松地将这些运算链接到计算图中。

Vertica 支持使用在 Vertica 外部训练的 TensorFlow 1.x 和 2.x 模型导入、导出和进行预测。

数据库内 TensorFlow 与 Vertica 的集成提供了几点好处:

  • 您的模型存在于您的数据库中,因此您无需移动数据来进行预测。

  • 您可以处理的数据量仅受 Vertica 数据库大小的限制,这使得 Vertica 特别适合大数据机器学习。

  • Vertica 提供数据库内模型管理,因此您可以存储任意数量的模型。

  • 导入的模型是便携式的,可以导出以在其他地方使用。

当您运行 TensorFlow 模型以预测数据库中的数据时,Vertica 会调用 TensorFlow 进程来运行模型。这使 Vertica 能够支持您使用 TensorFlow 创建和训练的任何模型。Vertica 仅提供输入(您在 Vertica 数据库中的数据)并存储输出。

6.8.1.1 - TensorFlow 集成和目录结构

本页介绍如何将 Tensorflow 模型导入 Vertica、对 Vertica 数据库中的数据进行预测、导出模型以使其可在其他 Vertica 群集或第三方平台上使用。

有关每个操作的从头到尾的示例,请参阅 TensorFlow 示例

Vertica 支持借助于 TensorFlow 1.x 和 2.x 创建的模型,但强烈建议使用 2.x 来创建模型。

要将 TensorFlow 与 Vertica 结合使用,请在任何节点上安装 TFIntegration UDX 包。您只需执行一次该操作:

$ /opt/vertica/bin/admintools -t install_package -d database_name -p 'password' --package TFIntegration

TensorFlow 模型的目录和文件结构

在导入模型之前,您应当为每个模型创建一个单独的目录,其中包含以下各项之一。请注意,Vertica 在导入时使用目录名称作为模型名称:

  • model_name.pb:冻结图格式的训练模型

  • tf_model_desc.json:模型的描述

您可以使用 Machine-Learning-Examples/TensorFlow 存储库(或 opt/vertica/packages/TFIntegration/examples)中包含的脚本 freeze_tf2_model.py 来为给定的 TensorFlow 2 模型生成两个文件,但您可能需要根据您的用例以及您希望 Vertica 数据库与模型交互的方式对描述进行修改:

$ python3 Tensorflow/freeze_tf2_model.py your/model/directory

例如,tf_models 目录包含两个模型 tf_mnist_estimatortf_mnist_keras

tf_models/
├── tf_mnist_estimator
│   ├── mnist_estimator.pb
│   └── tf_model_desc.json
└── tf_mnist_keras
    ├── mnist_keras.pb
    └── tf_model_desc.json

tf_model_desc.json

tf_model_desc.json 文件构成了 TensorFlow 和 Vertica 之间的桥梁。它描述了模型的结构,以便 Vertica 可以将其输入和输出正确匹配到输入/输出表。

请注意,freeze_tf2_model.py 脚本会自动为您的模型生成此文件,并且此生成的文件通常可以按原样使用。对于较复杂的模型或用例,您可能必须编辑此文件。有关每个字段的详细分类,请参阅tf_model_desc.json 概述

将 TensorFlow 模型导入 Vertica

要导入 TensorFlow 模型,请使用 IMPORT_MODELS 和类别 'TENSORFLOW'

导入单个模型。请记住,Vertica 数据库使用目录名称作为模型名称:

select IMPORT_MODELS ( '/path/tf_models/tf_mnist_keras' USING PARAMETERS category='TENSORFLOW');
 import_models
---------------
 Success
(1 row)

使用通配符 (*) 导入目录中的所有模型(每个模型都有自己的目录):

select IMPORT_MODELS ('/path/tf_models/*' USING PARAMETERS category='TENSORFLOW');
 import_models
---------------
 Success
(1 row)

使用导入的 TensorFlow 模型进行预测

导入 TensorFlow 模型后,您可以使用该模型来预测 Vertica 表中的数据。

PREDICT_TENSORFLOW 函数与其他预测函数的不同之处在于它不接受任何影响输入列的参数,例如 "exclude_columns" 或 "id_column";相反,该函数总是对提供的所有输入列进行预测。但是,它确实接受 num_passthru_cols 参数,该参数允许用户“跳过”一些输入列,如下所示。

OVER(PARTITION BEST) 子句告知 Vertica 跨多个节点并行化操作。有关详细信息,请参阅窗口分区子句

=> select PREDICT_TENSORFLOW (*
                   USING PARAMETERS model_name='tf_mnist_keras', num_passthru_cols=1)
                   OVER(PARTITION BEST) FROM tf_mnist_test_images;

--example output, the skipped columns are displayed as the first columns of the output
 ID | col0 | col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8 | col9
----+------+------+------+------+------+------+------+------+------+------
  1 |    0 |    0 |    1 |    0 |    0 |    0 |    0 |    0 |    0 |    0
  3 |    1 |    0 |    0 |    0 |    0 |    0 |    0 |    0 |    0 |    0
  6 |    0 |    0 |    0 |    0 |    1 |    0 |    0 |    0 |    0 |    0
...

导出 TensorFlow 模型

Vertica 将模型导出为冻结图,然后可以随时重新导入。请记住,保存为冻结图的模型无法进一步训练。

使用 EXPORT_MODELS 导出 TensorFlow 模型。例如,要将 tf_mnist_keras 模型导出到 /path/to/export/to 目录:

=> SELECT EXPORT_MODELS ('/path/to/export/to', 'tf_mnist_keras');
 export_models
---------------
 Success
(1 row)

导出 TensorFlow 模型时,Vertica 数据库会创建并使用指定的目录来存储描述模型的文件:

$ ls tf_mnist_keras/
crc.json  metadata.json  mnist_keras.pb  model.json  tf_model_desc.json

.pbtf_model_desc.json 文件描述了模型,其余文件是由 Vertica 数据库创建的组织文件。

另请参阅

6.8.1.2 - TensorFlow 示例

Vertica 使用 TFIntegration UDX 包与 TensorFlow 集成。您可以在 Vertica 数据库之外训练模型,然后将它们导入 Vertica 并对数据进行预测。

TensorFlow 脚本和数据集包含在 Machine-Learning-Examples/TensorFlow 下的 GitHub 存储库中。

下面的示例创建一个在 MNIST 手写数字分类数据集上训练的 Keras(一种 TensorFlow API)神经网络模型,其各层如下所示。

数据从上到下穿过每一层,每一层在返回分数之前修改输入。在此示例中,传入的数据是一组手写阿拉伯数字图像,输出内容是输入图像为特定数字的概率:

inputs = keras.Input(shape=(28, 28, 1), name="image")
x = layers.Conv2D(32, 5, activation="relu")(inputs)
x = layers.MaxPooling2D(2)(x)
x = layers.Conv2D(64, 5, activation="relu")(x)
x = layers.MaxPooling2D(2)(x)
x = layers.Flatten()(x)
x = layers.Dense(10, activation='softmax', name='OUTPUT')(x)
tfmodel = keras.Model(inputs, x)

有关 TensorFlow 如何与 Vertica 数据库交互以及如何导入更复杂模型的详细信息,请参阅TensorFlow 集成和目录结构

为 Vertica 准备 TensorFlow 模型

以下过程在 Vertica 之外进行。

训练和保存 TensorFlow 模型

  1. 安装 TensorFlow 2

  2. 训练您的模型。对于此特定示例,您可以运行 train_simple_model.py 来训练和保存模型。否则,一般而言,您可以在 Python 中手动训练您的模型,然后保存它

    $ mymodel.save('my_saved_model_dir')
    
  3. 运行 Machine-Learning-Examples 存储库opt/vertica/packages/TFIntegration/examples 中包含的 freeze_tf2_model.py 脚本,指定您的模型和输出目录(可选,默认为 frozen_tfmodel)。

    此脚本将您保存的模型转换为与 Vertica 兼容的冻结图格式并创建 tf_model_desc.json 文件,该文件描述了 Vertica 应如何将其表转换为 TensorFlow 张量:

    $ ./freeze_tf2_model.py path/to/tf/model frozen_model_dir
    

导入 TensorFlow 模型并在 Vertica 中进行预测

  1. 如果您尚未安装 TFIntegration UDX 包,请以 dbadmin 身份在任何节点上安装。您只需执行一次该操作。

    $ /opt/vertica/bin/admintools -t install_package -d database_name -p 'password' --package TFIntegration
    

  2. directory 复制到 Vertica 群集中的任何节点并导入模型:

    => SELECT IMPORT_MODELS('path/to/frozen_model_dir' USING PARAMETERS category='TENSORFLOW');
    
  3. 导入要对其进行预测的数据集。对于此示例:

    1. Machine-Learning-Examples/TensorFlow/data 目录复制到 Vertica 群集中的任何节点。

    2. 从该 data 目录中,运行 SQL 脚本 load_tf_data.sql 以加载 MNIST 数据集:

      $ vsql -f load_tf_data.sql
      
  4. 使用 PREDICT_TENSORFLOW 在数据集上使用您的模型进行预测。在此示例中,该模型用于对 MNIST 数据集中的手写数字图像进行分类:

    => SELECT PREDICT_TENSORFLOW (*
                       USING PARAMETERS model_name='tf_mnist_keras', num_passthru_cols=1)
                       OVER(PARTITION BEST) FROM tf_mnist_test_images;
    
    --example output, the skipped columns are displayed as the first columns of the output
     ID | col0 | col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8 | col9
    ----+------+------+------+------+------+------+------+------+------+------
      1 |    0 |    0 |    1 |    0 |    0 |    0 |    0 |    0 |    0 |    0
      3 |    1 |    0 |    0 |    0 |    0 |    0 |    0 |    0 |    0 |    0
      6 |    0 |    0 |    0 |    0 |    1 |    0 |    0 |    0 |    0 |    0
    ...
    
  5. 要使用 EXPORT_MODELS 导出模型:

    => SELECT EXPORT_MODELS('/path/to/export/to', 'tf_mnist_keras');
     EXPORT_MODELS
    ---------------
     Success
    (1 row)
    

TensorFlow 1(已弃用)

  1. 使用 Python 3.7 或更低版本安装 TensorFlow 1.15

  2. Machine-Learning-Examples/TensorFlow/tf1 中运行 train_save_model.py 以训练模型并将其保存为 TensorFlow 和冻结图格式。

另请参阅

6.8.1.3 - tf_model_desc.json 概述

在导入外部训练的 TensorFlow 模型之前,您必须:

  • 将模型保存为冻结图 (.pb) 格式

  • 创建 tf_model_desc.json,向 Vertica 数据库描述如何将其输入和输出映射到输入/输出表

方便的是,Machine-Learning-Examples 存储库的 TensorFlow 目录中(和opt/vertica/packages/TFIntegration/examples中)包含的脚本 freeze_tf2_model.py 会自动完成这两项工作。在大多数情况下,生成的 tf_model_desc.json 可以按原样使用,但对于较复杂的数据集和用例,您可能需要对其进行编辑。

用于示例数据集的 tf_model_desc.json

下面的 tf_model_desc.json 是从 TensorFlow 示例 使用的 MNIST 手写数据集生成的。

{
    "frozen_graph": "mnist_keras.pb",
    "input_desc": [
        {
            "op_name": "image_input",
            "tensor_map": [
                {
                    "idx": 0,
                    "dim": [
                        1,
                        28,
                        28,
                        1
                    ],
                    "col_start": 0
                }
            ]
        }
    ],
    "output_desc": [
        {
            "op_name": "OUTPUT/Softmax",
            "tensor_map": [
                {
                    "idx": 0,
                    "dim": [
                        1,
                        10
                    ],
                    "col_start": 0
                }
            ]
        }
    ]
}

该文件描述了模型输入和输出的结构。它必须包含一个与 .pb 模型的文件名匹配的 frozen_graph 字段、一个 input_desc 字段和一个 output_desc 字段。

  • input_descoutput_desc:TensorFlow 图中输入和输出节点的描述。其中每一个都包含以下字段:
    • op_name:创建和训练模型时设置的操作节点名称。您通常可以从 tfmodel.inputstfmodel.outputs 检索这些参数的名称。例如:

      
      $ print({t.name:t for t in tfmodel.inputs})
      {'image_input:0': <tf.Tensor 'image_input:0' shape=(?, 28, 28, 1) dtype=float32>}
      
      $ print({t.name:t for t in tfmodel.outputs})
      {'OUTPUT/Softmax:0': <tf.Tensor 'OUTPUT/Softmax:0' shape=(?, 10) dtype=float32>}
      

      在本例中,op_name 的相应值如下。

      • input_descimage_input

      • output_descOUTPUT/Softmax

      有关此过程的更详细示例,请查看 freeze_tf2_model.py 的代码。

    • tensor_map:如何将张量映射到可通过以下方式指定的 Vertica 列:

      • idx:张量的索引(第一个输入/输出应为 0,第二个输入/输出应为 1,等等)

      • dim:保存张量维度的向量;它提供了列数

      • col_start(仅在未指定 col_idx 时使用):起始列索引。当与 dim 一起使用时,它指定从 col_start 开始、在 col_start+dim 结束的 Vertica 列的索引范围;Vertica 从索引 col_start 指定的列处开始,获取接下来的 dim

      • col_idx:Vertica 列中对应于修整张量的索引。这允许您显式指定 Vertica 列的索引,否则无法使用 col_startdim 将其指定为简单范围(例如 1、3、5、7)

      • data_type(未显示):输入或输出的数据类型,为以下之一:

        • TF_FLOAT(默认值)

        • TF_DOUBLE

        • TF_INT8

        • TF_INT16

        • TF_INT32

        • TF_INT64

复杂 tf_model_desc.json 示例

下面是一个包含多个输入和输出的较复杂示例:

{
    "input_desc": [
        {
            "op_name": "input1",
            "tensor_map": [
                {
                    "idx": 0,
                    "dim": [
                        4
                    ],
                    "col_idx": [
                        0,
                        1,
                        2,
                        3
                    ]
                },
                {
                    "idx": 1,
                    "dim": [
                        2,
                        2
                    ],
                    "col_start": 4
                }
            ]
        },
        {
            "op_name": "input2",
            "tensor_map": [
                {
                    "idx": 0,
                    "dim": [],
                    "col_idx": [
                        8
                    ]
                },
                {
                    "idx": 1,
                    "dim": [
                        2
                    ],
                    "col_start": 9
                }
            ]
        }
    ],
    "output_desc": [
        {
            "op_name": "output",
            "tensor_map": [
                {
                    "idx": 0,
                    "dim": [
                        2
                    ],
                    "col_start": 0
                }
            ]
        }
    ]
}

另请参阅

6.8.2 - 使用 PMML 模型

Vertica 可以使用 4.4 版及以下版本的 PMML 模型导入、导出和进行预测。

6.8.2.1 - 以 PMML 格式导出 Vertica 模型

您可以利用 Vertica 中的内置分布式算法来训练机器学习模型。在某些情况下,您可能希望在 Vertica 之外(例如在边缘节点上)使用这些模型进行预测。现在,您可以将模型导出为 PMML 格式,并使用导出后的模型通过一个支持读取和评估 PMML 模型的库或平台进行预测。

这里有一个在 Vertica 中训练模型后以 PMML 格式导出的示例。下图显示了示例的工作流程。我们使用 vsql 来运行该示例。

假设您想在名为“患者”的关系中训练数据的逻辑回归模型,以预测患者在接受治疗后的第二次发作和特质焦虑。

训练后,模型显示在名为 V_CATALOG.MODELS 的系统表中,其中列出了 Vertica 中存档的 ML 模型。

=> -- Training a logistic regression model on a training_data
=> SELECT logistic_reg('myModel', 'patients', 'second_attack', 'treatment, trait_anxiety');
       logistic_reg
---------------------------
 Finished in 5 iterations

(1 row)

=> -- Looking at the models table
=> SELECT model_name, schema_name, category, model_type, create_time, size FROM models;
 model_name | schema_name |    category    |     model_type      |          create_time          | size
------------+-------------+----------------+---------------------+-------------------------------+------
 myModel    | public      | VERTICA_MODELS | LOGISTIC_REGRESSION | 2020-07-28 00:05:18.441958-04 | 1845
(1 row)

可以使用 GET_MODEL_SUMMARY 函数来查看模型摘要。

=> -- Looking at the summary of the model
=> \t
Showing only tuples.
=> SELECT get_model_summary(USING PARAMETERS model_name='myModel');


=======
details
=======
  predictor  |coefficient|std_err |z_value |p_value
-------------+-----------+--------+--------+--------
  Intercept  | -6.36347  | 3.21390|-1.97998| 0.04771
  treatment  | -1.02411  | 1.17108|-0.87450| 0.38185
trait_anxiety|  0.11904  | 0.05498| 2.16527| 0.03037


==============
regularization
==============
type| lambda
----+--------
none| 1.00000


===========
call_string
===========
logistic_reg('public.myModel', 'patients', '"second_attack"', 'treatment, trait_anxiety'
USING PARAMETERS optimizer='newton', epsilon=1e-06, max_iterations=100, regularization='none', lambda=1, alpha=0.5)

===============
Additional Info
===============
       Name       |Value
------------------+-----
 iteration_count  |  5
rejected_row_count|  0
accepted_row_count| 20

您还可以使用 GET_MODEL_ATTRIBUTE 函数检索模型的属性。

=> \t
Tuples only is off.
=> -- The list of the attributes of the model
=> SELECT get_model_attribute(USING PARAMETERS model_name='myModel');
     attr_name      |                    attr_fields                    | #_of_rows
--------------------+---------------------------------------------------+-----------
 details            | predictor, coefficient, std_err, z_value, p_value |         3
 regularization     | type, lambda                                      |         1
 iteration_count    | iteration_count                                   |         1
 rejected_row_count | rejected_row_count                                |         1
 accepted_row_count | accepted_row_count                                |         1
 call_string        | call_string                                       |         1
(6 rows)

=> -- Returning the coefficients of the model in a tabular format
=> SELECT get_model_attribute(USING PARAMETERS model_name='myModel', attr_name='details');
   predictor   |    coefficient    |      std_err       |      z_value       |      p_value
---------------+-------------------+--------------------+--------------------+--------------------
 Intercept     | -6.36346994178182 |   3.21390452471434 |  -1.97998101463435 | 0.0477056620380991
 treatment     | -1.02410605239327 |    1.1710801464903 | -0.874496980810833 |  0.381847663704613
 trait_anxiety | 0.119044916668605 | 0.0549791755747139 |   2.16527285875412 | 0.0303667955962211
(3 rows)

您可以在一个简单的语句中使用 EXPORT_MODELS 函数将模型导出为 PMML 格式,如下所示。


=> -- Exporting the model as PMML
=> SELECT export_models('/data/username/temp', 'myModel' USING PARAMETERS category='PMML');
 export_models
---------------
 Success
(1 row)

另请参阅

6.8.2.2 - 使用 PMML 模型导入和预测

作为 Vertica 用户,您可以在其他平台上训练 ML 模型,将其转换为标准 PMML 格式,然后将其导入 Vertica 以对存储在 Vertica 关系中的数据进行数据库内预测。

这里有一个如何导入在 Spark 中训练的 PMML 模型的示例。下图显示了示例的工作流程。

您可以在一个简单的语句中使用 IMPORT_MODELS 函数来导入 PMML 模型。导入的模型随后会出现在名为 V_CATALOG.MODELS 的系统表中,其中列出了 Vertica 中存档的 ML 模型。

=> -- importing the PMML model trained and generated in Spark
=> SELECT import_models('/data/username/temp/spark_logistic_reg' USING PARAMETERS category='PMML');
 import_models
---------------
 Success
(1 row)

=> -- Looking at the models table=> SELECT model_name, schema_name, category, model_type, create_time, size FROM models;
     model_name     | schema_name | category |      model_type       |          create_time          | size
--------------------+-------------+----------+-----------------------+-------------------------------+------
 spark_logistic_reg | public      | PMML     | PMML_REGRESSION_MODEL | 2020-07-28 00:12:29.389709-04 | 5831
(1 row)

可以使用 GET_MODEL_SUMMARY 函数来查看模型摘要。

=> \t
Showing only tuples.
=> SELECT get_model_summary(USING PARAMETERS model_name='spark_logistic_reg');

=============
function_name
=============
classification

===========
data_fields
===========
 name  |dataType|  optype
-------+--------+-----------
field_0| double |continuous
field_1| double |continuous
field_2| double |continuous
field_3| double |continuous
field_4| double |continuous
field_5| double |continuous
field_6| double |continuous
field_7| double |continuous
field_8| double |continuous
target | string |categorical


==========
predictors
==========
 name  |exponent|coefficient
-------+--------+-----------
field_0|   1    | -0.23318
field_1|   1    |  0.73623
field_2|   1    |  0.29964
field_3|   1    |  0.12809
field_4|   1    | -0.66857
field_5|   1    |  0.51675
field_6|   1    | -0.41026
field_7|   1    |  0.30829
field_8|   1    | -0.17788


===============
Additional Info
===============
    Name     | Value
-------------+--------
is_supervised|   1
  intercept  |-1.20173

您还可以使用 GET_MODEL_ATTRIBUTE 函数检索模型的属性。

=> \t
Tuples only is off.
=> -- The list of the attributes of the PMML model
=> SELECT get_model_attribute(USING PARAMETERS model_name='spark_logistic_reg');
   attr_name   |         attr_fields         | #_of_rows
---------------+-----------------------------+-----------
 is_supervised | is_supervised               |         1
 function_name | function_name               |         1
 data_fields   | name, dataType, optype      |        10
 intercept     | intercept                   |         1
 predictors    | name, exponent, coefficient |         9
(5 rows)

=> -- The coefficients of the PMML model
=> SELECT get_model_attribute(USING PARAMETERS model_name='spark_logistic_reg', attr_name='predictors');
  name   | exponent |    coefficient
---------+----------+--------------------
 field_0 |        1 |   -0.2331769167607
 field_1 |        1 |  0.736227459496199
 field_2 |        1 |   0.29963728232024
 field_3 |        1 |  0.128085369856188
 field_4 |        1 | -0.668573096260048
 field_5 |        1 |  0.516750679584637
 field_6 |        1 |  -0.41025989394959
 field_7 |        1 |  0.308289533913736
 field_8 |        1 | -0.177878773139411
(9 rows)

然后,您可以使用 PREDICT_PMML 函数将导入的模型应用于关系以进行数据库内预测。模型的内部参数可以通过名称或列出的位置与输入关系的列名匹配。也可以将直接输入值馈送到函数,如下所示。

=> -- Using the imported PMML model for scoring direct input values
=> SELECT predict_pmml(1.5,0.5,2,1,0.75,4.2,3.1,0.9,1.1
username(>                     USING PARAMETERS model_name='spark_logistic_reg', match_by_pos=true);
 predict_pmml
--------------
 1
(1 row)

=> -- Using the imported PMML model for scoring samples in a table
=> SELECT predict_pmml(* USING PARAMETERS model_name='spark_logistic_reg') AS prediction
=>     FROM test_data;
 prediction
------------
 1
 0
(2 rows)

另请参阅

6.8.2.3 - PMML 特征和属性

在 Vertica 中使用外部模型 概述了 Vertica 支持使用外部模型的功能。本主题提供有关 Vertica 如何支持使用 PMML 模型的限制的更多详细信息。

为了得到 Vertica 的支持,PMML 模型:

  • 不得包含数据预处理步骤。

  • 必须仅编码以下模型类型:k-means、线性回归、逻辑回归、随机森林(分类器和回归器)、XGBoost(分类器和回归器)、GeneralRegressionModel 和 TreeModel。

支持的 PMML 标记和属性

下表列出了支持的 PMML 标记及其属性。

7 - 地理空间分析

Vertica 提供的函数可用于操纵复杂的二维和三维空间对象。这些函数均遵循开放地理空间联盟 (OGC) 标准。Vertica 还提供了数据类型和 SQL 函数,用于根据 OGC 标准指定空间对象并存储在数据库中。

转换熟知文本 (WKT) 和熟知二进制 (WKB)

转换 WKT 和 WKB。

优化的空间联接

使用 ST_Intersects 和 STV_Intersects 执行快速空间联接。

通过 Shapefile 加载和导出空间数据

轻松地加载和导出 shapefile。

存储和检索对象

确定:

  • 对象是否包含自交点或自切点。

  • 一个对象是否完全在另一个对象范围内,例如多边形范围内的点。

测试对象之间的关系

例如,它们是否相交或接触:

  • 确定对象的边界。

  • 确定对象的顶点。

计算

  • 两个对象之间的最短距离。

  • 对象的大小(长度、面积)。

  • 一个或多个对象的质心。

  • 一个或多个对象周围的缓冲区。

7.1 - 地理空间分析的最佳实践

在 Vertica 中执行地理空间分析时,Vertica 建议采用以下最佳实践。

性能优化

使用点和多边形进行空间联接

Vertica 提供了两种方法来识别一组点是否与一组多边形相交。根据数据集的大小,选择可提供最佳性能的方法。

有关空间联接最佳实践的详细示例,请参阅空间联接的最佳实践

空间索引

STV_Create_Index 函数可能占用大量处理时间和内存。首次为新数据编制索引时,请监视内存使用情况,以确保其保持在安全限制以内。内存使用情况取决于:

  • 多边形数量

  • 顶点数量

  • 多边形之间的重叠量

检查多边形有效性

7.2 - 空间对象

Vertica 实施了多种数据类型,用于存储空间对象、熟知文本 (WKT) 字符串和熟知二进制 (WKB) 表示。这些数据类型包括:

7.2.1 - 支持的空间对象

Vertica 支持两种空间数据类型。这些数据类型在表列中存储二维和三维空间对象:

  • GEOMETRY:在笛卡尔平面中定义的且坐标表示为 (x,y) 对的空间对象。所有计算均使用笛卡尔坐标。
  • GEOGRAPHY:在理想球体的表面或在 WGS84 坐标系中定义的空间对象。坐标表示为经度/纬度角度值,单位为度。所有的计算都以米为单位。对于理想球体计算,球体半径为 6371 千米,与地球形状近似。

GEOMETRYGEOGRAPHY数据类型的最大大小为 10,000,000 字节 (10 MB)。不能将任何一种数据类型用作表的主键。

7.2.2 - 空间引用标识符 (SRID)

空间参照标识符 (SRID) 为整型值,表示在平面上投影坐标的方法。SRID 是指示在其中定义空间对象的坐标系的元数据。

使用几何实参的地理空间函数必须包含相同的 SRID。如果函数未包含相同的 SRID,查询将会返回错误。

例如,以下查询中的两个点具有不同的 SRID。因此,该查询返回错误:

=> SELECT ST_Distance(ST_GeomFromText('POINT(34 9)',2749), ST_GeomFromText('POINT(70 12)', 3359));
ERROR 5861:  Error calling processBlock() in User Function ST_Distance at [src/Distance.cpp:65],
error code: 0, message: Geometries with different SRIDs found: 2749, 3359

支持的 SRID

Vertica 支持派生自 EPSG 标准的 SRID。使用几何实参的地理空间函数在执行计算时必须使用支持的 SRID。SRID 值为 0 至 232-1 是有效的。使用超出此范围的 SRID 执行查询将会返回错误。

7.3.1 - 定义空间数据的表列

要定义包含 GEOMETRY 和 GEOGRAPHY 数据的列,请使用以下命令:

=> CREATE TABLE [[db-name.]schema.]table-name (
   column-name GEOMETRY[(length)],
   column-name GEOGRAPHY[(length)]);

如果忽略长度规范,则默认列大小为 1 MB。最大列大小为 10 MB。不强制执行上限,但地理空间函数只能接受或返回不超过 10 MB 的空间数据。

在创建后,无法修改 GEOMETRY 或 GEOGRAPHY 列的大小或数据类型。如果创建的列大小不足,请创建一个具有所需大小的新列。然后从旧列复制数据,并从表中删除旧列。

无法向包含另一个 Vertica 数据库的空间数据的表导入数据或从中导出数据。

7.3.2 - 从表中导出空间数据

可以将 Vertica 数据库表中的空间数据导出到 shapefile。

要将表中的空间数据导出到 shapefile:

  1. 超级用户的身份设置 shapefile 导出目录。

    => SELECT STV_SetExportShapefileDirectory(USING PARAMETERS path = '/home/geo/temp');
                   STV_SetExportShapefileDirectory
    ------------------------------------------------------------
     SUCCESS. Set shapefile export directory: [/home/geo/temp]
    (1 row)
    
  2. 将空间数据导出到 shapefile。

    => SELECT STV_Export2Shapefile(*
                  USING PARAMETERS shapefile = 'visualizations/city-data.shp',
                                shape = 'Polygon') OVER() FROM spatial_data;
     Rows Exported |                          File Path
    ---------------+----------------------------------------------------------------
           185873 | v_geo-db_node0001: /home/geo/temp/visualizations/city-data.shp
    (1 row)
    
    • 星号 (*) 值等同于列出 FROM 子句中的所有列。

    • 在导出 shapefile 时可以指定子目录。

    • Shapefile 必须以文件扩展名 .shp 结束。

  3. 确认现在有三个文件显示在 shapefile 导出目录中。

    $ ls
    city-data.dbf  city-data.shp   city-data.shx
    

7.3.3 - 识别 Null 空间对象

可使用 Vertica IS NULL 和 IS NOT NULL 结构来识别 null GEOMETRY 和 GEOGRAPHY 对象。

以下示例使用下面的表,其中 id=2 的行在 geog 字段中包含 null 值。


=> SELECT id, ST_AsText(geom), ST_AsText(geog) FROM locations
   ORDER BY 1 ASC;
 id |           ST_AsText              |                 ST_AsText
----+----------------------------------+--------------------------------------
  1 | POINT (2 3)                      | POINT (-85 15)
  2 | POINT (4 5)                      |
  3 | POLYGON ((-1 2, 0 3, 1 2, -1 2)) | POLYGON ((-24 12, -15 23, -20 27, -24 12))
  4 | LINESTRING (-1 2, 1 5)           | LINESTRING (-42.74 23.98, -62.19 23.78)
(4 rows)

识别具有 null geog 值的所有行:

=> SELECT id, ST_AsText(geom), (ST_AsText(geog) IS NULL) FROM locations
   ORDER BY 1 ASC;
 id |            ST_AsText             | ?column?
----+----------------------------------+----------
  1 | POINT (2 3)                      | f
  2 | POINT (4 5)                      | t
  3 | POLYGON ((-1 2, 0 3, 1 2, -1 2)) | f
  4 | LINESTRING (-1 2, 1 5)           | f
(4 rows)

识别 geog 值不为 null 的行:

=> SELECT id, ST_AsText(geom), (ST_AsText(geog) IS NOT NULL) FROM locations
   ORDER BY 1 ASC;
 id |            st_astext             | ?column?
----+----------------------------------+----------
  1 | POINT (2 3)                      | t
  2 | POINT (4 5)                      | f
  3 | LINESTRING (-1 2, 1 5)           | t
  4 | POLYGON ((-1 2, 0 3, 1 2, -1 2)) | t
(4 rows)

7.3.4 - 从 Shapefile 中加载空间数据

Vertica 提供了加载和解析 Shapefile 中存储的空间数据的功能。Shapefile 描述了点、线和多边形。Shapefile 由三个必要的文件构成:所有三个文件都必须存在,并且位于同一目录内以定义几何图形:

  • .shp—包含几何数据。

  • .shx—包含几何图形的位置索引。

  • .dbf—包含每个几何图形的属性。

要从 shapefile 加载空间数据:

  1. 使用 STV_ShpCreateTable 生成 CREATE TABLE 语句。

    => SELECT STV_ShpCreateTable ( USING PARAMETERS file = '/home/geo/temp/shp-files/spatial_data.shp')
                                   OVER() AS spatial_data;
               spatial_data
    ----------------------------------
     CREATE TABLE spatial_data(
       gid IDENTITY(64) PRIMARY KEY,
       uniq_id INT8,
       geom GEOMETRY(85)
    );
    (5 rows)
    
  2. 创建表。

    => CREATE TABLE spatial_data(
       gid IDENTITY(64) PRIMARY KEY,
       uniq_id INT8,
       geom GEOMETRY(85));
    
  3. 加载 Shapefile。

    => COPY spatial_data WITH SOURCE STV_ShpSource(file='/home/geo/temp/shp-files/spatial_data.shp')
        PARSER STV_ShpParser();
     Rows Loaded
    -------------
              10
    (1 row)
    

支持的 Shapefile 图形类型

下表列出了 Vertica 支持的 Shapefile 图形类型。

7.3.5 - 使用 COPY 将空间数据加载到表中

可以使用 COPY 语句将空间数据加载到 Vertica 中的表。

要使用 COPY 语句将数据加载到 Vertica:

  1. 创建表。

    => CREATE TABLE spatial_data (id INTEGER, geom GEOMETRY(200));
    CREATE TABLE
    
  2. 使用以下数据创建一个名为 spatial.dat 的文本文件。

    1|POINT(2 3)
    2|LINESTRING(-1 2, 1 5)
    3|POLYGON((-1 2, 0 3, 1 2, -1 2))
    
  3. 使用 COPY 将数据加载到表中。

    => COPY spatial_data (id, gx FILLER LONG VARCHAR(605), geom AS ST_GeomFromText(gx)) FROM LOCAL 'spatial.dat';
     Rows Loaded
    -------------
               3
    (1 row)
    

    该语句指定了一个 LONG VARCHAR(32000000) 填充符,它是 WKT 的最大大小。必须指定足够大的填充器值,以容纳想要插入表中的最大 WKT。

7.3.6 - 从表中检索作为熟知文本 (WKT) 的空间数据

GEOMETRY 和 GEOGRAPHY 数据在 Vertica 表中存储为用户不可读的 LONG VARBINARY。可使用 ST_AsText 返回熟知文本 (WKT) 格式的空间数据。

要返回 WKT 格式的空间数据:

=> SELECT id, ST_AsText(geom) AS WKT FROM spatial_data;
 id |               WKT
----+----------------------------------
  1 | POINT (2 3)
  2 | LINESTRING (-1 2, 1 5)
  3 | POLYGON ((-1 2, 0 3, 1 2, -1 2))
(3 rows)

7.3.7 - 使用 GeoHash 数据

Vertica 支持 GeoHash。GeoHash 是一种地理编码系统,用于对越来越细化的空间参照系进行分层编码。GeoHash 中的每个附加字符都会向下钻取到地图的较小部分。

您可以使用 Vertica 从 GeoHash 生成空间数据,以及从空间数据生成 GeoHash。Vertica 支持以下用于 GeoHash 的函数:

例如,从单点生成全精度和部分精度的 GeoHash。

=> SELECT ST_GeoHash(ST_GeographyFromText('POINT(3.14 -1.34)')), LENGTH(ST_GeoHash(ST_GeographyFromText('POINT(3.14 -1.34)'))),
                     ST_GeoHash(ST_GeographyFromText('POINT(3.14 -1.34)') USING PARAMETERS numchars=5) partial_hash;
      ST_GeoHash      | LENGTH | partial_hash
----------------------+--------+--------------
 kpf0rkn3zmcswks75010 |     20 | kpf0r
(1 row)

此示例说明如何从点集合点对象生成 GeoHash。返回的多边形是包含该 GeoHash 的最小图块的几何对象。

=> SELECT ST_AsText(ST_GeomFromGeoHash(ST_GeoHash(ST_GeomFromText('MULTIPOINT(0 0, 0.0002 0.0001)')))) AS region_1,
                    ST_AsText(ST_GeomFromGeoHash(ST_GeoHash(ST_GeomFromText('MULTIPOINT(0.0001 0.0001, 0.0003 0.0002)')))) AS region_2;
 -[ RECORD 1 ]---------------------------------------------------------------------------------------------
    region_1 | POLYGON ((0 0, 0.00137329101562 0, 0.00137329101562 0.00137329101562, 0 0.00137329101562, 0 0))
    region_2 | POLYGON ((0 0, 0.010986328125 0, 0.010986328125 0.0054931640625, 0 0.0054931640625, 0 0))

7.3.8 - 使用 ST_Intersects 和 STV_Intersect 执行空间联接

利用空间联接可以确定两组空间数据之间的空间关系。例如,可使用空间联接:

  • 计算不同区域内的移动呼叫密度,以确定新蜂窝站的位置。

  • 确定在飓风影响区域内的家庭。

  • 计算在某个邮政编码区域内生活的用户数量。

  • 计算零售店在任意指定时间的顾客数量。

7.3.8.1 - 空间联接的最佳实践

使用以下最佳实践来提高总体性能并优化空间查询。

在 Vertica 中使用空间联接的最佳实践包括:

  • 通过表分段提高创建索引的速度

  • 适当调整 Geometry 列的大小以存储点数据

  • 在 COPY 语句中使用 STV_GeometryPoint,将熟知文本 (WKT) 直接加载到 Geometry 列中。

  • 将 OVER (PARTITION BEST) 与 STV_Intersect 变换查询配合使用

最佳实践示例

在执行以下示例中的步骤之前,请从 Vertica Place GitHub 存储库 (https://github.com/vertica/Vertica-Geospatial) 下载 place_output.csv.zip。需要使用此存储库中的数据集。

  1. 创建用于多边形的表。使用适合数据但不会过大的 GEOMETRY 列宽。合适的列宽有助于提高性能。此外,通过哈希处理对表进行分段有利于并行计算。

    => CREATE TABLE artworks (gid int, g GEOMETRY(700)) SEGMENTED BY HASH(gid) ALL NODES;
    
  2. 将 Copy 语句与 ST_Buffer 配合使用,创建并加载对其运行相交函数的多边形。通过在 Copy 语句中使用 ST_Buffer,可使用该函数创建多边形。

    => COPY artworks(gid, gx FILLER LONG VARCHAR, g AS ST_Buffer(ST_GeomFromText(gx),8)) FROM STDIN DELIMITER ',';
    >> 1, POINT(10 45)
    >> 2, POINT(25 45)
    >> 3, POINT(35 45)
    >> 4, POINT(35 15)
    >> 5, POINT(30 5)
    >> 6, POINT(15 5)
    >> \.
    
  3. 创建用于位置数据(表示为点)的表。可以将点数据存储在大小为 100 字节的 GEOMETRY 列中。避免设置过大的 GEOMETRY 列。否则可能严重影响空间相交的性能。此外,通过哈希处理对表进行分段有利于并行计算。

    => CREATE TABLE usr_data (gid identity, usr_id int, date_time timestamp, g GEOMETRY(100))
         SEGMENTED BY HASH(gid) ALL NODES;
    
  4. 在执行 Copy 语句的过程中,将原始位置数据转换为 GEOMETRY 数据。由于位置数据需要使用 GEOMETRY 数据类型,因此必须执行此变换。使用函数 STV_GeometryPoint 变换源表的 xy 列。

    => COPY usr_data (usr_id, date_time, x FILLER LONG VARCHAR,
                      y FILLER LONG VARCHAR, g AS STV_GeometryPoint(x, y))
       FROM LOCAL 'place_output.csv' DELIMITER ',' ENCLOSED BY '';
    
  5. 创建用于多边形的空间索引。此索引有助于加快相交计算的速度。

    => SELECT STV_Create_Index(gid, g USING PARAMETERS index='art_index', overwrite=true) OVER() FROM artworks;
    
  6. 编写用于返回每个多边形的相交数量的分析查询。指定 Vertica 忽略与给定多边形相交次数少于 20 次的任何 usr_id

    => SELECT pol_gid,
           COUNT(DISTINCT(usr_id)) AS count_user_visit
       FROM
         (SELECT pol_gid,
           usr_id,
           COUNT(usr_id) AS user_points_in
        FROM
           (SELECT STV_Intersect(usr_id, g USING PARAMETERS INDEX='art_index') OVER(PARTITION BEST) AS (usr_id,
                                                            pol_gid)
        FROM usr_data
          WHERE date_time BETWEEN '2014-07-02 09:30:20' AND '2014-07-02 17:05:00') AS c
        GROUP BY pol_gid,
        usr_id HAVING COUNT(usr_id) > 20) AS real_visits
        GROUP BY pol_gid
        ORDER BY count_user_visit DESC;
    

示例查询中的优化

此查询采用了以下优化:

  • 断言的时间出现在子查询中。

  • 使用位置数据表避免需要巨大开销的联接操作。

  • 查询使用 OVER (PARTITION BEST),通过对数据进行分区提高了性能。

  • user_points_in 提供了所有游客与艺术品相交所花的综合时间估算值。

7.3.8.2 - 创建或刷新索引之前确保多边形的有效性

当 Vertica 创建或更新空间索引时,它不会检查多边形的有效性。为防止查询空间索引时获得无效结果,您应在创建或更新空间索引之前检查多边形的有效性。

以下示例向您展示如何检查多边形的有效性。

  1. 创建表并加载空间数据。

    => CREATE TABLE polygon_validity_test (gid INT, geom GEOMETRY);
    CREATE TABLE
    => COPY polygon_validity_test (gid, gx FILLER LONG VARCHAR, geom AS St_GeomFromText(gx)) FROM STDIN;
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> 2|POLYGON((-31 74,8 70,8 50,-36 53,-31 74))
    >> 3|POLYGON((-38 50,4 13,11 45,0 65,-38 50))
    >> 4|POLYGON((-12 42,-12 42,27 48,14 26,-12 42))
    >> 5|POLYGON((0 0,1 1,0 0,2 1,1 1,0 0))
    >> 6|POLYGON((3 3,2 2,2 1,2 3,3 3))
    >> \.
    
  2. 使用 ST_IsValid 和 STV_IsValidReason 查找任何无效的多边形。

    => SELECT gid, ST_IsValid(geom), STV_IsValidReason(geom) FROM polygon_validity_test;
     gid | ST_IsValid |            STV_IsValidReason
    -----+------------+------------------------------------------
       4 | t          |
       6 | f          | Self-intersection at or near POINT (2 1)
       2 | t          |
       3 | t          |
       5 | f          | Self-intersection at or near POINT (0 0)
    (5 rows)
    

既然我们已经在表中识别出无效的多边形,因此在创建或刷新空间索引时,我们可以通过几种不同的方式来处理无效的多边形。

使用 WHERE 子句筛选无效多边形

此方法比创建索引之前进行筛选要慢,因为它在执行时将检查每个多边形的有效性。

以下示例向您展示如何使用 WHERE 子句排除无效多边形。

```
=> SELECT STV_Create_Index(gid, geom USING PARAMETERS index = 'valid_polygons') OVER()
   FROM polygon_validity_test
   WHERE ST_IsValid(geom) = 't';
```

创建或刷新索引之前筛选无效多边形

此方法比使用 WHERE 子句进行筛选更快,因为在构建索引之前会降低性能。

以下示例向您展示如何创建一个排除无效多边形的新表,以此来排除无效多边形。

```
=> CREATE TABLE polygon_validity_clean AS
   SELECT *
   FROM polygon_validity_test
   WHERE ST_IsValid(geom) = 't';
CREATE TABLE
=> SELECT STV_Create_Index(gid, geom USING PARAMETERS index = 'valid_polygons') OVER()
   FROM polygon_validity_clean;
```

7.3.8.3 - STV_Intersect:标量函数与转换函数

这些 STV_Intersect 函数的用途相似,但用法不同。

在下面的示例中,STV_Intersect 标量函数会将 points 表中的点与名为 my_polygons 的空间索引中的多边形进行比较。 STV_Intersect 将会返回所有完全匹配的点和多边形:


=> SELECT gid AS pt_gid
   STV_Intersect(geom USING PARAMETERS index='my_polygons') AS pol_gid
   FROM points ORDER BY pt_gid;
 pt_gid | pol_gid
--------+---------
    100 |       2
    101 |
    102 |       2
    103 |
    104 |
    105 |       3
    106 |
    107 |
 (8 rows)

以下示例显示了如何使用 STV_Intersect 变换函数来返回关于匹配的三个点-多边形对以及它们所匹配的每个多边形的信息:


=> SELECT STV_Intersect(gid, geom
   USING PARAMETERS index='my_polygons')
   OVER (PARTITION BEST) AS (pt_gid, pol_id)
   FROM points;
 pt_gid | pol_id
--------+--------
    100 |      1
    100 |      2
    100 |      3
    102 |      2
    105 |      3
(3 rows)

另请参阅

7.3.8.4 - 使用 STV_Intersect 函数执行空间联接

假定您想要处理一个大中型空间数据集,并确定哪些点与哪些多边形相交。这种情况下,请首先使用 STV_Create_Index 创建一个空间索引。空间索引提高了访问多边形集的效率。

然后,使用 STV_Intersect 标量或变换函数确定匹配的点-多边形对。

7.3.8.4.1 - 空间索引和 STV_Intersect

在使用 STV_Intersect 函数之一执行空间联接之前,必须首先运行 STV_Create_Index,创建一个包含多边形相关信息的数据库对象。此对象被称为多边形集的空间索引。空间索引缩短了 STV_Intersect 函数访问多边形数据所用的时间。

Vertica 在全局空间中创建空间索引。因此,任何有权访问 STV_*_Index 函数的用户都可以描述、重命名或删除其他任何用户创建的索引。

Vertica 提供了多个与空间索引配合使用的函数:

7.3.8.5 - 何时使用 ST_Intersects 与 STV_Intersect

Vertica 提供了两种功能来识别一组点是否与一组多边形相交。根据数据集的大小,选择可提供最佳性能的方法:

  • 在将一组几何图形与单个几何图形进行比较以查看其是否相交时,请使用 ST_Intersects 函数。

  • 要确定一组点是否与一个大中型数据集中的一组多边形相交,请首先使用 STV_Create_Index 创建空间索引。然后,使用 STV_Intersect 函数之一返回相交的点-多边形对集合。

7.3.8.5.1 - 使用 ST_Intersects 执行空间联接

ST_Intersects 确定两个 GEOMETRY 对象是否在一个点相交或接触。

当需要确定一个列中的小型几何图形集与给定的几何图形是否相交时,可使用 ST_Intersects。

示例

以下示例使用 ST_Intersects 将一列点几何图形与单个多边形进行比较。包含点的表有 1 百万行。

ST_Intersects 仅返回与多边形相交的点。这些点约占表中点数的 0.01%:

=> CREATE TABLE points_1m(gid IDENTITY, g GEOMETRY(100)) ORDER BY g;
=> COPY points_1m(wkt FILLER LONG VARCHAR(100), g AS ST_GeomFromText(wkt))
   FROM LOCAL '/data/points.dat';
 Rows Loaded
-------------
     1000000
(1 row)
=> SELECT ST_AsText(g) FROM points_1m WHERE
   ST_Intersects
     (
      g,
      ST_GeomFromText('POLYGON((-71 42, -70.9 42, -70.9 42.1, -71 42.1, -71 42))')
      );
         st_astext
----------------------------
 POINT (-70.97532 42.03538)
 POINT (-70.97421 42.0376)
 POINT (-70.99004 42.07538)
 POINT (-70.99477 42.08454)
 POINT (-70.99088 42.08177)
 POINT (-70.98643 42.07593)
 POINT (-70.98032 42.07982)
 POINT (-70.95921 42.00982)
 POINT (-70.95115 42.02177)
...
(116 rows)

Vertica 建议通过创建空间索引来测试两列几何图形的相交情况。使用STV_Intersect:标量函数与转换函数中所述的 STV_Intersect 函数之一。

7.4 - 使用客户端应用程序中的空间对象

Vertica 客户端驱动程序库提供用于将客户端应用程序连接到 Vertica 数据库的接口。这些驱动程序简化了执行加载、报告生成及其他常见数据库任务时的数据交换操作。

以下是三种不同的客户端驱动程序:

  • 开放数据库连接 (ODBC)—对于以 C、Python、PHP、Perl 和其他大多数语言编写的第三方应用程序和客户端而言最常用的接口。

  • Java 数据库连接 (JDBC)—供采用 Java 编程语言编写的客户端使用。

  • 面向 .NET 的 ActiveX 数据对象 (ADO.NET)—供使用 Microsoft .NET Framework 开发的并且以 C#、Visual Basic .NET 和其他 .NET 语言编写的客户端使用。

Vertica Place 支持以下新数据类型:

  • LONG VARCHAR

  • LONG VARBINARY

  • GEOMETRY

  • GEOGRAPHY

客户端驱动程序库支持上述数据类型;以下几节将介绍该项支持并提供示例。

7.4.1 - 将 LONG VARCHAR 和 LONG VARBINARY 数据类型与 ODBC 配合使用

ODBC 驱动程序支持 LONG VARCHAR 和 LONG VARBINARY 数据类型(这两种数据类型分别类似于 VARCHAR 和 VARBINARY 数据类型)。将输入参数或输出参数绑定到查询中的 LONG VARCHAR 或 LONG VARBINARY 列时,请使用 SQL_LONGVARCHAR 和 SQL_LONGVARBINARY 常数设置列的数据类型。例如,若要将输入参数绑定到 LONG VARCHAR 列,应使用如下所示的语句:

rc = SQLBindParameter(hdlStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR,
             80000, 0, (SQLPOINTER)myLongString, sizeof(myLongString), NULL);

7.4.2 - 将 LONG VARCHAR 和 LONG VARBINARY 数据类型与 JDBC 配合使用

在 JDBC 客户端应用程序中使用 LONG VARCHAR 和 LONG VARBINARY 数据类型与使用 VARCHAR 和 VARBINARY 数据类型相似。JDBC 驱动程序以透明方式处理转换(例如,Java String 对象和 LONG VARCHAR 之间的转换)。

以下示例代码演示了插入和检索 LONG VARCHAR 字符串。此示例使用 JDBC Types 类确定由 Vertica 返回的字符串的数据类型,但它实际上不需要知道数据库列是 LONG VARCHAR 还是 VARCHAR 即可检索值。

import java.sql.*;
import java.util.Properties;

public class LongVarcharExample {
    public static void main(String[] args) {
        try {
            Class.forName("com.vertica.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            System.err.println("Could not find the JDBC driver class.");
            e.printStackTrace();
            return;
        }
        Properties myProp = new Properties();
        myProp.put("user", "ExampleUser");
        myProp.put("password", "password123");
        Connection conn;
        try {
            conn = DriverManager.getConnection(
                            "jdbc:vertica://VerticaHost:5433/ExampleDB",
                            myProp);
            // establish connection and make a table for the data.
            Statement stmt = conn.createStatement();

            // How long we want the example string to be. This is
            // larger than can fit into a traditional VARCHAR (which is limited
            // to 65000.
            int length = 100000;

            // Create a table with a LONG VARCHAR column that can store
            // the string we want to insert.
            stmt.execute("DROP TABLE IF EXISTS longtable CASCADE");
            stmt.execute("CREATE TABLE longtable (text LONG VARCHAR(" + length
                            + "))");
            // Build a long string by appending an integer to a string builder
            // until we hit the size limit. Will result in a string
            // containing 01234567890123....
            StringBuilder sb = new StringBuilder(length);
            for (int i = 0; i < length; i++)
            {
                sb.append(i % 10);
            }
            String value = sb.toString();

            System.out.println("String value is " + value.length() +
                            " characters long.");

            // Create the prepared statement
            PreparedStatement pstmt = conn.prepareStatement(
                            "INSERT INTO longtable (text)" +
                            " VALUES(?)");
            try {
                // Insert LONG VARCHAR value
                System.out.println("Inserting LONG VARCHAR value");
                pstmt.setString(1, value);
                pstmt.addBatch();
                pstmt.executeBatch();

                // Query the table we created to get the value back.
                ResultSet rs = null;
                rs = stmt.executeQuery("SELECT * FROM longtable");

                // Get metadata about the result set.
                ResultSetMetaData rsmd = rs.getMetaData();
                // Print the type of the first column. Should be
                // LONG VARCHAR. Also check it against the Types class, to
                // recognize it programmatically.
                System.out.println("Column #1 data type is: " +
                                rsmd.getColumnTypeName(1));
                if (rsmd.getColumnType(1) == Types.LONGVARCHAR) {
                    System.out.println("It is a LONG VARCHAR");
                } else {
                    System.out.println("It is NOT a LONG VARCHAR");
                }

                // Print out the string length of the returned value.
                while (rs.next()) {
                    // Use the same getString method to get the value that you
                    // use to get the value of a VARCHAR.
                    System.out.println("Returned string length: " +
                                    rs.getString(1).length());
                }
            } catch (SQLException e) {
                System.out.println("Error message: " + e.getMessage());
                return; // Exit if there was an error
            }
            // Cleanup
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

7.4.3 - 将 GEOMETRY 和 GEOGRAPHY 数据类型用于 ODBC

Vertica GEOMETRY 和 GEOGRAPHY 数据类型受 LONG VARBINARY 原生类型支持,ODBC 客户端应用程序将其视为二进制数据。但是,这些数据类型的格式是 Vertica 所特有的。要在 C++ 应用程序中操纵此数据,必须使用 Vertica 中可将其转换为识别的格式的函数。

要将 WKT 或 WKB 转换为 GEOMETRY 或 GEOGRAPHY 格式,请使用以下 SQL 函数之一:

要将 GEOMETRY 或 GEOGRAPHY 对象转换为其对应的 WKT 或 WKB,请使用以下 SQL 函数之一:

  • ST_AsText—将 GEOMETRY 或 GEOGRAPHY 对象转换为 WKT,返回 LONGVARCHAR。

  • ST_AsBinary—将 GEOMETRY 或 GEOGRAPHY 对象转换为 WKB,返回 LONG VARBINARY。

以下代码示例使用 ST_GeomFromText 将 WKT 数据转换为 GEOMETRY 数据,然后将其存储在表中。之后,此示例从该表中检索 GEOMETRY 数据,并使用 ST_AsTextST_AsBinary 将它转换为 WKT 和 WKB 格式。

// Compile on Linux using:
//  g++ -g -I/opt/vertica/include -L/opt/vertica/lib64 -lodbc -o SpatialData SpatialData.cpp
// Some standard headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sstream>
// Only needed for Windows clients
// #include <windows.h>
// Standard ODBC headers
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
// Helper function to print SQL error messages.
template <typename HandleT>
void reportError(int handleTypeEnum, HandleT hdl)
{
    // Get the status records.
    SQLSMALLINT   i, MsgLen;
    SQLRETURN     ret2;
    SQLCHAR       SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
    SQLINTEGER    NativeError;
    i = 1;
    printf("\n");
    while ((ret2 = SQLGetDiagRec(handleTypeEnum, hdl, i, SqlState, &NativeError,
                                Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {
        printf("error record %d\n", i);
        printf("sqlstate: %s\n", SqlState);
        printf("detailed msg: %s\n", Msg);
        printf("native error code: %d\n\n", NativeError);
        i++;
    }
    exit(EXIT_FAILURE); // bad form... but Ok for this demo
}
int main()
{
    // Set up the ODBC environment
    SQLRETURN ret;
    SQLHENV hdlEnv;
    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hdlEnv);
    assert(SQL_SUCCEEDED(ret));
    // Tell ODBC that the application uses ODBC 3.
    ret = SQLSetEnvAttr(hdlEnv, SQL_ATTR_ODBC_VERSION,
        (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
    assert(SQL_SUCCEEDED(ret));
    // Allocate a database handle.
    SQLHDBC hdlDbc;
    ret = SQLAllocHandle(SQL_HANDLE_DBC, hdlEnv, &hdlDbc);
    assert(SQL_SUCCEEDED(ret));
    // Connect to the database
    printf("Connecting to database.\n");
    const char *dsnName = "ExampleDB";
    const char* userID = "dbadmin";
    const char* passwd = "password123";
    ret = SQLConnect(hdlDbc, (SQLCHAR*)dsnName,
        SQL_NTS,(SQLCHAR*)userID,SQL_NTS,
        (SQLCHAR*)passwd, SQL_NTS);
    if(!SQL_SUCCEEDED(ret)) {
        printf("Could not connect to database.\n");
        reportError<SQLHDBC>(SQL_HANDLE_DBC, hdlDbc);

    } else {
        printf("Connected to database.\n");
    }

    // Disable AUTOCOMMIT
    ret = SQLSetConnectAttr(hdlDbc, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF,
        SQL_NTS);

    // Set up a statement handle
    SQLHSTMT hdlStmt;
    SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);

    // Drop any previously defined table.
    ret = SQLExecDirect(hdlStmt, (SQLCHAR*)"DROP TABLE IF EXISTS polygons",
        SQL_NTS);
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}

    // Run query to create a table to hold a geometry.
    ret = SQLExecDirect(hdlStmt,
        (SQLCHAR*)"CREATE TABLE polygons(id INTEGER PRIMARY KEY, poly GEOMETRY);",
        SQL_NTS);
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}

    // Create the prepared statement. This will insert data into the
    // table we created above. It uses the ST_GeomFromText function to convert the
    // string-formatted polygon definition to a GEOMETRY datat type.
    printf("Creating prepared statement\n");
    ret = SQLPrepare (hdlStmt,
        (SQLTCHAR*)"INSERT INTO polygons(id, poly) VALUES(?, ST_GeomFromText(?))",
        SQL_NTS) ;
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}

    SQLINTEGER id = 0;
    int numBatches = 5;
    int rowsPerBatch = 10;

    // Polygon definition as a string.
    char polygon[] = "polygon((1 1, 1 2, 2 2, 2 1, 1 1))";
    // Bind variables to the parameters in the prepared SQL statement
    ret = SQLBindParameter(hdlStmt, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,
        0, 0, &id, 0 , NULL);
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT,hdlStmt);}
    // Bind polygon string to the geometry column
    SQLBindParameter(hdlStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR,
        strlen(polygon), 0, (SQLPOINTER)polygon, strlen(polygon), NULL);
     if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT,hdlStmt);}
    // Execute the insert
    ret = SQLExecute(hdlStmt);
    if(!SQL_SUCCEEDED(ret)) {
       reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);
    }  else {
        printf("Executed batch.\n");
    }

    // Commit the transaction
    printf("Committing transaction\n");
    ret = SQLEndTran(SQL_HANDLE_DBC, hdlDbc, SQL_COMMIT);
    if(!SQL_SUCCEEDED(ret)) {
        reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);
    }  else {
        printf("Committed transaction\n");
    }
    // Now, create a query to retrieve the geometry.
    ret = SQLAllocHandle(SQL_HANDLE_STMT, hdlDbc, &hdlStmt);
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}
    printf("Getting data from table.\n");
    // Execute a query to get the id, raw geometry data, and
    // the geometry data as a string. Uses the ST_AsText SQL function to
    // format raw data back into a string polygon definition
    ret = SQLExecDirect(hdlStmt,
        (SQLCHAR*)"select id,ST_AsBinary(poly),ST_AsText(poly) from polygons ORDER BY id;",
        SQL_NTS);
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT,hdlStmt);}

    SQLINTEGER idval;
    // 10MB buffer to hold the raw data from the geometry (10Mb is the maximum
    // length of a GEOMETRY)
    SQLCHAR* polygonval = (SQLCHAR*)malloc(10485760);
    SQLLEN polygonlen, polygonstrlen;
    // Buffer to hold a LONGVARCHAR that can result from converting the
    // geometry to a string.
    SQLTCHAR* polygonstr = (SQLTCHAR*)malloc(33554432);

    // Get the results of the query and print each row.
    do {
        ret = SQLFetch(hdlStmt);
        if (SQL_SUCCEEDED(ret)) {
            // ID column
            ret = SQLGetData(hdlStmt, 1, SQL_C_LONG, &idval, 0, NULL);
            if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}
            printf("id: %d\n",idval);
            // The WKB format geometry data
            ret = SQLGetData(hdlStmt, 2, SQL_C_BINARY, polygonval, 10485760,
                &polygonlen);
            if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}
            printf("Polygon in WKB format: ");
            // Print each byte of polygonval buffer in hex format.
            for (int z = 0; z < polygonlen; z++)
                printf("%02x ",polygonval[z]);
            printf("\n");
            // Geometry data formatted as a string.
            ret = SQLGetData(hdlStmt, 3, SQL_C_TCHAR, polygonstr, 33554432, &polygonstrlen);
            if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}
            printf("Polygon in WKT format: %s\n", polygonstr);
        }
    } while(SQL_SUCCEEDED(ret));


    free(polygonval);
    free(polygonstr);
    // Clean up
    printf("Free handles.\n");
    ret = SQLFreeHandle(SQL_HANDLE_STMT, hdlStmt);
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}
    ret = SQLFreeHandle(SQL_HANDLE_DBC, hdlDbc);
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}
    ret = SQLFreeHandle(SQL_HANDLE_ENV, hdlEnv);
    if (!SQL_SUCCEEDED(ret)) {reportError<SQLHDBC>(SQL_HANDLE_STMT, hdlStmt);}
    exit(EXIT_SUCCESS);
}

运行上述示例后的输出如下所示:

Connecting to database.
Connected to database.
Creating prepared statement
Executed batch.
Committing transaction
Committed transaction
Getting data from table.
id: 0
Polygon in WKB format: 01 03 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 f0 3f 00 00 00 00 00 00 f0 3f
Polygon in WKT format: POLYGON ((1 1, 1 2, 2 2, 2 1, 1 1))
Free handles.

7.4.4 - 将 GEOMETRY 和 GEOGRAPHY 数据类型用于 JDBC

Vertica GEOMETRY 和 GEOGRAPHY 数据类型受 LONG VARBINARY 原生类型支持,JDBC 客户端应用程序将其视为二进制数据。但是,这些数据类型的格式是 Vertica 所特有的。要在 Java 应用程序中操纵此数据,必须使用 Vertica 中可将其转换为识别的格式的函数。

要将 WKT 或 WKB 转换为 GEOMETRY 或 GEOGRAPHY 格式,请使用以下 SQL 函数之一:

要将 GEOMETRY 或 GEOGRAPHY 对象转换为其对应的 WKT 或 WKB,请使用以下 SQL 函数之一:

  • ST_AsText—将 GEOMETRY 或 GEOGRAPHY 对象转换为 WKT,返回 LONGVARCHAR。

  • ST_AsBinary—将 GEOMETRY 或 GEOGRAPHY 对象转换为 WKB,返回 LONG VARBINARY。

以下代码示例使用 ST_GeomFromTextST_GeomFromWKB 将 WKT 和 WKB 数据转换为 GEOMETRY 数据,然后将其存储在表中。之后,此示例从该表中检索 GEOMETRY 数据,并使用 ST_AsTextST_AsBinary 将它转换为 WKT 和 WKB 格式。

import java.io.InputStream;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
public class GeospatialDemo
{
    public static void main(String [] args) throws Exception
    {
        Class.forName("com.vertica.jdbc.Driver");
        Connection conn =
              DriverManager.getConnection("jdbc:vertica://localhost:5433/db",
                                          "user", "password");
        conn.setAutoCommit(false);

        Statement stmt = conn.createStatement();
        stmt.execute("CREATE TABLE polygons(id INTEGER PRIMARY KEY, poly GEOMETRY)");

        int id = 0;
        int numBatches = 5;
        int rowsPerBatch = 10;

        //batch inserting WKT data
        PreparedStatement pstmt = conn.prepareStatement("INSERT INTO polygons
                                  (id, poly) VALUES(?, ST_GeomFromText(?))");
        for(int i = 0; i < numBatches; i++)
        {

            for(int j = 0; j < rowsPerBatch; j++)
            {
                //Insert your own WKT data here
                pstmt.setInt(1, id++);
                pstmt.setString(2, "polygon((1 1, 1 2, 2 2, 2 1, 1 1))");
                pstmt.addBatch();
            }
            pstmt.executeBatch();
        }

        conn.commit();
        pstmt.close();
        //batch insert WKB data
        pstmt = conn.prepareStatement("INSERT INTO polygons(id, poly)
                                      VALUES(?, ST_GeomFromWKB(?))");
        for(int i = 0; i < numBatches; i++)
        {
            for(int j = 0; j < rowsPerBatch; j++)
            {
                //Insert your own WKB data here
                byte [] wkb = getWKB();
                pstmt.setInt(1, id++);
                pstmt.setBytes(2, wkb);
                pstmt.addBatch();
            }
            pstmt.executeBatch();
        }

        conn.commit();
        pstmt.close();
        //selecting data as WKT
        ResultSet rs = stmt.executeQuery("select ST_AsText(poly) from polygons");
        while(rs.next())
        {
            String wkt = rs.getString(1);
            Reader wktReader = rs.getCharacterStream(1);
            //process the wkt as necessary
        }
        rs.close();

        //selecting data as WKB
        rs = stmt.executeQuery("select ST_AsBinary(poly) from polygons");
        while(rs.next())
        {
            byte [] wkb = rs.getBytes(1);
            InputStream wkbStream = rs.getBinaryStream(1);
            //process the wkb as necessary
        }
        rs.close();

        //binding parameters in predicates
        pstmt = conn.prepareStatement("SELECT id FROM polygons WHERE
                                      ST_Contains(ST_GeomFromText(?), poly)");
        pstmt.setString(1, "polygon((1 1, 1 2, 2 2, 2 1, 1 1))");
        rs = pstmt.executeQuery();
        while(rs.next())
        {
            int pk = rs.getInt(1);
            //process the results as necessary
        }
        rs.close();

        conn.close();
    }
}

7.4.5 - 将 GEOMETRY 和 GEOGRAPHY 数据类型用于 ADO.NET

Vertica GEOMETRY 和 GEOGRAPHY 数据类型受 LONG VARBINARY 原生类型支持,ADO.NET 客户端应用程序将其视为二进制数据。但是,这些数据类型的格式是 Vertica 所特有的。要在 C# 应用程序中操纵此数据,必须使用 Vertica 中可将其转换为识别的格式的函数。

要将 WKT 或 WKB 转换为 GEOMETRY 或 GEOGRAPHY 格式,请使用以下 SQL 函数之一:

要将 GEOMETRY 或 GEOGRAPHY 对象转换为其对应的 WKT 或 WKB,请使用以下 SQL 函数之一:

  • ST_AsText—将 GEOMETRY 或 GEOGRAPHY 对象转换为 WKT,返回 LONGVARCHAR。

  • ST_AsBinary—将 GEOMETRY 或 GEOGRAPHY 对象转换为 WKB,返回 LONG VARBINARY。

以下 C# 代码示例使用 ST_GeomFromText 将 WKT 数据转换为 GEOMETRY 数据,然后将其存储在表中。之后,此示例从该表中检索 GEOMETRY 数据,并使用 ST_AsTextST_AsBinary 将它转换为 WKT 和 WKB 格式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Vertica.Data.VerticaClient;
namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            VerticaConnectionStringBuilder builder =
                             new VerticaConnectionStringBuilder();
            builder.Host = "VerticaHost";
            builder.Database = "VMart";
            builder.User = "ExampleUser";
            builder.Password = "password123";
            VerticaConnection _conn = new
                              VerticaConnection(builder.ToString());
            _conn.Open();

            VerticaCommand command = _conn.CreateCommand();
            command.CommandText = "DROP TABLE IF EXISTS polygons";
            command.ExecuteNonQuery();
            command.CommandText =
              "CREATE TABLE polygons (id INTEGER PRIMARY KEY, poly GEOMETRY)";
            command.ExecuteNonQuery();
            // Prepare to insert a polygon using a prepared statement. Use the
            // ST_GeomFromtText SQl function to convert from WKT to GEOMETRY.
            VerticaTransaction txn = _conn.BeginTransaction();
            command.CommandText =
             "INSERT into polygons VALUES(@id, ST_GeomFromText(@polygon))";
            command.Parameters.Add(new
                           VerticaParameter("id", VerticaType.BigInt));
            command.Parameters.Add(new
                           VerticaParameter("polygon", VerticaType.VarChar));
            command.Prepare();
            // Set the values for the parameters
            command.Parameters["id"].Value = 0;
            //
            command.Parameters["polygon"].Value =
                               "polygon((1 1, 1 2, 2 2, 2 1, 1 1))";
            // Execute the query to insert the value
            command.ExecuteNonQuery();

            // Now query the table
            VerticaCommand query = _conn.CreateCommand();
            query.CommandText =
               "SELECT id, ST_AsText(poly), ST_AsBinary(poly) FROM polygons;";
            VerticaDataReader dr = query.ExecuteReader();
            while (dr.Read())
            {
                Console.WriteLine("ID: " + dr[0]);
                Console.WriteLine("Polygon WKT format data type: "
                    + dr.GetDataTypeName(1) +
                    " Value: " + dr[1]);
                // Get the WKB format of the polygon and print it out as hex.
                Console.Write("Polygon WKB format data type: "
                               + dr.GetDataTypeName(2));
                Console.WriteLine(" Value: "
                               + BitConverter.ToString((byte[])dr[2]));
            }
            _conn.Close();
        }
    }
}

示例代码在系统控制台上输出以下内容:

ID: 0
Polygon WKT format data type: LONG VARCHAR Value: POLYGON ((1 1, 1 2,
2 2, 2 1,1 1))
Polygon WKB format data type: LONG VARBINARY Value: 01-03-00-00-00-01
-00-00-00-05-00-00-00-00-00-00-00-00-00-F0-3F-00-00-00-00-00-00-F0-3F
-00-00-00-00-00-00-F0-3F-00-00-00-00-00-00-00-40-00-00-00-00-00-00-00
-40-00-00-00-00-00-00-00-40-00-00-00-00-00-00-00-40-00-00-00-00-00-00
-F0-3F-00-00-00-00-00-00-F0-3F-00-00-00-00-00-00-F0-3F

7.5 - OGC 空间定义

使用 Vertica 需要了解开放地理空间联盟 (OGC) 的概念和功能。有关详细信息,请参见 OGC 简易功能访问第 1 部分 - 通用架构规范。

7.5.1 - 空间类

Vertica 支持 OGC 标准中定义的多个对象类。

7.5.1.1 - Point

使用以下方法之一标识的二维空间中的位置:

  • X 和 Y 坐标

  • 经度和纬度值

点的维度为 0,并且没有边界。

示例

下面的示例使用 GEOMETRY 点:

=> CREATE TABLE point_geo (gid int, geom GEOMETRY(100));
CREATE TABLE
=> COPY point_geo(gid, gx filler LONG VARCHAR, geom AS ST_GeomFromText(gx)) FROM stdin delimiter ',';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1, POINT(3 5)
>>\.
=> SELECT gid, ST_AsText(geom) FROM point_geo;
 gid |  ST_AsText
-----+-------------
   1 | POINT (3 5)
(1 row)

下面的示例使用 GEOGRAPHY 点:

=> CREATE TABLE point_geog (gid int, geog geography(100));
CREATE TABLE
=> COPY point_geog(gid, gx filler LONG VARCHAR, geog AS ST_GeographyFromText(gx)) FROM stdin delimiter ',';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1, POINT(42 71)
>>\.
=> SELECT gid, ST_AsText(geog) FROM point_geog;
 gid |   ST_AsText
-----+---------------
   1 | POINT (42 71)
(1 row)

7.5.1.2 - Multipoint

一个或多个点的集合。点集合对象的维度为 0,并且没有边界。

示例

下面的示例使用 GEOMETRY 点集合:

=> CREATE TABLE mpoint_geo (gid int, geom GEOMETRY(1000));
CREATE TABLE
=> COPY mpoint_geo(gid, gx filler LONG VARCHAR, geom AS ST_GeomFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|MULTIPOINT(4 7, 8 10)
>>\.
=> SELECT gid, ST_AsText(geom) FROM mpoint_geo;
 gid |       st_astext
-----+-----------------------
   1 | MULTIPOINT (7 8, 6 9)
(1 row)

下面的示例使用 GEOGRAPHY 点集合:

=> CREATE TABLE mpoint_geog (gid int, geog GEOGRAPHY(1000));
CREATE TABLE
=> COPY mpoint_geog(gid, gx filler LONG VARCHAR, geog AS ST_GeographyFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|MULTIPOINT(42 71, 41.4 70)
>>\.
=> SELECT gid, ST_AsText(geom) FROM mpoint_geo;
 gid |       st_astext
-----+-----------------------
   1 | MULTIPOINT (42 71, 41.4 70)
(1 row)

7.5.1.3 - 线串

由连续点对标识的一个或多个连接的线。线串的维度为 1。线串边界是包含其起点和终点的点集合对象。

以下是线串的示例:

示例

下面的示例使用 GEOMETRY 类型创建表,使用复制将线串加载到表,然后查询表来查看线串:

=> CREATE TABLE linestring_geom (gid int, geom GEOMETRY(1000));
CREATE TABLE
=> COPY linestring_geom(gid, gx filler LONG VARCHAR, geom AS ST_GeomFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|LINESTRING(0 0, 1 1, 2 2, 3 4, 2 4, 1 5)
>>\.
=> SELECT gid, ST_AsText(geom) FROM linestring_geom;
 gid |                 ST_AsText
-----+-------------------------------------------
   1 | LINESTRING (0 0, 1 1, 2 2, 3 4, 2 4, 1 5)
(1 row)

下面的示例使用 GEOGRAPHY 类型创建表,使用 COPY 将线串加载到表,然后查询表以查看线串:

=> CREATE TABLE linestring_geog (gid int, geog GEOGRAPHY(1000));
CREATE TABLE
=> COPY linestring_geog(gid, gx filler LONG VARCHAR, geog AS ST_GeographyFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|LINESTRING(42.1 71, 41.4 70, 41.3 72.9, 42.99 71.46, 44.47 73.21)
>>\.
=> SELECT gid, ST_AsText(geog) FROM linestring_geog;
 gid |                             ST_AsText
-----+--------------------------------------------------------------------
   1 | LINESTRING (42.1 71, 41.4 70, 41.3 72.9, 42.99 71.46, 44.47 73.21)
(1 row)

7.5.1.4 - 线串集合

零个或多个线串的集合。线串集合没有维度。线串集合的边界是包含所有线串起点和终点的点集合对象。

以下是线串集合的示例:

示例

下面的示例使用 GEOMETRY 类型创建表,使用复制将线串集合加载到表,然后查询表来查看线串集合:

=> CREATE TABLE multilinestring_geom (gid int, geom GEOMETRY(1000));
CREATE TABLE
=> COPY multilinestring_geom(gid, gx filler LONG VARCHAR, geom AS ST_GeomFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|MULTILINESTRING((1 5, 2 4, 5 3, 6 6),(3 5, 3 7))
>>\.
=> SELECT gid, ST_AsText(geom) FROM multilinestring_geom;
 gid |                     ST_AsText
-----+----------------------------------------------------
   1 | MULTILINESTRING ((1 5, 2 4, 5 3, 6 6), (3 5, 3 7))
(1 row)

下面的示例使用 GEOGRAPHY 类型创建表,使用 COPY 将线串集合加载到表,然后查询表以查看线串集合:

=> CREATE TABLE multilinestring_geog (gid int, geog GEOGRAPHY(1000));
CREATE TABLE
=> COPY multilinestring_geog(gid, gx filler LONG VARCHAR, geog AS ST_GeographyFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|MULTILINESTRING((42.1 71, 41.4 70, 41.3 72.9), (42.99 71.46, 44.47 73.21))
>>\.
=> SELECT gid, ST_AsText(geog) FROM multilinestring_geog;
 gid |                                  ST_AsText
-----+----------------------------------------------------------------------------
   1 | MULTILINESTRING((42.1 71, 41.4 70, 41.3 72.9), (42.99 71.46, 44.47 73.21))
(1 row)

7.5.1.5 - 多边形

由一组封闭的线串标识的对象。多边形可能包含一个或多个由内部边界定义的孔,但所有点都必须保持连接状态。以下是两个多边形的示例:

包含性和独占性多边形

如果多边形以顺时针顺序将其点包括在内,则包含多边形边内的所有空间,排除边外的所有空间。如果多边形以逆时针顺序将其点包括在内,则排除多边形边内的所有空间,包含边外的所有空间。

示例

下面的示例使用 GEOMETRY 类型创建表,使用复制将多边形加载到表,然后查询表来查看多边形:

=> CREATE TABLE polygon_geom (gid int, geom GEOMETRY(1000));
CREATE TABLE
=> COPY polygon_geom(gid, gx filler LONG VARCHAR, geom AS ST_GeomFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|POLYGON(( 2 6, 2 9, 6 9, 7 7, 4 6, 2 6))
>>\.
=> SELECT gid, ST_AsText(geom) FROM polygon_geom;
 gid |                 ST_AsText
-----+------------------------------------------
   1 | POLYGON((2 6, 2 9, 6 9, 7 7, 4 6, 2 6))
(1 row)

下面的示例使用 GEOGRAPHY 类型创建表,使用 COPY 将多边形加载到表,然后查询表以查看多边形:

=> CREATE TABLE polygon_geog (gid int, geog GEOGRAPHY(1000));
CREATE TABLE
=> COPY polygon_geog(gid, gx filler LONG VARCHAR, geog AS ST_GeographyFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|POLYGON((42.1 71, 41.4 70, 41.3 72.9, 44.47 73.21, 42.99 71.46, 42.1 71))
>>\.
=> SELECT gid, ST_AsText(geog) FROM polygon_geog;
 gid |                                ST_AsText
-----+---------------------------------------------------------------------------
   1 | POLYGON((42.1 71, 41.4 70, 41.3 72.9, 44.47 73.21, 42.99 71.46, 42.1 71))
(1 row)

7.5.1.6 - 多边形集合

零个或多个未重叠的多边形的集合。

示例

下面的示例使用 GEOMETRY 类型创建表,使用 COPY 将多边形集合加载到表,然后查询表以查看多边形:

=> CREATE TABLE multipolygon_geom (gid int, geom GEOMETRY(1000));
CREATE TABLE
=> COPY multipolygon_geom(gid, gx filler LONG VARCHAR, geom AS ST_GeomFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>9|MULTIPOLYGON(((2 6, 2 9, 6 9, 7 7, 4 6, 2 6)),((0 0, 0 5, 1 0, 0 0)),((0 2, 2 5, 4 5, 0 2)))
>>\.
=> SELECT gid, ST_AsText(geom) FROM polygon_geom;
 gid |                                           ST_AsText
-----+----------------------------------------------------------------------------------------------
   9 | MULTIPOLYGON(((2 6, 2 9, 6 9, 7 7, 4 6, 2 6)),((0 0, 0 5, 1 0, 0 0)),((0 2, 2 5, 4 5, 0 2)))
(1 row)

下面的示例使用 GEOGRAPHY 类型创建表,使用 COPY 将多边形集合加载到表,然后查询表以查看多边形:

=> CREATE TABLE multipolygon_geog (gid int, geog GEOGRAPHY(1000));
CREATE TABLE
=> COPY polygon_geog(gid, gx filler LONG VARCHAR, geog AS ST_GeographyFromText(gx)) FROM stdin delimiter '|';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1|POLYGON((42.1 71, 41.4 70, 41.3 72.9, 44.47 73.21, 42.99 71.46, 42.1 71))
>>\.
=> SELECT gid, ST_AsText(geog) FROM polygon_geog;
 gid |                                ST_AsText
-----+---------------------------------------------------------------------------
   1 | POLYGON(((42.1 71, 41.4 70, 41.3 72.9, 42.1 71)),((44.47 73.21, 42.99 71.46, 42.1 71, 44.47 73.21)))
(1 row)

7.5.2 - 空间对象表示方法

OGC 定义了两种表示空间对象的方法:

7.5.2.1 - 熟知文本 (WKT)

熟知文本 (WKT) 是空间对象的 ASCII 表示。

WKT 不区分大小写;Vertica 可识别小写字母和大写字母的任意组合。

以下是一些有效的 WKT 示例:

无效的 WKT:

  • POINT(1 NAN), POINT(1 INF)—坐标必须为数字。

  • POLYGON((1 2, 1 4, 3 4, 3 2))—多边形必须是封闭的。

  • POLYGON((1 4, 2 4))—线串不是有效的多边形。

7.5.2.2 - 熟知二进制 (WKB)

熟知二进制 (WKB) 是空间对象的二进制表示。此格式主要用于在应用程序之间移植空间数据。

7.5.3 - 空间定义

OGC 定义了描述以下内容的属性

  • 空间对象的特征

  • 对象之间可能存在的空间关系

Vertica 提供了用于测试和分析以下属性和关系的函数。

7.5.3.1 - 边界

定义空间对象限制的点集:

  • 点、点集合和 GeometryCollection 没有边界。

  • 线串的边界是点集合对象。此对象包含其起点和终点。

  • 线串集合的边界是点集合对象。此对象包含构成线串集合的所有线串的起点和终点。

  • 多边形的边界是在同一点开始和结束的线串。如果多边形包含一个或多个孔,则边界是包含外部多边形边界和任何内部多边形边界的线串集合。

  • 多边形集合的边界是包含构成多边形集合的所有多边形的边界的线串集合。

7.5.3.2 - 缓冲区

与空间对象边界的距离小于或等于指定距离的所有点的集合。该距离可能为正值或负值。

正缓冲区:

负缓冲区:

7.5.3.3 - 包含

如果一个空间对象的内部包括了另一个空间对象的所有点,则表示前者包含后者。如果某个对象(例如点或线串)仅沿多边形的边界存在,则多边形未包含该对象。如果某个点在线串上,则线串包含该点;线串的内部是指线串上除起点和终点以外的所有点。

Contains(a, b) 在空间上等同于 within(b, a)。

7.5.3.4 - 凸包

包含一个或多个空间对象的最小凸多边形。

在下图中,虚线表示一个线串和一个三角形的凸包。

7.5.3.5 - 交叉

如果以下两项均成立,则表示两个空间对象交叉:

  • 两个对象具有部分但非全部的公共内点。

  • 它们的相交结果的尺寸小于两个对象的最大尺寸。

7.5.3.6 - 非联接

没有任何公共点的两个空间对象;它们既不相交也不接触。

7.5.3.7 - 包络

包含空间对象的最小边界矩形。

以下多边形的包络表示为下图中的虚线。

7.5.3.8 - 相等

如果两个空间对象的坐标完全匹配,则二者相等。与在空间上等同同义。

在确定空间等价关系时点的顺序不重要:

  • LINESTRING(1 2, 4 3) 等于 LINESTRING(4 3, 1 2)。

  • POLYGON ((0 0, 1 1, 1 2, 2 2, 2 1, 3 0, 1.5 -1.5, 0 0)) 等于 POLYGON((1 1 , 1 2, 2 2, 2 1, 3 0, 1.5 -1.5, 0 0, 1 1))。

  • MULTILINESTRING((1 2, 4 3),(0 0, -1 -4)) 等于 MULTILINESTRING((0 0, -1 -4),(1 2, 4 3))。

7.5.3.9 - 外部

空间对象或其边界均未包含的点集。

7.5.3.10 - GeometryCollection

任何支持的空间对象类的零个或更多个对象的集合。

7.5.3.11 - 内部

空间对象中包含的点集,不包括其边界。

7.5.3.12 - 交集

两个或更多个空间对象的公共点集。

7.5.3.13 - 重叠

如果某个空间对象与另一个对象共享空间,但没有包含在该对象内,则表示这两个对象重叠。对象必须在其内部重叠;如果两个对象在单个点接触,或仅仅沿边界相交,则它们没有重叠。

7.5.3.14 - 相关

按照 DE-9IM 模式矩阵字符串的定义,某个空间对象与另一个对象在空间上相关。

DE-9IM 模式矩阵字符标识了两个空间对象彼此在空间上的相关性。有关 DE-9IM 标准的详细信息,请参见了解空间关系

7.5.3.15 - 简单

对于点、点集合、线串或线串集合,如果未与自身相交也没有自切点,则为简单空间对象。

多边形、多边形集合和 GeometryCollection 始终为简单空间对象。

7.5.3.16 - 余集

一对空间对象中没有彼此相交的所有点的集合。此差集在空间上等同于两个对象的并集减去其交集。余集包含交集的边界。

在下图中,阴影区域表示这两个矩形的余集。

下图显示了两个重叠的线串的余集。

7.5.3.17 - 并集

所有对象中的所有点的集合(针对两个或更多个空间对象)。

7.5.3.18 - 有效性

对于多边形或多边形集合,如果以下所有项均成立:

  • 它是封闭的;其起点与终点相同。

  • 其边界是一组线串。

  • 边界中没有任何两个交叉的线串。边界中的线串可能在某个点接触,但它们不能交叉。

  • 内部的任何多边形都必须完全包含在其中;这些多边形不能接触外部多边形边界除顶点以外的任何位置。

有效的多边形:

无效的多边形:

7.5.3.19 - 范围内

当一个空间对象的所有点都在另一个对象的内部时,前者被视为在后者范围内。因此,如果某个点或线串仅沿多边形的边界存在,则不被视为在该多边形范围内。多边形边界不属于其内部。

如果某个点在线串上,则被视为在该线串范围内。线串的内部是线串上除起点和终点以外的所有点。

Within(a, b) 在空间上等同于 Contains(b, a)。

7.6 - 空间数据类型支持限制

Vertica 不支持所有类型的 GEOMETRY 和 GEOGRAPHY 对象。有关函数支持的对象列表,请参见相应的函数页。球面几何通常比欧式几何更复杂。因此,支持 GEOGRAPHY 数据类型的空间函数较少。

空间数据类型支持的局限性:

  • 非 WGS84 GEOGRAPHY 对象是在半径为 6371 千米的理想球体的表面定义的空间对象。这个球体接近地球的形状。其他空间程序可能使用椭圆对地球进行建模,从而产生略微不同的数据。

  • 在创建后,无法修改 GEOMETRY 或 GEOGRAPHY 列的大小或数据类型。

  • 无法向包含另一个 Vertica 数据库的空间数据的表导入数据或从中导出数据。

  • 只能将 STV_Intersect 函数与点和多边形配合使用。

  • 不支持类型为 GEOMETRYCOLLECTION 的 GEOGRAPHY 对象。

  • 经度值必须介于 -180 和 +180 度之间。纬度值必须介于 –90 和 +90 度之间。Vertica 地理空间函数不验证这些值。

  • GEOMETRYCOLLECTION 对象不能包含空对象。例如,您不能指定 GEOMETRYCOLLECTION (LINESTRING(1 2, 3 4), POINT(5 6), POINT EMPTY)

  • 如果将 NULL 几何传递给空间函数,该函数将返回 NULL,除非另有说明。NULL 结果没有值。

  • Polymorphic 函数(例如 NVLGREATEST)不接受 GEOMETRY 和 GEOGRAPHY 实参。

8 - 时序分析

时序分析随时间的推移评估给定变量集的值,并将这些值分组到窗口(基于时间间隔)以进行分析和聚合。使用时序分析的常见场景包括:随着时间的推移不断进行的股市交易、投资组合业绩随时间推移的变动情况以及数据的图表趋势线。

由于时序中的时间和数据状态都是连续性的,因此随时间的推移来评估 SQL 查询具有挑战性。输入记录的间隔通常不均匀,因此会产生空白。为了解决此问题,Vertica 提供了:

  • 空白填充功能,它可以填充缺失的数据点。

  • 插值方案,它可以在已知数据点的离散集范围内推导新数据点。

Vertica 会在数据中插入非时序列(例如随时间片的变化计算得出的分析函数结果),并将缺失数据点添加到输出。此部分详细介绍了空白填充和插值。

您可以使用基于事件的窗口将时序数据拆分为与数据中的重大事件相邻的窗口。这在财务数据中尤其重要,因为分析可以侧重于触发了其他活动的特定事件。

会话化是基于事件的窗口的特殊用例,它通常用于分析点击流,例如根据记录的 Web 点击识别 Web 浏览会话。

Vertica 通过以下 SQL 扩展为时序分析提供了额外支持:

  • SELECT 语句中的 TIMESERIES 子句 支持空白填充和插值 (GFI) 计算。

  • TS_FIRST_VALUETS_LAST_VALUE 是时序聚合函数,用于在时间片开始或结束时返回值,而时间片的开始或结束时间由插值方案分别确定。

  • TIME_SLICE 是(SQL 扩展)日期/时间函数,用于按照不同的固定时间间隔聚合数据,并将向上舍入的输入 TIMESTAMP 值返回到与时间片间隔开始或结束时间相对应的值。

另请参阅

8.1 - 空白填充和插值 (GFI)

此主题中用于解释概念的示例和图形,均使用以下简单的架构:

CREATE TABLE TickStore (ts TIMESTAMP, symbol VARCHAR(8), bid FLOAT);
INSERT INTO TickStore VALUES ('2009-01-01 03:00:00', 'XYZ', 10.0);
INSERT INTO TickStore VALUES ('2009-01-01 03:00:05', 'XYZ', 10.5);
COMMIT;

在 Vertica 中,时序数据以一系列符合特定表架构的行表示,其中一列用于存储时间信息。

时序中的时间和数据状态都是连续性的。因此,随时间的推移对 SQL 查询进行评估具有挑战性,因为输入记录的间隔通常不均匀,而且会包含空白。

例如,下表中包含的两个输入行相隔五秒钟:3:00:00 和 3:00:05。

=> SELECT * FROM TickStore;
         ts          | symbol | bid
---------------------+--------+------
 2009-01-01 03:00:00 | XYZ    |   10
 2009-01-01 03:00:05 | XYZ    | 10.5
(2 rows)

根据这两个输入,如何确定时间位于这两个时间点之间的出价,例如 3:00:03 PM? TIME_SLICE 函数会将时间戳标准化为对应的时间片;但是,TIME_SLICE 并没有解决数据中缺少输入(时间片)这一问题。不过,Vertica 提供了空白填充和插值 (GFI) 功能,可以填充缺少的数据点并将已知数据点范围内的新(缺少的)数据点添加到输出中。它通过时序聚合函数(TS_FIRST_VALUETS_LAST_VALUE)以及 SQL TIMESERIES 子句完成这些任务。 但首先,我们将从常数插值开始,介绍 Vertica 中构成空白填充和插值的组件。 以下主题中的图像使用如下图例:

  • x 轴表示时间戳 (ts) 列

  • y 轴表示出价列。

  • 垂直的蓝线用来分隔时间片。

  • 红点表示表中的输入记录 $10.0 和 $10.5。

  • 蓝色星形表示输出值,包括内插值。

8.1.1 - 常数插值

根据示例 TickStore 架构中在 03:00:00 和 03:00:05 时的已知输入时间戳,如何确定 03:00:03 时的出价?

在财务数据中,使用的常见插值方案是将出价设置为到目前为止最后一个见到的值。这种方案称为常数插值,也就是说,Vertica 将根据之前的输入记录计算新值。

回到上面问到的查询,以下是表输出,它显示了在 03:00:00 和 03:00:05 时的出价之间存在 5 秒延迟:

=> SELECT * FROM TickStore;
         ts          | symbol | bid
---------------------+--------+------
 2009-01-01 03:00:00 | XYZ    |   10
 2009-01-01 03:00:05 | XYZ    | 10.5
(2 rows)

使用常数插值时,XYZ 的内插出价在 3:00:03 时仍为 $10.0,而且 3:00:03 介于在两个已知数据输入(3:00:00 PM 和 3:00:05)之间。在 3:00:05 时,值变为 $10.5。已知数据点以红点表示,在 3:00:03 时的内插值以蓝色星形表示。

包含 3 秒时间片的 TickStore 表

要编写查询使输入行更加均匀,您首先需要了解 TIMESERIES 子句和时序聚合函数

8.1.2 - TIMESERIES 子句和聚合

SELECT..TIMESERIES 子句和时序聚合会对数据进行标准化处理并将其放入 3 秒时间片,然后在发现空白时插入出价,以此来解决输入记录中存在空白的问题。

TIMESERIES 子句

TIMESERIES 子句 是时序分析计算的重要组件。它执行空白填充和插值 (GFI) 以生成输入记录中缺失的时间片。此子句会应用于数据中的时间戳列/表达式,其形式如下:

TIMESERIES slice_time AS 'length_and_time_unit_expression' 
OVER ( ... [ window-partition-clause[ , ... ] ] 
... ORDER BY time_expression )
... [ ORDER BY table_column [ , ... ] ]

时序聚合函数

时序聚合 (TSA) 函数 TS_FIRST_VALUETS_LAST_VALUE 随时间的推移评估给定变量集的值,并将这些值分组到窗口以进行分析和聚合。

TSA 函数负责处理属于每个时间片的数据。每个时间片会产生一行输出;如果存在分区表达式,则每个时间片的每个分区会产生一行输出。

下表显示了 3 秒时间片,其中:

  • 前两行位于第一个时间片,该时间片范围为 3:00:00 到 3:00:02。这些是 TSA 函数对应于 3:00:00 起的时间片的输出所对应的输入行。

  • 后两行位于第二个时间片,该时间片范围为 3:00:03 到 3:00:05。这些是 TSA 函数对应于 3:00:03 起的时间片的输出所对应的输入行。

    结果是每个时间片的开始时间。

    时序聚合输出

示例

以下示例比较了使用和不使用 TS_FIRST_VALUE TSA 函数时的返回值。

此示例显示了不使用 TS_FIRST_VALUE TSA 函数的 TIMESERIES 子句。

=> SELECT slice_time, bid FROM TickStore TIMESERIES slice_time AS '3 seconds' OVER(PARTITION by TickStore.bid ORDER BY ts);

此示例显示了 TIMESERIES 子句和 TS_FIRST_VALUE TSA 函数。该查询返回了 bid 列的值,这些值由指定的常数插值方案确定。

=> SELECT slice_time, TS_FIRST_VALUE(bid, 'CONST') bid FROM TickStore
   TIMESERIES slice_time AS '3 seconds' OVER(PARTITION by symbol ORDER BY ts);

Vertica 插入了最后一个已知值,填充了缺失的数据点,因此返回了对应于 3:00:03 的 10:

8.1.3 - 时序舍入

Vertica 按照与时间戳 2000-01-01 00:00:00 相关的相同间隔计算所有时序。Vertica 根据需要对时序时间戳进行四舍五入以符合此基线。对于指定的间隔,开始时间也会向下舍入为最接近的整数。

根据此逻辑,TIMESERIES 子句将生成时间戳系列,如以下部分所述。

分钟时序会向下舍入为整数分钟。例如,以下语句指定了 00:00:0300:05:50 这一时间范围:

=> SELECT ts FROM (
    SELECT '2015-01-04 00:00:03'::TIMESTAMP AS tm
      UNION
    SELECT '2015-01-04 00:05:50'::TIMESTAMP AS tm
   ) t
   TIMESERIES ts AS '1 minute' OVER (ORDER BY tm);

Vertica 将时间序列的开始时间和结束时间四舍五入到整分钟,分别为 00:00:0000:05:00


         ts
---------------------
 2015-01-04 00:00:00
 2015-01-04 00:01:00
 2015-01-04 00:02:00
 2015-01-04 00:03:00
 2015-01-04 00:04:00
 2015-01-04 00:05:00
(6 rows)

因为基线时间戳 2000-01-01 00:00:00 为星期六,所有周时序都从星期六开始。Vertica 相应地向下舍入了时序的开始和结束时间戳。例如,以下语句指定了 12/10/99 ‑ 01/10/00 的时间跨度:

=> SELECT ts FROM (
     SELECT '1999-12-10 00:00:00'::TIMESTAMP AS tm
       UNION
     SELECT '2000-01-10 23:59:59'::TIMESTAMP AS tm
     ) t
     TIMESERIES ts AS '1 week' OVER (ORDER BY tm);

指定的时间范围从星期五 (12/10/99) 开始,因此 Vertica 从上一个星期六 (12/04/99) 开始时序。时序结束于时间范围中的最后一个星期六,即 01/08/00:

         ts
---------------------
 1999-12-04 00:00:00
 1999-12-11 00:00:00
 1999-12-18 00:00:00
 1999-12-25 00:00:00
 2000-01-01 00:00:00
 2000-01-08 00:00:00
(6 rows)

月时序会相对于基线时间戳 2000-01-01 00:00:00 分为相等的 30 天间隔。例如,以下语句指定了 09/01/99 ‑ 12/31/00 这一时间范围:

=> SELECT ts FROM (
     SELECT '1999-09-01 00:00:00'::TIMESTAMP AS tm
       UNION
     SELECT '2000-12-31 23:59:59'::TIMESTAMP AS tm
   ) t
   TIMESERIES ts AS '1 month' OVER (ORDER BY tm);

Vertica 生成了一系列 30 天的间隔,其中每个时间戳都相对于基线时间戳进行了向上舍入或向下舍入:

         ts
---------------------
 1999-08-04 00:00:00
 1999-09-03 00:00:00
 1999-10-03 00:00:00
 1999-11-02 00:00:00
 1999-12-02 00:00:00
 2000-01-01 00:00:00
 2000-01-31 00:00:00
 2000-03-01 00:00:00
 2000-03-31 00:00:00
 2000-04-30 00:00:00
 2000-05-30 00:00:00
 2000-06-29 00:00:00
 2000-07-29 00:00:00
 2000-08-28 00:00:00
 2000-09-27 00:00:00
 2000-10-27 00:00:00
 2000-11-26 00:00:00
 2000-12-26 00:00:00
(18 rows)

年时序分为相等的 365 天间隔。如果时间范围重叠了自基线时间戳 2000-01-01 00:00:00 以来或在该基线时间戳以前的闰年,Vertica 将相应地对时序时间戳进行四舍五入。

例如,以下语句指定了 01/01/95 ‑ 05/08/09 这一时间范围,此时间范围重叠了四个闰年,包括基线时间戳:

=> SELECT ts FROM (
      SELECT '1995-01-01 00:00:00'::TIMESTAMP AS tm
        UNION
      SELECT '2009-05-08'::TIMESTAMP AS tm
    ) t timeseries ts AS '1 year' over (ORDER BY tm);

Vertica 生成了一系列时间戳,它们都相对于基线时间戳进行了向上舍入或向下舍入:

         ts
---------------------
 1994-01-02 00:00:00
 1995-01-02 00:00:00
 1996-01-02 00:00:00
 1997-01-01 00:00:00
 1998-01-01 00:00:00
 1999-01-01 00:00:00
 2000-01-01 00:00:00
 2000-12-31 00:00:00
 2001-12-31 00:00:00
 2002-12-31 00:00:00
 2003-12-31 00:00:00
 2004-12-30 00:00:00
 2005-12-30 00:00:00
 2006-12-30 00:00:00
 2007-12-30 00:00:00
 2008-12-29 00:00:00
(16 rows)

8.1.4 - 线性插值

线性插值是指 Vertica 基于指定的时间片将值内插到线性斜率中,而不是基于最后一个见到的值(常数插值)内插数据点。

后面的查询使用线性插值将输入记录放入 2 秒时间片中,然后返回每个股票代码/时间片组合的第一个出价值(时间片开始时的值):

=> SELECT slice_time, TS_FIRST_VALUE(bid, 'LINEAR') bid FROM Tickstore
   TIMESERIES slice_time AS '2 seconds' OVER(PARTITION BY symbol ORDER BY ts);
     slice_time      | bid
---------------------+------
 2009-01-01 03:00:00 |   10
 2009-01-01 03:00:02 | 10.2
 2009-01-01 03:00:04 | 10.4
(3 rows)

下图显示了前面的查询结果,同时显示了没有产生输入记录的 2 秒时间空白(3:00:02 和 3:00:04)。请注意,XYZ 的内插出价在 3:00:02 时变为 10.2,在 3:00:03 时变为 10.3,在 3:00:04 时变为 10.4,而所有这些都介于两个已知数据输入(3:00:00 和 3:00:05)之间。在 3:00:05 时,值可能会变为 10.5。

使用 ts_first_value 时的线性插值

以下是常数和线性插值方案的横向比较。

8.1.5 - GFI 示例

此主题介绍了可以使用常数和线性插值方案编写的部分查询。

常数插值

第一个查询使用 TS_FIRST_VALUE()TIMESERIES 子句将输入记录放入时长为 3 秒的时间片中,然后返回每个符号/时间片组合的第一个出价值(时间片开始时的值)。

=> SELECT slice_time, symbol, TS_FIRST_VALUE(bid) AS first_bid FROM TickStore
   TIMESERIES slice_time AS '3 seconds' OVER (PARTITION BY symbol ORDER BY ts);

因为股票 XYZ 的出价在 3:00:03 时为 10.0,即第二个时间片的 first_bid 值。此值从 3:00:03 开始时一直为 10.0,因为 10.5 的输入值直到 3:00:05 时才产生。在这种情况下,根据在股票 XYZ 中见到的最后一个值为时间 3:00:03 推断了内插值:

     slice_time      | symbol | first_bid
---------------------+--------+-----------
 2009-01-01 03:00:00 | XYZ    |        10
 2009-01-01 03:00:03 | XYZ    |        10
(2 rows)

下一个示例将输入记录放入 2 秒时间片以返回每个股票代码/时间片组合的第一个出价值:

=> SELECT slice_time, symbol, TS_FIRST_VALUE(bid) AS first_bid FROM TickStore
   TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

此时结果包含 2 秒时间片中的三个记录,所有这些出现在 03:00:00 时的第一个输入行和在 3:00:05 时的第二个输入行之间。请注意,第二个和第三个输出记录与不存在输入记录的时间片对应。

     slice_time      | symbol | first_bid
---------------------+--------+-----------
 2009-01-01 03:00:00 | XYZ    |        10
2009-01-01 03:00:02 | XYZ    |        10
2009-01-01 03:00:04 | XYZ    |        10
(3 rows)

使用同一个表架构时,下一个查询将 TS_LAST_VALUE() 与 TIMESERIES 结合使用以返回每个时间片最后的值(时间片结束时的值)。

=> SELECT slice_time, symbol, TS_LAST_VALUE(bid) AS last_bid FROM TickStore
   TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

请注意,最后一个值输出行为 10.5,因为在时间 3:00:05 时的值 10.5 是从 3:00:04 开始的 2 秒时间片中的最后一个点。

     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |       10
 2009-01-01 03:00:02 | XYZ    |       10
 2009-01-01 03:00:04 | XYZ    |     10.5
(3 rows)

请记住,因为常数插值为默认方案,如果按下述方式使用 CONST 参数编写查询,则会返回相同的结果:

=> SELECT slice_time, symbol, TS_LAST_VALUE(bid, 'CONST') AS last_bid FROM TickStore
   TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

线性插值

根据常数插值示例(指定了 2 秒时间片)中所列的上述输入记录,通过线性插值得出的 TS_LAST_VALUE 结果如下:

=> SELECT slice_time, symbol, TS_LAST_VALUE(bid, 'linear') AS last_bid   FROM TickStore
   TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

在结果中,没有返回最后一行的 last_bid 值,因为查询指定了 TS_LAST_VALUE,并且在 3:00:04 时间片之后没有要插值的任何数据点。

     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |     10.2
 2009-01-01 03:00:02 | XYZ    |     10.4
 2009-01-01 03:00:04 | XYZ    |
(3 rows)

使用多个时序聚合函数

同一个查询中可以使用多个时序聚合函数。按照 TIMESERIES 子句中的定义,它们共享同一个空白填充策略;然而,每个时序聚合函数可以指定自己的插值策略。在以下示例中,有两个常数插值和一个线性插值方案,但这三个函数全部使用三秒时间片。

=> SELECT slice_time, symbol,
       TS_FIRST_VALUE(bid, 'const') fv_c,
       TS_FIRST_VALUE(bid, 'linear') fv_l,
       TS_LAST_VALUE(bid, 'const') lv_c
   FROM TickStore
   TIMESERIES slice_time AS '3 seconds' OVER(PARTITION BY symbol ORDER BY ts);

在以下输出中,比较了原始输出与多个时序聚合函数返回的输出。

使用分析 LAST_VALUE 函数

以下是使用 LAST_VALUE() 的示例,这样您可以了解此函数与 GFI 语法之间的区别。

=> SELECT *, LAST_VALUE(bid) OVER(PARTITION by symbol ORDER BY ts)
   AS "last bid" FROM TickStore;

对于输出值,没有执行任何空白填充和内插。

         ts          | symbol | bid  | last bid
---------------------+--------+------+----------
 2009-01-01 03:00:00 | XYZ    |   10 |       10
 2009-01-01 03:00:05 | XYZ    | 10.5 |     10.5
(2 rows)

使用 slice_time

在 TIMESERIES 查询中,您无法在 WHERE 子句中使用 slice_time 列,因为 WHERE 子句的评估先于 TIMESERIES 子句,并且直到评估 TIMESERIES 之后才会生成 slice_time 列。例如,Vertica 不支持以下查询:

=> SELECT symbol, slice_time, TS_FIRST_VALUE(bid IGNORE NULLS) AS fv
   FROM TickStore
   WHERE slice_time = '2009-01-01 03:00:00'
   TIMESERIES slice_time as '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);
ERROR:  Time Series timestamp alias/Time Series Aggregate Functions not allowed in WHERE clause

但是,您可以编写子查询,将 slice_time 中的谓词放在外部查询中:

=> SELECT * FROM (
      SELECT symbol, slice_time,
        TS_FIRST_VALUE(bid IGNORE NULLS) AS fv
      FROM TickStore
      TIMESERIES slice_time AS '2 seconds'
      OVER (PARTITION BY symbol ORDER BY ts) ) sq
   WHERE slice_time = '2009-01-01 03:00:00';
 symbol |     slice_time      | fv
--------+---------------------+----
 XYZ    | 2009-01-01 03:00:00 | 10
(1 row)

创建稠密时间序列

借助 TIMESERIES 子句,您可以方便地创建稠密时间序列,以便与实际数据一起用于外部联接。结果将表示所有时间点,而不只是存在数据的时间点。

随后的示例将使用空白填充和插值 (GFI)中所述的相同 TickStore 架构,并添加一个新内部表以用于创建联接:

=> CREATE TABLE inner_table (
       ts TIMESTAMP,
       bid FLOAT
   );
=> CREATE PROJECTION inner_p (ts, bid) as SELECT * FROM inner_table
   ORDER BY ts, bid UNSEGMENTED ALL NODES;
=> INSERT INTO inner_table VALUES ('2009-01-01 03:00:02', 1);
=> INSERT INTO inner_table VALUES ('2009-01-01 03:00:04', 2);
=> COMMIT;

为了返回所有时间点,可以在相关时间帧的开始和结束范围之间创建一个简单的并集。在本例中以 1 秒为时间段:

=> SELECT ts FROM (
     SELECT '2009-01-01 03:00:00'::TIMESTAMP AS time FROM TickStore
     UNION
     SELECT '2009-01-01 03:00:05'::TIMESTAMP FROM TickStore) t
   TIMESERIES ts AS '1 seconds' OVER(ORDER BY time);
         ts
---------------------
 2009-01-01 03:00:00
 2009-01-01 03:00:01
 2009-01-01 03:00:02
 2009-01-01 03:00:03
 2009-01-01 03:00:04
 2009-01-01 03:00:05
(6 rows)

下一个查询将以 500 毫秒为时间段在时间帧的开始和结束范围之间创建一个并集:

=> SELECT ts FROM (
     SELECT '2009-01-01 03:00:00'::TIMESTAMP AS time
     FROM TickStore
     UNION
     SELECT '2009-01-01 03:00:05'::TIMESTAMP FROM TickStore) t
   TIMESERIES ts AS '500 milliseconds' OVER(ORDER BY time);
          ts
-----------------------
 2009-01-01 03:00:00
 2009-01-01 03:00:00.5
 2009-01-01 03:00:01
 2009-01-01 03:00:01.5
 2009-01-01 03:00:02
 2009-01-01 03:00:02.5
 2009-01-01 03:00:03
 2009-01-01 03:00:03.5
 2009-01-01 03:00:04
 2009-01-01 03:00:04.5
 2009-01-01 03:00:05
(11 rows)

以下查询以 1 秒为时间段在相关时间帧的开始和结束范围之间创建一个并集:

=> SELECT * FROM (
     SELECT ts FROM (
       SELECT '2009-01-01 03:00:00'::timestamp AS time FROM TickStore
       UNION
       SELECT '2009-01-01 03:00:05'::timestamp FROM TickStore) t
       TIMESERIES ts AS '1 seconds' OVER(ORDER BY time) ) AS outer_table
   LEFT OUTER JOIN inner_table ON outer_table.ts = inner_table.ts;

此并集将从左联接表返回一组与右联接表中记录相匹配的完整记录。如果查询没有找到任何匹配项,它会使用 NULL 值扩展右侧列:

         ts          |         ts          | bid
---------------------+---------------------+-----
 2009-01-01 03:00:00 |                     |
 2009-01-01 03:00:01 |                     |
 2009-01-01 03:00:02 | 2009-01-01 03:00:02 |   1
 2009-01-01 03:00:03 |                     |
 2009-01-01 03:00:04 | 2009-01-01 03:00:04 |   2
 2009-01-01 03:00:05 |                     |
(6 rows)

8.2 - 时序数据中的 NULL 值

NULL 值是空白填充和插值 (GFI) 计算的不常见输入。当存在 NULL 值时,您可以使用时间序列聚合 (TSA) 函数 TS_FIRST_VALUETS_LAST_VALUEIGNORE NULLS 影响插值的输出。系统处理 TSA 函数的方式与处理其对应的分析函数 FIRST_VALUELAST_VALUE 类似,因此,如果时间戳本身为 NULL,Vertica 将先筛选掉这些行,然后再执行空白填充和插值。

包含 NULL 值时的常数插值

图 1 显示了四个输入行中的输入都不包含 NULL 值时的默认(常数)插值结果。

图 2 显示了相同的输入行,但新增的一个输入记录的出价值为 NULL 且其对应的时间戳值为 3:00:03。

对于常数插值,从 3:00:03 开始的出价值将一直为 NULL,直到时序中出现下一个非 NULL 出价值。在图 2 中,由于存在 NULL 行,以阴影区域表示的时间间隔中的内插出价值为 NULL。如果在以 3:00:02 为起点的时间片中,使用常数插值对 TS_FIRST_VALUE(bid) 进行评估,其输出为非 NULL。但是,TS_FIRST_VALUE(bid) 在下一个时间片中仍会输出 NULL。如果 3:00:02 时间片的最后一个值为 NULL,则下一个时间片 (3:00:04) 的第一个值为 NULL。但是,如果将 TSA 函数与 IGNORE NULLS 结合使用,则在 3:00:04 时的值将与在 3:00:02 时的值相同。

为说明这一情况,请在 TickStore 表的 03:00:03 处插入一个包含 NULL 出价值的新行,此时 Vertica 将为 03:00:02 记录输出一个包含 NULL 值的行,但是没有为 03:00:03 输入输出一个行。

=> INSERT INTO tickstore VALUES('2009-01-01 03:00:03', 'XYZ', NULL);
=> SELECT slice_time, symbol, TS_LAST_VALUE(bid) AS last_bid FROM TickStore
-> TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);
     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |       10
 2009-01-01 03:00:02 | XYZ    |
 2009-01-01 03:00:04 | XYZ    |     10.5
(3 rows)

如果指定 IGNORE NULLS,Vertica 将使用常数插值方案填充缺失的数据点。在此,03:00:02 时的出价会内插到出价的上一个已知输入记录,即 03:00:00 时的 $10:

=> SELECT slice_time, symbol, TS_LAST_VALUE(bid IGNORE NULLS) AS last_bid FROM TickStore
     TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);
     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |       10
 2009-01-01 03:00:02 | XYZ    |       10
 2009-01-01 03:00:04 | XYZ    |     10.5
(3 rows)

此时,假如插入一个其时间戳列包含 NULL 值的行,Vertica 将在空白填充和插值开始之前筛选掉此行。

=> INSERT INTO tickstore VALUES(NULL, 'XYZ', 11.2);
=> SELECT slice_time, symbol, TS_LAST_VALUE(bid) AS last_bid FROM TickStore
     TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);

请注意,11.2 出价行没有对应的输出。

     slice_time      | symbol | last_bid
---------------------+--------+----------
 2009-01-01 03:00:00 | XYZ    |       10
 2009-01-01 03:00:02 | XYZ    |
 2009-01-01 03:00:04 | XYZ    |     10.5
(3 rows)

包含 NULL 值时的线性插值

对于线性插值,在图 3 中以阴影区表示的时间间隔中,内插出价值为 NULL。

如果在 3:00:03 时的输入值为 NULL,Vertica 无法以线性方式在此时间点附近内插出价值。

Vertica 在时间片的任意一侧中选择最近的非 NULL 值,并使用此值。例如,如果您使用线性插值方案,但未指定 IGNORE NULLS,并且您的数据包含一个实值和一个 NULL 值,则结果为 NULL。如果任意一侧中有值为 NULL,则结果为 NULL。因此,如果在以 3:00:02 为起始的时间片中,使用线性插值对 TS_FIRST_VALUE(bid) 进行评估,其输出为 NULL。 TS_FIRST_VALUE(bid) 在下一个时间片中仍为 NULL。

=> SELECT slice_time, symbol, TS_FIRST_VALUE(bid, 'linear') AS fv_l FROM TickStore
     TIMESERIES slice_time AS '2 seconds' OVER (PARTITION BY symbol ORDER BY ts);
     slice_time      | symbol | fv_l
---------------------+--------+------
 2009-01-01 03:00:00 | XYZ    |   10
 2009-01-01 03:00:02 | XYZ    |
 2009-01-01 03:00:04 | XYZ    |
(3 rows)

9 - 数据聚合

您可以使用 SUMCOUNT 等函数,在一个或多个级别聚合 GROUP BY 查询的结果。

9.1 - 单级别聚合

最简单的 GROUP BY 查询是在单级别聚合数据。例如,表可能包含下列家庭开支信息:

  • 类别

  • 当年在该类别上花费的金额

表数据可能类似于以下内容:

=> SELECT * FROM expenses ORDER BY Category;
 Year |  Category  | Amount
------+------------+--------
2005  | Books      |  39.98
2007  | Books      |  29.99
2008  | Books      |  29.99
2006  | Electrical | 109.99
2005  | Electrical | 109.99
2007  | Electrical | 229.98

您可以使用聚合函数,获取每个类别或每年的总开支:

=> SELECT SUM(Amount), Category FROM expenses GROUP BY Category;
 SUM     | Category
---------+------------
  99.96  | Books
 449.96  | Electrical
=> SELECT SUM(Amount), Year FROM expenses GROUP BY Year;
 SUM    | Year
--------+------
 149.97 | 2005
 109.99 | 2006
  29.99 | 2008
 259.97 | 2007

9.2 - 多级聚合

过一段时间后,经常更新的表可能会包含大量数据。以之前所示的简单表为例,假设您想要执行多级别查询,例如查询每年在每个类别上的开支金额。

以下查询使用了具有 SUM 函数的 ROLLUP 聚合,可计算各个类别的总开支和所有总开支。NULL 字段表示聚合中的小计值。

  • 只有当 Year 列为 NULL 时,小计才是针对所有 Category 值。

  • YearCategory 列为 NULL 时,小计针对两个列的所有 Amount 值。

使用 ORDER BY 子句,按开支类别、发生开支的年份以及 GROUPING_ID 函数创建的 GROUP BY 级别对结果进行排序:

=> SELECT Category, Year, SUM(Amount) FROM expenses
   GROUP BY ROLLUP(Category, Year) ORDER BY Category, Year, GROUPING_ID();
 Category   | Year |  SUM
------------+------+--------
 Books      | 2005 |  39.98
 Books      | 2007 |  29.99
 Books      | 2008 |  29.99
 Books      |      |  99.96
 Electrical | 2005 | 109.99
 Electrical | 2006 | 109.99
 Electrical | 2007 | 229.98
 Electrical |      | 449.96
            |      | 549.92

同样地,以下查询计算了各年份的总销售以及所有销售总额,然后使用 ORDER BY 子句来对结果进行排序:

=> SELECT Category, Year, SUM(Amount) FROM expenses
   GROUP BY ROLLUP(Year, Category) ORDER BY 2, 1, GROUPING_ID();
  Category  | Year |  SUM
------------+------+--------
 Books      | 2005 |  39.98
 Electrical | 2005 | 109.99
            | 2005 | 149.97
 Electrical | 2006 | 109.99
            | 2006 | 109.99
 Books      | 2007 |  29.99
 Electrical | 2007 | 229.98
            | 2007 | 259.97
 Books      | 2008 |  29.99
            | 2008 |  29.99
            |      | 549.92
(11 rows)

您可以使用 CUBE 聚合来执行所有可能的类别和年份开支分组。以下查询按分组进行了排序,并返回了所有可能的分组:

=> SELECT Category, Year, SUM(Amount) FROM expenses
   GROUP BY CUBE(Category, Year) ORDER BY 1, 2, GROUPING_ID();
 Category   | Year |  SUM
------------+------+--------
 Books      | 2005 |  39.98
 Books      | 2007 |  29.99
 Books      | 2008 |  29.99
 Books      |      |  99.96
 Electrical | 2005 | 109.99
 Electrical | 2006 | 109.99
 Electrical | 2007 | 229.98
 Electrical |      | 449.96
            | 2005 | 149.97
            | 2006 | 109.99
            | 2007 | 259.97
            | 2008 |  29.99
            |      | 549.92

结果包括每个类别每年的小计以及不分年份或类别的所有交易的总计 ($549.92)。

ROLLUPCUBEGROUPING SETS 在分组列生成了 NULL 值以确定小计。如果表数据包括 NULL 值,将这些值与小计中的 NULL 值区分有时非常具有挑战性。

在之前的输出中,Year 列中的 NULL 值表示行在 Category 列(而不是在两个列)进行了分组。在这种情况下,ROLLUP 添加了 NULL 值,以表示小计行。

9.3 - 多级别分组的聚合和函数

Vertica 提供了在多个级别对 GROUP BY 查询的结果进行分组的几个聚合和函数。

用于多级别分组的聚合

使用以下聚合进行多级别分组:

  • ROLLUP 会自动执行小计聚合。ROLLUP 会在不同级别跨多个维度执行一个或多个聚合。

  • CUBE 会对您指定的 CUBE 表达式的所有排列执行聚合。

  • GROUPING SETS 允许您指定所需的聚合分组。

您可以在 GROUPING SETS 表达式内使用 CUBE 或 ROLLUP 表达式。否则,您不能嵌套多级别聚合表达式。

分组函数

您可以使用具有 ROLLUP、CUBE 和 GROUPING SETS 的以下三个分组函数中的一个:

  • GROUP_ID 会返回一个或多个以零 (0) 开头的数字来唯一确定重复集。

  • GROUPING_ID 会为每个分组组合生成唯一 ID。

  • GROUPING 会为每个分组组合确定列是否是此分组的一部分。此函数也会将数据中的 NULL 值与 NULL 分组小计加以区分。

这些函数通常与多级别聚合一起使用。

9.4 - GROUP BY 的聚合表达式

您可以在 GROUPING SETS 聚合内包括 CUBE 和 ROLLUP 聚合。请注意,CUBE 和 ROLLUP 聚合会导致大量输出。但是,您可以使用 GROUPING SETS 仅返回指定结果来避免大量输出。

...GROUP BY a,b,c,d,ROLLUP(a,b)...
...GROUP BY a,b,c,d,CUBE((a,b),c,d)...

您不能将任何聚合包含在 CUBE 或 ROLLUP 聚合表达式中。

您可以将多个 GROUPING SETS、CUBE 或 ROLLUP 聚合附加到同一个查询中。

...GROUP BY a,b,c,d,CUBE(a,b),ROLLUP (c,d)...
...GROUP BY a,b,c,d,GROUPING SETS ((a,d),(b,c),CUBE(a,b));...
...GROUP BY a,b,c,d,GROUPING SETS ((a,d),(b,c),(a,b),(a),(b),())...

9.5 - 投影中的预聚合数据

如果查询使用了聚合函数,例如 SUMCOUNT,那么它们在使用已经包含聚合数据的投影时,可以更有效地执行。此效率提升在对大规模数据进行查询时尤其显著。

例如,电网公司要读取 3000 万智能电表的数据,而这些电表每五分钟提供一次数据。公司会将每个读数记录在数据库表中。在某个给定年度,此表中添加了三万亿条记录。

电网公司可以使用包含聚合函数的查询来分析这些记录,以便执行以下任务:

  • 建立使用模式。

  • 检测欺诈。

  • 衡量与天气模式或价格变动等外部事件的相关性。

要优化查询响应时间,您可以创建聚合投影,由该投影存储聚合后的数据。

聚合投影

Vertica 提供几种类型的投影来存储从聚合函数或表达式中返回的数据。

  • 实时聚合投影:包含通过锚表中的列聚合列值的列的投影。您还可以定义包括 用户定义的变换函数 的实时聚合投影。

  • Top-K 投影:一种实时聚合投影,用于从选定行的一个分区中返回前 k 行。创建一个满足 Top-K 查询条件的 Top-K 投影。

  • 预聚合 UDTF 结果的投影:调用用户定义的转换函数 (UDTF) 的实时聚合投影。为了在查询这种类型的投影时最大限度降低开销,Vertica 会在后台处理 UDTF 函数,并将其结果存储在磁盘上。

  • 包含表达式的投影:具有通过锚表列计算列值的列的投影。

建议使用

  • 针对大型数据集进行查询时,聚合投影最为有用。

  • 为获得最佳查询性能,LAP 投影的大小应该是锚表的小子集,理想情况下是锚表的 1% 到 10 % 之间或更小(如有可能)。

限制

  • 如果在具有实时聚合投影的目标表上执行 MERGE 操作,则这些操作必须优化

  • 您不能使用实时聚合投影更新或删除临时表中的数据。

要求

手动恢复未正常关闭的数据库时,实时聚合投影可能需要一些时间来刷新。

9.5.1 - 实时聚合投影

实时聚合投影包含一些列,这些列的值是从其锚表的列中聚合而成的。当您将数据加载到表中时,Vertica 首先会聚合数据,然后将其加载到实时聚合投影中。例如,通过 INSERTCOPY 执行后续加载操作时,Vertica 会使用新数据重新计算聚合并更新投影。

9.5.1.1 - 实时聚合投影支持的函数

Vertica 可以在以下聚合函数的实时聚合投影中聚合结果:

带 DISTINCT 的聚合函数

实时聚合投影可以支持包含用关键字 DISTINCT 限定的聚合函数的查询。需要满足以下要求:

  • 聚合表达式的求值结果必须为非常数。

  • 投影的 GROUP BY 子句必须指定聚合表达式。

例如,以下查询使用了 SUM(DISTINCT) 来计算给定区域的所有唯一薪金的总额:

SELECT customer_region, SUM(DISTINCT annual_income)::INT
   FROM customer_dimension GROUP BY customer_region;

此查询可以使用以下实时聚合投影,而该投影在其 GROUP BY 子句中指定了聚合列 (annual_income):

CREATE PROJECTION public.TotalRegionalIncome
(
 customer_region,
 annual_income,
 Count
)
AS
 SELECT customer_dimension.customer_region,
        customer_dimension.annual_income,
        count(*) AS Count
 FROM public.customer_dimension
 GROUP BY customer_dimension.customer_region,
          customer_dimension.annual_income
;

9.5.1.2 - 创建实时聚合投影

您可以使用以下语法定义一个实时聚合投影:


=> CREATE PROJECTION proj-name AS
      SELECT select-expression FROM table
      GROUP BY group-expression;

有关完整的语法选项,请参阅CREATE PROJECTION

例如:

=> CREATE PROJECTION clicks_agg AS
   SELECT page_id, click_time::DATE click_date, COUNT(*) num_clicks FROM clicks
   GROUP BY page_id, click_time::DATE KSAFE 1;

有关详细讨论,请参阅实时聚合投影示例

要求

以下要求适用于实时聚合投影:

  • 投影不能未分段。

  • SELECTGROUP BY 列的顺序必须相同。GROUP BY 表达式必须在 SELECT 列表的开头。

限制

以下限制适用于实时聚合投影:

  • 如果在具有实时聚合投影的目标表上执行 MERGE 操作,则这些操作必须优化

  • 实时聚合投影只能引用一个表。

  • Vertica 不会将实时聚合投影视为超投影,即使是包含所有表格列的投影也是如此。

  • 您不能修改实时聚合投影中包含的列的锚表元数据,例如列的数据类型或默认值。您也不能删除这些列。要进行这些更改,首先删除与表关联的所有实时聚合和 Top-K 投影。

9.5.1.3 - 实时聚合投影示例

此示例显示了如何使用下面的 clicks 表跟踪给定网页上的用户点击:


=> CREATE TABLE clicks(
   user_id INTEGER,
   page_id INTEGER,
   click_time TIMESTAMP NOT NULL);

您可以使用以下查询聚合用户特定的活动:


=> SELECT page_id, click_time::DATE click_date, COUNT(*) num_clicks FROM clicks
   WHERE click_time::DATE = '2015-04-30'
   GROUP BY page_id, click_time::DATE ORDER BY num_clicks DESC;

要提升此查询的性能,请创建可以统计每位用户的单击数的实时聚合投影:

=> CREATE PROJECTION clicks_agg AS
   SELECT page_id, click_time::DATE click_date, COUNT(*) num_clicks FROM clicks
   GROUP BY page_id, click_time::DATE KSAFE 1;

查询 clicks 表了解用户点击数时,Vertica 通常会将查询定向到实时聚合投影 clicks_agg。随着更多数据加载到 clicks,Vertica 会预聚合新数据并更新 clicks_agg,所以查询始终都会返回最新数据。

例如:

=> SELECT page_id, click_time::DATE click_date, COUNT(*) num_clicks FROM clicks
    WHERE click_time::DATE = '2015-04-30' GROUP BY page_id, click_time::DATE
    ORDER BY num_clicks DESC;
 page_id | click_date | num_clicks
---------+------------+------------
    2002 | 2015-04-30 |         10
    3003 | 2015-04-30 |          3
    2003 | 2015-04-30 |          1
    2035 | 2015-04-30 |          1
   12034 | 2015-04-30 |          1
(5 rows)

9.5.2 - Top-K 投影

Top-K 查询会从选定行的分区返回前 k 个行。Top-K 投影可以显著提升 Top-K 查询的性能。例如,您可以定义一个含有以下三列的表来存储气量表读数:气量表 ID、气量表读数对应的时间以及读数值:


=> CREATE TABLE readings (
    meter_id INT,
    reading_date TIMESTAMP,
    reading_value FLOAT);

以此表为例,以下 Top-K 查询会针对给定气量表返回五个最近的气量表读数:


SELECT meter_id, reading_date, reading_value FROM readings
    LIMIT 5 OVER (PARTITION BY meter_id ORDER BY reading_date DESC);

要提升此查询的性能,您可以创建 Top-K 投影,即一种特殊类型的实时聚合投影:


=> CREATE PROJECTION readings_topk (meter_id, recent_date, recent_value)
    AS SELECT meter_id, reading_date, reading_value FROM readings
    LIMIT 5 OVER (PARTITION BY meter_id ORDER BY reading_date DESC);

创建此 Top-K 投影并加载其数据(通过 START_REFRESHREFRESH)后,Vertica 通常会将查询重定向到投影,并返回预聚合数据。

9.5.2.1 - 创建 Top-K 投影

您可以使用以下语法定义一个 Top-K 投影:


CREATE PROJECTION proj-name [(proj-column-spec)]
    AS SELECT select-expression FROM table
    LIMIT num-rows OVER (PARTITION BY expression ORDER BY column-expr);

有关完整的语法选项,请参阅CREATE PROJECTION

例如:


=> CREATE PROJECTION readings_topk (meter_id, recent_date, recent_value)
    AS SELECT meter_id, reading_date, reading_value FROM readings
    LIMIT 5 OVER (PARTITION BY meter_id ORDER BY reading_date DESC);

有关详细讨论,请参阅Top-K 投影示例

要求

以下要求适用于 Top-K 投影:

  • 投影不能未分段。

  • 窗口分区子句必须使用 PARTITION BY

  • PARTITION BYORDER BY 子句中的列必须是 SELECT 列表中指定的第一个列。

  • 您必须使用 LIMIT 选项来创建 Top-K 投影,而不是使用子查询。例如,以下 SELECT 语句是等效的:

    
    => SELECT symbol, trade_time last_trade, price last_price FROM (
        SELECT symbol, trade_time, price, ROW_NUMBER()
        OVER(PARTITION BY symbol ORDER BY trade_time DESC) rn FROM trades) trds WHERE rn <=1;
    
    => SELECT symbol, trade_time last_trade, price last_price FROM trades
        LIMIT 1 OVER(PARTITION BY symbol ORDER BY trade_time DESC);
    

    两个语句返回了相同的结果:

    
          symbol      |      last_trade       | last_price
    ------------------+-----------------------+------------
     AAPL             | 2011-11-10 10:10:20.5 |   108.4000
     HPQ              | 2012-10-10 10:10:10.4 |    42.0500
    (2 rows)
    

    预聚合数据以供两个查询使用的 Top-K 投影必须包括 LIMIT 选项:

    
    => CREATE PROJECTION trades_topk AS
         SELECT symbol, trade_time last_trade, price last_price FROM trades
         LIMIT 1 OVER(PARTITION BY symbol ORDER BY trade_time DESC);
    

限制

以下限制适用于 Top-K 投影:

  • Top-K 投影只能引用一个表。

  • Vertica 不会将 Top-K 投影视为超投影,即使是包含所有表格列的投影也是如此。

  • 您不能修改 Top-K 投影中包含的列的锚表元数据,例如列的数据类型或默认值。您也不能删除这些列。要进行这些更改,首先删除与表关联的所有实时聚合和 Top-K 投影。

9.5.2.2 - Top-K 投影示例

以下示例说明了如何查询具有两个 Top-K 投影的表,检索每个股票代码的最近交易和交易日的最新交易的信息。

  1. 创建一个包含单个股票交易信息的表:

    • 股票代码

    • Timestamp

    • 每股价格

    • 股数

    => CREATE TABLE trades(
        symbol CHAR(16) NOT NULL,
        trade_time TIMESTAMP NOT NULL,
        price NUMERIC(12,4),
        volume INT )
        PARTITION BY (EXTRACT(year from trade_time) * 100 +
        EXTRACT(month from trade_time));
    
  2. 将数据加载到表中:

    
    INSERT INTO trades VALUES('AAPL','2010-10-10 10:10:10'::TIMESTAMP,100.00,100);
    INSERT INTO trades VALUES('AAPL','2010-10-10 10:10:10.3'::TIMESTAMP,101.00,100);
    INSERT INTO trades VALUES ('AAPL','2011-10-10 10:10:10.5'::TIMESTAMP,106.1,1000);
    INSERT INTO trades VALUES ('AAPL','2011-10-10 10:10:10.2'::TIMESTAMP,105.2,500);
    INSERT INTO trades VALUES ('HPQ','2012-10-10 10:10:10.2'::TIMESTAMP,42.01,400);
    INSERT INTO trades VALUES ('HPQ','2012-10-10 10:10:10.3'::TIMESTAMP,42.02,1000);
    INSERT INTO trades VALUES ('HPQ','2012-10-10 10:10:10.4'::TIMESTAMP,42.05,100);
    COMMIT;
    
  3. 创建包含 trades 表的以下信息的两个 Top-K 投影:

    为每个股票代码返回最近交易。

    
    => CREATE PROJECTION trades_topk_a AS SELECT symbol, trade_time last_trade, price last_price
           FROM trades LIMIT 1 OVER(PARTITION BY symbol ORDER BY trade_time DESC);
    
    => SELECT symbol, trade_time last_trade, price last_price FROM trades
       LIMIT 1 OVER(PARTITION BY symbol ORDER BY trade_time DESC);
    
          symbol      |      last_trade       | last_price
    ------------------+-----------------------+------------
     HPQ              | 2012-10-10 10:10:10.4 |    42.0500
     AAPL             | 2011-10-10 10:10:10.5 |   106.1000
    (2 rows)
    


    为每个股票代码返回每个交易日的最新交易。

    
    => CREATE PROJECTION trades_topk_b
        AS SELECT symbol, trade_time::DATE trade_date, trade_time, price close_price, volume
        FROM trades LIMIT 1 OVER(PARTITION BY symbol, trade_time::DATE ORDER BY trade_time DESC);
    
    
    => SELECT symbol, trade_time::DATE trade_date, trade_time, price close_price, volume
        FROM trades LIMIT 1 OVER(PARTITION BY symbol, trade_time::DATE ORDER BY trade_time DESC);
    
          symbol      | trade_date |      trade_time       | close_price | volume
    ------------------+------------+-----------------------+-------------+--------
     HPQ              | 2012-10-10 | 2012-10-10 10:10:10.4 |     42.0500 |    100
     AAPL             | 2011-10-10 | 2011-10-10 10:10:10.5 |    106.1000 |   1000
     AAPL             | 2010-10-10 | 2010-10-10 10:10:10.3 |    101.0000 |    100
    (3 rows)
    

在每个场景中,Vertica 都会将对 trades 表的查询重定向到相应的 Top-K 投影,并从中返回聚合数据。随着更多数据加载到此表,Vertica 会预聚合新数据并更新 Top-K 投影,所以查询始终都会返回最新数据。

9.5.3 - 预聚合 UDTF 结果

CREATE PROJECTION 可以定义调用用户定义的转换函数 (UDTF) 的实时聚合投影。为了在查询这些投影时最大限度降低开销,Vertica 会在后台处理这些函数,并将其结果存储在磁盘上。

定义含 UDTF 的投影

投影定义通过两种方法之一来确定其 UDTF 特征:

  • 将 UDTF 标识为预传递 UDTF,此时 UDTF 会在新加载的数据存储在投影 ROS 容器之前对其进行转换。

  • 将 UDTF 标识为批量 UDTF,此时 UDTF 会聚合和存储投影数据。

投影定义在窗口分区子句中通过关键字 PREPASSBATCH 将 UDTF 标识为预传递 UDTF 或批量 UDTF。投影可以指定一个预传递或批量 UDTF,或者包含两者(请参阅 UDTF 规范选项)。

在所有情况下,投影都在 PARTITION BY 列上隐式地分段和排序。

UDTF 规范选项

投影可以批量调用和预传递单个 UDTF 或 UDTF 组合。

单个预传递 UDTF:

将数据加载到投影的锚表时(例如通过 COPY 或 INSERT 语句),Vertica 会调用预传递 UDTF。预传递 UDTF 会转换新数据,然后将转换后的数据存储到投影的 ROS 容器中。

使用以下语法:


CREATE PROJECTION [ IF NOT EXISTS ] [[database.]schema.]projection
[ (
   { projection-column | grouped-clause
   [ ENCODING encoding-type ]
   [ ACCESSRANK integer ] }[,...]
) ]

AS SELECT { table-column | expr-with-table-columns }[,...], prepass-udtf(prepass-args)
    OVER (PARTITION PREPASS BY partition-column-expr[,...])
    [ AS (prepass-output-columns) ] FROM table [[AS] alias]

单个批量 UDTF

单个调用时,批量 UDTF 会在执行合并、数据加载和查询操作时转换并聚合投影数据。UDTF 会将聚合结果存储到投影的 ROS 容器中。聚合会在合并和加载操作期间累计,并在查询执行时完成(如有需要)。

使用以下语法:


CREATE PROJECTION [ IF NOT EXISTS ] [[database.]schema.]projection
[ (
   { projection-column | grouped-clause
   [ ENCODING encoding-type ]
   [ ACCESSRANK integer ]  }[,...]
) ]
AS SELECT { table-column | expr-with-table-columns }[,...], batch-udtf(batch-args)
   OVER (PARTITION BATCH BY partition-column-expr[,...])
   [ AS (batch-output-columns) FROM table [ [AS] alias ]

组合预传递和批量 UDTF

您可以定义一个含子查询的投影,并用它来调用预传递 UDTF。该预传递 UDTF 会将转换后的数据返回给外部批量查询。然后,批量 UDTF 会以迭代方式在多个合并操作期间聚合结果。它会在执行查询时完成聚合(如有需要)。

使用以下语法:


CREATE PROJECTION [ IF NOT EXISTS ] [[database.]schema.]projection
[ (
   { projection-column | grouped-clause
   [ ENCODING encoding-type ]
   [ ACCESSRANK integer ] }[,...]
) ]
AS SELECT { table-column | expr-with-table-columns }[,...], batch-udtf(batch-args)
   OVER (PARTITION BATCH BY partition-column-expr[,...]) [ AS (batch-output-columns) ] FROM (
      SELECT { table-column | expr-with-table-columns }[,...], prepass-udtf (prepass-args)
      OVER (PARTITION PREPASS BY partition-column-expr[,...]) [ AS (prepass-output-columns) ] FROM table ) sq-ref

示例

单个预传递 UDTF:
以下示例说明了如何使用从出现多次的文本文档字符串中提取内容的 UDTFtext_index

以下投影指定按预传递 UDTF 调用 text_index


=> CREATE TABLE documents ( doc_id INT PRIMARY KEY, text VARCHAR(140));

=> CREATE PROJECTION index_proj (doc_id, text)
     AS SELECT doc_id, text_index(doc_id, text)
     OVER (PARTITION PREPASS BY doc_id) FROM documents;

将数据加载到锚表 documents 时即会调用 UDTF。 text_index 会转换新加载的数据,并且 Vertica 会将转换后的数据存储在实时聚合投影 ROS 容器中。

所以,如果您将以下数据加载到 documents

=> INSERT INTO documents VALUES
(100, 'A SQL Query walks into a bar. In one corner of the bar are two tables.
 The Query walks up to the tables and asks - Mind if I join you?');
 OUTPUT
--------
      1
(1 row)

text_index 会转换新加载的数据,并且会将其存储在投影 ROS 容器中。当您查询投影时,它会返回以下结果:


doc_id | frequency |     term
-------+-----------+--------------
100    | 2         | bar
100    | 2         | Query
100    | 2         | tables
100    | 2         | the
100    | 2         | walks

组合预传递和批量 UDTF
以下投影分别指定了预传递和批量 UDTF stv_intersectaggregate_classified_points


CREATE TABLE points( point_id INTEGER, point_type VARCHAR(10), coordinates GEOMETRY(100));

CREATE PROJECTION aggregated_proj
   AS SELECT point_type, aggregate_classified_points( sq.point_id, sq.polygon_id)
   OVER (PARTITION BATCH BY point_type)
   FROM
      (SELECT point_type, stv_intersect(
         point_id, coordinates USING PARAMETERS index=‘polygons’ )
       OVER (PARTITION PREPASS BY point_type) AS (point_id, polygon_id) FROM points) sq;

预传递查询 UDTF stv_intersect 将其结果(点集和匹配的多边形 ID)返回给外部批量查询。然后,外部批量查询会调用 UDTF aggregate_classified_points。Vertica 会在合并操作整合投影数据时聚合 aggregate_classified_points 返回的结果集。查询投影时会发生最终聚合(如有需要)。

批量 UDTF 参数的名称和顺序必须与预传递 UDTF stv_intersect 返回的输出列完全匹配。在此示例中,预传递子查询显式命名了预传递 UDTF 输出列 point_idpolygon_id。因此,批量 UDTF 实参在名称和顺序方面与它们匹配: sq.point_idsq.polygon_id

9.5.4 - 通过表达式聚合数据

您可以创建其中的一个或多个列由表达式定义的投影。表达式可以引用一个或多个锚表列。例如,下表包含两个整数列 ab

=> CREATE TABLE values (a INT, b INT);

您可以创建含表达式的投影,利用表达式将列 c 的值计算为 ab 的乘积:

=> CREATE PROJECTION values_product (a, b, c)
   AS SELECT a, b, a*b FROM values SEGMENTED BY HASH(a) ALL NODES KSAFE;

当您将数据加载到此投影中时,Vertica 会解析列 c 中的表达式 a*b。然后,您可以查询投影而不是锚表。Vertica 会返回预计算的数据并避免资源密集型计算产生的开销。

在投影中使用表达式还可以让您对表达式的计算结果的数据进行排序或分段,而不是对单个列值进行排序。

支持用户定义的标量函数

Vertica 将用户定义的标量函数 (UDSF) 视为类似于其他表达式。在每个加载操作中,UDSF 会被调用并返回其结果。Vertica 将这些结果存储在磁盘上,并在您直接查询投影时返回这些结果。

在以下示例中,投影 points_p1 指定了在将数据加载到锚表 points 时调用的 UDSF zorder。当数据加载到投影时,Vertica 将调用此函数,并存储其结果,以便在未来查询时能够快速访问。

=> CREATE TABLE points(point_id INTEGER, lat NUMERIC(12,9), long NUMERIC(12,9));

=> CREATE PROJECTION points_p1
     AS SELECT point_id, lat, long, zorder(lat, long) zorder FROM points
     ORDER BY zorder(lat, long) SEGMENTED BY hash(point_id) ALL NODES;

要求

  • 所有 ORDER BY 表达式必须位于 SELECT 列表。

  • 所有投影列必须经过命名。

限制

  • 如果在具有实时聚合投影的目标表上执行 MERGE 操作,则这些操作必须优化

  • 与实时聚合投影不同,Vertica 不会将含表达式的查询重定向到等效现有投影。

  • 投影表达式必须不可变,换而言之,它们必须始终返回相同的结果。例如,投影不能包括使用 TO CHAR(取决于区域设置)或 RANDOM(在每次调用时返回不同的结果)的表达式。

  • 投影表达式不能包括 Vertica 元函数,例如 ADVANCE_EPOCHANALYZE_STATISTICSEXPORT_TABLESSTART_REFRESH

9.5.4.1 - 通过表达式查询数据示例

以下示例使用了包含两个整数列 ab 的表:

=> CREATE TABLE values (a INT, b INT);

您可以创建含表达式的投影,利用表达式将列 c 的值计算为 ab 的乘积:

=> CREATE PROJECTION values_product (a, b, c)
   AS SELECT a, b, a*b FROM values SEGMENTED BY HASH(a) ALL NODES KSAFE;
=> COPY values FROM STDIN DELIMITER ',' DIRECT;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 3,11
>> 3,55
>> 8,9
>> 8,23
>> 16,41
>> 22,111
>> \.
=>

要查询此投影,请使用 Vertica 分配给此投影或其伙伴实例投影的名称。例如,以下查询针对之前定义的投影的不同实例,并且返回相同的结果:

=> SELECT * FROM values_product_b0;
=> SELECT * FROM values_product_b1;

以下示例查询了锚表:

=> SELECT * FROM values;
 a  |  b
----+-----
  3 |  11
  3 |  55
  8 |   9
  8 |  23
 16 |  41
 22 | 111

假设使用之前创建的投影,查询该投影会返回以下值:

VMart=> SELECT * FROM values_product_b0;
 a  |  b  | product
----+-----+---------
  3 |  11 |      33
  3 |  55 |     165
  8 |   9 |      72
  8 |  23 |     184
 16 |  41 |     656
 22 | 111 |    2442

9.5.5 - 系统表中的聚合信息

您可以查询以下系统表字段,了解有关实时聚合投影、Top-K 投影以及含表达式的投影的信息: