SQL databases come up with algorithms you’d never have dreamed of (2017) [video]
youtube.com
youtube.com
Am I understanding this guy's presentation correctly? (I left around the 20 min mark of the 50 min presentation).
Oh yea, ORM.
I've always veiwed it as a legitimate (though always over engineered) solution to wrap some repetitive database operations in a consistent manner.
The biggest issue with ORM isn't the solution they came up with, half baked and buggy as they are, but the problem they are actually employeed to "solve": developers afraid of SQL. Whether they can't write it, can't learn it, or just straight hate the string representation in their code, it's the main reason people sign on for ORM.
Then they prop it up with all manner of baseless reasoning.
Theb there is the leakyness. ORMs are usually quite fluid in their language, they make it easy to represent the database in code. The only issue is that it isn't there. It's a separate thing. When you see database code it should be obvious and very far away from business logic. What does ORM do? Mix it all up On purpose. Have fun with all the mocks.
This sounds awfully like a straw man. I've worked on several projects that used an ORM, and it was never because developers couldn't write SQL.
ORMs solve a real problem: SQL isn't composable. That's it.
Sooner or later, every non-trivial program needs to compose arbitrary pieces of one query with arbitrary pieces of another, so you'll either use an ORM (i.e., native data structures instead of raw strings), or essentially write your own ad-hoc one.
It has nothing to do with lack of ability. The staunchest ORM advocate I've worked with was also the first to pull out the debugger and single-step the MySQL driver to track down a bug (that one was nasty -- it was indeed in the driver).
I use an HTML generator, and a compiler, too, and it's not because I'm "afraid" of assembly language or that I "can't learn" HTML. These abstractions have real value.
Anyway..
> ORMs solve a real problem: SQL isn't composable. That's it.
It's a DSL. It's literally the most composeble thing you could build as a solution. Hell it's so composable people decided they could build ORMs on top of it.
SQL was created to solve the problem of interfacing, composing and reasoning about tabular data.
ORMs were built because?
I feel like your thought that SQL isn't composable is due to a reluctance to create a function that executes a new query?
Or are you really looking for a way to compose queries over objects? Because SQL has that too..
How so? I've always found the opposite. I can build up a compilex query referencing many tables in SQL without impediment but am unable to do the same with an ORM.
You cannot easily "modify" a sql statement.
There are type-safe DSLs in certain languages (if they have an expressive enough type system) that let you do this, but they are uncommon
There is great value in having the entire SQL easily visible in your source code. If there is a performance issue with a specific query my app is running, I can very quickly pull it out and into a database IDE and see what's going on using the more detailed tools available (REPL, execution plan/statistics usage analysis etc).
If the query is composed from a bunch of ORM function calls, I have to step through the program to first generate it.
Reducing friction around debugging allows your devs to become more capable in fixing things or making them work better, and SQL is no exception to that.
Modify a query? Literally just code.
Like what do you people think SQL is? Select * from table?
You could do something similar with either dynamic queries in a stored procedure, but without a DSL those dynamic queries are brittle, and the runtime composition using them is hard and error prone.
In my experience query building to this degree is usually a sign of over optimising for code reuse where it isn't really needed.
The times it does make sense, it's usually a search problem that is better solved by something like elastic search.
That isn't always feasible so what you end up with is a naive approach and just build the damn string. Or if it's needed you decompose it into the parts that are needed elsewhere table_headers() is usually all you need to stay DRY.
It's not as pretty as an ORM, it shouldn't be used for every little query "because you might need it in the future". But the cases it is needed are rare, so does it really matter?
Like you've got to adjust your frame of reference. Your database schema is static, it's not going to completely change out from under you at runtime, it's just a non problem.
I need to very often and it's rarely about code reuse. You requirements like "on the client list screen we want to be able to search by client id or client name and we want to be able to sort by last login time or name"? What about writing an API, that's 90% handling things like this.
And then there's the other cases ORM's solve, like representing data as something more than a 2D table.
ORMs sometimes come with query builders that help things but in my experience it's not worth having to clusterbomb my codebase for it.
>ORMs solve a real problem: SQL isn't composable. That's it.
But really though, these are at odds. The question is never "how can we compose this SQL." ORMs are always about writing less SQL.
When we find something can be better expressed as SQL because of a complicated join from many tables, we use SQL.
ORM is just another tool in your toolbox. They are not mutually exclusive.
I agree that if you are building ORM with the intention of hiding so many SQL details that the developers don't need to learn SQL, you'll run into problems.
SQLAlchemy on the other hand starts simple, but is flexible enough that you can do inner/outer joins, you have different loading strategies etc. It is quite nice if you are proficient enough in SQL.
When writing complicated select queries, I'd start by writing the raw SQL then spend my time representing it in SQLAlchemy. It's a bit less than double the work sometimes, but working with the SQLAlchemy objects in Python is quite nice as long as you pull them in an efficient manner. SQLAlchemy just feels like a well thought out ORM.
If you define your database properly typed, defaulted, and not null unless an update field that sort of layer isn't needed at all.
Using the correct native types on your function params or class structures means you'll never run into type mismatch or cast failures loading or unloading from the db.
So I just don't get why the need for ORM. The only thing that needs mapping is the table schema to your data type object/structs.
ORMs don't offer anything of value their and often make things you need to do in your job harder.
> If you define your database properly typed, defaulted, and not null unless an update field that sort of layer isn't needed at all.
Which is a laudable goal but not realistic if you've worked on any backend that has even a hint of organic growth or legacy. I know of at least two books on the subject of dealing with anti patterns in and the refactoring of database schemas.
Database schemas are the mortgage of technical debt, they can take literally decades to pay off if you don't get them "right" in the first place, or you pivot, or you're growing quickly, or you have a large team. Using an ORM allows you to abstract away and isolate a lot of the technical debt, create single entry points, and then fix those.
And in the part where using the ORM's syntax would obfuscate or complicate compared to SQL, then just write your own SQL query anyway and put this in a virtual view.
Sure, you can create your own entry points that handle the madness and domain knowledge, with your own SQL handling to put the pieces together that return a nicely mapped object or data structure. Then what have you done? You've written your own ORM.
Bugs? You're shitting me, from what? Untested queries?
A mess? Clean it up. You can make a mess with an ORM too.
> Which is a laudable goal but not realistic if you've worked on any backend that has even a hint of organic growth or legacy.
Except it is realistic and I have cleaned up a lot of "organic growth" and "legacy code" like this.
> I know of at least two books on the subject of dealing with anti patterns in and the refactoring of database schemas.
Yea.. where do you think I got these ideas?
> Database schemas are the mortgage of technical debt, they can take literally decades to pay off if you don't get them "right" in the first place, or you pivot, or you're growing quickly, or you have a large team.
Database design is not hard and your schemas/changes should be peer reviewed like any code. It's also not static, yes, some, SOME, very few situations are essentially unchangeable.
But most database design is easily extended without causing breaking change.
Breaking change is more problematic but not in any way more or less than any other breaking change.
Then again, I'm kind of curious how any of these problems are solved by an ORM.
Database design is indeed not hard (for the vast majority of use cases), but we can't predict the future. I'll give you an example from our app.
We've recently had to update the schema to support multiple different tax amounts for transactions. This is several different tables in the old schema, which all now have an extra child table each, and a lookup table to store the tax rates, to write/read those tax amounts for the transactions (basically a 1 .. n table for each parent table). Pretty basic stuff.
Anyway, historically, i.e. when the app was written 15 years ago, it was in a single market with a single tax rate on transactions. The original developers didn't foresee that the platform would turn into a multi-billion turnover level system, that would expand into different countries which have their own complex tax rules. It made sense that you have an amount column and a tax_amount column. Now it doesn't make sense, because hell no i want this done right and we're not going to add more columns we're actually going to model and normalise this correctly and move the tax amount(s) into child tables.
Because the older code had all of the interaction with the database not so abstracted, poorly factored if you will, we had to update dozens (i forget how many) of queries to add all the new joins and of course it needs to be back/forwards compatible, and we have to roll this out piece by piece because (as i said above) this is a multi-billion turnover level app.
The existing queries are in the region of 50+ lines of SQL with a dozen or so joins in some case. That's not actually complex SQL, it's pretty mundane and trivial SQL - SELECT a few columns, JOINing a few other tables, WHERE stuff matches conditions. There's the occasional IF, CASE, COALESCE, statement in these queries as well, and the odd UNION here and there. Again, pretty simple stuff. There are a few nice surprises from it being an old schema, polymorphic relations for example.
The changes amounted to hundreds of lines of SQL additions, the equivalent in actual "getting stuff done" code additions, making sure this is all tested so there are no regressions in moving from the old tax column in the parent table to getting the tax amounts out of the new tables, and weeks of testing and logging to find any edge cases or bugs.
In the newer model code that wraps all this stuff in an ORM? A few lines of code, because we can just compose a role into the model classes giving them access to a tax_amounts method that returns the details of each transaction's multiple tax amounts + rates + other stuff. The ORM knows about the new tables, the queries run by it add in all the necessary joins for us. The model classes are correctly factored to compose the Taxes role.
Now you can argue that if the original code had been better factored then we wouldn't have been in the same situation, and it would have been easier, but we would still have had to update multiple queries. This is where the ORM shines and it's why I'm tired of writing trivial SQL having done so for twenty years against half a dozen different database engines. I really don't want to have to write SELECT a few columns, JOINing a few other tables, WHERE stuff matches conditions type queries again. Unless of course I'm sat in the database terminal client, which happens more times a day than I can COUNT.
That said, your old and new solutions both sound wrong to me.
Tax is a line item.
To me it's as much a line item as a product, service, shipping, handling, booking fee, or any other thing that gets summed to form the total order / transaction value.
You then get to ref optionally in the way that makes sense to map attribution correctly.
Tax belongs to an item? No problem. Two taxes, one item, no problem! One tax multiple items? No problem!
The new solution does what you talk about, I guess I didn't explain it very well.
The use of an ORM is abstracting away a lot of the technical debt in the schema and isolating it so we can concentrate on fixing that debt without the worry that it continues to spread. It's much looser coupling than continuing to litter the codebase with manually written SQL.
Edit: reading more of the replies to your original comment - there is btschaegg's comment that having a narrow scope is what you should aim for[1]. There's also a reply (first one) on stackoverflow that talks about not leaking the abstraction[2]. Finally the key to our use of an ORM is that the ORM is not our model, we do not leak the abstraction, and we can drop down to manual SQL when we need it but that is still in a narrow scope.
[1] https://news.ycombinator.com/item?id=19592042
[2] https://softwareengineering.stackexchange.com/questions/3045...
> The use of an ORM is abstracting away a lot of the technical debt in the schema and isolating it so we can concentrate on fixing that debt without the worry that it continues to spread.
Is there a good reason you're abstracting over technical debt instead of just paying it down?
I mean I get it, its convenient, but now you have at least two sources of truth. One for your database, one for your application, and then god knows how many for the rest of the company.
If you fix your underlying schema things will break. Good. You might see it.
> It's much looser coupling than continuing to litter the codebase with manually written SQL.
How is SQL litter? Its as much litter as fopen.
How is it much looser coupled? To what? The data? Why would you even want that?
Loose coupling is only a benefit when the cost of breaking change is significantly higher than the cost of an extra layer of abstraction and indirection.
My core problem with ORM's is that I'm not sold it's a net positive ROI. It has benefits, sure, but it comes at a steep price I'm not prepared to pay.
> [2] https://softwareengineering.stackexchange.com/questions/3045...
Kind of agree with a lot of it but it breaks down pretty rapidly.
> Instead, with a properly designed database and an OLTP use case, ORMs are golden. Most of the grunt work goes away and with tools such as DBIx::Class::Schema::Loader, I can go from a good database schema to working Perl code in minutes.
I don't actually care about grunt work, writing code is like 5% of my time spent. I also question that its even a true statement. ORM's tend to lead to lots of very customized queries that are never abstracted. It also leads to a lot of duplicate code that is hard to detect.
> In reality, what happens is that people leak their ORM code all over their controllers (and views) and when they hit scalability issues, they start blaming the ORM rather than their architecture. The ORM gets a bad rap (I see this repeatedly for many clients). Instead, hide that abstraction so that when you've genuinely hit ORM limits, you can choose appropriate solutions for your problem rather than let code be so tightly coupled to the ORM that you're hog-tied.
So the suggestion is to not only use an ORM to abstract the DB+SQL away from your application but to also then abstract your ORM away from your code. What's next? Abstracting your AbstractORM into an AbstractAbstractORM? Can you see the problem here? If I have to abstract the DAL from my code base (and I do) why not just do it once?
...
> As Rob Kinyon made clear above, reporting tends to be a weakness in ORMs. This is a subset of a larger problem where complicated SQL or SQL which spans multiple tables sometimes doesn't work well with ORMs. For example, sometimes the ORM forces a join type I don't want and I can't tell how to fix that. Or maybe I want to use an index hint in MySQL, but it's not easy. Or sometimes the SQL is just so darned complicated that it would be nicer to write the SQL rather than the abstraction provided.
So if I blame ORM's its my architectures fault, but when you do it its just "other limitations". I think if the underlying SQL is easier to write than the abstraction code then the abstraction failed, or to my original point is just straight up half baked and incomplete.
(lots of "yous" I know you didn't write this, assume I'm being pretty general, none of this is personal)
I wish it were that easy, I really do.
In my own work, the ORM is there for lots of boring stuff, like fetch the data and push it into an edit form and then push the contents of the edit form back into the database. When it comes to q query of any consequence I end up writing SQL and then presenting a data set to the ORM, something that it can understand.
Maybe it's my age, but I'm still not a big stored procedure guy. But you look at some of the interesting things you can do with SQL, it seems silly to try and interact with it only through someone else's ORM tool.
This is the only mapping I need from an ORM:
result = (object) row
row = (array) result
SQL does everything an ORM does better.I'm not sure how you can call moving from a low level programming language to a domain specific language like SQL "going lower". It's not and ORMs are usually so bad they're a regression in what you can do.
Also, when was the last time a change to the SQL spec broke your app?what about the last times you had to migrate ORM code?
That's kind of an arbitrary categorization. An ORM is just a library. If you asked me when the last time I had to update my code because a library changed interfaces, I'd have an answer for you (less than a month ago, I'm sure). I'm not going to stop using libraries, though.
I've had to update my programs several times because my compiler was updated, too. That may make me choosier in the future about which compiler I pick, but I'm not going to stop using compilers altogether.
Overall, I tend to agree with Uncle Bob's "Clean Architecture" [1][2] approach when it comes to DBs, though: The DB usually (rembember that there is an exception to every rule!) has no business of being the "center" of an application. It should provide you with functionality, not dictate how your business logic works.
If you can pull out your DB and give it a sensible interface, you still might end up having a lot of inline SQL queries, but they are not littered all over your codebase; they live inside a narrow scope (a plugin, say). Also, the SQL schema and queries in this case often are quite boring -- the tricky bit is coming up with a good interface that provides all the guarantees you need to uphold (complicated transactions maybe?). And remember that you need these guarantees because of how your business domain works, not because of your DB. Perhaps you can get rid of some of them if you organize your storage (i.e. DB) in a specific way?
[1]: http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-ar... [2]: https://youtu.be/o_TH-Y78tt4?t=2565
Edit: Also note that there's no reason not to use a ORM in this way, too (if it suits you). The important part is the same: The ORM has no business dictating how your business logic should be implemented, and if you try to remove it from your core application, you will end up doing the same things. Now, if your ORM makes the implementation easier for you, go ahead and use it. But don't expect things to change if you put ORM-generated code instead of inline SQL at the center of your application.
[0] https://github.com/krisajenkins/yesql [1] https://github.com/honza/anosql
You avoid inline sql like the plague and your editor can have a nice dedicated sql buffer with all the bells and whistles.
I wish it was a more common pattern, it just makes soooo much more sense.
I finally gave up, and now most of the ORM usage in the codebase is just to communicate my custom queries to the underlying database and translate to/from domain entities. I feel like this is the sweet spot―give me a thin abstraction over SQL and do the gruntwork of converting domain entities to tables and vice-versa, but nothing else, thanks.
I'm still unhappy about the performance (when everything is in the working set, query takes 2ms and returns a couple of rows but the whole API call, which doesn't do much except massage the response to some kind of JSON, takes 30ms!), but that's on me for my ORM/language/runtime choice, I suppose.
Most languages plain sql drivers have something like a cast result row into object as you cursor over the set.
E.g. PHP has fetchObject https://www.php.net/manual/en/pdostatement.fetchobject.php
Which ORM was it, if I may ask?
Something like iBatis/nBatis? (Haven't used this since 2004)
Query-builders make it easy to compose queries, leaning on your programming language to enforce syntactic correctness. When you're dealing with SQL directly, composition means string concatenation. A query builder gives you a data-structure that represents a query.
I think it's helpful to distinguish between query-building and object mapping when the "orm-vs-sql" thing comes up.
No, you weren't.
> ...queries where I didn't need 90% of the info.
Corporate management, consulting and engineering subfields are made of people who don't check out from their personal needs when they work. Hype cycles, fashions, scenes and subcultures exist in software engineering just like they exist in corporate management and consulting. People want new and change.
SQL is the best for most database needs, but not for everything. It may seem boring if there is no marketing push that connects to emotions. SQL was established and boring for new software engineers. The emergence of "Web 2.0" in early 2000s started hype cycle for no-SQL. The 'scene' was so emotionally engaging that enterprises were abandoning SQL databases because "they don't scale" and because that's what their developers wanted to work with.
If you do fast prototyping this is not needed, you just need some quick and dirty persistence. Unfortunately the prototype is always the 1.0 version to ship.
—Fred Brooks, The Mythical Man Month
NoSQL fixes a lot of the mismatch with code models but imo doesn't really solve much else and lacks the expressive power of SQL when it comes to reporting software.
I suspect most developers don't realise how much they are not the main user or consumer of SQL. (Business excel spreadsheets)
Basically navigational database is just set of links to records. Filesystem can be treated as hierarchal database. DOM model is modern version of hierarchal model.
The situation is so bad, I would actually like learn more about fashion and fabric than deal with another trend. Where is the objectivity ?
Just to be clear: the talk uses hyperbolic language to make a point being that SQL is perfect for data processing, not for everything
Your presentation was excellent. I'm not attacking you. I agreed with everything you said (at least the parts that I watched). My _rant_ (or attack), instead is with the _phenomenon_ we were so strongly and needlessly gripped in for two whole decades. A cult phenomenon that had all the hallmarks of religious indoctrination.
You said: "Just to be clear: the talk uses hyperbolic language to make a point being that SQL is perfect for data processing, not for everything"
I agree. SQL isn't useful for all cases. But those cases are few.
So, there's the point.
I feel that much of what we do with map, reduce, sort and filter on arrays in modern JS is similar to SQL. Unfortunately, there is no query optimisation, because that would break your procedural code.
What I'd like to see more of is domain-specific languages embedded in general-purpose languages, like React with JSX, or just a better bridge between the two. We have these two worlds of in-memory data structures on one side, and databases on the other, that we keep separate. NoSQL was an attempt at bringing them together, but it put far too little emphasis on complex queries and filters.
It would be nice if we could just define data structures and their relations once and not have to do it again. It would be nice if foreign keys neatly mapped to object references in OOP and everything stayed live, so if you change a reference, that happens instantly in the database too, unless you wrap it in a transaction block.
Now pull this off while still retaining the ability to open an SQL REPL to test out statements, or manipulate field definitions.
I think that's what everyone wants, really. A seamless way for code to interact with data on all levels, with no perceptible boundary once you've opened a session to your DB server.
"Database as a value" had me jaw dropping and then crying most of the way through..
maybe that, but then again, what we do with custom data structures that are required for certain things to be halfway efficient, is much harder or impossible to do in SQL, correct?
Neat. I hadn't thought of it that way before.
Python comprehensions, etc. are mostly just SQL -- likewise most languages where "map" is part of the collections API.
SQL motivates the utility of functional programming for data-oriented applications.
See TransformListComprehension section. Haskell uses tricks from SQL while still having nice syntax.
http://ekmett.github.io/discrimination/ - a library to perform what SQL engines do for optimization of relational queries, online and in Haskell.
And a map can be the same as a projection as found in sql.
The difference is that many languages have constructs that weren't value producing. It is why some have the ternary operator, but lisp just had if statements.
So, to that end, most loops do produce a value. And there are several common ways they do it. In many languages, you have to give the details of how they work. In some, you can only those details and the building if the output is a bit more declarative.
Maybe it's a language issue. I would say a 'loop' has a jump and a condition. So that's 'for', 'while' and 'do' in C, JS, Python, Pascal, Java...
Lots of languages including Lisp have recursion, maps etc but I wouldn't call that looping. Clojure even has TCO recursion but it only returns one value, not a sequence (unless you accumulate the whole thing)
I am just now reading Practical Common Lisp, and amusingly one of the first things they do is build a simple query language. Didn't even use loop. So, to your point, the imperative commands of looping can be far removed from what people today call comprehensions. That said, I don't think it is inherent. Just a quirk of history.
Loops in lots of languages have outputs. It's kind of necessary in expression-oriented languages.
But it's true that comprehensions are different than Python loops in that way (and are, in fact, equivalent to maps with joins and filters, like an SQL SELECT statement.)
I draw a distinction with map, recursion, lazy sequences etc
The method-chains-vs-expression-tree semantics don't matter much when doing typical in-memory map/filter/reduce on lists but make LINQ to SQL and other more complex use cases much more powerful.
A lot of people recognise apples and oranges for what they are. The talk is meaningless to those people.
A lot of other people don't / can't make the distinction (yet). For those people, the talk is an eye opener.
* first 20 mins: Writing a contrived report query that's easy in SQL is easy in SQL.
* but: most real reports require many different fields collated into many parts of say a pdf or xls. Once you take this into account writing custom code to do the entire report is often the only way to do it. :<
* you often can't do some of what the author suggests bc in a "real" org you have db zealots that only allow access to their precious through stored procs and a surprisingly large number of meetings
* trying to do some non-trivial where clauses? Not in SQL - so you have to use an external lang. I call this "Works in my presentation" syndrome.
* he acts surprised that a lang that uses a db through a SQL api can't be as fast/efficient/terse as a system that uses SQL and has direct access to the db/storage.
Stopped after that.
PS Seems like a funny guy tho - liked his style! :>
Can't comment on db zealots. It must suck when that happens.
But disagree entirely on your other parts. Haven't run into such cases yet, wrote all my reports in SQL (and something like XSLT for presentation logic). I've mainly used Oracle.
Nobody sane uses oracle for new development.
If you chose to code your app to MongoDB or Dynamo or whatever you are 100% locked in unless you do a rewrite of your whole data access layer.
To top it off, in the real world you’ll still need a separate SQL DB for reporting and analytics.
That said, SQL doesn't lend itself to easy composability and you can end up with "4000 line monsters" if you really tried to put as much business logic into the query as possible.
Is anyone working on SQL extensions, ORM, or new 4th Gen language that could actually support something like 4000 lines of SQL in a maintainable way?
In my experience so far, this has usually been the actual problem. If you are not actively trying to "cleanly" separate your business logic from your storage, you tend to end up maintaining a mess. People correctly identify the problems with the mess, but incorrectly attribute it to SQL instead of architectural problems. So they switch to an ORM and wonder why it doesn't improve anything a little while later.
And that's not to say it is easy to come up with a useful way to interface between storage and the business logic -- on the contrary. It's just that the naive approach (strongly coupling the two everywhere) is kind of a worst-case scenario. Also, there are certainly situations where ORMs are very useful; it's just that a messily integrated ORM isn't any better than messily integrated SQL.
The thing is: Your business logic needs to be provided with ways of triggering certain operations (along with certain guarantees). I haven't seen cases where a single operation would take 4000 lines of "interesting" SQL though (ignoring very long lists of columns :) ). So, if the SQL is hard to maintain: Is this a problem of the SQL, or is this a byproduct of unclear (or ever changing) interface requirements? Sometimes, taking a couple of steps back and considering alternative approaches can yield much better results than optimizing a "wrong" solution.
(I guess this could be considered a form of the "X Y Problem": http://xyproblem.info/).
Usually when I'm using any technology, being able to understand how it works is my most important priority, because it prevents me from using it improperly. I have a hard time understanding what those "algorithms" really are after all. I heard that databases engines use backtracking, but I'm not really sure that's what it is talked about here. Maybe databases don't use BT?
Using another language that parses every time you do something doesn't feel very fast. It is fast for many applications, but I don't think it is for all of them.
In the end, it's the same old combat, either choose peak performance or development delays with good enough performance.
Although I have to admit that for GIS (geographic information system), databases with embedded R-trees and other things are very much welcome, since implementing those algorithms from the ground up is way too hard.
Most of the focus was on things like database normalization instead of practical usage. I recall upon starting my first job, a co-worker gave me a crash course in what exactly a LEFT JOIN was (though at this point it had been ~3 years since the college database course, which had been so lackluster I hadn't used one during those years).
I will discuss a few ORMs to make the students aware they are out there but we will not teach one in the course.
As a contrasting example, the experience you get with ElasticSearch is that queries have predictable performance even as your data changes. In that way it's much, much nicer. On the other hand, you lose joins, which is a huge downside.