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

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

新規登録して質問してみよう
ただいま回答率
85.35%
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

SQL

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

Q&A

解決済

2回答

1560閲覧

あるカラムの値を、別のカラムの値によって、結果を表示する欄を変えたい。

Koto_2131

総合スコア4

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

SQL

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

0グッド

0クリップ

投稿2021/09/22 07:22

編集2021/09/22 08:05

MySQL 8.0.25を使用

1つのカラムが、他のカラムによって意味が決定するといったテーブルがあります。
ここでの区分は入退場を表していて、1が入場、2が退場を意味します。
時間のカラムは区分とセットで意味が決定します。

useStationテーブル

ユーザID日付時間区分
A0019/21AAA11:281
A0019/21BBB12:202
A0019/22CCC23:261
A0019/23DDD00:092

これをSQLで、ユーザ毎に以下のようなデータになるよう検索をしています。

SQL

1select distinct 2 userID,date,entrance.station,entrance.time,exit.station,exit.time 3from ( 4 select userID,date 5 from useStation 6 where userID = "A001" 7) as mainKey 8left join ( 9 select userID,date,station,time 10 from useStation 11 where class = '1' 12) as entrance 13on 14 Mainkey.userId = entrance.userId and 15 mainKey.date = entrance.date 16left join ( 17 select userID,date,station,time 18 from useStation 19 where class = '2' 20) as exit 21on 22 mainkey.userId = exit.userId 23 and mainkey.date = exit.date

|ユーザID|日付|入場駅|入場時間|退場駅|退場時間|
|:--:|:--:|:--:|:--:|:--:|
|A001|9/21|AAA|11:28|BBB|12:20|
|A001|9/22|CCC|23:26||
|A001|9/23|||DDD|00:09|

区分が1の時間と、区分が2の時間をLEFTJOINで結合しています。
出来上がった後にふと、このSQLは冗長で、CASEやらIFやらを使えば結合を使わなくてもできるのではないかと思いました。
しかし、どうしてもTHENで結果の欄を制御する方法が分からず質問いたしました。

自分が作成したSQLでも動作自体は問題ないとは思うのですが、
分かる方、もっといい方法を知っている方がいましたら、後学の為にもよろしくお願いいたします。


追記
同じユーザが同じ日に複数回の入退場を行ったときについて、
多い方のデータ分行が増えていく様な想定です。
理想は以下のようになる事でした。
|ユーザID|日付|入場駅|入場時間|退場駅|退場時間|
|:--:|:--:|:--:|:--:|:--:|
|A001|9/21|AAA|11:28|BBB|12:20|
|A001|9/21|BBB|23:26||
|A001|9/22|AAA|08:53|AAA|00:18|
|A001|9/22|||BBB|09:45|

が、自分の実装では値を表示する時のロジックで処理する前提として、
以下のようになっています。

|ユーザID|日付|入場駅|入場時間|退場駅|退場時間|
|:--:|:--:|:--:|:--:|:--:|
|A001|9/21|AAA|11:28|BBB|12:20|
|A001|9/21|BBB|23:26|BBB|12:20|
|A001|9/22|AAA|08:53|AAA|00:18|
|A001|9/22|AAA|08:53|BBB|09:45|

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

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

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

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

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

takanaweb5

2021/09/22 07:46

同じ人が同じ日に複数回入退場することは考慮しなくてよろしいのでしょうか?
sazi

2021/09/22 07:49 編集

同じ日に入退場を繰り返した時はどのように表現するのですか? また、その場合入退場のペアは時間の前後で判断しないと駄目だと思えます。
sazi

2021/09/22 08:45 編集

> 自分の実装では値を表示する時のロジックで処理する前提として、以下のようになっています。 入退の時間の大小で判断しているとしたら、同じ日に複数の入退場がある場合の判断が付かないのでは?
guest

回答2

0

ベストアンサー

MySQL 8.0.25 なら window関数が使えます。

window関数は比較的新しい構文なので、SQLに詳しい人でも食わず嫌いで、使いこなす人が少ないように思います。
ただし、window関数を使えば従来の方法では、自己結合などを駆使して可読性もパフォーマンスもあまり良くないSQLを簡潔にかつ、高パフォーマンスで記述することが可能です。
ぜひ、この機会にwindow関数を勉強されることをおすすめします。

それではwindows関数を使ったSQLを紹介します。

前提として、退場記録については、必ず対になる入場記録があることととします。
ただし、入場記録があっても、退場記録がないケースはあることとします。

sql

1-- スキーマ定義 2create table tbl(uid varchar(10),d date,station varchar(20),t time,status tinyint); 3insert into tbl values 4('A001','2021-09-21','AAA','11:28',1), 5('A001','2021-09-21','BBB','12:20',2), 6('A001','2021-09-21','BBB','13:00',1), -- 同一日に2回目の入場 7('A001','2021-09-21','AAA','13:30',2), 8('A001','2021-09-22','CCC','23:26',1), 9('A001','2021-09-23','DDD','00:09',2), -- 入場から日をまたいで退場 10('A001','2021-09-23','DDD','01:00',1), -- 退場記録がない 11-- ↓別のユーザ 12('A002','2021-09-21','AAA','01:00',1), 13('A002','2021-09-21','CCC','01:30',2);

sql

1select 2 uid 3, status 4, d 5, t 6, station 7-- window関数のlead()関数で、uidごとに時系列でソートした、次のレコードの値を取得します 8, lead(status) over(partition by uid order by d,t) as lead_status 9, lead(d) over(partition by uid order by d,t) as lead_d 10, lead(t) over(partition by uid order by d,t) as lead_t 11, lead(station) over(partition by uid order by d,t) as lead_station 12from tbl

結果

uidstatusdtstationlead_statuslead_dlead_tlead_station
A00112021/09/2111:28:00AAA22021/09/2112:20:00BBB
A00122021/09/2112:20:00BBB12021/09/2113:00:00BBB
A00112021/09/2113:00:00BBB22021/09/2113:30:00AAA
A00122021/09/2113:30:00AAA12021/09/2223:26:00CCC
A00112021/09/2223:26:00CCC22021/09/2300:09:00DDD
A00122021/09/2300:09:00DDD12021/09/2301:00:00DDD
A00112021/09/2301:00:00DDDNULLNULLNULLNULL
A00212021/09/2101:00:00AAA22021/09/2101:30:00CCC
A00222021/09/2101:30:00CCCNULLNULLNULLNULL

上記の結果から、必要な値だけを表示するようにします

sql

1with sub as ( 2select 3 uid 4, status 5, d 6, t 7, station 8, lead(status) over(partition by uid order by d,t) as lead_status 9, lead(d) over(partition by uid order by d,t) as lead_d 10, lead(t) over(partition by uid order by d,t) as lead_t 11, lead(station) over(partition by uid order by d,t) as lead_station 12from tbl 13) 14 15select 16 uid 17, d as 入場日 18, t as 入場時間 19, station as 入場駅 20, case when lead_status = 2 then lead_d end as 出場日 21, case when lead_status = 2 then lead_t end as 出場時間 22, case when lead_status = 2 then lead_station end as 出場駅 23from sub 24where status = 1;

結果

uid入場日入場時間入場駅出場日出場時間出場駅
A0012021/09/2111:28:00AAA2021/09/2112:20:00BBB
A0012021/09/2113:00:00BBB2021/09/2113:30:00AAA
A0012021/09/2223:26:00CCC2021/09/2300:09:00DDD
A0012021/09/2301:00:00DDDNULLNULLNULL
A0022021/09/2101:00:00AAA2021/09/2101:30:00CCC

なお以下のように、window部分を共通化して記述することも可能です。

sql

1with sub as ( 2select 3 uid 4, status 5, d 6, t 7, station 8, lead(status) over w as lead_status 9, lead(d) over w as lead_d 10, lead(t) over w as lead_t 11, lead(station) over w as lead_station 12from tbl 13window w as (partition by uid order by d,t) 14) 15 16select 17 uid 18, d as 入場日 19, t as 入場時間 20, station as 入場駅 21, case when lead_status = 2 then lead_d end as 出場日 22, case when lead_status = 2 then lead_t end as 出場時間 23, case when lead_status = 2 then lead_station end as 出場駅 24from sub 25where status = 1;

投稿2021/09/22 13:39

編集2021/09/30 14:18
takanaweb5

総合スコア359

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

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

takanaweb5

2021/09/22 21:53

日をまたいで入退場したケースは質問文では、日を分けて2レコードに編集していますが、この回答では、1レコードにまとめて出力しています。 本来は、1レコードにまとめたかったのではと勝手に想像してそうしましたが、どちらがお望みなのでしょうか?
Koto_2131

2021/09/29 08:48

ありがとうございます! 自分の勉強不足故、Window関数なるものを知りませんでした。 今急いで詰め込んだ感想なのですが、サブクエリを使わなくてもSUMの結果をカラムに使えるみたいな感じなのでしょうか。これを使いこなせられたら1つ見える世界が変わりそうな気がします。Windows関数達はこれからしっかりお勉強していきたいと思います。 日をまたぐ入退場については、自分の技術的な理由で1日1レコードにしていましたが、可能ならペアになる入退場は1レコードにまとめる事が望ましかったです。
takanaweb5

2021/09/29 09:11

>サブクエリを使わなくてもSUMの結果をカラムに使えるみたいな感じなのでしょうか。これを使いこなせられたら1つ見える世界が変わりそうな気がします。 私の経験からは応用次第で、自己テーブルを使用する相関サブクエリや自己結合で実現しているSQLはほとんど何でも代替出来てしまいます。 >日をまたぐ入退場については、自分の技術的な理由で1日1レコードにしていましたが、可能ならペアになる入退場は1レコードにまとめる事が望ましかったです。 想像通りで良かったです。
guest

0

SQL

1create table tbl(uid varchar(10),d date,station varchar(20),t time,status tinyint); 2insert into tbl values 3('A001','2021-09-21','AAA','11:28',1), 4('A001','2021-09-21','BBB','12:20',2), 5('A001','2021-09-22','CCC','23:26',1), 6('A001','2021-09-23','DDD','00:09',2); 7 8select uid 9,d 10,max(case status when 1 then station end) stat_1 11,max(case status when 1 then t end) time_1 12,max(case status when 2 then station end) stat_2 13,max(case status when 2 then t end) time_2 14from tbl 15group by uid,d;

投稿2021/09/22 07:35

yambejp

総合スコア116724

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問