Dynamic Query
Previously introduced queries are more inclined to be determined at compile time, where sqala can provide type safety and automatic deserialization capabilities. However, in certain applications (such as user-customizable reporting systems), the queries to be executed cannot be known in advance at compile time. We may not even know what tables exist in the database or what fields are in the tables at compile time. To address such scenarios, sqala provides a set of dynamic query DSLs.
First, add the dependency:
libraryDependencies += "com.wz7982" % "sqala-dynamic_3" % "latest.integration"The dynamic query DSL requires import sqala.dynamic.dsl.*.
We use the asTable method to create a table and incorporate it into the query:
import import sqala.dynamic.dsl.*
val department = asTable[Department]
val q = select (department.id, department.name) from department where department.id == 1We can also use the table and column methods to dynamically create tables and fields that are not known at compile time and incorporate them into the query:
import import sqala.dynamic.dsl.*
val q = select (List(column("a"), column("b"))) from table("t") where column("a") == 1sqala's dynamic queries can not only fill in simple strings, but we can also pass in an SQL fragment. The unsafeExpr method will enable an SQL parser and convert it into an SQL syntax tree:
val q = select (List(unsafeExpr("a"), unsafeExpr("b"))) from table("t") where unsafeExpr("a = 1") && unsafeExpr("b") == 1As the name suggests, the unsafeExpr method is unsafe and may pose SQL injection risks. Please use it with caution!
Next, let's delve into the details of sqala's dynamic query constructors.
SELECT
Use the select method to create a SELECT statement:
val s = select(List(column("a"), column("b") as "column"))Chaining select calls will sequentially concatenate them when generating SQL.
Suppose we have a list of fields obtained at runtime that need to be included in the query, we can write it like this:
// Assume the list is obtained at runtime
val columnList: List[String] = List("a", "b")
val s = select(columnList.map(column(_)))FROM
Use the from method with table to generate the FROM clause:
val s = select (column("a"), column("b")) from table("t")WHERE
Use the where method to generate the WHERE clause:
val s = select (column("a"), column("b")) from table("t") where column("a") == 1Suppose we have a list of values determined at runtime, and we need to use OR to concatenate them in the query, we can write it like this:
// Assume the list is obtained at runtime
val values = List("x", "y", "z")
val condition = values.map(v => column("a") == v).reduce((x, y) => x || y)
val s = select (column("a")) from table("t") where conditionThe generated SQL will look like:
SELECT a FROM t WHERE a = 'x' OR a = 'y' OR a = 'z'Chaining where calls will concatenate conditions using AND.
JOIN
Use join, leftJoin, rightJoin, fullJoin with the on method to generate a JOIN table, and then include it in from:
val t = table("a") join table("b") on column("a.x") == column("b.y")
val s = select (column("*")) from tGROUP BY
Use the groupBy method to generate the GROUP BY clause:
val s = select (List(column("a"), sum(column("b")))) from table("t") groupBy List(column("a"))Chaining groupBy calls will sequentially concatenate them.
Use the having method to generate the HAVING clause:
val s = select (List(column("a"), sum(column("b")))) from table("t") groupBy List(column("a")) having sum(column("b")) > 1ORDER BY
Use the orderBy method to generate the GROUP BY clause, used in conjunction with the asc and desc methods of expressions:
val s = select (List(column("a"), column("b"))) from table("t") orderBy List(column("a").asc, column("b").desc)Chaining orderBy calls will sequentially concatenate them.
LIMIT
Use the limit and offset methods to create the LIMIT clause:
val s = select (List(column("a"), column("b"))) from table("t") limit 10 offset 10The generated SQL will vary according to the database dialect.
UNION
Use union, unionAll, except, exceptAll, intersect, intersectAll methods to generate set queries:
val s1 = select (column("a")) from table("t1")
val s2 = select (column("b")) from table("t2")
val s = s1 union s2Subqueries
Subqueries can appear as part of expressions, for example:
val subQuery = select (max(column("a"))) from table("t")
val s = select (List(column("a"))) from table("t") where column("a") < subQueryAdditionally, subquery-related predicates such as IN, ANY, SOME, ALL, EXISTS are also supported.
For subqueries in FROM, we need to use the as method to alias the fields and the query, and then reference them again:
val subQuery = select (List(column("x"), column("y"))) from table("t1") as "q1"
val q = select (List(column("q1.x"), column("q1.y"))) from subQueryExpressions
The expressions in the dynamic query constructor mostly have similar usage to the expressions part of static queries, and you can refer to the explanations there. The difference lies in the usage of CASE expressions, which are designed to facilitate the dynamic construction of CASE at runtime:
val caseExpr = `case`(List(column("a") == 1 -> 1, column("a") == 2 -> 2), 0)To maintain ease of use in highly dynamic query construction scenarios, the expressions in dynamic queries do not adopt a strictly type-safe design.
Generating SQL
After creating the query, if we want to send it to JDBC for processing, we also need to obtain the generated SQL. We can use the sql method in conjunction with Dialect from sqala.printer to generate SQL:
import sqala.printer.Dialect
val s = select (List(column("a"), column("b"))) from table("t")
val sql: (String, Array[Any]) = s.sql(MySqlDialect)The sql method returns a tuple, where the first item is the generated SQL statement, and the second parameter is the parameters within it.