Skip to content

add join-lateral && case-when clauses #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
39 changes: 39 additions & 0 deletions src/honeysql_postgres/format.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))]))))
16 changes: 16 additions & 0 deletions src/honeysql_postgres/helpers.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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))
37 changes: 37 additions & 0 deletions test/honeysql_postgres/postgres_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
:refer
[add-column
alter-table
case-when
case-when-else
create-extension
create-table
create-view
Expand All @@ -19,6 +21,8 @@
drop-table
filter
insert-into-as
join-lateral
left-join-lateral
on-conflict
on-conflict-constraint
over
Expand Down Expand Up @@ -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))))