改进的逐步执行SQL查询,没有很多IF / ELSE块

时间:2019-01-24 17:44:55

标签: php sql azure azure-sql-database

考虑具有以下行的示例表:

+----+----------+-------+
| 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多行)上有一个索引。我的担心纯粹是从视觉上看,这是一个令人讨厌的查询。

我的问题:有没有更干净的方式编写此查询?

2 个答案:

答案 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
相关问题