Нормализация таблицы - PullRequest
       2

Нормализация таблицы

3 голосов
/ 21 марта 2012

У меня есть устаревшая таблица, которую я не могу изменить. Значения в нем могут быть изменены из устаревшего приложения (приложение также не может быть изменено). Из-за большого доступа к таблице из нового приложения (новое требование) я хотел бы создать временную таблицу, которая, как мы надеемся, ускорит запросы.

Фактическим требованием является подсчет количества рабочих дней от X до Y. Например, укажите все рабочие дни с 1 января 2001 года по 24 декабря 2004 года. Таблица используется для обозначения выходных дней. у разных компаний могут быть разные выходные - это не просто суббота + воскресенье)

Временная таблица будет создаваться из программы .NET, каждый раз, когда пользователь выходит на экран для этого запроса (пользователь может выполнить запрос несколько раз, с разными значениями, таблица создается один раз), поэтому я бы хотел, чтобы это было Быстро настолько, насколько это возможно. Подход, описанный ниже, работает менее чем за секунду, но я протестировал его только с небольшим набором данных, и, тем не менее, он занимает, вероятно, около полсекунды, что не очень хорошо для пользовательского интерфейса - хотя это просто накладные расходы для первого запроса.

Старая таблица выглядит следующим образом:

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 символа, а любой выходной день (суббота + воскресенье + праздничные дни) отмечается знаком X. Каждые полдня обозначается буквой «H». Например, если в четверг начинается месяц, он будет выглядеть следующим образом (четверг + рабочие дни пятницы, суббота + воскресенье, помеченные знаком X):

'  XX     XX ..'

Я бы хотел, чтобы новая таблица выглядела так:

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)

Для его заполнения у меня есть объединение, которое в основном объединяет calendar_month, calendar_month2, calendar_month3 и т. Д.

Если у меня есть цикл, который проходит по всем строкам в # Temp_2, после обработки каждой строки он удаляется из # 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)

Ответы [ 2 ]

2 голосов
/ 21 марта 2012

Это должно соответствовать требованию: «... рассчитать количество рабочих дней от X до Y».

Он считает каждый пробел рабочим днем, а все, кроме X или пробела - половиной.день (должно быть просто H, согласно 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

Предложение where может идти в самом внутреннем запросе.

Это не является стержнем унаследованного формата, на который OP тратит много времени и который, вероятно, займет больше (и, возможно, ненужных) вычислительных циклов.Я предполагаю, что такая вещь была "приятной на вид", а не частью требований.У Джеффа Модена есть отличные статьи о том, как таблица подсчета может помочь в этом случае (во всяком случае, для SQL Server).

Может потребоваться посмотреть конечные пробелы в зависимости от того, какова конкретная СУБДset (обратите внимание, что я использую длину данных, а не len).

ОБНОВЛЕНИЕ: Добавлена ​​запрошенная временная таблица 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 голосов
/ 21 марта 2012

Учитывая, что ваша временная таблица создается медленно, вы можете предварительно рассчитать ее?

Если вы можете установить триггер на существующей таблице, возможно, вы могли бы запустить процедуру, которая будет отбрасывать и создавать временную таблицу. Или поручите агенту задание проверить, обновлена ​​ли существующая таблица (где-нибудь поднять флаг), а затем пересчитать временную таблицу.

Структура существующей таблицы настолько печальна, что я не удивлюсь, если ее нормализация всегда будет дорогой. Предварительный расчет - это простой и легкий способ решения этой проблемы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...