Add support for postgres GROUPING SET, ROLLUP and CUBE grouping operators
Add support for mysql WITH ROLLUP grouping operator Add support for GROUPING operator
This commit is contained in:
parent
31dc7b6dd3
commit
fa69565dbf
9 changed files with 476 additions and 70 deletions
|
|
@ -68,7 +68,7 @@ func GenerateDSN(dsn, destDir string, templates ...template.Template) (err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func openConnection(connectionString string) *sql.DB {
|
func openConnection(connectionString string) *sql.DB {
|
||||||
fmt.Println("Connecting to MySQL database")
|
fmt.Println("Connecting to MySQL database...")
|
||||||
db, err := sql.Open("mysql", connectionString)
|
db, err := sql.Open("mysql", connectionString)
|
||||||
throw.OnError(err)
|
throw.OnError(err)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func GenerateDSN(dsn, schema, destDir string, templates ...template.Template) (e
|
||||||
}
|
}
|
||||||
|
|
||||||
func openConnection(dsn string) *sql.DB {
|
func openConnection(dsn string) *sql.DB {
|
||||||
fmt.Printf("Connecting to postgres database")
|
fmt.Println("Connecting to postgres database...")
|
||||||
|
|
||||||
db, err := sql.Open("postgres", dsn)
|
db, err := sql.Open("postgres", dsn)
|
||||||
throw.OnError(err)
|
throw.OnError(err)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ func OR(expressions ...BoolExpression) BoolExpression {
|
||||||
return newBoolExpressionListOperator("OR", expressions...)
|
return newBoolExpressionListOperator("OR", expressions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ROW is construct one table row from list of expressions.
|
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
||||||
func ROW(expressions ...Expression) Expression {
|
func ROW(expressions ...Expression) Expression {
|
||||||
return NewFunc("ROW", expressions, nil)
|
return NewFunc("ROW", expressions, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -602,16 +602,16 @@ func LEAST(value Expression, values ...Expression) Expression {
|
||||||
type funcExpressionImpl struct {
|
type funcExpressionImpl struct {
|
||||||
ExpressionInterfaceImpl
|
ExpressionInterfaceImpl
|
||||||
|
|
||||||
name string
|
name string
|
||||||
expressions []Expression
|
parameters parametersSerializer
|
||||||
noBrackets bool
|
noBrackets bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFunc creates new function with name and expressions parameters
|
// NewFunc creates new function with name and expressions parameters
|
||||||
func NewFunc(name string, expressions []Expression, parent Expression) *funcExpressionImpl {
|
func NewFunc(name string, expressions []Expression, parent Expression) *funcExpressionImpl {
|
||||||
funcExp := &funcExpressionImpl{
|
funcExp := &funcExpressionImpl{
|
||||||
name: name,
|
name: name,
|
||||||
expressions: parameters(expressions),
|
parameters: parametersSerializer(expressions),
|
||||||
}
|
}
|
||||||
|
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
|
|
@ -623,18 +623,43 @@ func NewFunc(name string, expressions []Expression, parent Expression) *funcExpr
|
||||||
return funcExp
|
return funcExp
|
||||||
}
|
}
|
||||||
|
|
||||||
func parameters(expressions []Expression) []Expression {
|
func (f *funcExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
var ret []Expression
|
if serializeOverride := out.Dialect.FunctionSerializeOverride(f.name); serializeOverride != nil {
|
||||||
|
serializeOverrideFunc := serializeOverride(ExpressionListToSerializerList(f.parameters)...)
|
||||||
for _, expression := range expressions {
|
serializeOverrideFunc(statement, out, FallTrough(options)...)
|
||||||
if _, isStatement := expression.(Statement); isStatement {
|
return
|
||||||
ret = append(ret, expression)
|
|
||||||
} else {
|
|
||||||
ret = append(ret, skipWrap(expression))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
addBrackets := !f.noBrackets || len(f.parameters) > 0
|
||||||
|
|
||||||
|
if addBrackets {
|
||||||
|
out.WriteString(f.name + "(")
|
||||||
|
} else {
|
||||||
|
out.WriteString(f.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.parameters.serialize(statement, out, options...)
|
||||||
|
|
||||||
|
if addBrackets {
|
||||||
|
out.WriteString(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type parametersSerializer []Expression
|
||||||
|
|
||||||
|
func (p parametersSerializer) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
||||||
|
|
||||||
|
for i, expression := range p {
|
||||||
|
if i > 0 {
|
||||||
|
out.WriteString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, isStatement := expression.(Statement); isStatement {
|
||||||
|
expression.serialize(statement, out, options...)
|
||||||
|
} else {
|
||||||
|
skipWrap(expression).serialize(statement, out, options...)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFloatWindowFunc creates new float function with name and expressions
|
// NewFloatWindowFunc creates new float function with name and expressions
|
||||||
|
|
@ -646,28 +671,6 @@ func newWindowFunc(name string, expressions ...Expression) windowExpression {
|
||||||
return windowExpr
|
return windowExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *funcExpressionImpl) serialize(statement StatementType, out *SQLBuilder, options ...SerializeOption) {
|
|
||||||
if serializeOverride := out.Dialect.FunctionSerializeOverride(f.name); serializeOverride != nil {
|
|
||||||
serializeOverrideFunc := serializeOverride(ExpressionListToSerializerList(f.expressions)...)
|
|
||||||
serializeOverrideFunc(statement, out, FallTrough(options)...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addBrackets := !f.noBrackets || len(f.expressions) > 0
|
|
||||||
|
|
||||||
if addBrackets {
|
|
||||||
out.WriteString(f.name + "(")
|
|
||||||
} else {
|
|
||||||
out.WriteString(f.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
serializeExpressionList(statement, f.expressions, ", ", out)
|
|
||||||
|
|
||||||
if addBrackets {
|
|
||||||
out.WriteString(")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type boolFunc struct {
|
type boolFunc struct {
|
||||||
funcExpressionImpl
|
funcExpressionImpl
|
||||||
boolInterfaceImpl
|
boolInterfaceImpl
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,38 @@ package jet
|
||||||
type GroupByClause interface {
|
type GroupByClause interface {
|
||||||
serializeForGroupBy(statement StatementType, out *SQLBuilder)
|
serializeForGroupBy(statement StatementType, out *SQLBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GROUPING_SETS operator allows grouping of the rows in a table by multiple sets of columns in a single query.
|
||||||
|
// This can be useful when we want to analyze data by different combinations of columns, without having to write separate
|
||||||
|
// queries for each combination.
|
||||||
|
func GROUPING_SETS(expressions ...Expression) GroupByClause {
|
||||||
|
return Func("GROUPING SETS", expressions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
|
||||||
|
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||||
|
func ROLLUP(expressions ...Expression) GroupByClause {
|
||||||
|
return Func("ROLLUP", expressions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CUBE operator is used with the GROUP BY clause to generate subtotals for all possible combinations of a group of columns.
|
||||||
|
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||||
|
func CUBE(expressions ...Expression) GroupByClause {
|
||||||
|
return Func("CUBE", expressions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GROUPING function is used to identify which columns are included in a grouping set or a subtotal row. It takes as input
|
||||||
|
// the name of a column and returns 1 if the column is not included in the current grouping set, and 0 otherwise.
|
||||||
|
// It can be also used with multiple parameters to check if a set of columns is included in the current grouping set. The result
|
||||||
|
// of the GROUPING function would then be an integer bit mask having 1’s for the arguments which have GROUPING(argument) as 1.
|
||||||
|
func GROUPING(expressions ...Expression) IntegerExpression {
|
||||||
|
return IntExp(Func("GROUPING", expressions...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WITH_ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
|
||||||
|
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||||
|
func WITH_ROLLUP(expressions ...Expression) GroupByClause {
|
||||||
|
return newCustomExpression(
|
||||||
|
parametersSerializer(expressions), Token("WITH ROLLUP"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -386,7 +386,7 @@ func (n *wrap) serialize(statementType StatementType, out *SQLBuilder, options .
|
||||||
out.WriteString(")")
|
out.WriteString(")")
|
||||||
}
|
}
|
||||||
|
|
||||||
// WRAP wraps list of expressions with brackets '(' and ')'
|
// WRAP wraps list of expressions with brackets - ( expression1, expression2, ... )
|
||||||
func WRAP(expression ...Expression) Expression {
|
func WRAP(expression ...Expression) Expression {
|
||||||
wrap := &wrap{expressions: expression}
|
wrap := &wrap{expressions: expression}
|
||||||
wrap.ExpressionInterfaceImpl.Parent = wrap
|
wrap.ExpressionInterfaceImpl.Parent = wrap
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ var (
|
||||||
OR = jet.OR
|
OR = jet.OR
|
||||||
)
|
)
|
||||||
|
|
||||||
// ROW is construct one table row from list of expressions.
|
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
||||||
var ROW = jet.ROW
|
var ROW = jet.ROW
|
||||||
|
|
||||||
// ------------------ Mathematical functions ---------------//
|
// ------------------ Mathematical functions ---------------//
|
||||||
|
|
@ -281,3 +281,15 @@ var GREATEST = jet.GREATEST
|
||||||
|
|
||||||
// LEAST selects the smallest value from a list of expressions, or null if any of the expressions is null.
|
// LEAST selects the smallest value from a list of expressions, or null if any of the expressions is null.
|
||||||
var LEAST = jet.LEAST
|
var LEAST = jet.LEAST
|
||||||
|
|
||||||
|
// ----------------------- Group By operators ----------------------------//
|
||||||
|
|
||||||
|
// WITH_ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
|
||||||
|
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||||
|
var WITH_ROLLUP = jet.WITH_ROLLUP
|
||||||
|
|
||||||
|
// GROUPING function is used to identify which columns are included in a grouping set or a subtotal row. It takes as input
|
||||||
|
// the name of a column and returns 1 if the column is not included in the current grouping set, and 0 otherwise.
|
||||||
|
// It can be also used with multiple parameters to check if a set of columns is included in the current grouping set. The result
|
||||||
|
// of the GROUPING function would then be an integer bit mask having 1’s for the arguments which have GROUPING(argument) as 1.
|
||||||
|
var GROUPING = jet.GROUPING
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ var (
|
||||||
OR = jet.OR
|
OR = jet.OR
|
||||||
)
|
)
|
||||||
|
|
||||||
// ROW is construct one table row from list of expressions.
|
// ROW function is used to create a tuple value that consists of a set of expressions or column values.
|
||||||
var ROW = jet.ROW
|
var ROW = jet.ROW
|
||||||
|
|
||||||
// ------------------ Mathematical functions ---------------//
|
// ------------------ Mathematical functions ---------------//
|
||||||
|
|
@ -390,3 +390,34 @@ func castFloatLiteral(fraction FloatExpression) FloatExpression {
|
||||||
}
|
}
|
||||||
return fraction
|
return fraction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------- Group By operators --------------------------//
|
||||||
|
|
||||||
|
// GROUPING_SETS operator allows grouping of the rows in a table by multiple sets of columns(or expressions) in a single query.
|
||||||
|
// This can be useful when we want to analyze data by different combinations of columns, without having to write separate
|
||||||
|
// queries for each combination. GROUPING_SETS sets of columns are constructed with WRAP method.
|
||||||
|
//
|
||||||
|
// GROUPING_SETS(
|
||||||
|
// WRAP(Inventory.FilmID, Inventory.StoreID),
|
||||||
|
// WRAP(),
|
||||||
|
// ),
|
||||||
|
var GROUPING_SETS = jet.GROUPING_SETS
|
||||||
|
|
||||||
|
// WRAP wraps list of expressions with brackets - ( expression1, expression2, ... )
|
||||||
|
// The construct (a, b) is normally recognized in expressions as a row constructor. WRAP and ROW method behave exactly the same,
|
||||||
|
// except when used in GROUPING_SETS. For top level GROUPING SETS expression lists WRAP has to be used.
|
||||||
|
var WRAP = jet.WRAP
|
||||||
|
|
||||||
|
// ROLLUP operator is used with the GROUP BY clause to generate all prefixes of a group of columns including the empty list.
|
||||||
|
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||||
|
var ROLLUP = jet.ROLLUP
|
||||||
|
|
||||||
|
// CUBE operator is used with the GROUP BY clause to generate subtotals for all possible combinations of a group of columns.
|
||||||
|
// It creates extra rows in the result set that represent the subtotal values for each combination of columns.
|
||||||
|
var CUBE = jet.CUBE
|
||||||
|
|
||||||
|
// GROUPING function is used to identify which columns are included in a grouping set or a subtotal row. It takes as input
|
||||||
|
// the name of a column and returns 1 if the column is not included in the current grouping set, and 0 otherwise.
|
||||||
|
// It can be also used with multiple parameters to check if a set of columns is included in the current grouping set. The result
|
||||||
|
// of the GROUPING function would then be an integer bit mask having 1’s for the arguments which have GROUPING(argument) as 1.
|
||||||
|
var GROUPING = jet.GROUPING
|
||||||
|
|
|
||||||
|
|
@ -217,16 +217,95 @@ GROUP BY payment.customer_id;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGroupByWithRollup(t *testing.T) {
|
||||||
|
skipForMariaDB(t)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
Inventory.FilmID.AS("film_id"),
|
||||||
|
Inventory.StoreID.AS("store_id"),
|
||||||
|
GROUPING(Inventory.FilmID).AS("grouping_film_id"),
|
||||||
|
GROUPING(Inventory.FilmID, Inventory.StoreID).AS("grouping_film_id_store_id"),
|
||||||
|
COUNT(STAR).AS("count"),
|
||||||
|
).FROM(
|
||||||
|
Inventory,
|
||||||
|
).WHERE(
|
||||||
|
Inventory.FilmID.IN(Int(2), Int(3)),
|
||||||
|
).GROUP_BY(
|
||||||
|
WITH_ROLLUP(Inventory.FilmID, Inventory.StoreID),
|
||||||
|
).ORDER_BY(
|
||||||
|
Inventory.FilmID,
|
||||||
|
Inventory.StoreID,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT inventory.film_id AS "film_id",
|
||||||
|
inventory.store_id AS "store_id",
|
||||||
|
GROUPING(inventory.film_id) AS "grouping_film_id",
|
||||||
|
GROUPING(inventory.film_id, inventory.store_id) AS "grouping_film_id_store_id",
|
||||||
|
COUNT(*) AS "count"
|
||||||
|
FROM dvds.inventory
|
||||||
|
WHERE inventory.film_id IN (2, 3)
|
||||||
|
GROUP BY inventory.film_id, inventory.store_id WITH ROLLUP
|
||||||
|
ORDER BY inventory.film_id, inventory.store_id;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
FilmID int
|
||||||
|
StoreID int
|
||||||
|
GroupingFilmID int
|
||||||
|
GroupingFilmIDStoreID int
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"FilmID": 0,
|
||||||
|
"StoreID": 0,
|
||||||
|
"GroupingFilmID": 1,
|
||||||
|
"GroupingFilmIDStoreID": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FilmID": 2,
|
||||||
|
"StoreID": 0,
|
||||||
|
"GroupingFilmID": 0,
|
||||||
|
"GroupingFilmIDStoreID": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FilmID": 2,
|
||||||
|
"StoreID": 2,
|
||||||
|
"GroupingFilmID": 0,
|
||||||
|
"GroupingFilmIDStoreID": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FilmID": 3,
|
||||||
|
"StoreID": 0,
|
||||||
|
"GroupingFilmID": 0,
|
||||||
|
"GroupingFilmIDStoreID": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"FilmID": 3,
|
||||||
|
"StoreID": 2,
|
||||||
|
"GroupingFilmID": 0,
|
||||||
|
"GroupingFilmIDStoreID": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubQuery(t *testing.T) {
|
func TestSubQuery(t *testing.T) {
|
||||||
|
|
||||||
rRatingFilms := Film.
|
rRatingFilms := SELECT(
|
||||||
SELECT(
|
Film.FilmID,
|
||||||
Film.FilmID,
|
Film.Title,
|
||||||
Film.Title,
|
Film.Rating,
|
||||||
Film.Rating,
|
).FROM(
|
||||||
).
|
Film,
|
||||||
WHERE(Film.Rating.EQ(enum.FilmRating.R)).
|
).WHERE(
|
||||||
AsTable("rFilms")
|
Film.Rating.EQ(enum.FilmRating.R),
|
||||||
|
).AsTable("rFilms")
|
||||||
|
|
||||||
rFilmID := Film.FilmID.From(rRatingFilms)
|
rFilmID := Film.FilmID.From(rRatingFilms)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1370,26 +1370,26 @@ GROUP BY customer.customer_id
|
||||||
HAVING SUM(payment.amount) > 125.6
|
HAVING SUM(payment.amount) > 125.6
|
||||||
ORDER BY customer.customer_id, SUM(payment.amount) ASC;
|
ORDER BY customer.customer_id, SUM(payment.amount) ASC;
|
||||||
`
|
`
|
||||||
query := Payment.
|
query := SELECT(
|
||||||
INNER_JOIN(Customer, Customer.CustomerID.EQ(Payment.CustomerID)).
|
Customer.AllColumns,
|
||||||
SELECT(
|
|
||||||
Customer.AllColumns,
|
|
||||||
|
|
||||||
SUMf(Payment.Amount).AS("amount.sum"),
|
SUMf(Payment.Amount).AS("amount.sum"),
|
||||||
AVG(Payment.Amount).AS("amount.avg"),
|
AVG(Payment.Amount).AS("amount.avg"),
|
||||||
MAX(Payment.PaymentDate).AS("amount.max_date"),
|
MAX(Payment.PaymentDate).AS("amount.max_date"),
|
||||||
MAXf(Payment.Amount).AS("amount.max"),
|
MAXf(Payment.Amount).AS("amount.max"),
|
||||||
MIN(Payment.PaymentDate).AS("amount.min_date"),
|
MIN(Payment.PaymentDate).AS("amount.min_date"),
|
||||||
MINf(Payment.Amount).AS("amount.min"),
|
MINf(Payment.Amount).AS("amount.min"),
|
||||||
COUNT(Payment.Amount).AS("amount.count"),
|
COUNT(Payment.Amount).AS("amount.count"),
|
||||||
).
|
).FROM(
|
||||||
GROUP_BY(Customer.CustomerID).
|
Payment.
|
||||||
HAVING(
|
INNER_JOIN(Customer, Customer.CustomerID.EQ(Payment.CustomerID)),
|
||||||
SUMf(Payment.Amount).GT(Float(125.6)),
|
).GROUP_BY(
|
||||||
).
|
Customer.CustomerID,
|
||||||
ORDER_BY(
|
).HAVING(
|
||||||
Customer.CustomerID, SUMf(Payment.Amount).ASC(),
|
SUMf(Payment.Amount).GT(Float(125.6)),
|
||||||
)
|
).ORDER_BY(
|
||||||
|
Customer.CustomerID, SUMf(Payment.Amount).ASC(),
|
||||||
|
)
|
||||||
|
|
||||||
//fmt.Println(query.DebugSql())
|
//fmt.Println(query.DebugSql())
|
||||||
|
|
||||||
|
|
@ -1422,6 +1422,252 @@ ORDER BY customer.customer_id, SUM(payment.amount) ASC;
|
||||||
testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/customer_payment_sum.json")
|
testutils.AssertJSONFile(t, dest, "./testdata/results/postgres/customer_payment_sum.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGroupByGroupingSets(t *testing.T) {
|
||||||
|
skipForCockroachDB(t)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
GROUPING(Inventory.FilmID, Inventory.StoreID).AS("grouping_filmId_store_id"),
|
||||||
|
Inventory.FilmID.AS("film_id"),
|
||||||
|
Inventory.StoreID.AS("store_id"),
|
||||||
|
COUNT(Inventory.InventoryID).AS("count"),
|
||||||
|
).FROM(
|
||||||
|
Inventory,
|
||||||
|
).WHERE(
|
||||||
|
Inventory.FilmID.IN(Int(2), Int(3)),
|
||||||
|
).GROUP_BY(
|
||||||
|
GROUPING_SETS(
|
||||||
|
WRAP(Inventory.FilmID, Inventory.StoreID),
|
||||||
|
WRAP(Inventory.FilmID),
|
||||||
|
WRAP(),
|
||||||
|
),
|
||||||
|
).ORDER_BY(
|
||||||
|
Inventory.FilmID,
|
||||||
|
Inventory.StoreID,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT GROUPING(inventory.film_id, inventory.store_id) AS "grouping_filmId_store_id",
|
||||||
|
inventory.film_id AS "film_id",
|
||||||
|
inventory.store_id AS "store_id",
|
||||||
|
COUNT(inventory.inventory_id) AS "count"
|
||||||
|
FROM dvds.inventory
|
||||||
|
WHERE inventory.film_id IN (2, 3)
|
||||||
|
GROUP BY GROUPING SETS((inventory.film_id, inventory.store_id), (inventory.film_id), ())
|
||||||
|
ORDER BY inventory.film_id, inventory.store_id;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
GroupingFilmIDStoreID int
|
||||||
|
FilmID *int
|
||||||
|
StoreID *int
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
//testutils.PrintJson(dest)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"GroupingFilmIDStoreID": 0,
|
||||||
|
"FilmID": 2,
|
||||||
|
"StoreID": 2,
|
||||||
|
"Count": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupingFilmIDStoreID": 1,
|
||||||
|
"FilmID": 2,
|
||||||
|
"StoreID": null,
|
||||||
|
"Count": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupingFilmIDStoreID": 0,
|
||||||
|
"FilmID": 3,
|
||||||
|
"StoreID": 2,
|
||||||
|
"Count": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupingFilmIDStoreID": 1,
|
||||||
|
"FilmID": 3,
|
||||||
|
"StoreID": null,
|
||||||
|
"Count": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"GroupingFilmIDStoreID": 3,
|
||||||
|
"FilmID": null,
|
||||||
|
"StoreID": null,
|
||||||
|
"Count": 7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupByCube(t *testing.T) {
|
||||||
|
skipForCockroachDB(t)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
Country.Country.AS("country"),
|
||||||
|
City.City.AS("city"),
|
||||||
|
COUNT(City.CityID).AS("count"),
|
||||||
|
).FROM(
|
||||||
|
City.INNER_JOIN(
|
||||||
|
Country,
|
||||||
|
Country.CountryID.EQ(City.CountryID),
|
||||||
|
),
|
||||||
|
).WHERE(
|
||||||
|
Country.Country.EQ(String("Belarus")),
|
||||||
|
).GROUP_BY(
|
||||||
|
CUBE(Country.Country, City.City),
|
||||||
|
).ORDER_BY(
|
||||||
|
Country.Country,
|
||||||
|
City.City,
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT country.country AS "country",
|
||||||
|
city.city AS "city",
|
||||||
|
COUNT(city.city_id) AS "count"
|
||||||
|
FROM dvds.city
|
||||||
|
INNER JOIN dvds.country ON (country.country_id = city.country_id)
|
||||||
|
WHERE country.country = 'Belarus'::text
|
||||||
|
GROUP BY CUBE(country.country, city.city)
|
||||||
|
ORDER BY country.country, city.city;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
Country string
|
||||||
|
City string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Country": "Belarus",
|
||||||
|
"City": "Mogiljov",
|
||||||
|
"Count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Country": "",
|
||||||
|
"City": "Mogiljov",
|
||||||
|
"Count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Country": "Belarus",
|
||||||
|
"City": "Molodetno",
|
||||||
|
"Count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Country": "",
|
||||||
|
"City": "Molodetno",
|
||||||
|
"Count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Country": "",
|
||||||
|
"City": "",
|
||||||
|
"Count": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Country": "Belarus",
|
||||||
|
"City": "",
|
||||||
|
"Count": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupByRollup(t *testing.T) {
|
||||||
|
skipForCockroachDB(t)
|
||||||
|
|
||||||
|
stmt := SELECT(
|
||||||
|
EXTRACT(YEAR, Rental.RentalDate).AS("year"),
|
||||||
|
EXTRACT(MONTH, Rental.RentalDate).AS("month"),
|
||||||
|
EXTRACT(DAY, Rental.RentalDate).AS("day"),
|
||||||
|
COUNT(Rental.RentalID).AS("count"),
|
||||||
|
).FROM(
|
||||||
|
Rental,
|
||||||
|
).WHERE(
|
||||||
|
Rental.RentalDate.LT(Timestamp(2005, 5, 26, 1, 1, 1)),
|
||||||
|
).GROUP_BY(
|
||||||
|
ROLLUP(
|
||||||
|
EXTRACT(YEAR, Rental.RentalDate),
|
||||||
|
EXTRACT(MONTH, Rental.RentalDate),
|
||||||
|
EXTRACT(DAY, Rental.RentalDate),
|
||||||
|
),
|
||||||
|
).ORDER_BY(
|
||||||
|
IntegerColumn("year").ASC(),
|
||||||
|
EXTRACT(MONTH, Rental.RentalDate).ASC(),
|
||||||
|
IntegerColumn("day").ASC(),
|
||||||
|
)
|
||||||
|
|
||||||
|
testutils.AssertDebugStatementSql(t, stmt, `
|
||||||
|
SELECT EXTRACT(YEAR FROM rental.rental_date) AS "year",
|
||||||
|
EXTRACT(MONTH FROM rental.rental_date) AS "month",
|
||||||
|
EXTRACT(DAY FROM rental.rental_date) AS "day",
|
||||||
|
COUNT(rental.rental_id) AS "count"
|
||||||
|
FROM dvds.rental
|
||||||
|
WHERE rental.rental_date < '2005-05-26 01:01:01'::timestamp without time zone
|
||||||
|
GROUP BY ROLLUP(EXTRACT(YEAR FROM rental.rental_date), EXTRACT(MONTH FROM rental.rental_date), EXTRACT(DAY FROM rental.rental_date))
|
||||||
|
ORDER BY year ASC, EXTRACT(MONTH FROM rental.rental_date) ASC, day ASC;
|
||||||
|
`)
|
||||||
|
|
||||||
|
var dest []struct {
|
||||||
|
Year *int
|
||||||
|
Month *int
|
||||||
|
Day *int
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
err := stmt.Query(db, &dest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testutils.AssertJSON(t, dest, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Year": 2005,
|
||||||
|
"Month": 5,
|
||||||
|
"Day": 24,
|
||||||
|
"Count": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Year": 2005,
|
||||||
|
"Month": 5,
|
||||||
|
"Day": 25,
|
||||||
|
"Count": 137
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Year": 2005,
|
||||||
|
"Month": 5,
|
||||||
|
"Day": 26,
|
||||||
|
"Count": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Year": 2005,
|
||||||
|
"Month": 5,
|
||||||
|
"Day": null,
|
||||||
|
"Count": 154
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Year": 2005,
|
||||||
|
"Month": null,
|
||||||
|
"Day": null,
|
||||||
|
"Count": 154
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Year": null,
|
||||||
|
"Month": null,
|
||||||
|
"Day": null,
|
||||||
|
"Count": 154
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAggregateFunctionDistinct(t *testing.T) {
|
func TestAggregateFunctionDistinct(t *testing.T) {
|
||||||
stmt := SELECT(
|
stmt := SELECT(
|
||||||
Payment.CustomerID,
|
Payment.CustomerID,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue