Metodo non iterativo/senza loop per calcolare la data di entrata in vigore?

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

  •  01-07-2019
  •  | 
  •  

Domanda

Ho una tabella chiamata OffDays, in cui vengono conservati i fine settimana e le date delle festività.Ho una tabella chiamata LeadTime in cui viene archiviata la quantità di tempo (in giorni) necessaria per la produzione di un prodotto.Infine ho una tabella chiamata Order in cui vengono conservati un prodotto e la data dell'ordine.

È possibile interrogare quando verrà terminata la produzione di un prodotto senza utilizzare procedure o cicli memorizzati?

Per esempio:

  • OffDays ha 2008-01-10, 2008-01-11, 2008-01-14.
  • LeadTime ne ha 5 per il prodotto 9.
  • L'ordine ha 2008-01-09 per il prodotto 9.

Il calcolo che cerco è questo:

  • 2008-01-09 1
  • 2008-01-10x
  • 2008-01-11X
  • 2008-01-12 2
  • 2008-01-13 3
  • 2008-01-14x
  • 2008-01-15 4
  • 2008-01-16 5

Mi chiedo se sia possibile ottenere una query che restituisca 2008-01-16 senza dover utilizzare una procedura memorizzata o calcolarla nel codice della mia applicazione.

Modifica (perché nessun processo/loop memorizzato):Il motivo per cui non posso utilizzare le procedure memorizzate è che non sono supportate dal database.Posso solo aggiungere tabelle/dati aggiuntivi.L'applicazione è uno strumento di reporting di terze parti in cui posso controllare solo la query SQL.

Modifica (come lo sto facendo ora):Il mio metodo attuale prevede di avere una colonna aggiuntiva nella tabella degli ordini per contenere la data calcolata, quindi un'attività pianificata/lavoro cron esegue il calcolo su tutti gli ordini ogni ora.Questo non è l'ideale per diversi motivi.

È stato utile?

Soluzione

È possibile generare in anticipo una tabella dei giorni lavorativi.

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

Quindi esegui una query come

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

Avresti bisogno di una procedura memorizzata con un ciclo per generare la tabella WorkingDays, ma non per query regolari.Sono inoltre necessari meno viaggi di andata e ritorno al server rispetto a quando si utilizza il codice dell'applicazione per contare i giorni.

Altri suggerimenti

L'approccio migliore consiste nell'utilizzare una tabella Calendario.

Vedere http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html.

Quindi la tua query potrebbe essere simile a:

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 
    )

Spero che aiuti,

RB.

Basta calcolarlo nel codice dell'applicazione...molto più semplice e non dovrai scrivere una query davvero brutta nel tuo sql

ecco un modo: usare la funzione dateadd.

Devo togliere questa risposta dal tavolo.Questo non funzionerà correttamente per tempi di consegna lunghi.Si trattava semplicemente di aggiungere il numero di giorni liberi trovati nel lead time e di spostare la data.Ciò causerà un problema quando nel nuovo intervallo verranno visualizzati più giorni di riposo.

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

Perché sei contrario all'uso dei loop?

//uno pseudocodice

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

sembra una funzione troppo semplice per cercare di trovare un modo senza loop.

Hmm..una soluzione potrebbe essere quella di memorizzare una tabella di date con un offset basato sul conteggio dei giorni non liberi dall'inizio dell'anno.Diciamo gennaio.2 è un giorno no.1/1/08 avrebbe un offset di 1 (o 0 se desideri iniziare da 0).1/3/08 avrebbe un offset di 2, perché il conteggio salta 1/2/08.Da lì è un semplice calcolo.Ottieni l'offset della data dell'ordine, aggiungi il lead time, quindi esegui una ricerca sull'offset calcolato per ottenere la data di fine.

Un modo (senza creare un'altra tabella) è utilizzare una sorta di funzione soffitto:per ogni data non aggiornata, scopri quante "date" la precedono, rispetto alla data dell'ordine, in una sottoquery.Quindi prendi il numero più alto che è inferiore al tempo di consegna.Utilizzare la data corrispondente, più il resto.

Questo codice potrebbe essere specifico per PostgreSQL, scusa se non è quello che stai utilizzando.

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;

Questa è la sintassi PostgreSQL ma dovrebbe essere facile da tradurre in altri dialetti 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

Questo sarà non tornare risultato per gli ordini che verrebbero completati dopo l'ultima data nella tabella dei giorni off (ordine numero 3), quindi è necessario fare attenzione a inserire i giorni off in tempo. Si presuppone che gli ordini non inizino nei giorni off.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top