sql >> Databáze >  >> RDS >> Sqlserver

Stále se mi zobrazuje aritmetické přetečení, když filtruji podle data a času obsazení, i když používám IsDate()

DateTime SQL Serveru má doménu 1753-01-01 00:00:00.000 ≤ x ≤ 9999-12-31 23:59:59,997. Rok 210 CE je mimo tuto doménu. Proto ten problém.

Pokud jste používali SQL Server 2008 nebo novější, můžete jej přenést do DateTime2 datový typ a byli byste zlatí (jeho doména je 0001-01-01 00:00:00.0000000 &le x ≤ 9999-12-31 23:59:59,9999999. Ale s SQL Server 2005 jste v podstatě SOL.

To je opravdu problém čištění dat. Můj sklon v případech, jako je tento, je načíst data třetí strany do pracovní tabulky s každým polem jako znakovým řetězcem. Poté vyčistěte data na místě a nahraďte například neplatná data hodnotou NULL. Jakmile je vyčištěn, proveďte nezbytnou konverzi, abyste jej přesunuli do jeho konečného cíle.

Dalším přístupem je použití porovnávání vzorů a filtrování data bez převodu čehokoli na datetime . Hodnoty data/času ISO 8601 jsou znakové řetězce, které mají chvályhodnou vlastnost, že jsou (A) čitelné pro člověka a (B) správně třídí a porovnávají.

V minulosti jsem provedl nějakou analytickou práci, abych identifikoval všechny vzory v poli datetime nahrazením desetinných číslic písmenem 'd' a následným spuštěním příkazu group by vypočítat počty každého různého nalezeného vzoru. Jakmile to budete mít, můžete si vytvořit nějaké tabulky vzorů, které vás vedou. Něco jako tyto:

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 )

Tyto dvě tabulky byste mohli zkombinovat do 1, ale počet kombinací má tendenci věci explodovat, i když to značně zjednodušuje dotaz.

Jakmile to máte, dotaz je [poměrně] snadný, vzhledem k tomu, že SQL není zrovna nejlepší světová volba jazyka pro zpracování řetězců:

---------------------------------------------------------------------
-- 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

Jak jsem řekl, SQL není nejlepší volbou pro přepínání řetězců.

Tohle by vás mělo dostat... na 90 %. Zkušenost mi říká, že stále najdete více špatných dat:měsíce menší než 1 nebo větší než 12, dny menší než 1 nebo větší než 31 nebo dny mimo rozsah pro daný měsíc (nic jako 31. únor, aby počítač kňučel) , atd. Zejména staré programy cobol rády používaly pole všech 9 k označení chybějících dat, například (ačkoli je to snadné řešit).

Mou preferovanou technikou je napsat perlový skript, který vyčistí data a hromadně je nahraje do SQL Serveru pomocí perlu BCP zařízení. To je přesně ten druh problémového prostoru, pro který je perl navržen.




  1. Lepší způsob, jak analyzovat celočíselné hodnoty z řetězce odděleného T-SQL

  2. Okamžitý klient Oracle:ORA-28759:nepodařilo se otevřít soubor

  3. Jak zkontrolovat varchar s úvodními nulami v mysql

  4. Dotaz databáze MYSQL pro vrácení nejvyššího skóre