sql

2024-06-25T00:15:16.332269Z

Up to this point we have only been using execute! and execute-one! in next.jdbc. Can someone explain when we might want to use plan instead? I read the docs but i'm still not sure exactly when we should be using it for better performance.

seancorfield 2024-06-25T00:27:46.348569Z

The choice is mostly between execute! and plan -- if you want a single item as a hash map, execute-one! is probably your best bet.

seancorfield 2024-06-25T00:28:42.458219Z

execute! produces a full vector of hash maps. All the rows from the (Java) ResultSet are converted into Clojure hash maps, and a vector is built and returned. Then you perform whatever processing you want on that vector.

seancorfield 2024-06-25T00:31:32.746759Z

plan is intended to be used with reduce / transduce to apply a process to each row in turn, and to accumulate some sort of result (could be anything, including a vector). The process is able to directly access column values from the ResultSet without Clojure hash maps being built. The ResultSet can be larger than would fit in memory as a Clojure vector of Clojure hash maps -- it can be made to "stream" from the database in chunks. The process is able to abort at any point with reduced.

seancorfield 2024-06-25T00:32:19.647469Z

The better performance comes from not converting the whole ResultSet into a Clojure vector of Clojure hash maps.

seancorfield 2024-06-25T00:34:34.157959Z

The short answer to "when to use it" is "all the time" 🙂 But for small result sets, it probably doesn't make enough of a difference for you to change from (map my-process (jdbc/execute! ...)) to (into [] (map my-process) (jdbc/plan ...)) or a reduce etc.

seancorfield 2024-06-25T00:35:46.919989Z

If you just want a sequence of, say, ID columns from a bunch of rows, next.jdbc.plan/select! is an nice, efficient way to get that vector of values. It uses plan under the hood to avoid constructing all those Clojure hash maps.

seancorfield 2024-06-25T00:37:22.041879Z

You can see how select! works here https://github.com/seancorfield/next-jdbc/blob/develop/src/next/jdbc/plan.clj#L66-L70 and that might give you an idea of how you'd want to use plan instead of execute!...

seancorfield 2024-06-25T00:39:50.022129Z

(note that the vector cols branch does create Clojure hash maps -- but the other branch can avoid that depending on what cols is: a single column name -- no map construction; a function -- if it just "gets" columns from the row passed in then the overhead is only what it does with those column values)

2024-06-25T00:56:11.825409Z

Hmm ok very interesting. So is the only reason to do the query like: (plan/select! ds :id ["select * from table"]) instead of (jdbc/execute! ds ["select id from table"]) so it doesn't actually perform the conversion from java data structures to Clojure data structures and instead it would read the id value directly from the java result set and construct the resulting vector like that? I'm trying to understand why the select * in plan would be more efficient than doing select id directly in execute or even in a plan.

2024-06-25T00:56:33.229599Z

Also are you basically saying anywhere we are using execute to get multiple rows we should just use plan instead?

seancorfield 2024-06-25T00:56:48.390449Z

You'd still want to do select id.

2024-06-25T00:57:18.319619Z

Oh ok I was looking at the doc string and you had (plan/select! ds :id ["select * from table"])

seancorfield 2024-06-25T00:57:58.909709Z

The difference is that execute! produces [ {:table/id 1} {:table/id 2} .. ] (or just :id if you use the unqualified builder), but the select! produces [ 1 2 .. ]

seancorfield 2024-06-25T00:59:09.542079Z

(into [] (map :id) (jdbc/plan ds ["select id from table"])) is the same as (plan/select! ds :id ["select id from table"])

2024-06-25T01:04:48.550019Z

Ok so you are saying (plan/select! ds :id ["select id from table"]) will be faster than doing (jdbc/execute! ds ["select id from table"]) . What about for queries that are selecting multiple keys using select *?

seancorfield 2024-06-25T01:06:35.649589Z

They do not return the same thing.

seancorfield 2024-06-25T01:07:38.781149Z

plan/select! will return a vector of the ID column values. jdbc/execute! will return a vector of hash maps, each of which has the key :id (or :table/id by default) and the corresponding value.

2024-06-25T01:08:01.612549Z

Yeah I understand but effectively its the same thing

seancorfield 2024-06-25T01:08:08.487849Z

No, it's not.

seancorfield 2024-06-25T01:08:11.921479Z

That's the point.

2024-06-25T01:08:15.826049Z

Meaning represents the same data though

seancorfield 2024-06-25T01:09:52.534699Z

With jdbc/execute! you get a fully-formed Clojure data structure that you then subsequently process. With jdbc/plan you get a reducible collection (that hasn't done any work) and then you reduce (or transduce) it to run the query and then you can get the raw column values from the ResultSet without building Clojure data structures.

seancorfield 2024-06-25T01:10:42.408959Z

I guess, with the way this thread is going, I should take a step back and ask: How comfortable are you with transducers?

seancorfield 2024-06-25T01:11:16.461639Z

I'm wondering if you don't "get" plan because you haven't used transducers much...?

2024-06-25T01:17:44.069049Z

I only really have experience using Clojure functions like filter, map, reduce etc. I haven't used the transduce function before nor do I know why should I.

seancorfield 2024-06-25T01:22:15.357019Z

Ah, OK, that's probably why the difference between execute! and plan seems hazy then...

2024-06-25T01:29:30.193879Z

Yeah sorry about that lol. Seems I have some more learning to do. Thanks though