位函数与运算符
TiDB 支持 MySQL 8.0 中所有的 位函数与运算符。
位函数与运算符:
| 名称 | 描述 |
|---|---|
BIT_COUNT() | 返回表达式中被设置为 1 的位数 |
& | 按位与 |
~ | 按位取反 |
| | 按位或 |
^ | 按位异或 |
<< | 左移 |
>> | 右移 |
BIT_COUNT()
BIT_COUNT(expr) 函数返回 expr 中被设置为 1 的位数。
SELECT BIT_COUNT(b'00101001');
+------------------------+
| BIT_COUNT(b'00101001') |
+------------------------+
| 3 |
+------------------------+
1 row in set (0.00 sec)
下面的示例与前面的类似,但参数使用的是十六进制字面量而不是位字面量。CONV() 函数将 0x29 从十六进制(基数 16)转换为二进制(基数 2),显示其二进制等价为 00101001。
SELECT BIT_COUNT(0x29), CONV(0x29,16,2);
+-----------------+-----------------+
| BIT_COUNT(0x29) | CONV(0x29,16,2) |
+-----------------+-----------------+
| 3 | 101001 |
+-----------------+-----------------+
1 row in set (0.01 sec)
BIT_COUNT(expr) 函数的一个实际用途是将网络掩码转换为 CIDR 表示法。在下面的示例中,网络掩码 255.255.255.0 被转换为其 CIDR 表示 24。
SELECT BIT_COUNT(INET_ATON('255.255.255.0'));
+---------------------------------------+
| BIT_COUNT(INET_ATON('255.255.255.0')) |
+---------------------------------------+
| 24 |
+---------------------------------------+
1 row in set (0.00 sec)
&(按位与)
& 运算符执行按位与操作。它比较两个数字对应位:如果对应位都为 1,则结果的该位为 1;否则为 0。
例如,1010 和 1100 进行按位与操作,结果为 1000,因为只有最左边的位在两个数字中都为 1。
1010
& 1100
----
1000
在 SQL 中,你可以如下使用 & 运算符:
SELECT CONV(b'1010' & b'1000',10,2);
+------------------------------+
| CONV(b'1010' & b'1000',10,2) |
+------------------------------+
| 1000 |
+------------------------------+
1 row in set (0.00 sec)
你可以结合 & 运算符与 INET_NTOA() 和 INET_ATON() 函数,对 IP 地址和网络掩码进行按位与操作以获得网络地址。这对于判断多个 IP 地址是否属于同一网络非常有用。
在下面的两个示例中,IP 地址 192.168.1.1 和 192.168.1.2 在掩码 255.255.255.0 下都属于同一网络 192.168.1.0/24。
SELECT INET_NTOA(INET_ATON('192.168.1.1') & INET_ATON('255.255.255.0'));
+------------------------------------------------------------------+
| INET_NTOA(INET_ATON('192.168.1.1') & INET_ATON('255.255.255.0')) |
+------------------------------------------------------------------+
| 192.168.1.0 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)
SELECT INET_NTOA(INET_ATON('192.168.1.2') & INET_ATON('255.255.255.0'));
+------------------------------------------------------------------+
| INET_NTOA(INET_ATON('192.168.1.2') & INET_ATON('255.255.255.0')) |
+------------------------------------------------------------------+
| 192.168.1.0 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)
~(按位取反)
~ 运算符对给定值执行按位取反(或按位非)操作。它会将给定值的每一位取反:0 变为 1,1 变为 0。
在操作前,值会被扩展为 64 位。
以二进制数 1111000011110000 为例,扩展为 64 位并取反后如下所示:
原始(16 位): 1111000011110000
扩展并取反(64 位): 1111111111111111111111111111111111111111111111110000111100001111
在 SQL 中,你可以如下使用 ~ 运算符:
SELECT CONV(~ b'1111000011110000',10,2);
+------------------------------------------------------------------+
| CONV(~ b'1111000011110000',10,2) |
+------------------------------------------------------------------+
| 1111111111111111111111111111111111111111111111110000111100001111 |
+------------------------------------------------------------------+
1 row in set (0.00 sec)
你可以再次对结果使用 ~ 运算符来还原取反操作:
SELECT CONV(~ b'1111111111111111111111111111111111111111111111110000111100001111',10,2);
+----------------------------------------------------------------------------------+
| CONV(~ b'1111111111111111111111111111111111111111111111110000111100001111',10,2) |
+----------------------------------------------------------------------------------+
| 1111000011110000 |
+----------------------------------------------------------------------------------+
1 row in set (0.00 sec)
|(按位或)
| 运算符执行按位或操作。它比较两个数字对应位:只要对应位中至少有一个为 1,结果的该位就为 1。
例如,1010 和 1100 进行按位或操作,结果为 1110,因为在前 3 位中,两个数字至少有一个对应位为 1。
1010
| 1100
----
1110
在 SQL 中,你可以如下使用 | 运算符:
SELECT CONV(b'1010' | b'1100',10,2);
+------------------------------+
| CONV(b'1010' | b'1100',10,2) |
+------------------------------+
| 1110 |
+------------------------------+
1 row in set (0.00 sec)
^(按位异或)
^ 运算符执行按位异或(异或或“互斥或”)操作。它比较两个数字对应位:如果对应位不同,结果的该位为 1。
例如,1010 和 1100 进行按位异或操作,结果为 0110,因为这两个数字的第 2 位和第 3 位不同。
1010
^ 1100
----
0110
在 SQL 中,你可以如下使用 ^ 运算符:
SELECT CONV(b'1010' ^ b'1100',10,2);
+------------------------------+
| CONV(b'1010' ^ b'1100',10,2) |
+------------------------------+
| 110 |
+------------------------------+
1 row in set (0.00 sec)
注意,结果显示为 110 而不是 0110,因为前导零被省略。
<<(左移)
<< 运算符执行左移操作,即将一个数字的位向左移动指定的位数,右侧空出的位用零填充。
例如,下面的语句使用 1<<n 将二进制值 1 向左移动 n 位:
WITH RECURSIVE cte(n) AS (
SELECT 0 AS n
UNION ALL
SELECT 1+n FROM cte WHERE n<10
)
SELECT n,1<<n,LPAD(CONV(1<<n,10,2),11,0) FROM cte;
+------+------+----------------------------+
| n | 1<<n | LPAD(CONV(1<<n,10,2),11,0) |
+------+------+----------------------------+
| 0 | 1 | 00000000001 |
| 1 | 2 | 00000000010 |
| 2 | 4 | 00000000100 |
| 3 | 8 | 00000001000 |
| 4 | 16 | 00000010000 |
| 5 | 32 | 00000100000 |
| 6 | 64 | 00001000000 |
| 7 | 128 | 00010000000 |
| 8 | 256 | 00100000000 |
| 9 | 512 | 01000000000 |
| 10 | 1024 | 10000000000 |
+------+------+----------------------------+
11 rows in set (0.00 sec)
>>(右移)
>> 运算符执行右移操作,即将一个数字的位向右移动指定的位数,左侧空出的位用零填充。
例如,下面的语句使用 1024>>n 将值 1024(二进制为 10000000000)向右移动 n 位:
WITH RECURSIVE cte(n) AS (
SELECT 0 AS n
UNION ALL
SELECT n+1 FROM cte WHERE n<11
)
SELECT n,1024>>n,LPAD(CONV(1024>>n,10,2),11,0) FROM cte;
+------+---------+-------------------------------+
| n | 1024>>n | LPAD(CONV(1024>>n,10,2),11,0) |
+------+---------+-------------------------------+
| 0 | 1024 | 10000000000 |
| 1 | 512 | 01000000000 |
| 2 | 256 | 00100000000 |
| 3 | 128 | 00010000000 |
| 4 | 64 | 00001000000 |
| 5 | 32 | 00000100000 |
| 6 | 16 | 00000010000 |
| 7 | 8 | 00000001000 |
| 8 | 4 | 00000000100 |
| 9 | 2 | 00000000010 |
| 10 | 1 | 00000000001 |
| 11 | 0 | 00000000000 |
+------+---------+-------------------------------+
12 rows in set (0.00 sec)
>> 运算符也可以用于从较大的数字中提取特定部分,例如从 TiDB TSO 时间戳中提取 UNIX 时间戳。
MySQL 兼容性
在处理位函数与运算符时,MySQL 8.0 与早期版本的 MySQL 存在一些差异。TiDB 旨在遵循 MySQL 8.0 的行为。
已知问题
在以下情况下,TiDB 的查询结果与 MySQL 5.7 相同,但与 MySQL 8.0 不同。