具有不同格式查找的电话号码

Rem*_*_me 2 postgresql

我的数据库中存储了不同的电话格式,例如:

727.170.4799
1-416-958-6390
1.561.374.4268
(813) 929-5892
Run Code Online (Sandbox Code Playgroud)

此表中大约有约 200 万条记录,因此修改需要很长时间。我需要通过执行电话查找来查找记录。

我控制的一件事是输入,我可以保证它将是数字。但是我不知道他们是否要输入带有或不带有国家/地区代码的数字,因此以这个数字为例,1-416-958-6390这两个都用于查找:

14169586390
4169586390
Run Code Online (Sandbox Code Playgroud)

应该返回同一行 ( 1-416-958-6390)

如何尽可能高效地拨打电话号码?

这是我得到的,但它超级慢(正如预期的那样):

select * from patients
WHERE ( REPLACE(phone_number, '-', '') LIKE '%4169586390' )
Run Code Online (Sandbox Code Playgroud)

a_h*_*ame 6

要清理数字,请使用正则表达式删除所有不是数字的内容:

regexp_replace(phone_number, '[^0-9]', '', 'g') 
Run Code Online (Sandbox Code Playgroud)

您可以在其上创建索引,但不会使用常规 B 树索引,例如通配符位于左侧的 LIKE 条件。

在 Postgres 中,您有两种选择:您可以在反向值上创建常规 B 树索引并使用它:

create index on patients ( reverse(regexp_replace(phone_number, '[^0-9]', '', 'g')) );
Run Code Online (Sandbox Code Playgroud)

然后使用该表达式并将其与反向输入进行比较:

select * 
from patients
where reverse(regexp_replace(phone_number, '[^0-9]', '', 'g')) like reverse('4169586390')||'%'
Run Code Online (Sandbox Code Playgroud)

另一种选择是安装pg_trgm扩展并在表达式上创建一个三元组索引。这样你就可以“按原样”使用输入:

create index on patients 
   using gist ( regexp_replace(phone_number, '[^0-9]', '', 'g') gist_trgm_ops);
Run Code Online (Sandbox Code Playgroud)

然后该索引可用于:

select * 
from patients
where regexp_replace(phone_number, '[^0-9]', '', 'g') like '%4169586390'
Run Code Online (Sandbox Code Playgroud)

然而,GiST 索引比 B-Tree 索引更大,维护起来也更慢。


如果您不想在每个查询中重复该正则表达式,您可以创建一个包含它的视图,例如:

create view patients_clean_phones
as
select ... other columns ..., regexp_replace(phone_number, '[^0-9]', '', 'g') as clean_phone
from patients;
Run Code Online (Sandbox Code Playgroud)

然后使用(假设您使用的是 GiST 索引,因此不需要反向):

select *
from patients_clean_phones
where clean_phone like '4169586390%';
Run Code Online (Sandbox Code Playgroud)

Postgres 足够聪明,在这种情况下仍然使用索引。


如果您使用的是 Postgres 12,则视图的替代方法是使用带有正则表达式的计算列,然后在计算列上创建索引。


Ric*_*mes 5

方案A:( 适用于美国和加拿大)

从删除标点和领先的“1”柱和传入的值是所有真正需要的。 这是许多搜索问题的通用解决方案 - 清理表和传入的查询,目的是提高查找效率。

方案B:

  1. 有另一列与REVERSE(num),称之为rev。它可能是一个“生成”列。索引它。
  2. 反转传入值: REVERSE("1.561.374.4268")
  3. 取它的前4位数字: LEFT(REVERSE("..."), 4)=“8624”
  4. 查找潜在结果: WHERE rev LIKE CONCAT(LEFT(REVERSE(inc), 4), '%')`

注意反转避免了前导通配符,这会阻止索引的有效使用。您现在有一个可能的值的小列表(大约 200 的 2M)。

  1. 执行REGEXP检查所有格式和其余数字。

更好的是从反向列表中删除所有标点符号。那么唯一的变化是它是否以1. 如果您仅限于美国和加拿大,只需从rev列和传入值中删除“1” 。

如果您正在检查“987-654-3210”的某些变体,其中任何一个都将完成对 200 的检查:

phone REGEXP "987.*654.*3210"  -- no anchoring needed

rev REGEXP "^0123.*456.*987"   -- one anchor needed
Run Code Online (Sandbox Code Playgroud)

所以...

计划C:

  1. 添加rev计算自的列REVERSE(phone)
  2. 索引它。
  3. 然后,在搜索“987-654-3210”的某些变体时,请执行

    WHERE rev LIKE '0123%' AND rev RLIKE "^0123.*456.*987"

注: LIKE愿意使用索引;RLIKE不是。

清洁

旧版本:

REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(num, '-', ''),
             ')', ''),
             '(', ''),
             ' ', ''),
             '.', '')
Run Code Online (Sandbox Code Playgroud)

在 MySQL 8.0 或 MariaDB 10.0 中:

REGEXP_REPLACE(num, '[^0-9]', '')
Run Code Online (Sandbox Code Playgroud)

另外(任何版本),要删除前导“1”,这是一种方法:

RIGHT(num, 10)
Run Code Online (Sandbox Code Playgroud)