{"id":13412195,"url":"https://github.com/leporo/sqlf","last_synced_at":"2026-01-02T13:04:30.818Z","repository":{"id":37010744,"uuid":"197893109","full_name":"leporo/sqlf","owner":"leporo","description":"Fast SQL query builder for Go","archived":false,"fork":false,"pushed_at":"2023-04-13T15:35:15.000Z","size":95,"stargazers_count":141,"open_issues_count":3,"forks_count":14,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-07-31T20:50:00.258Z","etag":null,"topics":["go","golang","sql-builder"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leporo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-07-20T07:03:27.000Z","updated_at":"2024-05-24T08:11:15.000Z","dependencies_parsed_at":"2024-01-30T04:53:23.660Z","dependency_job_id":null,"html_url":"https://github.com/leporo/sqlf","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leporo%2Fsqlf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leporo%2Fsqlf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leporo%2Fsqlf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leporo%2Fsqlf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leporo","download_url":"https://codeload.github.com/leporo/sqlf/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221490645,"owners_count":16831622,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","sql-builder"],"created_at":"2024-07-30T20:01:21.989Z","updated_at":"2026-01-02T13:04:25.788Z","avatar_url":"https://github.com/leporo.png","language":"Go","funding_links":[],"categories":["Database","Generators","数据库","数据库  `go语言实现的数据库`","Data Integration Frameworks","Uncategorized"],"sub_categories":["SQL Query Builders","Advanced Console UIs","SQL 查询语句构建库","SQL查询生成器"],"readme":"# sqlf\n\n[![GoDoc Reference](https://godoc.org/github.com/leporo/sqlf?status.svg)](http://godoc.org/github.com/leporo/sqlf)\n![Build Status](https://github.com/leporo/sqlf/actions/workflows/go.yml/badge.svg)\n[![Go Report Card](https://goreportcard.com/badge/github.com/leporo/sqlf)](https://goreportcard.com/report/github.com/leporo/sqlf)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#sql-query-builders)\n\n\nA fast SQL query builder for Go.\n\nWhat `sqlf` does?\n\n- It helps you efficiently build an SQL statement in run-time.\n- You may change the number of affected columns and change the number of arguments in a safe way.\n- You may use SQL expressions (like `UPDATE counters SET counter = counter + 1`) in your SQL statements.\n- You may dynamically apply filters by adding where conditions, change result ordering, etc.\n- You may safely use `?` placeholders in your SQL fragments - `sqlf` converts them to PostgreSQL-like `$1, $2, ...` placeholders if needed and does the numbering for you.\n- You may `.Bind` your structure to database columns like you do with other similar libraries.\n- `sqlf.Stmt` has methods to execute a query using any `database/sql` compatible driver.\n\nWhat `sqlf` doesn't?\n\n- `sqlf` isn't an ORM, you'll still have to use raw SQL.\n- There are no database schema migrations or any other database schema maintenance tools.\n- There are no compile-time type checks for query arguments, column and table names.\n- There is no wrapper for `OR` clause. It affects performance and in most cases can be avoided by using `UNION` expressions, `WITH` clause or window functions. Other option is to split a query into two.\n- `sqlf` doesn't help a developer to pinpoint the cause of issue with SQL statement.\n\n## Is It Fast?\n\nIt is. See benchmarks: https://github.com/leporo/golang-sql-builder-benchmark\n\nIn order to maximize performance and minimize memory footprint, `sqlf` reuses memory allocated for query building. The heavier load is, the faster `sqlf` works.\n\n## Usage\n\nBuild complex statements:\n\n```go\nvar (\n    region       string\n    product      string\n    productUnits int\n    productSales float64\n)\n\nsqlf.SetDialect(sqlf.PostgreSQL)\n\nerr := sqlf.From(\"orders\").\n    With(\"regional_sales\",\n        sqlf.From(\"orders\").\n            Select(\"region, SUM(amount) AS total_sales\").\n            GroupBy(\"region\")).\n    With(\"top_regions\",\n        sqlf.From(\"regional_sales\").\n            Select(\"region\").\n            Where(\"total_sales \u003e (SELECT SUM(total_sales)/10 FROM regional_sales)\")).\n    // Map query fields to variables\n    Select(\"region\").To(\u0026region).\n    Select(\"product\").To(\u0026product).\n    Select(\"SUM(quantity)\").To(\u0026productUnits).\n    Select(\"SUM(amount) AS product_sales\").To(\u0026productSales).\n    //\n    Where(\"region IN (SELECT region FROM top_regions)\").\n    GroupBy(\"region, product\").\n    OrderBy(\"product_sales DESC\").\n    // Execute the query\n    QueryAndClose(ctx, db, func(row *sql.Rows){\n        // Callback function is called for every returned row.\n        // Row values are scanned automatically to bound variables.\n        fmt.Printf(\"%s\\t%s\\t%d\\t$%.2f\\n\", region, product, productUnits, productSales)\n    })\nif err != nil {\n    panic(err)\n}\n```\n\nBind a structure:\n\n```go\ntype Offer struct {\n    Id        int64   `db:\"id\"`\n    ProductId int64   `db:\"product_id\"`\n    Price     float64 `db:\"price\"`\n    IsDeleted bool    `db:\"is_deleted\"`\n}\n\nvar o Offer\n\nerr := sqlf.From(\"offers\").\n    Bind(\u0026o).\n    Where(\"id = ?\", 42).\n    QueryRowAndClose(ctx, db)\nif err != nil {\n    panic(err)\n}\n```\n\nRetrieve data to private fields with more granular control on retrieved fields:\n\n```go\ntype Offer struct {\n    id        int64\n    productId int64\n    price     float64\n    isDeleted bool\n}\n\nvar o Offer\n\nerr := sqlf.From(\"offers\").\n    Select(\"id\").To(\u0026o.id).\n    Select(\"product_id\").To(\u0026o.productId).\n    Select(\"price\").To(\u0026o.price).\n    Select(\"is_deleted\").To(\u0026o.isDeleted).\n    Where(\"id = ?\", 42).\n    QueryRowAndClose(ctx, db)\nif err != nil {\n    panic(err)\n}\n```\n\nSome SQL fragments, like a list of fields to be selected or filtering condition may appear over and over. It can be annoying to repeat them or combine an SQL statement from chunks. Use `sqlf.Stmt` to construct a basic query and extend it for a case:\n\n```go\nfunc (o *Offer) Select() *sqlf.Stmt {\n    return sqlf.From(\"products\").\n        .Bind(o)\n        // Ignore records, marked as deleted\n        Where(\"is_deleted = false\")\n}\n\nfunc (o Offer) Print() {\n    fmt.Printf(\"%d\\t%s\\t$%.2f\\n\", o.id, o.name, o.price)\n}\n\nvar o Offer\n\n// Fetch offer data\nerr := o.Select().\n    Where(\"id = ?\", offerId).\n    QueryRowAndClose(ctx, db)\nif err != nil {\n    panic(err)\n}\no.Print()\n// ...\n\n// Select and print 5 most recently placed\n// offers for a given product\nerr = o.Select().\n    Where(\"product_id = ?\", productId).\n    OrderBy(\"id DESC\").\n    Limit(5).\n    QueryAndClose(ctx, db, func(row *sql.Rows){\n        o.Print()\n    })\nif err != nil {\n    panic(err)\n}\n// ...\n\n```\n\n## SQL Statement Construction and Execution\n\n### SELECT\n\n#### Value Binding\n\nBind columns to values using `To` method:\n\n```go\nvar (\n    minAmountRequested = true\n    maxAmount float64\n    minAmount float64\n)\n\nq := sqlf.From(\"offers\").\n    Select(\"MAX(amount)\").To(\u0026maxAmount).\n    Where(\"is_deleted = false\")\n\nif minAmountRequested {\n    q.Select(\"MIN(amount)\").To(\u0026minAmount)\n}\n\nerr := q.QueryRowAndClose(ctx, db)\nif err != nil {\n    panic(err)\n}\nif minAmountRequested {\n    fmt.Printf(\"Cheapest offer: $%.2f\\n\", minAmount)\n}\nfmt.Printf(\"Most expensive offer: $%.2f\\n\", minAmount)\n```\n\n#### Joins\n\nThere are helper methods to construct a JOIN clause: `Join`, `LeftJoin`, `RightJoin` and `FullJoin`.\n\n```go\nvar (\n    offerId     int64\n    productName string\n    price       float64\n}\n\nerr := sqlf.From(\"offers o\").\n    Select(\"o.id\").To(\u0026offerId).\n    Select(\"price\").To(\u0026price).\n    Where(\"is_deleted = false\").\n    // Join\n    LeftJoin(\"products p\", \"p.id = o.product_id\").\n    // Bind a column from joined table to variable\n    Select(\"p.name\").To(\u0026productName).\n    // Print top 10 offers\n    OrderBy(\"price DEST\").\n    Limit(10).\n    QueryAndClose(ctx, db, func(row *sql.Rows){\n        fmt.Printf(\"%d\\t%s\\t$%.2f\\n\", offerId, productName, price)\n    })\nif err != nil {\n    panic(err)\n}\n```\n\nUse plain SQL for more fancy cases:\n\n```go\nvar (\n    num   int64\n    name  string\n    value string\n)\nerr := sqlf.From(\"t1 CROSS JOIN t2 ON t1.num = t2.num AND t2.value IN (?, ?)\", \"xxx\", \"yyy\").\n    Select(\"t1.num\").To(\u0026num).\n    Select(\"t1.name\").To(\u0026name).\n    Select(\"t2.value\").To(\u0026value).\n    QueryAndClose(ctx, db, func(row *sql.Rows){\n        fmt.Printf(\"%d\\t%s\\ts\\n\", num, name, value)\n    })\nif err != nil {\n    panic(err)\n}\n```\n\n#### Subqueries\n\nUse `SubQuery` method to add a sub query to a statement:\n\n```go\n\tq := sqlf.From(\"orders o\").\n\t\tSelect(\"date, region\").\n\t\tSubQuery(\"(\", \") AS prev_order_date\",\n\t\t\tsqlf.From(\"orders po\").\n\t\t\t\tSelect(\"date\").\n\t\t\t\tWhere(\"region = o.region\").\n\t\t\t\tWhere(\"id \u003c o.id\").\n\t\t\t\tOrderBy(\"id DESC\").\n\t\t\t\tClause(\"LIMIT 1\")).\n\t\tWhere(\"date \u003e CURRENT_DATE - interval '1 day'\").\n\t\tOrderBy(\"id DESC\")\n\tfmt.Println(q.String())\n\tq.Close()\n```\n\nNote that if a subquery uses no arguments, it's more effective to add it as SQL fragment:\n\n```go\n\tq := sqlf.From(\"orders o\").\n\t\tSelect(\"date, region\").\n\t\tWhere(\"date \u003e CURRENT_DATE - interval '1 day'\").\n        Where(\"exists (SELECT 1 FROM orders po WHERE region = o.region AND id \u003c o.id ORDER BY id DESC LIMIT 1)\").\n        OrderBy(\"id DESC\")\n    // ...\n    q.Close()\n```\n\nTo select from sub-query pass an empty string to From and immediately call a SubQuery method.\n\nThe query constructed by the following example returns top 5 news in each section:\n\n```go\n\tq := sqlf.Select(\"\").\n\t\tFrom(\"\").\n\t\tSubQuery(\n\t\t\t\"(\", \") counted_news\",\n\t\t\tsqlf.From(\"news\").\n\t\t\t\tSelect(\"id, section, header, score\").\n\t\t\t\tSelect(\"row_number() OVER (PARTITION BY section ORDER BY score DESC) AS rating_in_section\").\n\t\t\t\tOrderBy(\"section, rating_in_section\")).\n\t\tWhere(\"rating_in_section \u003c= 5\")\n    // ...\n    q.Close()\n```\n\n#### Unions\n\nUse `Union` method to combine results of two queries:\n\n```go\n\tq := sqlf.From(\"tasks\").\n\t\tSelect(\"id, status\").\n\t\tWhere(\"status = ?\", \"new\").\n\t\tUnion(true, sqlf.PostgreSQL.From(\"tasks\").\n\t\t\tSelect(\"id, status\").\n            Where(\"status = ?\", \"wip\"))\n    // ...\n\tq.Close()\n```\n\n### INSERT\n\n`sqlf` provides a `Set` method to be used both for UPDATE and INSERT statements:\n\n```go\nvar userId int64\n\n_, err := sqlf.InsertInto(\"users\").\n    Set(\"email\", \"new@email.com\").\n    Set(\"address\", \"320 Some Avenue, Somewhereville, GA, US\").\n    Returning(\"id\").To(\u0026userId).\n    Clause(\"ON CONFLICT (email) DO UPDATE SET address = users.address\").\n    QueryRowAndClose(ctx, db)\n```\n\nThe same statement execution using the `database/sql` standard library looks like this:\n\n```go\nvar userId int64\n\n// database/sql\nerr := db.ExecContext(ctx, \"INSERT INTO users (email, address) VALUES ($1, $2) RETURNING id ON CONFLICT (email) DO UPDATE SET address = users.address\", \"new@email.com\", \"320 Some Avenue, Somewhereville, GA, US\").Scan(\u0026userId)\n```\n\nThere are just 2 fields of a new database record to be populated, and yet it takes some time to figure out what columns are being updated and what values are to be assigned to them.\n\nIn real-world cases there are tens of fields. On any update both the list of field names and the list of values, passed to `ExecContext` method, have to to be reviewed and updated. It's a common thing to have values misplaced.\n\nThe use of `Set` method to maintain a field-value map is a way to solve this issue.\n\n#### Bulk Insert\n\nTo insert a multiple rows via a single query, use `NewRow` method:\n\n```\n_, err := sqlf.InsertInto(\"users\").\n    NewRow().\n        Set(\"email\", \"first@email.com\").\n        Set(\"address\", \"320 Some Avenue, Somewhereville, GA, US\").\n    NewRow().\n        Set(\"email\", \"second@email.com\").\n        Set(\"address\", \"320 Some Avenue, Somewhereville, GA, US\").\n    ExecAndClose(ctx, db)\n```\n\n### UPDATE\n\n```go\n_, err := sqlf.Update(\"users\").\n    Set(\"email\", \"new@email.com\").\n    ExecAndClose(ctx, db)\n```\n\n### DELETE\n\n```go\n_, err := sqlf.DeleteFrom(\"products\").\n    Where(\"id = ?\", 42)\n    ExecAndClose(ctx, db)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleporo%2Fsqlf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleporo%2Fsqlf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleporo%2Fsqlf/lists"}