考虑具有以下行的示例表:
+----+----------+-------+
| id | postcode | value |
+----+----------+-------+
| 1 | A1A3A3 | one |
| 2 | A1A3A4 | two |
| 3 | A1A3B | three |
| 4 | A1A3C | four |
| 5 | A1A3D | five |
| 6 | A1A3 | six |
| 7 | A1A | seven |
| 8 | A1 | eight |
+----+----------+-------+
我的目标是执行查询,从而逐步降低邮政编码范围,直到找到完全匹配项。
假设我的起始查询参数为A1A3E9
。基于示例表的预期返回值将为six
。需要特别注意的是,每次降级时,我都会从起始查询参数的末尾删除一个字符。
因此,首先,我将尝试找到A1A3E9
,然后是A1A3E
,然后是A1A3
等的匹配项。
目前,我可以通过一系列IF / ELSE块简单地实现这一点,就像这样:
IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost6_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost6_2
END
ELSE IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost5_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost5_2
END
ELSE IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost4_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost4_2
END
ELSE IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost3_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost3_2
END
ELSE IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost2_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost2_2
END
请注意,我在PHP中使用参数绑定,因此仅出于上下文考虑,我的参数绑定最终看起来像这样:
$stmt->bindValue(':userPost6_1', "A1A3E9", PDO::PARAM_STR);
$stmt->bindValue(':userPost6_2', "A1A3E9", PDO::PARAM_STR);
$stmt->bindValue(':userPost5_1', "A1A3E", PDO::PARAM_STR);
$stmt->bindValue(':userPost5_2', "A1A3E", PDO::PARAM_STR);
$stmt->bindValue(':userPost4_1', "A1A3", PDO::PARAM_STR);
$stmt->bindValue(':userPost4_2', "A1A3", PDO::PARAM_STR);
$stmt->bindValue(':userPost3_1', "A1A", PDO::PARAM_STR);
$stmt->bindValue(':userPost3_2', "A1A", PDO::PARAM_STR);
$stmt->bindValue(':userPost2_1', "A1", PDO::PARAM_STR);
$stmt->bindValue(':userPost2_2', "A1", PDO::PARAM_STR);
就性能而言,我没有任何问题,因为我在邮政编码列(包含40,000多行)上有一个索引。我的担心纯粹是从视觉上看,这是一个令人讨厌的查询。
我的问题:有没有更干净的方式编写此查询?
答案 0 :(得分:1)
这是一种方法:
select top (1) t.*
from t
where 'A1A3E9' like t.postcode + '%'
order by t.postcode desc;
唯一的问题是您的多个if
语句可能更快。对于这类问题,获得性能是一个真正的挑战。一种方法使用多个join
:
select v.pc, coalesce(t0.value, t1.value, t2.value, . . . )
from (values ('A1A3E9')) v(pc) left join
t t0
on t0.postcode = v.pc left join
t t1
on t1.postcode = t0.postcode is null and
(case when len(v.pc) > 1 then left(v.pc, len(v.pc) - 1) end) left join
t t2
on t1.postcode is null and
t2.postcode = (case when len(v.pc) > 2 then left(v.pc, len(v.pc) - 2) end) left join
. . .
答案 1 :(得分:0)
我首先将所有可能匹配的行假脱机,例如:
select *
into #matches
from t
where postcode like 'AI%'
这可以在邮政编码上使用索引,因此应该便宜。然后,您对匹配项进行的任何查询都将对该子集进行操作。甚至编写将邮政编码与文字进行比较的UDF,例如:
select top 1 *
from #matches
order by dbo.NumberOfMatchingCharacters(postcode,'A1A3E9') desc