【问题标题】:Non-iterative / Non-looping Way To Calculate Effective Date?计算生效日期的非迭代/非循环方式?
【发布时间】:2010-09-10 16:46:13
【问题描述】:

我有一个名为 OffDays 的表,其中保留了周末和节假日日期。我有一个名为 LeadTime 的表,其中存储了要制造的产品的时间量(以天为单位)。最后,我有一个名为 Order 的表,其中保存了产品和订单日期。

是否可以在不使用存储过程或循环的情况下查询产品何时完成制造?

例如:

  • OffDays 有 2008-01-10、2008-01-11、2008-01-14。
  • LeadTime 有 5 个产品 9。
  • 产品 9 的订单编号为 2008-01-09。

我正在寻找的计算是这样的:

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

我想知道是否可以让查询返回 2008-01-16 而无需使用存储过程,或者在我的应用程序代码中计算它。

编辑(为什么没有存储过程/循环): 我不能使用存储过程的原因是数据库不支持它们。我只能添加额外的表/数据。该应用程序是第三方报告工具,我只能控制 SQL 查询。

编辑(我现在的做法): 我目前的方法是在订单表中有一个额外的列来保存计算的日期,然后计划任务/cron 作业每小时对所有订单运行计算。由于几个原因,这不太理想。

【问题讨论】:

    标签: sql date


    【解决方案1】:

    只需在应用程序代码中计算...更简单,您不必在 sql 中编写非常丑陋的查询

    【讨论】:

      【解决方案2】:

      最好的方法是使用日历表。

      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 
          )
      

      希望对你有帮助,

      RB。

      【讨论】:

      • 抱歉直言不讳,但这是我见过的最棘手的查询之一。您将日历与订单表交叉连接,其中日历上的唯一过滤器是两个布尔值,并且您有一个提前期表,其中包含您加入 order.LeadId 的leadId。嗯?
      • 嗨,Darrel - 是的,我刚刚意识到我错过了他提到的产品表。过失!我确实意识到日历交叉连接会使这变得非常慢 - 我现在正在考虑如何重写它......
      【解决方案3】:

      你为什么反对使用循环?

      //一些伪代码

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

      这似乎是一个过于简单的函数,无法尝试找到非循环方式。

      【讨论】:

        【解决方案4】:

        嗯.. 一种解决方案可能是存储一个日期表,该表具有基于从年初开始计算的非休息天数的偏移量。让我们说一月。 2 是休息日。 08 年 1 月 1 日的偏移量为 1(如果您想从 0 开始,则为 0)。 08 年 1 月 3 日的偏移量为 2,因为计数跳过了 08 年 1 月 2 日。从那里可以进行简单的计算。获取订单日期的偏移量,加上提前期,然后对计算出的偏移量进行查找以获取结束日期。

        【讨论】:

          【解决方案5】:

          您可以提前生成一个工作日表。

          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
          【解决方案6】:

          这是一种方法 - 使用 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
          

          【讨论】:

            【解决方案7】:

            一种方法(不创建另一个表)是使用一种上限函数:对于每个关闭日期,找出在子查询中相对于订单日期有多少“在日期”出现在它之前。然后取小于提前期的最高数字。使用对应的日期,加上余数。

            此代码可能特定于 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;
            

            【讨论】:

              【解决方案8】:

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

              这将不返回对于将在 offdays 表中的最后一个日期之后完成的订单(订单号 3)的结果,因此您必须注意按时插入 offdays。假设订单确实休息日不开始。

              【讨论】:

                猜你喜欢
                • 2019-09-10
                • 1970-01-01
                • 1970-01-01
                • 2011-03-16
                • 2017-12-14
                • 2015-12-03
                • 2019-07-12
                • 2013-10-11
                • 1970-01-01
                相关资源
                最近更新 更多