There are several reasons:
1. SQL is a standard and standards are very hard to change. Especially when they work and data is incredibly “sticky”.
2. SQL is declarative: compare a SQL statement producing a report with a Java or even Python program. Then show it to a none-engineer.
3. SQL is very forgiving – something ppl usually don’t think about. Database optimizers as well as the sheer computing powers “forgives” badly written SQL statements.
4. SQL is run by databases and databases take time to mature. There are awesome database products that run SQL.
Join is an amazing building block, which you need to use wisely.
First I would like to address an opinion that “joins have a quadratic complexity (O(n^2)) in both time and space”. This is simply not true. In practice joins usually have O(1) (nested loop and merge join) or O(N) (hash join) space complexity, and M*logN and logM + logN (nested loop join with an index lookup on the inner side) or M + N (merge join or hash join) complexity in time, where M and N are the cardinalities of each side of the join.
Joins and SQL in general are also one of the most expressive tools for analytical queries.
Joins can be “bad” because you can concisely express a lot of computational complexity, and system that allow join can have “bad neighbors”. That’s why if you have a lot of developers working on a shared real-time system it makes sense sometimes to restrict your api to not allow them:
TAO: The power of the graph