Microsoft Access 掲示板

サブクエリ(exists)を使った最大値、最小値の抽出

7 コメント
views
4 フォロー

Mtbl_所属歴というテーブルに、社員No、社員名、開始日、終了日、所属が入っています。
1.開始日の最小値と終了日の最大値以外の所属を抽出し、
2.特定の名称(育児、介護の文字が含まれる)が含まれる場合を削除したいと思っています。

第一段階として、このようなサブクエリを作ったのですが、結果は取れるのですがパフォーマンスが悪すぎてまともに動きません。
SELECT T1.FROM [Mtbl_所属歴] AS T1
WHERE
Exists (select
from [Mtbl_所属歴] AS T2 where T1.社員No = T2.社員No and T1.開始日 > T2.開始日)
and
Exists (select * from [Mtbl_所属歴] AS T2 where T1.社員No = T2.社員No and T1.終了日 < T2.終了日)

当初はこのように書いていたのですが、結果が取れず、上記のように修正したのですが、
この書き方ではどうして取れないのでしょうか、この点も理解できておりません。
SELECT T1.FROM [Mtbl_所属歴] AS T1
WHERE
Exists (select
from [Mtbl_所属歴] AS T2 where T1.社員No = T2.社員No and T1.開始日 > T2.開始日 and T1.終了日 < T2.終了日)

よろしくお願いします。

はづき
作成: 2024/04/03 (水) 15:17:35
通報 ...
1

開始日の最小値と終了日の最大値以外の所属を抽出

[社員No]の値が同一であるグループごとに、ということでしょうか。

2
hatena 2024/04/03 (水) 17:41:11 修正

EXISTSを使った相関サブクエリは、基本的に遅いです。
相関(サブクエリからメインを参照する)を利用しない方法を検討しましょう。

下記でどうでしょう。

SELECT
 T1.社員No, T1.社員名, T1.所属
FROM
 Mtbl_所属歴 T1
 INNER JOIN
 (SELECT
   社員No, Min(開始日) AS Min開始日, Max(終了日) AS Max終了日
  FROM Mtbl_所属歴
  GROUP BY 社員No) Q1
 ON T1.社員No=Q1.社員No
WHERE
 T1.開始日>Min開始日 And T1.終了日<Max終了日;

これでもまだ遅いなら、
社員No、開始日、終了日 にインデックスを設定してください。

3
りんご 2024/04/03 (水) 18:33:37 935bc@0e907

1.開始日の最小値と終了日の最大値以外の所属を抽出し、

意味があるのかしら?例えば、年度毎に保管期間5年間して過ぎたら削除する、みたいな感じじゃダメなの?

4

Accessは( Microsoft謹製のDBMSでは )
相関サブクエリのパフォーマンスがJOINに比べて劣る印象はありますね

しかしながら、Access-SQLにおいて

削除したいと思っています

という場合、
つまり、UPDATE文やDELETE文を発行するケースでは
FROM句やJOIN句に sum, max, min といった集計関数を含めることは出来ません
( 利用できるのは、パフォーマンスが悪いD系の定義域集計関数だけ )

そのため、hatenaさんのアドバイスにあるとおり適切にINDEXを設定した上で
以下のようなSQLを発行するといいでしょう

SELECT * FROM Mtbl_所属歴 x
WHERE EXISTS
(
    SELECT 1 FROM Mtbl_所属歴 y
     WHERE x.社員No = y.社員No
    HAVING x.開始日 > min(y.開始日)
       AND x.終了日 < max(y.終了日)
);

 
結果セットとパフォーマンスが確認できたら
SELECT → DELETE に変更し

2.特定の名称(育児、介護の文字が含まれる)が含まれる

の条件も付与して下さい

なお、上記の相関サブクエリでも

パフォーマンスが悪すぎてまともに動きません

という場合は
hatenaさんの回答>> 2のサブクエリ Q1 の結果セットを
一旦テンポラリテーブルに格納するといいでしょう

5
はづき 2024/04/04 (木) 09:29:13 7475b@267c5

ご回答ありがとうございます。
EXISTSを使った相関サブクエリはJOINに比べて劣るのですね。勉強になります。

履歴データなので基本的には消せないのですが、
ワークテーブルを使って削除するか、もしくはフラグを立てて読み飛ばすかということを
したいなと。いずれにしても更新を想定しています。

となると、インデックスを貼ってEXISTSですか。まずは試してみます。
開始、終了日の条件はHAVINGになるのですね。勉強になりました。

それでも重いようであれば、この方法を採用してみます。
>hatenaさんの回答>> 2のサブクエリ Q1 の結果セットを
>一旦テンポラリテーブルに格納するといいでしょう

SQLをゴリゴリ書スキルがなく、更新不可となってしまうクエリを一時テーブルに書き出して、
Delete、Updateなどを行うとかはよく使っていました。

6

履歴データなので基本的には消せないのですが、
ワークテーブルを使って削除するか、もしくはフラグを立てて読み飛ばすかということを
したいなと。いずれにしても更新を想定しています。

データベースなので履歴データは基本は削除しない運用が望ましいですね。
ワークテーブルを使うのなら、上記の集計クエリを含むクエリを追加クエリにして追加すればすみます。

フラグフィールドを追加してそれを更新するなら、EXISTSサブクエリを使用した更新クエリにすることになりますが、
自分がするなら、前回の質問でも回答しましたが、VBAでレコードセットを回して更新する方法をとります。

Public Sub SetFlag()
    Dim rs As DAO.Recordset
    Dim strSQL As String
    Dim pre社員No
 
    CurrentDb.Execute "UPDATE Mtbl_所属歴 SET flg削除 = False WHERE flg削除 = =True;"
    
    strSQL = "SELECT * FROM Mtbl_所属歴 ORDER BY 社員No, 開始日, 終了日;" 
    Set rs = CurrentDb.OpenRecordset(strSQL, dbOpenDynaset, dbFailOnError) 
    Do Until rs.EOF
        If pre社員No <> rs!社員No Then
            pre社員No = rs!社員No
            rs.Edit
            rs!flg削除 = True
            rs.Update
            rs.MovePrevious
            rs.Edit
            rs!flg削除 = True
            rs.Update
            rs.MoveNext
        ElseIf 特定の名称が含まれる Then
            rs.Edit
            rs!flg削除 = True
            rs.Update
        End If 
        rs.MoveNext
    Loop 
    rs.Close
    Set rs = Nothing
 
    MsgBox "完了"
End Sub

SQLでいくか、VBAでいくかは、得意不得意があると思いますので、好みでいいと思います。

7
はづき 2024/04/04 (木) 20:21:04 7475b@267c5

SQLで大分改善はしました。が、ちょっと待ち時間がストレスに感じるくらいです。

hatenaさまにいただいた、DAOでループ処理する方法でゆきたいと思います。
コードを一見して動きは理解できました。

rs.MovePreviousで1レコード前に戻るというのもできるのですね。

考えつきもしませんでした、勉強になりました。