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 congress jp 2019 make query great again! (slide share)

4,449 views

Published on

Django Congress JP 2019 Make Query Great Again!

Published in: Technology
  • Be the first to comment

Django congress jp 2019 make query great again! (slide share)

  1. 1. 開始前スライド:目次
 対象:初心者
 第一部:データを取得しよう!
 第二部:親や子のモデルへのアクセス
 第三部:select_relatedとprefetch_related
 第四部:集計/集約
 第五部:応用編:JsonFieldへのクエリ発行(※PostgreSQL限定)

  2. 2. MAKE QUERY GREAT AGAIN! Django Congress JP 2019 @pro_proretariat
  3. 3. Omae Dare YO? Profile = namedtuple('Profile', ('name', 'age', 'company', 'prefectures', 'twitter', 'features')) Profile(name='Nakajima Yuuki', age=34, company='日本システム技研', prefectures='Nagano', twitter='pro_proletariat', features='20代はC0B0LERでした、Django歴は3年ぐらい' )
  4. 4. 正直この規模の登壇経験がない上に、ネタっぽいタイトルが自分 しかいないので震えてます((((;゚Д゚))))

  5. 5. アジェンダ
 ・話すこと
  ・Djangoでselect文を書く方法
  ・簡単なwhere句から集計・集約まで
 

  6. 6. アジェンダ
 ・話すこと
  ・Djangoでselect文を書く方法
  ・簡単なwhere句から集計・集約まで
 ・話さないこと
  ・SQLの詳しい説明
  ・O/RMの是非

  7. 7. アジェンダ
 ・話すこと
  ・Djangoでselect文を書く方法
  ・簡単なwhere句から集計・集約まで
 ・話さないこと
  ・SQLの詳しい説明
  ・O/RMの是非
 ・話したいけど話さないこと
  ・サブカル最前線
  ・ライブアイドルとその魅力

  8. 8. 今回話そうと思ったきっかけ
 ・Django初心者の人がSQLを直書きしていた
 ・自分自身、よく分からずにググってコピペして乗り切っていた
 ・Django人口が増えてきたので初級的な話しは需要がありそう

  9. 9. 目指すこと
 なるべくSQLを書かない!

  10. 10. 環境
 ● Python3.7
 ● Django2.1.7
 ● PostgresSQL11.0

  11. 11. 目次
 第一部:データを取得しよう! 🍣
 第二部:親や子のモデルへのアクセス
 第三部:select_relatedとprefetch_related
 第四部:集計/集約
 第五部:JsonFieldへのクエリ発行(※PostgreSQL限定)

  12. 12. 大将『お客を増やすためにSNS炎上を繰り返したら大 変なことになってしまったんだ』
 ある日寿司屋にて・・・・

  13. 13. ヤバいお寿司屋さん ウエブサイト 経路案内 保存 1.0 ☆☆☆☆☆ 口コミ(2,147,483,647)
 所在地:ゴッサムシティ路地裏通り 3丁目
  14. 14. 大将『きちんと経営を見直したいからまずお品書きを webで管理したい』

  15. 15. models.py
 class SushiTopping(models.Model): """タコ、サワラ、シメサバなどの具材""" name = models.CharField(max_length=255) class Menu(models.Model): """お品書き""" name = models.CharField(max_length=255, unique=True) price = models.IntegerField(default=0) sushi_toppings = models.ManyToManyField(SushiTopping) class Sale(models.Model): sales_date = models.DateTimeField() menu = models.ForeignKey(Menu, on_delete=models.PROTECT)
  16. 16. 実際のデータベース
 sqlite> .tables auth_group django_content_type auth_group_permissions django_migrations auth_permission django_session auth_user sales_menu auth_user_groups sales_menu_sushi_toppings # 中間テーブル auth_user_user_permissions  sales_sale django_admin_log sales_sushitopping sqlite>
  17. 17. 大将『メニューを全件表示して欲しい』

  18. 18. 全件取得: Model.objects.all()
 Menu.objects.all() SELECT ...(略)... FROM sales_menu; 
 id | name | price 
 ----+----------------------+--------- 
 1 | トロ | 4996 
 2 | サーモン | 4335 
 3 | 炙りサーモン | 7326 
 ...(略)...
 15 | 裏メニュー | 5000000 
 

  19. 19. 大将:「価格が高い順番に並べてよ」

  20. 20. ソート: order_by('field_name') 
 Menu.objects.all().order_by('-price') SELECT ...(略)... FROM sales_menu ORDER BY price DESC; id | name | price | features 
 ----+----------------------+---------+---------- 
 15 | 裏メニュー | 5000000 | 
 8 | アナゴ | 5055 | 
 ...(略)... 
 4 | 炙りシメサバ | 126 | 
 13 | オススメコース | 0 | 

  21. 21. 大将:「商品名で検索したいんだけど」

  22. 22. 絞り込み:filter()
 Menu.objects.filter(name="カリフォルニアロール") SELECT (略) FROM sales_menu WHERE name = 'カリフォルニアロール'; id name price ---------- ------------------------------ ---------- 9 カリフォルニアロール 8744
  23. 23. 大将:「サーモン以外のデータとか出して欲しい んだけど」

  24. 24. NOT条件で絞り込み:exclude()
 Menu.objects.exclude(name="サーモン") SELECT ...(略)... FROM sales_menu WHERE not (name = 'サーモン'); id | name | price ----+----------------------+--------- 3 | 炙りサーモン | 7326 4 | 炙りシメサバ | 126 6 | シメサバ | 1843
  25. 25. 大将:「2000円以上とかイカとか」
 大将:「トロで検索したら大トロも中トロもだして」

  26. 26. Field lookups 
 Field lookups are how you specify the meat of an SQL WHERE clause. https://docs.djangoproject.com/ja/2.2/ref/models/querysets/#field-lookups フィールドルックアップは、SQLのWHERE句の中身を指定 する方法です。
 

  27. 27. where句の条件の指定方法:field_name__[取得方法]=value
 Menu.objects.filter(price__gte=2000) # 2000円以上 -> Greater Than
  28. 28. Like検索: contains
 # 大トロ、中トロ Menu.objects.filter(name__contains="トロ")
  29. 29. その他は公式をチェック
 https://docs.djangoproject.com/en/2.1/ref/models/querysets/#field-lookups https://docs.djangoproject.com/en/2.2/ref/models/querysets/#field-lookups
  30. 30. 大将:「はまち か 2000円以下で検索みたいな ことしたいんだけど」

  31. 31. Qオブジェクト
 from django.db.models import Q q = Q(name="ハマチ") q |= Q(price__lt=2000) # |= でor &= でand Menu.objects.filter(q) SELECT ...(略)... FROM sales_menu WHERE (name = “ハマチ” OR price < 2000);
  32. 32. 補足:Qオブジェクトが力を発揮するのは処理の中
 q = Q(name="サーモン") if is_hungry: q |= Q(name="オススメコース")   Menu.objects.filter(q)
  33. 33. 大将:「色々ありがとう、これでお品書きの管理 ができるよ」

  34. 34. ・全件取得 : Model.objects.all() ・ソート : Model.objects.all().order_by('field_name') ・普通の絞り込み:Model.objects.filter(field_name="value") ・色々な絞り込み:Model.objects.filter(field_name__[取得方法]="value") ・Qオブジェクト: Model.objects.filter(Q(field="value")) 第一部まとめ

  35. 35. 一度休憩
 ● 一度お水を飲んで落ち着け
 ● ペースはあってる?
 ● 早口になってない?

  36. 36. おまけSQLの確認方法 query
 SELECT ...(略)... FROM sales_menu WHERE name 'マグロ'; print(Menu.objects.filter(name="マグロ").query)

  37. 37. 目次
 第一部:データを取得しよう! 🏅
 第二部:親や子のモデルへのアクセス 🍣
 第三部:select_relatedとprefetch_related
 第四部:集計/集約
 第五部:JsonFieldへのクエリ発行(※PostgreSQL限定)

  38. 38. 大将:「売上げの一覧も出してくれよな」

  39. 39. models.py
 class SushiTopping(models.Model): """タコ、サワラ、シメサバなどのトッピング""" name = models.CharField(max_length=255) class Menu(models.Model): """お品書き""" name = models.CharField(max_length=255, unique=True) price = models.IntegerField(default=0) sushi_toppings = models.ManyToManyField(SushiTopping) class Sale(models.Model): sales_date = models.DateTimeField() menu = models.ForeignKey(Menu, on_delete=models.PROTECT)
  40. 40. models.py
 class SushiTopping(models.Model): """タコ、サワラ、シメサバなどのトッピング""" name = models.CharField(max_length=255) class Menu(models.Model): """お品書き""" name = models.CharField(max_length=255, unique=True) price = models.IntegerField(default=0) sushi_toppings = models.ManyToManyField(SushiTopping) class Sale(models.Model): sales_date = models.DateTimeField() menu = models.ForeignKey(Menu, on_delete=models.PROTECT)
  41. 41. 一覧取得はobjects.all()でイナフ
 Sale.objects.all() SELECT ...(略)... FROM sales_sale; id | sales_date | menu_id 
 ----+------------------------+--------- 
 1 | 2019-02-02 06:11:26+00 | 9 
 2 | 2019-05-07 12:26:06+00 | 14 
 3 | 2019-01-03 19:02:25+00 | 3 
 ...(略)...
 899 | 2019-03-16 17:17:54+00 | 2 
 900 | 2019-03-23 11:34:56+00 | 14 
 
 

  42. 42. 大将:「お品書きも出してよ?」

  43. 43. やりたいこと
 id name 1 マグロ id 売上げ日時 menu_id 1 2019/05/16 1 ID 売上げ日時 menu.name 1 2019/05/16 マグロ Menu
Sale

  44. 44. s = Sale.objects.get(id=1) s.menu.name >タマゴヤキ 親モデルの取得:object.[ForeignKeyField]
 models.py class Menu(models.Model): pass class Sale(models.Model): menu = models.ForeignKey(Menu, on_delete=models.PROTECT)
  45. 45. id name 1 マグロ id 売上げ日時 menu_id 1 2019/05/16 1 Menu
 Sale
 ①s = Sale.objects.get(id=1) ①取得
 Django
  46. 46. id name 1 マグロ id 売上げ日時 menu_id 1 2019/05/16 1 Menu
 Sale
 ①s = Sale.objects.get(id=1) ②s.menu.name ①取得
 ②取得
 Django where menu_id=1
  47. 47. ● ドットで感覚的にアクセスできる
 ● 実はJoinしていない
 ● Python側で親のオブジェクトを1件取得している
 親モデルへのアクセスまとめ

  48. 48. 大将:「お品書きから売上げ一覧をだして欲しいな」

  49. 49. やりたいこと:親(menu)から子(Sale)をもとめる
 id name 1 マグロ id 売上げ日時 menu_id 1 2019/05/16 1 2 2019/05/15 2 3 2019/05/19 1 Menu
 Sale
 id 売上げ日時 menu_id 1 2019/05/16 1 2 2019/05/15 2 3 2019/05/19 1
  50. 50. 親から子: .[子のモデル]_set.all()
 m = Menu.objects.get(id=1) m.sale_set.all() SELECT 略 FROM sales_sale WHERE menu_id = 1; id | sales_date | menu_id -----+------------------------+--------- 7 | 2019-02-24 20:53:55+00 | 1 27 | 2019-04-14 06:58:29+00 | 1
  51. 51. m = Menu.objects.get(id=1) m.sale_set.all() 親→子
 子→親
 SQLは どちらも同じ
 Sale.objects.filter(menu_id=1)
  52. 52. 親から子の取り方
 ● 親→子なので複数レコード取得される
 ● まとまり(セット)で取得するから_setとつける
 ● obj.[子のモデル名]_set.all()

  53. 53. まとめ ● 子から親へのアクセス
 ○ 子モデル.ForeignKeyField
 ○ Sale.menu.name
 ● 親から子へのアクセス(複数レコード) 
 ○ 親モデル.子モデル_set.all()
 ○ menu.sale_set.all()

  54. 54. 大将:「メニューに具材の名前もだしてくれよな」

  55. 55. models.py
 class SushiTopping(models.Model): pass class Menu(models.Model): sushi_toppings = models.ManyToManyField(SushiTopping)
  56. 56. ManyToMany(多対多)って何?
 🐟 🦑 🥑 お任せ2貫🐟🦑
 マグロ 🐟
 アボガド 🥑
 寿司トッピング
 お品書き

  57. 57. 多対多:ManyToMany(M2M) 
 🐟 🦑 🥑 お任せ2貫🐟🦑
 マグロ 🐟
 アボガド 🥑
 寿司トッピング
 お品書き
 トッピング お品書き 🐟 🐟 🐟 🐟🦑 中間テーブル

  58. 58. 多対多:ManyToMany(M2M) 
 🐟 🦑 🥑 お任せ2貫🐟🦑
 マグロ 🐟
 アボガド 🥑
 寿司トッピング
 お品書き
 トッピング お品書き 🐟 🐟 🐟 🐟🦑 🦑 🐟🦑
 中間テーブル

  59. 59. 多対多:ManyToMany(M2M) 
 🐟 🦑 🥑 お任せ2貫🐟🦑
 マグロ 🐟
 アボガド 🥑
 寿司トッピング
 お品書き
 トッピング お品書き 🐟 🐟 🐟 🐟🦑 🦑 🐟🦑
 🥑 🥑 中間テーブル

  60. 60. やること:Menuが持つ寿司トッピングを取得
 id 品書き 2 🐟🦑お任せ2貫 トッピング お品書き 🐟 🐟🦑お任せ2貫 🦑 🐟🦑お任せ2貫
 Menu
 中間テーブル
 id Menu トッピング 2 🐟🦑お任せ2貫 🐟 2 🐟🦑お任せ2貫 🦑
  61. 61. ManyToMany(M2M)のモデル取得:M2M_field.all()
 Menu.objects.get(id=11).sushi_toppings.all() SELECT ...(略)... FROM sales_sushitopping INNER JOIN sales_menu_sushi_toppings ON (...(略)...) WHERE menu_id = 11; models.py class Menu(models.Model): sushi_toppings = models.ManyToManyField(SushiTopping)
  62. 62. トッピングが持つMenuを取得
 id トッピング 1 🐟 トッピング お品書き 🐟 🐟🦑お任せ2貫 🦑 🐟🦑お任せ2貫
 トッピング
 中間テーブル
 id トッピング お品書き 1 🐟 🐟 1 🐟 🐟🦑お任せ2貫
  63. 63. SushiTopping.objects.get(id=1).menu_set.all() SELECT ...(略)... FROM sales_menu INNER JOIN sales_menu_sushi_toppings ON (...(略)...) WHERE menu_sushi_toppings.sushitopping_id = 1; M2Mを逆から参照:関連しているテーブル名_set.all()

  64. 64. ManyToManyまとめ
 ● ManyToMany(M2M)は中間テーブルが作られる
 ● M2Mはどちら側からも多で取得
 ● 仕組みが分かれば怖くない!

  65. 65. 大将:「サーモンの売上げだけだしてくれよ」

  66. 66. やりたいこと:親モデルの項目で絞り込み
 id name 2 🐟サーモン 売上げ日
 お品書き_id
 2019/05/16
 2
 2019/05/18
 2
 2019/05/17
 3
 menu
 売上げ日 お品書き 2019/05/16 2 2019/05/18 2
 Sale

  67. 67. Sale.objects.filter(menu__name='サーモン') 親子関係の別モデルで絞り込み:FK__field_name=value
 →アンダースコア2つで辿れる
 SELECT ...(略)... FROM "sales_sale" INNER JOIN "sales_menu" ON (略) WHERE sales_menu.name = 'サーモン'; id | sales_date | menu_id | id | name | price | -----+------------------------+---------+----+----------+-------+- 17 | 2019-03-28 06:33:02+00 | 2 | 2 | サーモン | 4335 | 318 | 2019-01-20 16:46:40+00 | 2 | 2 | サーモン | 4335 |
  68. 68. おさらい ● 親子関係
 ○ 子 → 親 model.ForeignKey 
 ○ 親 → 子 model.親のモデル名_set.all()
 ● ManyToMany(M2M)
 ○ 宣言してる方 → してない方: model.ManyToManyField.all()
 ○ 宣言してない方 → してる方: model.相手のモデル名_set.all()
 ● 別モデルの項目で絞り込み
 ○ [親子モデル]__[field_name]=value 
 ○ Sale.objects.filter(menu__name= 'サーモン')

  69. 69. 大将:「これで売上げの管理も完璧だな」

  70. 70. 目次
 第一部:データを取得しよう! 🏅
 第二部:親や子のモデルへのアクセス 🏅
 第三部:select_relatedとprefetch_related 🍣
 第四部:集計/集約
 第五部:JsonFieldへのクエリ発行(※PostgreSQL限定)

  71. 71. 大将:「売上げの一覧が重すぎる」

  72. 72. for s in Sale.objects.all(): s.menu.name クエリを確認

  73. 73. うわわあああああああああああ
 (0.014) SELECT "sales_sale"."id", "sales_sale"."sales_date", "sales_sale"."menu_id" FROM "sales_sale"; 
 (0.002) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 9; 
 (0.002) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 14; 
 (0.002) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 3; 
 (0.002) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 7; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 8; 
 (0.002) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 3; 
 (0.002) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 1; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 14; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 14; 
 (0.010) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 13; 
 (1.009) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 8; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 13; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 7; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 10; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 11; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 13; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 2; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 5; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 11; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 8; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 11; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 3; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 10; 
 (0.002) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 13; 
 (0.001) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 7; 
 (0.002) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu" WHERE "sales_menu"."id" = 11; 

  74. 74. ● ドットで感覚的にアクセスできる
 ● 実はJoinしていない
 ● Python側で親のオブジェクトを1件取得している
 
 親へのアクセス時
 思い出してみましょう

  75. 75. for s in Sale.objects.select_related("menu").all(): s.menu.name select_related:
 SELECT …(略) … FROM sales_sale INNER JOIN sales_menu ON (sales_sale.menu_id = sales_menu.id);
  76. 76. 大将:「類似確認してる?お品書きのトッピングが出すとこ ろも無駄なクエリがでてなイカ?」

  77. 77. for m in Menu.objects.all(): for st in m.sushi_toppings.all(): st.name (0.003) SELECT "sales_menu"."id", "sales_menu"."name", "sales_menu"."price", "sales_menu"."features" FROM "sales_menu"; (0.003) SELECT "sales_sushitopping"."id", "sales_sushitopping"."name" FROM "sales_sushitopping" INNER JOIN "sales_menu_sushi_toppings" ON ("sales_sushitopping"."id" = "sales_menu_sushi_toppings"."sushitopping_id") WHERE "sales_menu_sushi_toppings"."menu_id" = 1; (0.004) SELECT "sales_sushitopping"."id", "sales_sushitopping"."name" FROM "sales_sushitopping" INNER JOIN "sales_menu_sushi_toppings" ON ("sales_sushitopping"."id" = "sales_menu_sushi_toppings"."sushitopping_id") WHERE "sales_menu_sushi_toppings"."menu_id" = 2; (0.003) SELECT "sales_sushitopping"."id", "sales_sushitopping"."name" FROM "sales_sushitopping" INNER JOIN "sales_menu_sushi_toppings" ON ("sales_sushitopping"."id" = "sales_menu_sushi_toppings"."sushitopping_id") WHERE "sales_menu_sushi_toppings"."menu_id" = 3; (0.002) SELECT "sales_sushitopping"."id", "sales_sushitopping"."name" FROM "sales_sushitopping" INNER JOIN "sales_menu_sushi_toppings" ON ("sales_sushitopping"."id" = "sales_menu_sushi_toppings"."sushitopping_id") WHERE "sales_menu_sushi_toppings"."menu_id" = 4; (0.002) SELECT "sales_sushitopping"."id", "sales_sushitopping"."name" FROM "sales_sushitopping" INNER JOIN "sales_menu_sushi_toppings" ON ("sales_sushitopping"."id" = "sales_menu_sushi_toppings"."sushitopping_id") WHERE "sales_menu_sushi_toppings"."menu_id" = 5; (0.002) SELECT "sales_sushitopping"."id", "sales_sushitopping"."name" FROM "sales_sushitopping" INNER JOIN "sales_menu_sushi_toppings" ON ("sales_sushitopping"."id" = "sales_menu_sushi_toppings"."sushitopping_id") WHERE "sales_menu_sushi_toppings"."menu_id" = 6; やっぱり!

  78. 78. 浅はかな僕は何も考えず同じ事をする
 for m in Menu.objects.select_related(‘sushi_toppings’).all(): for st in m.sushi_toppings.all(): st.name django.core.exceptions.FieldError: Invalid field name(s) given in select_related: 'sushi_toppings'. Choices are: (none)
  79. 79. 回答:prefetch_related
 prefetch_related は、各リレーションシップに対して別々に検索を行い、Python で '結合’ を行います。これにより、select_related で可能な外部キーおよび一対一のリレーション シップだけでなく、多対多および多対一のオブジェクトも事前に読み込んでおけるように なります。
 https://docs.djangoproject.com/ja/2.2/ref/models/querysets/#prefetch-related
  80. 80. for m in Menu.objects.prefetch_related('sushi_toppings').all(): for st in m.sushi_toppings.all(): st.name SQLは2本になった!
 SELECT ...(略)... FROM "sales_menu"; SELECT ...(略)...(sales_menu_sushi_toppings.menu_id) AS _prefetch_related_val_menu_id, ON ( "sales_sushitopping"."id" = "sales_menu_sushi_toppings"."sushitopping_id" ) WHERE sales_menu_sushi_toppings.menu_id IN(1, ...(略)... 15);
  81. 81. 取得結果
 _prefetch_related_val_menu_id | id | name -------------------------------+----+------------------ 14 | 8 | アナゴ 14 | 1 | トロ 14 | 2 | サーモン 2 | 2 | サーモン 多対多の中間テーブル
 id | name | price ----+-----------------+--------- 2 | サーモン | 4335 14 | 王道3貫 | 2000 15 | 裏メニュー | 5000000 Menu

  82. 82. 取得結果
 _prefetch_related_val_menu_id | id | name -------------------------------+----+------------------ 14 | 8 | アナゴ 14 | 1 | トロ 14 | 2 | サーモン 2 | 2 | サーモン 多対多の中間テーブル
 id | name | price ----+-----------------+--------- 2 | サーモン | 4335 14 | 王道3貫 | 2000 15 | 裏メニュー | 5000000 Menu

  83. 83. 取得結果
 _prefetch_related_val_menu_id | id | name -------------------------------+----+------------------ 14 | 8 | アナゴ 14 | 1 | トロ 14 | 2 | サーモン 2 | 2 | サーモン 多対多の中間テーブル
 id | name | price ----+-----------------+--------- 2 | サーモン | 4335 14 | 王道3貫 | 2000 15 | 裏メニュー | 5000000 Menu
 ここの取得はSQLではなく Pythonがおこなっている! 

  84. 84. prefetch_relatedとは ● 多対多の相手のモデルをprefetch
 ● 実際にはPythonが複数のクエリの結果を結合している
 ● SQLだけでは完結していない
 ● その辺を意識しなくても利用することができる

  85. 85. まとめ
 ・世界は残酷なのでSQLがいっぱい吐かれていてもそれが見えない
 
 ・select_related:SQLの世界で対1(親など)のオブジェクトをJoin
 ・prefetch_related: 別々のクエリを先に取得してPythonが結合
 
 ・裏側がどう動いていても感覚的にデータの取得は出来る!

  86. 86. 大将:「良い感じになったね」

  87. 87. 休憩
 今日の正装についての簡単な説明をします。(今日一番言いたいこと)

  88. 88.   ∧_∧   / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 
  ( ・∀・)<常に向上するって意味でマーベルの人も使っているとても良い言葉なんだ! 
  ( 建前 )  \_______________ 
  | | |
 __(__)_)______________ 
  ( _)_)
  | | |
  ( 本音 )   / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ 
  ( 。A。)< 某ライブアイドルグループの最新アルバムだよ!みんな聴いてね! 
   ∨ ̄∨     \_______________ 
 エクセルシオールとは

  89. 89. 目次
 第一部:データを取得しよう! 🏅
 第二部:親や子のモデルへのアクセス 🏅
 第三部:select_relatedとprefetch_related 🏅
 第四部:集計/集約 🍣
 第五部:JsonFieldへのクエリ発行(※PostgreSQL限定)

  90. 90. 大将:「総売上っていくらなの?」

  91. 91. Aggregate:指定した項目で集約する
 Sale.objects.all().aggregate(Sum("menu__price")) SELECT SUM(sales_menu.price) AS menu__price__sum FROM sales_sale INNER JOIN sales_menu ON (sales_sale.menu_id = sales_menu.id); menu__price__sum ------------------ 3471964
  92. 92. Aggregate(集計)のイメージ 売上げ日 お品書き 価格 昨日 カッパ巻き 1000 一昨日 コース 1200 3日前 かんぴょう巻き 2000 総売上 4200 Sale.objects.all().aggregate(Sum("menu__price"))
  93. 93. 大将:「お品書きが売れた回数知りたい」

  94. 94. Djangoの集約に対する一般人僕の認識
 はっきり言って分かりにくい Django初心者が真っ先に躓くポイント、今でもよく分かりません。 SQLを書いた方がまし O/RMが叩かれる原因 主人がO/Rマッパーに転がされて 1年が過ぎました ハム エッグ スパム
  95. 95. もうO/RMに疲れたよ・・・・
 class Menu(models.Model): ・・・ @property def sales_count(self): return self.sale_set.all().count() m = Menu.objects.get(id=1) m.sales_count # ->9999
  96. 96. 大将:「SQLの世界でなんとかしてよ」

  97. 97. Annotate:オブジェクト単位に集約
 from django.db.models import Count Menu.objects.annotate(Count('sale')) id | name | price | features | sale__count ----+----------------------+---------+----------+------------- 11 | カリフォルニアロール | 4844 | | 61 9 | タマゴヤキ | 4414 | | 69 15 | 裏メニュー | 5000000 | | 0 3 | 炙りサーモン | 7326 | | 66 5 | マグロ | 3953 | | 78
  98. 98. SQLを見てみると・・・
 SELECT COUNT("sales_sale"."id") AS "sale__count" FROM sales_menu LEFT OUTER JOIN sales_sale ON (sales_menu.id = sales_sale.menu_id) GROUP BY sales_menu.id, sales_menu.name, sales_menu.price; Menu.objects.annotate(Count('sale'))
  99. 99. SQLを見てみると・・・
 SELECT COUNT("sales_sale"."id") AS "sale__count" FROM sales_menu LEFT OUTER JOIN sales_sale ON (sales_menu.id = sales_sale.menu_id) GROUP BY sales_menu.id, sales_menu.name, sales_menu.price; Menu.objects.annotate(Count('sale')) ←子のSaleモデルのIDの数を数える 
 ←Menu単位にグルーピングする 

  100. 100. Menu単位にgroup_byってどこでわかる?

  101. 101. よく分からないので公式を見ます
 Per-object summaries can be generated using the annotate() clause. When an annotate() clause is specified, each object in the QuerySet will be annotated with the specified values. The syntax for these annotations is identical to that used for the aggregate() clause. Each argument to annotate() describes an aggregate that is to be calculated. https://docs.djangoproject.com/ja/2.2/topics/db/aggregation/#generating-aggregat es-for-each-item-in-a-queryset
  102. 102. よく分からないのでGoogle先生に翻訳してもらいました
 annotate()句を使用して、オブジェクトごとのサマリーを生成できます。 annotate() 句が指定されると、QuerySet内の各オブジェクトは指定された値で注釈が付けら れます。 これらの注釈の構文は、aggregate()句に使用されているものと同じです。 annotate()の各引数は、計算される集計を表します。 https://docs.djangoproject.com/ja/2.2/topics/db/aggregation/#generating-aggregat es-for-each-item-in-a-queryset
  103. 103. ん?
 annotate()句を使用して、オブジェクトごとのサマリーを生成できます。 
 https://docs.djangoproject.com/ja/2.2/topics/db/aggregation/#generating-aggregat es-for-each-item-in-a-queryset
  104. 104. つまりは
 オブジェクト毎というのはMenuのレコード毎か!
 Menu.objects.annotate(Count('sale'))
  105. 105. (再掲)Aggregate(集計)のイメージ 売上げ日 お品書き 価格 昨日 カッパ巻き 1000 一昨日 コース 1200 3日前 かんぴょう巻き 2000 総売上 4200 Sale.objects.all().aggregate(Sum("menu__price"))
  106. 106. Annotate(注釈)のイメージ 売上げ 日 お品書き 価格 sale__count 昨日 カッパ巻き 1000 100 一昨日 コース 1200 111 3日前 かんぴょう巻 き 2000 120 Sale.objects.all().aggregate(Sum("menu__price"))
  107. 107. Annotate(注釈)のイメージ 売上げ日 お品書き 価格 sale__count 昨日 カッパ巻き 1000 100 一昨日 コース 1200 111 3日前 かんぴょう 巻き 2000 120 Sale.objects.all().aggregate(Sum("menu__price")) レコードの枚数はそのままで、CountやSUMといった注釈情報を加えることが出来 る!

  108. 108. 分かった!(気がする)

  109. 109. じゃあ違う単位でAnnotateするには?
  110. 110. 集約の単位を変える: values().annotate() Sale.objects.all().values("menu").annotate(Count('menu')) SELECT sales_sale.menu_id, COUNT(sales_sale.menu_id) AS menu__count FROM sales_sale GROUP BY sales_sale.menu_id; menu_id | menu__count ---------+------------- 4 | 69
  111. 111. まとめ
 ● Aggregate
 ○ クエリセット全体の集計を取る、結果は1つになる
 ○ Sale.objects.all().aggregate(Sum("menu__price")) 
 ○ 総額だけ取れる
 ● Annotate
 ○ オブジェクト単位で集約結果などの”注釈情報”を付与する
 ○ Menu.objects.annotate(Count('sale')) 
 ○ Menu毎に売上げ回数がセットされる

  112. 112. 大将:「60回以上売れたお品書きだけだ してよ」

  113. 113. 注釈のFieldで絞り込み:filter(XX=XX)
 Menu.objects.annotate(Count('sale')).filter(sale__count__gte=60) SELECT COUNT(sales_sale.id) AS sale__count FROM sales_menu LEFT OUTER JOIN sales_sale ON (sales_menu.id = sales_sale.menu_id) GROUP BY sales_menu.id HAVING COUNT(sales_sale.id) >= 60; id | name | price | features | sale__count ----+----------------------+-------+----------+------------- 11 | カリフォルニアロール | 4844 | | 61 9 | タマゴヤキ | 4414 | | 69
  114. 114. すごい直感的!

  115. 115. まとめ
 ● Having
 ○ Annotateで取得したエリアでfilterすればよい
 ○ Menu.objects.annotate(Count('sale')).filter(sale__count__gte=60)
 ○ 60回以上売れたMenuだけとれる

  116. 116. 大将:「集計も集約も完璧じゃなイ カ・・」

  117. 117. winodow関数について
 表題にはwindow関数までと書きましたが時間がないので割愛します。。
 やり方は公式を見れば分かると思います :bow:
 
 https://docs.djangoproject.com/en/2.2/ref/models/expressions/#window-functions

  118. 118. 休憩:Django ShellでいちいちModelのimportだるい
 shell_plusってライブラリを入れるとDjango shellがすごく使いやすくなるのでオススメ! PycharmのPythonConcsoleで動かしたいときは拙ブログにやり方が載ってるよ! http://farewell-work.hatenablog.com/entry/2019/04/09/184141
  119. 119. 休憩:Django ShellでModelのimportつらい
 shell_plusというライブラリ
 ● モデルのインポート
 ● settingsのインポート
 等々をやってくれるのでオススメ!
 PycharmのPythonConsoleで動かしたいときは拙ブログにやり方が載ってるよ!
 http://farewell-work.hatenablog.com/entry/2019/04/09/184141

  120. 120. 目次
 第一部:データを取得しよう! 🏅
 第二部:親や子のモデルへのアクセス 🏅
 第三部:select_relatedとprefetch_related 🏅
 第四部:集計/集約 🏅
 第五部:JsonFieldへのクエリ発行(※PostgreSQL限定) 🍣

  121. 121. 大将:「お品書きに味:うまい , とてもうまい,  セール:賞味期限切れセール実施中! みた いに好きなこと入れたいんだけど味に限らずど んな項目でも自由に入れられるようにして欲し い」 Jsonフィールドへのクエリ発行

  122. 122. models.py
 from django.contrib.postgres.fields import JSONField class Menu(models.Model): ... features = JSONField(blank=True, null=True)
  123. 123. レコードにはJsonが入る
 id | name | price | features ----+------+-------+----------------------- 1 | トロ | 3063 | {"taste": "うまい!"}
  124. 124. JSONフィールドの取り方:__key="value"
 Menu.objects.filter(features__taste="うまい!") SELECT …(略)... FROM sales_menu WHERE (sales_menu.features - > 'teste') = '"u3046u307eu3044uff01"'; id | name | price | features ----+------+-------+----------------------- 1 | トロ | 3063 | {"taste": "うまい!"}
  125. 125. 大将:「これが最後の依頼だ自由入力の値も一 部引っかかった値での検索もよろしく」

  126. 126. 敗北宣言:SQLを書かざるを得なかった・・・
 with connection.cursor() as cursor: sql = "select * from sales_menu where features ->> %s like %s" cursor.execute(sql, ["taste", "%うまい%"]) results = cursor.fetchall()
  127. 127. まとめ
 ● Jsonフィールドに対してもクエリ発行できる
 ○ Model.objects.filter(json_field__key=value) 
 ○ like検索はできなかった・・

  128. 128. 大将:「色々ありがとう!おかげでお店の評価 も上がりそうだ」

  129. 129. 総まとめ
 ● 第一部:データを取得しよう! 🏅 
 ○ filterやexcludeで簡単にwhere句が使える 
 ● 第二部:親や子のモデルへのアクセス 🏅
 ○ _setやドットで簡単に親子どちら側からもアクセスできる! 
 ● 第三部:select_relatedとprefetch_related 🏅
 ○ これを指定してあげればパフォーマンスが一挙に改善するかも! 
 ● 第四部:集計/集約 🏅
 ○ Annotateについてなんか分かった気がする! 
 ● 第五部:JsonFieldへのクエリ発行(※PostgreSQL限定) 🏅
 ○ like検索は出来なかったけど普通に取得は出来た!  

  130. 130. 感想 ● Djangoのクエリは結構感覚的!
 ● 逆に感覚的すぎてよくわからない
 ● でも深追いすると結構分かった気がする
 ● 今回は登壇することで勉強になった

  131. 131. 🍣 Excelsior! 常に向上を!


×