質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
SQL Server

SQL Serverはマイクロソフトのリレーショナルデータベース管理システムです。データマイニングや多次元解析など、ビジネスインテリジェンスのための機能が備わっています。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

Q&A

解決済

2回答

2206閲覧

2つのテーブルから時間を足し算、引き算をして項目列ごとの合計がしたいです。

zardpray

総合スコア6

SQL Server

SQL Serverはマイクロソフトのリレーショナルデータベース管理システムです。データマイニングや多次元解析など、ビジネスインテリジェンスのための機能が備わっています。

SQL

SQL(Structured Query Language)は、リレーショナルデータベース管理システム (RDBMS)のデータベース言語です。大きく分けて、データ定義言語(DDL)、データ操作言語(DML)、データ制御言語(DCL)の3つで構成されており、プログラム上でSQL文を生成して、RDBMSに命令を出し、RDBに必要なデータを格納できます。また、格納したデータを引き出すことも可能です。

0グッド

0クリップ

投稿2020/01/24 02:44

前提・実現したいこと

SQL Serverを使用し、2つのテーブルから時間を足し算、引き算をしてSTATUS列ごとの合計がしたいです。

例、
TABLE_A

START_DATEEND_DATESTATUS
2020/01/05 06:002020/01/05 09:00運転
2020/01/05 09:002020/01/05 11:00停止
2020/01/05 11:002020/01/05 14:00運転
2020/01/05 14:002020/01/05 16:00停止
2020/01/05 16:002020/01/05 17:00運転

TABLE_B

START_DATEEND_DATESTATUS
2020/01/05 06:002020/01/05 07:00休憩
2020/01/05 08:002020/01/05 12:00休憩
20/01/05 13:002020/01/05 15:00休憩

イメージ説明
上記から下記のようなレコードを抽出したいです。

STATUSTOTAL_TIME
運転3:00
停止1:00
休憩7:00

イメージ説明
補足情報(FW/ツールのバージョンなど)

SQL Server 2016

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hihijiji

2020/01/24 03:47

START_DATE、END_DATEには時間未満(分、秒など)は無いと思っていいのですか?
hihijiji

2020/01/24 03:52

休憩でも運転でも停止でも無い時間はありますか?
zardpray

2020/01/24 03:53

すみません情報不足でした。 実際は、分、秒まではあります。 よろしくお願いいたします。
zardpray

2020/01/24 04:02

実際は、未接続、異常停止もあります。 よろしくお願いいたします。
guest

回答2

0

ベストアンサー

TABLEAのデータに対して休憩を差し引いたもの(差し引く休憩はサブクエリーで取得)とTableBをunionしたものを集計すれば良さそうです。

start_dateとend_dateを差し替える場合の値は、調整が必要かもしれませんが、こんな感じかと思います。

SQL

1select status 2 , sum(end_date - start_date) total_time 3from ( 4 select case when start_date < B_end_date then B_end_date else start_Date end as start_date 5 , case when end_date > B_start_date then B_start_date else end_Date end as end_date 6 , status 7 from ( 8 select A.* 9 , (select start_date from tableB where start_date<=A.end_date and end_date>=A.start_date) as B_start_date 10 , (select end_date from tableB where start_date<=A.end_date and end_date>=A.start_date) as B_end_date 11 from tableA A 12 ) t1 13 union all 14 select start_date, end_date, status 15 from tableB 16) t2 17group by status

追記

tableBから休憩でない時間帯を行の隙間と行の外側について求めます。
隙間についてはlag()/lead()どちらを利用しても良いですが、サンプルではlag()を使用しました。
外側については日付が取りうる最小と最大の値を用いてunionで生成しています。

このクエリー(gap)とtableAを突合して、休憩以外の開始/終了を調整します。
それとTableBをunionしたものに対して集計します。

SQL

1with 2 tableA as( 3 select * from (values 4 (convert(datetime,'2020/01/05 06:00'),convert(datetime,'2020/01/05 09:00'),'運転') 5 ,(convert(datetime,'2020/01/05 09:00'),convert(datetime,'2020/01/05 11:00'),'停止') 6 ,(convert(datetime,'2020/01/05 11:00'),convert(datetime,'2020/01/05 14:00'),'運転') 7 ,(convert(datetime,'2020/01/05 14:00'),convert(datetime,'2020/01/05 16:00'),'停止') 8 ,(convert(datetime,'2020/01/05 16:00'),convert(datetime,'2020/01/05 17:00'),'運転') 9 ) as w(START_DATE,END_DATE,STATUS) 10 ) 11, tableB as ( 12 select * from (values 13 (convert(datetime,'2020/01/05 06:00'),convert(datetime,'2020/01/05 07:00'),'休憩') 14 ,(convert(datetime,'2020/01/05 08:00'),convert(datetime,'2020/01/05 12:00'),'休憩') 15 ,(convert(datetime,'2020/01/05 13:00'),convert(datetime,'2020/01/05 15:00'),'休憩') 16 ) as w(START_DATE,END_DATE,STATUS) 17 ) 18, gap1 as ( 19 select * 20 , lag(end_date) over(order by start_date) lag_end_date 21 , lead(start_Date) over(order by start_date) as lead_start_date 22 from tableB 23 ) 24, gap as ( 25 select lag_end_date as start_date, start_Date as end_date 26 from gap1 27 where lag_end_date is not null 28 union all 29 select CONVERT(DATETIME, '1900/01/01 00:00:00'), start_Date 30 from gap1 31 where lag_end_date is null --休憩の最小データの外側 32 union all 33 select end_date, CONVERT(DATETIME, '9999/12/31 23:59:59') 34 from gap1 35 where lead_start_date is null --休憩の最大データの外側 36 union all 37 select * 38 from (values 39 (CONVERT(DATETIME, '1900/01/01 00:00:00'), CONVERT(DATETIME, '9999/12/31 23:59:59')) 40 ) as w(start_date, end_date) 41 where not exists(select 1 from gap1) --gap1のデータが無い時 42) 43select status 44 , format((total_minutes / 60) * 100 + total_minutes % 60, '00:00') total_time 45from ( 46 select status 47 , sum(datediff(mi,start_date,end_date)) total_minutes 48 from ( 49 select case when b.start_date > a.start_Date 50 then b.start_date else a.start_date 51 end as start_date 52 , case when b.end_date < a.end_date 53 then b.end_date else a.end_date 54 end as end_date 55 , a.status 56 from tableA a inner join gap b 57 on a.start_date<b.end_date and a.end_date>b.start_date 58 union all 59 select start_date, end_date, status from gap1 60 ) step1 61 group by status 62) step2

データのパターンについて多くは検証していませんので、不備はあるかもしれませんが、そこは良しなにお願いします。
一応質問のパターンについては、求める結果にはなっています。
追記

tebleB が0件の場合の対応

SQL

1with 2 tableA as( 3 select * from (values 4 (convert(datetime,'2020/01/05 06:00'),convert(datetime,'2020/01/05 09:00'),'運転') 5 ,(convert(datetime,'2020/01/05 09:00'),convert(datetime,'2020/01/05 11:00'),'停止') 6 ,(convert(datetime,'2020/01/05 11:00'),convert(datetime,'2020/01/05 14:00'),'運転') 7 ,(convert(datetime,'2020/01/05 14:00'),convert(datetime,'2020/01/05 16:00'),'停止') 8 ,(convert(datetime,'2020/01/05 16:00'),convert(datetime,'2020/01/05 17:00'),'運転') 9 ) as w(START_DATE,END_DATE,STATUS) 10 ) 11, tableB as ( 12 select * from (values 13 (convert(datetime,'2020/01/05 06:00'),convert(datetime,'2020/01/05 07:00'),'休憩') 14 ,(convert(datetime,'2020/01/05 08:00'),convert(datetime,'2020/01/05 12:00'),'休憩') 15 ,(convert(datetime,'2020/01/05 13:00'),convert(datetime,'2020/01/05 15:00'),'休憩') 16 ) as w(START_DATE,END_DATE,STATUS) 17 ) 18, gap1 as ( 19 select * 20 , lag(end_date) over(order by start_date) lag_end_date 21 , lead(start_Date) over(order by start_date) as lead_start_date 22 from tableB 23 ) 24, gap as ( 25 select lag_end_date as start_date, start_Date as end_date 26 from gap1 27 where lag_end_date is not null 28 union all 29 select CONVERT(DATETIME, '1900/01/01 00:00:00'), start_Date 30 from gap1 31 where lag_end_date is null --休憩の最小データの外側 32 union all 33 select end_date, CONVERT(DATETIME, '9999/12/31 23:59:59') 34 from gap1 35 where lead_start_date is null --休憩の最大データの外側 36) 37select status 38 , format((total_minutes / 60) * 100 + total_minutes % 60, '00:00') total_time 39from ( 40 select status 41 , sum(datediff(mi,start_date,end_date)) total_minutes 42 from ( 43 select case when b.start_date > a.start_Date 44 then b.start_date else a.start_date 45 end as start_date 46 , case when b.end_date < a.end_date 47 then b.end_date else a.end_date 48 end as end_date 49 , a.status 50 from tableA a left join gap b 51 on a.start_date<b.end_date and a.end_date>b.start_date 52 union all 53 select start_date, end_date, status from gap1 54 ) step1 55 -- where XXX データの範囲を決定するとしたらここで。 56 group by status 57) step2

投稿2020/01/24 03:22

編集2020/01/28 00:56
sazi

総合スコア25173

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

zardpray

2020/01/24 05:31

ご回答ありがとうございます。 すみません。t1のところを抜き出して動かしてみましたら 「サブクエリは複数の値を返しました。」と表示されます。 TABLE_Aに対しTABLE_Bが複数返すので起きていると思われます。 何か対処法はありませんでしょうか よろしくお願いいたします。
sazi

2020/01/24 08:10 編集

よく考えるとSQLのみでは結構難題ですね。 なにが難題かというと、tableAのデータをTableBの状態によって分割(行を増やす)しないと駄目なところです。 再帰用いてできるかどうかって所ですね。
sazi

2020/01/24 08:33

lagとかlead使えば良さそうですね。 ちょっと考えてみます。
zardpray

2020/01/24 09:34

ありがとうございます。 自分も考えてみます。 よろしくお願いいたします。
sazi

2020/01/25 01:19

そのまま実行できるように、テーブルデータをcteで展開しました。
zardpray

2020/01/27 00:51

回答ありがとうございます。 実際に試してみたら上手く動作いたしました。 私は、最初に休憩時間と重なる時間を出して最後に合計という流れでSQL文を作っていましたが 上手くいかず。 saziさんのlag,lead関数を使って休憩外の時間で計算して驚きました。 ありがとうございました。
zardpray

2020/01/27 10:01

TABLE_Bの休憩データを日付で絞り込み、レコードが0件になった場合に、TABLE_Aレコードが表示されなくなりました。(WITH gap1 ASの所に日付絞り込み条件追加しました。) 何か対処法がありませんでしょうか?
sazi

2020/01/27 23:53 編集

inner join をleft join に変更し、開始日/終了日の調整用のCASEを反転させ、集計時にgap1参照するように変更した版を追記ました。
sazi

2020/01/27 23:54 編集

gapにgap1がデータなしの場合に全期間となるデータをunionで追記するように2番目のsqlを修正。 ※一応最後でtable_bをunionする箇所もgap1に変更 ただ、「TABLE_Bの休憩データを日付で絞り込み」というのは条件とする場所としては不適切ではないでしょうか。 適切なのは集計する階層で、全体に対して行うべき(日付の範囲ならTable_Aも同様の範囲になるべき)かと思います。
zardpray

2020/01/28 00:02

すみません。 tableAも日付で絞り込みます。 全体的に日付で絞り込み休憩がない場合の条件になります。
guest

0

デバッグすらしてませんが、だいたいこんな感じで運転中の休憩時間は出せると思います。

SQL

1SELECT SUM( 2 CASE WHEN a.END_DATE > b.END_DATE THEN b.END_DATE ELSE a.END_DATE END 3 - CASE WHEN a.START_DATE > b.START_DATE THEN a.START_DATE ELSE b.START END)) AS 運転中の休憩 4FROM TABLE_A AS a 5INNER JOIN TABLE_B AS b 6ON a.START_DATE <= b.END_DATE AND a.END_DATE >= b.START_DATE 7WHERE a.STATUS = "運転"

運転時間の合計から引けば休憩じゃない運転時間は出ると思います。
SQL Serverじゃなかったらもうちょっとすっきり書けるのですが…

投稿2020/01/24 04:47

hihijiji

総合スコア4150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hihijiji

2020/01/24 07:52

よく考えたら運転中に複数回休憩がある場合を考慮してませんでした。 一応残しておきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問