SQLAlchemyとOracleの文字化け問題と闘った

WindowsのOracleにアクセスしてSQLAlchemyを使おうとすると日本語が化ける。

engine = create_engine("xxxxxxxx", encoding='cp932')

とcreate_engineまわりをいじっても全然改善できない。これはcx_Oracleまわりかなぁとそっち方面を攻めたら解決したのでメモ。

Oracleのエンコーディングを調べる

対象のDBにアクセスしてコマンドを叩く

SELECT value FROM nls_database_parameters WHERE parameter='NLS_CHARACTERSET';

JA16SJISTILDEという文字列が返ってきた。

環境変数を設定する

スクリプト中で環境変数を設定する

import os
os.environ["NLS_LANG"] = "JAPANESE_JAPAN.JA16SJISTILDE"

これで解決した。

ProductName 超変換!! もじバケる6 20個入 BOX (食玩)

バンダイ / 2100円 ( 2012-12-12 )


SQLAlchemyのmany-to-manyのsecondaryにunique制約をかけたい

同じ外部キー同士の組み合わせが重複して登録されないようにしたい。Objectでチェックするよりはデータベースに任せたかったのでこんな風にしてみた。

patents_tags = db.Table('patents_tags',
     db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
     db.Column('patent_id', db.Integer, db.ForeignKey('patent.id')),
     db.UniqueConstraint('tag_id', 'patent_id', name='tag_patent')
)

ちなみに単に複数の属性をuniqueにしたいんだったら__table_args__を使えばよろしい。

ProductName Essential Sqlalchemy
Rick Copeland
Oreilly & Associates Inc / 2556円 ( 2008-06 )


SQLAlchemyのmany-to-manyであるタグを含むエントリをフィルタする方法

単にタグを含むエントリを取ってくる場合にはこうやればいいんだけど、さらに絞り込む場合にどうやればいいのか悩んだ。

filter(Entry.tags.in_(tags))

とかやったら、

"in_()" operator is not currently implemented for many-to-one-relations

みたいなエラーが出てきたので、グーグル先生にお伺いを立てたら解答が見つかった。

というわけで

Entry.query.filter_by(status=1).filter(Entry.tags.any(Tag.id.in_([tag.id])))

という感じで、anyとin_を組み合わせるのがいいらしい。

もう少し精進せなアカンなと。

ProductName Essential Sqlalchemy
Rick Copeland
Oreilly & Associates Inc / 2556円 ( 2008-06 )


結局こういったあたりで悩むんだったら、最初からpymongoでいいんじゃなかろうかと思ったりするんだけど、Flask-SQLAlchemyが便利すぎなのでなかなか悩ましいところ。

SQLAlchemyで日付の範囲を指定する

今年一年どんだけエントリ書いたかなーと思ったので調べようとしたら、日付の範囲指定が分からなかったので調べた。

datetime.dateで2011/1/1と2012/1/1の間をand_でつないだものでfilterする

filter(and_(
Entry.pubdate >= datetime.date(relevant_year, 1, 1),
Entry.pubdate < datetime.date(relevant_year + 1, 1, 1),
))

この本はバージョンがちょっと古くなってるけど、僕の中ではまだ現役でたまにお世話になってる。

ProductName Essential Sqlalchemy
Rick Copeland
Oreilly & Associates Inc / 2719円 ( 2008-06 )


Flaskでone-to-manyのデータの数を表示する

FlaskというよりはSQLAlchemy+Jinja2の話なのだけど。

one-to-manyのスキーマがあったとして(例えばあるレビューに付いたコメント数)、countメソッド使うと怒られるんだろうなぁと思いつつ使ってみる

{{ bookmark.comments.count() }}

これはやはり怒られた。

TypeError: count() takes exactly one argument (0 given)

で、Jinja2でlenが使えるかなとふと思ったのでやってみたら使えなかったが、builtin filtersにlengthがあるそうなので、それで解決。

onetomany

ところで、サービスは一気に動くところまで仕上げないと、だれるっていうかモチベーションが下がってきて生産性が落ちるなぁ。みんなはどうやってやる気を保ち続けているんだろうか?こつが知りたい。

SQLAlchemyで複数のカラムにUnique制約をかける

declarativeで二つのカラムに対してユニーク制約をかけたい時には__table_args__を使う

class Music(Base):
    __tablename__ = 'music'
    __table_args__ = (UniqueConstraint('title','artist'),{})
    id = Column(Integer, primary_key=True)
    title  = Column(String(128))
    artist = Column(String(128))

class Graph(Base):
    __tablename__ = 'graph'
    __table_args__ = (UniqueConstraint('head','tail'),{})
    id = Column(Integer, primary_key=True)
    head = Column(Integer, ForeignKey('music.id'))
    tail = Column(Integer, ForeignKey('music.id'))

実際に作られたテーブルのスキーマ

CREATE TABLE graph (
    id INTEGER NOT NULL, 
    head INTEGER, 
    tail INTEGER, 
    PRIMARY KEY (id), 
     FOREIGN KEY(head) REFERENCES music (id), 
     UNIQUE (head, tail), 
     FOREIGN KEY(tail) REFERENCES music (id)
);
CREATE TABLE music (
    id INTEGER NOT NULL, 
    title VARCHAR(128), 
    artist VARCHAR(128), 
    PRIMARY KEY (id), 
     UNIQUE (title, artist)
);

SQLAlchemyのmany-to-manyの検索のやりかたがわからない

なぜかFlaskではなくSQLAlchemyではまる

よくあるタグとエントリーの多対多のテーブル。

entry_tags = Table('entry_tags', Base.metadata,
    Column('entry_id', Integer, ForeignKey('entries.id')),
    Column('tag_id', Integer, ForeignKey('tags.id'))
)

class Tag(Base):
    __tablename__ = 'tags'
    id      = Column(Integer, primary_key=True)
    name    = Column(String(128), unique=True)
    entries = relation("Entry", secondary=entry_tags)

class Entry(Base):
    __tablename__    = 'entries'
    id               = Column(Integer, primary_key=True)
    title            = Column(String(128), unique=True)
    content          = Column(Text())
    pubdate          = Column(DateTime)
    tags             = relation("Tag", secondary=entry_tags)

このとき、タグをもつエントリを検索したい。単にエントリ最新10件をとってきたい場合には

entries = db_session.query(Entry).order_by(Entry.pubdate.desc()).limit(10)

でいい。

続いて、あるタグがふられているエントリの最新10件をとってきたい場合にどう書いていいか悩んだ挙句、結局わからなかったのでINを使うことにした。

tag_ids = [t.id for t in db_session.query(Tag).filter(Tag.name == tagname).first().entries]
entries = db_session.query(Entry).filter(Entry.id.in_(tag_ids)).order_by(Entry.pubdate.desc()).limit(10)

filterかfilter_byに何入れればいいんだろう?

ProductName Essential Sqlalchemy
Rick Copeland
Oreilly & Associates Inc / ¥ 3,370 ()
通常1~3週間以内に発送

2010.08.01 追記

relationでorder_byを設定すればよかった。

entries = relation("Entry", secondary=entry_tags, order_by=Entry.pubdate.desc)

でもって

entries = db_session.query(Tag).filter(Tag.name == tagname).first().entries

とすれば、あるタグを含むエントリを日付の最近の順に取ってくる。

既存のSQLiteデータにSQLAlchemyでアクセスする

多数の化合物のファイルはSMILES形式でsqliteのデータベースにしておくと、あつかいやすいのだけど、たまにさくっとアクセスしてゴニョりたいときにsqliteでSQL文発行してとかめんどくさかったりする。

で、SQLAlchemy使うとpythonの対話環境で使えてそのままpybelとかに持っていけるので便利なんだけど、いちいちマッピングしなくても良いらしい。

create_engineでsqliteのファイルを指定して、reflectをTrueにすればよい。

from sqlalchemy import *

db = create_engine('sqlite:///test.db')
metadata = MetaData(bind=db, reflect=True)
table = metadata.tables['table_name']

このdrkcoreのファイルをつかって、タイトルを抜き出してみる。

>>> execfile("satest.py")
>>> stmt = entries.select()
>>> result = stmt.execute()
>>> for row in result:
...   print row.title
... 
blogを変えてみた
卓次郎商店でつけ麺
かど乃やで黒びしおラーメン

らくちん。

ProductName Essential Sqlalchemy
Rick Copeland
Oreilly & Associates Inc / 2719円 ( 2008-06 )


4章くらいまで読んだ。データの永続化用にsqlalchemy使えるようにしとくと仕事が非常に楽になりそうなので、ちゃんと読む。