(ns metabase.lib.schema.aggregation (:require [metabase.lib.hierarchy :as lib.hierarchy] [metabase.lib.schema.expression :as expression] [metabase.lib.schema.mbql-clause :as mbql-clause] [metabase.util.i18n :as i18n] [metabase.util.malli.registry :as mr])) | |
count has an optional expression arg. This is the number of non-NULL values -- corresponds to count( | (mbql-clause/define-catn-mbql-clause :count :- :type/Integer [:expression [:? [:schema [:ref ::expression/expression]]]]) |
cum-count has an optional expression arg | (mbql-clause/define-catn-mbql-clause :cum-count :- :type/Integer [:expression [:? [:schema [:ref ::expression/expression]]]]) |
(mbql-clause/define-tuple-mbql-clause :avg :- :type/Float [:schema [:ref ::expression/number]]) | |
number of distinct values of something. | (mbql-clause/define-tuple-mbql-clause :distinct :- :type/Integer [:schema [:ref ::expression/expression]]) |
(mbql-clause/define-tuple-mbql-clause :count-where :- :type/Integer [:schema [:ref ::expression/boolean]]) | |
min and max should work on anything orderable, including numbers, temporal values, and even text values. | (mbql-clause/define-tuple-mbql-clause :max [:schema [:ref ::expression/orderable]]) |
check either the type of the first arg, or fall back to | (lib.hierarchy/derive ::type-is-type-of-first-arg-or-number :lib.type-of/type-is-type-of-first-arg) |
(defmethod expression/type-of-method ::type-is-type-of-first-arg-or-number
[expr]
(let [expr-type ((get-method expression/type-of-method :lib.type-of/type-is-type-of-first-arg) expr)]
(if (= expr-type ::expression/type.unknown)
:type/Number
expr-type))) | |
(lib.hierarchy/derive :max ::type-is-type-of-first-arg-or-number) | |
apparently median and percentile only work for numeric args in Postgres, as opposed to anything orderable. Not sure this makes sense conceptually, but since there probably isn't as much of a use case we can keep that restriction in MBQL for now. | (mbql-clause/define-tuple-mbql-clause :median [:schema [:ref ::expression/number]]) |
(lib.hierarchy/derive :median ::type-is-type-of-first-arg-or-number) | |
(mbql-clause/define-tuple-mbql-clause :min [:schema [:ref ::expression/orderable]]) | |
(lib.hierarchy/derive :min ::type-is-type-of-first-arg-or-number) | |
(mr/def ::percentile.percentile
[:and
{:error/message "valid percentile"}
[:ref ::expression/number]
[:fn
{:error/message "percentile must be between zero and one"}
#(<= 0 % 1)]]) | |
(mbql-clause/define-tuple-mbql-clause :percentile #_expr [:ref ::expression/number] #_percentile [:ref ::percentile.percentile]) | |
(lib.hierarchy/derive :percentile ::type-is-type-of-first-arg-or-number) | |
(mbql-clause/define-tuple-mbql-clause :share :- :type/Float [:schema [:ref ::expression/boolean]]) | |
(mbql-clause/define-tuple-mbql-clause :stddev :- :type/Float [:schema [:ref ::expression/number]]) | |
(mbql-clause/define-tuple-mbql-clause :sum [:schema [:ref ::expression/number]]) | |
(mbql-clause/define-tuple-mbql-clause :cum-sum [:schema [:ref ::expression/number]]) | |
(lib.hierarchy/derive :sum ::type-is-type-of-first-arg-or-number) | |
(lib.hierarchy/derive :cum-sum ::type-is-type-of-first-arg-or-number) | |
(mbql-clause/define-tuple-mbql-clause :sum-where [:schema [:ref ::expression/number]] [:schema [:ref ::expression/boolean]]) | |
(lib.hierarchy/derive :sum-where ::type-is-type-of-first-arg-or-number) | |
(mbql-clause/define-tuple-mbql-clause :var :- :type/Float #_expr [:schema [:ref ::expression/number]]) | |
(doseq [tag [:avg
:count
:cum-count
:count-where
:distinct
:max
:median
:min
:offset
:percentile
:share
:stddev
:sum
:cum-sum
:sum-where
:var
;; legacy metric ref
:metric]]
(lib.hierarchy/derive tag ::aggregation-clause-tag)) | |
A clause is a valid aggregation if it is an aggregation clause, or it is an expression that transitively contains a single aggregation clause. | (defn- aggregation-expression?
[x]
(when-let [[tag _opts & args] (and (vector? x) x)]
(or (lib.hierarchy/isa? tag ::aggregation-clause-tag)
(some aggregation-expression? args)))) |
(mr/def ::aggregation
[:and
[:ref :metabase.lib.schema.mbql-clause/clause]
[:fn
{:error/message "Valid aggregation clause"}
aggregation-expression?]]) | |
(mr/def ::aggregations
[:sequential {:min 1} [:ref ::aggregation]]) | |
The list of available aggregation operator. The order of operators is relevant for the front end. | (def aggregation-operators
[{:short :count
:requires-column? false
:driver-feature :basic-aggregations
:display-info (fn []
{:display-name (i18n/tru "Count of rows")
:column-name (i18n/tru "Count")
:description (i18n/tru "Total number of rows in the answer.")})}
{:short :sum
:supported-field :metabase.lib.types.constants/summable
:requires-column? true
:driver-feature :basic-aggregations
:display-info (fn []
{:display-name (i18n/tru "Sum of ...")
:column-name (i18n/tru "Sum")
:description (i18n/tru "Sum of all the values of a column.")})}
{:short :avg
:supported-field :metabase.lib.types.constants/summable
:requires-column? true
:driver-feature :basic-aggregations
:display-info (fn []
{:display-name (i18n/tru "Average of ...")
:column-name (i18n/tru "Average")
:description (i18n/tru "Average of all the values of a column")})}
{:short :median
:supported-field :metabase.lib.types.constants/summable
:requires-column? true
:driver-feature :percentile-aggregations
:display-info (fn []
{:display-name (i18n/tru "Median of ...")
:column-name (i18n/tru "Median")
:description (i18n/tru "Median of all the values of a column")})}
{:short :distinct
:supported-field :any
:requires-column? true
:driver-feature :basic-aggregations
:display-info (fn []
{:display-name (i18n/tru "Number of distinct values of ...")
:column-name (i18n/tru "Distinct values")
:description (i18n/tru "Number of unique values of a column among all the rows in the answer.")})}
{:short :cum-sum
:supported-field :metabase.lib.types.constants/summable
:requires-column? true
:driver-feature :basic-aggregations
:display-info (fn []
{:display-name (i18n/tru "Cumulative sum of ...")
:column-name (i18n/tru "Sum")
:description (i18n/tru "Additive sum of all the values of a column.\ne.x. total revenue over time.")})}
{:short :cum-count
:requires-column? false
:driver-feature :basic-aggregations
:display-info (fn []
{:display-name (i18n/tru "Cumulative count of rows")
:column-name (i18n/tru "Count")
:description (i18n/tru "Additive count of the number of rows.\ne.x. total number of sales over time.")})}
{:short :stddev
:supported-field :metabase.lib.types.constants/summable
:requires-column? true
:driver-feature :standard-deviation-aggregations
:display-info (fn []
{:display-name (i18n/tru "Standard deviation of ...")
:column-name (i18n/tru "SD")
:description (i18n/tru "Number which expresses how much the values of a column vary among all rows in the answer.")})}
{:short :min
:supported-field :metabase.lib.types.constants/scope
:requires-column? true
:driver-feature :basic-aggregations
:display-info (fn []
{:display-name (i18n/tru "Minimum of ...")
:column-name (i18n/tru "Min")
:description (i18n/tru "Minimum value of a column")})}
{:short :max
:supported-field :metabase.lib.types.constants/scope
:requires-column? true
:driver-feature :basic-aggregations
:display-info (fn []
{:display-name (i18n/tru "Maximum of ...")
:column-name (i18n/tru "Max")
:description (i18n/tru "Maximum value of a column")})}]) |
(mr/def ::operator
[:map
[:lib/type [:= :operator/aggregation]]
[:short (into [:enum] (map :short) aggregation-operators)]
[:supported-field {:optional true} [:maybe :keyword]] ; TODO more precise type?
[:requires-column? :boolean]
[:driver-feature :keyword] ; TODO more precise type?
[:display-info fn?]]) | |