web-dev-qa-db-ja.com

約250万行のテーブルで欠落しているデータのギャップを見つける

私は経済コンサルティング会社で働いており、すべてのデータはSQL Server 11(2012)に格納されています。私たちのデータは基本的に、日付列、生データ列、いくつかの計算列、および各データセットを次のセットと区別するためのショートコードを持つ列で構成されています。

何千ものこれらのセットがあり、すべて同じテーブルに読み込まれ(合計で約2.5ミリ行)、クエリから取得され、並べ替えられます。それらは通常、最初の月から始まり、最後の月まで月ごとに移動する日付順になっています。各シリーズの開始日と終了日は、わずか1年分の時間から100年以上までさまざまです。

最近、データのランダムなセクションが消えるという問題が発生しています。行全体がすぐに終了します。そのため、これらの行の欠落を見つけるのは、月ごとに欠落しているかどうかを確認するために月ごとをくまなく調べるのがやや難しく、250万行の表ではやや不可能な作業です。

上司は、この巨大なテーブルを調べて行が欠落しているセットとそれらの場所を確認するクエリ/ストアドプロシージャを作成するように私に命じました。

私はSQLのスキルがある場所より少し上にあるこの問題に対処しようとしていますが、Webのどこにも同様の問題が発生した人を見つけることができません。私はすでに持っているものを調べます。多分誰かが私が正しい方向に向かっているかどうかを教えてくれ、おそらくここからどこへ行くべきかについていくつかの洞察を提供できます。

Webで見つけた最良の解決策は、CTEを使用して日付の一時テーブルを作成し、それを元のテーブルと比較することでした。これは、1つの特定のデータセットの問題をスキャンするだけの場合にうまく機能しますが、同じテーブル内に多くのデータセットがあり、すべて開始日と終了日が異なります。とにかく私はそれを使って、結局それらを拡張してそれらの多くを検索できるようにしたいと思っていました。これが私のコードです:

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)

「RMI West」はこの特定のデータセットをマークするショートコードであり、2004年11月から2005年3月までのデータが欠落しており、このクエリを実行するとnullとして表示されます。これはほぼ正確に必要なことですが、テーブルにあるすべてのデータセットについてです。

このクエリを適切に書き込むにはどうすればよいですか?私たちの会社は、私たちのデータベースで行われた一連の作業を請け負っています。そのため、私は実際にはその根本にあまり精通していません。アップロード機能があり、それを使用してデータをプルすると、すべて正常です。しかし、1〜2週間後のデータを見ると、ランダムな日付がありません。彼らは解決策を検討してきましたが、当面の間、これらのギャップを見つける方法が必要です。

私たちは常に月をチェックします。ほとんどの場合、データは指定された年の1月に始まり、今月またはデータがまだ出ていない場合はその月の終わりに終了します。

私はショートコードのメタデータテーブルを持っていますが、これまでこの情報をまったく使用していなかったため、各ショートコードの予想月数は含まれていません。これが適切に機能するために不可欠である場合、データの更新時にフィールドを追加し、更新スクリプトを変更してそれらを含めるように問い合わせることができます。

私が勤めている会社は、歴史的にすべてのデータを膨大な数のExcelスプレッドシートに保存してきました。彼らはこのすべてのデータをSQLに移行する過程にあります。私の仕事は、SQL Serverのデータが元のスプレッドシートのデータと一致することを確認するために、さまざまな方法でそれをチェックすることです。生データには、ファイルシステムのスプレッドシートネットワークからアクセスできます。データを更新するときも、Excelを使用して情報を更新し、前述のアップロード機能を使用してSQLにロードします。しばらくの間、データはほとんどクリーンで一致しています。このデータギャップの問題が発生したのは、過去数か月以内だけです。

6
Zimmerel

日付を生成する必要はありません。


次のクエリは、行がまったくないSHORTCODESのリストを提供します。

select SHORTCODE from shortcodes
except
select SHORTCODE from VWTBL_INDICATOR

次のクエリは、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

必要に応じて、追加の情報レイヤーを持つ次のバージョンを使用できます。

  • 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

はじめに、202か月で確認することは大きな問題ではありませんが、再帰CTEは一般に、パフォーマンスの観点から、セットを導出するための最悪の方法です(私はこれを証明します here および ここ )。

このクエリを複数回実行する場合(そして、誰が/何がこのデータを削除し、最初にギャップを作成するかという別の問題を解決するまで、そうなると思われます)、単にビルドしないのはなぜですか常にそこにある月のテーブル?

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;

2029年まで機能する30か月の月数(whopping72kbに保存)最初にこれを書いたとき、私は皮肉なことになんとか強調しましたが、なぜこれが期待された2ではなく9ページであるのかを説明する必要があります。 )、ストレージエンジンは、新しいオブジェクトのために全体の均一なエクステントを予約します。これは8 x 8kbページに加えて、72kbのIAMページです。この場合、実際に必要なのはデータページの1つだけなので、7つは未割り当てのままになります。つまり、すべてのカタログビューに表示されるわけではありませんが、簡単に見つけることができます( クリックして拡大 )。

enter image description here

ユーザーデータベースに対してこの動作をオフにすることができますが、個人的にはしない( 理由により、デフォルトにした )。最初の本能はディスクスペースではなくメモリを節約することかもしれませんが、これによりディスクに72 kbが置かれますが、バッファプールに読み込まれるのは16 kbだけです。だから、それについて慌てる必要はありません。

これで、クエリは次のようになります。

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;

現在これは、thatShortCodeに有効な範囲外であっても、ShortCodeが表示されない定義済み範囲内のすべての月を識別します。 ShortCodeごとの有効範囲がどこかに定義されている場合は、その情報を質問に追加してください。

「VWTBL」とは一体何ですか?

9
Aaron Bertrand

なぜ私のデータが消えてしまうのか

データできません SQLテーブルから(破損することなく)そのまま消えるだけで、何かを削除する必要があります。

悪意のあるユーザーか何かかもしれませんが、私の経験では、意図したよりも多くの行をキャッチしている、不十分に書かれたアーカイブルーチンのようなものである可能性がはるかに高くなります。古いレコードをクリーンアップするためにデータベースで実行されるメンテナンスルーチンはありますか?

データベースサポートの一部を契約しているとおっしゃっていましたが、これを優先度の高い問題として提起できますか?それを行う彼らのルーチンの1つである可能性があります。

また、これらの行はあなたの考えでは削除されない可能性があります。おそらく、間違った日付で多数の行を更新する不適切に記述されたクエリと、無効/重複としてフラグを付けてそれらを削除する別のルーチンがあるでしょう。

最後に、このテーブルはパーティション化されていますか?それが日付で分割されていて、いくつかの凝ったローリング日付ウィンドウを実行している場合、それが正確に設定されていることに問題がある可能性があります。

しかし、ゼロから、ここで私がチェックするものは次のとおりです。

1。データベースの破損をチェックします

定期的に実行していない場合は、営業時間外にデータベースでDBCC CHECKDBを実行してください。エラーが返された場合は、より大きな問題がある可能性があります。

2。ユーザーのセキュリティをロックダウンします

さまざまなグループの人々が必要とするアクセスのタイプを識別し、必要最小限のアクセス権をそれらに付与します。これは、データベースレベル(ロールを介して)または個別のテーブルレベル(明示的な権限を介して)で実行できます。

レポートのみを実行していますか?読み取り専用。

データをインポートしていますか? INSERT、ただしUPDATEまたはDELETE。

3。トレースを実行してデータベースのアクティビティを監視します

プロファイラートレースを実行して(または サーバー側トレース を開始して)、削除がいつ発生したかを確認できます。キャプチャーする行数を減らすために、DELETEのフィルターを追加します。

4。テーブルでの削除の追跡

発生する削除ステートメントを追跡する方法はいくつかあります この質問で説明します 。あなたの状況では、テーブルトリガーのような音が最も簡単な解決策になります。

4
BradC

カレンダーテーブルとサブクエリを使用して、ショートコードの日付範囲を生成できます。

次の例では、 SQL Serverでの日付ディメンションまたはカレンダーテーブルの作成-Aaron Bertrand からのスニペットを使用しています。

-- if regional settings are interfering with interpretation of dates/literals:
/*
set datefirst 7;
set dateformat mdy;
set language us_english;
--*/

if object_id('dbo.Calendar_Months') is not null drop table dbo.Calendar_Months;
create table dbo.Calendar_Months (
    [Date]         date     not null
  , [Year]         smallint not null
  , [Month]        tinyint  not null
  , [Quarter]      tinyint  not null
  , [YearMonth]    char(7)  not null /* yyyy-mm */
  , [YearQuarter]  char(7)  not null /* yyyy-qq */

  , constraint pk_Calendar_Months primary key clustered (date)
  );

declare @FromDate date = '19000101';
declare @ThruDate date = '20201201';

with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, d as (
  select DateValue=convert(date,dateadd(month
      , row_number() over (order by (select 1)) -1, @fromdate))
    from         n as deka
      cross join n as hecto
      cross join n as kilo    
      cross join n as [10k]   
      --cross join n as [100k]
      --cross join n as mega
)
insert into dbo.Calendar_Months 
    ([Date], [Year], [Month], [Quarter], [YearMonth],[YearQuarter])
  select top (datediff(month, @FromDate, @ThruDate)+1) 
      [Date]        = DateValue
    , [Year]        = convert(smallint,datepart(year,DateValue))
    , [Month]       = convert(tinyint,datepart(month,DateValue)) 
    , [Quarter]     = convert(tinyint,datepart(quarter,DateValue)) 
    , [YearMonth]   = convert(char(7) ,convert(char(4), datepart(year,DateValue))
                    +right('0'+convert(varchar(2), datepart(month,DateValue)),2))
    , [YearQuarter] = convert(char(7) ,convert(char(4), datepart(year,DateValue))
                    +'-Q'+convert(char(1), datepart(quarter,DateValue)))

    from d
    order by DateValue;

  with ShortCodeDateSpan as (
    select 
          i.shortcode
        , FromDate=min(i.monthyear)
        , ThruDate=max(i.monthyear)
      from vwtbl_indicator as i
      group by i.shortcode
    )

    select 
          scds.shortcode
        , MissingYearMonth=cm.Date
      from ShortCodeDateSpan as scds
        inner join dbo.Calendar_Months cm 
             on cm.Date >= scds.FromDate
            and cm.Date <= scds.ThruDate
        where not exists (
          select 1 
            from vwtbl_indicator as i 
            where i.shortcode=scds.shortcode 
              and cm.Date = i.MonthYear
          );

カレンダーと数字の表の参照:

1
SqlZim