2012-03-20 20 views
3

私は変更できない従来のテーブルを持っています。 その中の値は、従来のアプリケーションから変更できます(アプリケーションも変更できません)。 新しいアプリケーション(新しい要件)からテーブルへのアクセスが多いため、私はうまくいけばクエリをスピードアップする一時テーブルを作成したいと思います。テーブルの正規化

実際の要件は、XからYまでの営業日数を計算することです。たとえば、2001年1月1日から2004年12月24日までのすべての営業日をお知らせください。 (土曜日と日曜日だけではありません)

一時テーブルは、ユーザーがこのクエリの画面を入力するたびに.NETプログラムから作成されます複数回、異なる値で、テーブルが一度作成されるので)、私はできるだけ早くそれをしたいと思います。以下のアプローチは1秒以内に実行されますが、私は小さなデータセットでテストしましたが、おそらく最初のクエリのオーバヘッドであるにもかかわらず、UIにはあまり役に立ちません。

はレガシーテーブルには、次のようになります。

CREATE TABLE [business_days](
    [country_code] [char](3) , 
    [state_code] [varchar](4) , 
    [calendar_year] [int] , 
    [calendar_month] [varchar](31) , 
    [calendar_month2] [varchar](31) , 
    [calendar_month3] [varchar](31) , 
    [calendar_month4] [varchar](31) , 
    [calendar_month5] [varchar](31) , 
    [calendar_month6] [varchar](31) , 
    [calendar_month7] [varchar](31) , 
    [calendar_month8] [varchar](31) , 
    [calendar_month9] [varchar](31) , 
    [calendar_month10] [varchar](31) , 
    [calendar_month11] [varchar](31) , 
    [calendar_month12] [varchar](31) , 
misc. 
) 

毎月31文字、および任意の休日(土曜日+日曜日+祝日)各半日が「H」とマークされているXでマークされてい。例えば、それは次のようになりますよりも月が、木曜日に開始した場合(木+金曜の平日、土曜日+日曜日はX印):

' XX  XX ..' 

私はそう見えるように新しいテーブルが欲しい:

temporary-を作成します。これまでで私がやってしまった、
create table #Temp (country varchar(3), state varchar(4), date datetime, hours int) 

そして、私は(以前のクエリからのXまたはHでマークされた)のみオフになっている日間の行を持っているしたいと思います

中間テーブル、これは次のようになります:

create table #Temp_2 (country_code varchar(3), state_code varchar(4), calendar_year int, calendar_month varchar(31), month_code int) 

を移入するために、私は、各行が処理された後、私は、#Temp_2内のすべての行をループループを持っているよりも基本的には労働組合のはCalendar_Minute、calendar_month2、calendar_month3など

組合を持っています#Temp_2から削除されます。 行を処理するために、1〜31のループがあり、部分文字列(calendar_month、counter、1)がXまたはHのいずれかでチェックされます。この場合、#Tempテーブルへの挿入があります。

Declare @country_code char(3) 
Declare @state_code varchar(4) 
Declare @calendar_year int 
Declare @calendar_month varchar(31) 
Declare @month_code int 
Declare @calendar_date datetime 
Declare @day_code int 
WHILE EXISTS(SELECT * From #Temp_2) -- where processed = 0) 
BEGIN 
    Select Top 1 @country_code = t2.country_code, @state_code = t2.state_code, @calendar_year = t2.calendar_year, @calendar_month = t2.calendar_month, @month_code = t2.month_code From #Temp_2 t2 -- where processed = 0 

    set @day_code = 1 
    while @day_code <= 31 
    begin 
     if substring(@calendar_month, @day_code, 1) = 'X' 
     begin 
      set @calendar_date = convert(datetime, (cast(@month_code as varchar) + '/' + cast(@day_code as varchar) + '/' + cast(@calendar_year as varchar))) 
      insert into #Temp (country, state, date, hours) values (@country_code, @state_code, @calendar_date, 8) 
     end 
     if substring(@calendar_month, @day_code, 1) = 'H' 
     begin 
      set @calendar_date = convert(datetime, (cast(@month_code as varchar) + '/' + cast(@day_code as varchar) + '/' + cast(@calendar_year as varchar))) 
      insert into #Temp (country, state, date, hours) values (@country_code, @state_code, @calendar_date, 4) 
     end 

     set @day_code = @day_code + 1 
    end 
    delete from #Temp_2 where @country_code = country_code AND @state_code = state_code AND @calendar_year = calendar_year AND @calendar_month = calendar_month AND @month_code = month_code 
    --update #Temp_2 set processed = 1 where @country_code = country_code AND @state_code = state_code AND @calendar_year = calendar_year AND @calendar_month = calendar_month AND @month_code = month_code 
END 

[編集コードを追加]私はSQLの専門家ではないですので、私は私のアプローチにいくつかの入力、および多分より良いアプローチの提案を取得したいのですが。

一時テーブルを持った後、私は(日付はテーブルから来ることになる)行うことを計画しています:

select cast(convert(datetime, ('01/31/2012'), 101) -convert(datetime, ('01/17/2012'), 101) as int) - ((select sum(hours) from #Temp where date between convert(datetime, ('01/17/2012'), 101) and convert(datetime, ('01/31/2012'), 101))/8) 

テーブルを正規化するの溶液に加えて、私は今のところ実装され、他の解決策は、あります現在のテーブルをスキャンして営業日を取得するというこのすべてのロジックを実行する関数です。それはかなり速く実行されますが、結果を得るために単純なクエリを追加できるのであれば、私は関数を呼び出すことを躊躇しています。

(私は現在、MSSQLでこれをしようとしているが、私は、Sybase ASEとOracleのために同じことを行う必要があります)

+0

ループコードを送信します。 – Paparazzi

+0

あなたは3つの異なるDBMSで動作するようにクエリが必要だと言っていますか? –

+0

mssql + sybase + oracleで同じクエリを実行する必要はありません。他のデータベースと同様のものが必要です。誰かがそれらのいずれかの提案をすることができ、私はそのデータベースから始め、mssqlにバックポートします。 – Alex

答えて

2

これは「、要件を満たす必要があります... XからYへの営業日数を計算します"

各営業日を営業日とし、Xまたは半角スペース以外のものをOPとします。

私は、SQL Server 2008 R2でこれを引っ張っ:句は、最も内側のクエリに行くことができ

-- Calculate number of business days from X to Y 
declare @start date = '20120101' -- X 
declare @end date = '20120101' -- Y 
-- Outer query sums the length of the full_year text minus non-work days 
-- Spaces are doubled to help account for half-days...then divide by two 
select sum(datalength(replace(replace(substring(full_year, first_day, last_day - first_day + 1), ' ', ' '), 'X', ''))/2.0) as number_of_business_days 
from (
    select 
     -- Get substring start value for each year 
     case 
       when calendar_year = datepart(yyyy, @start) then datepart(dayofyear, @start) 
       else 1 
      end as first_day 
     -- Get substring end value for each year 
     , case 
       when calendar_year = datepart(yyyy, @end) then datepart(dayofyear, @end) 
       when calendar_year > datepart(yyyy, @end) then 0 
       when calendar_year < datepart(yyyy, @start) then 0 
       else datalength(full_year) 
      end as last_day 
     , full_year 
    from (
     select calendar_year 
      -- Get text representation of full year 
      , calendar_month 
      + calendar_month2 
      + calendar_month3 
      + calendar_month4 
      + calendar_month5 
      + calendar_month6 
      + calendar_month7 
      + calendar_month8 
      + calendar_month9 
      + calendar_month10 
      + calendar_month11 
      + calendar_month12 as full_year 
     from business_days 
     -- where country_code = 'USA' etc. 
    ) as get_year 
) as get_days 

Aを。

これは、OPが多くの時間を費やし、多分(おそらくは不要な)計算サイクルを要するレガシーフォーマットのピボットではありません。私は、そのようなものが要件の一部ではなく「見て良かった」と仮定しています。 Jeff Modenには、そのような場合にtally tableがどのように役立つかについての素晴らしい記事があります(とにかくSQL Server用)。

特定のDBMSがどのように設定されているかによって、後続のスペースを監視する必要があります(データ長を使用していて、lenではないことに注意してください)。

UPDATE:はOPの要求された一時テーブルを追加しました:あなたの一時テーブルを考えると

select country_code 
    , state_code 
    , dateadd(d, t.N - 1, cast(cast(a.calendar_year as varchar(8)) as date)) as calendar_date 
    , case substring(full_year, t.N, 1) when 'X' then 0 when 'H' then 4 else 8 end as business_hours 
from (
    select country_code 
     , state_code 
     , calendar_year 
     , calendar_month 
      + calendar_month2 
      + calendar_month3 
      + calendar_month4 
      + calendar_month5 
      + calendar_month6 
      + calendar_month7 
      + calendar_month8 
      + calendar_month9 
      + calendar_month10 
      + calendar_month11 
      + calendar_month12 
      as full_year 
    from business_days 
) as a, (
     select a.N + b.N * 10 + c.N * 100 + 1 as N 
     from (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) a 
      , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) b 
      , (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) c 
    ) as t -- cross join with Tally table built on the fly 
where t.N <= datalength(a.full_year) 
+0

ありがとうございます。おそらく、tempテーブルを使用する代わりに、あなたが指定したselectステートメントを使用するつもりですが、両方を行う方法についての情報を提供してくれてありがとう。 – Alex

0

を作成するために遅いです、あなたはそれを事前に計算することができますか?

既存のテーブルにトリガをかけることができれば、おそらく、一時テーブルを削除して作成するprocを起動する可能性があります。または、既存のテーブルが更新されたかどうかをチェックして(どこかのフラグを立てて)、一時テーブルを再計算するエージェントジョブを作成します。

既存のテーブルの構造は、常にそれを正規化するのに高価になる場合、私は驚かないほど悲惨です。事前計算は、その問題を回避する簡単で簡単な方法です。