关于提高查询性能的建议~1天

时间:2017-09-25 15:41:50

标签: sql sql-server tsql

我有5个表 - 每个表都有成千上万的记录

1个主要/非常重要的表格(表A) 2个其他表(表B / C)仍然很重要但不如表

那么重要

在A< => B和A< => C之间保持主键的2个边桌(TABLES D / E),即每个只有两列

3个主表各有~140列,所有列都具有相同的列名

我的查询的目的是在一个查询中在所有表A< => D< => B和A< => E< => C之间执行列级匹配

最终查询将包含大约286列(每个主表中有两个ID列,

select tableA.ID1 as [TABLEAID1],
    tableA.ID2 as [TABLEAID2],
    tableB.ID1 as [TABLEBID1],
    tableB.ID2 as [TABLEBID2],
    tableC.ID1 as [TABLECID1],
    tableC.ID2 as [TABLECID2],
    fn_TESTMatcher(tableA.[postCode], tableB.[postCode],) as 
        [TABLEAB.postCode.RESULT],
    fn_TESTMatcher(tableA.[CityCode], tableB.[CityCode],) as 
        [TABLEAB.CityCode.RESULT], 
.
.
. x238 more 'fn_TESTMatcher(...) as xyz' columns
.
INTO #Results
From tableA WITH (NOLOCK)
    FULL JOIN tableD WITH (NOLOCK) ON tableA.ID1 = tableD.A
        ) FULL JOIN tableB WITH (NOLOCK) ON tableD.B = tableB.ID1
            ) FULL JOIN tableE WITH (NOLOCK) ON tableA.ID1 = tableE.A
) FULL JOIN tableC WITH (NOLOCK) ON tableE.B = tableC.ID

fn_TESTMatcher是一个函数,它从两个主表中输入相同的列,然后它删除/替换特殊字符/缩写,然后尝试匹配它们,如果匹配则返回一点' 1&#39 ;如果不是那么一点' 0'。

目前需要一天的时间才能运行(我无法通过某种查询计时器实现它),我可以注释掉除了最后一列之外的所有列并运行它并且相当快,但我不认为我可以扩大规模

有没有人有一些建议?我的第一个假设是开始谷歌搜索索引是什么...也许..将它应用到每个表的ID1虽然我有点犹豫a)搞乱我的表和b)添加一个索引,最终没用了

=========================================== 更新2:表结构明智地所有主表的所有列都是varchars,长度为100-250个字符,其中ID(主键)不可为空

使用两个边表,它们只有两列,varchar,100个字符限制(它们都是外键)。此中最重要的表格ID不可为空

对于功能,我在技术上有两个:

FUNCTION [dbo].[fn_TESTStripCharacters]
(
    @String NVARCHAR(MAX) ,
    @MatchExpression VARCHAR(255)
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE @expres  VARCHAR(50) = '%[~,@,#,^,_,+,-,$,%,&,/,|,\,*,(,),.,!,`,:,<,>,?]%'
  WHILE PATINDEX( @expres, @String ) > 0
      SET @String = REPLACE(REPLACE(REPLACE( @String, SUBSTRING( @String, PATINDEX( @expres, @String ), 1 ),''),';',''),'-','')
  RETURN @String
END

和第二个功能

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[fn_TESTMatcher](@Field1 NVARCHAR(MAX), @Field2 
    NVARCHAR(MAX))
    RETURNS BIT 
    BEGIN
   SET @Field1 = UPPER(LTRIM(RTRIM(REPLACE(dbo.fn_TESTStripCharacters(@Field1,@SpecialCharacters),'-',''))))
   SET @Field2 = UPPER(LTRIM(RTRIM(REPLACE(dbo.fn_TESTStripCharacters(@Field2,@SpecialCharacters),'-',''))))
   SET @Field1 = REPLACE(@Field1,' RD ',' ROAD ')
   SET @Field2 = REPLACE(@Field2,' RD ',' ROAD ')

   SET @Field1 = REPLACE(@Field1,' ST ',' STREET ')
   SET @Field2 = REPLACE(@Field2,' ST ',' STREET ')


   SET @Field1 = REPLACE(@Field1,' ','')
   SET @Field2 = REPLACE(@Field2,' ','')


   RETURN 
          CASE WHEN @Field1=@Field2
            THEN '1'
            ELSE '0'
          END
END

============================= 更新2

示例表数据 - 假设所有3个表中都存在相同的两个记录(并非总是如此)

TableA (main + most important table):
ID1   ID2    postCode, cityCode, ................
10001 1221   IG11PJ    London     ................
10230 1022   IG22PJ    Nottingham ................


tableB (slightly less important table)
ID1   ID2    postCode, cityCode, ................
10031 1011   IG1 1PJ    london     ................
10980 982   IG2 2PJ    nottingham ................


tableC (slightly less important table)
ID1   ID2    postCode, cityCode, ................
10551 1011   iG1 1pj    london     ................
20980 982    iG2 2pJ    nottingham ................

tableD (side table)
A         B
10001  10031 
10230  10980

table E (side table)
A        B
10001 10551  
10230 20980  

4 个答案:

答案 0 :(得分:0)

如果表A,B和C应该相同除了格式差异我建议您创建3个CTE,第一个选择TableA ID和所有其他列的HASHBYTES(列需要转换为char / varchar所以可以在那里进行任何格式化和替换),第二个CTE对于表B是相同的,第三个用于表C.

然后只匹配HASHBYTES值。 正如已经说过的那样,没有样本数据,表结构,函数的DDL等我们只是在猜测。

Sean和Milney在标量与内联表函数和NOLOCK的使用方面都有很好的分数

答案 1 :(得分:0)

我将这些视为不属于一个查询的任务。我会创建一组新表(或者这些表是备份/不需要保存数据),然后在这些新表中执行数据清理步骤。

如果您对数据已经标准化感到满意,那么请执行单个查询来比较表格。

尝试将所有内容放在一个查询中没有任何优势,您也不会逐步取得进展。例如,如果您发现忘记从一次字段中删除空格,则必须重做所有内容。如果你使用&#34;清理&#34;制作新表数据可以逐步投入时间来清理数据(这显然是这个过程的缓慢部分),直到数据完美,然后进行快速比较。忘了什么 - 这是一个相对快速的更新和运行。

答案 2 :(得分:0)

Instead of all the headaches you are running through, making copies of everything, and then trying to parse based on functions that can not be optimized, I would suggest the following. You state you have a column that gets stripped of special characters. I would add a "CleanKey" column for each table and represented column. Then, via a possible table trigger, or before the add/save, pre-clean that value into the "CleanKey" column and you are done. Then have an index on THOSE "Clean" columns and do a direct join.

Since the rest of the system does not know of these "Clean" columns, you can add the columns, clean them out of whatever function you have and not worry about duplicating or otherwise ruining other data.

Yes, it may take a bit to pre-"Clean" these columns, but then its DONE. Your query should be fast after that.

答案 3 :(得分:0)

我同意其他人说清理这些字符串值是个好主意。但是因为你仍然需要完成它而我绝对讨厌循环和标量函数,我决定推出一个内联表值函数而不是这两个嵌套的标量函数。我这里没有使用任何循环,性能可能让你大吃一惊。

我正在使用计数器或数字表。我喜欢将其中一个作为视图。这是我使用的视图的代码。

create View [dbo].[cteTally] as

WITH
    E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
    E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
    cteTally(N) AS 
    (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
    )
select N from cteTally
GO

然后,您可以使用此计数表来派生基于集合的方法,以适应您决定两个值是否匹配的业务规则。你也不需要这里的逗号分隔符。在您的示例中,您在要删除的值列表中几乎每个其他字符都使用逗号。每个字符的单个实例就足够了。

create function [dbo].[fn_TESTMatcher_Sean]
(
    @Field1 nvarchar(max)
    , @Field2 nvarchar(max)
    , @CharsToRemove nvarchar(max)
) returns table as 
    RETURN

    with MyValues1 as
    (
        select substring(@Field1, N, 1) as MyChar
            , t.N
        from cteTally t 
        where N <= len(@Field1)
            and charindex(substring(@Field1, N, 1), @CharsToRemove) = 0
    )
    , MyValues2 as
    (
        select substring(@Field2, N, 1) as MyChar
            , t.N
        from cteTally t 
        where N <= len(@Field2)
            and charindex(substring(@Field2, N, 1), @CharsToRemove) = 0
    )

    select convert(bit, case when mv1.MyResult = mv2.MyResult then 1 else 0 end) as IsMatch
    from
    (
        select distinct MyResult = 
        replace(
            replace(replace(STUFF((select MyChar + ''
                    from MyValues1 mv2
                    order by mv2.N
                    FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'), 1, 0, '')
                    , ' RD ', ' ROAD ')
                    , ' ST ', ' STREET ')
                    , ' ', '')
        from MyValues1 mv
    ) mv1
    cross join
    (
        select distinct MyResult = 
    replace(
        replace(replace(STUFF((select MyChar + ''
                from MyValues2 mv2
                order by mv2.N
                FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'), 1, 0, '')
                , ' RD ', ' ROAD ')
                , ' ST ', ' STREET ')
                , ' ', '')
    from MyValues2 mv
    ) mv2
    ;

请试一试,让我知道这是否适用于您的环境。

例如:

select *
from fn_TESTMatcher_Sean('123 any st rd or something', '123 any street road or something', '%[~,@#^_+-$%&/|\*().!`:<>?]%')

上述内容返回1,因为它们是您定义的规则下的匹配项。