Ask HN: Does anyone else think SQL needs help?
Fundamentally, SQL lacks meaningful abstraction, and there's no sane way to package very common operations.
Say you want to find elements in a row that correspond to some maximum value when grouped by date. Today, you'd need to write something like this EVERY SINGLE TIME:
``` SELECT sd1.sale_person_id, sd1.sale_person_name, sd1.no_products_sold, sd1.commission_percentage, sd1.sales_department FROM sales_department_details sd1 INNER JOIN (SELECT sale_person_id, MAX(no_products_sold) AS no_products_sold FROM sales_department_details GROUP BY sale_person_id) sd2 ON sd1.sale_person_id = sd2.sale_person_id AND sd1.no_products_sold = sd2.no_products_sold; ```
Wouldn't something like this be nicer?
``` SELECT sale_person_id, max(no_products_sold) as max_sold, link(sale_person_name, max_sold), FROM sales_department_details ```
Frankly, it seems like some sort of macro system is needed. Perhaps the SQL compiles into the above?
212 comments
[ 0.78 ms ] story [ 232 ms ] threadIn the same vein that different languages provide higher-level abstractions for machine code or JVM bytecode.
There are so many different ways to abstract aspects for different goals.
Yes: https://docs.djangoproject.com/en/4.1/topics/db/queries/#ret... and https://docs.djangoproject.com/en/4.1/topics/db/queries/#loo...
> Can I compose named functions that represent transforms (map, filter, etc.) in a type-safe fashion with each other and have those translate to appropriate CTEs
The Django ORM doesn't use CTEs (yet). It's common however to create and compose custom filter operations which the ORM will then combine into larger SQL queries. A rough example:
This would build the following SQL query: There are plenty of more sophisticated ways to build abstractions over queries in Django - using custom QuerySet methods for example: https://docs.djangoproject.com/en/4.1/topics/db/managers/#ca...https://www.edgedb.com/ https://www.edgedb.com/showcase/edgeql
The Department of Defense was asked by Congress to answer “Where does the money go?” and tried to use OWL and SPARQL queries across all the relational databases they owned but they couldn’t get it to work.
I can’t help but think something along those lines could present as a ‘low code’ data tool.
You can accomplish something similar with views, foreign tables, stored procedures, user defined functions, and other mechanisms which are common in databases like PostgreSQL, Microsoft SQL, etc.
I find PostgreSQL pretty handy because it supports embedded data structures such as lists in columns, supports queries over JSON documents in columns, etc.
My favorite DB for side projects is ArangoDB which uses collections of JSON objects like tables and the obvious algebra over them which lets you query and update like the best relational dbs but you don’t have the programmability of views, stored procedures, etc.
I think you are pretty familiar with those technologies. Do you think they have any value?
That is, it will defeat any technology that you bring to the table.
My own feelings about RDF are mixed. On one hand I've accomplished quite a few things with it, for instance, developing a technique for bringing multiple schema systems (say EMOF, XML Schema, and FIX Orchestra) into a single framework but it has been like
https://supernatural.fandom.com/wiki/Binb%C5%8Dgami
for me in that I have put huge amounts of time and money into it with very little return, not because of technical problems per se but because of problems marketing it.
There's a certain kind of stupidity inside the community that keeps it from making tools that are truly effective but there is even more stupidity outside the community that makes it seem it won't ever be understood. The semantic web seems to be cursed that it will never develop a brand like GraphQL, Docker, mongodb, or "FAANG" that works like a Jedi mind trick to bring people into its domains.
For instance I saw GraphQL as beneath contempt for many years because there was no specification, it was just "Facebook will return any answers that further their business interests if and when they want to" and somehow people are seduced by that the same way that they are seduced by Donald Trump or will cut out their left eyeball because Matt Butts said that is how to raise your PageRank by 0.1% next Thursday.
But I don't get it. Could someone explain how this works?
SQL views are useful for that kind of thing too.
This can definitely cause issues. I’ve seen queries with CTEs that basically process unindexed 100 MB data due to querying the materialized rows.
It would be cool to be able generate indexes with the CTEs though
Also note that materialization acts as an optimization fence. That is, PostgreSQL will not push down filter criteria and such from the query into the `WITH` clause. It can do such if it's not materialized.
Not sure which engine you're using, but for MSSQL at least, the engine writes stuff to disk where it can index it so that it can avoid table scans on retrieval. That is ideal where you have big joins or large tables. And is generally good for most use cases.
When you use CTE's you're telling the engine, "I want this in memory, don't do any indexing." That's awesome for write heavy workloads. But if you plan on joining the results of the CTE later, you will find that you can't read from it very quickly because for every row joined you must scan the entire table.
Now you'd think that since it's also forcing the data to stay in memory (if there's enough) that this would be faster. But that's not true. There's compute power at work to make comparisons on each row. The point of indexing is to reduce the required compute power by reducing the number of rows that you need to compare.
With that in mind, it's obvious that it kills performance if you just told your engine to not use indexes but do a big join on a large set of data.
Temporary tables are a better choice for read heavy workload compared with CTEs. MSSQL will index temp tables automatically. And in very rare cases where the automatic indexes aren't performant, then you can index them manually with a few extra keywords.
Happy querying.
WITH cte1 AS ( /* whatever / ), cte2 AS ( / you can refer to cte1 here / ), cte3 AS ( / you can refer to cte1 and cte2 here */ ), ...
If I need access into multiple statements that I can't refactor into a single one, explicitly using temporary tables is a viable option (some engines implement CTEs and other operations using implicit temporary tables anyway). Otherwise, yes, views.
I'm a principal engineer who is relatively new with SQL (just learned it 5 years ago, using it for a side project, not my daily work) and almost completely self-taught; and the biggest problem I have with SQL is that I find it nearly impossible to do even basic levels of DRY in a way that's not ugly.
Another issue is that the “SELECT” is also part of the function, which makes it even worse - so you can’t create just one function and reuse it all over the place. If only you could tell SQL that a certain JOIN always contains one row, or zero or one row (for a LEFT JOIN), then there would be more room for optimization.
I don't see any reason that the "select" limits reusability. If you define your function with relevant parameters you can create theoretically infinite reusability.
https://prql-lang.org/
[0] https://www.edgedb.com/ [1] https://www.edgedb.com/showcase/edgeql
https://www.postgresql.org/docs/14/ddl-generated-columns.htm...
> A generated column is a special column that is always computed from other columns. Thus, it is for columns what a view is for tables.
Postgres does have index compression as of a recent version, but I’m not sure how much that would improve random keys.
You're right of course that Postgres lacks true clustered indexes. The closest thing it has is covering indexes, which don't help at all with saving space; quite the opposite in fact.
As far as memory complexity, this got me thinking a userspace implementation (I’ve prototyped one for Postgres using VIEWs for fun and personal education) could reasonably borrow optimization techniques from persistent data structures common in FP languages. But I don’t know how well a query planner would cope with that, and my instinct is that the optimization would be better applied at the storage engine level (again as I see it, advantage: Maria).
• add a timestamp range column
• create a history table that matches the main table
• using table inheritance, have the live table inherit from the history table
At this point all queries on the main table are "live" and queries on the history include past and present data.
• add an insert trigger that initializes the live table's timestamp range to start "now()"
• add AFTER UPDATE/DELETE/TRUNCATE triggers to insert the OLD record into the history table with appropriate timestamp ranges
Bonus
• have event triggers automatically set up the temporal tables on CREATE TABLE
The thing which helped me grasp this was correcting historical accounting according to the domain (schema), eg updating meeting records. You of course want to preserve the previous record upon update, but the “current” state of what was decided a week ago might still change.
eg.
SELECT col1, col2, col3(t) FROM table_name t;
This is how Postgraphile solved the problem to great effect. https://www.graphile.org/postgraphile/computed-columns/
Agree, SQL can already do what the OP proposes in almost the exact same syntax.
and together with CTEs, intermediate temp tables or views, you can totally avoid the spaghetti and break down the queries in manageable chunks.
select sale_person_id, sale_person_name, no_products_sold, commission_percentage, sales_department from sales_department_details natural join max_products_sold;
https://github.com/looker-open-source/malloy
For me the value of being able to walk up to an unfamiliar database of widely varying tech and start getting value out of it immediately is the killer app. Macros or other DB-specific extensions would be useful at times, but I’m not sure how much they would enable solving the messiness problem in a generic way that reshapes the paradigm more broadly. My instinct is the messiness is a consequence of the direct utility and trying to make it nicer from a programmerly aesthetics point of view might be hard to sell. It’s not like SQL doesn’t have affordances for aesthetics already (eg. WITH clauses).
- Queries that are difficult to interpret/verify.
- Non trivial amount of effort to go from question to query: I often feel like I have to jump through hoops to obtain something trivial. I have to think, and I don't want to think.
At least SQL is old enough to get plenty of relevant search results. You can be a copy-paste DJ if you really can't figure it out.
> Fundamentally, SQL lacks meaningful abstraction
My RDBMS course at college was really hardcore and SQL code answers for exam questions were enormous.
One neat trick they didn't teach us, but still perfectly legal SQL:1999, is to use WITH recursive constructs.
This way you can decompose very complex queries into simple ones, that can tested independently.
Also, integrating aggregate functions along with row_number() and similar functions to perform more complex work with less code.
IMHO declarative and relational algebra are orthogonal concepts. And why do you say SQL is relational algebra in disguise (paraphrased) ? Most introductions to SQL I've read, but certainly the SQL page on Wikipedia [1], starts with a remark about Codd's relational model. Granted SQL does not implement it perfectly, and it is not perfectly declarative, although that is a matter of taste.
> Non trivial amount of effort to go from question to query: I often feel like I have to jump through hoops to obtain something trivial.
I've had colleagues which expressed similar sentiments, but I have the opposite impression. I think I can express many queries on a much higher abstraction level than with most other languages.
> I have to think, and I don't want to think.
Not wanting to think while writing code is an understandable wish, but also a big red flag about developers for me. Even the best AI with perfect mind reading capabilities will not be able to execute the query you want, if you don't know what you want yourself. Even the AI would guess sometimes right, you would have no way of knowing whether it was right or wrong.
[1]: https://en.wikipedia.org/wiki/SQL
Dude... these are simple queries. I have *far* more important things to ponder than the specific incantations needed to get one of the values that correspond to a maximum.
I find 80% of queries that people want across a company are trivial.
I’d have saved myself many many hours over the years if people could just ask a question in natural language rather than asking me to write a query.
Hell they even have a name for databases specialized for this usecase — OLAP
The problem is that databases, and SQL, are such a pain to extend and define sufficiently general operations that you don’t just have scipy in the database, and thus your complicated analytics have to be fully specified with the few rudimentary analytic functions that are actually provided to you.
https://docs.timescale.com/timescaledb/latest/how-to-guides/...
https://duckdb.org/2022/05/04/friendlier-sql.html
It's a shame that so many people regard SQL using the reference SQL92 spec when in fact that it is continually evolving and improving like any other language still in use.
smh...
This is outdated; most modern DBs treat views as they would CTEs. If you don’t reference a defined field downstream, it doesn’t need to get calculated.
(Or maybe my knowledge is outdated and the optimizers have gotten way better than they were 3-4 years ago.)
Imagine wanting something as simple as "find_percentile_value(table, column, percentile)". There is no portable and standard way to write this parametric abstraction and then reuse it on any column source you need to interrogate later.
Now, ... reuse. Surely you write the result to another table and then refer to that.
Note, this was just meant as an example where a view is not helpful. We don't always desire particular calculated data to be named for reuse. Rather, we want a reusable method we can apply to any data. In my earlier post, I tried to address the topic of macros or generated SQL, and so suggested it take a table and column name as input to expand a query idiom for a set of values stored in a table. But, that's not how I'd really want to do it. The rest of this comment will veer into a more elaborate perspective on real functional libraries, rather than mere macros...
Today, with dialect-specific mechanisms, I can try to define window functions or ordered-set aggregate functions. It would be possible to solve the percentile problem this way. In fact, many SQL dialects have built-in functions that can help with percentiles, since this is recognized as such a basic statistical concept. But, I only chose percentile as an accessible yet concrete example. These built-in solutions are not the answer to my question. My question is how can user programmers extend the system with their own abstractions.
Writing aggregate and window functions requires a big mental shift for the programmer. You usually need to define a state variable and provide a set of input/output/accumulator functions and register all that as an aggregate function. This is because of the way this calling convention is meant to be integrated into query plans in a streaming fashion. It's a bit like forcing a programmer to learn some async or co-routine calling convention without offering a simpler synchronous option as a starting point. It's an obstacle.
The application of window or ordered-set aggregate functions also require a lot of verbose syntax to wire up columns or expressions to function inputs and to configure the windows and ordering modes. There isn't really any support to abstract these details away behind the named function, such that the naive user could just call it without knowing how it works internally to solve the whole problem. In other words, having a functional abstraction which can encapsulate data-preconditioning behind an easier interface.
Finally, this approach only addresses the case of reducing a set to a scalar or assigning new scalars for every row in a set. To match the generality of the numpy API would require functions that take sets as input and produce arbitrarily different sets as output. It also needs the host language to allow low-syntax manipulation of intermediate data when composing functions.
So, for full generality, a data transform function might take one set as input and produce another set as output. We would also want things like: set-based function interface typing and function overloading; functions that can setup their own desired traversals of input data including sorting and filtering; functions that can allocate (potentially) large intermediate data using sets or other common data structure idioms; and complementary call-site syntax to easily compose functions with little syntactic boilerplate but also with low marginal syntax overhead to customize and restructure data passed between functions.
It is possible to return arbitrary sets from functions in many systems-- recent versions of the SQL standard even specify table functions (with syntax "RETURNS TABLE(columns)")[0] as well as polymorphic table functions (PTFs)[1]. And in addition to implementations of those standards, extensions like PL/[pg]SQL also have a few other ways of effecting table-like returns (parameter modes, composite type returns).
>function overloading
Not standard, but some systems support it[2].
[0] https://www.postgresql.org/docs/current/xfunc-sql.html#XFUNC...
[1] https://share.ansi.org/Shared%20Documents/News%20and%20Publi...
[2] https://www.postgresql.org/docs/current/xfunc-overload.html
I'd also want to allow a function to be both a table source and a table sink, which I think means that you might need a sub-query like syntax to supply the source that will be consumed by the function. This is a vague idea and not a well-defined language proposal:
I’ve generally used it as a step in a data pipeline, where some intermittent state or aggregation where being stale/stable for a period of time is ok. Did this recently for a daily trends report on some news data - live querying the data is slow, but baking it off into a MV makes it much faster and keeps the query result from changing too rapidly to new data.
It's been an ANSI standard for 36 years.
It's _every single time_ you need to do this kind of trivial querying you have to write this convoluted mess of SQL.
Many systems support user-defined aggregates[0][1]
[0] https://www.postgresql.org/docs/current/sql-createaggregate....
[1] https://docs.oracle.com/en/database/oracle/oracle-database/2...
https://www.scattered-thoughts.net/writing/against-sql (found from HN a week or so ago)
https://tablam.org
---
Exist 2 major ways to solve this: You do a transpiler (like most ORM are, actually) or you go deeper and fix from the base.
The second could yield much better results. It will sound weird at first, but not exist anything that block the idea of "a SQL" language work for make the full app, with UI and all that.
SQL as-is is just too limited, in some unfortunate ways even for the use-case of have a restricted language usable for ad-hoc queries, but that is purely incidental.
The relational model is much more expressive than array, functional models (because well, it can supersede/integrate both) and with some extra adjustment you can get something like python/ML that could become super-productive.
Every expression represents a filter of some kind.
In SQL (and in relational algebra), every expression represents a dataset.
That means composing SQL expressions is composing data, not composing operations on data.
Since there is a somewhat low, backwards-compatible barrier to entry to build a combinator language on top of SQL (Pine is an example), whether or not trading the increased learning curve of using combinators for better composability is a good idea can stand the test of time.
[1]: https://github.com/pine-lang/pine [2]: https://stedolan.github.io/jq/
Views, materialized views, CTEs.
Say I have a table with the following properties:
- It has a column that corresponds to an id
- It has a column that corresponds to a category
- It has a column that corresponds to a value
- It has a column that corresponds to a timestamp
- It has a bunch of other columns
I want to have a general transformation that allows me to:
Select an aggregate min, max over a period of time for a given category along with the other columns linked to min/max.
The stored procedure ends up being a mashup of string concatenation. Meanwhile, it just feels something is missing to make the language expressive enough.
Difficult to be sure what you mean, but I think you're wrong. You can select from any table source (edit: result set, if you prefer that term).
> Select an aggregate min, max over a period of time for a given category along with the other columns linked to min/max.
I am not sure what you're asking for if the above is not it. Can you give an example please?You can copy, paste and edit 100 times and create 100 view objects
Or in eg numpy-python, you’d write a single function taking the table/columns as arguments, and reuse it.
I think the usual SQL solution would be to implement a stored function and start smashing strings together
Which doesn’t matter in many of these simple examples, but this is the thing that really stops you from getting to have your import gravity ecosystem of utilities that modern application programming enjoys. And the continuing buildup of libraries on top of libraries for further complexity.
Instead you’re limited to extensions/packages, which are coupled tightly to the RDBMS internals (which is why you don’t see much of SQL extensions shared across DBs, despite at the end of the day operating all on the same shared framework [relations]) and whatever functionality the database provider has deemed fit to provide (perhaps at cost)
1. To Union them into multiple views
2. To instantiate individual views per query, to reuse in other queries
3. To return to client directly
I don’t see how this expansion will help you, but go wild. The problem remains that you can’t separate the logic of the view from the schema it’s acting upon
The min/max aggregations over a period of time per category is pretty straight forward (aggregating rows), but what do you mean by "along with the other columns linked to min/max"? How would other columns come into play after aggregating rows?
[0] https://github.com/seancorfield/honeysql
[1] https://github.com/seancorfield/honeysql#extensibility
Like say you have a query that applies to one table and potentially others where the column names are slightly different. Why cant you just port that query?
Just asking for mooar build in stuff just leads to bloated software.
Check out stored procedures and views.
Here's a great summary: https://stackoverflow.com/a/5195020