在具有约 250 万行的表中查找缺失的数据间隙

Zim*_*rel 6 sql-server sql-server-2012 gaps-and-islands

我在一家经济咨询公司工作,我们所有的数据都保存在 SQL Server 11 (2012) 中。我们的数据基本上由一个日期列、一个原始数据列、几个计算列和一个具有用于区分每组数据和下一组数据的短代码的列组成。

我们有数千个这样的集合,它们都加载到同一个表中(总共约 250 万行),然后从查询中提取和排序。它们通常按日期排序,从第一个月开始,逐月移动到最后一个月。每个系列的开始和结束日期从一年到 100 多年不等。

最近,我们遇到了一些随机数据部分消失的问题。整行只是向上和离开,这使得找到这些丢失的行有点困难,而无需每个月都检查是否丢失了一个月,这对于 250 万行的表来说是一项有点不可能完成的任务。

我的老板让我编写一个查询/存储过程,它将查看这个巨大的表并查看哪些集合缺少行以及它们在哪里。

我一直在努力解决这个问题,这个问题有点超出我的 SQL 技能,我似乎无法在网络上的任何地方找到任何有类似问题的人。我将经历我已经拥有的东西,也许有人至少可以告诉我我是否朝着正确的方向前进,并可能提供一些关于我应该从哪里开始的见解。

我在网上找到的最佳解决方案是使用 CTE 创建一个临时日期表,然后将它们与原始表进行比较。如果我只是扫描一个特定数据集中的问题,这很有效,但我在同一个表中有许多数据集,所有数据集都有不同的开始和结束日期。所以我还是选择了它,希望我最终可以扩展它以搜索其中的很多。这是我的代码:

declare @startDate Date, @endDate Date 
set @startDate = '2000-01-01'
set @endDate = '2016-11-01'

;with GetDates As  
(  
select @startDate as TheDate
UNION ALL  
select DATEADD(MONTH,1, TheDate) from GetDates
where DATEADD(MONTH,1, TheDate) <= @endDate
)

SELECT TheDate,SHORTCODE,MonthYear 
From GetDates
LEFT OUTER JOIN VWTBL_INDICATOR
ON GetDates.TheDate=VWTBL_INDICATOR.MonthYear
AND VWTBL_INDICATOR.SHORTCODE='RMI WEST'
OPTION(MAXRECURSION 1000)
Run Code Online (Sandbox Code Playgroud)

'RMI West' 将是标记此特定数据集的短代码,它缺少从 2004 年 11 月到 2005 年 3 月的数据,当我执行此查询时,这些数据显示为空值。这几乎正​​是我需要的,但对于我在表中拥有的每个数据集。

如何正确编写此查询?我们公司将在我们的数据库上完成的大量工作外包出去,所以我并不是很熟悉它的本质。我们有一个上传功能,当我使用它提取数据时,一切都很好。但是当我在一两周后查看数据时,它缺少随机日期。他们一直在寻找解决方案,但同时我们需要一种方法来找到这些差距。

我们将始终检查几个月。我们的数据几乎总是从给定年份的一月开始,到当月结束,或者如果数据还没有出来的话,就接近这个月。

我有一个用于短代码的元数据表,但不包括每个短代码的预期月数,因为我们直到现在还没有使用这些信息。我可以询问添加字段并修改更新脚本以在数据更新时包含它们,如果这对于使其正常工作至关重要。

我工作的公司历来将其所有数据存储在大量 Excel 电子表格中。他们一直在将所有这些数据转换为 SQL。我的工作是通过各种方法对其进行检查,以确保 SQL Server 上的数据与原始电子表格中的数据相匹配。原始数据可通过我们文件系统上的电子表格网络访问。更新数据的时候,我们还是用Excel来更新信息,然后用我前面提到的上传功能加载到SQL中。一段时间以来,数据大多是干净和匹配的。仅在最近几个月才出现这种数据差距问题。

Aar*_*and 9

首先,虽然只有 202 个月的时间来检查它不会是一个大问题,但就性能而言,递归 CTE 通常是导出集合的最糟糕的方法(我在这里这里证明了这一点)。

如果您将多次运行此查询(听起来您会这样做,直到您解决了谁/什么删除此数据并首先创建间隙的单独问题),为什么不直接构建一个永远存在的月份表?

CREATE TABLE dbo.Months([Month] date PRIMARY KEY);

DECLARE @StartDate     date = '20000101', 
        @NumberOfYears int  = 30;

INSERT dbo.Months([Month])
  SELECT TOP (12*@NumberOfYears) 
  DATEADD(MONTH, ROW_NUMBER() OVER (ORDER BY number) -1, @StartDate) 
FROM master.dbo.spt_values;
Run Code Online (Sandbox Code Playgroud)

30 年的月份,将持续到 2029 年,存储在高达72kb 中。当我第一次写这篇文章时,我讽刺地强调了高达,但我应该解释为什么这有 9 页而不是预期的 2 页。 ),存储引擎为新对象保留一个完整的、统一的范围。这是 8 x 8kb 的页面,加上 72kb 的 IAM 页面——在这种情况下,实际上只需要一个数据页面,所以 7 个页面保持未分配。这意味着它们不会出现在所有目录视图中,但它们仍然很容易找到(点击放大):

在此处输入图片说明

您可以为用户数据库关闭此行为,但我个人不会(出于某种原因,他们将其设为默认值)。您的第一直觉可能是节省内存而不是磁盘空间,但是虽然这会在磁盘上放置 72kb,但只有 16kb 会被加载到缓冲池中。所以没有必要对此感到恐慌。

现在您的查询可以是:

DECLARE @startDate date = '20000101', @endDate date = '20161101';

;WITH shortcodes AS
(
  SELECT DISTINCT ShortCode 
  FROM dbo.VWTBL_INDICATOR
  WHERE MonthYear >= @startDate AND MonthYear <= @endDate
)
SELECT m.[Month], s.ShortCode 
FROM dbo.Months AS m
CROSS JOIN shortcodes AS s
LEFT OUTER JOIN dbo.VWTBL_INDICATOR AS vwtbl
ON s.ShortCode = vwtbl.ShortCode
AND m.[Month] = vwtbl.MonthYear
WHERE m.[Month] >= @startDate AND m.[Month] <= @endDate
AND vwtbl.MonthYear IS NULL;
Run Code Online (Sandbox Code Playgroud)

请注意,目前这将标识您定义的范围内没有出现 ShortCode 的所有月份,即使它超出了对该ShortCode有效的范围。如果在某处定义了每个 ShortCode 的有效范围,请将该信息添加到问题中。

“VWTBL”到底是什么?

  • @Zimmerel 听起来像是门房。我有房子,你用门进去。所以,门屋! (5认同)

Dav*_*itz 2

无需生成日期。


以下查询将为您提供一个根本没有行的 SHORTCODES 列表:

select SHORTCODE from shortcodes
except
select SHORTCODE from VWTBL_INDICATOR
Run Code Online (Sandbox Code Playgroud)

以下查询将为您提供每个 SHORTCODE 的 MonthYear 的连续范围。

select      SHORTCODE
            ,min(MonthYear) as from_MonthYear
            ,max(MonthYear) as to_MonthYear
            ,count(*)       as months

from       (SELECT   SHORTCODE
                    ,MonthYear
                    ,row_number() over (partition by SHORTCODE order by MonthYear)  as rn

            From     VWTBL_INDICATOR
            ) t

group by    SHORTCODE
            ,DATEADD(month,-rn,MonthYear)   

order by    SHORTCODE
            ,from_MonthYear
Run Code Online (Sandbox Code Playgroud)

如果您愿意,可以使用以下具有附加信息层的版本:

  • Missing_from_MonthYear + to_MonthYear:中间缺少范围
  • 范围:每个 SHORTCODE 的范围数(范围>1 意味着中间有间隙)
  • range_seq:每个SHORTCODE范围的顺序号
  • is_first:指示每个 SHORTCODE 的第一个范围(检查 from_MonthYear 以查看是否缺少前面的日期)
  • is_last:指示每个 SHORTCODE 的最后一个范围(检查 to_MonthYear 以查看是否缺少以下日期)

select      SHORTCODE
           ,from_MonthYear                                                                                  as exists_from_MonthYear
           ,to_MonthYear                                                                                    as exists_to_MonthYear
           ,dateadd (day,1,to_MonthYear)                                                                    as missing_from_MonthYear
           ,dateadd (day,-1,lead (from_MonthYear) over (partition by SHORTCODE order by from_MonthYear))    as missing_to_MonthYear
           ,count       (*) over (partition by SHORTCODE)                                                   as ranges
           ,row_number  ()  over (partition by SHORTCODE order by from_MonthYear)                           as range_seq
           ,case from_MonthYear when min(from_MonthYear) over (partition by SHORTCODE) then 1 end           as is_first
           ,case to_MonthYear   when max(to_MonthYear)   over (partition by SHORTCODE) then 1 end           as is_last

from       (select      SHORTCODE
                       ,min(MonthYear)  as from_MonthYear
                       ,max(MonthYear)  as to_MonthYear
                       ,count(*)        as months

            from       (SELECT      SHORTCODE
                                   ,MonthYear
                                   ,row_number() over (partition by SHORTCODE order by MonthYear)   as rn

                        From        VWTBL_INDICATOR
                        ) t

            group by    SHORTCODE
                       ,DATEADD(month,-rn,MonthYear)    
            ) t

order by    SHORTCODE
           ,from_MonthYear
Run Code Online (Sandbox Code Playgroud)