web-dev-qa-db-ja.com

Oracle SQLで営業日を計算します(関数またはプロシージャなし)

Oracle selectで2つの日付間の営業日を計算しようとしています。私の計算では、特定の日付に対して正しい結果がほとんど得られるようになりました(ExcelのNETWORKDAYSと比較します)が、2日から-2日まで変動することがあります-なぜかわかりません...

ここに私のコードがあります:

SELECT
((to_char(CompleteDate,'J') - to_char(InstallDate,'J'))+1) - (((to_char(CompleteDate,'WW')+ (52 * ((to_char(CompleteDate,'YYYY') - to_char(InstallDate,'YYYY'))))) - to_char(InstallDate,'WW'))*2) as BusinessDays
FROM TABLE

ありがとう!

10
yochim

最終的に解決策:

SELECT OrderNumber, InstallDate, CompleteDate,
  (TRUNC(CompleteDate) - TRUNC(InstallDate) ) +1 - 
  ((((TRUNC(CompleteDate,'D'))-(TRUNC(InstallDate,'D')))/7)*2) -
  (CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='Sun' THEN 1 ELSE 0 END) -
  (CASE WHEN TO_CHAR(CompleteDate,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END) as BusinessDays
FROM Orders
ORDER BY OrderNumber;

すべてのあなたの応答をありがとう!

23
yochim

上記のさまざまなアプローチをすべて考慮し、2つの日付の間の年の各月の稼働日数を提供する簡単なクエリを思い付きました。

WITH test_data AS ( SELECT TO_DATE('01-JAN-14') AS start_date, TO_DATE('31-DEC-14') AS end_date
FROM dual ), all_dates AS (
SELECT td.start_date, td.end_date, td.start_date + LEVEL-1 as week_day FROM test_data td CONNECT BY td.start_date + LEVEL-1 <= td.end_date) SELECT TO_CHAR(week_day, 'MON'), COUNT(*)
FROM all_dates WHERE to_char(week_day, 'dy', 'nls_date_language=AMERICAN') NOT IN ('Sun' , 'sat') GROUP BY TO_CHAR(week_day, 'MON');

必要に応じてクエリを自由に変更してください。

5
OraGeek

これを試して:

with holidays as 
(
select d from (
select minDate + level -1 d
 from (select min(submitDate) minDate, max (completeDate) maxDate
 from t)
 connect by level <= maxDate - mindate + 1) 
 where to_char(d, 'dy', 'nls_date_language=AMERICAN') not in ('Sun' , 'sat')
)
select t.OrderNo, t.submitDate, t.completeDate, count(*) businessDays
from t join holidays h on h.d between t.submitDate and t.completeDate
group by t.OrderNo, t.submitDate, t.completeDate
order by orderno

これはsqlfiddleデモです

1
A.B.Cade

マークされた最終的な解決策が常に正しいとは限りません。 InstallDateが月の1日(土曜日に該当する場合)、CompleteDateが月の16日(日曜日に該当する場合)であるとします

その場合、実際の営業日は10ですが、マークされたクエリ結果は12として答えを提供します。したがって、このタイプのケースも処理する必要があります。

(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='SAT' AND TO_CHAR(CompleteDate,'DY','nls_date_language=english')='Sun' THEN 2 ELSE 0 END

それを処理する行。

SELECT OrderNumber, InstallDate, CompleteDate,
(TRUNC(CompleteDate) - TRUNC(InstallDate) ) +1 - 
((((TRUNC(CompleteDate,'D'))-(TRUNC(InstallDate,'D')))/7)*2) -
(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='Sun' THEN 1 ELSE 0 END) -
(CASE WHEN TO_CHAR(CompleteDate,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END) -
(CASE WHEN TO_CHAR(InstallDate,'DY','nls_date_language=english')='SAT' AND TO_CHAR(CompleteDate,'DY','nls_date_language=english')='Sun' THEN 2 ELSE 0 END)as BusinessDays
FROM Orders
ORDER BY OrderNumber;
1
Hiran

受け入れられた解決策は非常に近いものですが、場合によっては間違っているようです(例:2015年2月1日から2015年2月28日まで、または2015年5月1日から2015年5月31日まで)。ここに洗練されたバージョンがあります...

  end_date-begin_date+1 /* total days */
  - TRUNC(2*(end_date-begin_date+1)/7) /* weekend days in whole weeks */
  - (CASE
      WHEN TO_CHAR(begin_date,'D') = 1 AND REMAINDER(end_date-begin_date+1,7) > 0 THEN 1
      WHEN TO_CHAR(begin_date,'D') = 8 - REMAINDER(end_date-begin_date+1,7) THEN 1
      WHEN TO_CHAR(begin_date,'D') > 8 - REMAINDER(end_date-begin_date+1,7) THEN 2
      ELSE 0
    END) /* weekend days in partial week */
  AS business_days

7の倍数(週全体)を処理する部分が適切です。しかし、部分的な週の部分を考慮する場合、次のマトリックスに従って、曜日のオフセットと部分的な部分の日数の両方に依存します...

   654321
1N 111111
2M 100000
3T 210000
4W 221000
5R 222100
6F 222210
7S 222221
0
deanashuff

このクエリを使用すると、指定した日付からN日間遡ることができます(営業日のみ)

たとえば、2017-05-17から15日前に戻ります。

select date_point, closest_saturday - (15 - offset + floor((15 - offset) / 6) * 2) from(
   select date_point,
          closest_saturday,
          (case
             when weekday_num > 1 then
              weekday_num - 2
             else
              0
           end) offset
    from (
           select  to_date('2017-05-17', 'yyyy-mm-dd') date_point,
                   to_date('2017-05-17', 'yyyy-mm-dd') - to_char(to_date('2017-05-17', 'yyyy-mm-dd'), 'D') closest_saturday,
                   to_char(to_date('2017-05-17', 'yyyy-mm-dd'), 'D') weekday_num
           from dual
          ))

簡単な説明:特定の日付からN日前に戻りたいと仮定します-特定の日付以下の最も近い土曜日を見つけます。 -最も近い土曜日から、病棟(N-オフセット)日に戻ります。 offsetは、最も近い土曜日から指定された日付(指定された日付を除く)までの営業日数です。

*土曜日からM日(営業日のみ)戻るには、この式DateOfMonthOfTheSaturday-[M + Floor(M/6)* 2]を使用します

0
beckham12a18

サンプルを読みやすく、バスのカウントを返すように変更しました。間の日。 「J」-ジュリアン形式が必要な理由がわかりません。必要なのは、開始日/インストール日と終了日/完了日だけです。これを使用して、2つの日付の間の正しい日数を取得します。日付を自分のものに置き換え、必要に応じてNLSを追加します...:

 SELECT Count(*) BusDaysBtwn
  FROM
  (
  SELECT TO_DATE('2013-02-18', 'YYYY-MM-DD') + LEVEL-1 InstallDate  -- MON or any other day 
       , TO_DATE('2013-02-25', 'YYYY-MM-DD') CompleteDate           -- MON or any other day
       , TO_CHAR(TO_DATE('2013-02-18', 'YYYY-MM-DD') + LEVEL-1, 'DY') InstallDay   -- day of week
    FROM dual 
  CONNECT BY LEVEL <= (TO_DATE('2013-02-25', 'YYYY-MM-DD') - TO_DATE('2013-02-18', 'YYYY-MM-DD')) -- end_date - start_date 
   )
   WHERE InstallDay NOT IN ('SAT', 'Sun')
  /

  SQL> 5
0
Art

日曜日と土曜日を削除するには、これを使用できます

_SELECT Base_DateDiff
     - (floor((Base_DateDiff + 0 + Start_WeekDay) / 7))
     - (floor((Base_DateDiff + 1 + Start_WeekDay) / 7))
FROM   (SELECT 1 + TRUNC(InstallDate) - TRUNC(InstallDate, 'IW') Start_WeekDay
             , CompleteDate - InstallDate + 1 Base_DateDiff
        FROM TABLE) a
_

_Base_DateDiff_は、2つの日付間の日数をカウントします
_(floor((Base_DateDiff + 0 + Start_WeekDay) / 7))_は日曜日の数をカウントします
_(floor((Base_DateDiff + 1 + Start_WeekDay) / 7))_は、土曜日の数をカウントします

1 + TRUNC(InstallDate) - TRUNC(InstallDate, 'IW')月曜日の1から日曜日の7まで

0
Serpiton