У меня есть таблица OffDays, где хранятся выходные и праздничные дни. У меня есть таблица под названием LeadTime, в которой хранится время (в днях) для производства продукта. Наконец, у меня есть таблица под названием «Заказ», в которой хранятся продукт и дата заказа.
Можно ли узнать, когда продукт будет готов к производству, без использования хранимых процедур или циклов?
Например:
Расчет, который я ищу, таков:
Мне интересно, можно ли получить возврат запроса 2008-01-16 без использования хранимой процедуры или вычислить его в моем коде приложения.
Изменить (почему нет сохраненных процедур / циклов): Причина, по которой я не могу использовать хранимые процедуры, заключается в том, что они не поддерживаются базой данных. Могу только добавить лишние таблицы / данные. Приложение представляет собой сторонний инструмент отчетности, в котором я могу управлять только запросом SQL.
Изменить (как я сейчас это делаю): Мой текущий метод заключается в том, что у меня есть дополнительный столбец в таблице заказов для хранения рассчитанной даты, а затем запланированное задание / задание cron запускает расчет для всех заказов каждый час. Это далеко не идеально по нескольким причинам.


Просто вычислите это в коде приложения ... намного проще, и вам не придется писать действительно уродливый запрос в вашем sql
Лучше всего использовать календарную таблицу.
Тогда ваш запрос может выглядеть примерно так:
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
)
Надеюсь, это поможет,
РБ.
Привет, Даррел - да, я только что понял, что пропустил упомянутую им таблицу продуктов. Моя вина! Я действительно понимал, что перекрестное соединение календаря сделает это довольно медленно - я пытаюсь подумать, как его переписать в данный момент ...
Почему вы против использования петель?
// какой-то псевдокод
int leadtime = 5;
date order = 2008-01-09;
date finishdate = order;
while (leadtime > 0) {
finishdate.addDay();
if (!IsOffday(finishdate)) leadtime--;
}
return finishdate;эта функция кажется слишком простой, чтобы пытаться найти способ без цикла.
Хм .. одним из решений могло бы быть хранение таблицы дат со смещением, основанным на подсчете нерабочих дней с начала года. Допустим, ян. 2 - выходной. 1/1/08 будет иметь смещение 1 (или 0, если вы хотите начать с 0). 1/3/08 будет иметь смещение 2, потому что счет пропускает 1/2/08. Отсюда простой расчет. Получите смещение даты заказа, добавьте время выполнения заказа, затем выполните поиск по рассчитанному смещению, чтобы получить дату окончания.
Вы можете заранее сформировать таблицу рабочих дней.
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
Вам понадобится хранимая процедура с циклом для создания таблицы WorkingDays, но не для обычных запросов. Кроме того, меньше обращений к серверу, чем при использовании кода приложения для подсчета дней.
Это выглядит очень многообещающим. +1
вот один способ - с помощью функции 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
Один из способов (без создания другой таблицы) - использовать своего рода функцию потолка: для каждой даты в подзапросе узнать, сколько «сроков» предшествует ей относительно даты заказа. Затем возьмите наибольшее число, которое меньше времени выполнения заказа. Используйте соответствующую дату плюс остаток.
Этот код может быть специфическим для 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), поэтому вы должны позаботиться о том, чтобы вовремя вставить выходные дни. Предполагается, что заказы не начинаются в выходные дни.
Извините за резкость, но это один из самых неприятных вопросов, которые я когда-либо видел. Вы перекрестно объединяете календарь с таблицей заказов, где ваш единственный фильтр в календаре - это два логических значения, и у вас есть таблица сроков выполнения с leadId, которую вы присоединяете к order.LeadId. Хм?