upvote
> I expect someone who uses an ORM to understand SQL well.

From experience, I don't. ORMs are usually sold as 'learn this instead of learning SQL'. For many, the ORM creates the tables, alters the tables, and queries the tables; they don't see SQL and they don't know SQL. When that works, it works, but when it falls apart, they have to debug the SQL and the abstraction layer. I'd rather have fewer unnecessary abstraction layers.

> If I pull a datetime from SQL there's a lot of value in having a single piece of code handle that datetime the same way across the entire stack.

There's value there, datetimes are very complex, but the rest of the stuff it comes with obscures the value IMHO.

> Obviously you are encountering code made by people who don't understand this but the problem isn't the ORM. They would have made that mistake with or without an ORM.

It's hard to write the kind of complex queries I've seen by hand, and I like to imagine if you out how to do that, you'll also know why it's slow and not need my help... But the ORM is part of the problem, because when you've written bad queries by hand, and I give you a better query (or sequence of queries), it's easy to apply. When you've done it with an ORM, you may not even know where the query is made.

reply
You can always make the ORM Model based on a view. Sometimes a background job compiling a simple result set table is the appropriate answer.

Almost all ORMs boil down their queries down to a single query handler so it's actually super easy to find the query.

My ORM for example:

  *Read paths*

  - Models/Factory/Getters/GetAllRecords.php:28 - table(...) when indexField is set.
  - Models/Factory/Getters/GetAllRecords.php:31 - allRecords(...).
  - Models/Factory/Getters/GetAllRecordsByWhere.php:95 - table(...) when indexField is set.
  - Models/Factory/Getters/GetAllRecordsByWhere.php:98 - allRecords(...).
  - Models/Factory/Getters/GetRecordByWhere.php:20 - oneRecord(...).
  - Models/Factory/Getters/GetByQuery.php:9 - oneRecord(...).
  - Models/Factory/Getters/GetAllByQuery.php:9 - allRecords(...).
  - Models/Factory/Getters/GetTableByQuery.php:9 - table(...).
  - Models/Versioning.php:122 - revision table(...).
  - Models/Versioning.php:124 - revision allRecords(...).

  *Write paths*

  - Models/Events/Save.php:41 - insert on save() for phantom records.
  - Models/Events/Save.php:53 - update on save() for existing dirty records.
  - Models/Events/Delete.php:18 - delete by primary key.
  - Models/Events/Destroy.php:24 - insert history row before destroy for versioned models.
  - Models/Versioning.php:180 - insert history row after versioned save.

  Error/retry path

  - Models/Events/HandleException.php:35 - direct $connection->exec(...) for auto-creating missing tables.
  - Models/Events/HandleException.php:43 - direct $connection->query(...) to rerun the failed query after table creation.

  All of those eventually bottom out in IO/Database/StorageType.php:119 for non-result queries via PDO exec, or IO/Database/StorageType.php:149 for result queries via PDO query.

I used to profile all my queries in those two methods but with tools like NewRelic there's no need to slow the code down with profiling cruft.
reply