diff --git a/CHANGELOG.md b/CHANGELOG.md index 09db2f1..aa0a50a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changes * 2.6.next in progress + * Fix [#551](https://github.com/seancorfield/honeysql/issues/551) by supporting multiple `WINDOW` clauses. * Fix [#549](https://github.com/seancorfield/honeysql/issues/549) by using `:bb` conditionals to support Babashka (and still support Clojure 1.9.0), and add testing against Babashka so it is fully-supported as a target via PR [#550](https://github.com/seancorfield/honeysql/pull/550) [@borkdude](https://github.com/borkdude) * Address [#532](https://github.com/seancorfield/honeysql/issues/532) by adding support for `EXCLUDE` and `RENAME` and starting to write tests for XTDB compatibility. diff --git a/doc/clause-reference.md b/doc/clause-reference.md index a21d093..69ed736 100644 --- a/doc/clause-reference.md +++ b/doc/clause-reference.md @@ -1090,7 +1090,7 @@ but is rendered into the SQL later in precedence order. ## window, partition-by (and over) -`:window` accepts a pair of SQL entity (the window name) +`:window` accept alternating pairs of SQL entity (the window name) and the window "function" as a SQL clause (a hash map). `:partition-by` accepts the same arguments as `:select` above @@ -1116,6 +1116,25 @@ SELECT id, AVG(salary) OVER (PARTITION BY department ORDER BY designation ASC) A FROM employee WINDOW w AS (PARTITION BY department) "] +;; multiple windows: +user=> (sql/format {:select [:id + [[:over + [[:avg :salary] + {:partition-by [:department] + :order-by [:designation]} + :Average] + [[:max :salary] + :w + :MaxSalary]]]] + :from [:employee] + :window [:w {:partition-by [:department]} + :x {:partition-by [:salary]}]} + {:pretty true}) +[" +SELECT id, AVG(salary) OVER (PARTITION BY department ORDER BY designation ASC) AS Average, MAX(salary) OVER w AS MaxSalary +FROM employee +WINDOW w AS (PARTITION BY department), x AS (PARTITION BY salary) +"] ;; easier to write with helpers (and easier to read!): user=> (sql/format (-> (select :id (over [[:avg :salary] (-> (partition-by :department) (order-by :designation)) :Average] @@ -1128,6 +1147,18 @@ SELECT id, AVG(salary) OVER (PARTITION BY department ORDER BY designation ASC) A FROM employee WINDOW w AS (PARTITION BY department) "] +;; multiple window clauses: +user=> (sql/format (-> (select :id + (over [[:avg :salary] (-> (partition-by :department) (order-by :designation)) :Average] + [[:max :salary] :w :MaxSalary])) + (from :employee) + (window :w (partition-by :department)) + (window :x (partition-by :salary))) {:pretty true}) +[" +SELECT id, AVG(salary) OVER (PARTITION BY department ORDER BY designation ASC) AS Average, MAX(salary) OVER w AS MaxSalary +FROM employee +WINDOW w AS (PARTITION BY department), x AS (PARTITION BY salary) +"] ``` The window function in the `:over` expression may be `{}` or `nil`: diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index 51fa0ba..4d986ab 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -953,6 +953,9 @@ (defn- format-selector [k xs] (format-selects k [xs])) +(defn- format-window [k xs] + (format-selects k (into [] (partition-all 2 xs)))) + (declare columns-from-values) (defn- format-insert [k table] @@ -1643,7 +1646,7 @@ :where #'format-on-expr :group-by #'format-group-by :having #'format-on-expr - :window #'format-selector + :window #'format-window :partition-by #'format-selects :order-by #'format-order-by :limit #'format-on-expr diff --git a/test/honey/sql/helpers_test.cljc b/test/honey/sql/helpers_test.cljc index db6b89a..1e7c92c 100644 --- a/test/honey/sql/helpers_test.cljc +++ b/test/honey/sql/helpers_test.cljc @@ -533,6 +533,33 @@ " MAX(salary) OVER w AS MaxSalary" " FROM employee" " WINDOW w AS (PARTITION BY department)")])) + ;; multiple window tests + (is (= (-> (select :id + (over [[:avg :salary] (-> (partition-by :department) (order-by :designation)) :Average] + [[:max :salary] :w :MaxSalary])) + (from :employee) + (window :w (partition-by :department)) + (window :x (partition-by :salary)) + sql/format) + [(str "SELECT id," + " AVG(salary) OVER (PARTITION BY department ORDER BY designation ASC) AS Average," + " MAX(salary) OVER w AS MaxSalary" + " FROM employee" + " WINDOW w AS (PARTITION BY department)" + ", x AS (PARTITION BY salary)")])) + (is (= (-> (select :id + (over [[:avg :salary] (-> (partition-by :department) (order-by :designation)) :Average] + [[:max :salary] :w :MaxSalary])) + (from :employee) + (window :w (partition-by :department) + :x (partition-by :salary)) + sql/format) + [(str "SELECT id," + " AVG(salary) OVER (PARTITION BY department ORDER BY designation ASC) AS Average," + " MAX(salary) OVER w AS MaxSalary" + " FROM employee" + " WINDOW w AS (PARTITION BY department)" + ", x AS (PARTITION BY salary)")])) ;; test nil / empty window function clause: (is (= (-> (select :id (over [[:avg :salary] {} :Average]