Describe the feature
Implement with.go to support a With clause (CTE).
I have a fork repository that implements this API and has been successfully tested.
func (db *DB) With(alias interface{}, query interface{}, args ...interface{}) (tx *DB)
Usage
db.With("cte_name", "SELECT * FROM users").Table("cte_name").Scan(&users)
// WITH `cte_name` AS (SELECT * FROM users) SELECT * FROM `cte_name`
db.With("cte_name", "SELECT * FROM users WHERE name = ?", "name").Table("cte_name").Scan(&users)
// WITH `cte_name` AS (SELECT * FROM users WHERE name = 'name') SELECT * FROM `cte_name`
db.With("cte_name", db.Table("users").Where("name = ?", "name")).Table("cte_name").Scan(&users)
// WITH `cte_name` AS (SELECT * FROM users WHERE name = 'name') SELECT * FROM `cte_name`
db.With(clause.CTE{Alias: "cte_name", Columns: []string{"id", "name"}}, db.Table("users")).Table("cte_name").Scan(&users)
// WITH `cte_name`(`id`,`name`) AS (SELECT * FROM users) SELECT * FROM `cte_name`
db.With(clause.CTE{Recursive: true, Alias: "cte_name"}, db.Table("users")).Table("cte_name").Scan(&users)
// WITH RECURSIVE `cte_name` AS (SELECT * FROM users) SELECT * FROM `cte_name`
Can I make a pull request?
Motivation
I wanted to use gorm to write the with clause, but I couldn't find a way to do it in the documentation or in past issues.
First of all, I successfully implemented the StatementModifier to write the With clause, but that often tends to make the code longer.
The with clause is defined in SQL99 and is implemented in any DB that supports gorm. I figured that there must be an official gorm supported way to write this, so I found the unimplemented with.go while doing some research and decided to implement it.
Related Issues
-
2483 (No comments there)
Comment From: WinterYukky
Remarks: With clause is not support in MySQL < 8.
Comment From: jinzhu
Hello @WinterYukky
I would prefer to write code like following but don't use the with clause:
db.Table("? AS cte_name", db.Model(&User{}).Select("id", "name").Where("name = ?", "name")).Scan(&users)
Comment From: WinterYukky
Hello, and thank you @jinzhu
I wanted to use the With clause because I couldn't use it in the From clause, but I understood.
Comment From: WinterYukky
~~For those who want to use the With clause, please refer to the With implementation of StatementModifier in my repository.~~ If you want to use "with clause", please check https://github.com/go-gorm/gorm/issues/3955#issuecomment-881866443.
Comment From: shudipta
I also prefer with clause support. If you want, I can write a PR.
Comment From: WinterYukky
Thank you @shudipta for your help 😄! However, the maintainer doesn't seem to want to support "with clause".
If more people want to support "with clause", I will write a PR.
Comment From: WinterYukky
I'm sorry to reply for closed issue.
Currenty, gorm support to customize clauses, unlike when I opened this PR.
This means if we write struct to implemented clause.Interface, we can use any clauses without gorm's support.
This change is very nice.
I made the plugin project for unsupported clauses by gorm. If you want to use "with clause", please check this repository.
It is useful than StatementModifier.
Comment From: ceferrari
I'm sorry to reply for closed issue. Currenty, gorm support to customize clauses, unlike when I opened this PR. This means if we write struct to implemented
clause.Interface, we can use any clauses without gorm's support. This change is very nice.I made the plugin project for unsupported clauses by gorm. If you want to use "with clause", please check this repository. It is useful than
StatementModifier.
Awesome plugin! I used it to build a complex multilevel CTE that is not even exemplified in the documentation and it worked perfectly.
I could have used Raw for my query, but I also had to use Method Chaining with some joins and scopes, and they would't work with Raw but they did work with your plugin!
gorm should definetly incorporate it.
Comment From: lifegit
@WinterYukky thank you very much!
Comment From: rashetu-notch
I'm sorry to reply for closed issue. Currenty, gorm support to customize clauses, unlike when I opened this PR. This means if we write struct to implemented
clause.Interface, we can use any clauses without gorm's support. This change is very nice. I made the plugin project for unsupported clauses by gorm. If you want to use "with clause", please check this repository. It is useful thanStatementModifier.Awesome plugin! I used it to build a complex multilevel CTE that is not even exemplified in the documentation and it worked perfectly.
I could have used
Rawfor my query, but I also had to useMethod Chainingwith some joins and scopes, and they would't work withRawbut they did work with your plugin!
gormshould definetly incorporate it.
Hey do you have any example on that?
Comment From: ceferrari
I'm sorry to reply for closed issue. Currenty, gorm support to customize clauses, unlike when I opened this PR. This means if we write struct to implemented
clause.Interface, we can use any clauses without gorm's support. This change is very nice. I made the plugin project for unsupported clauses by gorm. If you want to use "with clause", please check this repository. It is useful thanStatementModifier.Awesome plugin! I used it to build a complex multilevel CTE that is not even exemplified in the documentation and it worked perfectly. I could have used
Rawfor my query, but I also had to useMethod Chainingwith some joins and scopes, and they would't work withRawbut they did work with your plugin!gormshould definetly incorporate it.Hey do you have any example on that?
Sure:
SELECT *
FROM (
WITH
cards_multi_version AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY version_id ORDER BY created_at DESC) AS rn
FROM cards
WHERE is_published = TRUE AND version_id IS NOT NULL
),
cards_single_version AS (
SELECT *, 1 AS rn
FROM cards
WHERE is_published = TRUE AND version_id IS NULL AND id NOT IN (SELECT version_id FROM cards_multi_version)
)
SELECT *
FROM cards_multi_version
WHERE rn = 1
UNION ALL
SELECT *
FROM cards_single_version
) cards
WHERE EXISTS (
SELECT 1
FROM card_classifications
WHERE card_classifications.card_id = cards.id
AND card_classifications.id IN (SELECT UNNEST(STRING_TO_ARRAY('{UUID_1},{UUID_2},...,{UUID_N}', ','))
)
Gets translated into:
func (r Repository) Get(ctx context.Context, classificationsStr string) ([]entities.card, error) {
var db *gorm.DB
cte := db.Clauses(exclause.With{CTEs: []exclause.CTE{
{Name: "cards_multi_version", Subquery: clause.Expr{SQL: `
SELECT *, ROW_NUMBER() OVER(PARTITION BY parent_id ORDER BY created_at DESC) AS rn
FROM cards
WHERE is_published = TRUE AND parent_id IS NOT NULL
`}},
{Name: "cards_single_version", Subquery: clause.Expr{SQL: `
SELECT *, 1 AS rn
FROM cards
WHERE is_published = TRUE AND parent_id IS NULL AND id NOT IN (SELECT parent_id FROM cards_multi_version)
`}},
}}).
Table("cards_multi_version").Where("rn = 1").
Clauses(exclause.NewUnion("ALL ?", db.Table("cards_single_version")))
db = db.Table("(?) as cards", cte).Where(`EXISTS (
SELECT 1
FROM card_classifications
WHERE card_classifications.card_id = cards.id
AND card_classifications.catalog_id IN (SELECT UNNEST(STRING_TO_ARRAY(?, ',')))
)`, classificationsStr)
db = db.Scopes(
// hidden
)
var cards []entities.card
err := db.Find(&cards).Error
if err != nil {
return nil, err
}
return cards, nil
}
The WHERE EXISTS used to be a JOIN when I posted my first answer, that's why I mentioned it. But it works both ways, you are free to use Method Chaining as much as you need