Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Как Django и Alchemy (не) справляются со сложным SQL

Как Django и Alchemy (не) справляются со сложным SQL

Михаил Попугин (S7, Python-разработчик) @ Moscow Python №77

"Иногда проект перерастает Django ORM, и в игру входит SQLAlchemy. Расскажу, как каждый из них справляется (или нет) с нашей сложной бизнес-логикой. Ещё немного о том, почему мы выбрали SQLAlchemy, а что всё-таки можно было сделать, не выходя из Django".

Видео: https://moscowpython.ru/meetup/77/django-alchemy/

Moscow Python Meetup

June 09, 2022
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. 1 Михаил Попугин × S7 Techlab как Django ORM и

    SQLAlchemy (не) справляются со сложным SQL
  2. обо мне ✓ работал в S7, писал на Python ➜

    делал бэкенд в ML-продукте 5
  3. обо мне ✓ работал в S7, писал на Python ✓

    делал бэкенд в ML-продукте ➜ написал SQL-запрос 😎 6
  4. наш сложный SQL 8 with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) matching_rules as ( select * from rule, phase, flights where rule.origin = flights.origin and rule.destination = flights.destination and ( rule.flight_number = flights.flight_number or rule.flight_number is null ) ) prioritized_rules as ( select *, row_number() over ( partition by {{ pb_clause }} order by {{ ob_clause_1 }} nulls last, {{ ob_clause_2 }} nulls last, ... ) as priority from matching_rules ) select * from prioritized_rules where prioritized_rules.priority = 1;
  5. наш сложный SQL 9 with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) matching_rules as ( select * from rule, phase, flights where rule.origin = flights.origin and rule.destination = flights.destination and ( rule.flight_number = flights.flight_number or rule.flight_number is null ) ) prioritized_rules as ( select *, row_number() over ( partition by {{ pb_clause }} order by {{ ob_clause_1 }} nulls last, {{ ob_clause_2 }} nulls last, ... ) as priority from matching_rules ) select * from prioritized_rules where prioritized_rules.priority = 1; всё это — части одного запроса
  6. наш сложный SQL 10 with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) matching_rules as ( select * from rule, phase, flights where rule.origin = flights.origin and rule.destination = flights.destination and ( rule.flight_number = flights.flight_number or rule.flight_number is null ) ) prioritized_rules as ( select *, row_number() over ( partition by {{ pb_clause }} order by {{ ob_clause_1 }} nulls last, {{ ob_clause_2 }} nulls last, ... ) as priority from matching_rules ) select * from prioritized_rules where prioritized_rules.priority = 1;
  7. наш сложный SQL 11 with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) matching_rules as ( select * from rule, phase, flights where rule.origin = flights.origin and rule.destination = flights.destination and ( rule.flight_number = flights.flight_number or rule.flight_number is null ) ) prioritized_rules as ( select *, row_number() over ( partition by {{ pb_clause }} order by {{ ob_clause_1 }} nulls last, {{ ob_clause_2 }} nulls last, ... ) as priority from matching_rules ) select * from prioritized_rules where prioritized_rules.priority = 1; а это — CTE
  8. что такое CTE with flights as ( ... ), matching_rules

    as ( ... ), prioritized_rules as ( ... ) select * from prioritized_rules where prioritized_rules.priority = 1; 13
  9. что такое CTE with flights as ( ... ), matching_rules

    as ( ... ), prioritized_rules as ( ... ) select * from prioritized_rules where prioritized_rules.priority = 1; 14
  10. with flights as ( ... ), matching_rules as ( ...

    ), prioritized_rules as ( ... ) select * from prioritized_rules where prioritized_rules.priority = 1; что такое CTE 15 -- common table expressions
  11. with flights as ( ... ), matching_rules as ( ...

    ), prioritized_rules as ( ... ) select * from prioritized_rules where prioritized_rules.priority = 1; что такое CTE 16
  12. CTE упрощают жизнь select * from ( ... ) as

    prioritized_rules 19 with prioritized_rules as ( ... ) select * from prioritized_rules
  13. CTE упрощают жизнь select * from ( ... /* <--

    */ ) as prioritized_rules 20 with prioritized_rules as ( ... /* <-- */ ) select * from prioritized_rules
  14. CTE упрощают жизнь select * from ( ... ) as

    prioritized_rules 21 with prioritized_rules as ( ... ) select * from prioritized_rules
  15. CTE упрощают жизнь 30 select * from ( select *

    from ( select * from ( select 1 ) as t_1 ) as t_2 ) as t_3
  16. CTE упрощают жизнь 31 select * from ( select *

    from ( select * from ( select 1 ) as t_1 ) as t_2 ) as t_3 select 1
  17. CTE упрощают жизнь 32 select * from ( select *

    from ( select * from ( select 1 ) as t_1 ) as t_2 ) as t_3 select 1
  18. CTE упрощают жизнь 33 select * from ( select *

    from ( select * from ( select 1 ) as t_1 ) as t_2 ) as t_3 with data as ( select 1 ), t_1 as (select * from data) select * from t_1;
  19. CTE упрощают жизнь 34 select * from ( select *

    from ( select * from ( select 1 ) as t_1 ) as t_2 ) as t_3 with data as ( select 1 ), t_1 as (select * from data), t_2 as (select * from t_1) select * from t_1;
  20. CTE упрощают жизнь 35 select * from ( select *

    from ( select * from ( select 1 ) as t_1 ) as t_2 ) as t_3 with data as ( select 1 ), t_1 as (select * from data), t_2 as (select * from t_1), t_3 as (select * from t_2) select * from t_1;
  21. наша CTE с данными with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) 37
  22. наша CTE с данными with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) 38 origin destination flight_number OVB VVO 5201 OVB VVO 5202
  23. наша CTE с данными with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) 39 origin destination flight_number OVB VVO 5201 OVB VVO 5202
  24. select without from — SWF 43 select 'OVB' as origin,

    'VVO' as destination origin destination OVB VVO
  25. select without from — SWF 44 select 'OVB' as origin,

    'VVO' as destination, 5201 as flight_number origin destination flight_number OVB VVO 5201
  26. select without from — SWF 45 select 'OVB' as origin,

    'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number origin destination flight_number OVB VVO 5201 OVB VVO 5202
  27. select without from — SWF 46 select 'OVB' as origin,

    'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number origin destination flight_number OVB VVO 5201 OVB VVO 5202
  28. select without from — SWF 47 select 'OVB' as origin,

    'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number origin destination flight_number OVB VVO 5201 OVB VVO 5202
  29. SWF удобно читать create temp table flights as select *

    from ( values (5201) ) as t (flight_number); 49
  30. SWF удобно читать create temp table flights as select *

    from ( values (5201) ) as t (flight_number); 50 select 5201 as flight_number
  31. SWF удобно читать create temp table flights as select *

    from ( values (5201) ) as t (flight_number); 51 select 5201 as flight_number
  32. SWF удобно читать create temp table flights as select *

    from ( values (5201) ) as t (flight_number); 52 select 5201 as flight_number
  33. ещё раз, всё вместе with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) 54
  34. ещё раз, всё вместе with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) 55
  35. ещё раз, всё вместе with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) 56 -- cte
  36. ещё раз, всё вместе with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) 57
  37. ещё раз, всё вместе with flights as ( select 'OVB'

    as origin, 'VVO' as destination, 5201 as flight_number union all select 'OVB' as origin, 'VVO' as destination, 5202 as flight_number ) 58 -- swf -- swf
  38. зачем нам ORM ✓ можно не собирать SQL руками ✓

    защита от наивных ошибок ➜ аккуратная работа с типами 62
  39. зачем нам ORM ✓ можно не собирать SQL руками ✓

    защита от наивных ошибок ✓ аккуратная работа с типами ➜ переиспользование моделей (в планах) 63
  40. зачем нам ORM ✓ можно не собирать SQL руками ✓

    защита от наивных ошибок ✓ аккуратная работа с типами ✓ переиспользование моделей (в планах) ➜ миграции (тоже в планах) 64
  41. SWF × SQLAlchemy 70 import sqlalchemy as sa sa.select([ sa.literal(5201).label(

    'flight_number' ) ]) select 5201 as flight_number хорошо
  42. SWF × Django ORM 73 from django.db import ( connections

    ) conn = connections['rules'] swf_query = ... with conn.cursor() as cursor: cursor.execute(swf_query) data = cursor.fetchall() select 5201 as flight_number
  43. SWF × Django ORM 74 from django.db import ( connections

    ) conn = connections['rules'] swf_query = ... with conn.cursor() as cursor: cursor.execute(swf_query) data = cursor.fetchall() select 5201 as flight_number
  44. SWF × Django ORM 75 from django.db import ( connections

    ) conn = connections['rules'] swf_query = ... with conn.cursor() as cursor: cursor.execute(swf_query) data = cursor.fetchall() select 5201 as flight_number плохо
  45. CTE × SQLAlchemy with flights as ( ... ) select

    * from flights; 78 import sqlalchemy as sa ... flights = ... flights_cte = \ flights.cte( name='flights' ) session.query(flights_cte)
  46. CTE × SQLAlchemy with flights as ( ... /* <--

    */ ) select * from flights; 79 import sqlalchemy as sa ... flights = ... flights_cte = \ flights.cte( name='flights' ) session.query(flights_cte)
  47. CTE × SQLAlchemy with flights as ( ... /* <--

    */ ) select * from flights; 80 import sqlalchemy as sa ... flights = ... flights_cte = \ flights.cte( name='flights' ) session.query(flights_cte)
  48. CTE × SQLAlchemy with flights as ( ... /* <--

    */ ) select * from flights; 81 import sqlalchemy as sa ... flights = ... flights_cte = \ flights.cte( name='flights' ) session.query(flights_cte) хорошо
  49. 83 with flights as ( ... ) select * from

    flights; CTE × Django ORM
  50. 84 from django.db import ( connections ) conn = connections['rules']

    cte_query = ... with conn.cursor() as cursor: cursor.execute(cte_query) data = cursor.fetchall() with flights as ( ... ) select * from flights; CTE × Django ORM
  51. CTE × Django ORM 85 from django.db import ( connections

    ) conn = connections['rules'] cte_query = ... with conn.cursor() as cursor: cursor.execute(cte_query) data = cursor.fetchall() with flights as ( ... ) select * from flights;
  52. CTE × Django ORM 86 from django.db import ( connections

    ) conn = connections['rules'] cte_query = ... with conn.cursor() as cursor: cursor.execute(cte_query) data = cursor.fetchall() with flights as ( ... ) select * from flights; плохо
  53. reflection import sqlalchemy as sa engine = \ sa.create_engine('...') metadata

    = sa.MetaData(engine) Rule = sa.Table( 'rule', metadata, autoload=True, autoload_with=engine ) 88 бонус
  54. reflection import sqlalchemy as sa engine = \ sa.create_engine('...') metadata

    = sa.MetaData(engine) Rule = sa.Table( 'rule', metadata, autoload=True, autoload_with=engine ) 89 бонус
  55. reflection import sqlalchemy as sa engine = \ sa.create_engine('...') metadata

    = sa.MetaData(engine) Rule = sa.Table( 'rule', metadata, autoload=True, autoload_with=engine ) 90 бонус
  56. reflection import sqlalchemy as sa engine = \ sa.create_engine('...') metadata

    = sa.MetaData(engine) Rule = sa.Table( 'rule', metadata, autoload=True, autoload_with=engine ) 91 python manage.py \ inspectdb \ --database rules \ > models.py бонус
  57. выводы ✓ сложный SQL — реальность ✓ на SQLAlchemy можно

    начать ➜ на ней удобно продолжить 95
  58. выводы ✓ сложный SQL — реальность ✓ на SQLAlchemy можно

    начать ✓ на ней удобно продолжить ➜ Django ORM часто не тянет 😒 96
  59. выводы ✓ сложный SQL — реальность ✓ на SQLAlchemy можно

    начать ✓ на ней удобно продолжить ✓ Django ORM часто не тянет 😒 ➜ выбор очевиден 97