8 ms·
SQL Anti-Patterns
- 0xbadcafebee 1y agoAt this point it's malpractice not to use AI to analyze your SQL statements and tables for optimizations
- jpnc 1y agoAre we on bizarro HN? No, you ask the DB to EXPLAIN itself to you.
- Arch-TK 1y agoNext you'll be telling me that instead of asking AI to find my bug I should just use print statements or a debugger to observe the state of my program over time to find where it deviates from expectations and figure it out that way.
- mjb 1y agoI agree. Modern code models tend to do a great job advising in SQL, especially if you include the table definition and EXPLAIN output in the context. Alternatively, I've found that an EXPLAIN MCP tool works well.
- jwsteigerwalt 1y agoThat’s my rap sheet…
- JohnHaugeland 1y agothese aren’t anti patterns. these are just things you shouldn’t do
- em500 1y agoStill waiting for the definitive article on how using the term anti-pattern is an anti-pattern.
- readthenotes1 1y agoIf a pattern is a common problem (e.g., becoming accustomed to a spectacular view) and generally-useful solution to that problem (blocking the view so that effort is required to obtain it), then an anti-pattern is what? I think most people think an anti-pattern is an aberration in the "solution" section that creates more problems. So here, the anti-pattern is that people use a term so casually (e.g., DevOps) that no one knows what it's referring to anymore. (The problem: need a way to refer to concept(s) in a pithy way. The solution: make up or reuse an existing word/phrase to incorporate the concept(s) by reference so that it can can, unambiguously, be used as a replacement for the longer description. )
- JadeNB 1y ago> If a pattern is a common problem (e.g., becoming accustomed to a spectacular view) and generally-useful solution to that problem (blocking the view so that effort is required to obtain it), then an anti-pattern is what? Strange choice of example! I'm not sure I agree that your example is a common problem, and I'm even less sure that the proposed solution to it is generally useful.
- readthenotes1 1y agoIt's name is Zen View, and is one of the memorable patterns from Alexander's catalog of design patterns
- JohnHaugeland 1y ago> If a pattern is a common problem it isn't, is the thing. if you read the book design patterns, they spell out what a pattern is. if you read the book anti-patterns, he spells out what an anti-pattern is. people have gotten the wrong idea by learning the phrases from casual usage.
- MaxBarraclough 1y agoPointing to books isn't very helpful here. Please just state the definition you are advocating.
- karmakaze 1y agoI'm waiting for the anti-patterns we shouldn't avoid.
- hobs 1y agohttps://pragprog.com/titles/bksqla/sql-antipatterns/ https://pragprog.com/titles/bksqla/sql-antipatterns/ There's an actual book on them that had me nodding along the entire time.
- evanelias 1y agoAgreed, it’s an excellent book by a great author. Bill is also quite prolific on Stack Overflow, and generally if you see an answer from him there, you can be confident it’s solid advice.
- JohnHaugeland 1y agothat's a fantastic book; one of the best i've read, and i'm glad to see it get brought up but also, the book anti-patterns is pretty clear here
- jacknews 1y ago"When handling large CASE WHEN statements, it is better to create a dimension table or view, ideally sourced from the landed table where the original status column is populated." Is this code for 'use a lookup table' or am I falling behind on the terminology? The modern term should be 'sum table' or something similar surely.
- LikesPwsh 1y ago"Dimension table" is the name for lookup tables in a star or snowflake schema.
- jacknews 1y agoTIL, Thanks. 'Landed table'? Is that the 'fact table', the one that contains the codes that need to be looked-up?
- tremon 1y agoI'm pretty sure the landed table refers to the local copy of the original source. In an ETL* pipeline, the place where source data is stored for further processing is usually called the landing zone. Fact and Dimension tables are outputs of the process, whereas the landing tables are the inputs. * in whatever order they're used
- parpfish 1y agobut sometimes large case statements cant be turned into a simple dimension table/lookup table because it's not a simple key-value transformation. if your case statement is just a series of straighahead "WHEN x=this THEN that", you're very lucky. the nasty case statements are the ones were the when expression sometimes uses different pieces of data and/or the ordering of the statements is important.
- jasonpbecker 1y agoWe did the views on view thing once when triggers, at least how we implemented them failed. This became a huge regret that we lived with for years and not-so affectionately called "view mountain". We finally slayed viewed mountain over the last 2 years and it feels so good.
- chongli 1y agoWhen working with larger enterprise software, it is common to have large CASE WHEN statements translating application status codes into plain English. For example, status code 1 could mean the item is out of stock. Why wouldn’t you store this information in a table and query it when you need it? What if you need to support other languages? With a table you can just add more columns for more languages!
- megaman821 1y agoI usually use generated columns for this. It still uses CASE WHEN but it is obvious to all consumers of the table that it exists.
- anthonyIPH 1y ago"Instead you should: query WHERE name = ‘abc’ create an indexed UPPER(name) column" Should there be an "or" between these 2 points, or am I missing something? Why create an UPPER index column and not use it?
- karmakaze 1y ago[and a third] OR use a case-insensitive collation for the name column.
- MiscCompFacts 1y agoI think they reversed the 2 expressions. You should use “WHERE UPPER(name) = ‘ABC’” if you want to use the index.
- wmonk 1y agoThe section of using functions on indexes could do with more explicit and deeper explanation. When you use the function on the index it becomes a full scan of the data instead as the query runner has to run the function on every row and column, effectively removing any benefit of the index. Unfortunately I learned this the hard way!
- LikesPwsh 1y agoSome well known docs on the topic- https://use-the-index-luke.com/sql/where-clause/obfuscation https://use-the-index-luke.com/sql/where-clause/obfuscation
- readthenotes1 1y ago"Unfortunately I learned this the hard way!" ... Seems to be the motto of SQL developers. Otoh, it seems a fairly stable language (family of dialects?) so finding the pitfalls has long leverage
- deleted 1y ago[deleted]
- tremon 1y agoThe given solution (create an indexed UPPER(name) column) is not the best way to solve this, at least not on MS SQL Server. Not sure if this is equally supported in other databases, but the better solution is to create a case-insensitive computed column: ALTER TABLE example ADD name_ci AS name COLLATE SQL_Latin1_General_CI_AS; (season to taste)
- layer8 1y agoIt depends on the database system, but for systems that support functional indexes, you can create an index using the same function expression that you use in the query, and the query optimizer will recognize that they match up and use the index. For example, you define an index on UPPER(name_column), and in your query you can use WHERE UPPER(name_to_search_for) = UPPER(name_column), and it will use the index.
- 1y ago
- EvanAnderson 1y ago> Overusing DISTINCT to “Fix” Duplicates Any time I see DISTINCT in a query I immediately become suspicious that the query author has an incomplete understanding of the data model, a lack of comprehension of set theory, or more likely both.
- Sesse__ 1y agoOr just doesn't know how to do semijoins in SQL, since they don't follow the same syntax as normal joins for whatever historical reason.
- leptons 1y agoAnd that's okay. Not every developer knows every single thing there is to know about every single tech. Sometimes you just need a solution, and someone with more specific knowledge can optimize later. How many non-database related mistakes would you make if you had to build every part of a system yourself?
- pessimizer 1y agoBut what if they don't know that they need your approval not to know things?
- bts89 1y agoThat’s almost always my experience too. Though fairly recently I learned that even with all the correct joins in place, sometimes adding a DISTINCT within a CTE can dramatically increase performance. I assume there’s some optimizations the query planner can make when it’s been guaranteed record uniqueness.
- dgb23 1y agoIf „select *“ breaks your code, then there‘s something wrong with your code. I think Rich Hickey talked about this. Providing more than is needed should never be a breaking change. Certain languages, formats and tools do this correctly by default. For the others you need a source of truth that you generate from.
- tremon 1y agoIf you have select * in your code, there already is something wrong with your code, whether it breaks or not: the performance and possibly output of your code is now dependent on the table definition. I'm pretty sure Rich Hickey has also talked about the importance of avoiding non-local dependencies and effects in your code.
- onli 1y agoThe performance and partly the output of the code is always dependent on the table definition. * instead of column names just removes an output limiter, which can be useful or can be irrelevant, depending on the context. Though sure, known to negatively affect performance, I think in some database systems more than in others?
- rileymat2 1y agoThe reasoning is in the article, and true. > Schema evolution can break your view, which can have downstream effects Select * is the problem itself in the face of schema evolution and things like name collision.
- sql_nitpicker 1y agoI don't see anything wrong with what the article is saying. If you have a view over a join of A and B, and the view uses "select *", then what is gonna happen when A adds a column with the same name as a column in B? In sqlite, the view definition will be automatically expanded and one of the columns in the output will automatically be distinguished with an alias. Which column name changes is dependent on the order of tables in the join. This can absolutely break code. In postgres, the view columns are qualified at definition time so nothing changes immediately. But when the view definition gets updated you will get a failure in the DDL. In any system, a large column can be added to one of the constituent tables and cause a performance problem. The best advice is to avoid these problems and never use "select *" in production code.
- skybrian 1y ago> three or four layers of subqueries, each one filtering or aggregating the results of the previous one, totaling over 5000 lines of code In a better language, this would be a pipeline. Pipelines are conceptually simple but annoying to debug, compared to putting intermediate results in a variable or file. Are there any debuggers that let you look at intermediate results of pipelines without modifying the code?
- tremon 1y agoThis is not a pipeline in the control flow sense; the full query is compiled into a single processing statement, and the query compiler is free to remove and/or reorder any of the subqueries as it sees fit. The intermediate results during query execution (e.g. temp table spools) do not follow the structure of the original query, as CTEs and subqueries are not execution boundaries. It's more accurate to compare this to a C compiler that performs aggressive link-time optimization, including new rounds of copy elision, loop unrolling and dead code elimination. If you want to build a pipeline and store each intermediate result, most tooling will make that easy for you. E.g. in dbt, just put each subquery in its separate file, and the processing engine will correctly schedule each subresult after the other. Just make sure you have enough storage available, it's not uncommon for intermediate results to be hundreds of times larger than the end result (e.g. when you perform a full table join in the first CTE, and do target filtering in another).
- skybrian 1y agoSure, a sufficiently smart compiler can do what it wants, but it's often conceptually a pipeline and could be implemented as one in debug mode, without having to rewrite the code. Not in production, though, since you don't want to store stuff in temporary files when you're not debugging them. In some languages, a series of assignments and a large expression will often compile to the same thing, but if written as assignments, it will make it easier to set breakpoints.
- physicles 1y agoI wrote some tooling to help debug sql queries with many CTEs. It parses the sql, finds all the CTEs, and prints the result of each CTE formatted as csv. If the .sql file changes on disk, it reruns the query and tells you which CTEs’ output changed. Saved me hours in debugging.
- egeozcan 1y agoI don't know about anti patterns but what I like to do is putting 1=1 after each WHERE to align ANDs nicely and this is enough to create huge dramas in PR reviews.
- ffsm8 1y agoIt's always perfectly aligned for me, because enter prefixes 2 whitespace in my ide in SQL files, ending with where a=1 And k=2 And v=3
- egeozcan 1y agoBut the first condition looks special while it isn't and it sometimes leads to changes touching one too many lines.
- DrewADesign 1y ago> what I like to do is putting 1=1 after each WHERE to align ANDs nicely Frankly, that sounds like one of those things that totally makes sense in the author’s head, but inconsiderately creates terrible code ergonomics and needless cognitive load for anyone reading it. You know to just ignore those expressions when you’re reading it because you wrote it and know they have no effect, but to a busy code reviewer, it’s annoying functionless clutter making their job more annoying. “Wait, that should do nothing… but does it actually do something hackish and ‘clever’ that they didn’t comment? Let’s think about this for a minute.” Use an editor with proper formatting capability, and don’t use executable expressions for formatting in code that other people look at.
- MobiusHorizons 1y agoI use `WHERE true` for this. Very little cognitive load parsing that. And it makes AND conditions more copy pastable. Effectively the trailing comma of SQL where clauses
- DrewADesign 1y ago
- kijin 1y agoSome of these things happen because people try to come up with a single clever query that does everything at once and returns a perfect spreadsheet. Translating status codes into English or some other natural language? That's better done in the application, not the database. Maybe even leave it to the frontend if you have one. As a rule of thumb, any transformation that does not affect which rows are returned can be applied in another layer after those rows have been returned. Just because you know SQL doesn't mean you have to do everything in SQL. Deeply nested subqueries? You might want to split that up into simpler queries. There's nothing shameful about throwing three stones to kill three birds, as long as you don't fall into the 1+N pattern. Whoever has to maintain your code will thank you for not trying to be too clever. Also, a series of simple queries often run faster than a single large query, because there's a limit to how well the query planner can optimize an excessively complicated statement. With proper use of transactions, you shouldn't have to worry about the data changing under your feet as you make these queries.
- petalmind 1y ago> Overusing DISTINCT to “Fix” Duplicates I wrote a small tutorial (~9000 words in two parts) on how to design complicated queries so that they don't need DISTINCT and are basically correct by construction. https://kb.databasedesignbook.com/posts/systematic-design-of-join-queries/ https://kb.databasedesignbook.com/posts/systematic-design-of...
- joshmn 1y agoNice articles in there. Bookmarked. Edit: it’s also actually a book!
- SoftTalker 1y agoA big one that isn't listed is looking for stuff that isn't there. Using != or NOT IN (...) is almost always going to be inefficient (but can be OK if other predicates have narrowed down the result set already). Also, understand how your DB handles nulls. Are nulls and empty strings the same? Does null == null? Not all databases do this the same way.
- magicalhippo 1y ago> Also, understand how your DB handles nulls. Also in regards to indexing. The DBs I've used have not indexed nulls, so a "WHERE col IS NULL" is inefficient even though "col" is indexed. If that is the case and you really need it, have a computed column with a char(1) or bit indicating if "col" is NULL or not, and index that.
- SoftTalker 1y agoNULL should generally never be used to "mean" anything. If your business rules say that "not applicable" or "no entry" is a value, store a value that indicates that, don't use NULL.
- rplnt 1y agoInteresting, I don't think I've seen that while NULLs are very common. I guess you would handle it in the application and not in the query, right?
- SoftTalker 1y agoI've seen it too, very often. But it's good if you can just keep NULL meaning NULL (i.e. "the absence of any value"), because otherwise you will eventually be surprised by behavior.
- crazygringo 1y agoNot sure what you mean. If you have a table of customers and someone of them don't have addresses, it's standard to leave the address fields NULL. If some of them don't belong to a company, it's standard to leave the company_id field NULL. This is literally what NULL is for. It's a special value precisely because missing data or a N/A field is so common. If you're suggesting mandatory additional has_address and has_customer_id fields, I would disagree. You'd be reinventing a database tool that already exists precisely for that purpose.
- btilly 1y agoThe biggest SQL antipattern is failing to recognize that SQL is actually a programming language. Therefore you should create a consistent indentation style for SQL. See https://bentilly.blogspot.com/2011/02/sql-formatting-style.html https://bentilly.blogspot.com/2011/02/sql-formatting-style.h... for mine. Second, you should try to group logical things together. This is why people should move subqueries into common table expressions. And finally, don't be afraid of commenting wisely.
- xyzzy_plugh 1y agoStyle opinions are borderline irrelevant without appropriate linters.
- javcasas 1y agoGo and use Google BigQuery auto-formatter in a complex query with CASE and EXTRACT YEAR FROM date, and you will have a totally different opinion. How that auto-formatter indents is borderly almost a hate crime. A thousand times better to indent manually.
- OscarCunningham 1y agoI've even seen the BigQuery formatter change the behaviour of a query, by mixing a keyword from a comment into the real code.
- aerzen 1y agoThese "anti-patterns" are just workarounds for bad language design of SQL (or lack of design actually). I'm working on a language that can run on SQL databases, so I hope it will do better with every one of these points. If anyone wants to check out a half-done lang with lacking documentation, I'd be happy to read your feedback: https://lutra-lang.org https://lutra-lang.org
- mkeedlinger 1y agoHey, this looks really cool! Best wishes and I’ll try to watch out for when this is more ready
- gregjor 1y ago"SQL database" doesn't describe anything. Variations of SQL have implementations on relational and non-relational databases. SQL and relational often get used interchangeably but given your goal you might want to use the terms more precisely. Experts including Codd recognized the problems with SQL since that language got traction. Some alternatives got proposed, perhaps most notably Tutorial D by Chris Date and Hugh Darwen. No SQL replacement goes anywhere because of the vast quantity of SQL code and supporting tools dating back decades. Chris Date wrote the textbook on databases, and at least one book going through the problems with SQL and various implementations of the relational model. SQL perfectly illustrates what Strostrup meant by "There are only two kinds of languages: the ones people complain about and the ones nobody uses." In some sense I would welcome a better query language. On the other hand I attribute decades of job security and steady income to knowing SQL and dealing with its problems.
- Arch-TK 1y agoForgot to add (all seen in production): * Don't store UUIDs as strings. * Don't use random UUID variants for your primary key (or don't use UUIDs for your primary key). * Don't use a random column in your clustered index.
- MichaelNolan 1y agoI guess things are DB dependent. Spanner for instance not only recommends using uuidv4 as a PK, it also stores it as string(36). Uuidv4 as a PK works fine on Postgres as well.
- darkerside 1y agoCan you share any details? A teammate wants to change primary identifiers to a GUID, but I'm not sure it's a good idea.
- Arch-TK 1y agoUUIDs take up 36 bytes as strings so store them natively as 16 bytes instead if you can. This is still 2x the space of an auto increment number. This is overhead for every table, every index, and every relationship. That might be acceptable in your case though, the case where it became unacceptable in my experience was in a MSSQL Express context. But it was an idiotic decision to use MSSQL to begin with in that scenario. Regarding random clustered indexes. Broadly speaking you want your clustered index to be made up of some incremental unique set of fields. I mean, technically there is not a massive issue, but the largest tables in your database will be the non-indexes (indexes are just tables) and you want your big, mainly append only, tables to be nicely compact so a bunch of space isn't taken up by half full pages. But again, I should honestly have clarified that the problem was mainly an MSSQL Express problem where databases are limited to 10GiB. You might honestly be fine, but do look for documentation on your specific database.
- thehours 1y ago> Mishandling Excessive Case When Statements User Defined Functions (UDFs) are another option to consolidate the logic in one place. > Using Functions on Indexed Columns In other words, the query is not sargable [0] > Overusing DISTINCT to “Fix” Duplicates Orthogonal to author's point about dealing with fanout from joins, I'm a fan of using something like this for 'de-duping' records that aren't exact matches in order to conform the output to the table grain: ROW_NUMBER() OVER (PARTITION BY <grain> ORDER BY <deterministic sort>) = 1 Some database engines have QUALIFY [1], which lends itself to a fairly clean query. [0] https://en.wikipedia.org/wiki/Sargable https://en.wikipedia.org/wiki/Sargable [1] https://docs.aws.amazon.com/redshift/latest/dg/r_QUALIFY_clause.html https://docs.aws.amazon.com/redshift/latest/dg/r_QUALIFY_cla...
- andersmurphy 1y agoNon sargability easy to solve with expression indexes. At least in sqlite.
- exceldrawing 1y agoOracle DATE field stores a time component. You have to be aware and adjust your queries to be specific.
- BrenBarn 1y ago> SQL is one of those languages that looks simple on the surface but grows in complexity as teams and systems scale. The funny thing is it's actually several of those languages. :-)
- ftchd 1y agothe points are fine and helpful, but they seem like a note from the author to themself rather than a cheatsheet that tries to be exhaustive. was surprised to not see anything about dates/time.
- seanhunter 1y agoI can’t take any article like this seriously if it doesn’t lead with the #1 sql antipattern which kills performance all the time - doing things row-by-row instead of understanding that databases operate on relations, so you need to do operations over whole relations. Very often I have seen this problem buried in code design and it always sucks. Sometimes an orm obscures this but the basic antipattern looks like Select some stuff For each row in stuff: … do some important things … Select a thing to do with this row … maybe do some other things … Early on in my career an old-hand sql guru said to me “any time you are doing sql in a loop, you are probably doing it wrong”. The non-sucky version of the code above is Select some stuff, joining on all the things you need for the rows because databases are great For each row in stuff: … do some important things … … maybe do some other things …
- ddxv 1y agoI've built myself a few problems that I haven't fixed yet: Many materialized views that rely on materialized views. When one at the bottom, or a table, needs a changed all views need to be dropped and recreated. Using a warm standby for production. I love having a read only production database, but since it's not the primary, it always feels like it's on the losing end of the system. Recently upgraded to Postgres 18 and forgot that means I need to rm rf the standby and pg_basebackup to rebuild... That wasn't fun.
- echelon 1y agoI'd like to call views, triggers, and integrity constraints antipatterns. Your code should handle the data model and never allow bad states to enter the database. There's too much performance loss and too many footguns from these "features".
- physicles 1y agoWhy do you need a warm standby for production? Do you need >= 3 nines? Our staging environment has its own instance that is rebuilt from prod, with pii removed, every day outside working hours (this normally takes about 15 minutes). It’s fantastic for testing migrations, and is easy to support compared with a warm standby.
- ddxv 1y ago"Do you need >= 3 nines?" No, it's a one person project. I'd be happy with a single 9 or even no 9s just a 99.0 haha I switched to warm standby to reduce stress on the production db which was in the cloud. There is just a single production server and having it constantly run the heavy data processing MVs + handle queries was CPU intensive and slowed everything down. The CPU was costly. To fix those issues, especially the CPU, I run the primary on a home server where it can crank the CPU as much as it wants running the data processing MVs and then sends the processed WALs to the warm standby that just handles the queries. This has fixed those CPU and slow queries (when an MV is updating a table that is being constantly read). But introduced headaches anytime I update postgres. My understanding is the 'fix' is to move data processing to another postgresql DB or flow? My biggest reason for not using another DB is I didn't like the idea of losing direct relations for keys. Anyways, I appreciate the input, it's been a thorny issue I hit once or twice a year and am always unsure if what I'm doing is 'normal' or what I should do to fix it.
- FuriouslyAdrift 1y agoThe single biggest thing that helped me speed up my queries and lower resource usage on the server was focusing on making my queries more sargable. https://en.wikipedia.org/wiki/Sargable https://en.wikipedia.org/wiki/Sargable https://www.brentozar.com/blitzcache/non-sargable-predicates/ https://www.brentozar.com/blitzcache/non-sargable-predicates...
- jdnier 1y agoLooking up the etymology of "sargeable", I found this StackOverflow answer: https://dba.stackexchange.com/a/217983 https://dba.stackexchange.com/a/217983 And Google explains "The term 'sargable' is a portmanteau of "Search ARGument ABLE," formed by combining the words from a SQL database context."
- crazygringo 1y agoI'm really curious, what communities use that word? I've been working with SQL for 20+ years, and have literally never come across that word a single time in any documentation, tutorial, Stack Overflow answer, or here on HN. Working in Postgres, MySQL and SQLite. Is it used at some particular company, or open source community, or with a particular database, or something?
- cowboylowrez 1y agoMy guess is mssql as I've seen the term quite a bit with those guys.
- FuriouslyAdrift 1y agoOracle, DB2, SQl Server... I've seen in the more Enterprise targeted docs for Postgres.
- MaxBarraclough 1y agoI'm sceptical of that article, it's making guesses about the limitations of SQL query optimisers. Consider the Simple example it presents. The article is in effect implying that no query optimiser would be able to figure out the equivalence of the two predicates. (Let's ignore that the two predicates aren't actually equivalent; the first version may raise an exception if myIntColumn is negative, depending on the DBMS.)
- bambax 1y ago> Excessive View Layer Stacking Guilty as charged. I love to do this. Materialized views aren't really possible on sqlite, and so I find stacking views on top of one another very readable and manageable. But it's true other people find it a little obscure and weird.
- spprashant 1y agoNot all of these are "anti-patterns", your query clause not matching your index is a problem of not understanding how indexes work. Some of these have nothing to do with SQL the language itself, and more to do with database schema design. If you have to do a DISTINCT, it means your primary key design is likely not right. If you are layering too many views, something is broken in the base table design, requiring the creation of all these views. A good database model goes a long way to avoiding all this.
- CuriousRose 1y agoI don’t fully agree with the nested view argument. In our context (POS software) we use them heavily to have a single source of truth for a clean transaction view, joining common tables like product, category, etc and then using that as the backbone for all user reporting that might be more/less complex. Not doing this means that we need to accommodate for each where clause in each table on each report. For example eliminating voided lines, voided transactions, returned transactions, etc. Not having this means that a single logic change would need to update 20+ views/stored procs so for our case I think its valid to nest.
- chuliomartinez 1y agoSQL makes it very hard to express real world requirements. 1. No easy way to limit child records count in joins - find all orders with orderproduct.amount is greater than X. Obviously this will genereate duplicates for orders that have more than one such orderproduct. So you slap a distinct on it… but what if you need an aggregation? The possible fixes are highly non-trivial: subqueries, window functions, or vendor specific: outer apply. 2. Or queries, that is when you group where conditions with OR are very hard (impossible) to optimize. Apart from the trivial case where the conditions are all on the same column, you are better of leaving the declarative world and imperatively tell sql to do a union. I wrote a bit about it here: https://www.inuko.net/blog/platform_sql_or_conditions_on_joins/ https://www.inuko.net/blog/platform_sql_or_conditions_on_joi...
- dfe 1y agoYour own article points out that exists handles the first case. Exists is not actually implemented as a subquery, it is merely syntactically a subquery.
- chuliomartinez 1y agoSure exists make sense if you dont need columns from the child table. Exists is also far from basic sql.
- procaryote 1y agoI don't really understand the problem in 1 in 2, looking at your article, from your first query it looks like person_relationship contains both (A,B) and (B,A) for all related people A and B; otherwise the left join won't work. If you also make people related to themselves and store (A,A) and (B,B) there your query becomes much simpler: SELECT other.id, other.name FROM person p JOIN person_relationship r ON r.from_person_id = p.id JOIN person other ON r.to_person_id = other.id WHERE p.family_id = @familyId;
- chuliomartinez 1y agoCreative solution! Of course this creates more maintenance to ensure such records exist. I guess you kinda proved my point, you have to work around it.
- KronisLV 1y agoNo mentions of EAV/OTLT, I will use this opportunity to have a crashout about it and how in some companies/regions for whatever reason it's overused to the point where you'll see it in most projects and it's never nice to work with: https://softwareengineering.stackexchange.com/questions/93124/eav-is-it-really-bad-in-all-scenarios https://softwareengineering.stackexchange.com/questions/9312... If I have to work with one more "custom field" or "classifier" implementation, I am going to cry. Your business domain isn't too hard to model, if you need a 100 different "entities" as a part of it, then you should have at least 100 different tables, instead of putting everything into an ill fitting grab bag. Otherwise you can't figure out what is connected to what by just looking at the foreign keys pointing to and from a table, because those simply don't exist. Developers inevitably end up creating shitty polymorphic links with similarly inevitable data integrity issues and also end up coupling the schema to the back end, so you don't get like "table" and "table_id" but rather "section" and "entity_id" so you can't read the schema without reading the back end code either. Before you know it, you're not working with the business domain directly, but it's all "custom fields this" and "custom fields that" and people end up tacking on additional logic, like custom_field_uses, custom_field_use_ids, custom_field_periods, custom_field_sources and god knows what else. If I wanted to work with fields that much, I'd go and work on a farm. Oh, you're afraid of creating 100 tables? Use codegen, even your LLM of choice has no issues with that. Oh, you're too afraid that you're gonna need to do blanket changes across them and will forget something? Surely you're not above a basic ADR, literally putting a Markdown file in a folder in the repo. Oh, you're afraid that something will go wrong in those 100 migrations? How is that any different than you building literally most of your app around a small collection of tables and having fewer migrations that will affect pretty much everything? Don't even get me started on what it's like when the data integrity issues and refactoring gone bad starts. Worst of all, people love taking that pattern and putting it literally everywhere, feels like I'm taking crazy pills and nobody seems to have an issue what it's like when most of the logic in your app has something to do with CustomFieldService. Fuck EAV/OTLT, thanks for coming to my rant. When it comes to bad patterns, it's very much up there, alongside using JSON in a relational database for the data that you can model and predict and put into regular columns, instead of just using JSON for highly dynamic data. > Excessive View Layer Stacking > In larger data environments, it’s easy to fall into the trap of layering views on top of views. At first, this seems modular and organized. But over time, as more teams build their own transformations on top of existing views, the dependency chain becomes unmanageable. Performance slows down because the database has to expand multiple layers of logic each time, and debugging turns into an archaeological dig through nested queries. The fix is to flatten transformations periodically and materialize heavy logic into clean, well-defined base views or tables. I will say that this is nice to strive for, but at the same time, I much prefer having at least a lot of views instead of dynamically generated SQL by the application (see: myBatis XML mappers), because otherwise with complex logic it's impossible to predict exactly how your application will query the DB and you'll need to run the app locally with logging debug levels on so you see the actual SQL, but god forbid you have noisy DB querying or an N+1 problem somewhere, log spam for days, so unpleasant to work with. It's even more fun when people start nesting mappers and fucking around with aliases, just give me MongoDB at this point, it's web scale.