使用条件和格式化字符串连接优化SQL Server查询

时间:2014-07-07 18:10:50

标签: sql sql-server conditional inner-join

我需要执行一个将在名为a.PatientAddressb.ADDRESS的字段上连接两个表的查询,问题是b.ADDRESS需要标准化并格式化以匹配找到的标准化地址在a.PatientAddress。我无法控制传入的数据格式,因此在进入b表之前清除数据不是一种选择。例如:

a.PatientAddress可能与1234 Someplace Cool Dr. Apt 1234相同,而b.ADDRESS中的匹配地址可能与1234 Someplace Cool Dr. #1234相同(实际上这只是众多可能中的一种)。公寓号码(如果地址中存在)是需要格式化以便正确加入的波动区域。

我在数据集中看到的一些可能的Apt变体是:

  • 1234 Someplace Cool Dr.#1234
  • 1234 Someplace Cool Dr. Apt 1234
  • 1234 Someplace Cool Dr. Apt#1234
  • 1234 Someplace Cool Dr. Apt#1234

现在,我已经尝试过了;

    SELECT  vgi.VisitNo
            ,vgi.AdmitDate
            ,vgi.ChargesTotal
            ,MONTH(vgi.AdmitDate)           AS AdmitMonth
            ,DATENAME(MONTH, vgi.AdmitDate) AS AdmitMonthName
            ,YEAR(vgi.AdmitDate)            AS AdmitYear
            ,vgi.PatientAddress
            ,mm.MAIL_DATE
            ,mm.ADDRESS

    FROM    VISIT_GENERAL_INFORMATION vgi
            INNER JOIN MARKETING_MAILING mm ON vgi.AdmitDate >= mm.MAIL_DATE
                AND vgi.AdmitDate > '2014-01-01 00:00:00.000'
                AND (
                    -- IF APT IS NOT FOUND, THEN ADDRESS SHOULD DIRECTLY EQUAL ANOTHER ADDRESS
                       (    mm.ADDRESS NOT LIKE '%[$0-9]'
                            AND UPPER(vgi.PatientAddress) = UPPER(mm.ADDRESS) 
                       )
                       OR
                       (
                             mm.ADDRESS LIKE '%[$0-9]'
                             AND UPPER(vgi.PatientAddress) = 
                             -- PATIENT ADDRESS SHOULD EQUAL THE FORMATTED ADDRESS OF THE MAIL RECIPIENT
                             -- GET THE FIRST PART OF THE ADDRESS, UP TO THE ADDRESS NUMBER
                             SUBSTRING(mm.ADDRESS,1,CHARINDEX(REPLACE(LTRIM(RIGHT(mm.ADDRESS, CHARINDEX(' ', mm.ADDRESS)-1)),'#',''),mm.ADDRESS))
                             + ' ' + 
                             -- GET THE APARTMENT ADDRESS NUMBER AND FORMAT IT
                             -- TAKE OUT EXTRA SPACING AROUND IT AND THE # CHARACTER IF IT EXISTS
                             REPLACE(LTRIM(RIGHT(mm.ADDRESS, CHARINDEX(' ', mm.ADDRESS)-1)),'#','')
                       )
                    )

这里的问题是查询需要20多分钟才能执行,有时甚至在操作时间到期之前都没有完成。我还尝试将这两个条件分成UNION个语句。我还尝试拆分街道地址和公寓号码来创建一个类似的声明,内容为UPPER(vgi.PatientAddress) LIKE UPPER('%1234 Someplace Cool Dr.%1234%'),而且似乎也无效。我开始没有想法,想看看其他人的建议。

提前感谢任何指示或帮助!

2 个答案:

答案 0 :(得分:2)

清理数据所需的逻辑超出了我们可以为您做的范围。您可能会发现,最终,需要此查询的其他一些键才能正常工作。但是,假设您现有的逻辑足以创建良好的匹配(即使速度很慢),我们也许可以帮助提高性能。

您可以改进的一种方法是加入清理数据的地址表的投影。 (这意味着加入子查询)。该预测可能如下所示:

SELECT Mail_Date, Address, 
           CASE WHEN ADDRESS LIKE '%[$0-9]' THEN
                    -- GET THE FIRST PART OF THE ADDRESS, UP TO THE ADDRESS NUMBER
                    SUBSTRING(ADDRESS,1,CHARINDEX(REPLACE(LTRIM(RIGHT(ADDRESS, CHARINDEX(' ', ADDRESS)-1)),'#',''),ADDRESS))
                    + ' ' + 
                     -- GET THE APARTMENT ADDRESS NUMBER AND FORMAT IT
                     -- TAKE OUT EXTRA SPACING AROUND IT AND THE # CHARACTER IF IT EXISTS
                     REPLACE(LTRIM(RIGHT(ADDRESS, CHARINDEX(' ', ADDRESS)-1)),'#','')
                ELSE UPPER(ADDRESS) 
           END AS ADDRESS_CLEAN
FROM MARKETING_MAILING

这改善了事情,因为它避免了JOIN中的“OR”条件;您只需匹配投影列。但是,这会强制投影到表格中的每一行(提示:这可能发生了反正),因此它仍然没有那么快。您可以了解这是否有助于自行运行投影所需的时间。

您可以通过将上面ADDRESS_CLEANcomputed column添加到您的Marketing_Mailing表中来进一步改进投影方法。这将强制调整在插入时发生,这意味着已经为慢速查询完成了工作。您甚至可以在列上编制索引。当然,这是以较慢的插入物为代价的。您也可以在桌面上尝试查看(或materialized view)。这将有助于Sql Server保存一些工作,它可以计算多个查询中的额外列。为了获得最佳效果,还要考虑在创建投影时可以使用的WHERE过滤器,以避免每次都计算这些行上的额外列。

另外需要注意的是,对于默认排序规则,您可以跳过使用UPPER()函数。这可能会损害您的索引使用。

像这样把它们放在一起:

SELECT  vgi.VisitNo
        ,vgi.AdmitDate
        ,vgi.ChargesTotal
        ,MONTH(vgi.AdmitDate)           AS AdmitMonth
        ,DATENAME(MONTH, vgi.AdmitDate) AS AdmitMonthName
        ,YEAR(vgi.AdmitDate)            AS AdmitYear
        ,vgi.PatientAddress
        ,mm.MAIL_DATE
        ,mm.ADDRESS

FROM    VISIT_GENERAL_INFORMATION vgi
        INNER JOIN 
           (
               SELECT Mail_Date, Address, 
                   CASE WHEN ADDRESS LIKE '%[$0-9]' THEN
                       -- GET THE FIRST PART OF THE ADDRESS, UP TO THE ADDRESS NUMBER
                       SUBSTRING(ADDRESS,1,CHARINDEX(REPLACE(LTRIM(RIGHT(ADDRESS, CHARINDEX(' ', ADDRESS)-1)),'#',''),ADDRESS))
                       + ' ' + 
                        -- GET THE APARTMENT ADDRESS NUMBER AND FORMAT IT
                        -- TAKE OUT EXTRA SPACING AROUND IT AND THE # CHARACTER IF IT EXISTS
                        REPLACE(LTRIM(RIGHT(ADDRESS, CHARINDEX(' ', ADDRESS)-1)),'#','')
                   ELSE ADDRESS END AS ADDRESS_CLEAN
                 FROM MARKETING_MAILING
            ) mm ON vgi.AdmitDate >= mm.MAIL_DATE
                    AND vgi.AdmitDate > '2014-01-01 00:00:00.000'
                    AND vgi.PatientAddress = mm.ADDRESS_CLEAN

尚未涵盖的另一个巨大的因素是索引。您的VISIT_GENERAL_INFORMATION表上有哪些索引?我特别希望看到一个涵盖AdmitDate和PatientAddress的索引。哪个顺序取决于这些字段的基数,以及Marketing_Mail表中的清洁程度和数据量。

最后,我自己的一个请求:如果这有帮助,我想听听它有多大帮助。如果查询过去需要20分钟,现在需要多长时间?

答案 1 :(得分:0)

我同意@TomTom,你会真正受益于"预标准化"进入

  • 即时更新的派生列
  • 或查询过程中的视图或临时表

为您提供干净的匹配。

有了这个,我会使用第三方服务或库,理想情况下,因为他们花了很多时间使它成为一个可靠的解析。

任何一个选项在收到您无法控制的数据后都有效,因此这不是问题 您正在做的是创建自己的标准化内部副本 当然,你需要通过相同的标准化来管理另一方,#34; a&#34 ;.