19 ms·
Python: Just Write SQL
- ckdot 3y agoCongrats, you just wrote your own ORM. Please mind that ORM doesn’t necessarily mean ActiveRecord, which could be considered an anti pattern.
- extasia 3y agoWhat's Active Record and why is it an anti pattern?
- tantalor 3y agohttps://guides.rubyonrails.org/active_record_basics.html https://guides.rubyonrails.org/active_record_basics.html
- flow2kudo 3y agoActive Record is NOT anti pattern. It's just one of ways to get things done related to (relational) database and your application. Active Record, Data Mapper, Raw SQL or whetever has its pros and cons.
- iamflimflam1 3y agoThe active record pattern is an approach to accessing data in a database. A database table or view is wrapped into a class. Thus, an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save. Any object loaded gets its information from the database. When an object is updated, the corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for each column in the table or view. https://en.wikipedia.org/wiki/Active_record_pattern https://en.wikipedia.org/wiki/Active_record_pattern It was fashionable for a while to say it was an anti-pattern because that was a contrary view and ActiveRecord is very tied into building Rails applications.
- robertlagrant 3y agoIt's not about fashion. Observations about fashion are no deeper than fashion itself. It scales badly with table size, I think by design. That's why SQLAlchemy's and Hibernate's Data Mapper pattern is slightly more cumbersome to write, but works out much better.
- ckdot2 3y agoIt's a pattern where a single object (the "active record") not only represents a single database row, but also usually is responsible for saving/inserting data into the database (via save method) and retrieving them (via find methods). Because of this it breaks SRP. If this is neglectable or not, I don't want to argue here. Personally, I would not use it anymore because of bad experience in the past.
- khazhoux 3y agoHe didn't really. The SQL is right there, and this is important. What I've experienced (unfortunately) across multiple projects is that people who understand databases will write SQL with a nice collection of helper and wrapper functions as needed, and the people that think that databases are mysterious black boxes will reach for ORM. I've seen the ORM-happy teams getting scared at the idea of a million (1,000,000!) rows in a table, and they always neglect to set up even basic indexes or to think through what their JOINs are really doing. YMMV but that's the pattern I see again and again.
- ckdot2 3y agoWell, the SQL is always somewhere. If you use an ORM library, even if you use ActiveRecord, you will find some SQL in it. In the end, it always translates to SQL. In the blogpost, the writer created a User Python object ("O"). A corresponding (R) database row will be mapped (M) to the object. That's basically ORM. Not as heavy as the usual libraries that support relationships etc, but still, ORM.
- baq 3y agosqlalchemy allows you to separate ORM from SQL and combine them when needed. the idea that 'ORM == you don't have to write SQL and/or you can't write SQL' is, please excuse my strong words here, wrong. my biggest gripe with sqlalchemy is that sometimes I know what I need to write in SQL (have a working prototype usually) and have trouble mapping the concept to sqlalchemy.core constructs, but that's mostly inexperience.
- mickeyp 3y agoI have decades of experience with databases and I happily use ORMs. You're conflating ORM with people who know nothing about databases. Why?
- zero_shift 3y agoFrom my experience, ORMs allow folk who know nothing about databases to continue knowing nothing about databases
- robertlagrant 3y agoWith SQLAlchemy, I come for the type checking. I stay for the Alembic migrations.
- BozeWolf 3y agoIndeed. So much more than this simple example. It gets interesting for more advanced use cases. If i now rename a field on the model, it will not be renamed in the database. If i want that to match, i have to change the query. And make a migration. But that is probably another simple blog post. Putting it all together is another blog post. And if you have colleagues: probably needs documentation. Which you also have to maintain yourself.
- TwentyPosts 3y agoI feel this, sort of. I taught myself how to code by writing a Python bot (among other things), and eventually needed some sort of database handling to make things work. I decided on teaching myself basic SQLite, and just did it raw with `sqlite3`. Currently 50% of my anxiety when it comes to my bot is related to database matters. There's no type checking so I gotta be careful when writing them, and stuff might blow up weirdly at runtime. Refactoring tables is also a major pain, or at least was until I (sort of) figured out a 'routine' of how to do it. It's doable, and I assume that teaching myself some basic SQL and using it in production was a great learning experience, but once I'd reached the point where I was inventing database migration tooling from first principles and considering how to implement that, I realized that I probably just want to look at SQLAlchemy again.
- robertlagrant 3y agoI like writing things in Python purely because of SQLAlchemy. I think it's completely great.
- c120 3y agoSo far all my projects have targeted a specific database with no reason to change it. So what I do is write SQL commands, but keep all inside a specific file or module of the project. So that I can decide later to refactor it into an ORM. I think ORMs are great if you write libraries that target more than one database. Or situations, where you have more than one database and need a proper migration part. If you don't need migration, but in the worst case can start with a fresh, empty database, then write SQL. But for production system, the no/manual migration might get old quickly. Writing migration code that just adds fields, indexes or tables is easy. But writing code that changes fields or table structures? Not do much. Still, you don't need an ORM at the beginning of a project, just don't put SQL everywhere.
- rtpg 3y agoIf you're going to end up querying all the fields and putting them into a model like this dataclass anyways... Django can do that for you. If you're going to later pick and choose what fields you query on the first load, and defer other data til later.... Django can do that for you. If you're going to have some foreign relations you want to easily query.... Django can do that for you. If you're doing a bunch of joins and are using some custom postgres extensions for certain fields and filtering... Django can help you organize the integration code cleanly. I totally understand people having issues with Django's ORM due to the query laziness making performance tricky to reason about (since an expression might or might not trigger a query depending on the context). In some specialized cases there are some real performance hits from the model creation. But Django is very good at avoiding weird SQL issues, does a lot of things correctly the first time around, and also includes wonderful things like a migration layer. You might have a database that is _really_ unamenable to something like an ORM (like if most of your tables don't have ID rows), but I wonder how much of the wisdom around ORMs is due to people being burned by half-baked ORMs over the years. I am curious as to what a larger codebase with "just SQL queries all over" ends up looking like. I have to imagine they all end up with some (granted, specialized) query builder pattern. But I think my bias is influenced by always working on software where there are just so many columns per table that it would be way too much busywork to not use something.
- noirscape 3y agoThe main problem I've encountered with complaints surrounding ORMs usually tend to be the result of trying to overfit the ORM in a certain way. ORMs are, for the most part, good at the CRUD operations - that is to say, they easily translate SELECT, UPDATE, INSERT and DELETE operations between conventional class objects and database rows. Things they usually aren't very good at are when you start trying to do things that require a lot of optimization - it's very easy to have an ORM accidentally retrieve way more data than you need or to have it access a bunch of foreignkey data too many times (in Django you can thankfully preload the latter by specifying it in a queryset). That's less an issue for basic CRUD, but is an issue if you're doing say, mass calculation and only need one column and none of the foreignkey data for speed reasons. Basically - an ORM is good but don't let yourself feel suffocated by it. If it's not a good fit for an ORM, then don't do it in the ORM, either use SQL code to do it in the DB server or do a simpler SELECT (in the ORM) and do the complex operation in your regular application before INSERTing it back in the db (if that's a goal for the operation anyway). If it's outside of the CRUD types of DB access already, the extra maintenance overhead you get from having non-ORM database code (if you're doing the SQL approach) in the application would be there anyway, you'd just get a very slow application instead of a hard error, and the latter is easier to troubleshoot (and often fix), while with the former you need to start pulling up profiling tools.
- p4bl0 3y agoI always write SQL code directly, but that's mostly because I actually enjoy writing SQL queries :).
- seunosewa 3y agoSame here. SQL is arguably the best language for writing queries. That's what it was designed for.
- promiseofbeans 3y ago> ... Python dot not have anything in the standard library that supports database interaction, this has always been a problem for the community to solve. Python has built-in support for SQLite in the standard library: https://docs.python.org/3/library/sqlite3.html https://docs.python.org/3/library/sqlite3.html
- dikei 3y agoAlso, DB-API 2.0 is a standard that's followed by most database drivers, similar to what JDBC is for Java, though not as strictly enforced.
- uranusjr 3y agoPython also has the DBAPI specification, which defines what interface a library must support to be considered a database driver. The author claiming Go’s sql package encourages writing SQL directly while Python doesn’t really seems a bit awkward.
- reportgunner 3y agosqlite-utils[0] is a great library for working with SQLite [0] https://sqlite-utils.datasette.io/en/stable/ https://sqlite-utils.datasette.io/en/stable/
- baq 3y agoIt's fine advice... if you don't ever need to build queries programmatically (you will, probably) and don't care about type checks (you should, it's 2023). If you don't know what you're doing on the DB-app interface, you're still better off with an ORM most of the time. If you don't know if you know, you don't know (especially if you think you know but details are fuzzy); please go read sqlalchemy docs, no, skimming doesn't count. If you know what you're doing but are new to Python, use sqlalchemy.core. PS. zzzeek is a low-key god-tier hacker.
- rav 3y agoIt's fine advice - if you can type check your queries. My colleague wrote a mypy plugin for parsing SQL statements and doing type checking against a database schema file, which helps to identify typos and type errors early: https://github.com/antialize/py-mysql-type-plugin https://github.com/antialize/py-mysql-type-plugin
- baq 3y agoRaw sql doesn’t compose, so it’s a no go for me except in special cases, but the tool would be a great addition to sqlalchemy.core for when those special cases occur.
- Jackevansevo 3y ago> I have spent enough time in tech to see languages and frameworks fall out of grace, libraries and tools coming and going. I feel like Django ORM and SQLAlchemy are the de-facto ORMs for Python and have been around for over a decade. If anything I'd recommend juniors to pick one of these over hand rolling their own solution because it's so ubiquitous in the ecosystem.
- bbojan 3y agoThe article is missing the code for creating the "users" database table. What about indexes? Migrations? Relations to other tables? I mean you can just write SQL instead of using the ORM if your project consists of a single table with no indexes that will never change, sure.
- joaodlf 3y agoWhen it comes to migrations, I've been fine with https://github.com/golang-migrate/migrate https://github.com/golang-migrate/migrate There are a multitude of extra things to consider, but none of those things are, in my opinion, imperative to having success with SQL in Python. Will it be hard to achieve the same level of convenience that modern ORMs provide? Absolutely. But there is always a cost. I firmly believe that for most projects (especially in the age of "services"), an approach like this is very much good enough. Also, a great way to onboard new developers and present both SQL and simple abstractions that can be applied to many other areas of building software.
- tracker1 3y agoAgreed, I've seen plenty of what wind up being very byzantine and complex migration strategies over the years, and in the end simple SQL scripts tends to work the best. I will note, that it's sometimes easier to do a DB dump for the likes of sprocs, functions, etc, if you want the "current" database bits to search through.
- somat 3y agosometimes the idea is that the database lives it's own life outside the application. Probably not the case here, but under that viewpoint the application is just one of perhaps many that access the data and as such creating tables, indexes, migrations and relations are none of it's business.
- m000 3y agoBut it is the application's business. You may not be altering the database schema from your application, but you still need to make sure that its code is in-sync with it. This means that you will need extra tooling, and if you're DIYing you will need to write it yourself.
- hobbescotch 3y agoI’ve been a data engineer for many years and have lots of practice optimizing SQL touching many parts of the language and I still enjoy using SQLAlchemy for its tight, elegant integration with flask/django. Of course some queries make sense to optimize with raw sql but I think here, like with many other things, there’s no black/white conclusion to draw from these situations.
- sergioisidoro 3y agoI've used Rails AR and Django/SQL Alchemy orms, and the more I use it, the more I wish for a fusion of both. Django ORM is amazing for Schema management and migrations, but I dislike their query interfaces (using "filter" instead of "where"). I really like Rails AR way of lightly wrapping SQL, with a almost 1-1, and similar names, but does not have a migration manager - and there is always the chance that your schema and your code will diverge. If I would get a Schema / migration manager, that would allow to do type checks and that would work well with a language server for autocompletes, but use SQL or a very very thin wrapper around SQL, that would be my Goldilock solution.
- joaodlf 3y agoThis is also why I really like Peewee in Python. If I am not going to write SQL, at least give me an API that looks and feels like it. When I look at Peewee code, I can often see the end result query.
- chucke 3y agoThea answer to your prayers already exists: http://sequel.jeremyevans.net/ http://sequel.jeremyevans.net/. By far the best database toolkit (ORM, query builder, migration engine) I have seen for any programming language.
- pak9rabid 3y agoHmm, is AR's Migration framework not a migration manager?
- NewEntryHN 3y agoAny serious application beyond the example given in this article will include conditional SQL constructs which go beyond SQL query parameters and will therefore require string formatting to build the SQL. Think a simple UI switch to sort some result either ascending or descending, which will require you format either an `ASC` or a `DESC` in your SQL string. The moment you build SQL with string formatting is the moment you're rewriting the SQL formatter from an ORM, meeting plenty of opportunities to shoot yourself in the foot.
- williamdclt 3y agoThere’s a world between a query builder and an ORM. The point of ORMs isn’t to build queries, if that’s the only need might as well just use a query builder which is a lot more lightweight and doesn’t come with all the downsides of orms
- masklinn 3y agoThe OP literally says to ignore query builders, not just ORMs. When they state “just write SQL” that’s their actual thesis.
- coldtea 3y agoWhat you describe just needs a query builder (e.g. in Java something like jOOQ), not necessarily an ORM.
- masklinn 3y agoOK but TFA is not just against ORMs, it’s also against query builders. That’s what GP is replying to.
- nicoburns 3y ago> The moment you build SQL with string formatting is the moment you're rewriting the SQL formatter from an ORM, meeting plenty of opportunities to shoot yourself in the foot. I used to think this, but at my last company we ended up rewriting all these queries to use conditional string formatting as we found it much more readable. The key was having named parameter binding for that string, so you didn't have to worry about matching up position arguments. That along with JavaScripts template string interpolation actually made the string-formatted version pretty nice to work with.
- dotdi 3y agoYes, this! I've been burned countless times by Hibernate (and consorts) and now I argue in favour of plain SQL wherever I can. I do not imply that Hibernate is in itself bad, I just have collected many years of observations about projects built upon it, and they all had similar problems regarding tech debt and difficult maintenance, and most of them sooner or later ran into situations where Hibernate had to be worked around in very ugly ways. Yes, I can understand some of the arguments for ORMs, especially when you get a lot of functionality automagically à la Spring Boot repositories. And since nowadays I have more influence, I do advocate for plain SQL or - the middle ground - projects like jOOQ, but without code generation, without magic, just for type safety. We've been quite happy with this approach for a very large rewrite that is now being used productively with success.
- Dowwie 3y agoYou'll eventually write your own dynamic query building logic if you take this development path
- molly0 3y agoAn ORM makes sense if you need to make very dynamic SQL queries, ie advanced logic at runtime. If your app can work well with static queries then you should not add an ORM.
- sams99 3y agoFor those looking for a rubyish approach to this see: https://github.com/discourse/mini_sql https://github.com/discourse/mini_sql
- never_inline 3y agoThe premise is that Go language users only use standard library SQL package. Anecdotally, I haven't seen a place where Go is used without something like gorm or sqlc.
- rowanseymour 3y agoWe've always used https://github.com/jmoiron/sqlx https://github.com/jmoiron/sqlx which is just the standard package + mapping to/from structs.
- badcppdev 3y agoIf you are just going to "Just Write SQL" then I really don't think you should be coding your own Object and Repository classes. My vision of the "Just Write SQL" paradigm would be a "class" or equivalent that would take a SQL command and return the response from the server. Obviously the response has a few different forms but if you're "just writing SQL" then those responses are database responses and not models or collections of models. (For the record I think simple ORM type functionality is actually quite useful as your use case moves past the scale of small utility scripts.
- semrekkers 3y agoShameless plug, with channel support: https://github.com/semrekkers/sqlz https://github.com/semrekkers/sqlz
- ploppyploppy 3y agoLow quality naive summary.
- jlnho 3y agoHow is this article different from your comment, then?
- Improvotter 3y agoIt might be worth mentioning LiteralStrings from [PEP 675](https://peps.python.org/pep-0675/ https://peps.python.org/pep-0675/) and how you should use them to prevent SQL injections. I'm not sure this blog adds much to the discussion when it comes to when to write SQL and when not to. It does not cover the struggles, the benefits, and the downfalls.
- zknill 3y agoSeems like there's 3 groups of opinions on ORMs: Firstly (1); "I want to use the ORM for everything (table definitions, indexes, and queries)" Then second (2), on the other extreme: "I don't want an ORM, I want to do everything myself, all the SQL and reading the data into objects". Then thirdly (3) the middle ground: "I want the ORM to do the boring reading/writing data between the database and the code's objects". The problem with ORMs is that they are often designed to do number 1, and are used to do number 3. This means there's often 'magic' in the ORM, when really all someone wanted to do was generate the code to read/write data from the database. In my experience this pushes engineers to adopt number 2. I'm a big fan of projects like sqlc[1] which will take SQL that you write, and generate the code for reading/writing that data/objects into and out of the database. It gives you number 3 without any of the magic from number 1. [1] https://sqlc.dev/ https://sqlc.dev/
- karmakaze 3y agoMaybe those are the main/popular groupings. Where I fall is that I want typesafe constructions of queries that match the current schema. The query compositions should follow the SQL-style structure so there's no 'shape-mismatch' composing the query using the library. Some may not consider this to be an ORM (though it does map relations to objects).
- waffletower 3y agoThere is definitely a fourth category -- "I want to build database queries natively using the paradigms of the language I am developing with, without use of SQL or an intermediary which translates into SQL."
- tracker1 3y agoI'm pretty firmly in #2... it's relatively straight forward in a scripting language, and easy enough with something like C# with Dapper. In the end ORMs tend to over-consume, and often poorly. And even when they don't in most cases, they start to in more difficult cases. That doesn't even get into the amount of boilerplate for ORMs. You have to buy in to far more than their query model(s).
- tantaman 3y ago
- davidthewatson 3y agoI'm happy to see someone mention peewee, having used it for numerous startup prototypes since its inception. Coleifer does not get enough credit IMHO: https://github.com/coleifer/ https://github.com/coleifer/ Peewee has been solid since I began using it a decade ago. Coleifer's stewardship is hard to see at once, but I've interacted with him numerous times back then and the software reflects the mindset of its creator.
- jrichardshaw 3y agoI'd definitely like to second how great peewee is. It's been a core part of running our telescope for the last 8 years. It strikes a nice balance between power and simplicity, a significant set of useful extensions and great documentation. Sometimes I find it impossible to believe that @coleifer is just one person. Peewee has 2300 issues and 500 PR's none of which are open and outstanding, and almost all of which he has personally responded too in a genuinely helpful way. He pipes up on Stackoverflow for peewee questions too.
- radus 3y agoPeewee is excellent! I've especially enjoyed using it with SQLite - there are a number of handy extensions and very good support for user defined functions.
- boxed 3y agoThis is just reimplementing Djangos ORM, but badly. ORM queries compose. That's why [Python] programmers prefers them. You can create a QuerySet in Django, and then later add a filter, and then later another filter, and then later take a slice (pagination). This is hugely important for maintainable and composable code. Another thing that's great about Djangos ORM is that it's THIN. Very thin in fact. The entire implementation is pretty tiny and super easy to read. You can just fork it by copying the entire thing into your DB if you want.
- joaodlf 3y ago> This is just reimplementing Djangos ORM, bud badly. I guess this is a good thing, as "reimplementing" Django's ORM is the opposite of what I wanted to do here :) > ORM queries compose. That's why [Python] programmers prefers them. You can create a QuerySet in Django, and then later add a filter, and then later another filter, and then later take a slice (pagination). This is hugely important for maintainable and composable code. I don't really disagree, but there are many ways to skin a cat. You can absolutely write maintainable code taking this approach. In fact, I can build highly testable, unit, functional, code following a abstraction very similar to this. The idea that "maintainable and composable code" can only be achieved by having a very opinionated approach to interacting with a database, is flimsy. I offer a contrary point of view: With the Django ORM, you are completely locked in to Django. You build around the framework, the framework never bends to your will. My approach is flexible enough to be used in a Django project, a flask project, a non web dev project, any scenario really. I want complete isolation in my business logic, which is what I try to convey just before my conclusion.
- boxed 3y agoDjangos ORM isn't highly opinionated. That's just wrong. > With the Django ORM, you are completely locked in to Django Another bit of nonsense again. You have a dependency. Sure. Just like you have a dependency on Python. But it's an open source dependency, and the ORM part is a tiny part that you can just copy paste into your own code base if you want. Also, worrying about being "locked into" something that you depend on is madness. Where does it end? Do you worry about being "locked into" Python? Of course not. > You build around the framework, the framework never bends to your will. You don't actually seem to understand Django at all. It's just a few tiny Python libraries grouped together: an ORM, request/response stuff, routing, templates, forms. That's it. You do NOT need to follow the conventions. You can put all your views in urls.py. You can not use urls.py at all. You do NOT bend to the frameworks will. That's just false. You bend to it by your own accord, don't blame anyone else on your choice.
- hprotagonist 3y agoIf you haven’t yet, check out https://pugsql.org/ https://pugsql.org/ . all the power of sqla-core, none of the ORM fuss. PugSQL is a simple Python interface for using parameterized SQL, in files, with any SQLAlchemy-supported database.
- fmajid 3y agoI'd take it a step further and move all SQL into stored procedures and call those using a functional interface. That's because of PostgreSQL's excellent stored procedure support, it might be harder with, e.g. MySQL. One major benefit of stored procedures, in addition to separation of concerns, is that you can declare them SECURITY DEFINER and give them access to tables the Python process doesn't (in a way reminiscent of setuid), thus improving the security posture dramatically. One example: you could have a procedure authenticate(login, password) that has access to the table of users and (hashed) passwords, but if the Python app server is compromised it doesn't have access to even the hashed passwords or even the ability to enumerate users of the system.
- baq 3y agoLast project we've explicitly decided to not have any stored procedures ever since you basically can't test nor deploy them in any sane way. I'm all ears how you make it work.
- felipetrz 3y agoYou can test them very easily by using database containers.
- tracker1 3y agoThe setup/teardown, and even working with schema migrations can get complex, and potentially excessively so for the benefit of doing everything in SPs. Also, the developer experience and discoverability are definitely less than ideal.
- claytonjy 3y agoI used sqitch to have very explicit tests for procedures, written in PL/pgSQL. Worked great though writing that code was a little weird due to lack of IDE support I'm used to in any other language. Could take it even further with pgTAP.
- gregw2 3y agoI had a boss once with a similar viewpoint, so I learned from him how they automated database deployments for Java/Scala apps using Liquibase+Maven and found I could apply and integrate the same principles to testing and deploying a data-layer stored procedure engine I had previously built into a joint product we were building together. For that project I was able to put SQL DDL+DML+stored procedures in version control, create/run stored procedure (TDD even) unit/integration tests on mock data against other stored procedures, had pass-fail testing/deployment in my CICD tool right alongside native app code, and did some rollback support (although that was more trouble than I think it was worth), all using Liquibase change sets (+git+Jenkins). Flyway could also have worked but we were using Liquibase. It did require some creative thinking to apply changesets and preconditions and post conditions and ability to have stored procedures execute and read results of other stored procedures. Last I checked, the system had been used over many years to process/evaluate $50 billion in order transactions.
- felipetrz 3y ago"without using ORMs" ... Proceeds to create an ad-hoc ORM.
- kervantas 3y agoEvery ORM basing post is like this. Some dude is dissatisfied with Hiberante/GORM/SQLAlchemy, declares ORMs as an "anti-pattern", then proceeds to reinvent the wheel.
- felipetrz 3y agoThe main antipattern involved in this post is Go. Rob Pike's messed up ideology made people think abstraction is bad.
- dep_b 3y agoA good ORM knows when it needs to fuck off. I just want an easy and boiler plate avoiding way to do crud operations on certain tables and map a custom type against a custom query. The lengths I have to go through to just have a custom query in some ORM's is mind-boggling. I remember fighting Microsoft's Linq to SQL or whatever the incarnation was called so hard. I could do it in the end but it fought me all the way to the end.
- zzzeek 3y agoORMs do much more than "write SQL". This is about 40% of the value they add. As this argument comes up over, and over, and over, and over again, writers of the "bah ORM" club continuously thinking, well I'm not sure, that ORMs are just going to go "poof" one day? I wrote some years back the "SQL is Just As Easy as an ORM Challenge" which demonstrates maybe a few little things that ORMs do for you besides "write SQL", like persisting and loading data between classes and tables that are joined in various very common ways to represent associations between classes: https://gist.github.com/zzzeek/5f58d007698c4a0c372edd95ab8e0267 https://gist.github.com/zzzeek/5f58d007698c4a0c372edd95ab8e0... this is why whenever someone writes one of these "just write SQL" comments, or wow here a whole blog post! wow. I just shake my head. Because this is not at all what the ORM is really getting you. Plenty of ORMs let you write raw SQL or something very close to it. The SQL is not really the point. It's about the rows and objects, moving the data from the objects to the INSERT statement, moving the data from the rows you SELECTed back to the objects. Not to mention abstraction over all the other messy things the database drivers do like dealing with datatypes and stuff like that. It looks like in this blog post, they actually implemented their own nano-ORM that stores one row and queries one table. Well great, now scale that approach up and see how much fun it is to write the same boilerplate XYZRepository / XYZPostgresqlRepository code with the same INSERT / SELECT statement over, and over again. I'd sure want to automate all that tedium. I'd want a one-to-many collection too maybe. You can use SQLAlchemy (which I wrote) and write all the SQL 100% yourself as literal strings, and still use the ORM, and still be using an enormous amount of automation to deal with the database drivers and moving data between your objects and rows. But why would anyone really want to, writing SQL for CRUD is really repetitive and tedious. Computer can do that for you.
- gmassman 3y agoThanks for you perspective, Mike. Completely agree that the interface between data and code should be handled by a single tool. That tool must meet some minimum complexity, because it’s solving a very hard problem! Also just want to say that my team has benefited greatly from your work on SQLAlchemy, and we appreciate you immensely!
- dagmx 3y agoI completely agree with you. Every single time someone says: “Just write your own abstraction over an sql generator ”, it eventually devolves into a full blown ORM. I swear the majority of these opinion posts against ORMs are from people who must have worked in a badly implemented project that left them with a bad experience and they blamed the pattern rather than the technology. One of the best bits about an ORM is making it consistent for non-db users on the team to simply grok and work with, without creating monstrous and hard to debug joins everywhere. But when badly set up it can lead to a lot of debugging spaghetti. Which is the same as can happen with SQL but I suppose people think that at least there’s one less layer to debug while ignoring the problem is actually how they got where they are and not the technology I switched a project from manually written sql to sql alchemy on a project that’s used by multiple Oscar nominated films daily for reviews. The SQL version was gross and impossible for the team to manage because it had bloated over the years, with no nice way to detangle the statement generations and joins. SQL Alchemy made it so any one of the technical directors on the team could step in and add new functionality, without serious performance footguns. Instead of me having to clean up bad sql every year (projects would fork per film and merge at the end) to keep performance up, I could trust the ORM to do that for me. At its worst, it was way too easy for TDs to get the raw SQL to be tens of minutes per review session by structuring their logic incorrectly, but it was so difficult to see. Switching to an ORM meant I could get the performance down to seconds per session and they couldn’t destroy the performance in subtle ways.
- mkl95 3y agoA better title would be "just write your own ORM". I have used several Python ORMs over the years, both for SQL and NoSQL. SQLAlchemy is the most powerful way of interacting with a relational database I have experienced. I also write Go, and when I do, I do not use an ORM. But when it comes to Python I know my solution won't be better than SQLAlchemy, so why bother rolling out my own?
- megaman821 3y agoQuestion to the SQL-only people, how would you handle something dynamic? If I have a database of shoes and want people to be able to find them by brand, size, style, etc., what does that look like?
- mitch3x3 3y agoIf statements that either add conditional statements or blank lines to the SQL block. There are a lot of tradeoffs to the pure SQL method but I prefer being able to look up exact snippets in the codebase to find things.
- ggregoire 3y agoIf you don't want to maintain several queries, you could write something like SELECT * FROM shoes WHERE (CASE WHEN :brand_id IS NOT NULL THEN brand_id = :brand_id ELSE TRUE END) AND (CASE WHEN :size IS NOT NULL THEN size = :size ELSE TRUE END) AND (CASE WHEN :style IS NOT NULL THEN style = :style ELSE TRUE END)
- megaman821 3y agoThat is pretty good. Much more readable than gluing a bunch of strings together.
- pjmlp 3y agoAnd if you want to make it even more readable, those inner expressions can be wrapped into functions.
- fb03 3y agoAlright, let's use the custom approach. And then you need another field. and then you need some slight type checking or (de)serialization, which can change over time. You'll end up writing your own custom, kludgy ORM over time. I have seen people write their own custom crazy version of GraphQL ("I've created a JSONified way of fetching only some fields from an API call) over ego or just ignorance. It's never a good path. Why bother moving away from SQLAlchemy, which will do all of that for you in a simplified, type-checked and portable way? SQLAlchemy is literally one of the best ORMs out there, it's ease of use and maintainability is insane. People that complain about ORMs might have never really used SQLAlchemy. It is that good. I'm a fan and zzzeek is huge force behind why it is so good. And as always, if you need an escape hatch, you can use raw sql for that ONE sql statement that the ORM is giving you grief for.
- plopz 3y agoI come from the PHP world and have used a variety of ORMs/query builders in that ecosystem but the most common issue I encounter that they don't handle well is when I want to do a "left join foo where foo.id is null"
- WesolyKubeczek 3y agoMy god, object-relational impedance mismatch seems to be more polarizing than US politics. I've been observing this field for more than 15 years, and it's always "JUST WRITE SQL!!!!!" versus "DRY!!! DRY!!! USE ORMs SO YOU DON'T HAVE TO WRITE SQL!!!! BUSINESS LOGIC!!!" shouting matches. It's like this topic itself takes 30 IQ points away from each participant and the conversation then devolves into complete chaos. Maybe there's some professional trauma at work, as many of us have been traumatized by shitty databases and shitty code working with them alike, ORM or not. But ORMs do come and go, promise bliss, deliver diddly, and I'm reading the same stuff I've been reading in 2008, as if nothing ever changed since.
- sakex 3y agoC++: Just write Assembly
- jredwards 3y agoThere's a huge module in our python codebase that approaches building queries in roughly raw SQL. Let me tell you, tracking how data moves from one stage to the next in that "ETL pipeline" is an absolute nightmare. Never again.
- lifewallet_dev 3y agoI can already see this doesn't have connection pooling which all those ORMs he listed have without you knowing what a connection pool is it just works, and scales, doing that on your own is not easy.
- ggregoire 3y agoI've been using PugSQL to write SQL in Python [1]. With this package, you write the SQL inside SQL files so you can benefit from syntax highlighting, auto formatting, static analysis, etc. At the difference of writing strings of SQL inside Python files. I'm surprised this is not more popular. [1] https://pugsql.org https://pugsql.org
- duckmysick 3y agoGreat concept, based on the Clojure library HugSQL. Unfortunately it depends on a specific version of SQLAlchemy and won't run with the latest version.
- waffletower 3y agoAs a data engineer, the pattern the OP shares is very familiar. I find it much preferable to use of ORMs for wide variety of reasons. However, I view implementing with SQL as an antiquated problem rather than a pragmatic feature. The evolution of this pattern would be to integrate database querying into languages more directly and eliminate SQL entirely. While this could be achieved in Python, I find that a language like Clojure, via functional programming (FP) primitives and transducers, is a natural candidate, particularly for JVM implemented databases. Rather than encapsulating SQL via query building or ORM based APIs, an FP core could be integrated into database engines to allow, via transducers, complex native forms to be executed directly across database clusters. Apache Spark is an analog of this. In particular the Clojure project, powderkeg (https://github.com/HCADatalab/powderkeg https://github.com/HCADatalab/powderkeg), as an Apache Spark interface, demonstrates the potential of utilizing transducers in a database cluster context.
- Sparkyte 3y agoSentiments on this is that sticking close to native as possible reduces coherency issues between anything. Adding layers of abstraction on top of layers of abstraction often reduces contextual understandings and further dilludes the problem solving technique. If the abstraction is truly needed a thurough way to evaluate executions is needed and a proper way to contextualize which that is not. In-line comments or even very easy to navitage documentation but the former thing or even both is superior to the latter.
- phatboyslim 3y agoTechnologists have a hard time accepting an established standard. Email is a perfect corollary to this conversation. There is a graveyard of companies that have attempted to "Solve email", yet it is still ubiquitous and attempts to 'improve' it continue to fizzle out. I'm not saying that progress, or an attempt at progress, is pointless, but to argue that writing vanilla SQL is somehow antiquated or archaic is false and OP makes several valid points highlighting why it is a perfectly valid approach.
- atoav 3y agoRecently I was wondering myself whether I should just write SQL as I didn't particularly enjoy working with SQLAlchemy. Then I discovered peewee. I am happy now.
- Rudism 3y agoThe next logical step after writing the code given in the article is to abstract common boilerplate SQL into a library so you're not spending 50% of your time writing and re-writing basic SQL insert, update, and select statements every time your models need to be updated. At which point all you've done is write your own ORM. If you want to go full-blown SQL you can use something like PostGraphile, which allows you to create all of your business entities as tables or views and then write all your business logic as SQL stored procedures, which get automatically translated into a GraphQL API for your clients to consume, but once you move beyond basic CRUD operations and reporting it becomes incredibly difficult to work with since there aren't really any good IDEs that help you manage and navigate huge pure-SQL code bases. If you're really dead set against using a powerful ORM, it's probably still a good idea to find and use a lightweight one--something that handles the tedious CRUD operations between your tables and objects, but lets you break out and write your own raw queries when you need to do something more complex. I think there's a sweet spot between writing every line of SQL your application executes and having an ORM take care of boilerplate for you that will probably be different in every case but will never be 100% at one end or the other.
- hot_gril 3y agoThe SQL inserts/updates have never felt tedious for me even in large projects, partially owed to careful use of jsonb for big objects where it makes sense (e.g. user settings dicts). Other than that, keeping a tight schema design.
- hot_gril 3y agoOf course, jsonb didn't exist until 2014ish. IMO this was a serious gap in SQL before, and it likely spawned the concepts of NoSQL and ORMs to begin with, which may have been the inspiration for jsonb. Hurray for competition.
- ruuda 3y agoOne challenge working with SQL from statically typed languages (including Python + Mypy) is that you have to convert the query inputs/outputs to/from types and it's a lot of boilerplate. I started an experiment to generate this from annotated queries. [1] Python support is still incomplete, but I'm using it somewhat successfully for using SQLite from Rust so far. [1]: https://github.com/ruuda/squiller https://github.com/ruuda/squiller
- tantaman 3y agosimilar project that generates types for queries into an intermediate representation that can be consumed by, say TypeScript, to get static Types: https://github.com/vlcn-io/typed-sql https://github.com/vlcn-io/typed-sql
- hot_gril 3y agoYou don't need an ORM, but this isn't how you avoid one. If you're thinking of your DB as a mere object store / OOP connector like this article is, you're better off with an ORM or NoSQL than this basically equivalent DIY solution. It's best to instead learn how to use a relational DB like a relational DB, and the rest will follow. Also, I'm not one of those people who dislike easy things (and will often whine about JS or Python existing). I'm all for ease and focusing on the business goals. It's just that ORMs and bad schema design will make things harder.
- tantaman 3y agoI was sad to find that the author proclaimed "just write SQL" then fell into the trap of modeling his data as objects. If you're going to model your data this way... you might as well use an ORM. A better way is to just just write SQL (or datalog) and model our data, from DB all the way up to the application, as relations rather than objects. Rather than re-hash, this idea has previously been discussed on HN here: https://news.ycombinator.com/item?id=34948816 https://news.ycombinator.com/item?id=34948816
- stuaxo 3y agoDjango isn't just about the ability to programmatically stick together things to make your query or the migrations. It's that, combined with tools that help you debug database issues, and most importantly the patterns that it imposes. As a Django developer it's straightforward to go from one Django project to another, which isn't the case with other stuff as you don't know where everything is going to be.
- sanderjd 3y agoI always ctrl-f to search for the word "composability" when I come across arguments like this. I could take or leave ORMs, but relational query-building libraries are invaluable for composability, compared to proliferating mostly-duplicative raw SQL in format strings all over the place.
- ris 3y agoI've been both ways on this, and ultimately I come down heavily in the camp of using ORMs for as far as it makes sense. Why? Sure, the "just use SQL because it's so simple" crowd use seductively simple examples, and indeed for very static use-cases it can be quite neat and simple. But projects (almost always) grow, and once you need to start conditionally adding filter clauses, conditionally adding joins, things start to get very weird very fast. And no, letting the database connector library do the quoting for you won't save you from SQL injection attacks unless you're just using it to substitute primitive values. And once all your logic is having to spend more space dealing with conditional string formatting, the clarity of what the query is actually trying to do is long gone. I'll refrain from digging up the piece of code where I was having to get the escaping correct for a field that was embedded SQL-in-SQL-in-SQL-in-go. And I could hear the echos of the original author's "YAGNI"s haunting me.
- izoow 3y agoTo those who write plain SQL in Python, what do you use for migrations?
- tpoacher 3y agoA combination of Ibuprofen and Paracetamol usually does the trick. ... oh wait, I thought you said migranes.
- metalforever 3y agoWhat happens at big companies is that they will build a custom ORM over time , and it will be way shittier and more vulnerable than if you had just used one in the first place.
- bafe 3y agoJust write SQL and eventually you will reinvent 50% of any ORM
- bastardoperator 3y agoNo thanks, been there, done that. Writing SQL by hand almost never scales.
- ak217 3y agoI've seen many iterations of this type of debate by now, and learned to recognize the patterns. The people arguing for the ostensibly simpler solution are really asking you to trust their ability to architect apps out of simpler building blocks without using an abstraction that they don't like. This can work, but it often leads to situations like someone writing a bespoke system, then leaving the job or otherwise imposing extra complexity on the team. In the immediate term, what often gets overlooked is - The ORM is an externally maintained open-source project with a plurality of contributors; "just write SQL" is not - The ORM is designed to support the full lifecycle of the application including migrations; "just write SQL" is not - The ORM is documented to be legible to newcomers; "just write SQL" is not (for all but the simplest of applications) - The ORM is composable and extensible with opinionated and customizable interfaces for doing so (I've lost track of the number of times I've had my mind blown by how elegant and smart Django and SQLAlchemy's query management tooling is) - The ORM has a security posture that allows you to both reason about your application's security and receive security updates when bugs are found - The ORM is a platform for many other modules responsible for different layers of the application (DRF, OpenAPI, django-admin, testing utilities, etc. etc.) to plug into and allow the application to grow sustainably I now try to guide people to a middle ground. Yes, both Django's and SQLAlchemy's ORMs can be annoying, have performance issues, etc. But for large applications maintained by multiple people over time, their benefits usually outweigh the drawbacks. Both have extensible architectures that allow customization and opinionated restriction of the interface that the ORM presents. If you're unhappy with your organization's ORM, I suggest you try that route first.
- impulser_ 3y agoIf you want to try out something cool, check out https://github.com/sqlc-dev/sqlc https://github.com/sqlc-dev/sqlc It's written in Go and it converts your sql migrations and queries into typesafe code that you use access your database. It currently has a plugin for Python that's in Beta, but what essentially does something similar to what this post is saying. https://github.com/sqlc-dev/sqlc-gen-python https://github.com/sqlc-dev/sqlc-gen-python You write your migrations, and queries and a config file and it does the rest.
- monomers 3y agoThere's a whole family of libraries like that. Yesql is the first I became aware of. The repo has an (incomplete) list of ports to other languages: https://github.com/krisajenkins/yesql#other-languages https://github.com/krisajenkins/yesql#other-languages
- CodeWriter23 3y agoSQL and ORM both have merit in different situations. Pick the correct tool for the given use case, and don’t be afraid to mix & match IMO.
- timmit 3y agoBased on my personal experience, I have seen some raw SQL codes about 200 to 1000 lines in some production source codes, not readable at all, not easy to change, which is a terrible development experience. I guess if it is simple CRUD, it does not give too much problem, but it will definitely work in a complex case.
- pharmakom 3y agoI no longer feel the need for an ORM. Here's what I do instead: - immutable record type for each table in the database (could be a data class) - functions for manipulating the tables that accept or return the immutable record types and directly use SQL - that's it you can generate the functions from the database schema if its too much boilerplate.
- slotrans 3y agoYes. Just write SQL. SQL is good. Never use an (active record) ORM. Ever. For any purpose. They are a disastrous idea that should be un-invented. I have this discussion with a lot of people who claim they cannot imagine working without an ORM and that "surely" using SQL is so much more work blah blah blah. Yet they have never tried! And aren't willing to try! You should try it.