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

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

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

PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

SQL

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

Q&A

解決済

3回答

1005閲覧

中間テーブルを2つまたがるリレーションがある時適切な検索ヒットをしたい。

acre_maker

総合スコア145

PostgreSQL

PostgreSQLはオープンソースのオブジェクトリレーショナルデータベース管理システムです。 Oracle Databaseで使われるPL/SQLを参考に実装されたビルトイン言語で、Windows、 Mac、Linux、UNIX、MSなどいくつものプラットフォームに対応しています。

SQL

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

1グッド

0クリップ

投稿2017/10/02 08:55

編集2017/10/03 02:37

###前提

  • Table A
  • Table B
  • A, Bの中間テーブルであるTable AB
  • Table C
  • B, Cの中間テーブルであるTable BC
  • Table Bのレコードを2個あるいは3個のみがTable Cの1つのレコードとリレーションを

持つように登録している。

中間テーブル以外にはPKがあり(ここでは便宜上A_id, B_id, C_idとします)
中間テーブルは例えばTable ABならA_id, B_idというカラムのみを持ち、
Unique(A_id, B_id)と設定されています。

###実現したいこと
A_idを決めた時(例えばA_id=1とした時)
それとリレーションのあるTable AB→Table B→Table BC→Table Cとたどり、
最終的にリレーションのあるTable Cの数をカウントしたいです。

追記
例えば以下の例では

A_id=1に関しては、Table CのC_id=500, 501, 502の3つを検索ヒットし
C_id=503, 504は検索ヒットしたくありません。
(A_id=1はB_id=33, 34とリレーションがないため)

A_id=2に関しては、Table CのC_id=500の1つを検索ヒットし
C_id=501, 502, 503, 504は検索ヒットしたくありません。
(A_id=2はB_id=32, 33, 34とリレーションがないため)

A_id=3に関しては、Table CのC_id=503, 504, 505の3つを検索ヒットし
C_id=500, 502は検索ヒットしたくありません。
(A_id=3はB_id=30, 32とリレーションがないため)

###発生している問題・エラーメッセージ
追記
単純に中間テーブルを用いたSQL文を以下のように考えました。

sql

1SELECT COUNT(DISTINCT(C_id)) FROM BC WHERE B_id in ( 2 SELECT B_id FROM AB WHERE A_id = 1 3)

しかし、この自分で考えたSQL文では
以下のようなリレーションがある時、A_id=2を満たすBのB_idを取得して、
そのB_idを含むTable Cという単純な条件で検索をすると(31, 502)のレコードも
ヒットしてしまいます。
しかし、Table BCのレコード(31, 502), (32, 502)はA_id=1由来で登録したレコードなので、
ヒットしたくありません。

Table A

A_idvalue
1foo
2baz
3bar

Table B

B_idvalue
30-1
31-2
32-3
33-4
34-5

Table AB

A_idB_id
130
131
132
230
231
331
333
334

Table C

C_idvalue
500Alice
501Bob
502Chris
503Dylan
504Emily
505Franc

Table BC

B_idC_id
30500
31500
30501
32501
31502
32502
31503
33503
31504
34504
33505
34505

どのようにすれば、私が望むように検索ヒットすることができるでしょうか?
このテーブルにこだわる必要はありませんので、
そもそもテーブルの構築があまり良くないと言ったアドバイスでも結構です。
ご指導ご鞭撻よろしくお願いします。

###補足情報
PostgreSQL (version9.5.7)

###追記
専門的なことをしているので、抽象的に書いたほうがわかりやすいと思いましたが、
かえってわかりにくいという印象を与えてしまったようなので、
具体的なテーブルも下に書いておきます。

化学構造のデータを扱っていまして

A: 化学構造を含むライブラリ(データベース)名
B: 化学構造自体のレコード
AB: 複数のライブラリが同じ化学構造を含むことがあるためこのような形でリレーション
C: Bの化学構造のうちの2あるいは3個を組み合わせた新規の化学構造
BC: Cの新規の化学構造のために組み合わせたBの化学構造

となっております。

退会済みユーザー👍を押しています

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

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

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

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

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

karamarimo

2017/10/02 10:09

「Table BCのレコード(31, 502), (32, 502)はA_id=1由来で登録したレコードなので」とありますが、どのA_id由来かはどうやって判断するのでしょうか?
acre_maker

2017/10/02 10:44

コメントありがとうございます。saziさんへのコメントのような方法で識別できるかなと思ったのですが、難しいでしょうか?
A.Ichi

2017/10/03 03:55

A_id=1で作成されたTableBCはどの値でしょうか?関係ない事ですが、TableBのマイナス値は何を表しているのでしょうか?
acre_maker

2017/10/03 04:00

ご質問ありがとうございます。A_id=1はB_id=30,31,32とリレーションがあるので、TableBCのC_id=500, 501, 502の計6個を作成しています。ただし、A_id=2に関するレコードが先に作られた場合C_id=500のレコード2個はすでに作成されているのでそちらに関しては追加では作りません。Table Bのマイナス値は抽象的なTableを作るためにかいただけなので特に意味はありません。すいません。実際のテーブルでは化学構造を扱うための文字列であるSMILESキーと呼ばれる値が入っています。
A.Ichi

2017/10/03 06:08 編集

上記より察するにTableAが追加された場合にTableBCへ追加は、既に同キーが存在していない場合と言う事でしょうか。さらに選択する場合には、追加したA_id以外のidでは対象外にする事になりますでしょうか。
acre_maker

2017/10/03 06:10

>上記より察するにTableAが追加された場合にTableBCへ追加は、既に同キーが存在していない場合と言う事でしょうか。→はい。その通りです。
acre_maker

2017/10/03 06:11

>さらに選択する場合には、追加したA_id以外のidでは対象外にする事になりますでしょうか。→すいません。選択とはどういう意味でしょうか?他のidからも呼び出されることはあります。
A.Ichi

2017/10/03 06:26

Table BCのレコード(31, 502), (32, 502)はA_id=1由来で登録したレコードなので、 ヒットしたくありません。の由来とは作成のA_id事ではないかどおもいまして
acre_maker

2017/10/03 08:21

そうですね。作成時のみわかるので本来は作成時に何かしらの情報を残すべきだったと思っています。
A.Ichi

2017/10/03 08:26

そうであれば、Table BCに作成のA_idを加える事で、SELECTでの排他条件に加える事ができるのではと考ました。
acre_maker

2017/10/03 12:36

そういうのがあるのですね。排他制御というものでしょうか?勉強してみます。
guest

回答3

0

ベストアンサー

Table BCのレコード(31, 502), (32, 502)はA_id=1由来で登録したレコードなので

このことが分かる条件が無い限り無理でしょう。
c_idが識別可能なルールで採番されているなら条件として使用できますが、
そうでなければ、table cにその識別が必要かと思います。

追記

以下の構造で十分な気がします。

TableA(A_id,Value) TableB(B_id,Value,A_id[]) TableC(C_id,Value,A_id[]) TableBC(B_id,C_id)

※BCの組み合わせがあるので、BC追加。

この構造であれば、目的の件数取得は以下になります。

SQL

1select count(*) from TableC where A_id @> array[1]

配列の個数をA_idと対比させ固定するなら以下のようになります。

SQL

1select count(*) from TableC where A_id[1]=1 2--A_id[]をBoolean型とするなら 3select count(*) from TableC where A_id[1]

追記2

以前提案していた構造を今回のもので表すと

TableA(A_id,Value) TableB(B_id,Value,B_id[],A_id[])

となります。
TableBはBとCを合わせたもので、B_id[]が分子構造の構成要素になります。
元々のTableBの識別が必要なら、属性項目を持たせた方が良いでしょう。
基本となる構成要素も検索対象にするならB_id[]にはB_idも含めます。
含めないならB_id[]は空にしておけば検索対象にはなりません。

で、B_id[]のGinインデックスを作成し、

where B_id[]@>array[30,32] -- B_id[]は便宜上の表現

のようにすれば良いのではないかと。

投稿2017/10/02 10:34

編集2017/10/03 06:24
sazi

総合スコア25195

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

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

acre_maker

2017/10/02 10:43

ご回答ありがとうございます。 >このことが分かる条件が無い限り無理でしょう。 (1)A_id=2の条件よりTable ABからB_id=30, 31 ((a)とします)を検索する。 (2)(a)の条件とTable BCからC_id=500, 501, 502を検索する。 (3)Table BC上で502に対してB_id=31, 32をみつけるけど、 32は(a)に含まれていないから502は除外する。 としたら行けそうな気がしましたけどSQL文では難しい工程でしょうか? >c_idが識別可能なルールで採番されているなら条件として使用できますが、 >そうでなければ、table cにその識別が必要かと思います。 例えば、Table BCにA_idを外部キーとしてカラムを追加するといった 解決策がよいでしょうか?
karamarimo

2017/10/02 11:15

つまり検索したいのは「A_id=2と関係があるBすべてに関係があるC」ということですか?
sazi

2017/10/02 11:33

A_idは2件だけなのでしょうか?3件以上の場合はその排他の条件はどうなるのでしょう?
acre_maker

2017/10/02 12:46 編集

>karamarimo様 おっしゃるとおりになります。 >sazi様 A_idは3件以上になる予定です。 私の理解不足のせいか、2件が3件以上になって、 考慮しなければならない点がみえていません。 排他の条件とはそちらを指しているのでしょうか? より詳しく教えていただけたら助かりす。
sazi

2017/10/02 17:19

>(3)Table BC上で502に対してB_id=31, 32をみつけるけど、32は(a)に含まれていないから502は除外する。 その条件がid=3に含まれるとどうなりますか? id毎に自身以外のIDにないものの件数を取得したいということ?
acre_maker

2017/10/02 21:58

お返事ありがとうございます。 id=3に含まれる場合もヒットしてもいいです。 あくまで注目しているA_idとリレーションがあるBから構成されていたらいいです。 テーブルのデータ数を増やして各A_idの時 どうヒットしてほしいか追記してみました。 お暇な時にご確認ください。
sazi

2017/10/03 02:41

>A: 化学構造を含むライブラリ(データベース)名 >B: 化学構造自体のレコード >AB: 複数のライブラリが同じ化学構造を含むことがあるためこのような形でリレーション >C: Bの化学構造のうちの2あるいは3個を組み合わせた新規の化学構造 >BC: Cの新規の化学構造のために組み合わせたBの化学構造 それぞれのライブラリごとに化学構造が幾つあるかカウントしたいということなのですね。 それ以外の目的としてはそのデータがどのライブラリからの由来であるかどうかでしょうから、 最終的に参照できれば良いかと思うのですが、ライブラリも検索条件になりますか? 検索対象としての頻度が低いならBやCに配列として持たせても十分だと思いますが。
acre_maker

2017/10/03 04:01

ご回答ありがとうございます。 >それぞれのライブラリごとに化学構造が幾つあるかカウントしたいということなのですね。 >それ以外の目的としてはそのデータがどのライブラリからの由来であるかどうかでしょうから、 >最終的に参照できれば良いかと思うのですが、ライブラリも検索条件になりますか? >検索対象としての頻度が低いならBやCに配列として持たせても十分だと思いますが。 おっしゃるとおりですね。 各ライブラリから作成した新規の化学構造が何個あるか確認したいです。 その後、新規の化学構造を以前アドバイスいただいたように別のライブラリに対して部分構造検索する と言った流れになっています。 (話がそれますがこちらを先に作らないと前アドバイスいただいた式によるインデックスが試せません) >ライブラリも検索条件になりますか? ライブラリは基本的に検索条件にはなりません。 A_id = 1, 2, 3, ...のいずれかで絞り込むだけですね。 >検索対象としての頻度が低いならBやCに配列として持たせても十分だと思いますが。 メインは上にかいた部分構造検索を行うため、 基本的に確認などで検索するだけなのでおっしゃるとおり頻度は低いです。 配列として保存するならTable BCを B_id | C_id | ---------------------- [30, 31] | 500 [30, 32] | 501 [31, 32] | 502 [31, 33] | 503 [31, 34] | 504 [33, 34] | 505 と保存することが考えられますがこちらはあまり好ましくありませんか? これなら2,3つの組み合わせどちらにも対応できそうですが。 お時間あるときにお答えいただけると助かります。
acre_maker

2017/10/03 05:13

追記ありがとうございました! 自身で紙の上でテーブルを書いてみて意図が理解できました。 追記2でご提示してもらっているものがしっくりきました。 >基本となる構成要素も検索対象にするなら はい。質問には書いていませんでしたが、 基本構成1つからなるものも含めるにこしたことはないのでこちらを採用します。 ARRAY_LNEGTH(B_id[ ], 1)を使えばいくつの構成要素からなるかで 絞り込みもできますしよさそうですね。 長々とありがとうございました!
sazi

2017/10/03 06:53 編集

構成要素からその構成を参照する用途がある場合は、unnest(B_id[])のように配列を行に直してから、結合するのが効率良いかと思います。 また、この構造が親子での2階層であればよいのですが、多階層で、階層を辿る必要があるような場合は、自己結合用の項目を(親のB_id)を持つと共に再帰問い合わせ(WITH RECURSIVE)を用い、B_id[]に含まれるレベルを揃えるなどの工夫が必要になることが予想されます。
sazi

2017/10/03 09:00

>自己結合用の項目を(親のB_id) これは、十分ではないし、B_id[]で肩代わりできるので不要でした。
guest

0

A: 化学構造を含むライブラリ(データベース)名

B: 化学構造自体のレコード
AB: 複数のライブラリが同じ化学構造を含むことがあるためこのような形でリレーション
C: Bの化学構造のうちの2あるいは3個を組み合わせた新規の化学構造
BC: Cの新規の化学構造のために組み合わせたBの化学構造

A:B=1:多
C:B=1:多
Aが含まれるCをカウントするということですね。
Aが含まれるBを抽出し、そのBが含まれるCをカウントするというイメージでしょうか。
効率化を考えなければ、単純にサブクエリーを利用するのが考えやすそうです。

投稿2017/10/03 02:35

t_obara

総合スコア5488

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

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

0

質問に記載のデータ構造に不足しているのは、A_id = 1 の場合にABテーブルから得られるB_idの組(30,31,32)が(30,31),(30,32),(31,32)の組み合わせとならなければいけないという意味付けです。

B_id 2つにより C_id が特定できるという情報を持っているのはBCテーブルですので、BCテーブルの定義を下記とすることで特定が可能になります。

BC (B_id1, B_id2, C_id)

上記定義に変更した場合、下記のように取得できるのではないかと思います。

SQL

1SELECT C_id FROM BC 2INNER JOIN AB AB1 ON BC.B_id1 = AB1.B_id 3INNER JOIN AB AB2 ON BC.b_id2 = AB2.b_id 4WHERE AB1.A_id = AB2.A_id AND AB1.A_id = 1;

追記:
せっかく具体例をご提示いただいたのですが、上手な方法は思いつきませんでした。
後々のことを考えると列を増やすのは確かに得策ではないと感じましたので
もともとのデータ構造で実現するとしたら、ということで下記のように考えてみました。
もしかしたら、組み合わせの列挙方法がご想定のものとは異なるかもものになっているかもしれません。

SQL

1SELECT BC1.c_id FROM BC BC1 2INNER JOIN BC BC2 ON BC1.C_id = BC2.C_id AND BC1.B_id < BC2.B_id 3 LEFT JOIN BC BC3 ON BC1.C_id = BC3.C_id AND BC2.B_id < BC3.B_id 4INNER JOIN AB AB1 ON BC1.B_id = AB1.B_id 5INNER JOIN AB AB2 ON BC2.B_id = AB2.B_id 6 LEFT JOIN AB AB3 ON BC3.B_id = AB3.B_id 7WHERE (BC3.c_id IS NOT NULL OR BC1.c_id NOT IN ( 8 SELECT BC1.c_id FROM BC BC1 9 INNER JOIN BC BC2 ON BC1.C_id = BC2.C_id AND BC1.B_id < BC2.B_id 10 INNER JOIN BC BC3 ON BC1.C_id = BC3.C_id AND BC2.B_id < BC3.B_id 11 )) 12 AND AB1.A_id = AB2.A_id 13 AND 14 ( 15 (BC3.B_id IS NOT NULL AND AB3.B_id IS NOT NULL AND AB1.A_id = AB3.A_id) 16 OR (BC3.B_id IS NULL AND AB3.B_id IS NULL) 17 ) 18 AND AB1.A_id = 1;

投稿2017/10/03 00:00

編集2017/10/03 05:05
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

acre_maker

2017/10/03 00:57

ご回答ありがとうございます。 2つの時はおっしゃるSQL文でうまくとりだすことができそうですね! 少しわかりにくいですが >Table Bのレコードを2個あるいは3個のみがTable Cの1つのレコードとリレーションを 持つように登録している。 という条件で3個の場合があります。 この場合を考慮するとどうなりますかね? この場合を考慮するために現在のテーブルみたいにしていたのですが それだとまたもとに戻ってしまいますしね。
退会済みユーザー

退会済みユーザー

2017/10/03 01:29

BCテーブルのB_id列を3つに増やすか、3つ組の関連テーブルを増やして同様のことをすればよいかと思います。 2つの場合、3つの場合はUNIONで結合する形になります。 ご質問の内容は抽象化されており、A_id、B_id、C_idの関連がわかりませんので、実ケースでのよりよいデータの持ち方はすぐには思いつきませんでした。
acre_maker

2017/10/03 01:53

回答ありがとうございます。 >BCテーブルのB_id列を3つに増やすか、3つ組の関連テーブルを増やして同様のことをすればよいかと思います。 >2つの場合、3つの場合はUNIONで結合する形になります。 2つと3つの場合で別々にテーブルを持つ必要があるということなんですね。 一緒にまとめたかったですが、それが最適ならそのようにする必要がありそうですね。 >ご質問の内容は抽象化されており、A_id、B_id、C_idの関連がわかりませんので、実ケースでのよりよいデータの持ち方はすぐには思いつきませんでした。 かなり専門的なことをしているので抽象化したのですが返ってわかりにくいでしたかね? すいません。 化学構造のデータを扱っていまして A: 化学構造を含むライブラリ(データベース)名 B: 化学構造自体のレコード AB: 複数のライブラリが同じ化学構造を含むことがあるためこのような形でリレーション C: Bの化学構造のうちの2あるいは3個を組み合わせた新規の化学構造 BC: Cの新規の化学構造のために組み合わせたBの化学構造 となっております。
acre_maker

2017/10/03 05:09

追記ありがとうございました! なんとかできるんじゃないかなと思っていましたが やはり相当複雑なSQL文になりますね。 テーブルの構造を見直してみます。 なにはともあれありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問