sql >> データベース >  >> RDS >> Sqlserver

IsDate() を使用している場合でも、キャスト日時をフィルター処理すると、算術オーバーフローが発生します。

    SQL Server の DateTime ドメインは 1753-01-01 00:00:00.000 ≤ x です ≤ 9999-12-31 23:59:59.997。西暦 210 年はそのドメインの外にあります。したがって、問題です。

    SQL Server 2008 以降を使用している場合は、DateTime2 にキャストできます。 datatype とあなたはゴールデンです (そのドメインは 0001-01-01 00:00:00.0000000 &le x です) ≤ 9999-12-31 23:59:59.9999999。しかし、SQL Server 2005 では、ほとんど SOL です。

    これは実際にはデータクリーニングの問題です。このような場合の私の傾向は、サードパーティのデータを各フィールドを文字列としてステージング テーブルにロードすることです。次に、たとえば無効な日付を NULL に置き換えて、その場でデータをクレンジングします。クレンジングしたら、必要な変換作業を行って最終目的地に移動します。

    もう 1 つの方法は、パターン マッチングを使用して、日付を datetime に変換せずに日付をフィルタリングすることです。 .

    私が過去に行ったことは、10 進数を 'd' に置き換えてから group by を実行することで、datetime フィールドのすべてのパターンを特定するための分析作業です。 見つかったそれぞれの異なるパターンのカウントを計算します。それができたら、ガイドとしていくつかのパターン テーブルを作成できます。次のようなもの:

    create table #datePattern
    (
      pattern varchar(64) not null primary key clustered ,
      monPos  int         not null ,
      monLen  int         not null ,
      dayPos  int         not null ,
      dayLen  int         not null ,
      yearPos int         not null ,
      yearLen int         not null ,
    )
    
    insert #datePattern values ( '[0-9]/[0-9]/[0-9] %'                          ,1,1,3,1,5,1)
    insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9] %'                     ,1,1,3,1,5,2)
    insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9][0-9] %'                ,1,1,3,1,5,3)
    insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9][0-9][0-9] %'           ,1,1,3,1,5,4)
    insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9] %'                     ,1,1,3,2,6,1)
    insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9] %'                ,1,1,3,2,6,2)
    insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9][0-9] %'           ,1,1,3,2,6,3)
    insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9] %'      ,1,1,3,2,6,4)
    insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9] %'                     ,1,2,4,1,6,1)
    insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9] %'                ,1,2,4,1,6,2)
    insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9][0-9] %'           ,1,2,4,1,6,3)
    insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9][0-9][0-9] %'      ,1,2,4,1,6,4)
    insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9] %'                ,1,2,4,2,7,1)
    insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9] %'           ,1,2,4,2,7,2)
    insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9] %'      ,1,2,4,2,7,3)
    insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9] %' ,1,2,4,2,7,4)
    
    create table #timePattern
    (
      pattern varchar(64) not null primary key clustered ,
      hhPos int not null ,
      hhLen int not null ,
      mmPos int not null ,
      mmLen int not null ,
      ssPos int not null ,
      ssLen int not null ,
    )
    insert #timePattern values ( '[0-9]:[0-9]:[0-9]'                ,1,1,3,1,5,1 )
    insert #timePattern values ( '[0-9]:[0-9]:[0-9][0-9]'           ,1,1,3,1,5,2 )
    insert #timePattern values ( '[0-9]:[0-9][0-9]:[0-9]'           ,1,1,3,2,6,1 )
    insert #timePattern values ( '[0-9]:[0-9][0-9]:[0-9][0-9]'      ,1,1,3,2,6,2 )
    insert #timePattern values ( '[0-9][0-9]:[0-9]:[0-9]'           ,1,2,4,1,6,1 )
    insert #timePattern values ( '[0-9][0-9]:[0-9]:[0-9][0-9]'      ,1,2,4,1,6,2 )
    insert #timePattern values ( '[0-9][0-9]:[0-9][0-9]:[0-9]'      ,1,2,4,2,7,1 )
    insert #timePattern values ( '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]' ,1,2,4,2,7,2 )
     

    これら 2 つのテーブルを 1 つに結合することもできますが、結合の数が爆発的に増える傾向がありますが、その場合クエリは大幅に簡素化されます。

    SQL が文字列処理に最適な言語ではないことを考えると、クエリは [かなり] 簡単です。

    <プレ>--------------------------------------------------------------------- -- first, get your lower bound in ISO 8601 format yyyy-mm-dd hh:mm:ss -- This will compare/collate properly --------------------------------------------------------------------- declare @dtLowerBound varchar(255) set @dtLowerBound = convert(varchar,dateadd(year,-1,current_timestamp),121) ----------------------------------------------------------------- -- select rows with a start date more recent than the lower bound ----------------------------------------------------------------- select isoDate = + right( '0000' + substring( t.startDate , coalesce(dt.yearPos,1) , coalesce(dt.YearLen,0) ) , 4 ) + '-' + right( '00' + substring( t.startDate , coalesce(dt.monPos,1) , coalesce(dt.MonLen,0) ) , 2 ) + '-' + right( '00' + substring( t.startDate , coalesce(dt.dayPos,1) , coalesce(dt.dayLen,0) ) , 2 ) + case when tm.pattern is not null then ' ' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.hhPos , tm.hhLen ) , 2 ) + ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.mmPos , tm.mmLen ) , 2 ) + ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.ssPos , tm.ssLen ) , 2 ) else '' end ,* from someTableWithBadData t left join #datePattern dt on t.startDate like dt.pattern left join #timePattern tm on ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ) like tm.pattern where @lowBound <= + right( '0000' + substring( t.startDate , coalesce(dt.yearPos,1) , coalesce(dt.YearLen,0) ) , 4 ) + '-' + right( '00' + substring( t.startDate , coalesce(dt.monPos,1) , coalesce(dt.MonLen,0) ) , 2 ) + '-' + right( '00' + substring( t.startDate , coalesce(dt.dayPos,1) , coalesce(dt.dayLen,0) ) , 2 ) + case when tm.pattern is not null then ' ' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.hhPos , tm.hhLen ) , 2 ) + ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.mmPos , tm.mmLen ) , 2 ) + ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.ssPos , tm.ssLen ) , 2 ) else '' end

    私が言ったように、SQL は文字列を変更するための最良の選択ではありません.

    これで... 90% 達成できるはずです。経験上、まだ悪い日付が見つかることがわかります:1 未満または 12 より大きい月、1 未満または 31 より大きい日、またはその月の範囲外の日 (2 月 31 日のようにコンピューターを鳴らすようなものは何もありません)など。特に古い COBOL プログラムでは、たとえば、すべて 9 のフィールドを使用して欠落データを示すことが好まれていました (ただし、これは簡単に対処できます)。

    私が好むテクニックは、perl の BCP 機能を使用して、データをスクラブし、SQL Server に一括ロードする perl スクリプトを作成することです。それはまさに、perl が設計された種類の問題空間です。




    1. Oracle pl sqlを使用したhttpリクエストのutl_httpパッケージに代わるものはありますか?

    2. MySQL:MySQLテーブルのすべてのレコードの内部レコード識別子のようなものはありますか?

    3. 存在する場合と存在しない場合があるグループのマッチング

    4. OracleSYS_GUIDは変更されません