• ベストアンサー

Accessでの抽出方法を教えてください。

Access初心者の域を脱しきれない者です。 質問するにも説明が難しく、悩みましたが画像を添付すれば解っていただけると思い質問させていただきます。 サケ科の魚を一匹ずつランダムに比較、同族(属)とした固有名(魚名)が対になったたデータ存在します。この際、比較する/される側のいずれになるかは分からないとする。 このデータから、個々の魚名に対し同族(属)の魚名を抽出した表を作成する方法を教えてください。(画像のデータでは、便宜上、サケ属とイワナ属の一部としています。) 初心者の私にとっては、クエリで処理できればありがたいのですが、VBでないと無理なのでしょうか。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.8

【確認とVBの提示】  以下のVBコードを書いて実行すると添付図のような結果を得ました。このようなVBコードで宜しいのかどうかは判りませんので、コード中で使用している関数の紹介は割愛します。  なお、元テーブルと同じ構造のテーブルを別途用意して、それに結果をINSERTしています。また、テストテーブルの列名は以下のようです。 ・No.--->ID ・個有名--->name ・属名------>family Private Sub コマンド0_Click()   Dim H       As Integer   Dim I       As Integer   Dim N       As Integer   Dim M       As Integer   Dim intTotal   As Integer   Dim strDatas(1)  As String ' DBSelect()の戻り値を格納する配列   Dim strNames()  As String ' 列[name]の値を格納する配列   Dim strFamilies() As String ' 列[family]の値を格納する配列   Dim strSQL    As String ' SQL文をセットする変数      DoCmd.SetWarnings (False)      ' ----------------------------------------   ' name 一覧を取得する   ' ----------------------------------------   strSQL = "SELECT name FROM FamilyList ORDER BY ID"   strDatas(0) = DBSelect(strSQL, ",", ",")   strSQL = "SELECT family FROM FamilyList ORDER BY ID"   strDatas(1) = DBSelect(strSQL, ",", ",")   If Len(strDatas(0) & "") Then     strNames() = Split(PackList(strDatas(0) & "," & strDatas(1)), ",")   Else     strNames() = Split(PackList(strDatas(1)), ",")   End If   ' ----------------------------------------   ' [FamilyList_ALL] をクリアする   ' ----------------------------------------   DoCmd.RunSQL "DELETE FROM FamilyList_ALL"   ' --------------------------------------------------------   ' テーブル[FamilyList_ALL]に固有名と属名を挿入する   ' --------------------------------------------------------   intTotal = UBound(strNames)   For I = 0 To intTotal     '     ' name に対応する主属名と副属名を取得し配列に代入     '     strFamilies() = Split(GetFamilies(strNames(I)), ",")     '     ' テーブル[FamilyList_ALL]の現在の行数を取得     '     N = DBLookup("SELECT COUNT(*) FROM FamilyList_ALL")     '     ' For-Next     '     M = UBound(strFamilies)     For H = 0 To M       '       ' テーブル[FamilyList_ALL]に'固有名'対'属名'関係を登録       '       ' 注意1: <個有名=属名> も発生する!       '       If Len(strFamilies(H) & "") And strNames(I) <> strFamilies(H) Then         N = N + 1         strSQL = "INSERT INTO FamilyList_ALL (ID,name,family)" & _             " VALUES (" & N & ",'" & strNames(I) & "','" & strFamilies(H) & "');"         DoCmd.RunSQL strSQL       End If     Next H   Next I   DoCmd.SetWarnings (True) End Sub

その他の回答 (11)

回答No.12

**********************************  新補足3:≪GetFamilies()≫ ********************************** Public Function GetFamilies(ByVal strStartName As String) As String   Dim ExitNow   As Boolean   Dim N      As Integer   Dim I      As Integer   Dim S      As Integer   Dim T      As Integer   Dim strINList(1) As String   Dim strSQL    As String   Dim strDatas   As String   Dim strFamily  As String   Dim strFamilies As String   Dim strName   As String   Dim strNames   As String      ' ========================   ' 準備処理   ' ========================   strNames = strStartName   strFamilies = strStartName   ' ========================   ' メイン   ' ========================   Do     '     ' ExitNowの初期値を設定する     '     ExitNow = True     ' *********************************************************************     '     ' name に対応する family を検索・取得する     '     ' 【SQL文】NOT IN 句を利用して既知の family の取得を回避する     '     '  SELECT DISTINCT family FROM テーブル名     '    WHERE name='XXXXX'     '     AND family NOT IN('AAA','BBB',CCC')     '     ' *********************************************************************     N = CharCount(strNames, ",")     For I = S To N       strName = CutStr(strNames, ",", I + 1)       strINList(0) = "'" & Replace(strFamilies, ",", "','") & "'"       strSQL = "SELECT DISTINCT family FROM familyList" & _            " WHERE name='" & strName & "'" & _            " AND family NOT IN (" & strINList(0) & ")"       '       ' SQL文を実行して合致する[family]データを取得する       '       strDatas = DBSelect(strSQL, ",", ",")       If Len(strDatas & "") > 0 Then         ExitNow = False         strFamilies = PackList(strFamilies & "," & strDatas)       Else         If strFamilies = strName Then           ExitNow = False         Else           '           ' name に対応する family がヒットしなくても           ' name に対応する name が存在するケースがある           ' その場合には、name を新 family と見做す           '           ' (例) 4  AAAA  BBBB <---- ヒットしない           '     5  BBBB  CCCC <---- 検索済み           '           '       AAAA <---- 新 family と見做す           '           strSQL = "SELECT name FROM familyList WHERE family='" & strName & "'"           strDatas = DBLookup(strSQL) & ""           If Len(strDatas & "") > 0 Then             ExitNow = False             strFamilies = PackList(strFamilies & "," & strDatas)           End If         End If       End If     Next I     '     ' 次回は、今回N番目の次から     '     S = N + 1     ' *********************************************************************     '     ' strFamily に対応する name を検索・取得する     '     ' 【SQL文】NOT IN 句を利用して既知の name の取得を回避する     '     '  SELECT DISTINCT name FROM テーブル名     '    WHERE family='XXXXX'     '     AND name NOT IN('AAA','BBB',CCC')     '     ' *********************************************************************     If Not ExitNow Then       N = CharCount(strFamilies, ",")       For I = T To N         '         ' 属名リストから個別名を抽出する         '         strFamily = CutStr(strFamilies, ",", I + 1)         '         ' SQL文を作成する         '         strINList(1) = "'" & Replace(strNames, ",", "','") & "'"         strSQL = "SELECT DISTINCT name FROM familyList" & _              " WHERE family='" & strFamily & "'" & _              " AND name NOT IN (" & strINList(1) & ")"         '         ' SQL文を実行して合致する[name]データを取得する         '         strDatas = DBSelect(strSQL, ",", ",")         If Len(strDatas & "") > 0 Then           ExitNow = False           '           ' 新しいnameが見つかったらstrNamesとstrFamiliesに付加する           '           strNames = strNames & "," & strDatas           '           ' 新しいnameはstrFamiliesにも付加する           '           strFamilies = PackList(strFamilies & "," & strDatas)         End If       Next I       '       ' 次回は、今回N番目の次から       '       T = N + 1     End If   Loop Until ExitNow   ' ========================   ' 終了処理   ' ========================   GetFamilies = strFamilies End Function 祈、成功!

回答No.11

**********************************  新補足2:≪標準ライブラリの必須関数≫ ********************************** Public Function PackList(ByVal strList As String, _              Optional strDemiliter As String = ",") As String   Dim I     As Integer   Dim N     As Integer   Dim strDatas() As String   Dim strNewList As String      strDatas() = Split(strList, strDemiliter)   N = UBound(strDatas)   strNewList = strDatas(0)   For I = 1 To N     If InStr(1, strNewList, strDatas(I)) = 0 Then       strNewList = strNewList & "," & strDatas(I)     End If   Next I   PackList = strNewList End Function Public Function CutStr(ByVal Text As String, _             ByVal Separator As String, _             ByVal N As Integer) As String   Dim strDatas() As String      strDatas = Split("" & Separator & Text, Separator, , 0)   CutStr = strDatas(N * Abs(N <= UBound(strDatas))) End Function Public Function CharCount(ByVal Text As String, _              ByVal C As String) As Integer   CharCount = Len(Text) - Len(Replace(Text, C, "")) End Function Public Function DBSelect(ByVal strQuerySQL As String, _              Optional colDelimita As String = ";", _              Optional rowDelimita As String = ";") As String On Error GoTo Err_DBSelect   Dim R      As Integer ' 行インデックス   Dim N      As Integer ' 行総数 - 1   Dim rst     As ADODB.Recordset   Dim fld     As ADODB.Field   Dim strList   As String ' 全てのデータを区切子で連結して格納      Set rst = New ADODB.Recordset   With rst     .Open strQuerySQL, _        CurrentProject.Connection, _        adOpenStatic, _        adLockReadOnly     If Not .BOF Then       N = .RecordCount - 1       .MoveFirst       For R = 0 To N         For Each fld In .Fields           With fld             strList = strList & .Value & colDelimita           End With         Next fld         strList = Mid(strList, 1, Len(strList) - 1) & rowDelimita         .MoveNext       Next R     Else       strList = ""     End If   End With Exit_DBSelect: On Error Resume Next   rst.Close   Set rst = Nothing   DBSelect = IIf(Len(strList) > 0, Replace(strList & "[END]", rowDelimita & "[END]", ""), "")   Exit Function Err_DBSelect:   MsgBox "SELECT 文の実行時にエラーが発生しました。(DBSelect)" & Chr(13) & Chr(13) & _       "・Err.Description=" & Err.Description & Chr(13) & _       "・SQL Text=" & strQuerySQL, _       vbExclamation, " 関数エラーメッセージ"   Resume Exit_DBSelect End Function Public Function DBLookup(ByVal strQuerySQL As String, _              Optional ByVal ReturnValue = Null) As Variant On Error GoTo Err_DBLookup   Dim DataValue   Dim rst As ADODB.Recordset   Set rst = New ADODB.Recordset   With rst     .Open strQuerySQL, _        CurrentProject.Connection, _        adOpenStatic, _        adLockReadOnly     If Not .BOF Then       .MoveFirst       DataValue = .Fields(0)     End If   End With Exit_DBLookup: On Error Resume Next   rst.Close   Set rst = Nothing   DBLookup = IIf(Len(DataValue & ""), DataValue, ReturnValue)   Exit Function Err_DBLookup:   MsgBox "SELECT 文の実行時にエラーが発生しました。(DBLookup)" & Chr$(13) & Chr$(13) & _       "・Err.Description=" & Err.Description & Chr$(13) & _       "・SQL Text=" & strQuerySQL, _       vbExclamation, " 関数エラーメッセージ"   Resume Exit_DBLookup End Function ※添付図が落ちていました。

回答No.10

**********************************  新補足1:先のプログラミングやUNIONクエリ版を動かすには? ********************************** 以下の関数を標準ライブラリに登録して下さい。 ≪標準ライブラリの必須関数≫ 【ベーシック関数】 1、PackList(文字列,区切子) ? PackList("シロサケ,ギンザケ,シロサケ") シロサケ,ギンザケ  PackList()は、区切子で区切られた単語列から重複を取り除きます。 2、CutStr(文字列,区切子,切出す位置) ? CutStr("シロサケ,ギンザケ", ",", 1) シロサケ ? CutStr("シロサケ,ギンザケ", ",", 2) ギンザケ  CutStr()は、区切子で区切られた単語列から指定番目を取出します。 3、CharCount(文字列,カウントする文字) ? CharCount("シロサケ,ギンザケ", ",") 1  CharCount()は、文字列中に指定の文字が何個あるかを調べます。 【データベースアクセス関数】 1、DBSelect(SQL文,列区切子,行区切子) ? DBSelect("SELECT * FROM FamilyLIST", ",", chr(13)) 1,シロサケ,ギンザケ 2,ベニザケ,ギンザケ 3,シロサケ,サクラマス 4,アメマス,イワナ 5,イワナ,オショロコマ  DBSelect()は、SQL文の実行結果を指定の列区切子と行区切子とで連結した一つの文字列を返します。 2、DBLookup(SQL文) ? DBLookup("SELECT name FROM FamilyList WHERE ID=1") シロサケ  DBLookup()は、SQL文の実行で最初に見つかったデータを1個だけ返します。 【アプリケーション用関数】 1、GetFamlies(個有名)  添付図のように、個有名に対応する属の全てを戻します。 PS、以上の6つの関数を全て標準ライブラリに登録されて下さい。 登録すべき関数の全容は、新補足2で示します。なお、これらの関数は1985年~1996年に書いたものですので試され済みです。新しく書いた関数は、GetFamlies(個有名)のみです。私は、既に70歳を迎えようかという老人。若い質問者の柔らかい頭が、これらを理解できないことはない筈。頑張ってください。

moco-don
質問者

お礼

f_a_007さん、改めてご回答を振り返りながら勉強させてください。能力不足の小職をここまで導いていただき感謝いたします。

回答No.9

【補足:UNIONクエリを利用すると・・・】 更に、VBコードの記述を簡便化できます。 ≪クエリ1≫ SELECT   name,    replace(GetFamilies(name), name &",","") AS FamiliesList   FROM FamilyList UNION SELECT   family,   replace(GetFamilies(family), family &",","") AS FamiliesList   FROM FamilyList; 用意するのは、このようなUNIONクエリです。同クエリを実行すると添付図のような結果が得られます。ここまでの情報が取得できれば、整形し出力するのは簡単です。実際には、次のようで事足ります。 Private Sub コマンド0_Click()   Dim intTotal  As Integer   Dim H      As Integer   Dim I      As Integer   Dim N      As Integer   Dim M      As Integer   Dim strDatas  As String   Dim strName   As String   Dim strFamily  As String   Dim strFamilies As String   Dim strSQL   As String      DoCmd.SetWarnings (False)      DoCmd.RunSQL "DELETE FROM FamilyList_ALL"      strDatas = DBSelect("クエリ1", ";", Chr(13))   intTotal = CharCount(strDatas, Chr(13))   For I = 0 To intTotal     strName = CutStr(CutStr(strDatas, Chr(13), I + 1), ";", 1)     strFamilies = CutStr(CutStr(strDatas, Chr(13), I + 1), ";", 2)     N = DBLookup("SELECT COUNT(*) FROM FamilyList_ALL")     M = CharCount(strFamilies, ",")     For H = 0 To M       N = N + 1       strFamily = CutStr(strFamilies, ",", H + 1)       strSQL = "INSERT INTO FamilyList_ALL (ID,name,family)" & _            " VALUES (" & N & ",'" & strName & "','" & strFamily & "');"       DoCmd.RunSQL strSQL     Next H   Next I   DoCmd.SetWarnings (True) End Sub ※以上のようなやり方でもよければキー関数である GetFamilies() のコードをご提示します。

moco-don
質問者

お礼

f_a_007さん、度々ご教示頂き有難うございました。前回の回答が私が望んだ姿だったので試してみました。結果はコンパイルエラー(SubまたはFunctionが定義されていません。)でした。『DBSelect』が引っかかっている様でした。こうなると知恵の無い私にとってはどうすることもできず諦めモードです。 幾度ものご回答の応えられず、申しわけありませんでした。

  • m3_maki
  • ベストアンサー率64% (296/460)
回答No.7

私の実力では、クエリでは絶対に不可能とは言い切れませんが VBA ならできます。 ワークテーブルを使いますので複数ユーザー同時作業はできません。 準備。 テーブル、クエリを作成します。 テーブル【分類】 ・項目(テキスト型)主キー ・グループ(長整数型) クエリ【項目一覧】 SELECT [データ1].項目A as 項目 FROM データ1 UNION Select [データ1].項目B as 項目 FROM データ1 クエリ【左更新】 PARAMETERS GNo Long; UPDATE (分類 INNER JOIN データ1 ON 分類.項目 = [データ1].項目A) INNER JOIN 分類 AS 元 ON [データ1].項目B = 元.項目 SET 分類.[グループ] = [GNo] WHERE 分類.[グループ] Is Null AND 元.[グループ]=[GNo] クエリ【右更新】 PARAMETERS GNo Long; UPDATE (分類 INNER JOIN データ1 ON 分類.項目 = [データ1].項目B) INNER JOIN 分類 AS 元 ON [データ1].項目A = 元.項目 SET 分類.[グループ] = [GNo] WHERE 分類.[グループ] Is Null AND 元.[グループ]=[GNo] クエリ【抽出データ】 SELECT 分類.[グループ], 分類.項目 AS 項目C, 分類_1.項目 AS 同族 FROM 分類 INNER JOIN 分類 AS 分類_1 ON 分類.[グループ] = 分類_1.[グループ] WHERE 分類_1.項目<>[分類]![項目] ORDER BY 分類.[グループ], 分類.項目, 分類_1.項目 モジュール Sub Sample()   Dim dbs As DAO.Database   Dim qdf1 As DAO.QueryDef   Dim qdf2 As DAO.QueryDef   Dim GNo As Long   Dim cnt1 As Long   Dim cnt2 As Long   Dim strSQL As String   Dim varName As Variant   Set dbs = CurrentDb   strSQL = "DELETE FROM 分類"   dbs.Execute strSQL   strSQL = "INSERT INTO 分類 ( 項目 )" & _         "SELECT 項目 FROM 項目一覧"   dbs.Execute strSQL   Set qdf1 = dbs.QueryDefs("左更新")   Set qdf2 = dbs.QueryDefs("右更新")   Do While True     varName = DLookup("項目", "分類", "グループ Is Null")     If IsNull(varName) Then       Exit Do     End If     If cnt1 + cnt2 = 0 Then       GNo = GNo + 1       strSQL = "UPDATE 分類 SET グループ = " & GNo & _             " WHERE 項目='" & varName & "'"       dbs.Execute strSQL     End If     qdf1.Parameters("GNo") = GNo     qdf1.Execute     cnt1 = qdf1.RecordsAffected     qdf2.Parameters("Gno") = GNo     qdf2.Execute     cnt2 = qdf2.RecordsAffected   Loop   Set qdf1 = Nothing   Set qdf2 = Nothing   dbs.Close   Set dbs = Nothing End Sub テーブル、クエリを準備し、Sample() を実行します。 選択クエリ【抽出データ】に結果が得られます。 結果をテーブルにするなり、Excel に出力するなり、ご自由にどうぞ。

moco-don
質問者

お礼

ご指南、有難うございました。見よう見真似で実行したところ、アウトプットは出ました。但し、望んだものとは少しばかり違ったみたいです。お手を 煩わせることになり申しわけございませんでした。

回答No.6

もしかして・・・PartII 仮に最後の添付図が目的のそれに近いとして、それでもなお幾つかの疑問が残ります。それは、[データ1]の固有名と同族名とを入れ替えた探索の必要性の有無。もし、その必要性があれば、最後の添付図を作成する処理を左右入れ替えて再度実行するのが一番簡単です。その場合、幾つかの重複する組合せが発生するかも知れませんが、それは重複を除く最終処理を行えば解決します。 以上、念のために・・・・。

moco-don
質問者

お礼

f_a_007さん、度重なるご回答有難うございました。VBで処理しなさいと理解しました。高いハードルが目の前にちらついてきました。

回答No.5

もしかして・・・ [データ1]から作成する表は、新添付図のようなのかな?【お詫び】の添付図は、関連を示しただけ。今回は、 >個々の魚名に対し同族(属)の魚名を抽出 に着目したところが違います。いずれも、考え方と処理とは99%一緒ですが・・・。

回答No.4

【お詫び】 先の考えでデータを抽出すると添付図のようになった。つまり、私は、質問内容を理解できていないようだ。よって、先の回答はキャンセルする。

回答No.3

Q、Accessでの抽出方法を教えてください。 A、一番簡単なのは? 01:”シロサケ”の同族名を検索する。 02:”1,シロサケ,ギンザケ”を目的のテーブルへINSERTする。 03:同族控え文字列”シロサケ,ギンザケ”を生成する。  ↓ 04:”シロサケ”の同族名を検索済み以外を検索する。 05:”2,シロサケ,サクラマス”を目的のテーブルへINSERTする。 06:同族控え文字列”シロサケ,ギンザケ,サクラマス”に更新する。  ↓ (04~06を繰り返す)  ↓ 07:同族控え文字列”ギンザケ”を持つ”シロサケ”以外の固有名を検索する。 08:”3,ギンザケ,ベニザケ”を目的のテーブルへINSERTする。 09:同族控え文字列”ギンザケ,サクラマス|01|ベニザケ”に更新する。  ↓ 10:同族控え文字列”サクラマス”を持つ”シロサケ”以外の固有名を検索する。 11:ヒットしないのでINSERTなし。  ↓ 12:新しい同族控え文字列”ベニザケ”を持つ”ギンザケ”以外の固有名を検索する。 13:ヒットしないのでINSERTなし。 以上の手続きを全ての固有名で行えば目的は達成されると思う。 【ポイント】 同族控え文字列の生成   ”XXXXX,XXXXX|01|XXXXX,XXXXX|02|XXXXX・・・・・ 既検索リストの生成      "シロサケ,ギンザケ,サクラマス|01|ベニザケ・・・・・ 無限ループを回避する工夫が必要。 と、まずは、アルゴリズムをWORDかなんかでまとめるのが先決。

noname#231195
noname#231195
回答No.2

クエリでできると思いますが、問題は連続的な(連鎖していく)データがあることです。 これ連鎖していくと、もとの所に帰ってきて無限連鎖になるものが出てきますよ。 それに下に紹介するやり方では、無限連鎖を扱えません。 いずれにしろ連鎖の数を制限する必要があります。 連鎖の数を2つに限定した場合を例にとります。 まずクエリビルダに同じテーブルを2つ並べます。説明の都合上左右に並べるということにしましょう。 そして左の項目Bと右の項目Aをつなぎます(矢印なしの線でつなぎます)。 表示する項目は左の項目Aと右の項目Bです。 このクエリを実行すると「アメマス・オショロコマ」のレコードが表示されるはずです。 これは手元で確かめてみました。 同じようにしてテーブルを3つ並べて左のB-中のA、中のB-右のAをつないだクエリを作って、左のAと右のBだけを表示させます。 これは連鎖の数が2の場合の表です。 オリジナルの表も目的の表の一部になりますから、テーブル一つだけでAとBを表示させるクエリも作ります。 この3つのクエリを合体させれば、目的の表になります。 合体させるにはユニオンクエリを使います。 これはクエリビルダでは作ることができません。SQLビューで作ります。ここら辺を参考にしてください。 http://www.sk-access.com/syo_query/sqa013_union.html https://support.office.com/ja-jp/article/%E3%83%A6%E3%83%8B%E3%82%AA%E3%83%B3-%E3%82%AF%E3%82%A8%E3%83%AA%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E8%A4%87%E6%95%B0%E3%81%AE%E9%81%B8%E6%8A%9E%E3%82%AF%E3%82%A8%E3%83%AA%E3%81%AE%E7%B5%90%E6%9E%9C%E3%82%92%E7%B5%90%E5%90%88%E3%81%99%E3%82%8B-3856f16c-0a22-43f2-8c23-29ec44acbc05 たぶんダブる項目がたくさん出てくると思います。 ですから、それはさらに集計クエリでダブった項目を整理します。 ほんの一部だけを手元で確かめただけなので、これを作っていく際にどこかでうまくいかないところが出てくるかもしれませんが・・・そのときはゴメン。 なお、連鎖の数を制限すると、当然漏れが出てくるかもしれません。

moco-don
質問者

お礼

藁でも何でも掴みたい気持ちで初めてこのサイトで質問させていただきましたが、ご丁寧な回答有難うございました。願わくば、連鎖が無限な場合はどうすればいいのかご教示頂けると感謝感激です。VBになりますかね・・・。

moco-don
質問者

補足

稚拙な質問内容の意図を汲み取って頂いた上、適切な回答、有り難うございました。ご回答頂いたユニオンクエリ、クエリの集計機能を利用した方法については理解できました。 但し、今回は連鎖の数が不明なので、無限連鎖の場合の求め方を知りたいのです。 どうやら、クエリでは無理なようですが、今からVBを勉強するには時間的余裕は無い上、能力的にもきついのが実情です。 VBのサンプルをご紹介いただけると幸甚です。

関連するQ&A