Неитеративный/нециклический способ расчета даты вступления в силу?

StackOverflow https://stackoverflow.com/questions/92699

  •  01-07-2019
  •  | 
  •  

Вопрос

У меня есть таблица OffDays, в которой хранятся выходные и праздничные дни.У меня есть таблица LeadTime, в которой хранится количество времени (в днях) для производства продукта.Наконец, у меня есть таблица Order, в которой хранится продукт и дата заказа.

Можно ли узнать, когда будет закончено производство продукта, без использования хранимых процедур или циклов?

Например:

  • В OffDays есть 10 января 2008 г., 11 января 2008 г., 14 января 2008 г.
  • У LeadTime 5 для продукта 9.
  • В заказе указан продукт 9 от 09.01.2008.

Расчет, который я ищу, заключается в следующем:

  • 2008-01-09 1
  • 10 января 2008 г. х
  • 11 января 2008 г. х
  • 2008-01-12 2
  • 2008-01-13 3
  • 14 января 2008 г. х
  • 2008-01-15 4
  • 2008-01-16 5

Мне интересно, возможно ли вернуть запрос 16 января 2008 г. без необходимости использовать хранимую процедуру или вычислять ее в коде моего приложения.

Изменить (почему нет сохраненных процедур/циклов):Причина, по которой я не могу использовать хранимые процедуры, заключается в том, что они не поддерживаются базой данных.Я могу только добавлять дополнительные таблицы/данные.Приложение представляет собой сторонний инструмент отчетности, с помощью которого я могу контролировать только SQL-запрос.

Редактировать (как я это делаю сейчас):Мой текущий метод заключается в том, что у меня есть дополнительный столбец в таблице заказов для хранения расчетной даты, а затем запланированное задание/задание cron запускает расчет для всех заказов каждый час.Это далеко не идеально по нескольким причинам.

Это было полезно?

Решение

Вы можете заранее сформировать таблицу рабочих дней.

WDId | WDDate
-----+-----------
4200 | 2008-01-08
4201 | 2008-01-09
4202 | 2008-01-12
4203 | 2008-01-13
4204 | 2008-01-16
4205 | 2008-01-17

Затем выполните запрос, например

SELECT DeliveryDay.WDDate FROM WorkingDay OrderDay, WorkingDay DeliveryDay, LeadTime, Order where DeliveryDay.WDId = OrderDay.WDId + LeadTime.LTDays AND OrderDay.WDDate = '' AND LeadTime.ProductId = Order.ProductId AND Order.OrderId = 1234

Вам понадобится хранимая процедура с циклом для создания таблицы WorkDays, но не для обычных запросов.Кроме того, требуется меньше обращений к серверу, чем при использовании кода приложения для подсчета дней.

Другие советы

Лучший подход — использовать таблицу календаря.

Видеть http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html.

Тогда ваш запрос может выглядеть примерно так:

SELECT c.dt, l.*, o.*, c.*
    FROM [statistics].dbo.[calendar] c, 
    [order] o  JOIN
    lead l ON l.leadId = o.leadId
    WHERE c.isWeekday = 1 
    AND   c.isHoliday =0 
    AND   o.orderId = 1
    AND   l.leadDays = ( 
        SELECT COUNT(*)  
            FROM [statistics].dbo.Calendar c2 
            WHERE c2.dt >= o.startDate
            AND c2.dt <= c.dt 
            AND c2.isWeekday=1 
            AND c2.isHoliday=0 
    )

Надеюсь, это поможет,

РБ.

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

вот один из способов - использование функции dateadd.

Мне нужно убрать этот ответ со стола.Это не будет работать должным образом в течение длительного времени.Он просто добавлял количество выходных дней, найденных во время выполнения заказа, и сдвигал дату.Это вызовет проблему, если в новом диапазоне появится больше выходных дней.

-- Setup test
create table #odays (offd datetime)
create table #leadtime (pid int , ltime int)
create table [#order] (pid int, odate datetime)


insert into #odays 
select '1/10/8'
insert into #odays 
select '1/11/8'
insert into #odays 
select '1/14/8'


insert into #Leadtime
values (3,5)
insert into #leadtime
values (9, 5)

insert into #order 
values( 9, '1/9/8')

select dateadd(dd, 
(select count(*)-1 
   from #odays 
   where offd between odate and  
    (select odate+ltime 
       from #order o 
       left join #leadtime l 
         on o.pid = l.pid 
       where l.pid = 9
     )
 ),
 odate+ltime) 
 from #order o 
 left join #leadtime l  
   on o.pid = l.pid 
 where o.pid = 9

Почему вы против использования циклов?

//некоторый псевдокод

int leadtime = 5;
date order = 2008-01-09;
date finishdate = order;
while (leadtime > 0) {
finishdate.addDay();
if (!IsOffday(finishdate)) leadtime--;
}
return finishdate;

это кажется слишком простой функцией, чтобы пытаться найти способ без цикла.

Хм..Одним из решений может быть сохранение таблицы дат со смещением, основанным на количестве невыходных дней с начала года.Допустим, январь.2- выходной.01.01.08 будет иметь смещение 1 (или 0, если вы хотите начать с 0).03.01.08 будет иметь смещение, равное 2, поскольку отсчет пропускает 02.01.08.Отсюда простой расчет.Получите смещение даты заказа, добавьте время выполнения, затем выполните поиск по рассчитанному смещению, чтобы получить дату окончания.

Один из способов (без создания еще одной таблицы) — использовать своего рода потолочную функцию:для каждой даты выясните, сколько «дат» предшествует дате заказа по отношению к дате заказа в подзапросе.Затем возьмите наибольшее число, которое меньше времени выполнения заказа.Используйте дату, соответствующую этому, плюс остаток.

Этот код может быть специфичным для PostgreSQL. Извините, если вы его не используете.

CREATE DATABASE test;
CREATE TABLE offdays
(
  offdate date NOT NULL,
  CONSTRAINT offdays_pkey PRIMARY KEY (offdate)
);
insert into offdays (offdate) values ('2008-01-10');
insert into offdays (offdate) values ('2008-01-11');
insert into offdays (offdate) values ('2008-01-14');
insert into offdays (offdate) values ('2008-01-18'); -- just for testing
CREATE TABLE product
(
  id integer NOT NULL,
  CONSTRAINT product_pkey PRIMARY KEY (id)
);
insert into product (id) values (9);
CREATE TABLE leadtime
(
  product integer NOT NULL,
  leaddays integer NOT NULL,
  CONSTRAINT leadtime_pkey PRIMARY KEY (product),
  CONSTRAINT leadtime_product_fkey FOREIGN KEY (product)
      REFERENCES product (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);
insert into leadtime (product, leaddays) values (9, 5);
CREATE TABLE "order"
(
  product integer NOT NULL,
  "start" date NOT NULL,
  CONSTRAINT order_pkey PRIMARY KEY (product),
  CONSTRAINT order_product_fkey FOREIGN KEY (product)
      REFERENCES product (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);
insert into "order" (product, "start") values (9, '2008-01-09');

-- finally, the query:

select e.product, offdate + (leaddays - ondays)::integer as "end"
from
(
    select c.product, offdate, (select (a.offdate - c."start") - count(b.offdate) from offdays b where b.offdate < a.offdate) as ondays, d.leaddays
    from offdays a, "order" c
    inner join leadtime d on d.product = c.product
) e
where leaddays >= ondays
order by "end" desc
limit 1;

Это синтаксис PostgreSQL, но его легко перевести на другой диалект SQL.

--Sample data
create table offdays(datum date);

insert into offdays(datum)
select to_date('2008-01-10','yyyy-MM-dd') UNION 
select to_date('2008-01-11','yyyy-MM-dd') UNION 
select to_date('2008-01-14','yyyy-MM-dd') UNION 
select to_date('2008-01-20','yyyy-MM-dd') UNION
select to_date('2008-01-21','yyyy-MM-dd') UNION
select to_date('2008-01-26','yyyy-MM-dd');

create table leadtime (product_id integer , lead_time integer);
insert into leadtime(product_id,lead_time) values (9,5);

create table myorder (order_id integer,product_id integer, datum date);
insert into myorder(order_id,product_id,datum) 
values (1,9,to_date('2008-01-09','yyyy-MM-dd'));
insert into myorder(order_id,product_id,datum) 
values (2,9,to_date('2008-01-16','yyyy-MM-dd'));
insert into myorder(order_id,product_id,datum) 
values (3,9,to_date('2008-01-23','yyyy-MM-dd'));

--Query
select order_id,min(finished_date)
FROM 
    (select mo.order_id,(mo.datum+lead_time+count(od2.*)::integer-1) as finished_date
     from 
         myorder mo
         join leadtime lt on (mo.product_id=lt.product_id)
         join offdays od1 on (mo.datum<od1.datum)
         left outer join offdays od2 on (mo.datum<od2.datum and od2.datum<od1.datum)
     group by  mo.order_id,mo.datum,lt.lead_time,od1.datum
     having (mo.datum+lead_time+count(od2.*)::integer-1) < od1.datum) tmp
group by 1;       

--Results :
1    2008.01.16
2    2008.01.22

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top