【问题标题】:Storing data in two CSV strings vs two db tables for fastest comparison将数据存储在两个 CSV 字符串与两个 db 表中以进行最快的比较
【发布时间】:2017-08-14 18:10:09
【问题描述】:

场景是我们有两个列表:

答:23,45,g5,33

B:11,12,45,g9

我们想要 SQL SERVER 中最快的机制来查看 B 中的任何值是否在 A 中可用,在此示例中 45 在 A 中,因此它必须返回 true。

解决方案应描述存储列表(CSV、表格等)的方式和比较机制。

每个列表都相对较小(每个列表平均有 10 个值),但要进行多次比较(写入很少,读取很多)

【问题讨论】:

  • 音量?索引表应该是最快的
  • 两个列表?你的意思是两列?如何将它们存储在 SAME 列中,并带有一个 ID 用于拆分它。您尚未说明此数据是否已在 SQL Server 中。但是,一般来说,永远不要在 SQL Server 中将值存储为逗号分隔的字符串。无论如何,您都必须将其拆分以进行比较。
  • 如果您要求将其存储在不同的表中而不是同一个表和不同的列中,这是一个奇怪的问题。同一张表会更快,但这不是设计数据库时通常要问的问题,而是关系和规范化
  • XY 问题是询问您尝试的解决方案,而不是您的实际问题。描述你的真实场景,而不是你解决它的方法
  • 简单明了。停止存储这样的分隔数据。它违反了 1NF,使查询变得更加困难和缓慢。

标签: sql sql-server csv


【解决方案1】:

如果您被分隔字符串卡住,请考虑以下事项:

示例:

Declare @YourTable Table ([ColA] varchar(50),[ColB] varchar(50))
Insert Into @YourTable Values 
 ('23,45,g5,33' ,'11,12,45,g9')
,('no,match'    ,'found,here')


Select * 
 from @YourTable A
 Cross Apply (
                Select Match=IsNull(sum(1),0)
                 From  [dbo].[udf-Str-Parse-8K](ColA,',') B1
                 Join  [dbo].[udf-Str-Parse-8K](ColB,',') B2 on B1.RetVal=B2.RetVal
             ) B

退货

ColA          ColB          Match
23,45,g5,33   11,12,45,g9   1
no,match      found,here    0

有兴趣的UDF

CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (@String varchar(max),@Delimiter varchar(25))
Returns Table 
As
Return (  
    with   cte1(N)   As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
           cte2(N)   As (Select Top (IsNull(DataLength(@String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
           cte3(N)   As (Select 1 Union All Select t.N+DataLength(@Delimiter) From cte2 t Where Substring(@String,t.N,DataLength(@Delimiter)) = @Delimiter),
           cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(@Delimiter,@String,s.N),0)-S.N,8000) From cte3 S)

    Select RetSeq = Row_Number() over (Order By A.N)
          ,RetVal = LTrim(RTrim(Substring(@String, A.N, A.L)))
    From   cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')

【讨论】:

  • 很好,但它比@scsimon 答案快吗?
  • @mohas 很难说。仍然不是 100% 清楚。您是否正在寻找任何字符串中的任何匹配项?如果是这样,我有一个替代方案
  • @mohas 如果不出意外,提供的 Parse Function 是最快的方法之一
  • 是的,请分享
【解决方案2】:

我仍然对核心思想感到困惑......但这是一个比逗号分隔列表更好的简单解决方案。当然,创建索引会使其更快。它比循环快得多。

declare @table table (id char(4), v varchar(256))
insert into @table
values
('A','23'),
('A','45'),
('A','g5'),
('A','33'),
('B','11'),
('B','12'),
('B','45'),
('B','g9')


select distinct
    base.v
    --,base.*
    --,compare.*
from 
    @table base
inner join
    @table compare
    on compare.v = base.v
    and compare.id <> base.id

分路

declare @table table (id char(4), v varchar(256))
insert into @table
values
('A','23,45,g5,33'),
('B','11,12,45,g9')

;with cte as(
    select 
        t.ID
        ,base.Item
    from 
        @table t
        cross apply dbo.DelimitedSplit8K(t.v,',') base)

select
    t.Item
from
    cte t
inner join
    cte x on 
    x.Item = t.Item
    and x.id <> t.id
where
    t.id = 'A'

USING THIS FUNCTION

CREATE FUNCTION [dbo].[DelimitedSplit8K] (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!

RETURNS TABLE WITH SCHEMABINDING AS
RETURN

/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/

  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       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 (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l
;
GO

【讨论】:

    【解决方案3】:

    根据之前的回答,我觉得应该是这样的:

    declare @table table (id char(4), v varchar(256))
    insert into @table
    values
    ('A','23'),
    ('A','45'),
    ('A','g5'),
    ('A','33'),
    ('B','11'),
    ('B','12'),
    ('B','45'),
    ('B','g9')
    
    
    if exists( select count(1)
            from 
                @table base
            inner join
                @table compare
                on compare.v = base.v
                and base.id='A' and compare.id='B') 
     print 'true'
     else
     print 'false'
    

    id、v 或 v、id 上的索引取决于数据的增长

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多