统计信息模式#

警告

本规范目前视为实验性标准。

原理#

统计信息对于快速查询处理非常有用。许多查询引擎使用统计信息来优化其查询计划。

Apache Arrow 格式本身不包含统计信息,但可以作为 Apache Arrow 数据读取的其他格式可能包含统计信息。例如,Apache Parquet C++ 实现可以读取 Apache Parquet 文件作为 Apache Arrow 数据,而该 Apache Parquet 文件可能包含统计信息。

为了便于交换,我们将统计信息的表示形式标准化为 Apache Arrow 数组。

用例#

以下是 Arrow C 流接口 的用例之一

  1. 模块 A 读取 Apache Parquet 文件并将其作为 Apache Arrow 数据。

  2. 模块 A 通过 Arrow C 流接口将读取到的 Apache Arrow 数据传递给模块 B。

  3. 模块 B 处理传递过来的 Apache Arrow 数据。

如果模块 A 能够将 Apache Parquet 文件关联的统计信息传递给模块 B,模块 B 就可以使用这些统计信息来优化其查询计划。

例如,DuckDB 使用了这种方法,但由于缺乏表示 Apache Arrow 数据统计信息的标准化方式,DuckDB 此前无法使用这些统计信息。

目标#

  • 建立一种将统计信息表示为 Apache Arrow 数组的标准方式。

非目标#

  • 建立一种传递表示统计信息的 Apache Arrow 数组的标准方式。

  • 建立一种将统计信息嵌入到 Apache Arrow 数组本身的标准方式。

模式#

本规范仅提供统计信息的模式。这是将 Apache Arrow 数据集的统计信息表示为 Apache Arrow 数据的规范模式。

以下是统计信息模式的大纲

struct<
  column: int32,
  statistics: map<
    key: dictionary<values: utf8, indices: int32>,
    items: dense_union<...all needed types...>
  >
>

以下是顶层 struct 的详细信息

姓名

数据类型

可为空

备注

column(列)

int32

true

以 0 为基准的列索引;如果统计信息描述的是整个表或记录批次(record batch),则为 null。

列索引的计算遵循与 RecordBatch 消息 相同的规则。

statistics(统计信息)

映射型 (map)

false

目标列、表或记录批次的统计信息。详细信息请参阅下方的独立表格。

以下是 statisticsmap 的详细信息

键或项

数据类型

可为空

备注

key(键)

dictionary<values: utf8, indices: int32>

false

字符串键即为统计信息的名称。由于同一统计信息可能在不同列中重复出现,因此使用字典编码以提高效率。精确统计值和近似统计值分配不同的键。每种统计信息均有下方说明。

items(项)

dense_union(密集联合类型)

false

统计信息的值为密集联合类型。根据键中的统计信息种类,它至少包含所有需要的类型。例如,当同时存在 int64 类型的唯一值计数统计和 float64 类型的平均字节宽度统计时,至少需要 int64float64 类型。请参阅下文每种统计信息的说明。

密集联合数组虽然有字段名称,但我们未对其进行标准化,因为可以通过类型代码访问相应的字段。因此,可以使用任何有效的字段名称。

标准统计信息#

每种统计信息类型都有一个名称,作为每一列或整张表的统计信息映射中的键。dictionary<values: utf8, indices: int32> 用于对名称进行编码,以节省空间。

我们为同一统计信息的不同变体分配不同的名称,而不是使用标志位。例如,我们为“唯一值计数(distinct count)”统计信息的精确值和近似值分配不同的名称。

冒号 : 用作命名空间分隔符,类似于 自定义应用元数据。它可以在名称中多次使用。

ARROW 前缀是本规范当前及未来版本中预定义统计信息名称的保留命名空间。用户定义的统计信息不得使用此名称。例如,您可以使用您的产品名称作为命名空间,如 MY_PRODUCT:my_statistics:exact

以下是预定义的统计信息名称

姓名

数据类型

备注

ARROW:average_byte_width:exact

float64

目标列中行的平均大小(以字节为单位)。(精确)

ARROW:average_byte_width:approximate

float64

目标列中行的平均大小(以字节为单位)。(近似)

ARROW:distinct_count:exact

int64

目标列中唯一值的数量。(精确)

ARROW:distinct_count:approximate

float64

目标列中唯一值的数量。(近似)

ARROW:max_byte_width:exact

int64

目标列中行的最大大小(以字节为单位)。(精确)

ARROW:max_byte_width:approximate

float64

目标列中行的最大大小(以字节为单位)。(近似)

ARROW:max_value:exact

取决于目标

目标列中的最大值。(精确)

ARROW:max_value:approximate

取决于目标

目标列中的最大值。(近似)

ARROW:min_value:exact

取决于目标

目标列中的最小值。(精确)

ARROW:min_value:approximate

取决于目标

目标列中的最小值。(近似)

ARROW:null_count:exact

int64

目标列中 null 值的数量。(精确)

ARROW:null_count:approximate

float64

目标列中 null 值的数量。(近似)

ARROW:row_count:exact

int64

目标表、记录批次或数组中的行数。(精确)

ARROW:row_count:approximate

float64

目标表、记录批次或数组中的行数。(近似)

如果您发现某种可能对多个系统有用的统计信息,请在 Apache Arrow 开发邮件列表上提出建议。

当统计信息的生产者和消费者遵循预先约定的统计信息规范时,互操作性会得到提升。

示例#

以下是一些示例以供参考。

简单记录批次#

Schema

vendor_id: int32
passenger_count: int64

数据

vendor_id:       [5, 1, 5, 1, 5]
passenger_count: [1, 1, 2, 0, null]

统计信息

目标

姓名

记录批次

行数

5

vendor_id

null 的数量

0

唯一值的数量

2

最大值

5

最小值

1

passenger_count

null 的数量

1

唯一值的数量

3

最大值

2

最小值

0

列索引

索引

目标

0

vendor_id

1

passenger_count

统计模式

struct<
  column: int32,
  statistics: map<
    key: dictionary<values: utf8, indices: int32>,
    items: dense_union<0: int64>
  >
>

统计信息数组

column: [
  null, # record batch
  0,    # vendor_id
  1,    # passenger_count
]
statistics:
  offsets: [
    0,
    1, # record batch: 1 value: [0]
    5, # vendor_id: 4 values: [1, 2, 3, 4]
    9, # passenger_count: 4 values: [5, 6, 7, 8]
  ]
  key:
    values: [
      "ARROW:row_count:exact",
      "ARROW:null_count:exact",
      "ARROW:distinct_count:exact",
      "ARROW:max_value:exact",
      "ARROW:min_value:exact",
    ]
    indices: [
      0, # "ARROW:row_count:exact"
      1, # "ARROW:null_count:exact"
      2, # "ARROW:distinct_count:exact"
      3, # "ARROW:max_value:exact"
      4, # "ARROW:min_value:exact"
      1, # "ARROW:null_count:exact"
      2, # "ARROW:distinct_count:exact"
      3, # "ARROW:max_value:exact"
      4, # "ARROW:min_value:exact"
    ]
  items:
    children:
      0: [ # int64
        5, # record batch: "ARROW:row_count:exact"
        0, # vendor_id: "ARROW:null_count:exact"
        2, # vendor_id: "ARROW:distinct_count:exact"
        5, # vendor_id: "ARROW:max_value:exact"
        1, # vendor_id: "ARROW:min_value:exact"
        1, # passenger_count: "ARROW:null_count:exact"
        3, # passenger_count: "ARROW:distinct_count:exact"
        2, # passenger_count: "ARROW:max_value:exact"
        0, # passenger_count: "ARROW:min_value:exact"
      ]
    types: [ # all values are int64
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
    ]
    offsets: [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
    ]

复杂记录批次#

使用嵌套类型。

Schema

col1: struct<a: int32, b: list<item: int64>, c: float64>
col2: utf8

数据

col1: [
        {a: 1, b: [20, 30, 40], c: 2.9},
        {a: 2, b: null,         c: -2.9},
        {a: 3, b: [99],         c: null},
      ]
col2: ["x", null, "z"]

统计信息

目标

姓名

记录批次

行数

3

col1

null 的数量

0

col1.a

null 的数量

0

唯一值的数量

3

近似最大值

5

近似最小值

0

col1.b

null 的数量

1

col1.b.item

最大值

99

最小值

20

col1.c

null 的数量

1

近似最大值

3.0

近似最小值

-3.0

col2

null 的数量

1

唯一值的数量

2

列索引

索引

目标

0

col1

1

col1.a

2

col1.b

3

col1.b.item

4

col1.c

5

col2

请参阅 RecordBatch 消息 了解如何计算列索引。

统计模式

struct<
  column: int32,
  statistics: map<
    key: dictionary<values: utf8, indices: int32>,
    items: dense_union<
      # For the number of rows, the number of nulls and so on.
      0: int64,
      # For the max/min values of col1.c.
      1: float64
    >
  >
>

统计信息数组

column: [
  null, # record batch
  0,    # col1
  1,    # col1.a
  2,    # col1.b
  3,    # col1.b.item
  4,    # col1.c
  5,    # col2
]
statistics:
  offsets: [
    0,
    1,  # record batch: 1 value: [0]
    2,  # col1: 1 value: [1]
    6,  # col1.a: 4 values: [2, 3, 4, 5]
    7,  # col1.b: 1 value: [6]
    9,  # col1.b.item: 2 values: [7, 8]
    12, # col1.c: 3 values: [9, 10, 11]
    14, # col2: 2 values: [12, 13]
  ]
  key:
    values: [
      "ARROW:row_count:exact",
      "ARROW:null_count:exact",
      "ARROW:distinct_count:exact",
      "ARROW:max_value:approximate",
      "ARROW:min_value:approximate",
      "ARROW:max_value:exact",
      "ARROW:min_value:exact",
    ]
    indices: [
      0, # "ARROW:row_count:exact"
      1, # "ARROW:null_count:exact"
      1, # "ARROW:null_count:exact"
      2, # "ARROW:distinct_count:exact"
      3, # "ARROW:max_value:approximate"
      4, # "ARROW:min_value:approximate"
      1, # "ARROW:null_count:exact"
      5, # "ARROW:max_value:exact"
      6, # "ARROW:min_value:exact"
      1, # "ARROW:null_count:exact"
      3, # "ARROW:max_value:approximate"
      4, # "ARROW:min_value:approximate"
      1, # "ARROW:null_count:exact"
      2, # "ARROW:distinct_count:exact"
    ]
  items:
    children:
      0: [ # int64
        3,  # record batch: "ARROW:row_count:exact"
        0,  # col1: "ARROW:null_count:exact"
        0,  # col1.a: "ARROW:null_count:exact"
        3,  # col1.a: "ARROW:distinct_count:exact"
        5,  # col1.a: "ARROW:max_value:approximate"
        0,  # col1.a: "ARROW:min_value:approximate"
        1,  # col1.b: "ARROW:null_count:exact"
        99, # col1.b.item: "ARROW:max_value:exact"
        20, # col1.b.item: "ARROW:min_value:exact"
        1,  # col1.c: "ARROW:null_count:exact"
        1,  # col2: "ARROW:null_count:exact"
        2,  # col2: "ARROW:distinct_count:exact"
      ]
      1: [ # float64
        3.0,  # col1.c: "ARROW:max_value:approximate"
        -3.0, # col1.c: "ARROW:min_value:approximate"
      ]
    types: [
      0, # int64: record batch: "ARROW:row_count:exact"
      0, # int64: col1: "ARROW:null_count:exact"
      0, # int64: col1.a: "ARROW:null_count:exact"
      0, # int64: col1.a: "ARROW:distinct_count:exact"
      0, # int64: col1.a: "ARROW:max_value:approximate"
      0, # int64: col1.a: "ARROW:min_value:approximate"
      0, # int64: col1.b: "ARROW:null_count:exact"
      0, # int64: col1.b.item: "ARROW:max_value:exact"
      0, # int64: col1.b.item: "ARROW:min_value:exact"
      0, # int64: col1.c: "ARROW:null_count:exact"
      1, # float64: col1.c: "ARROW:max_value:approximate"
      1, # float64: col1.c: "ARROW:min_value:approximate"
      0, # int64: col2: "ARROW:null_count:exact"
      0, # int64: col2: "ARROW:distinct_count:exact"
    ]
    offsets: [
      0,  # int64: record batch: "ARROW:row_count:exact"
      1,  # int64: col1: "ARROW:null_count:exact"
      2,  # int64: col1.a: "ARROW:null_count:exact"
      3,  # int64: col1.a: "ARROW:distinct_count:exact"
      4,  # int64: col1.a: "ARROW:max_value:approximate"
      5,  # int64: col1.a: "ARROW:min_value:approximate"
      6,  # int64: col1.b: "ARROW:null_count:exact"
      7,  # int64: col1.b.item: "ARROW:max_value:exact"
      8,  # int64: col1.b.item: "ARROW:min_value:exact"
      9,  # int64: col1.c: "ARROW:null_count:exact"
      0,  # float64: col1.c: "ARROW:max_value:approximate"
      1,  # float64: col1.c: "ARROW:min_value:approximate"
      10, # int64: col2: "ARROW:null_count:exact"
      11, # int64: col2: "ARROW:distinct_count:exact"
    ]

简单数组#

Schema

int64

数据

[1, 1, 2, 0, null]

统计信息

目标

姓名

数组

行数

5

null 的数量

1

唯一值的数量

3

最大值

2

最小值

0

列索引

索引

目标

0

数组

统计模式

struct<
  column: int32,
  statistics: map<
    key: dictionary<values: utf8, indices: int32>,
    items: dense_union<0: int64>
  >
>

统计信息数组

column: [
  0, # array
]
statistics:
  offsets: [
    0,
    5, # array: 5 values: [0, 1, 2, 3, 4]
  ]
  key:
    values: [
      "ARROW:row_count:exact",
      "ARROW:null_count:exact",
      "ARROW:distinct_count:exact",
      "ARROW:max_value:exact",
      "ARROW:min_value:exact",
    ]
    indices: [
      0, # "ARROW:row_count:exact"
      1, # "ARROW:null_count:exact"
      2, # "ARROW:distinct_count:exact"
      3, # "ARROW:max_value:exact"
      4, # "ARROW:min_value:exact"
    ]
  items:
    children:
      0: [ # int64
        5, # array: "ARROW:row_count:exact"
        1, # array: "ARROW:null_count:exact"
        3, # array: "ARROW:distinct_count:exact"
        2, # array: "ARROW:max_value:exact"
        0, # array: "ARROW:min_value:exact"
      ]
    types: [ # all values are int64
      0,
      0,
      0,
      0,
      0,
    ]
    offsets: [
      0,
      1,
      2,
      3,
      4,
    ]

复杂数组#

使用嵌套类型。

Schema

struct<a: int32, b: list<item: int64>, c: float64>

数据

[
  {a: 1, b: [20, 30, 40], c: 2.9},
  {a: 2, b: null,         c: -2.9},
  {a: 3, b: [99],         c: null},
]

统计信息

目标

姓名

数组

行数

3

null 的数量

0

a

null 的数量

0

唯一值的数量

3

近似最大值

5

近似最小值

0

b

null 的数量

1

b.item

最大值

99

最小值

20

c

null 的数量

1

近似最大值

3.0

近似最小值

-3.0

列索引

索引

目标

0

数组

1

a

2

b

3

b.item

4

c

请参阅 RecordBatch 消息 了解如何计算列索引。

统计模式

struct<
  column: int32,
  statistics: map<
    key: dictionary<values: utf8, indices: int32>,
    items: dense_union<
      # For the number of rows, the number of nulls and so on.
      0: int64,
      # For the max/min values of c.
      1: float64
    >
  >
>

统计信息数组

column: [
  0, # array
  1, # a
  2, # b
  3, # b.item
  4, # c
]
statistics:
  offsets: [
    0,
    2,  # array: 2 values: [0, 1]
    6,  # a: 4 values: [2, 3, 4, 5]
    7,  # b: 1 value: [6]
    9,  # b.item: 2 values: [7, 8]
    12, # c: 3 values: [9, 10, 11]
  ]
  key:
    values: [
      "ARROW:row_count:exact",
      "ARROW:null_count:exact",
      "ARROW:distinct_count:exact",
      "ARROW:max_value:approximate",
      "ARROW:min_value:approximate",
      "ARROW:max_value:exact",
      "ARROW:min_value:exact",
    ]
    indices: [
      0, # "ARROW:row_count:exact"
      1, # "ARROW:null_count:exact"
      1, # "ARROW:null_count:exact"
      2, # "ARROW:distinct_count:exact"
      3, # "ARROW:max_value:approximate"
      4, # "ARROW:min_value:approximate"
      1, # "ARROW:null_count:exact"
      5, # "ARROW:max_value:exact"
      6, # "ARROW:min_value:exact"
      1, # "ARROW:null_count:exact"
      3, # "ARROW:max_value:approximate"
      4, # "ARROW:min_value:approximate"
    ]
  items:
    children:
      0: [ # int64
        3,  # array: "ARROW:row_count:exact"
        0,  # array: "ARROW:null_count:exact"
        0,  # a: "ARROW:null_count:exact"
        3,  # a: "ARROW:distinct_count:exact"
        5,  # a: "ARROW:max_value:approximate"
        0,  # a: "ARROW:min_value:approximate"
        1,  # b: "ARROW:null_count:exact"
        99, # b.item: "ARROW:max_value:exact"
        20, # b.item: "ARROW:min_value:exact"
        1,  # c: "ARROW:null_count:exact"
      ]
      1: [ # float64
        3.0,  # c: "ARROW:max_value:approximate"
        -3.0, # c: "ARROW:min_value:approximate"
      ]
    types: [
      0, # int64: array: "ARROW:row_count:exact"
      0, # int64: array: "ARROW:null_count:exact"
      0, # int64: a: "ARROW:null_count:exact"
      0, # int64: a: "ARROW:distinct_count:exact"
      0, # int64: a: "ARROW:max_value:approximate"
      0, # int64: a: "ARROW:min_value:approximate"
      0, # int64: b: "ARROW:null_count:exact"
      0, # int64: b.item: "ARROW:max_value:exact"
      0, # int64: b.item: "ARROW:min_value:exact"
      0, # int64: c: "ARROW:null_count:exact"
      1, # float64: c: "ARROW:max_value:approximate"
      1, # float64: c: "ARROW:min_value:approximate"
    ]
    offsets: [
      0, # int64: array: "ARROW:row_count:exact"
      1, # int64: array: "ARROW:null_count:exact"
      2, # int64: a: "ARROW:null_count:exact"
      3, # int64: a: "ARROW:distinct_count:exact"
      4, # int64: a: "ARROW:max_value:approximate"
      5, # int64: a: "ARROW:min_value:approximate"
      6, # int64: b: "ARROW:null_count:exact"
      7, # int64: b.item: "ARROW:max_value:exact"
      8, # int64: b.item: "ARROW:min_value:exact"
      9, # int64: c: "ARROW:null_count:exact"
      0, # float64: c: "ARROW:max_value:approximate"
      1, # float64: c: "ARROW:min_value:approximate"
    ]