sql

All things SQL and JDBC...
seancorfield 2020-06-25T00:08:02.394300Z

I wonder if user is treated as USER which is PG's session variable and "user" is treated as user which matches the (lowercase) table you created?

seancorfield 2020-06-25T00:08:27.394900Z

(our old DBA had a rule that you never create a plural table -- always singular)

seancorfield 2020-06-25T00:09:43.395700Z

MySQL's information_schema has all CAPITALIZED and nearly all plural table names, just as a data point.

adam 2020-06-25T00:11:02.396300Z

I went with singular names after reading the reasoning here: https://stackoverflow.com/a/5841297/1257775

seancorfield 2020-06-25T00:14:19.397100Z

The "irregular plurals" is a really good reason to stick with singular names.

adam 2020-06-25T00:46:04.397700Z

I am trying my luck in building a variation of find-by-keys... just find with the keys being optional and with limit and offset support. That would cover the majority of select use cases.

seancorfield 2020-06-25T00:48:51.398600Z

next.jdbc.sql.builder has a bunch of SQL string construction functions that might help you there (and they handle all the quoting stuff too).

seancorfield 2020-06-25T00:50:06.399400Z

limit and offset are MySQL specific aren't they? I know SQL Server has a different syntax. I don't think SQL-92 has a standard for those (I should look!)...

seancorfield 2020-06-25T00:51:50.400600Z

Note that :suffix can be provided in options to the query/find/get functions to provide a SQL string to append so :suffix "limit ? offset ?" would probably already let you do that...

adam 2020-06-25T00:52:39.401100Z

They are are available in Postgres: https://www.postgresql.org/docs/12/queries-limit.html

seancorfield 2020-06-25T00:52:40.401400Z

It was added originally to allow for FOR UPDATE to be appended to a SELECT.

seancorfield 2020-06-25T00:53:06.401500Z

Oh, good to know. Maybe I should make them core options in the "friendly" functions?

adam 2020-06-25T00:54:22.401900Z

Thanks, will look into it. Maybe I’ll cheat and use Honey SQL in the background. Still not sure :)

adam 2020-06-25T00:59:30.402Z

They would be certainly helpful. Almost every product have some sort of pagination.

seancorfield 2020-06-25T01:02:58.402200Z

Looks like the more standard approach is offset / fetch -- and although PG supports limit it is non-standard (and like MySQL's version)

adam 2020-06-25T01:14:11.402400Z

I see. Always used it via ORMs that’s why I got attached to the friendly functions quickly.

seancorfield 2020-06-25T02:30:22.405Z

This means that (sql/find-by-keys :table :all {:offset (* page page-size) :fetch page-size}) will let you page through all rows in TABLE and the query will be SELECT * FROM table OFFSET ? ROWS FETCH NEXT ? ROWS ONLY with the offset and fetch values added as query parameters.

seancorfield 2020-06-25T02:31:04.405900Z

Instead of :all you can still pass a hash map (query by example) or a vector ("SQL String" param1 param2 ..) to do a limited query.

seancorfield 2020-06-25T02:31:36.406400Z

(I wasn't planning to enhance and of the next.jdbc.sql functions... but...)

adam 2020-06-25T03:28:04.407800Z

Ah just saw the above. I was building this lol:

(defn find
  [connectable table {:keys [fields where offset limit] :or {fields :*}}]
  (execute! connectable (honey/format (-> {:select fields
                                           :from   [table]}
                                          (cond->
                                           where (assoc :where where)
                                           limit (assoc :limit limit)
                                           offset (assoc :offset offset)))
                                           :quoting :ansi)))
Will test the official implementation tomorrow.

adam 2020-06-25T18:42:31.411200Z

I didn’t realize how nicely HoneySQL plays with next.jdbc when I wrote the above. I ended up using an extremely thin wrapper to query my DB cleanly:

(db/query ds (db/fmt {:select [:id :name :role]
                               :from   [:user]}))

seancorfield 2020-06-25T19:49:46.418300Z

Just pushed an update that supports :columns in the options passed to find-by-keys. It supports basic aliases and computed expressions (if you provide an alias).

❤️ 1
seancorfield 2020-06-25T19:51:24.418500Z

But, yes, I'd mostly suggest using HoneySQL for anything beyond the basic operations. And I personally prefer using the honeysql.helpers so I'd write

(db/query ds (h/fmt (-> (select :id :name :role) (from :user))))

seancorfield 2020-06-25T19:52:03.418700Z

We use HoneySQL very heavily at work (one of my former colleagues gave a talk at Clojure/West about it, years ago).

seancorfield 2020-06-25T19:52:49.418900Z

I took over as maintainer of HoneySQL a while back and Justin (the original author) recently transferred it to my account so you can guarantee that next.jdbc + HoneySQL will always work well together.

adam 2020-06-25T20:22:33.419100Z

Thanks for the background info. Any technical for preferring honey’s helper functions, or just a matter of taste?

adam 2020-06-25T20:23:20.419300Z

I wrote the same thing twice, the data version was 181 chars and the fns version was 186

seancorfield 2020-06-25T21:02:41.420Z

With the data version, I tend to forget when things are nested in vectors vs just a vector of things.

seancorfield 2020-06-25T21:03:30.420200Z

And I find the fn-based code easier to read when composing, since it looks more like SQL than a whole bunch of assoc/`update` calls.

seancorfield 2020-06-25T21:04:59.420400Z

For example:

(-> (cond-> (-> (select      [:u.id :id])
                                (from        [:user :u])
                                (where       [:= :u.username username])
                                (user-status :u.id :has-user? true))
                      (not= username (u/username self))
                      (user-gender (:id self))
                      (seq exclude)
                      (merge-where [:not-in :q.id exclude]))
                    (add-order-by descriptor)
                    (sql/build :limit page-size :offset offset)
                    (sql/format :quoting :mysql))
That would be a lot uglier with assoc/`update` calls all over.

seancorfield 2020-06-25T21:05:38.420600Z

especially for things like user-status which is this logic:

(-> query
      (cond->
        has-user?
        (merge-where   [:and
                        [:= :u.id id-col]
                        [:= :u.suspended false]
                        [:= :u.deleted false]])
        (not has-user?)
        (merge-join    [:user :u]
                       [:and
                        [:= :u.id id-col]
                        [:= :u.suspended false]
                        [:= :u.deleted false]]))
      (merge-left-join [:approvedtadpole :tp]
                       [:= :tp.userid id-col])
      (merge-where     [:or
                        [:= :u.statusid 1]
                        [:and
                         [:= :u.statusid 2]
                         [:<> :tp.userid nil]]])))

adam 2020-06-26T00:48:23.421Z

Gotcha

seancorfield 2020-06-25T03:29:25.408600Z

The only thing "missing" from the official version is a list of fields -- it's always SELECT *. I'll have a think about that.

dharrigan 2020-06-25T03:38:47.408700Z

user is a reserved word in PostgreSQL. (actually a SQL definition). In all cases I use users, the only occasion, part from groups where I use plural table names.

dharrigan 2020-06-25T03:39:55.408900Z

select users.username, groups.name from users join groups on users.group_id = groups.id

dharrigan 2020-06-25T03:42:04.409200Z

There is a better way. Doing a select with limit and offset requires the db to load in the entire table and scan and filter - which can be very expensive.

dharrigan 2020-06-25T03:42:22.409400Z

This explains it, with some references to other examples:

dharrigan 2020-06-25T03:43:52.409900Z

I'm not advocating not doing your #124, simply that there are better ways, more efficient ways 🙂

dharrigan 2020-06-25T03:44:35.410100Z

Perhaps borrowing the concept of a seek function

seancorfield 2020-06-25T03:49:06.410600Z

@dharrigan If folks care about performance, they shouldn't be using the "friendly SQL functions" in the first place 🙂 and next.jdbc is not the "SQL police": folks who want to write SQL that does fast pagination (such as using a where condition that includes an id or timestamp field based on the "largest" value already seen), then they can do that via execute! (or better still using plan).

dharrigan 2020-06-25T03:50:03.410800Z

Indeed 🙂 Just pointing out (for others benefit who may be only aware of offset/limit) that there are alternatives 🙂

dharrigan 2020-06-25T03:50:30.411Z

A nicely crafted SQL query goes a long way 🙂

seancorfield 2020-06-25T19:22:32.413300Z

@daniel.stephen.lee There are two things at play with clojure.java.jdbc/insert-multi!: 1) if you specify a sequence of hash maps, c.j.j does a separate insert for each map (because they could be different) 2) if you use array of column names followed by array of rows (array of array of column values) then c.j.j attempts to use a single batch statement for the insert...

seancorfield 2020-06-25T19:23:53.414900Z

...however, some databases still do multiple round-trips for batch inserts 😞 so you need a database-specific option at connection time (in the JDBC URL string, effectively) to tell the driver to rewrite the batch to a single insert.

seancorfield 2020-06-25T19:24:45.415400Z

The c.j.j docs don't really cover that level of detail but the replacement for c.j.j does: https://cljdoc.org/d/seancorfield/next.jdbc/1.0.409/doc/getting-started/prepared-statements#caveats

seancorfield 2020-06-25T19:25:18.415900Z

"In particular, PostgreSQL requires the :reWriteBatchedInserts true option "

Dan 2020-06-25T19:29:11.416700Z

would I put :rewriteBatchedStatement true'` in the db-spec then?

Dan 2020-06-25T19:29:44.417100Z

or :reWriteBatchedInserts truerather for postgres

seancorfield 2020-06-25T19:29:58.417400Z

Yes, if you're using the array approach.

seancorfield 2020-06-25T19:30:08.417600Z

See my comment above.

seancorfield 2020-06-25T19:30:28.418Z

(in the message where I mentioned you to invite you in)