People don't generally like writing raw SQL because you have to map the results to and from your programming language. So at a minimum, you need a query builder that does some minimal and flexible mapping.
Building basic CRUD apps as a hobbyist, I've just never had that problem. My app needs some data. I fire of a query af psocopg2 gives me back my data.
I know I'm the least experienced. So I'm not arguing. I just don't understand it
(I work mostly be with data / BI, so I'm familiar with SQL)
1. Query building, particularly when the query needs to be dynamic based on user input? Do you end up concatenating strings together or do you use a separate query builder?
2. Coalescing result sets produced by JOINs back into object form? Example: if you want to fetch users along with all their posts your query will return multiple rows per user, but when working with objects in your app you want each user to have a list of posts so you can simply say users.posts.
3. Property change tracking? Example: different parts of your app might update different properties for each user. If the user's email and last_login changes you need to write one query. If the user's password changes you need to write a different query. If the user's email, name, and location changes you need to write another query. An ORM with change tracking will figure out exactly which properties have been modified and issue the correct SQL to update only the changed properties. When working with raw SQL do you simply end up writing different queries for each possible permutation of changes?
SQL's design is optimized for processing unordered sets of records.
This seems like the biggest trend the more I use RAILs: it’s great to iterate and prototype, but as soon as you hit scale the maintainability becomes a big issue. I’m not saying other web frameworks are immune or that this is a problem that cannot be solved, it’s just all of these abstractions and cascade of configuration objects to make RAILs do what you want end up getting in the way.
A query builder is the best of both worlds: semantics that resemble raw SQL with some ability for composition.
# Boilerplate
from sqlalchemy import create_engine, MetaData, Table
engine = create_engine('sqlite:///:memory:', echo=True)
metadata = MetaData()
customers = Table('customers', metadata, autoload_with=engine)
query = customers.select([customers.c.id, customers.c.fname, customers.c.lname, customers.c.phone])
# => SELECT customers.id, customers.fname, customers.lname, customers.phone FROM customers
conn = engine.connect()
conn.execute(query)
# => [(1, "Foo", "Bar", "12345678"), ...]
ActiveRecord also has Arel which can be use as a standalone. Documentation is a bit more sparse compared to SQLAlchemy Core or its ActiveRecord ORM counterpart, though.