MySQL查询匹配英国邮政编码,无论空格数量如何

时间:2011-03-10 20:03:46

标签: mysql sql geolocation postal-code

我拥有世界上最简单的表格,用于查找英国邮政编码的lat / lng值(加载完整的英国邮政编码数据):

CREATE TABLE postcodes (
  postcode char(7) NOT NULL,
  lat double(10,6) NOT NULL,
  lng double(10,6) NOT NULL,
  KEY postcode (postcode)
)

“邮政编码”字段中的邮政编码在前半部分的末尾有2位数字,或者一个,然后是空格。我认为这个空间对于它们如何匹配(??)的完整性很重要,而且我不想删除表中的空格,因为我还要拔出用于显示目的的邮政编码(我不喜欢我想要一个重复的领域,因为我很挑剔!)。例子:

'LE115AF', 'BS6 5EE', 'W1A 1AA', 'BS216RS', 'M3 1NH'

所以,有些人有空位,有些则没空。大多数是7个字符,有些只有6个。

无论如何,重点是我希望用户能够输入邮政编码查询,包括部分邮政编码,有或没有空格,并且如果他们的输入字符串有效(即他们没有输入完整或部分),总是找到匹配表中不存在的邮政编码。)

到目前为止,我已经完成了这项工作(在PHP的帮助下):

{...} WHERE `postcode` LIKE '" . str_replace(' ','%',$query) . "%' LIMIT 1

这有利于:

  • db
  • 中不包含空格的完整邮政编码
  • 部分邮政编码,如果已输入空格并且数据库中有相应的空格,或查询的部分没有空格发生的位置(例如'W1A'将匹配'W1A 1AA','M3 1'将匹配'M3 1AR'等。)

但不适用于这些查询:

  • 'W1A1AA'应匹配'W1A 1AA'
  • 'BS65EE'应匹配'BS6 5EE'
  • 'BS65'应匹配db中的第一个'BS6 5%'邮政编码,即'BS6 5AA'
  • 'M31'应该同样匹配'M3 1AR'

我猜我需要以某种方式做一些MySQL字符串函数魔法,以便在行的邮政编码字段中有空格,并相应地调整我的WHERE子句逻辑?有人对最佳方法有任何建议吗?理想情况下我也希望:

  • 避免使用MySQL存储过程(首选内联函数)
  • 除了PHP部分中的内联字符串函数之外什么都不做

6 个答案:

答案 0 :(得分:5)

创建一个新列,它只是剥离了空格的邮政编码字段,并在其上创建唯一索引。你不应该找到任何重复。这应该让你放心,这个空间真的不重要:)

在剥离输入邮政编码上的空格后,使用 进行查找。

请记住,涉及将字符串函数应用于表的postcode列的解决方案可能会阻止MySQL使用该列上的任何索引。 (索引基于列中的确切数据,因此,如果您开始将函数应用于该数据,则优化器通常会确定该索引无用。)

如果你确实觉得需要重新格式化,那么最简单的选择就是知道邮政编码的“出站”部分 - 空格之前的部分 - 格式稍有不同,“入站” “部分 - 空间之后的部分 - 始终是一个数字后跟两个字母。

顺便说一下,我发现的格式上最好的资源是the Wikipedia entry

答案 1 :(得分:1)

您也可以删除数据库级别的空格:

{...} WHERE replace(`postcode`, ' ','') LIKE '" . str_replace(' ','%',$query) . "%' LIMIT 1

答案 2 :(得分:1)

首先,我不认为这个空间很重要。关于Royal Mail web page的描述没有提到空格。对于每个带有我看过的空间的邮政编码,第二组总是3个字符长,所以可能你可以从后面拆分它。网页上说“通常只是一个数字”,所以可能有例外。

如果您愿意预处理查询字符串(就像您在示例中使用php一样),您可以按如下方式解决问题:通过(1)删除所有空格然后将查询后置代码转换为正则表达式(2)在所有字符之间添加?(即空格的可选匹配)。最后在末尾添加.*以允许不完整的代码。例子:

  • W1A1AA变为W ?1 ?A ?1 ?A ?A.*。这符合“W1A1AA”和“W1A 1AA”。
  • M31变为M ?3 ?1.*

在此表单中获得查询后置代码后,您可以使用MySQL的REGEXP运算符进行匹配:

{...} WHERE `postcode` LIKE 'M ?3 ?1.*' LIMIT 1

最后,顺便说一下,将' '替换为%的伎俩有点危险。这种方式BS6 5将匹配BS6 456,因为%会匹配4

答案 3 :(得分:0)

您可以通过拆分所有字母来查询。

WHERE `postcode` LIKE '" . implode("%", str_split("W1A1AA")) . "%' LIMIT 1

因为你正在查询一个有限长度的字段,所以不应该产生太多的误报,你可以在检索后通过代码中的 likeness 进行排序(我假设这是为了一个自动完成)。表现应该不好。

答案 4 :(得分:0)

马特的解决方案运作良好。但是,我仍然需要允许用户在他们的查询中专门有一个空格,并处理它,即:

  • 'M31'应匹配'M31 4AA',而
  • 'M3 1'应匹配'M3 1AR'

所以,我的增强解决方案(解决了上述问题):

CREATE TABLE postcodes (
  postcode varchar(7) NOT NULL,
  postcode_display char(7) NOT NULL,
  lat double(10,6) NOT NULL,
  lng double(10,6) NOT NULL,
  UNIQUE KEY postcode (postcode),
  UNIQUE KEY postcode_display (postcode_display)
)

postcode已删除空格,postcode_display将其留在..

<?php
if (strlen($query) <= 7 && strpos($query,' ') !== false) { $hasSpace = true; }
?>

...

WHERE `postcode" . ($hasSpace ? '_display' : '') . "` LIKE '" . str_replace(' ',($hasSpace ? '%' : ''),$query) . "%' LIMIT 1

有进一步改进的空间吗?

答案 5 :(得分:0)

我会在表格中为扇区和区域生成新条目,这将消除对LIKE的需求。

LIKE条件不能使用索引,因此需要扫描整个表以获得结果。这很慢,特别是当你有一个170万价值的完整英国邮政编码数据库时。

因此,为'M3'创建一个新条目作为邮政编码。对“M31”执行相同操作,依此类推。对于与这些新编码相对应的纬度/经度值,您可以执行一些基本数学计算所有单个邮政编码的平均位置。

试试这个:

SELECT
  SUBSTRING(postcode, 1, LOCATE(' ', postcode) - 1),
  AVG(lat),
  AVG(long)
FROM
  postcodes
GROUP BY
  SUBSTRING(postcode, 1, LOCATE(' ', postcode) - 1)

然后,您可以将结果反馈回邮政编码表。