Skip to content

Support for filtered aggregations #356

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
kblomster opened this issue Jun 10, 2024 · 6 comments
Open

Support for filtered aggregations #356

kblomster opened this issue Jun 10, 2024 · 6 comments

Comments

@kblomster
Copy link
Contributor

kblomster commented Jun 10, 2024

Problem

SQL:2003 introduced filtered aggregations, e.g. SELECT COUNT(*) FILTER(WHERE foo > 0). They're currently supported in Postgres and SQLite.

If I want to use this syntax in Jet, I have to resort to Raw, which is inconvenient. I'd like to be able to build the FILTER(WHERE ...) clause in the same way I build any other WHERE clause.

To be fair, the ANSI SQL form of the FILTER clause is in many cases essentially syntactic sugar, and it can usually be emulated with a CASE statement, but this is not always true. Taking the CASE approach typically relies on aggregate functions ignoring nulls, but this does not work with functions such as Postgres' json_arrayagg, where nulls may or may not be a desired part of the output. Postgres also extends standard SQL by allowing subqueries like EXISTS() in the FILTER clause, and that can't be emulated with a CASE statement either.

Possible solution

Aggregate functions in dialects that support this syntax could return an extended Expression object that would allow adding a FILTER clause, similar to how the window OVER clause is implemented. Rough sketch:

type AggregateIntegerExpression interface {
    IntegerExpression
    FILTER_WHERE(expression BoolExpression) IntegerExpression
}

// usage:
SELECT(
    COUNT(STAR).
        FILTER_WHERE(SomeCol.IS_NOT_NULL))
@kblomster kblomster changed the title Support for ANSI SQL filtered aggregations Support for filtered aggregations Jun 10, 2024
@go-jet
Copy link
Owner

go-jet commented Jun 11, 2024

Related comment - #355 (comment).

@go-jet go-jet added this to the Version 2.12.0 milestone Nov 3, 2024
@go-jet
Copy link
Owner

go-jet commented Nov 3, 2024

With the release v2.12.0 users can now build custom expressions and operators using newly exposed CustomExpression and Token functions. (wiki)

@go-jet go-jet closed this as completed Nov 3, 2024
@brettinternet
Copy link

brettinternet commented Feb 19, 2025

Hi, do I have this right?

func FILTER_WHERE(agg Expression, where BoolExpression) Expression {
	return CustomExpression(agg, Token("FILTER (WHERE"), where, Token(")"))
}

SELECT(
	FILTER_WHERE(
		COUNT(DISTINCT(MyTable.ID)),
		MyOtherTable.Name.EQ("John"),
	),
)

Or can the agg type be more narrow than Expression?

@go-jet
Copy link
Owner

go-jet commented Feb 20, 2025

Yeah, it looks good.

You can also make it a bit more SQL like:

type filterExpand struct {
	agg Expression
}

func (f *filterExpand) WHERE(cond BoolExpression) Expression {
	return CustomExpression(f.agg, Token("FILTER (WHERE"), cond, Token(")"))
}

func FILTER(agg Expression) *filterExpand {
	return &filterExpand{agg}
}

then it can be used like:

SELECT(
	FILTER(COUNT(DISTINCT(MyTable.ID))).WHERE(MyOtherTable.Name.EQ("John")),
)

@go-jet
Copy link
Owner

go-jet commented Feb 20, 2025

Hmm, not sure why I closed this issue. It is a valid missing feature.

@go-jet go-jet reopened this Feb 20, 2025
@brettinternet
Copy link

Thank you for your solution, that's very helpful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants