diff --git a/README.md b/README.md index 7d060ef..a7f73e1 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,36 @@ The `ilike` and `not-ilike` operators can be used to query data using a pattern => ["SELECT name FROM products WHERE name NOT ILIKE ?" "%name%"] ``` +### case when then else + +The case condition expression can be used to `select` or `where` clauses. +- case-when +```clojure +(-> (select (case-when [:= :name "Bob"] "Mr. B." + [:= :name "Alisa"] "Strange Lady")) + (from :guests) + sql/format) +=> ["SELECT FROM guests CASE WHEN name = ? THEN ? WHEN name = ? THEN ? END" + "Bob" + "Mr. B." + "Alisa" + "Strange Lady"] +``` +- case-when-else +```clojure +(-> (select (case-when-else [:= :name "Bob"] "Mr. B." + [:= :name "Alisa"] "Strange Lady" + "Unknown")) + (from :guests) + sql/format) +=> ["SELECT FROM guests CASE WHEN name = ? THEN ? WHEN name = ? THEN ? ELSE ? END" + "Bob" + "Mr. B." + "Alisa" + "Strange Lady" + "Unknown"] +``` + ### except ```clojure @@ -240,6 +270,35 @@ The `ilike` and `not-ilike` operators can be used to query data using a pattern 0.25 0.50 0.75] ``` +### join lateral + +Sometimes we need some pivot functionality to solve some analytical tasks or just improve the performance of our queries. In PostgreSQL we have `JOIN LATERAL` clauses to do that. +- join-lateral +```clojure +(-> (h/select [:%count.* :total-n] + [:%count.gr-coef :good-n] + [:%count.br-coef :bad-n] + [:%avg.gr-length :good-length] + [:%avg.gr-coef :good-coef]) + (h/from [:stats-open-results :r]) + (h/join [:stats-positions :p] [:= :p.id :r.position-id]) + (e/join-lateral [(h/select + [(e/case-when [:>= :strength-coef (sql/inline 3)] + :best-length) :gr-length] + [(e/case-when [:>= :strength-coef (sql/inline 3)] + :strength-coef) :gr-coef] + [(e/case-when [:< :strength-coef (sql/inline 3)] + :strength-coef) :br-coef]) + :z0] :true) + (h/where [:= :p.direction "UP"]) + (h/group :r.class-id) + sql/format) +=> ["SELECT count(*) AS total_n, count(gr_coef) AS good_n, count(br_coef) AS bad_n, avg(gr_length) AS good_length, avg(gr_coef) AS good_coef FROM stats_open_results r INNER JOIN stats_positions p ON p.id = r.position_id INNER JOIN LATERAL (SELECT (CASE WHEN strength_coef >= 3 THEN best_length END) AS gr_length, (CASE WHEN strength_coef >= 3 THEN strength_coef END) AS gr_coef, (CASE WHEN strength_coef < 3 THEN strength_coef END) AS br_coef) z0 ON true WHERE (p.direction = ?) GROUP BY r.class_id" + "UP"] +``` +- left-join-lateral +The same functionality but for the case when the existing of a pivot result for each row is not mandatory. + ### SQL functions The following are the SQL functions added in `honeysql-postgres` diff --git a/src/honeysql_postgres/format.cljc b/src/honeysql_postgres/format.cljc index 5b6b194..ed35da8 100644 --- a/src/honeysql_postgres/format.cljc +++ b/src/honeysql_postgres/format.cljc @@ -17,6 +17,8 @@ :within-group 55 :over 55 :insert-into-as 60 + :join-lateral 153 + :left-join-lateral 154 :partition-by 165 :window 195 :upsert 225 @@ -267,3 +269,40 @@ (-> extension-name util/get-first sqlf/to-sql))) + +(defn- format-join [type table pred] + (str (when type + (str (string/upper-case (name type)) " ")) + "JOIN LATERAL " (sqlf/to-sql table) + (when (some? pred) + (str " ON " (sqlf/format-predicate* pred))))) + +(defn- make-join [type join-groups] + (sqlf/space-join (map #(apply format-join type %) + (partition 2 join-groups)))) + +(defmethod format-clause :join-lateral [[_ join-groups] _] + (make-join :inner join-groups)) + +(defmethod format-clause :left-join-lateral [[_ join-groups] _] + (make-join :left join-groups)) + +(defn- format-case-preds [pred-thens] + (map (fn [[pred then]] + (str "WHEN " (sqlf/format-predicate* pred) + " THEN " (sqlf/to-sql then))) + (partition 2 pred-thens))) + +(defn- format-branches + [branches] + (str "CASE " (sqlf/space-join branches) " END")) + +(defmethod format-clause :case-when + [[_ pred-thens] _] + (format-branches (format-case-preds pred-thens))) + +(defmethod format-clause :case-when-else + [[_ pred-thens] _] + (let [else (last pred-thens)] + (format-branches (concat (vec (format-case-preds (drop-last pred-thens))) + [(str "ELSE " (sqlf/to-sql else))])))) diff --git a/src/honeysql_postgres/helpers.cljc b/src/honeysql_postgres/helpers.cljc index 2a590a1..b44d807 100644 --- a/src/honeysql_postgres/helpers.cljc +++ b/src/honeysql_postgres/helpers.cljc @@ -80,3 +80,19 @@ (defhelper drop-extension [m extension-name] (assoc m :drop-extension (sqlh/collify extension-name))) + +(defhelper join-lateral + [m clauses] + (assoc m :join-lateral clauses)) + +(defhelper left-join-lateral + [m clauses] + (assoc m :left-join-lateral clauses)) + +(defhelper case-when + [m clauses] + (assoc m :case-when clauses)) + +(defhelper case-when-else + [m clauses] + (assoc m :case-when-else clauses)) diff --git a/test/honeysql_postgres/postgres_test.cljc b/test/honeysql_postgres/postgres_test.cljc index 2f650a1..05bfd95 100644 --- a/test/honeysql_postgres/postgres_test.cljc +++ b/test/honeysql_postgres/postgres_test.cljc @@ -8,6 +8,8 @@ :refer [add-column alter-table + case-when + case-when-else create-extension create-table create-view @@ -19,6 +21,8 @@ drop-table filter insert-into-as + join-lateral + left-join-lateral on-conflict on-conflict-constraint over @@ -334,3 +338,36 @@ (-> (drop-extension :uuid-ossp) (sql/format :allow-dashed-names? true :quoting :ansi)))))) + +(deftest case-when-test + (is (= ["CASE WHEN x = ? THEN x WHEN x > ? THEN y END" 1 2] + (-> (case-when [:= :x 1] :x + [:> :x 2] :y) + sql/format)))) + +(deftest case-when-else-test + (is (= ["CASE WHEN x = ? THEN x WHEN x > ? THEN y ELSE ? END" 1 2 5] + (-> (case-when-else [:= :x 1] :x + [:> :x 2] :y + 5) + sql/format)))) + +(deftest join-lateral-test + (is (= ["SELECT count(x3), count(x0) FROM x_values INNER JOIN LATERAL (SELECT (CASE WHEN x > ? THEN x END) AS x3, (CASE WHEN x > ? THEN x END) AS x0) z ON true" 3 0] + (-> (select :%count.x3 + :%count.x0) + (from :x-values) + (join-lateral [(select + [(case-when [:> :x 3] :x) :x3] + [(case-when [:> :x 0] :x) :x0]) :z] :true) + sql/format)))) + +(deftest left-join-lateral-test + (is (= ["SELECT count(x3), count(x0) FROM x_values LEFT JOIN LATERAL (SELECT (CASE WHEN x > ? THEN x END) AS x3, (CASE WHEN x > ? THEN x END) AS x0) z ON true" 3 0] + (-> (select :%count.x3 + :%count.x0) + (from :x-values) + (left-join-lateral [(select + [(case-when [:> :x 3] :x) :x3] + [(case-when [:> :x 0] :x) :x0]) :z] :true) + sql/format))))