Microsoft Access 掲示板

キーブレイク処理(クエリにて)

23 コメント
views
4 フォロー

基幹人事システムから発令に基づくアクション一覧を取得し、
組織の在籍期間を算定しようと思っています。

キーブレイク処理ができなくてクエリでなんとかできないかとここまでやりましたが、
最大、最小で抽出をしているため、出戻りの組織(人事)があると崩れてしまうことが判明しました。
⇒赤色でハイライトした箇所が正しく拾えず、黄色の箇所が結果として合っている
⇒出戻りの、更に、出戻りもある可能性もあります

お知恵を拝借できますでしょうか。

アクションテーブルに対し1~4のクエリを作成(4を最終形)
1.氏名、組織、開始日、終了日でグループ化
2.氏名、組織、開始日の最小値でグループ化
3.氏名、組織、開始日の最大値でグループ化
4.クエリ1の氏名、開始日とクエリ2の氏名と開始日の最小値
  クエリ1の氏名、終了日とクエリ3の氏名と開始日の最大値

アクションテーブル

アクション 発令日   開始日       終了日     組織名

採用      20140401   20140401  20140525    人事
出向開始   20140526   20140526    20140531    人事
職種変更   20140601   20140601    20150331    人事
出向終了   20151101   20151101    20170930    経営企画
職種変更   20171001   20171001    20210331    法務
         20200801   20171001    20210331    法務
出向開始   20210401   20210401    20220331    人事
職種変更   20220401   20220401    20230331    総務
出向先変更 20230401   20230401  99991231    総務

クエリ4

氏名     開始日     終了日       組織名

青山 太郎 20140401              人事
青山 太郎                     人事
青山 太郎            20151031    人事
青山 太郎 20151101 20170930    経営企画
青山 太郎 20171001 20210331    法務
青山 太郎  20210401 20220331    人事
青山 太郎 20220401              総務
青山 太郎       99991231     総務

はづき
作成: 2024/03/27 (水) 10:13:59
最終更新: 2024/03/27 (水) 10:23:39
通報 ...
1
りんご 2024/03/27 (水) 10:57:55 935bc@0e907

基幹人事システムならば、社員所属テーブルがあるはずですが?何故、クエリで捻り出さないといけないの?

2
はづき 2024/03/27 (水) 11:19:09 7475b@267c5

普通そう思いますよね。
基幹システムが全く機能していなくて、所属歴が取れないので苦肉の策です。

3
hiroton 2024/03/27 (水) 11:45:10 8c6e2@f966d

日付が連続するとしているならば、各組織での開始日を、不要なレコードを削除することによって求めることができます。各レコードに於いて、「『開始日』の前日が終了日であるレコード」の組織が同じ組織であれば、そのレコードは連続した組織のデータであり、開始日として不要なデータとなります

そのようなデータであるかどうかを求める場合

DCount("組織","T_アクション","組織='" & [組織] & "' AND 終了日='" & Format(DateValue(Format([開始日],"@@@@\/@@\/@@"))-1,"yyyymmdd") & "'")

このような計算をすると、各組織の開始日に当たるレコードだけ「0」の結果が得られます

※質問中のデータの場合、6行目(アクションが空白のレコード)も抽出されます

「開始日だけ」のデータが出来上がれば、「次のレコードの開始日の前日」が「終了日」です

4

職種変更   20171001   20171001    20210331    法務
         20200801   20171001    20210331    法務

上記の部分は、下記のものの転記間違いでしょうか。

職種変更   20171001   20171001    20200731    法務
         20200801   20200801    20210331    法務

あと、
開始日      終了日       組織名
20140401  20140525    人事
20140526    20140531    人事

上記のように、終了日と次のレコードの開始日が20140525→20140526 というように連続していたら、一つの在籍期間として扱うということでしょうか。

クエリの最終出力フォーマットは、下記のような感じにですか。

氏名     開始日     終了日       組織名
青山 太郎 20140401  20151031    人事
青山 太郎 20151101 20170930    経営企画
青山 太郎 20171001 20210331    法務
青山 太郎  20210401 20220331    人事
青山 太郎 20220401  99991231     総務

それとも質問のように、空欄も必要なのですか。

5
はづき 2024/03/27 (水) 12:39:14 7475b@267c5

hiroton さま
ありがとうございます。
なるほどです。こんな考え方があるのですね。

1点だけ不備がありまして、赤のところが0,1逆になりました。

氏名       開始日      終了日     組織名 判定フラグ
青山 太郎 20140401 20140525 人事 0
青山 太郎 20140526 20140531 人事 1
青山 太郎 20140601 20150331 人事 1
青山 太郎 20150401 20170930 経営企画 0
青山 太郎 20171001 20210331 法務 0
青山 太郎 20210401 20220331 総務 0
青山 太郎 20220401 20230331 総務 0
青山 太郎 20230401 99991231 総務 1

7
hiroton 2024/03/27 (水) 13:01:16 8c6e2@f966d >> 5

想定しているデータは>> 3の通りですが、補足しているようにデータによっては他の条件を検討する必要がでます

また、終了日を含めて一つの結果にするにはそれなりのクエリ(計算)が必要になります
質問中には「総務」のレコードは2件しかありませんので、途中結果であれ「総務」が3レコードになるのはどこかに間違いがあるのでしょう
hirotonの提案は1発で開始日、終了日両方合わせたような結果が出るようなものではないので、元データと、どのようなクエリを組んだのか確認してみてください

8
はづき 2024/03/27 (水) 13:42:53 7475b@267c5 >> 7

氏名       開始日      終了日     組織名 判定フラグ
青山 太郎 20140401 20140525 人事 0
青山 太郎 20140526 20140531 人事 1
青山 太郎 20140601 20150331 人事 1
青山 太郎 20150401 20170930 経営企画 0
青山 太郎 20171001 20210331 法務 0
青山 太郎 20210401 20220331 人事 0
青山 太郎 20220401 20230331 総務 0
青山 太郎 20230401 99991231 総務 1

失礼いたしました、転記ミスです。
他の条件も検討する必要があるとのことですね。

6
はづき 2024/03/27 (水) 12:43:28 7475b@267c5

hatenaさま
ありがとうございます。

こちらで正しいです。アクションが空白のデータもありますし、
アクション違いで、複数の同一組織、氏名のレコードは存在します。
職種変更   20171001   20171001    20210331    法務
         20200801   20171001    20210331    法務

おっしゃる通り、こちらが正解です。
氏名     開始日     終了日       組織名
青山 太郎 20140401  20151031    人事
青山 太郎 20151101 20170930    経営企画
青山 太郎 20171001 20210331    法務
青山 太郎  20210401 20220331    人事
青山 太郎 20220401  99991231     総務

9

組織の在籍期間を算定しようと思っています。
⇒赤色でハイライトした箇所が正しく拾えず、黄色の箇所が結果として合っている
⇒出戻りの、更に、出戻りもある可能性もあります

方法は色々とありますけど、SQLのサンプルを載せておきます

■アクションテーブル

氏名開始日終了日組織名
青山 太郎2014/04/012014/05/25人事
青山 太郎2014/05/262014/05/31人事
青山 太郎2014/06/012015/03/31人事
青山 太郎2015/04/012017/09/30経営企画
青山 太郎2017/10/012018/03/31法務
青山 太郎2018/04/012019/12/31法務
青山 太郎2020/01/012021/03/31法務
青山 太郎2021/04/012022/03/31人事
青山 太郎2022/04/012099/12/31総務
山田 花子2022/01/012022/03/31営業
山田 花子2023/04/012023/08/31経営企画
山田 花子2023/09/012023/12/31営業
山田 花子2024/01/012024/03/31営業
山田 花子2024/04/019999/12/31総務

 
■SQL

SELECT 氏名
     , 組織名
     , 開始日
     , Min( q.終了日 ) As 終了日
FROM
(
    SELECT x.氏名
         , x.組織名
         , x.開始日
         , y.終了日
    FROM アクションテーブル x
    INNER JOIN ( アクションテーブル y
                 INNER JOIN アクションテーブル z
                         ON y.氏名 = z.氏名 and y.組織名 = z.組織名
    )
    ON x.氏名 = y.氏名 AND
       x.組織名 = y.組織名
    WHERE x.終了日 <= y.終了日
    GROUP BY x.氏名
           , x.組織名
           , x.開始日
           , y.終了日
    HAVING Sum(
               IIf( 
                       ( z.開始日 <  x.開始日
                            AND 
                         x.開始日 <= DateAdd( 'd', 1, z.終了日 )
                       )
                   OR
                       ( y.終了日 <  z.終了日
                           AND 
                         z.開始日 <= DateAdd( 'd', 1, y.終了日 )
                       )
                 , 1
                 , 0
               )
           ) = 0
) q
GROUP BY 氏名
       , 組織名
       , 開始日
ORDER BY 1, 3 ;

 
■結果

氏名組織名開始日終了日
青山 太郎人事2014/04/012015/03/31
青山 太郎経営企画2015/04/012017/09/30
青山 太郎法務2017/10/012021/03/31
青山 太郎人事2021/04/012022/03/31
青山 太郎総務2022/04/012099/12/31
山田 花子営業2022/01/012022/03/31
山田 花子経営企画2023/04/012023/08/31
山田 花子営業2023/09/012024/03/31
山田 花子総務2024/04/019999/12/31
10
はづき 2024/03/27 (水) 16:46:18 7475b@267c5

mayuさま

ありがとうございます。
これすごいですね。

SQLでここまできるとは。
結果セットも3万レコードくらいできるのですが、パフォーマンスも高いです。

11
hatena 2024/03/27 (水) 18:07:33 修正

SQLもいいのですが複雑になるので、私がするなら、メンテナンス性とパフォーマンスを考慮して、
テーブルに在籍期間判定用のフィールドを追加して、
VBA(DAO)でそこに在籍期間ごとの連番を入力していくようにするかな。

Public Sub SetRenban()
    Dim rs As DAO.Recordset
    Dim c As Long
    Dim pre氏名, pre組織名
    Dim strSQL As String
 
    strSQL = "SELECT 在籍期間No, 氏名, 開始日, 終了日, 組織名 FROM アクションテーブル " & _
             "ORDER BY 氏名, 開始日, 終了日;"
 
    Set rs = CurrentDb.OpenRecordset(strSQL, dbOpenDynaset, dbFailOnError)
 
    Do Until rs.EOF
        If pre氏名 = rs!氏名 Then
           If pre組織名 <> rs!組織名 Then
                c = c + 1
                pre組織名 = rs!組織名
           End If
        Else
            c = 1
            pre氏名 = rs!氏名
            pre組織名 = rs!組織名
        End If
 
        rs.Edit
        rs(0) = c
        rs.Update
        rs.MoveNext
    Loop
 
    rs.Close
    Set rs = Nothing
 
    MsgBox "完了"
End Sub

上記の実行結果

ID氏名アクション発令日開始日終了日組織名在籍期間No
1青山 太郎採用201404012014040120140525人事1
2青山 太郎出向開始201405262014052620140531人事1
3青山 太郎職種変更201406012014060120150331人事1
4青山 太郎出向終了201511012015110120170930経営企画2
5青山 太郎職種変更201710012017100120210331法務3
6青山 太郎202008012017100120210331法務3
7青山 太郎出向開始202104012021040120220331人事4
8青山 太郎職種変更202204012022040120230331総務5
9青山 太郎出向先変更202304012023040199991231総務5
10山田 花子採用201404012022010120220331営業1
11山田 花子出向開始201405262023040120230831経営企画2
12山田 花子職種変更202309012023090120231231営業3
13山田 花子出向終了202401012024010120240331営業3
14山田 花子職種変更202404012024040199991231総務4

SQL

SELECT t.氏名, t.組織名, Min(t.開始日) AS 開始日, Max(t.終了日) AS 終了日
FROM アクションテーブル As t
GROUP BY t.氏名, t.組織名, t.在籍期間No
ORDER BY t.氏名, t.在籍期間No;

SQL出力結果

氏名組織名開始日の最小終了日の最大
山田 花子営業2022010120220331
山田 花子経営企画2023040120230831
山田 花子営業2023090120240331
山田 花子総務2024040199991231
青山 太郎人事2014040120150331
青山 太郎経営企画2015110120170930
青山 太郎法務2017100120210331
青山 太郎人事2021040120220331
青山 太郎総務2022040199991231

終了日と次のレコードの開始日の連続性は考慮していないので、もし、連続しない(例えば休職とか)場合があるなら、
VBAにその条件判断を追加する必要はあります。

12
はづき 2024/03/28 (木) 07:18:36 7475b@267c5

hatenaさま

ありがとうございます。
私もレコードセットで回してキーブレイク処理で、
判定フラグを立てる方法を試行錯誤していたのですが、
どうしてもできずにいたところでした。

まずは、じっくり内容確認させていただきます。

13
はづき 2024/03/29 (金) 10:58:36 7475b@267c5

ロジックおよび動作確認できました。
当初はDAOによるキーブレイク処理を試みましたが、
最大、最小の日付を取ろうとして、何度も書いてもダメでした。

hatenaさまのロジックは、2段階になっていて、
なるほどと思いました。

どうもありがとうございました。

14
はづき 2024/06/19 (水) 14:45:08 07832@267c5

こちらのスレッドでアドレスいただいた者です。

ベースになるデータの仕様が少し変わりまして、開始日・終了日を持たず、
発令日(=開始日)のみしか持たないデータソースを使うこととなりました。

SetRenban関数にて在籍期間Noをふり、開始日および終了日はSQLで在籍期間NoのMin、Max
にて計算するというものを実装しています。

今回、ご相談させていただきたいのは、開始日は在籍期間NoのMinで良いのですが、
終了日が、理論上は[在籍期間No] + 1のMinの前日となると思っていますが、
私ができる初歩的なレベルですと、もうひとつクエリを作って、両者を結合させるとかになります。

このあたりの実装でご相談させてください。

15
はづき 2024/06/19 (水) 14:49:39 07832@267c5 >> 14

[在籍期間No]をひとつずらして結合させるところができずにいます。

18

サンプルを載せておきます

最初に SetSequenceNumber関数 をデータベースにインポートして
利用可能な状態にしておきましょう

グループ毎連番を自動入力する関数
クエリで連番を表示する場合、DCount関数やサブクエリを利用する方法はあちこちで紹介されています。 しかし、この方法は自分より前のレコード件数をカウントするというロジックなのでレコード件数が多くなると幾何級数的に重くなります。また、グループ毎に連番を振るという仕様になると、条件式も複雑になってきます。 クエリは使わずにテーブルに連番フィールドを持たせて、そこに VBA で連番を書きこむようにすると高速...
fc2

 
■DDL

CREATE TABLE アクションテーブル (
      氏名     VARCHAR(50) NOT NULL
    , 発令日   DATETIME    NOT NULL
    , 組織名   VARCHAR(30) NOT NULL
    , GSeq日付 INT
    , GSeq組織 INT
    , CONSTRAINT pk_action PRIMARY KEY ( 氏名, 発令日 )
);

 
■データ例 (連番付与前)

氏名発令日組織名GSeq日付GSeq組織
山田 花子2022/01/01営業
山田 花子2023/04/01経営企画
山田 花子2023/09/01営業
山田 花子2024/01/01営業
山田 花子2024/04/01総務
青山 太郎2014/04/01人事
青山 太郎2014/05/26人事
青山 太郎2014/06/01人事
青山 太郎2015/04/01経営企画
青山 太郎2017/10/01法務
青山 太郎2018/04/01法務
青山 太郎2020/01/01法務
青山 太郎2021/04/01人事
青山 太郎2022/04/01総務

 
■グループ連番の付与

Sub set_number()
    Call SetSequenceNumber("GSeq日付", "アクションテーブル", "氏名", "発令日")
    Call SetSequenceNumber("GSeq組織", "アクションテーブル", "氏名,組織名", "発令日")
End Sub

 
■データ例 (連番付与後)

氏名発令日組織名GSeq日付GSeq組織
山田 花子2022/01/01営業11
山田 花子2023/04/01経営企画21
山田 花子2023/09/01営業32
山田 花子2024/01/01営業43
山田 花子2024/04/01総務51
青山 太郎2014/04/01人事11
青山 太郎2014/05/26人事22
青山 太郎2014/06/01人事33
青山 太郎2015/04/01経営企画41
青山 太郎2017/10/01法務51
青山 太郎2018/04/01法務62
青山 太郎2020/01/01法務73
青山 太郎2021/04/01人事84
青山 太郎2022/04/01総務91
19
mayu 2024/06/19 (水) 19:06:32 修正 fc5d2@6c788 >> 14

(続き)

■DML

SELECT x.氏名
     , x.組織名
     , Min( x.発令日 ) As 発令日
     , Max( Nz( DateAdd( 'd', -1, y.発令日 ), #12/31/2099# ) ) As 失効日
FROM アクションテーブル x
LEFT JOIN  アクションテーブル y
       ON x.氏名 = y.氏名
      AND x.GSeq日付 = y.GSeq日付 - 1
GROUP BY x.氏名
       , x.組織名
       , x.GSeq日付 - x.GSeq組織
ORDER BY x.氏名
       , Min( x.発令日 )
;

 
■結果

氏名組織名発令日失効日
山田 花子営業2022/01/012023/03/31
山田 花子経営企画2023/04/012023/08/31
山田 花子営業2023/09/012024/03/31
山田 花子総務2024/04/012099/12/31
青山 太郎人事2014/04/012015/03/31
青山 太郎経営企画2015/04/012017/09/30
青山 太郎法務2017/10/012021/03/31
青山 太郎人事2021/04/012022/03/31
青山 太郎総務2022/04/012099/12/31
16
りんご 2024/06/19 (水) 15:38:24 07313@9ce75

新しい質問を立てるのはどうでしょう?
主キーと関連するテーブルを過不足なく提示したり、スクショを提示したりするのがおすすめです。

20

今回、ご相談させていただきたいのは、開始日は在籍期間NoのMinで良いのですが、
終了日が、理論上は[在籍期間No] + 1のMinの前日となると思っていますが、
私ができる初歩的なレベルですと、もうひとつクエリを作って、両者を結合させるとかになります。

アクションテーブルに失効日のフィールドを追加しておいて、SetRenban関数のループ内で発行日の前日を前レコードの失効日に代入するという処理を追加すればどうでしょう。

Public Sub SetRenban()
    Dim rs As DAO.Recordset
    Dim c As Long, d As Long
    Dim pre氏名, pre組織名
    Dim strSQL As String
 
    strSQL = "SELECT 在籍期間No, 終了日, 発令日, 氏名, 組織名 FROM アクションテーブル " & _
             "ORDER BY 氏名, 発令日;"
 
    Set rs = CurrentDb.OpenRecordset(strSQL, dbOpenDynaset, dbFailOnError)
 
    Do Until rs.EOF
        If pre氏名 = rs!氏名 Then
            If pre組織名 <> rs!組織名 Then
                c = c + 1
                pre組織名 = rs!組織名
            End If
            d = Format(DateAdd("d", -1, Format(rs!発令日, "0000/00/00")), "yyyymmdd")
            rs.MovePrevious
            rs.Edit
            rs!失効日 = d
            rs.Update
            rs.MoveNext
        Else
            c = 1
            pre氏名 = rs!氏名
            pre組織名 = rs!組織名
        End If
 
        rs.Edit
        rs(0) = c
        rs.Update
        rs.MoveNext
    Loop
 
    rs.Close
    Set rs = Nothing
 
    MsgBox "完了"
End Sub
SELECT t.氏名, t.組織名, Min(t.発令日) AS 開始日, Max(t.終了日) AS 終了日
FROM アクションテーブル As t
GROUP BY t.氏名, t.組織名, t.在籍期間No
ORDER BY t.氏名, t.在籍期間No;
21
はづき 2024/06/24 (月) 12:47:53 07832@267c5

Mayuさま
どうもありがとうございます。
SetSequenceNumber関数、これは凄いものですね!
今後、使えるようにしておきたいです。

私がやったものとは格段に改善しました。

22
はづき 2024/06/24 (月) 12:53:31 07832@267c5

Hatenaさま
ありがとうございます。
>アクションテーブルに失効日のフィールドを追加しておいて、SetRenban関数のループ内で発行日の前日を前レコードの失効>日に代入するという処理を追加すればどうでしょう。

movePreviousとかで一旦戻って、Editすることができるんですね。
正直知りませんでした。今回、こちらのほうほうで完璧にできました。

今回、組織コードというおおくくりの組織の配下に、部門、部署というレベルがありまして、
表示する際は、氏名と開始日をもとに、所属歴を参照しては以下組織を持ってきていたのですが、
同じ在籍期間Noでの期間が長いと組織名称が変わっていたりして、終了時点の組織名称を参照する
方法に苦心していましたが、グループ内採番の日付Max値のデータを保持するクエリを作成して、
これを参照することで、Mayuさまのロジックを使わせていただき、なんとかこちらも対応できました。

どうもありがとうございました。

23
はづき 2024/06/26 (水) 18:57:34 07832@267c5

対象組織の在籍期間Noごとに、役職・職種を持っているのですが、
単純に最新の役職を持つクエリ、最新の職種を持つクエリを作成
(それぞれのクエリで、在籍期間Noでグループ化して、役職・職種を先頭(最後)で抽出)
したものを、こちらのクエリから参照する方法もありますね(全くスマートではないですが)。

SELECT t.氏名, t.組織名, Min(t.発令日) AS 開始日, Max(t.終了日) AS 終了日
FROM アクションテーブル As t
GROUP BY t.氏名, t.組織名, t.在籍期間No
ORDER BY t.氏名, t.在籍期間No;