Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

【修正版】Django + SQLAlchemy: シンプルWay

3,054 views

Published on

Simple Way with Django + SQLAlchemy
AT PyCon JP 2020
https://pycon.jp/2020/timetable/?id=203756

質疑応答

> Ryuji Tsutsui から全員に: 02:58 PM
> INNNER JOIN
> Nが1個多い?
ほんとだ。slideshareにあげた資料、直せません!

> Taku Shimizu から全員に: 03:00 PM
> 「ドキュメントに記載されていない」なかなかのパワーフレーズですね
でしょー

> uranusjr から全員に: 03:04 PM
> g2の別名はT3になるのはなぜですか?
Djangoが自動的にテーブルを2回JOINすることもあって、そういう場合自動的にテーブル名の別名が付けられます。T2,T3,T4と連番で増えていきます。
たぶん、登場する3つ目のテーブルだからT3なのだと思います。
`annotate(g2=FilteredRelation(...)` のように名前指定しているのに使われないのは、バグなのかどうなのか追ってません。SQLは動作するので、バグとはいえないかも。

> Manabu から全員に: 03:17 PM
> SQLAlchemy の モデルクラスを直接書いていましたが、 automap_base() は使わないのですか?
全テーブルを使いたいわけではないのと、用途の目的から、SQLAlchemyでForeignKeyを独自に追加定義したいなどもあるため、個別に書いています。
automap_base()を使っても良いと思います。

> c-bata から全員に: 03:19 PM
> DBのマイグレーションはalembicを使う感じでしょうか?
Dango ORM側で全てマイグレーションするか、それ以外(alembicや生DDL)でマイグレーションするかは統一すればよいと思います。
Django ORM側でやるのが楽だと思いますが、Redshiftなどの場合生DDLでやるしかなかったりするし、そういう環境でこそこの方法が有用だったりします。

> あ、基本的にDjango ORMを使っていて、難しいクエリだけSQLALchemyで
SQL生成している感じですかね。
はい。そういう感じです。

Published in: Technology
  • Be the first to comment

【修正版】Django + SQLAlchemy: シンプルWay

  1. 1. Takayuki Shimizukawa
  2. 2. @shimizukawa (清水川)  (株)ビープラウド 取締役 / IT Architect  IT勉強会支援サービス connpass  オンライン学習サービス PyQ  一般社団法人PyCon JP Association 理事  PyCon JP 年次イベントの見守り  Python Boot Camp 主催  Sphinx コミッター(休業中) おまえ誰よ / Who are you 22020/8/28
  3. 3. 執筆・翻訳に関わったPython書籍 2020/8/28 3 2018/2/282018/2/26 翻訳・監訳 補章執筆 進行管理 6章: デプロイ 9章: ドキュメント 2018/6/12 3章: package 9章: package (7章)ドキュメント 2017/10/20 1章: Sphinxとは 付録A: reStructuredTextリファレン ス 第17刷
  4. 4. 自走プログラマー(2020年2月27日) 2020/8/28 4 本書には 「プログラミング入門者が 中級者にランクアップ」 するのに必要な 「自走するための知識」を 「120のベストプラクティス」 にまとめました。 NEW
  5. 5. Django ORMをうまく使いこなすには、このスライドで紹介する事の他に も 色々知っておくべきことがあります。 書籍『自走プログラマー』では、以下のトピックを紹介しています。  58: DBのスキーママイグレーションとデータマイグレーションを分け る  59: データマイグレーションはロールバックも実装する  60: Django ORMでどんなSQLが発行されているか気にしよう  61: ORMのN+1問題を回避しよう  62: SQLから逆算してDjango ORMを組み立てる 『自走プログラマー』とDjango ORM 2020/8/28 5
  6. 6. 62020/8/28
  7. 7.  DjangoのORMがあればSQLが分からなくても大丈夫 (´・ω・) … 大丈夫...  SQLとDjango ORM、用語が違って難しい (´・ω・) SQLのgroup byはORMではannotate、とかね  SQLは書けるけど、DjangoのORMは難しい (´・ω・) SQLからORMクエリを逆算するのが難しいよね  Django+SQLAlchemy ってシンプルじゃないよね? (´・ω・`) そうかも この資料のターゲット 72020/8/28
  8. 8.  Django ORM と SQLAlchemy の併用をお勧めするスライドです  SQL の SELECT クエリのみ扱います  INSERT/UPDATE/DELETE は Django ORM に統一でよいかと  トランザクションなど色々考える事が増えるため このスライドの前提 2020/8/28 8
  9. 9. 1. Django ORMで表現が難しいSQLの例 2. SQLを直接書いてDjangoで実行する例 3. SQLAlchemyの紹介と簡単な実行例 4. SQLAlchemyでテーブル別名JOINを組み立ててDjangoに組み込む 5. SQLAlchemyとDjangoの連携に Aldjemy を使う 6. 複雑なSQLをSQLAlchemyで組み立てる 7. まとめ と 補足 アジェンダ 2020/8/28 9
  10. 10. (´?ω?`)Djangoでどう書くの? 102020/8/28
  11. 11. クエリで欲しい結果のイメージ  personごとに、ある2つの年(year1, year2)の成績を一覧化したい 前提: ER図と欲しいデータ 2020/8/28 11 name year1_grade year2_grade ===== =========== =========== Ham 4 5 Spam 4 2 Egg None 4
  12. 12. class Person(models.Model): class Meta: db_table = 'person' name = models.CharField('名前', max_length=255) class Grade(models.Model): class Meta: db_table = 'grade' year = models.IntegerField('年度') person = models.ForeignKey(Person, on_delete=models.CASCADE) seiseki = models.IntegerField('成績', validators=[ MinValueValidator(1), MaxValueValidator(5) ]) Django ORMの定義例 2020/8/28 12
  13. 13.  シンプルなクエリを書きやすい  バージョンアップにつれて、複雑なクエリも可能になりつつある  サブクエリや、後述する別名JOINなど  SQLとORMでレイヤーが異なるため用語が異なる  WHERE ではなく filter  SELECT する項目を選択するには values  SELECT する項目を追加するには annotate  GROUP BY は aggregate  INNER JOIN / LEFT OUTER JOIN は自動判別 DjangoのORMは ... 2020/8/28 13 >>> qs = Person.objects.filter(grade__year=2019) .values('name', 'grade__seiseki') SELECT person.name, grade.seiseki FROM person INNER JOIN grade ON (person.id = grade.person_id) WHERE grade.year = 2019
  14. 14.  INNER JOIN / LEFT OUTER JOIN  暗黙的に可能、明示的に指定できない  (´・ω・) このINNER JOIN を LEFT OUTER JOINに変えるにはどう書くの?  外部キー制約が定義されていないModelのJOIN  extrasを使えば INNER JOIN に近いクエリは可能  ModelにForeignObjectを定義すれば、通常のクエリ実装でJOINが可能  1つのテーブルを複数回JOIN  FilteredRelationを使えば可能  セルフJOIN  不可能(裏技を駆使すれば可能) Django ORMで表現が難しいSQL 2020/8/28 14
  15. 15. INNER JOIN / OUTER JOINの指定は暗黙的に可能 INNER JOIN LEFT OUTER JOIN . Django ORMで表現が難しいSQL: JOIN 2020/8/28 15 >>> qs = Person.objects.filter(grade__year=2019).values('name', 'grade__seiseki') SELECT person.name, grade.seiseki FROM person INNER JOIN grade ON (person.id = grade.person_id) WHERE grade.year = %s filter()で行を絞り込むと 通常 INNER JOIN になり NULLが除外される filter()で行を絞り込むと 通常 INNER JOIN になり NULLが除外される >>> qs = Person.objects.filter(Q(grade__year=2019)|Q(grade__isnull=True)) .values( 'name', 'grade__seiseki') SELECT person.name, grade.seiseki FROM person LEFT OUTER JOIN grade ON (person.id = grade.person_id) WHERE (grade.year = 2019 OR grade.id IS NULL) NULLを含むfilter()指定で、 LEFT OUTER JOIN になる
  16. 16. 外部キー制約が定義されていないModelのJOIN extrasを使えば、内部結合(INNER JOIN相当)は可能 ただし INNER JOIN と異なり、gradeに値がないpersonは除外される。 LEFT OUTER JOIN は表現できない。 そして、コードの理解が難しくなるので書きたくない... Django ORMで表現が難しいSQL: FKなし1 2020/8/28 16 # GradeモデルにForeignKeyを定義していない場合 >>> qs = Person.objects.extra( ... tables=['grade'], ... where=['grade.person_id=person.id', 'grade.year=2019'] ... ).extra(select={'seiseki': 'grade.seiseki'}) SELECT (grade.seiseki) AS seiseki, person.id, person.name FROM person, grade WHERE (grade.person_id=person.id) AND (grade.year=2019) extra は最終手段、将来廃止予定 ― QuerySet API reference より (´・ω・`)色々な事情が ありましてね・・・
  17. 17. 「関連」をModelに設定すれば、普通のORMコードでJOIN可能になる 外部キー制約(ForeignKey)を定義できない状況(マイグレーションでDB に反映されては困る時)の回避策として使える。 extraで頑張るより良い。 だが、ドキュメントに記載されていない (´・ω・`) Django ORMで表現が難しいSQL: FKなし2 2020/8/28 17 明示的な関連のない2つのモデルを JOINする方法は? ― Django Issue #29551 より class Grade(models.Model): person_id = models.IntegerField('Person') person = ForeignObject( Person, models.CASCADE, from_fields=['person_id'], to_fields=['id'] )
  18. 18. テーブルを別の名前でJOINするのは可能。 Django 2.0 で導入された FilteredRelation を使う Django ORMで表現が難しいSQL: 別名JOIN1 2020/8/28 18 >>> qs = Person.objects.annotate( ... g=FilteredRelation('grade'), ... ).filter( ... Q(g__year=2019) | Q(g__isnull=True) ... ).values('name', 'g__seiseki') SELECT person.name, g.seiseki FROM person LEFT OUTER JOIN grade g ON (person.id = g.person_id) WHERE (g.year = 2019 OR g.id IS NULL) ※ ただし JOIN には ForeignKey が必 要FilteredRelation を annotate() に指定すること で、JOIN の ON句を指定できます。 ― QuerySet API reference より
  19. 19. 1つのテーブルを別の名前で複数回 JOINするのも可能。 Django ORMで表現が難しいSQL: 別名JOIN2 2020/8/28 19 >>> qs = Person.objects.annotate( ... g1=FilteredRelation('grade'), ... g2=FilteredRelation('grade'), ... ).filter( ... Q(g1__year=2019) | Q(g1__isnull=True), ... Q(g2__year=2020) | Q(g2__isnull=True), ... ).values('name', 'g1__seiseki', 'g2__seiseki') SELECT person.name, g1.seiseki, T3.seiseki FROM person LEFT OUTER JOIN grade g1 ON (person.id = g1.person_id) LEFT OUTER JOIN grade T3 ON (person.id = T3.person_id) WHERE ( (g1.year = 2019 OR g1.id IS NULL) AND (T3.year = 2020 OR T3.id IS NULL)) 2つめの名前指定 は無視される
  20. 20. FilteredRelationの本来の使い方は、ON句で絞り込みを指定する。 絞り込んでからJOINされるため、SQLの実行効率が良い。 Django ORMで表現が難しいSQL: 別名JOIN3 2020/8/28 20 >>> qs = Person.objects.annotate( ... g1=FilteredRelation('grade', condition=Q(grade__year=2019)), ... g2=FilteredRelation('grade', condition=Q(grade__year=2020)), ... ).values('name', 'g1__seiseki', 'g2__seiseki') SELECT person.name, g1.seiseki, T3.seiseki FROM person LEFT OUTER JOIN grade g1 ON ((person.id = g1.person_id) AND (g1.year = 2019)) LEFT OUTER JOIN grade T3 ON ((person.id = T3.person_id) AND (T3.year = 2020))
  21. 21. 例: Grade.seiseki の毎年の変化をPersonごとに行いたい  FK定義なしでのセルフJOINは、通常の方法では不可能  裏技(!)を駆使すれば可能らしい  https://stackoverflow.com/questions/1578362/self-join-with-django-orm  ForeignObjectを使えば可能かもしれない  今のところうまくいってません  ドキュメントにないクラスを使うのは避けたい Django ORMで表現が難しいSQL: セルフJOIN 2020/8/28 21
  22. 22. ここまでのまとめ Django ORMで表現が難しいSQL 2020/8/28 22
  23. 23.  SQLを知ってる人は  SQLをDjango ORMに翻訳している感じ  Django ORMで思い通りのSQLを発行するのは意外と難しい  SQLに不慣れな人は  目隠しして(SQLを見ずに)データを取り出そうとする感じ  Django ORMで思い通りのデータを得るのは意外と難しい Django ORMで表現が難しいSQL: まとめ 2020/8/28 23 ― 『自走プログラマ-』 "60: Django ORMでどんなSQLが発行されているか気にしよう" より 残念ながら、ORMは「SQLを知らなくても使える便利な仕組み」ではあ りません。 簡単なクエリであればSQLを確認する必要はなく、多くの要 件は簡単なクエリの発行で済むかもしれません。 だからといって、ORM がどんなSQLを発行しているか気にしないままでいると、落とし穴には まってしまいます。
  24. 24. (´・ω・`)やっちゃう? 242020/8/28
  25. 25. 欲しいデータ: personごとに、2つの年(year1, year2)の成績を一覧化 実行効率の良い、生々しいSQL 生SQLを書いて実行しよう 2020/8/28 25 SELECT p.name AS name, g1.seiseki AS grade1, g2.seiseki AS grade2 FROM person AS p LEFT OUTER JOIN grade AS g1 ON ((p.id = g1.person_id) AND (g1.year = 2018)) LEFT OUTER JOIN grade AS g2 ON ((p.id = g2.person_id) AND (g2.year = 2019)) name year1_grade year2_grade ===== =========== =========== Ham 4 5 Spam 4 2 Egg None 4
  26. 26. 生SQLをrawメソッドで実行する NG!!  文字列操作でSQLに値を埋め込むと、SQLインジェクションの原因に! 生SQLの落とし穴 2020/8/28 26 >>> year1, year2 = 2019, 2020 >>> sql = """ SELECT p.id, p.name, g1.seiseki AS grade1, g2.seiseki AS grade2 FROM person AS p LEFT OUTER JOIN grade AS g1 ON ((p.id = g1.person_id) AND (g1.year = %s)) LEFT OUTER JOIN grade AS g2 ON ((p.id = g2.person_id) AND (g2.year = %s)) """.format(year1, year2) >>> qs = Person.objects.raw(sql) >>> for raw in qs: ... print(f"{p.name}, {p.grade1}, {p.grade2}")
  27. 27. プレースホルダ (%s) を使い、ドライバ側で値をエスケープ処理する 課題  動的な条件追加などは文字列操作しかない(WHERE句追加など) パラメータのエスケープ処理 2020/8/28 27 >>> year1, year2 = 2019, 2020 >>> sql = """ SELECT p.id, p.name, g1.seiseki AS grade1, g2.seiseki AS grade2 FROM person AS p LEFT OUTER JOIN grade AS g1 ON ((p.id = g1.person_id) AND (g1.year = %s)) LEFT OUTER JOIN grade AS g2 ON ((p.id = g2.person_id) AND (g2.year = %s)) """ >>> qs = Person.objects.raw(sql, [year1, year2]) >>> for raw in qs: ... print(f"{p.name}, {p.grade1}, {p.grade2}") 生SQLを使う前に、ORMの使用を考え て! ― 素の SQL 文の実行 より
  28. 28.  動的なSQLを書くには文字列操作が必要  WHEREに条件を追加したい、ORDER BYを変えたい  文字列操作は、SQLインジェクションの原因に!  SQLの方言は吸収されない  SQLはRDBによって方言がある  ORMなら方言の違いを吸収してくれたが... 生SQL実行における課題 2020/8/28 28
  29. 29. (´・ω・)ちょっとだけね 2020/8/28 29
  30. 30. SQLAlchemy はSQLツールキット(クエリビルダ)とORMを搭載した、 Pythonでは最も高機能なライブラリ。 DjangoのORMと比べて...  多くのSQL構文をサポート(CTE/WITH構文など)  ORMマッパーとクエリビルダを個別に定義できる  思い通りのSQLを生成でき、 SQLを知っている人が使いやすい  使い始めは、簡単ではない SQLAlchemy? 2020/8/28 30 参考: • Django Issue #28919 • Sharding with SQLAlchemy | PyCon JP 2017
  31. 31. テーブル定義 (ORMのモデル定義が不要な場合) クエリビルダ実行例 (SQLの文法に近い表現) SQLAlchemyのテーブル定義と実行 2020/8/28 31 import sqlalchemy as sa metadata = sa.MetaData() person = sa.Table( 'person', metadata, sa.Column('id', sa.Integer), sa.Column('name', sa.String), ) >>> stmt = sa.select([person]).where(person.c.name.contains('a')) >>> print(stmt) SELECT person.id, person.name FROM person WHERE (person.name LIKE '%' || :name_1 || '%')
  32. 32. モデル定義 (テーブル定義は自動(個別定義も可能)) クエリ実行例 (一般的なORMの構文に近い表現) SQLAlchemyのモデル定義と実行 2020/8/28 32 import sqlalchemy as sa from sqlalchemy.ext.declarative import declarative_base metadata = sa.MetaData() Base = declarative_base() class Person(Base): __tablename__ = 'users' id = sa.Column(sa.Integer, primary_key=True) name = sa.Column(sa.String) >>> from sqlalchemy.orm import sessionmaker >>> Session = sessionmaker() >>> stmt = session.query(Person).filter(Person.name.contains('a')) >>> print(stmt) SELECT person.id AS person_id, person.name AS person_name FROM person WHERE (person.name LIKE '%' || :name_1 || '%')
  33. 33. (`・ω・)見せて貰おうか、SQLAlchemyの性能とやらを 332020/8/28
  34. 34. Djangoのモデル定義とは別に定義が必要 SQLAlchemyのテーブル定義 2020/8/28 34 import sqlalchemy as sa metadata = sa.MetaData() person = sa.Table( 'person', metadata, sa.Column('id', sa.Integer), sa.Column('name', sa.String), ) grade = sa.Table( 'grade', metadata, sa.Column('id', sa.Integer), sa.Column('person_id', None, sa.ForeignKey('person.id')), sa.Column('year', sa.Integer), sa.Column('seiseki', sa.Integer), )
  35. 35. SQLビルダーの書き味はほぼSQL  ほぼSQLをPythonコードで書けるのが最大のメリット SQLAlchemyのクエリステートメント 2020/8/28 35 import sqlalchemy as sa year1, year2 = 2019, 2020 g1 = grade.alias('g1') # AS g1 g2 = grade.alias('g2') # AS g2 stmt = sa.select([ person.c.id, person.c.name, g1.c.seiseki.label('grade1'), g2.c.seiseki.label('grade2'), ]).select_from( person. outerjoin(g1, sa.and_(g1.c.person_id == person.c.id, g1.c.year == year1)). outerjoin(g2, sa.and_(g2.c.person_id == person.c.id, g2.c.year == year2)) )
  36. 36. sqliteのdialect(方言)に変換  SQLAでの実装コードそのままのSQLが生成される  compile時に接続先RDB用のdialectを選ぶ必要がある SQLAlchemyで生成されるSQL 2020/8/28 36 >>> from sqlalchemy.dialects.sqlite import dialect >>> query = stmt.compile(dialect=dialect()) >>> sql, params = str(query), query.params >>> print(sql) >>> print('params=', params) SELECT person.id, person.name, g1.seiseki AS grade1, g2.seiseki AS grade2 FROM person LEFT OUTER JOIN grade AS g1 ON g1.person_id = person.id AND g1.year = %s LEFT OUTER JOIN grade AS g2 ON g2.person_id = person.id AND g2.year = %s params= [2019, 2020]
  37. 37.  SQLAlchemyからRDBに接続するために独自のコネクションは使いたくない  Djangoの設定と別に管理したくない、コネクションを共存したい Djangoのconnectionを使ってSQLを実行 結果は行毎に値のタプルとなるため、行毎の辞書に変換 SQLAlchemyとDjangoの共存 2020/8/28 37 from django.db import transaction def execute_raw_sql(sql, params): conn = transaction.get_connection() with conn.cursor() as cursor: cursor.execute(sql, params) yield from cursor def execute(stmt): sql, params = ... # compile等 results = execute_raw_sql(sql, params) columns = [c.name for c in stmt.columns] for row in results: yield dict(zip(columns, row))
  38. 38. SQLAlchemyをDjangoに組み込む観点で メリット  SQLを知っている人が使いやすい  文字列操作せずに動的なSQLを組める  多くのSQL構文に対応していて、RDBの機能をフル活用できる デメリット  SQLA用のテーブル定義が必要  RDBにSQLを渡して実行するための実装が増える SQLAlchemyをDjangoに組み込むメリットデメリット 2020/8/28 38
  39. 39. (・ω・`) ほぅ 392020/8/28
  40. 40. Aldjemy は SQLAlchemyをDjangoに組み込むプラグイン メリット  SQLAを使ってクエリを書ける  Django settingsのDB接続情報を使ってくれる  SQLA用のテーブル定義不要  compileもしてくれるため、RDBの種類を気にしなくて良い  RDBにSQLを渡す処理もやってくれる デメリット  RDBコネクションはSQLA独自で接続  Djangoのpoolから生connectionを取得 【追記: 2020/08/28 17:50】  このためDjango Debug Toolbar等でSQLを確認できない Aldjemy? 2020/8/28 40
  41. 41. インストール Django settings.py Django Modelに sa が生えるので、sa経由で実行 .sa 以降はSQLAlchemyのORM Aldjemyの設定 2020/8/28 41 $ pip install aldjemy INSTALLED_APPS = [ ... 'aldjemy', ] >>> Person.sa.query().filter(Person.sa.name.contains('a')).all() [<aldjemy.orm.Person object at 0x0000026D09D49220>, <aldjemy.orm.Person object at 0x0000026D09D49310>]
  42. 42.  実行されるSQLは、aldjemyを使わない場合と同じ  実行結果(戻り値)もSQLAlchemyのORMと同じ Aldjemyでの実装サンプル 2020/8/28 42 import sqlalchemy as sa from app.models import Person, Grade # Djangoのモデル year1, year2 = 2019, 2020 g1 = Grade.sa.table.alias('g1') # SQLAで AS g1 を指定 g2 = Grade.sa.table.alias('g2') # SQLAで AS g2 を指定 stmt = Person.sa.query( Person.sa.id, Person.sa.name, g1.c.seiseki.label('grade1'), g2.c.seiseki.label('grade2'), ).select_from( Person.sa.table. outerjoin(g1, sa.and_(g1.c.person_id == Person.sa.id, g1.c.year == year1)). outerjoin(g2, sa.and_(g2.c.person_id == Person.sa.id, g2.c.year == year2)) ) results = stmt.all()
  43. 43.  RDBコネクションがDjangoと別で管理されるところは気になる  コネクションをそれほど気にしない場合は無視できる  【追記: 2020/08/28 17:50】  connection自体はDjangoから取り出しているため、session数は心配ない  Djangoのconnection wrapperを回避しているためDjangoのサポートは受けられない  Django + SQLAlchemy を小さく始めるのに良い Aldjemy まとめ 2020/8/28 43
  44. 44. (`・ω・´)任せろー バリバリー 442020/8/28
  45. 45. join か outerjoin を明示的に指定 INNER JOIN / LEFT OUTER JOIN 2020/8/28 45 >>> stmt = sa.select([ ... person.c.name, ... grade.c.year, ... grade.c.seiseki, ... ]).select_from( ... person.outerjoin(grade, ... sa.and_(grade.c.person_id == person.c.id, grade.c.year == 2019)) ... ) SELECT person.name, grade.year, grade.seiseki FROM person LEFT OUTER JOIN grade ON grade.person_id = person.id AND grade.year = %s name year seiseki ===== ==== ======= Ham 2019 4 Spam 2019 4 Egg None None
  46. 46. FKがある場合 JOINの明示は必要だが、ON句は不要 FKがない場合 ON句を指定できます FKのあるJOIN, FKのないJOIN 2020/8/28 46 >>> stmt = sa.select([ ... person.c.name, ... grade.c.seiseki, ... ]).select_from( ... person.join(grade) ... ) >>> stmt = sa.select([ ... person.c.name, ... grade.c.seiseki, ... ]).select_from( ... person.join(grade, grade.c.person_id==person.c.id) ... )
  47. 47. セルフJOIN 2020/8/28 47 >>> g1, g2 = grade.alias('g1'), grade.alias('g2') >>> stmt = sa.select([ ... person.c.name, ... g2.c.year, ... g1.c.seiseki.label('last_year_grade'), ... g2.c.seiseki.label('current_year_grade'), ... (g2.c.seiseki - g1.c.seiseki).label('diff'), ... ]).select_from( ... g1.join(person).join(g2, ... sa.and(g1.c.person_id == g2.c.person_id, g1.c.year == g2.c.year + 1)) ... ).order_by(person.c.id, g1.c.year) SELECT person.name, g2.year, g1.seiseki AS last_year_grade, g2.seiseki AS current_year_grade, g2.seiseki - g1.seiseki AS diff FROM grade AS g1 JOIN person ON person.id = g1.person_id JOIN grade AS g2 ON g1.person_id = g2.person_id AND g1.year = g2.year + %s ORDER BY person.id, g1.year
  48. 48. (´・ω・`)おつかれさまでした 482020/8/28
  49. 49.  DjangoのORMを使いこなすには  SQLを書けるようになろう  SQLとORMの関係を覚えよう  SQLからDjango ORMへの翻訳に疲れたら  SQLAlchemyでSQLを組み立てよう  動的に変化しないなら、生SQLを書くのもあり(sql-importerを使うと安全)  SQLAlchemyをDjangoに組み込むには  Aldjemyを使うのが比較的楽  細かく制御が必要になったら、コネクション周りを実装  Django+SQLAlchemy ってシンプルじゃないよね?  (・ω・`)はい  (´・ω・)ORMの書き方で悩むよりはシンプルかと まとめ と 補足 492020/8/28 【訂正】 connection周りでDjangoのサ ポートが必要になった場合は、
  50. 50. Questions? Twitter: @shimizukawa Slack Channel: jp-2020-track3 . HashTag: #pyconjp_3 . 2020/8/28 50
  51. 51. Thanks :) 2020/8/28 51

×