Skip to content

BITMAP 函数

函数说明

BITMAP 函数是一组用于处理位图(bitmap)的内置函数,bitmap 是存储为二进制数据类型的连续内存片段。这些函数特别适用于处理层次化聚合(如多个分组集合)时的不同值(distinct values)的计数,返回结果与 count(distinct) 一致,但更高效。

我们可以只使用一个 bit 位标识一个元素的存在与否,存在为 1,不存在则为 0,用 bitmap 的第 n 个 bit 来记录这个元素是否存在。

我们规定 bitmap 最大宽度为 32768(2^15 = 4K),对于非负整数 n,取其低 15 位(二进制)作为在 bitmap 的位置,其它高位作为位图桶 (bitmap bucket) 的编号。下图为 bitmap 的逻辑图:

每个 bucket 是一个 bitmap,由于各个 bucket 是正交的,每个 bucket 做运算 (or,bit_count) 可以只在当前 bucket 中进行,而不必关心其它 bucket。

以下是一些常用的 BITMAP 函数及其用法:

BITMAP_BUCKET_NUMBER

BITMAP_BUCKET_NUMBER() 函数的目的是确定给定值所属的 bucket 的编号。bucket 是一个更大的位集合,可以包含多个位,每个位代表数据集中的一个特定值。这个函数返回 bucket 的编号。一个 bucket 编号通常用于在执行聚合操作时对 bitmap 进行分组。

语法

> BITMAP_BUCKET_NUMBER(numeric_expr)

参数释义

参数 说明
numeric_expr 必需的。可以 cast 成非负整型的表达式。

示例

mysql> SELECT bitmap_bucket_number(0);-- 返回 0,表示属于第一个 bucket,第一个 bucket 记录 0-32767 的位置
+-------------------------+
| bitmap_bucket_number(0) |
+-------------------------+
|                       0 |
+-------------------------+
1 row in set (0.00 sec)

mysql> SELECT bitmap_bucket_number(32767);-- 返回 0,因为 32767 属于第一个 bucket 的末尾位置
+-----------------------------+
| bitmap_bucket_number(32767) |
+-----------------------------+
|                           0 |
+-----------------------------+
1 row in set (0.00 sec)

mysql> SELECT bitmap_bucket_number(32768);-- 返回 1,因为 32768 属于第二个 bucket 的起始位置
+-----------------------------+
| bitmap_bucket_number(32768) |
+-----------------------------+
|                           1 |
+-----------------------------+
1 row in set (0.00 sec)

BITMAP_BIT_POSITION

BITMAP_BIT_POSITION() 函数用于返回给定值在 bucket 中的相对位位置(从 0 开始索引到 32767 结束)。与 BITMAP_BUCKET_NUMBER() 配合使用,可以唯一标识 bitmap 中的任何数字。因为实际 BITMAP_BIT_POSITION() 标记参数的低 15 位(以二进制表示),BITMAP_BUCKET_NUMBER() 标记参数的高位。

语法

BITMAP_BIT_POSITION(numeric_expr)

参数释义

参数 说明
numeric_expr 必需的。可以 cast 成非负整型的表达式。

示例

mysql> SELECT bitmap_bit_position(0);-- 返回 0,因为 0 在第一个 bucket 的第一个位置
+------------------------+
| bitmap_bit_position(0) |
+------------------------+
|                      0 |
+------------------------+
1 row in set (0.00 sec)

mysql> SELECT bitmap_bit_position(32767);-- 返回 32767,因为 32767 在第一个 bucket 中的位置是最后一个
+----------------------------+
| bitmap_bit_position(32767) |
+----------------------------+
|                      32767 |
+----------------------------+
1 row in set (0.00 sec)

mysql> SELECT bitmap_bit_position(32768);-- 返回 0,因为 32768 在第二个 bucket 的第一个位置
+----------------------------+
| bitmap_bit_position(32768) |
+----------------------------+
|                          0 |
+----------------------------+
1 row in set (0.00 sec)

--40000 的二进制为:1001110001000000,bitmap_bit_position 记录低 15 位:001110001000000,bitmap_bucket_number 记录高位:1
mysql> select bin(bitmap_bucket_number(40000)), bin(bitmap_bit_position(40000)),bin(40000);
+----------------------------------+---------------------------------+------------------+
| bin(bitmap_bucket_number(40000)) | bin(bitmap_bit_position(40000)) | bin(40000)       |
+----------------------------------+---------------------------------+------------------+
| 1                                | 1110001000000                   | 1001110001000000 |
+----------------------------------+---------------------------------+------------------+
1 row in set (0.01 sec)

BITMAP_COUNT

BITMAP_COUNT() 函数用于计算 bitmap 中设置为 1 的位的数量,从而得到不同值的总数。这相当于对 bitmap 执行 COUNT(DISTINCT) 操作,但通常比传统的 COUNT(DISTINCT) 查询更快。

BITMAP_COUNT() 函数一般结合下述的 BITMAP_CONSTRUCT_AGG()BITMAP_OR_AGG() 函数使用。

BITMAP_CONSTRUCT_AGG

BITMAP_CONSTRUCT_AGG() 是一个聚合函数,它在数据库中用于构建 bitmap。

当需要对一组密集的非重复整数值进行计数时,BITMAP_CONSTRUCT_AGG() 函数非常有用,因为它可以高效地将这些值转换为 bitmap 形式。

语法

BITMAP_CONSTRUCT_AGG( <bit_position> )

参数释义

参数 说明
bit_position 必需的。在 bitmap 中的位置(BITMAP_BIT_POSITION 函数返回)

示例

CREATE TABLE t1 ( n1 int);
INSERT INTO t1 VALUES(0),(1),(1),(32767);--插入 [0,32767] 内的数据

mysql> select * from t1;
+-------+
| n1    |
+-------+
|     0 |
|     1 |
|     1 |
| 32767 |
+-------+
4 rows in set (0.01 sec)

mysql> SELECT BITMAP_CONSTRUCT_AGG(BITMAP_BIT_POSITION(n1)) AS bitmap FROM t1;
+------------------------+
| bitmap                 |
+------------------------+
| :0              ?  |
+------------------------+
1 row in set (0.00 sec)

Note

bitmap 列包含 bitmap 的物理表示形式,不可读。为了确定哪些位被设置,我们应结合使用 BITMAP 函数(而不是自己检查二进制值)。

mysql> SELECT bitmap_count(BITMAP_CONSTRUCT_AGG(BITMAP_BIT_POSITION(n1))) AS n1_discnt FROM t1;--bitmap 中设置为 1 的数量
+-----------+
| n1_discnt |
+-----------+
|         3 |
+-----------+
1 row in set (0.00 sec)

mysql> SELECT count(DISTINCT n1) AS n1_discnt FROM t1;--返回一致
+-----------+
| n1_discnt |
+-----------+
|         3 |
+-----------+
1 row in set (0.01 sec)

INSERT INTO t1 VALUES(32768),(32769),(65535);--插入大于 32767 的数据

mysql> select * from t1;
+-------+
| n1    |
+-------+
|     0 |
|     1 |
|     1 |
| 32767 |
| 32768 |
| 32769 |
| 65535 |
+-------+
7 rows in set (0.01 sec)

--结果与第一次插入一样,因为 bucket_bit_position = n1 % 32768,第二次插入的数据与第一次插入的数据位于不同 bucket 的相同位置,所以被去重了。
mysql> SELECT bitmap_count(BITMAP_CONSTRUCT_AGG(BITMAP_BIT_POSITION(n1))) AS n1_discnt FROM t1;
+-----------+
| t1_bitmap |
+-----------+
|         3 |
+-----------+
1 row in set (0.00 sec)

mysql> SELECT bitmap_bit_position(0),bitmap_bit_position(1),bitmap_bit_position(32767),bitmap_bit_position(32768),bitmap_bit_position(65535);
+------------------------+------------------------+----------------------------+----------------------------+----------------------------+
| bitmap_bit_position(0) | bitmap_bit_position(1) | bitmap_bit_position(32767) | bitmap_bit_position(32768) | bitmap_bit_position(65535) |
+------------------------+------------------------+----------------------------+----------------------------+----------------------------+
|                      0 |                      1 |                      32767 |                          0 |                      32767 |
+------------------------+------------------------+----------------------------+----------------------------+----------------------------+
1 row in set (0.00 sec)

因此如果要对大于 32767 的数据去重需结合 BITMAP_BUCKET_NUMBER() 函数。

--以 bucket 分组,第一个 bucket 里面有三个非重复数(0,1,32767),第二个 bucket 里有三个非重复数 (32768,32769,65535)。
mysql> SELECT bitmap_count(BITMAP_CONSTRUCT_AGG(BITMAP_BIT_POSITION(n1))) AS t1_bitmap FROM t1 GROUP BY BITMAP_BUCKET_NUMBER(n1);
+-----------+
| t1_bitmap |
+-----------+
|         3 |
|         3 |
+-----------+
2 rows in set (0.01 sec)

--结合 sum() 函数计算 n1 的非重复值
mysql> SELECT SUM(t1_bitmap) FROM (
    -> SELECT bitmap_count(BITMAP_CONSTRUCT_AGG(BITMAP_BIT_POSITION(n1))) AS t1_bitmap
    -> FROM t1 
    -> GROUP BY BITMAP_BUCKET_NUMBER(n1)
    -> );
+----------------+
| sum(t1_bitmap) |
+----------------+
|              6 |
+----------------+
1 row in set (0.01 sec)

BITMAP_OR_AGG

BITMAP_OR_AGG() 函数用于计算多个 bitmap 的按位或(OR)结果。通常用于合并多个 bitmap,以便在一个 bitmap 中表示所有输入 bitmap 的组合信息。

当需要对不同维度的数据进行集合并集操作时,BITMAP_OR_AGG() 十分有用,尤其是在数据仓库和分析型查询中。

语法

BITMAP_OR_AGG( bitmap )

参数释义

参数 说明
bitmap 必需的。所有 bitmap 按位或合并得到的 bitmap。

示例

--创建一张表,用来存储作者出版书籍的信息,包含作者名称,出版年份和书籍 id
CREATE TABLE book_table(
    id int auto_increment primary key,
    author varchar(100),
    pub_year varchar(100),
    book_id int
);
INSERT INTO book_table(author,pub_year,book_id) VALUES
('A 作者','2020',1),('A 作者','2020',1),('A 作者','2020',32768),
('A 作者','2021',32767),('A 作者','2021',32768),('A 作者','2021',65536),
('B 作者','2020',2),('B 作者','2020',10),('B 作者','2020',32769),
('B 作者','2021',5),('B 作者','2021',65539);

mysql> select * from book_table;
+------+----------+----------+---------+
| id   | author   | pub_year | book_id |
+------+----------+----------+---------+
|    1 | A 作者   | 2020     |       1 |
|    2 | A 作者   | 2020     |       1 |
|    3 | A 作者   | 2020     |   32768 |
|    4 | A 作者   | 2021     |   32767 |
|    5 | A 作者   | 2021     |   32768 |
|    6 | A 作者   | 2021     |   65536 |
|    7 | B 作者   | 2020     |       2 |
|    8 | B 作者   | 2020     |      10 |
|    9 | B 作者   | 2020     |   32769 |
|   10 | B 作者   | 2021     |       5 |
|   11 | B 作者   | 2021     |   65539 |
+------+----------+----------+---------+
11 rows in set (0.00 sec)

--定义一张预计算表,把粗粒度的计算结果保存在表中,后续各种不同维度聚合可以使用预计算表中的结果,经过简单的计算就可以得到结果,加速查询。
CREATE TABLE precompute AS
SELECT
  author,
  pub_year,
  BITMAP_BUCKET_NUMBER(book_id) as bucket,
  BITMAP_CONSTRUCT_AGG(BITMAP_BIT_POSITION(book_id)) as bitmap 
FROM book_table
GROUP BY  author,pub_year,bucket;

mysql> select * from precompute;
+---------+----------+--------+----------------------+
| author  | pub_year | bucket | bitmap               |
+---------+----------+--------+----------------------+
| A作者   | 2020     |      0 | :0                |
| A作者   | 2020     |      1 | :0                 |
| A作者   | 2021     |      0 | :0            ?    |
| A作者   | 2021     |      1 | :0                 |
| A作者   | 2021     |      2 | :0                 |
| B作者   | 2020     |      0 | :0                |
| B作者   | 2020     |      1 | :0                |
| B作者   | 2021     |      0 | :0                |
| B作者   | 2021     |      2 | :0                |
+---------+----------+--------+----------------------+

--计算在作者和出版年份聚合情况下 book_id 的去重数量,反应的是作者在不同年份出版书籍类型的数量。
--sum() 函数累加不同 bucket 的 bitmap 中 1 的数量。
--例如当 author=A 作者,pub_year=2020 时,book_id=(1,1,32768),去重后为 book_id=(1,32768),但是 1 位于第一个 bucket,32768 位于第二个 bucket,所以需要 sum 作累加。
mysql> SELECT
    ->   author,
    ->   pub_year,
    ->   SUM(BITMAP_COUNT(bitmap))
    -> FROM precompute
    -> GROUP BY  author,pub_year;
+---------+----------+---------------------------+
| author  | pub_year | sum(bitmap_count(bitmap)) |
+---------+----------+---------------------------+
| A作者   | 2020     |                         2 |
| A作者   | 2021     |                         3 |
| B作者   | 2020     |                         3 |
| B作者   | 2021     |                         2 |
+---------+----------+---------------------------+
4 rows in set (0.00 sec)

mysql> SELECT author,pub_year,count( DISTINCT book_id) FROM book_table group by author,pub_year;--返回一致
+----------+----------+-------------------------+
| author   | pub_year | count(distinct book_id) |
+----------+----------+-------------------------+
| A 作者   | 2020     |                       2 |
| A 作者   | 2021     |                       3 |
| B 作者   | 2020     |                       3 |
| B 作者   | 2021     |                       2 |
+----------+----------+-------------------------+
4 rows in set (0.00 sec)

--计算在作者聚合情况下 book_id 的去重数量,反应的是作者一共出版书籍类型的数量。
--BITMAP_OR_AGG() 函数合并不同维度(相同作者不同年份)的 bitmap。
--例如当 author=A 作者,pub_date=2020 时,book_id 去重后为 (1,32768),pub_date=2021 时,book_id 去重后为 (32767,32768,65536),BITMAP_OR_AGG 对两个不同年份的 bitmap 做或运算得到 book_id=(1,32767,32768,65536),最后 sum() 累加不同 bucktet 的 book_id。
mysql> SELECT author, SUM(cnt) FROM (
    ->   SELECT
    ->     author,
    ->     BITMAP_COUNT(BITMAP_OR_AGG(bitmap)) cnt
    ->   FROM precompute
    ->   GROUP BY author,bucket
    -> )
    -> GROUP BY author;
+---------+----------+
| author  | sum(cnt) |
+---------+----------+
| A作者   |        4 |
| B作者   |        5 |
+---------+----------+
2 rows in set (0.01 sec)

mysql> SELECT author,count(DISTINCT book_id) FROM book_table GROUP BY author;--返回一致
+----------+-------------------------+
| author   | count(distinct book_id) |
+----------+-------------------------+
| A 作者   |                       4 |
| B 作者   |                       5 |
+----------+-------------------------+
2 rows in set (0.00 sec)