Symfoware

Symfowareについての考察blog

SQLAlchemyの使い方8 多対多のリレーション

SQLAlchemyの使い方を勉強してます。

前回に引き続き、チュートリアルに沿って進めてみます。
http://docs.sqlalchemy.org/en/latest/orm/tutorial.html


現在登録されているデータは以下のとおり。


sample=# select * from students;
id |        name        |         kana        
----+--------------------+--------------------------
18 | 西住 みほ    | ニシズミ ミホ
19 | 武部 沙織    | タケベ サオリ
20 | 五十鈴 華    | イスズ ハナ
21 | 秋山 優花里 | アキヤマ ユカリ
22 | 冷泉 麻子    | レイゼイ マコ
(5 rows)



sample=# select * from addresses;
id |     email_address        | students_id
----+----------------------------+-------------
1 | nishizumi.miho@example.com |         18
3 | mipolin@example.com        |         18
(2 rows)








Building a Many To Many Relationship



今までは一対多のリレーションを行ってきました。
今回は、多対多のリレーションを定義してみます。

ストーリーとしては、生徒がBlogを書くことになりました。
新しく、「posts」と「keywords」という2つのテーブルを定義します。

「posts」にはブログの本文。
「keywords」には、検索用のキーワードを登録します。


テーブルを作成するため、以下のプログラムを実行します。


  1. import sqlalchemy
  2. import sqlalchemy.orm
  3. import sqlalchemy.ext.declarative
  4. import sqlalchemy.sql
  5. Base = sqlalchemy.ext.declarative.declarative_base()
  6. class Student(Base):
  7.     __tablename__ = 'students'
  8.     id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
  9.     name = sqlalchemy.Column(sqlalchemy.String(20))
  10.     kana = sqlalchemy.Column(sqlalchemy.String(40))
  11.     
  12.     # StudentとAddressテーブルの関連を定義
  13.     # cascadeを指定して、削除を連動
  14.     addresses = sqlalchemy.orm.relationship("Address", order_by="Address.id", backref="students",
  15.         cascade="all, delete, delete-orphan")
  16.     
  17.     def __repr__(self):
  18.         r = u"<Student:(name='%s', kana='%s')>" % (self.name, self.kana)
  19.         return r.encode('utf-8')
  20. class Address(Base):
  21.     __tablename__ = 'addresses'
  22.     id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
  23.     email_address = sqlalchemy.Column(sqlalchemy.String(100), nullable=False)
  24.     students_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('students.id'))
  25.     
  26.     def __repr__(self):
  27.         r = u"<Address:(email_address='%s')>" % (self.email_address)
  28.         return r.encode('utf-8')
  29. # blog機能を追加
  30. post_keywords = sqlalchemy.Table('post_keywords', Base.metadata,
  31.     sqlalchemy.Column('post_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('posts.id')),
  32.     sqlalchemy.Column('keyword_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('keywords.id'))
  33. )
  34. class BlogPost(Base):
  35.     __tablename__ = 'posts'
  36.     id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
  37.     students_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('students.id'))
  38.     headline = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
  39.     body = sqlalchemy.Column(sqlalchemy.Text)
  40.     # many to many BlogPost<->Keyword
  41.     keywords = sqlalchemy.orm.relationship('Keyword', secondary=post_keywords, backref='posts')
  42.     def __init__(self, headline, body, author):
  43.         self.author = author
  44.         self.headline = headline
  45.         self.body = body
  46.         def __repr__(self):
  47.             r = u"BlogPost(%r, %r, %r)" % (self.headline, self.body, self.author)
  48.             return r.encode('utf-8')
  49. class Keyword(Base):
  50.     __tablename__ = 'keywords'
  51.     id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
  52.     keyword = sqlalchemy.Column(sqlalchemy.String(50), nullable=False, unique=True)
  53.     def __init__(self, keyword):
  54.         self.keyword = keyword
  55. BlogPost.author = sqlalchemy.orm.relationship(Student,
  56.     backref=sqlalchemy.orm.backref('posts', lazy='dynamic'))
  57. url = 'postgresql://pgadmin:password@192.168.1.101:5432/sample'
  58. engine = sqlalchemy.create_engine(url, echo=False)
  59. # セッションを作成
  60. Session = sqlalchemy.orm.sessionmaker(bind=engine)
  61. session = Session()
  62. # スキーマ構成
  63. Base.metadata.create_all(engine)







実行すると、postsとkeywords、それに関連するシーケンスが作成されました。
postsとkeywordsの関連を管理するpost_keywordsも作成されています。


Schema |     Name     | Type | Owner
--------+------------------+----------+---------
public | addresses        | table    | pgadmin
public | addresses_id_seq | sequence | pgadmin
public | keywords         | table    | pgadmin
public | keywords_id_seq | sequence | pgadmin
public | post_keywords    | table    | pgadmin
public | posts            | table    | pgadmin
public | posts_id_seq     | sequence | pgadmin
public | students         | table    | pgadmin
public | students_id_seq | sequence | pgadmin








こんなプログラムに変更して、動きを見てみます。


  1. import sqlalchemy
  2. import sqlalchemy.orm
  3. import sqlalchemy.ext.declarative
  4. import sqlalchemy.sql
  5. Base = sqlalchemy.ext.declarative.declarative_base()
  6. class Student(Base):
  7.     __tablename__ = 'students'
  8.     id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
  9.     name = sqlalchemy.Column(sqlalchemy.String(20))
  10.     kana = sqlalchemy.Column(sqlalchemy.String(40))
  11.     
  12.     # StudentとAddressテーブルの関連を定義
  13.     # cascadeを指定して、削除を連動
  14.     addresses = sqlalchemy.orm.relationship("Address", order_by="Address.id", backref="students",
  15.         cascade="all, delete, delete-orphan")
  16.     
  17.     def __repr__(self):
  18.         r = u"<Student:(name='%s', kana='%s')>" % (self.name, self.kana)
  19.         return r.encode('utf-8')
  20. class Address(Base):
  21.     __tablename__ = 'addresses'
  22.     id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
  23.     email_address = sqlalchemy.Column(sqlalchemy.String(100), nullable=False)
  24.     students_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('students.id'))
  25.     
  26.     def __repr__(self):
  27.         r = u"<Address:(email_address='%s')>" % (self.email_address)
  28.         return r.encode('utf-8')
  29. # blog機能を追加
  30. post_keywords = sqlalchemy.Table('post_keywords', Base.metadata,
  31.     sqlalchemy.Column('post_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('posts.id')),
  32.     sqlalchemy.Column('keyword_id', sqlalchemy.Integer, sqlalchemy.ForeignKey('keywords.id'))
  33. )
  34. class BlogPost(Base):
  35.     __tablename__ = 'posts'
  36.     id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
  37.     students_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('students.id'))
  38.     headline = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
  39.     body = sqlalchemy.Column(sqlalchemy.Text)
  40.     # many to many BlogPost<->Keyword
  41.     keywords = sqlalchemy.orm.relationship('Keyword', secondary=post_keywords, backref='posts')
  42.     def __init__(self, headline, body, author):
  43.         self.author = author
  44.         self.headline = headline
  45.         self.body = body
  46.     def __repr__(self):
  47.         s = unicode(str(self.author), 'utf-8')
  48.         r = u"BlogPost(%s, %s, %s)" % (self.headline, self.body, s)
  49.         return r.encode('utf-8')
  50. class Keyword(Base):
  51.     __tablename__ = 'keywords'
  52.     id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
  53.     keyword = sqlalchemy.Column(sqlalchemy.String(50), nullable=False, unique=True)
  54.     def __init__(self, keyword):
  55.         self.keyword = keyword
  56. BlogPost.author = sqlalchemy.orm.relationship(Student,
  57.     backref=sqlalchemy.orm.backref('posts', lazy='dynamic'))
  58. url = 'postgresql://pgadmin:password@192.168.1.101:5432/sample'
  59. engine = sqlalchemy.create_engine(url, echo=False)
  60. # セッションを作成
  61. Session = sqlalchemy.orm.sessionmaker(bind=engine)
  62. session = Session()
  63. # 生徒:秋山 優花里が投稿
  64. yukari = session.query(Student).filter_by(name=u'秋山 優花里').one()
  65. # 最初の投稿データ
  66. post = BlogPost(u'優花里の投稿', u'初めてBlogに投稿します。', yukari)
  67. session.add(post)
  68. post.keywords.append(Keyword(u'初めて'))
  69. post.keywords.append(Keyword(u'Blog'))
  70. post.keywords.append(Keyword(u'投稿'))
  71. # 2つ目の投稿データ
  72. post = BlogPost(u'二回目の投稿', u'二回目の投稿になります。', yukari)
  73. session.add(post)
  74. post.keywords.append(Keyword(u'二回目'))
  75. # 「Blog」というキーワードで検索
  76. bp = session.query(BlogPost).filter(BlogPost.keywords.any(keyword='Blog')).all()
  77. print bp
  78. # 「二回目」というキーワードで検索
  79. bp = session.query(BlogPost).filter(BlogPost.keywords.any(keyword='二回目')).all()
  80. print bp
  81. session.commit()





ちゃんとキーワードに合致する投稿内容が取得出来ました。


$ python sample.py
[BlogPost(優花里の投稿, 初めてBlogに投稿します。, <Student:(name='秋山 優花里', kana='アキヤマ ユカリ')>)]
[BlogPost(二回目の投稿, 二回目の投稿になります。, <Student:(name='秋山 優花里', kana='アキヤマ ユカリ')>)]






プログラム実行後のテーブルの内容はそれぞれ以下のようになりました。
なんとなくわかった気がします。



# select * from posts;
id | students_id |     headline     |                 body                
----+-------------+--------------------+--------------------------------------
17 |         21 | 優花里の投稿 | 初めてBlogに投稿します。
18 |         21 | 二回目の投稿 | 二回目の投稿になります。
(2 rows)


# select * from keywords;
id | keyword
----+-----------
50 | 初めて
51 | Blog
52 | 投稿
53 | 二回目


sample=# select * from post_keywords;
post_id | keyword_id
---------+------------
     17 |         50
     17 |         51
     17 |         52
     18 |         53
(4 rows)

関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2014/04/09(水) 23:46:18|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<Python BottleフレームワークでSQLAlchemyを使用する | ホーム | SQLAlchemyの使い方7 JOINデータの取得方法とデータの削除>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/1381-db9fd968
この記事にトラックバックする(FC2ブログユーザー)