r/programming • u/wheatBread • 7d ago
Solving the 1+N Query Problem
https://acadia.engineering/blog/solving-the-1-plus-N-query-problem94
u/archipeepees 7d ago
ive been using EF Core for ORM and this hasn't really been a problem for me or any of my colleagues for at least a decade because we don't use lazy loading. database calls are explicit; mapping the results to objects is implicit. is everyone outside of .net still banging their heads against stuff like this?
43
u/alex-weej 7d ago
The problem is that unless you have engineering leadership that forces a stack on you, nobody can agree on solutions and network effects remain out of reach. N+1 is one of thousands of problems that have excellent solutions yet nobody uses.
12
u/MannerShark 7d ago
JS/TS gets a new ORM every other week, everyone uses different ones and they're all just miles behind C#
3
u/SourcerorSoupreme 5d ago
The JS community is exposed to a constant flux of junior developers that mistake accessibility/familiarity for expertise.
They keep reinventing without regard of past solutions they keep forgetting, if not ignoring, the lessons already learned by their forebears.
Unchecked hubris. It's Dunning-Krugger in action.
6
u/diegoeche 7d ago
I'm porting a legacy project to dotnet 8. Honestly, we have saved so much money fixing the code people wrote because they had no understanding what they are doing. Code is full of N+1s
They were in dotnet. They were banging their head against that stuff. I don't think ef core is a silver bullet. You can still do N+1s.
16
u/ericl666 7d ago
I absolutely love EF Core.
dbContext.Book.Include(I => Publisher).Where(w => w.Title == "my book").FirstOrDefaultAsync()
You just add Include/ThenInclude to add all the joins you need.
24
u/davehax1 7d ago
It's great for small queries and small data sets, but look up Cartesian explosion and also review the generated SQL when using .Include(). You'd be surprised at what gets generated and how quickly performance can degrade as the result set grows
16
u/AyrA_ch 7d ago
Cartesian explosion in EF can be mitigated by either adding
.AsSplitQuery()to the queries where necessary, or by specifying it as the global default during context setup.4
u/ChemicalRascal 7d ago
Yeah, it's still something you need to be aware of and correctly mentally model, though. And if you don't, or if your colleague doesn't even know about it, well, then it bites you in the ass.
4
u/ericl666 7d ago
Yeah, I make sure to watch out for that. I've made some massive cartesian explosions in my day for sure. There are some occasions where AsSplitQuery() is inescapable or you just break those queries out to raw sql.
if you are dealing with a lot of 1:1 entities, it works fantastic.
1
u/modernkennnern 6d ago
Something I've been looking into adding to one of our projects is updating our E2E snapshot tests — which as of now records all external requests as well as Activity (OpenTelemetry) happenings so I can see what's going on — to also log all SQL queries so we can see clear regressions in SQL queries.
My main blocker right now is the fact that if you have multiple inserts in one EF Core LINQ query then it generates a (documented) random ordering of the inserts, so the snapshots never match.
1
u/slvrsmth 5d ago
Split on keywords, sort the lines? You just need an indicator "something changed", not a valid query.
14
u/andrerav 7d ago
Same here. Been using EF for over 15 years and I can count on one hand the number of times N+1 has shown up. It's so easy to catch as well. Mind-bending to learn from this post how much misinformation and hate there is towards ORMs simply because even seasoned senior devs either aren't competent or using trash tools.
10
u/Crafty_Independence 7d ago
I've known some seniors who were adamantly opposed to ORMs while writing some of the least efficient SQL I've ever had to engage with.
2
u/i_am_bromega 7d ago
Yeah, I’ve had a N+1 problem once with EF, and it was my mistake that was easily fixed. I can understand ORMs may be less than ideal for certain types of systems, but I think they’re great for the vast majority of applications.
1
u/FuckFuckingKarma 6d ago
Some people think the point of ORMs is to abstract away the database so the developer doesn't have to think about its limitations.
Doesn't have to be like that though.
3
u/Schmittfried 7d ago
Nah, in Python this is a solved problem, too. Hibernate also solves this, with arguably more footguns though.
12
u/ericl666 7d ago
Most ORMs have a solution (Spring Data has one of the worst I must say syntax wise).
If you use Hibernate, I highly recommend disabling lazy loading though. You can effortlessly add in N+1 queries without even noticing with lazy loading on.
3
u/wildjokers 7d ago
Most ORMs have a solution (Spring Data has one of the worst I must say syntax wise).
Spring Data isn't an ORM.
2
2
1
u/CandidateNo2580 3d ago
I work with SQLAlchemy in Python and no, there are loading options for whatever situation you can think of built right into the ORM.
Sometimes I don't want to load the entire object graph into memory so lazy loading should be allowed, that's the developers problem to define.
16
u/wildjokers 7d ago
Is this the yoda version of n+1?
13
u/somebodddy 7d ago
That's the meaningful way to name it.
1is the desired query,Nmore queries hide behind the abstraction.If it really was
N+1- that is,Ndesired query and1hidden unintentional one - it wouldn't have been that much of an issue.
62
u/disposepriority 7d ago
There is a really easy way of avoiding this when you just don't use ORMs
21
55
u/andrerav 7d ago
ORM's worth their salt solved this at least a decade ago. And, most developers will implement N+1 anyway, without or without an ORM.
23
u/solve-for-x 7d ago
If you know SQL, it's relatively easy to spot when a developer has put a query inside a loop. But there is an entire generation of developers who are accustomed to asking their ORM to give them data without having to think about how it's fetching that data behind the scenes.
12
u/infrastructure 7d ago
I swear I remember a library I used years ago on rails projects called like bullet or magic bullet that would output where you are making n+1 queries with the ORM. Was pretty neat.
13
u/andrerav 7d ago
It was a gem called Bullet that helped identify ActiveRecord performance problems. ActiveRecord was very neat at the time of its release, but also made it extremely easy to do N+1.
1
u/baseballlover723 6d ago
Tbf, it also generally wasn't a huge deal to fix it either (basically just needed to tell the query which subobjects you planned to use). At least once I started using Rails at version 4. Idk if it was worse before that.
10
u/Schmittfried 7d ago
If you’re too lazy to learn the proper usage of your tools, it likely won’t be solved by not using those tools. Those same developers won’t care about proper indexing, query planning etc. either.
-6
u/andrerav 7d ago
Right. So because you're both incompetent at using ORM's, the n00b among you should write raw SQL so it's easier to spot their mistakes. Good plan.
17
u/Chroiche 7d ago
They didn't solve anything. They gave you tools to solve it, but devs need to consciously use them, which they often don't. And you can chain so deep with different lazy vs eager loads all over the shop thanks to these "tools" that it becomes a total mess to know what's actually going to run.
-8
11
u/shoot_your_eye_out 7d ago
This problem has nothing to do with ORMs. I’ve seen people write this anti pattern in raw sql too many times to count.
6
3
u/lamp-town-guy 7d ago
I wouldn't use ORM that doesn't have support for this. I was using solution to N+1 15+ years ago. There's no excuse to not have it implemented.
3
u/diegoeche 7d ago
Probably joking, but man... I wish.
I'm dealing with some fucking idiotic dotnet code that seem to have been written by people that thought like that. And since they are afraid of JOINS, or complex sql, then they get so many N+1s.
3
u/devraj7 7d ago
But it requires expertise in SQL.
If you use an ORM, that problem is solved automatically without you having to be a SQL expert, which is one of the points of ORMs.
WebLogic had resolved the N+1 problem in the early 2000s.
7
u/read_at_own_risk 7d ago
So instead of learning how to define your schema in SQL and how to use joins and indexes, you learn how to define your schema in code that gets mapped to SQL, and about eager/lazy loading (implying joins), how to use indexes (they're still there), how to bypass the ORM to run SQL directly, how to invalidate your cache after you've done that, how to rehidrate a resultset that has a composite candidate key (lol, no, you can't), how to get to the SQL the ORM generates so that you can diagnose performance issues, how to manipulate the ORM into generating the SQL you want...
ORMs make the easy parts easy and the hard parts much harder.
7
u/smoovewill 7d ago
SQL is a leaky abstraction. You may have to rewrite a query to something that is logically equivalent to take advantage of indexes / table structure.
But imo, ORMs are far more leaky, and I very often find I have to dig into the generated SQL / figure out how to get the ORM to emit the SQL I want (to avoid N+1 queries, to use indexes properly etc.).
I'm not dying on this hill by any means, but I strongly believe having intermediate knowledge of SQL is far more useful than having expert knowledge in an ORM (though the latter probably requires the former)
2
u/BaNyaaNyaa 3d ago
Honestly, I only like ORMs for the 90% of queries that are plainly straightforward. Get something by primary key, foreign key, simple conjunctions... As soon as there's a somewhat complex condition or a join, I want my SQL.
1
u/smoovewill 2d ago
Yea, this is basically where I land on work projects, since most people do prefer the ORM for those queries. The one thing I do try to push for is mapping query results (whether from ORM models or rows from a raw query) to plain objects (ie, dataclasses / structs / typescript interfaces), and not passing ORM objects around.
8
u/Schmittfried 7d ago
Pretty sure you’ll write your own, worse ORM then. ORMs solve the very real problem of avoiding tons of tedious and error-prone mapping overhead. They will also let you fall back to SQL for anything that would be too complicated to do on the ORM level, so you get the best of both worlds.
8
u/HappyAngrySquid 7d ago
There are plenty of query builders and scanner libraries that give you this without kneecapping you.
3
u/Schmittfried 7d ago
If they automatically map between your entity/DTO/DAO objects and your DB tables, they are ORMs in my book. What is an ORM if not a query builder with automated record mapping and maybe a structured/type-safe way to write queries?
2
u/FuckFuckingKarma 6d ago
Any widespread ORM out there has support for joins. If you're not joining that's on the developer not the tool. You can write the exact same inefficient SQL by hand, and I am sure some people do just that.
-5
u/Chroiche 7d ago
This. Especially with the advent of LLMs (I know, I know). It's just so much easier to know what the actual fuck is going on too.
GQL is also a plague.
17
u/disposepriority 7d ago
Looking at sql which you can copy, paste and run explain analyze on has always been more clear to me than a space wizard generating things behind the scenes.
For me, with how nice db driver/interaction libraries are (e.g. jdbcClient for spring) there's just no reason to use ORMs to save a few lines of SQL you'll make up for in 200 annotations anyway.
3
1
u/i_am_bromega 7d ago
Once you learn the ins and outs of your ORM of choice, it just makes so many things so easy that I ended up preferring it over every previous project I had worked on previously that didn’t have it. And if there’s something truly better suited for raw SQL, you just use that instead and benefit from the ORM everywhere else.
3
u/disposepriority 7d ago
I don't have the same experience, generally something that maps result set column names to classes and nothing else is preferable to me.
You get rid of the main annoying part, you can have it fail if something doesn't get mapped on either side (e.g. projection field is empty OR return set contains unmapped column) if that's what you're looking for - you don't deal with lazy loading (not that it should be the default anyway) you don't deal with cascades you don't deal with any kind of meta information ORMs sometimes need.
So what you miss out on is that you have to write simple selects or inserts yourself, which I don't find to be that annoying.
One situation where an ORM can save you a lot of time is if you're working with many, many projections of the same table/view which would require a pretty wide data set- in which case you could honestly just reuse the widest query and transfer some wasted bytes of the network if it's that annoying.
I guess it comes down to the average complexity of the SQL and database features used in a given project, I find it more annoying than pleasant to work with.
At the end of the day, the common, language agnostic denominator is the database regardless of stack, learning the ins and outs of your database and your language's driver for it is in my eyes a better idea.
-7
u/gjosifov 7d ago
Try to update 10 tables from Integer to Long in 10+ year code base with String as SQL
it is 1 year task for a team of 5in ORM it is 1 week and without retesting for any small change from the customer side
4
u/HappyAngrySquid 7d ago
What? Why would it take longer in one vs the other?
0
u/gjosifov 7d ago
because you don't have a compiler to check the type, you have to search the whole code base
if we add the fact that in a lot of codebases there are people who want to over complicate things (and this is industry standard, because metaprogramming is more fun that solving business problems) and this is a recipe for disaster with every change that is required, because you have edge cases that only 1-2 people in the company knows about them and they are very proud of that factSo now imagine, people with 2-3 years of experience and having to be part of such task
on paper it looks like a small job, easy, but the only true test is on production, because you don't know the edge cases, so you need those 1-2 people to hold the hand on the teamWith ORM even junior dev without any knowledge of the code base can make the change, because the compiler will help him
ORM acts as compiler for the SQL and it is hard to learn and to be able to produce the same queries as you will do in SQL, but for huge and unmaintainable codebases it is huge risk reduction
5
u/Norphesius 7d ago
Nifty. I think the strategy of removing Turing completeness from a process to gain more potential performance and guarantees about state is chronically overlooked. It's not exactly simple to do, and it's hard to recognize when it's even viable, but not every part of a program or language needs maximal computational power.
24
u/JimmyM_1 7d ago
I don't know why people hate on ORMs for this one, yes abstracting SQL can lead some devs to forget / not even know about what is actually happening under the hood, but it isn't that big of a deal!
Make sure your dev team is aware of the JOIN concept and you're good to go!
8
u/Schmittfried 7d ago
To be fair, after using Laravel’s ORM and Hibernate, I’m pretty amazed how far ahead django’s ORM is in terms of usability and making the efficient implementation the path of least resistance. You really have to jump through some hoops to make certain joins efficient with Hibernate that would be trivial with the django ORM.
3
u/baseballlover723 6d ago edited 6d ago
To be fair, after using Laravel’s ORM and Hibernate, I’m pretty amazed how far ahead django’s ORM is in terms of usability and making the efficient implementation the path of least resistance.
As a ruby person, I never got all the hate that ORMs got, since ActiveRecord was excellent (even if you could still footgun yourself) and I usually felt like I was basically writing sql via ruby DSL most of the time. Never had issues with the simple handful of tables with a few connections between them with it that most uncomplicated DBs look like.
And then I used Hibernate, and I now understand why everyone hates ORMs. I couldn't get forward and backward full object links between relations. I couldn't figure out how to get it to be able to choose if it should load all the sub relations or not. I had N+1 issues because JPA was too dumb to group them all together. I also couldn't get composite primary keys to work with relations either. Just everything felt like it was made hard for no real reason. And the DB isn't even that complicated. It's still a handful of tables with some connections between them.
So now I understand. People don't hate ORMs. They hate shitty ORMs. And there are evidently some popular and shitty ORMs still around for some reason.
3
u/Absolute_Enema 6d ago
Just everything felt like it was made hard for no real reason
Old Java libs in a nutshell.
1
u/JimmyM_1 7d ago
Haven't worked with either, but that sounds interesting.
I will look them up.
2
u/Schmittfried 7d ago
Honestly I’m still shocked that the Java world just accepts that Hibernate doesn’t offer a way to have safe and still uncomplicated automatic schema migrations without paying for something like Liquibase and using their unreliable Maven plugin. Django has first-class schema migrations built-in, and they just work. Java devs just live with the fact that they have to remember to also write an SQL script for every change they make to an entity class.
2
u/yaboiabrahamlincoln 7d ago
Hibernate does automatic schema migration if you have it turned on, but it can’t do something like detect a column name change and rename the column in the sql. It’ll pretty much treat it as a new column and leave the old one with all the data inaccessible. The way to do it correctly is with an explicit migration script, but it’ll only be relevant for existing dbs that have the old schema, new dbs will start with the new schema. The migration should deal with both cases, or do nothing in case the column is already correct. Not easy to do a column rename conditionally (I don’t know how off the top). So all in all, it’s much easier to do the explicit scripts to avoid this (every db follows the same migrations rather than potentially different paths to the same schema.
I’m curious how django’s orm handles this situation if you have time to respond. Will do some research later because hibernate + flyway has not been super fun for migrations
3
u/Schmittfried 7d ago edited 7d ago
So it doesn’t handle schema migrations. The create.sql feature is nowhere near full migration support in my book (or am I missing something?).
Django will diff your current models as they are in the code against the projection of applying all past migrations in order. It will then create a new migration file containing the changes necessary to make the virtual DB state match the models. Trivial cases like adding new columns, dropping columns etc. will be generated automatically. For renamed single columns it will ask if those should be a rename or if you removed one column and added another (i.e. drop + add). If multiple fields of the same model were renamed or for any other ambiguities, it will warn you and ask you to write a custom migration. And you can always invoke a command to create a bare migration file for you to fill out with more complex migration logic (custom order of steps, custom SQL, even custom Python).
All of that is DB-agnostic (except for the SQL you write yourself, though you can add multiple versions for different dialects) and defined in pure Python. It never hits a DB until you apply migrations. Migrations are basically an ordered list of Python objects describing changes, with Django providing predefined steps for all common building blocks like adding a column, creating an index etc..
That migration will have an ID and point to the previous ID, which makes them independent of filenames and allows squashing multiple migrations into one when the history becomes expensively long (it will even consolidate counteracting/obsolete changes into no-ops).
It also provides automatic inversion for non-destructive changes and allows you to define custom backwards migrations for destructive changes and custom Python/SQL, so that a well-maintained Django history is always fully reversible and allows jumping to arbitrary past versions.
Last time I checked, native Hibernate doesn’t even cover half of that. People commonly use additional tooling, but Flyway doesn’t support diffing at all and Liquibase needs a (poorly maintained) Maven plugin for that, and even then it will only diff against a reference DB, not a virtual state reconstructed from your migration history. So you have to make sure the DB matches the migration state you’re expecting. Support for custom naming strategies is also quite bad, I still remember I had to fall back to explicit column/index naming a few times because it just wouldn’t apply the naming strategy to the migration file. Support for
@Embeddedwas very lackluster, too. It simply wouldn’t correctly apply column lengths for string columns in embedded types. Not to mention, XML as a migration format sucks and Flyway is just applying arbitrary SQL scripts, so it can’t possibly provide the same level of diffing/analysis/safeguards without solving the halting problem or running all your scripts against a throwaway DB.Granted, I think Django doesn’t even support the embedded pattern, but my point is: Django ORM provides one coherent developer experience whereas my options in the Java world feel like I have to glue a set of tools with varying features together and hope my use case is covered by their common denominator. Which is odd, given the whole idea of Spring and similar projects was to provide a coherent and battle-tested experience.
-1
u/wildjokers 7d ago
ou really have to jump through some hoops to make certain joins efficient with Hibernate
You literally just write a join in HQL. There are no hoops to jump through.
1
u/Schmittfried 5d ago edited 5d ago
If HQL supports all the features you need for your query. Like, any complex query at all. Note the phrasing “certain joins”. Need to build a search query dynamically and still load all the joined tables into your object tree with a single roundtrip? Have fun fiddling around with the criteria API and using the correct join/fetch methods. Need to join multiple many-to-many relationships efficiently (i.e. one top-level query and then one query per relationship instead of one explosive join or N+1 queries)? Enjoy writing all of that logic yourself and fighting the entity cache (with pseudo queries that don’t actually hit the DB and are only there to make Hibernate link the cached entities). Because Hibernate can’t fucking figure it out. With Django it’s always
prefetch_related(list of navigational properties)and you’re done. No matter if it’s one relationship or 100, one nesting level or 10, Django picks the right join strategy for you. When I used Hibernate with Spring there were so many situations where I would have preferred to use the underlying EntityManager directly, but that’s not what all the default Spring patterns want you to do, so you choose between fighting Spring or fighting Hibernate.Also, if you have to resort to HQL you’re basically falling back to SQL in my book. The whole point of ORMs is to not write query boilerplate.
1
u/wildjokers 5d ago
When I used Hibernate with Spring there were so many situations where I would have preferred to use the underlying EntityManager directly, but that’s not what all the default Spring patterns want you to do, so you choose between fighting Spring or fighting Hibernate.
You literally just inject the EntityManager:
@Service public class AccountService {
private final EntityManager entityManager; public AccountService(EntityManager entityManager) { this.entityManager = entityManager; } @Transactional public void doSomething(Long accountId) { Account account = entityManager.find(Account.class, accountId); // use entityManager... }}
The whole point of ORMs is to not write query boilerplate.
That is not the even remotely the point of ORMs. The point is right in the name Object Relationship Mapper. Its sole purpose to map resultsets to objects.
Need to build a search query dynamically and still load all the joined tables into your object tree with a single roundtrip? Have fun fiddling around with the criteria API and using the correct join/fetch methods. Need to join multiple many-to-many relationships efficiently (i.e. one top-level query and then one query per relationship instead of one explosive join or N+1 queries)?
Just drop down to native SQL. Hibernate will still map your result set to an object.
1
u/Schmittfried 5d ago edited 5d ago
You literally just inject the EntityManager
Sure, now you have to create a custom repo implementation instead of the default way of using repo inferfaces with the method name DSL.
I didn’t say Spring+Hibernate can’t do these things. It’s just that the convenient low-boilerplate parts are extremely limited and doing anything slightly more complex instantly requires you to study the intricacies of the session context and how Spring interferes with that, or create several classes just because you wanted a single join strategy that wasn’t covered by the defaults despite being pretty standard and that would have been a single line with django.
I wish I could give you more specifics, but that experience was 2 years ago and I don’t have access to the code anymore. What burned itself into my memory was how ridiculously troublesome it was to get a query over three 1:n relations including a recursive one (a tree) with a few transitive 1:1 relations working as its supposed to (no combinatorial explosion, no additional N queries when processing the result set and accessing navigational properties).
My entire point is: If it’s this annoying to write efficient queries, many developers will simply not care. They will use their simple generated repo method and be done with it because N+1 queries probably won’t be prohibitively bad in many situations (and I’ve even heard one saying the Hibernate cache figures that out automatically anyway and they won’t try to be smarter than the ORM, vastly overestimating what Hibernate does in this case by default).
1
u/which1umean 7d ago
Maybe the hood is in the way of something somebody should be looking at if people don't know what's under the hood and it's causing problems. 😬
6
u/vbilopav89 7d ago
You don't solve the N+1 problem by eliminating for loops and recursions.
You solve N+1 problem by training and teaching SQL operators to think in sets and graphs instead of algos and data structures.
Programmers trained to think in algos and data structures will simply fallback to language that has loops and recursions (some ORM probably).
Yes, procedural extensions of SQL do have those things. Because they are, well, procedural extensions. That doesn't mean that loops and recursions aren't anti patterns in SQL or set based arithmetic and thinking. That they are.
But that doesn't mean they don't have a legit case in database programming. They do. Cursors come to mind for loops, rare but sometimes needed, or formula expression parser that works on a row level for recursions. Rarely needed, but it happened to me once. Although graph processing did require a lot of recursion but with introduction of the new graph langaugue features it becomes obsolote.
Hope this helps.
4
u/Pharisaeus 7d ago
Solving the 1+N Query Problem
Reminds me of: "communism is bravely overcoming problems... unknown in any other political system".
N+1 generally comes from using the wrong tool / using the tool incorrectly.
2
u/Groundbreaking-Fish6 7d ago
This is an add, but it is a real problem. However, most ORMs will solve this problem if you use them correctly.
This is also a problem with many libraries (vibe coding makes it worse) where libraries are included for a solution without understanding its implementation.
Relational DataBase Management Systems (DBMS) are optimized to store and retrieve data in a controlled manner allowing multiple processes to quickly and efficiently query data while also maintaining integrity. Other programming languages are optimized for algorithms or workflow. The Object Relational Mapping only converts the relational model to the object model to work with in code. How well it does that is up to the programmer not the ORM.
3
u/read_at_own_risk 7d ago
Object-relational mappers support neither object-orientation nor the relational model.
Object-oriented programming wasn't created to model state, it was designed to manage complexity by breaking a system into independent things that talk to each other. Data is incidental and encapsulated or passed between objects, rather than a blueprint for objects.
The relational model of data viewed tables as representing relations, and rows as representing facts about entities. Entities were not equated to rows and relationships did not exist "between tables". The contemporary naïve ER view that most developers and all ORMs use, is in fact the pre-relational "network data model". Go read Bachman's "The programmer as navigator" and see how well that aligns with the ORM approach today.
A more accurate name for ORMs would "network data model to SQL mappers". Entity-component systems are closer to being true object-relational mappers than ORMs are.
1
u/wannaliveonmars 4d ago
TL;DR - Acadia language is not Turing complete, and has no for-loops, apparently.
1
u/ProvablyTrue 6d ago
I think some commenters might be misunderstanding what I suspect (I don't know) the author means by "problem". I think the entire point is that this language removes the very possibility of a bad thing and fills the need with a purely declarative approach. Competent senior engineers not being likely to manifest the N + 1 doesn't change that they still have to think about it (also AI agents and new engineers can definitely get it wrong).
Everyone gets to decide whether they think it is worth the tradeoff (possibly losing capabilities) but I think it is a little weird if anyone thinks that "safer, more declarative, and putting less cognitive load on engineers" is meritless.
-5
u/vancha113 7d ago
Man hitting us with bangers left and right here. Thanks you for another interesting read ^ ^ After having to write a bunch of raw sql to fix this exact problem in django i can definitely say I'm curious to try out acadia.
-2
109
u/NoLegJoe 7d ago
Software Developers will do anything but learn SQL