LanguagesNovember 18, 20254 min read
SQL: The 50-Year-Old Language That Refuses to Retire
USAMA DARWASHI

SQL is older than TCP/IP, older than the first C standard, older than most of the people writing it today. It has outlived object databases, XML databases, and at least one full hype cycle that declared it obsolete. After six years of building systems, including a stretch of freelancing since 2020 where I got to see a lot of other people's codebases, I keep arriving at the same conclusion: the most valuable skill in my toolbox is still the one from 1974.
Every ORM leaks in the same place
Let me be clear: I like ORMs. Hibernate and SQLAlchemy have saved me thousands of lines of mapping tedium, and for the write path of a typical application, save() and findById() are exactly the right abstraction. The leak never happens there. It happens the first time somebody asks an analytical question.
"Which operators dropped below the quality threshold this quarter, and how does that compare with the same quarter last year?" There is no findQuarterOverQuarterDegradation() in any repository interface. You can assemble the answer from ORM primitives, but what you actually assemble is a loop that drags half a table into application memory and reimplements, badly, what the database already does well. I have written that loop. I have also watched it take a service down at month-end, once the data finally got big enough.
This is not an ORM design flaw. It is a boundary. ORMs map objects; analytics is about sets, ranks, and windows over time. The moment your question is about the shape of the data rather than individual records, you have left the ORM's country and should switch languages accordingly.
The query that replaced a service
At Byanat I worked on Mada, a platform that tracks signal degradation across Omantel's towers. An early version had a scheduled job doing what you would expect: pull raw measurements into Python, group by tower, compute rolling averages, flag anything trending downward. It ran for the better part of an hour and needed babysitting.
The replacement was one query. A CTE to bucket measurements by day, then AVG(metric) OVER (PARTITION BY tower_id ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) for the rolling window, then LAG() to compare each tower against its own history. PostgreSQL did in seconds what the job did in an hour, and the job got deleted, along with its retry logic, its monitoring, and its 2 a.m. pages.
The same lesson repeated on Ooredoo's M2M analytics platform, roughly 300 thousand IoT SIMs reporting usage. You do not ship rows for 300 thousand devices over the wire to compute a distribution in application code. You ship the question to the data: percentile_cont, FILTER (WHERE ...), one round trip.
The database has spent fifty years learning to answer questions about data. Your for-loop has not.
Window functions and CTEs are the real language
Most developers learn SELECT, JOIN, GROUP BY, and stop, which is a bit like learning a language's alphabet and declaring victory. The parts that pay rent start after that:
- Window functions.
ROW_NUMBER,RANK,LAG,LEAD, running totals withOVER. Anything involving "compared to the previous period" or "top N per group" is a window function, and the imperative alternative is always uglier. - CTEs.
WITHclauses let you name intermediate result sets and read a hundred-line query top to bottom like a pipeline. When we built Sabr, the TRA's centralized analytics platform, some dashboard queries were long, but nobody feared them, because each CTE read like a well-named function. - Aggregate modifiers.
FILTER (WHERE ...)gives you conditional counts without CASE gymnastics, andpercentile_contgives you medians computed where the data lives.
Why the database keeps winning
Two boring reasons. First, data locality: moving computation to the data beats moving data to the computation, every time, at every scale I have worked at. Second, the query planner is the product of fifty years of research and millions of production installs; my hand-rolled aggregation loop is the product of one sprint.
There is a third, less boring reason. SQL is declarative, and declarative survives. Frameworks I used when I started freelancing in 2020 are already legacy. The Angular and Spring Boot versions I ship today will look dated in five years. A well-written query from 1995 still runs, still reads, and would still pass review.
So my advice is unglamorous. Keep the ORM for the write path. Drop to SQL the moment a read aggregates anything. Spend a real weekend on window functions. Fifty years in, the old language is not going anywhere. It is just waiting for the rest of your stack to leak.