teratail header banner
teratail header banner
質問するログイン新規登録

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

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

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

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

Q&A

解決済

3回答

513閲覧

特定の期間に人物とクラスが紐づいていないレコードの抽出をしたい

taiyo-2017

総合スコア49

MySQL

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

0グッド

1クリップ

投稿2023/08/30 01:43

編集2023/08/31 02:09

0

1

実現したいこと

  • 2022-04-01~2023-03-31の期間にクラスに属していない人物のレコードを抽出する

前提

学校の生徒、クラスを管理しようとしています。
学年とは別なので、同じクラスに複数回所属することもあります。

userテーブルで人の情報を管理、
classテーブルでクラス自体を管理、
class_userテーブルで人とクラスの紐づけを管理しています。

userテーブル
id, (userテーブルの一意なid)
number, (番号)
name, (氏名)
valid_start, (有効な期間の開始日)
valid_end (有効な期間の終了日)

classテーブル
id, (classテーブルの一意なid)
name, (クラス名)
valid_start, (有効な期間の開始日)
valid_end (有効な期間の終了日)

class_userテーブル
id, (class_userテーブルの一意なid)
user_id, (userテーブルのid)
class_id, (classテーブルのid)
valid_start, (有効な期間の開始日)
valid_end (有効な期間の終了日)

データ

userテーブル

idnumbernamevalid_startvalid_end
10001佐藤 太郎2020-04-019999-12-31
20002伊藤 花子2020-04-019999-12-31
30003高橋 次郎2021-04-019999-12-31
40004田中 幸子2021-04-012022-03-31

classテーブル

idnamevalid_startvalid_end
1手芸2020-04-019999-12-31
2園芸2020-04-019999-12-31
3折紙2022-04-019999-12-31
4編物2020-04-019999-12-31

class_userテーブル

iduser_idclass_idvalid_startvalid_end
1142020-04-019999-12-31
2212020-04-012021-03-31
3312021-04-012021-06-30
4412021-04-012022-03-31
5222021-04-012022-12-31
6222023-04-012023-12-31
7332023-04-019999-12-31

発生している問題・エラーメッセージ

2022-04-01~2023-03-31の期間にクラスに所属していないユーザがいるようでその人物を抽出したいです。
sqlを作成していましたが、sqlの知識が浅いためうまく抽出ができず…
↑のデータで言えば
「2023-01-01~2023-03-31」にクラスに属していない伊藤 花子さんと
「2021-07-07~2023-03-31」にクラスに属していない高橋 次郎さんを抽出対象にしたいです。
アドバイスをよろしくお願いいたします。

作成したsql

sql

1select 2 users.id, 3 users.number, 4 users.name, 5 class.id, 6 class.name 7from 8 users 9 left join 10 class_user 11 on users.id = class_user.user_id 12 left join 13 class 14 on class.id = class_user.class_id 15where 16 class_user.valid_start not between '2022-04-01' and '2023-03-31' 17;

上記のsqlを作成して実行しましたが、
「2020-04-01」からずっと属している佐藤 太郎さん等も抽出対象になってしまい、どうしたらうまく抽出できるのか考えあぐねているところです…
考え方等やsql文の記載についてアドバイスいただけますと幸いです。よろしくお願いいたします。

追記

mysqlのバージョンは5.7.35です。
mysqlのみで完結できたらと思っていましたが、Exce、phpl等を使って抽出できるのであればそのアドバイスもいただけますと幸いです。

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

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

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

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

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

guest

回答3

0

期間内にクラスに所属したことある人のリストを作って not in するのはダメでしょうか?

sql

1SELECT 2 users.id, 3 users.number, 4 users.name, 5 class.id, 6 class.name 7FROM 8 users 9LEFT JOIN 10 class_user ON users.id = class_user.user_id 11LEFT JOIN 12 class ON class.id = class_user.class_id 13WHERE 14 users.id NOT IN ( 15 SELECT DISTINCT user_id 16 FROM class_user 17 WHERE valid_start <= '2023-03-31' AND valid_end >= '2022-04-01' 18 ) 19 OR class_user.user_id IS NULL;

※SQLは動作検証していません
※各レコードvalid_start < valid_end は保証されている前提です

投稿2023/08/31 16:15

hirohiro

総合スコア2068

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

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

taiyo-2017

2023/09/01 01:05 編集

回答ありがとうございます! >※各レコードvalid_start < valid_end は保証されている前提 これは保証されている前提で大丈夫です。 SQLは問題なく動作しました!ですが、クラスに1度も属していないユーザがいる(class_userテーブルにレコードを持たないユーザがいる)ため、そのユーザも対象となってしまっているようでした。 NOT INの使い方が曖昧なので調べてみますが、なんだかもう少しで抽出できそうな気がしてきました! 少し内容を変えて挑戦してみたいと思います。
guest

0

ベストアンサー

  • 2022-04-01~2023-03-31の期間に、1日でも有効だったユーザを対象とする
  • 2022-04-01~2023-03-31の期間に、クラスに所属しなかった日が1日でもあるユーザを抽出したい

実現したいことは上記であっていますか? 無効なクラスに所属したりなど、不適切なデータは存在しないとすると、次のようなSQLで実現できるかと思います。

SQL

1WITH RECURSIVE CAL (dt) AS ( 2 SELECT STR_TO_DATE('2022-04-01', '%Y-%m-%d') 3 UNION ALL 4 SELECT ADDDATE(dt, 1) 5 FROM CAL 6 WHERE dt < '2023-03-31' 7) 8SELECT * 9FROM user U 10WHERE '2022-04-01' <= U.valid_end AND U.valid_start <= '2023-03-31' 11AND ( 12 SELECT COUNT(*) 13 FROM CAL C 14 WHERE EXISTS ( 15 SELECT * 16 FROM class_user CU 17 WHERE CU.user_id = U.id 18 AND CU.valid_start <= C.dt AND C.dt <= CU.valid_end 19 ) 20) < DATEDIFF('2023-03-31','2022-04-01') + 1;

投稿2023/08/30 09:30

neko_the_shadow

総合スコア2395

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

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

taiyo-2017

2023/08/31 01:36 編集

記載ありがとうございます! 実現したい内容は記載の通りで間違いないです。 記載ができていなかったのですがmysqlのバージョンが5.7.35だったためWITH句が使えないようでした。 カレンダーを作成していると認識しましたので、先にコメントいただいたyambejp様のカレンダーを流用(call add_calendar('2022-04-01','2023-03-31')を実行)して検証させていただきました。 実行したところ、レコードが全く存在しないユーザは取得できたのですが所属が歯抜け状態になっているユーザは取得することができませんでした… WITHでカレンダーを作成していないことが原因でしょうか?
neko_the_shadow

2023/08/31 02:59

MySQL5にて検証しました。「calendarテーブルに2022-04-01~2023-03-31の日付のみが投入されている」という前提のもと、以下のSQLを実行したところ、伊藤 花子と高橋 次郎が結果として返されることが確認できました。 SELECT * FROM user U WHERE '2022-04-01' <= U.valid_end AND U.valid_start <= '2023-03-31' AND ( SELECT COUNT(*) FROM calendar C WHERE EXISTS ( SELECT * FROM class_user CU WHERE CU.user_id = U.id AND CU.valid_start <= C.d AND C.d <= CU.valid_end ) ) < DATEDIFF('2023-03-31','2022-04-01') + 1;
taiyo-2017

2023/08/31 05:44 編集

再度コメントありがとうございます! 改めてカレンダーをtruncateしてデータを入れ直して実行するとそれらしいデータを取得できました! ですが、一部思うように取得できていないようです。 ユーザの有効期限が2020-04-01~9999-12-31となっており、 クラスに属している期間が 2021/4/1 2022/3/31 2023/4/1 2023/5/31 2023/6/1 9999/12/31 となっているユーザが取得できませんでした…。 2022-04-01~2023-03-31の1年間ずっとクラスに属していないユーザが取得できていないのかもしれません…。 sqlを修正してみようかと思いましたが、知識不足のため 「AND CU.valid_start <= C.d AND C.d <= CU.valid_end」 「) < DATEDIFF('2023-03-31','2022-04-01') + 1」 の箇所の意味合いがよくわからず…ご教示いただけると幸いです。
neko_the_shadow

2023/08/31 06:50

MySQL5にて検証しました。以下のようなテストデータを投入し、上記SQLを実行したところ、id=5の高村良子のデータは取得できました。何か別の問題があるかもしれません… INSERT INTO user(id,number,name,valid_start,valid_end) VALUES (5,'005','高村 良子','2020-04-01', '9999-12-31'); INSERT INTO class_user(id,user_id,class_id,valid_start,valid_end) VALUES ( 8,5,1, '2021-04-01', '2022-03-31'); INSERT INTO class_user(id,user_id,class_id,valid_start,valid_end) VALUES ( 9,5,1, '2023-04-01', '2023-05-31'); INSERT INTO class_user(id,user_id,class_id,valid_start,valid_end) VALUES (10,5,1, '2023-06-01', '9999-12-31');
taiyo-2017

2023/09/01 05:46

再度回答ありがとうございます! 改めてclass_userのデータを確認したところ、データの誤りがあったようでした。 記載させていただいたユーザに'2022-04-01'~'2023-03-31'の期間のレコードがある状態だったようです。大変失礼いたしました。 件数が多いため全てのデータの確認はできていませんが、いくつか確認ができ、欲しい情報が抽出されていることを確認いたしました。 何度も確認、記載していただきありがとうございました!
guest

0

今回の命題に関係なさそうなクラスは省略

SQL

1create table user( 2uid int primary key, 3name varchar(20), 4valid_start date, 5valid_end date); 6 7insert into user values 8(1,'佐藤 太郎','2020-04-01','9999-12-31'), 9(2,'伊藤 花子','2020-04-01','9999-12-31'), 10(3,'高橋 次郎','2021-04-01','9999-12-31'), 11(4,'田中 幸子','2021-04-01','2022-03-31'); 12 13create table class_user( 14id int primary key, 15uid int, 16cid int, 17valid_start date, 18valid_end date); 19 20insert into class_user values 21(1,1,4,'2020-04-01','9999-12-31'), 22(2,2,1,'2020-04-01','2021-03-31'), 23(3,3,1,'2021-04-01','2021-06-30'), 24(4,4,1,'2021-04-01','2022-03-31'), 25(5,2,2,'2021-04-01','2022-12-31'), 26(6,2,2,'2023-04-01','2023-12-31'), 27(7,3,3,'2023-04-01','9999-12-31');

ここでカレンダーテーブルを作っておきます。
プロシージャを使ってデータ投入しますがカレンダーは余裕を持って日付設定をしておきます

SQL

1create table calendar(d date primary key); 2 3drop procedure if exists add_calendar; 4delimiter // 5create procedure add_calendar(IN from_date date,to_date date) 6begin 7set @d:=from_date; 8set @count:=0; 9while @d<=to_date do 10if @count=0 then 11set @sql:='insert ignore into calendar values'; 12else 13set @sql:=concat(@sql,","); 14end if; 15set @sql:=concat(@sql,"('",@d,"')"); 16set @count:=@count +1; 17if @count>=1000 or @d=to_date then 18set @count:=0; 19prepare stmt from @sql; 20execute stmt; 21end if; 22set @d:=@d + interval 1 day; 23end while; 24end 25// 26delimiter ;

データ投入

SQL

1call add_calendar('2018-01-01','2025-12-31')

検索

SQL

1select distinct t1.uid,max(t1.name) as name from user as t1 2inner join ( 3select * from calendar 4where d between '2020-04-01' and '2023-03-31' 5) as t2 on 1 6left join class_user as t3 7on t1.uid=t3.uid 8and t2.d between t3.valid_start and t3.valid_end 9or not t2.d between t1.valid_start and t1.valid_end 10group by t1.uid ,t2.d 11having count(id)=0

投稿2023/08/30 03:45

編集2023/08/30 05:26
yambejp

総合スコア117871

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

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

yambejp

2023/08/30 05:26 編集

とりあえずアップしましたが、検証が必要
taiyo-2017

2023/08/31 01:36

記載ありがとうございます! プロシージャでカレンダーを作成することが全く頭になかったのでとても勉強になります。 ユーザやクラスの件数が多かったため少し内容を変更して抽出期間を絞って検索してみましたが実行時間が長くタイムアウトしてしまいました… 検索対象を少なくしたいのですが、 カレンダーのselect時の「where d between '2020-04-01' and '2023-03-31'」を「where d between '2022-04-01' and '2022-04-01'」にすることで 『「'2022-04-01'」にクラスに属していない人』を抽出できるのでしょうか? sqlの読み取りが拙いため教えていただけますと幸いです。
yambejp

2023/08/31 01:43

タイムアウトするようじゃ実用的ではないですね・・・失礼しました 考え方を変えて、適用外期間をclass_userテーブルに登録してしまったほうが早いかもしれません ちょっと宿題
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問