API Reference
FunSQL.FunSQLError — TypeBase error class for all errors raised by FunSQL.
render()
FunSQL.render — Methodrender(node; tables = Dict{Symbol, SQLTable}(),
dialect = :default,
cache = nothing)::SQLStringCreate a SQLCatalog object and serialize the query node.
FunSQL.render — Methodrender(catalog::Union{SQLConnection, SQLCatalog}, node::SQLNode)::SQLStringSerialize the query node as a SQL statement.
Parameter catalog of SQLCatalog type encapsulates available database tables and the target SQL dialect. A SQLConnection object is also accepted.
Parameter node is a composite SQLNode object.
The function returns a SQLString value. The result is also cached (with the identity of node serving as the key) in the catalog cache.
Examples
julia> catalog = SQLCatalog(
:person => SQLTable(:person, columns = [:person_id, :year_of_birth]),
dialect = :postgresql);
julia> q = From(:person) |>
Where(Get.year_of_birth .>= 1950);
julia> print(render(catalog, q))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE ("person_1"."year_of_birth" >= 1950)FunSQL.render — Methodrender(dialect::Union{SQLConnection, SQLCatalog, SQLDialect},
clause::SQLClause)::SQLStringSerialize the syntax tree of a SQL query.
reflect()
FunSQL.reflect — Methodreflect(conn;
schema = nothing,
dialect = nothing,
cache = 256)::SQLCatalogRetrieve the information about available database tables.
The function returns a SQLCatalog object. The catalog will be populated with the tables from the given database schema, or, if parameter schema is not set, from the default database schema (e.g., schema public for PostgreSQL).
Parameter dialect specifies the target SQLDialect. If not set, dialect will be inferred from the type of the connection object.
SQLConnection and SQLStatement
FunSQL.DB — TypeShorthand for SQLConnection.
FunSQL.SQLConnection — TypeSQLConnection(conn; catalog)Wrap a raw database connection object together with a SQLCatalog object containing information about database tables.
FunSQL.SQLStatement — TypeSQLStatement(conn, raw; vars = Symbol[])Wrap a prepared SQL statement.
DBInterface.connect — MethodDBInterface.connect(DB{RawConnType},
args...;
schema = nothing,
dialect = nothing,
cache = 256,
kws...)Connect to the database server, call reflect to retrieve the information about available tables and return a SQLConnection object.
Extra parameters args and kws are passed to the call:
DBInterface.connect(RawConnType, args...; kws...)DBInterface.execute — MethodDBInterface.execute(conn::SQLConnection, sql::SQLNode, params)
DBInterface.execute(conn::SQLConnection, sql::SQLClause, params)Serialize and execute the query node.
DBInterface.execute — MethodDBInterface.execute(conn::SQLConnection, sql::SQLNode; params...)
DBInterface.execute(conn::SQLConnection, sql::SQLClause; params...)Serialize and execute the query node.
DBInterface.execute — MethodDBInterface.execute(stmt::SQLStatement, params)Execute the prepared SQL statement.
DBInterface.prepare — MethodDBInterface.prepare(conn::SQLConnection, str::SQLString)::SQLStatementGenerate a prepared SQL statement.
DBInterface.prepare — MethodDBInterface.prepare(conn::SQLConnection, sql::SQLNode)::SQLStatement
DBInterface.prepare(conn::SQLConnection, sql::SQLClause)::SQLStatementSerialize the query node and return a prepared SQL statement.
SQLCatalog, SQLTable, and SQLColumn
FunSQL.SQLCatalog — TypeSQLCatalog(; tables = Dict{Symbol, SQLTable}(),
dialect = :default,
cache = 256,
metadata = nothing)
SQLCatalog(tables...;
dialect = :default, cache = 256, metadata = nothing)SQLCatalog encapsulates available database tables, the target SQL dialect, a cache of serialized queries, and an optional metadata.
Parameter tables is either a dictionary or a vector of SQLTable objects, where the vector will be converted to a dictionary with table names as keys. A table in the catalog can be included to a query using the From node.
Parameter dialect is a SQLDialect object describing the target SQL dialect.
Parameter cache specifies the size of the LRU cache containing results of the render function. Set cache to nothing to disable the cache, or set cache to an arbitrary Dict-like object to provide a custom cache implementation.
Examples
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth, :location_id]);
julia> location = SQLTable(:location, columns = [:location_id, :state]);
julia> catalog = SQLCatalog(person, location, dialect = :postgresql)
SQLCatalog(SQLTable(:location, SQLColumn(:location_id), SQLColumn(:state)),
SQLTable(:person,
SQLColumn(:person_id),
SQLColumn(:year_of_birth),
SQLColumn(:location_id)),
dialect = SQLDialect(:postgresql))FunSQL.SQLColumn — TypeSQLColumn(; name, metadata = nothing)
SQLColumn(name; metadata = nothing)SQLColumn represents a column with the given name and optional metadata.
FunSQL.SQLTable — TypeSQLTable(; qualifiers = [], name, columns, metadata = nothing)
SQLTable(name; qualifiers = [], columns, metadata = nothing)
SQLTable(name, columns...; qualifiers = [], metadata = nothing)The structure of a SQL table or a table-like entity (TEMP TABLE, VIEW, etc) for use as a reference in assembling SQL queries.
The SQLTable constructor expects the table name, an optional vector containing the table schema and other qualifiers, an ordered dictionary columns that maps names to columns, and an optional metadata.
Examples
julia> person = SQLTable(qualifiers = ["public"],
name = "person",
columns = ["person_id", "year_of_birth"],
metadata = (; is_view = false))
SQLTable(qualifiers = [:public],
:person,
SQLColumn(:person_id),
SQLColumn(:year_of_birth),
metadata = [:is_view => false])SQLDialect
FunSQL.SQLDialect — TypeSQLDialect(; name = :default, kws...)
SQLDialect(template::SQLDialect; kws...)
SQLDialect(name::Symbol, kws...)
SQLDialect(ConnType::Type)Properties and capabilities of a particular SQL dialect.
Use SQLDialect(name::Symbol) to create one of the known dialects. The following names are recognized:
:duckdb:mysql:postgresql:redshift:spark:sqlite:sqlserver
Keyword parameters override individual properties of a dialect. For details, check the source code.
Use SQLDialect(ConnType::Type) to detect the dialect based on the type of the database connection object. The following types are recognized:
DuckDB.DBMySQL.ConnectionLibPQ.ConnectionSQLite.DB
Examples
julia> postgresql_dialect = SQLDialect(:postgresql)
SQLDialect(:postgresql)
julia> postgresql_odbc_dialect = SQLDialect(:postgresql,
variable_prefix = '?',
variable_style = :positional)
SQLDialect(:postgresql, variable_prefix = '?', variable_style = :POSITIONAL)SQLString
FunSQL.SQLString — TypeSQLString(raw; columns = nothing, vars = Symbol[])Serialized SQL query.
Parameter columns is a vector describing the output columns.
Parameter vars is a vector of query parameters (created with Var) in the order they are expected by the DBInterface.execute() function.
Examples
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(person);
julia> render(q)
SQLString("""
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1\"""",
columns = [SQLColumn(:person_id), SQLColumn(:year_of_birth)])
julia> q = From(person) |> Where(Fun.and(Get.year_of_birth .>= Var.YEAR,
Get.year_of_birth .< Var.YEAR .+ 10));
julia> render(q, dialect = :mysql)
SQLString("""
SELECT
`person_1`.`person_id`,
`person_1`.`year_of_birth`
FROM `person` AS `person_1`
WHERE
(`person_1`.`year_of_birth` >= ?) AND
(`person_1`.`year_of_birth` < (? + 10))""",
columns = [SQLColumn(:person_id), SQLColumn(:year_of_birth)],
vars = [:YEAR, :YEAR])
julia> render(q, dialect = :postgresql)
SQLString("""
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE
("person_1"."year_of_birth" >= $1) AND
("person_1"."year_of_birth" < ($1 + 10))""",
columns = [SQLColumn(:person_id), SQLColumn(:year_of_birth)],
vars = [:YEAR])FunSQL.pack — Functionpack(sql::SQLString, vars::Union{Dict, NamedTuple})::Vector{Any}Convert a dictionary or a named tuple of query parameters to the positional form expected by DBInterface.execute().
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(person) |> Where(Fun.and(Get.year_of_birth .>= Var.YEAR,
Get.year_of_birth .< Var.YEAR .+ 10));
julia> sql = render(q, dialect = :mysql);
julia> pack(sql, (; YEAR = 1950))
2-element Vector{Any}:
1950
1950
julia> sql = render(q, dialect = :postgresql);
julia> pack(sql, (; YEAR = 1950))
1-element Vector{Any}:
1950SQLNode
FunSQL.AbstractSQLNode — TypeA tabular or a scalar operation that can be expressed as a SQL query.
FunSQL.DuplicateLabelError — TypeA duplicate label where unique labels are expected.
FunSQL.IllFormedError — TypeA scalar operation where a tabular operation is expected.
FunSQL.InvalidArityError — TypeUnexpected number of arguments.
FunSQL.InvalidGroupingSetsError — TypeGrouping sets are specified incorrectly.
FunSQL.RebaseError — TypeA node that cannot be rebased.
FunSQL.ReferenceError — TypeAn undefined or an invalid reference.
FunSQL.SQLNode — TypeAn opaque wrapper over an arbitrary SQL node.
FunSQL.TabularNode — TypeA node that produces tabular output.
FunSQL.TransliterationError — TypeInvalid application of the @funsql macro.
FunSQL.@funsql — MacroConvenient notation for assembling FunSQL queries.
Agg
FunSQL.Agg — MethodAgg(; over = nothing, name, args = [], filter = nothing)
Agg(name; over = nothing, args = [], filter = nothing)
Agg(name, args...; over = nothing, filter = nothing)
Agg.name(args...; over = nothing, filter = nothing)An application of an aggregate function.
An Agg node must be applied to the output of a Group or a Partition node. In a Group context, it is translated to a regular aggregate function, and in a Partition context, it is translated to a window function.
Examples
Number of patients per year of birth.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Group(Get.year_of_birth) |>
Select(Get.year_of_birth, Agg.count());
julia> print(render(q, tables = [person]))
SELECT
"person_1"."year_of_birth",
count(*) AS "count"
FROM "person" AS "person_1"
GROUP BY "person_1"."year_of_birth"Number of distinct states among all available locations.
julia> location = SQLTable(:location, columns = [:location_id, :state]);
julia> q = From(:location) |>
Group() |>
Select(Agg.count_distinct(Get.state));
julia> print(render(q, tables = [location]))
SELECT count(DISTINCT "location_1"."state") AS "count_distinct"
FROM "location" AS "location_1"For each patient, show the date of their latest visit to a healthcare provider.
julia> person = SQLTable(:person, columns = [:person_id]);
julia> visit_occurrence =
SQLTable(:visit_occurrence, columns = [:visit_occurrence_id, :person_id, :visit_start_date]);
julia> q = From(:person) |>
LeftJoin(:visit_group => From(:visit_occurrence) |>
Group(Get.person_id),
on = (Get.person_id .== Get.visit_group.person_id)) |>
Select(Get.person_id,
:max_visit_start_date =>
Get.visit_group |> Agg.max(Get.visit_start_date));
julia> print(render(q, tables = [person, visit_occurrence]))
SELECT
"person_1"."person_id",
"visit_group_1"."max" AS "max_visit_start_date"
FROM "person" AS "person_1"
LEFT JOIN (
SELECT
max("visit_occurrence_1"."visit_start_date") AS "max",
"visit_occurrence_1"."person_id"
FROM "visit_occurrence" AS "visit_occurrence_1"
GROUP BY "visit_occurrence_1"."person_id"
) AS "visit_group_1" ON ("person_1"."person_id" = "visit_group_1"."person_id")For each visit, show the number of days passed since the previous visit.
julia> visit_occurrence =
SQLTable(:visit_occurrence, columns = [:visit_occurrence_id, :person_id, :visit_start_date]);
julia> q = From(:visit_occurrence) |>
Partition(Get.person_id,
order_by = [Get.visit_start_date]) |>
Select(Get.person_id,
Get.visit_start_date,
:gap => Get.visit_start_date .- Agg.lag(Get.visit_start_date));
julia> print(render(q, tables = [visit_occurrence]))
SELECT
"visit_occurrence_1"."person_id",
"visit_occurrence_1"."visit_start_date",
("visit_occurrence_1"."visit_start_date" - (lag("visit_occurrence_1"."visit_start_date") OVER (PARTITION BY "visit_occurrence_1"."person_id" ORDER BY "visit_occurrence_1"."visit_start_date"))) AS "gap"
FROM "visit_occurrence" AS "visit_occurrence_1"Append
FunSQL.Append — MethodAppend(; over = nothing, args)
Append(args...; over = nothing)Append concatenates input datasets.
Only the columns that are present in every input dataset will be included to the output of Append.
An Append node is translated to a UNION ALL query:
SELECT ...
FROM $over
UNION ALL
SELECT ...
FROM $(args[1])
UNION ALL
...Examples
Show the dates of all measuments and observations.
julia> measurement = SQLTable(:measurement, columns = [:measurement_id, :person_id, :measurement_date]);
julia> observation = SQLTable(:observation, columns = [:observation_id, :person_id, :observation_date]);
julia> q = From(:measurement) |>
Define(:date => Get.measurement_date) |>
Append(From(:observation) |>
Define(:date => Get.observation_date));
julia> print(render(q, tables = [measurement, observation]))
SELECT
"measurement_1"."person_id",
"measurement_1"."measurement_date" AS "date"
FROM "measurement" AS "measurement_1"
UNION ALL
SELECT
"observation_1"."person_id",
"observation_1"."observation_date" AS "date"
FROM "observation" AS "observation_1"As
FunSQL.As — MethodAs(; over = nothing, name)
As(name; over = nothing)
name => overIn a scalar context, As specifies the name of the output column. When applied to tabular data, As wraps the data in a nested record.
The arrow operator (=>) is a shorthand notation for As.
Examples
Show all patient IDs.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |> Select(:id => Get.person_id);
julia> print(render(q, tables = [person]))
SELECT "person_1"."person_id" AS "id"
FROM "person" AS "person_1"Show all patients together with their state of residence.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth, :location_id]);
julia> location = SQLTable(:location, columns = [:location_id, :state]);
julia> q = From(:person) |>
Join(From(:location) |> As(:location),
on = Get.location_id .== Get.location.location_id) |>
Select(Get.person_id, Get.location.state);
julia> print(render(q, tables = [person, location]))
SELECT
"person_1"."person_id",
"location_1"."state"
FROM "person" AS "person_1"
JOIN "location" AS "location_1" ON ("person_1"."location_id" = "location_1"."location_id")Bind
FunSQL.Bind — MethodBind(; over = nothing; args)
Bind(args...; over = nothing)The Bind node evaluates a query with parameters. Specifically, Bind provides the values for Var parameters contained in the over node.
In a scalar context, the Bind node is translated to a correlated subquery. When Bind is applied to the joinee branch of a Join node, it is translated to a JOIN LATERAL query.
Examples
Show patients with at least one visit to a heathcare provider.
julia> person = SQLTable(:person, columns = [:person_id]);
julia> visit_occurrence = SQLTable(:visit_occurrence, columns = [:visit_occurrence_id, :person_id]);
julia> q = From(:person) |>
Where(Fun.exists(From(:visit_occurrence) |>
Where(Get.person_id .== Var.PERSON_ID) |>
Bind(:PERSON_ID => Get.person_id)));
julia> print(render(q, tables = [person, visit_occurrence]))
SELECT "person_1"."person_id"
FROM "person" AS "person_1"
WHERE (EXISTS (
SELECT "visit_occurrence_1"."visit_occurrence_id"
FROM "visit_occurrence" AS "visit_occurrence_1"
WHERE ("visit_occurrence_1"."person_id" = "person_1"."person_id")
))Show all patients together with the date of their latest visit to a heathcare provider.
julia> person = SQLTable(:person, columns = [:person_id]);
julia> visit_occurrence =
SQLTable(:visit_occurrence, columns = [:visit_occurrence_id, :person_id, :visit_start_date]);
julia> q = From(:person) |>
LeftJoin(From(:visit_occurrence) |>
Where(Get.person_id .== Var.PERSON_ID) |>
Order(Get.visit_start_date |> Desc()) |>
Limit(1) |>
Bind(:PERSON_ID => Get.person_id) |>
As(:visit),
on = true) |>
Select(Get.person_id, Get.visit.visit_start_date);
julia> print(render(q, tables = [person, visit_occurrence]))
SELECT
"person_1"."person_id",
"visit_1"."visit_start_date"
FROM "person" AS "person_1"
LEFT JOIN LATERAL (
SELECT "visit_occurrence_1"."visit_start_date"
FROM "visit_occurrence" AS "visit_occurrence_1"
WHERE ("visit_occurrence_1"."person_id" = "person_1"."person_id")
ORDER BY "visit_occurrence_1"."visit_start_date" DESC
FETCH FIRST 1 ROW ONLY
) AS "visit_1" ON TRUEDefine
FunSQL.Define — MethodDefine(; over; args = [], before = nothing, after = nothing)
Define(args...; over, before = nothing, after = nothing)The Define node adds or replaces output columns.
By default, new columns are added at the end of the column list while replaced columns retain their position. Set after = true (after = <column>) to add both new and replaced columns at the end (after a specified column). Alternatively, set before = true (before = <column>) to add both new and replaced columns at the front (before the specified column).
Examples
Show patients who are at least 16 years old.
julia> person = SQLTable(:person, columns = [:person_id, :birth_datetime]);
julia> q = From(:person) |>
Define(:age => Fun.now() .- Get.birth_datetime, before = :birth_datetime) |>
Where(Get.age .>= "16 years");
julia> print(render(q, tables = [person]))
SELECT
"person_2"."person_id",
"person_2"."age",
"person_2"."birth_datetime"
FROM (
SELECT
"person_1"."person_id",
(now() - "person_1"."birth_datetime") AS "age",
"person_1"."birth_datetime"
FROM "person" AS "person_1"
) AS "person_2"
WHERE ("person_2"."age" >= '16 years')Conceal the year of birth of patients born before 1930.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Define(:year_of_birth => Fun.case(Get.year_of_birth .>= 1930,
Get.year_of_birth,
missing));
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
(CASE WHEN ("person_1"."year_of_birth" >= 1930) THEN "person_1"."year_of_birth" ELSE NULL END) AS "year_of_birth"
FROM "person" AS "person_1"From
FunSQL.From — MethodFrom(; source)
From(tbl::SQLTable)
From(name::Symbol)
From(^)
From(df)
From(f::SQLNode; columns::Vector{Symbol})
From(::Nothing)From outputs the content of a database table.
The parameter source could be one of:
- a
SQLTableobject; - a
Symbolvalue; - a
^object; - a
DataFrameor any Tables.jl-compatible dataset; - A
SQLNoderepresenting a table-valued function. In this case,Fromalso requires a keyword parametercolumnswith a list of output columns produced by the function. nothing.
When source is a symbol, it can refer to either a table in SQLCatalog or an intermediate dataset defined with the With node.
The From node is translated to a SQL query with a FROM clause:
SELECT ...
FROM $sourceFrom(^) must be a component of Iterate. In the context of Iterate, it refers to the output of the previous iteration.
From(::DataFrame) is translated to a VALUES clause.
From(nothing) emits a dataset with one row and no columns and can usually be omitted.
Examples
List all patients.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(person);
julia> print(render(q))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"List all patients.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person);
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"Show all patients diagnosed with essential hypertension.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> condition_occurrence =
SQLTable(:condition_occurrence,
columns = [:condition_occurrence_id, :person_id, :condition_concept_id]);
julia> q = From(:person) |>
Where(Fun.in(Get.person_id, From(:essential_hypertension) |>
Select(Get.person_id))) |>
With(:essential_hypertension =>
From(:condition_occurrence) |>
Where(Get.condition_concept_id .== 320128));
julia> print(render(q, tables = [person, condition_occurrence]))
WITH "essential_hypertension_1" ("person_id") AS (
SELECT "condition_occurrence_1"."person_id"
FROM "condition_occurrence" AS "condition_occurrence_1"
WHERE ("condition_occurrence_1"."condition_concept_id" = 320128)
)
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE ("person_1"."person_id" IN (
SELECT "essential_hypertension_2"."person_id"
FROM "essential_hypertension_1" AS "essential_hypertension_2"
))Show the current date.
julia> q = From(nothing) |>
Select(Fun.current_date());
julia> print(render(q))
SELECT CURRENT_DATE AS "current_date"
julia> q = Select(Fun.current_date());
julia> print(render(q))
SELECT CURRENT_DATE AS "current_date"Query a DataFrame.
julia> df = DataFrame(name = ["SQL", "Julia", "FunSQL"],
year = [1974, 2012, 2021]);
julia> q = From(df) |>
Group() |>
Select(Agg.min(Get.year), Agg.max(Get.year));
julia> print(render(q))
SELECT
min("values_1"."year") AS "min",
max("values_1"."year") AS "max"
FROM (
VALUES
(1974),
(2012),
(2021)
) AS "values_1" ("year")Parse comma-separated numbers.
julia> q = From(Fun.regexp_matches("2,3,5,7,11", "(\\d+)", "g"),
columns = [:captures]) |>
Select(Fun."CAST(?[1] AS INTEGER)"(Get.captures));
julia> print(render(q, dialect = :postgresql))
SELECT CAST("regexp_matches_1"."captures"[1] AS INTEGER) AS "_"
FROM regexp_matches('2,3,5,7,11', '(\d+)', 'g') AS "regexp_matches_1" ("captures")Fun
FunSQL.Fun — MethodFun(; name, args = [])
Fun(name; args = [])
Fun(name, args...)
Fun.name(args...)Application of a SQL function or a SQL operator.
A Fun node is also generated by broadcasting on SQLNode objects. Names of Julia operators (==, !=, &&, ||, !) are replaced with their SQL equivalents (=, <>, and, or, not).
If name contains only symbols, or if name starts or ends with a space, the Fun node is translated to a SQL operator.
If name contains one or more ? characters, it serves as a template of a SQL expression where ? symbols are replaced with the given arguments. Use ?? to represent a literal ? mark. Wrap the template in parentheses if this is necessary to make the SQL expression unambiguous.
Certain names have a customized translation in order to generate common SQL functions and operators with irregular syntax:
Fun node | SQL syntax |
|---|---|
Fun.and(p₁, p₂, …) | p₁ AND p₂ AND … |
Fun.between(x, y, z) | x BETWEEN y AND z |
Fun.case(p, x, …) | CASE WHEN p THEN x … END |
Fun.cast(x, "TYPE") | CAST(x AS TYPE) |
Fun.concat(s₁, s₂, …) | dialect-specific, e.g., (s₁ || s₂ || …) |
Fun.current_date() | CURRENT_DATE |
Fun.current_timestamp() | CURRENT_TIMESTAMP |
Fun.exists(q) | EXISTS q |
Fun.extract("FIELD", x) | EXTRACT(FIELD FROM x) |
Fun.in(x, q) | x IN q |
Fun.in(x, y₁, y₂, …) | x IN (y₁, y₂, …) |
Fun.is_not_null(x) | x IS NOT NULL |
Fun.is_null(x) | x IS NULL |
Fun.like(x, y) | x LIKE y |
Fun.not(p) | NOT p |
Fun.not_between(x, y, z) | x NOT BETWEEN y AND z |
Fun.not_exists(q) | NOT EXISTS q |
Fun.not_in(x, q) | x NOT IN q |
Fun.not_in(x, y₁, y₂, …) | x NOT IN (y₁, y₂, …) |
Fun.not_like(x, y) | x NOT LIKE y |
Fun.or(p₁, p₂, …) | p₁ OR p₂ OR … |
Examples
Replace missing values with N/A.
julia> location = SQLTable(:location, columns = [:location_id, :city]);
julia> q = From(:location) |>
Select(Fun.coalesce(Get.city, "N/A"));
julia> print(render(q, tables = [location]))
SELECT coalesce("location_1"."city", 'N/A') AS "coalesce"
FROM "location" AS "location_1"Find patients not born in 1980.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Where(Get.year_of_birth .!= 1980);
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE ("person_1"."year_of_birth" <> 1980)For each patient, show their age in 2000.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Select(Fun."-"(2000, Get.year_of_birth));
julia> print(render(q, tables = [person]))
SELECT (2000 - "person_1"."year_of_birth") AS "_"
FROM "person" AS "person_1"Find invalid zip codes.
julia> location = SQLTable(:location, columns = [:location_id, :zip]);
julia> q = From(:location) |>
Select(Fun." NOT SIMILAR TO '[0-9]{5}'"(Get.zip));
julia> print(render(q, tables = [location]))
SELECT ("location_1"."zip" NOT SIMILAR TO '[0-9]{5}') AS "_"
FROM "location" AS "location_1"Extract the first 3 digits of the zip code.
julia> location = SQLTable(:location, columns = [:location_id, :zip]);
julia> q = From(:location) |>
Select(Fun."SUBSTRING(? FROM ? FOR ?)"(Get.zip, 1, 3));
julia> print(render(q, tables = [location]))
SELECT SUBSTRING("location_1"."zip" FROM 1 FOR 3) AS "_"
FROM "location" AS "location_1"Get
FunSQL.Get — MethodGet(; over, name)
Get(name; over)
Get.name Get."name" Get[name] Get["name"]
over.name over."name" over[name] over["name"]
nameA reference to a column of the input dataset.
When a column reference is ambiguous (e.g., with Join), use As to disambiguate the columns, and a chained Get node (Get.a.b.….z) to refer to a column wrapped with … |> As(:b) |> As(:a).
Examples
List patient IDs.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Select(Get(:person_id));
julia> print(render(q, tables = [person]))
SELECT "person_1"."person_id"
FROM "person" AS "person_1"Show patients with their state of residence.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth, :location_id]);
julia> location = SQLTable(:location, columns = [:location_id, :state]);
julia> q = From(:person) |>
Join(From(:location) |> As(:location),
on = Get.location_id .== Get.location.location_id) |>
Select(Get.person_id, Get.location.state);
julia> print(render(q, tables = [person, location]))
SELECT
"person_1"."person_id",
"location_1"."state"
FROM "person" AS "person_1"
JOIN "location" AS "location_1" ON ("person_1"."location_id" = "location_1"."location_id")Group
FunSQL.Group — MethodGroup(; over, by = [], sets = sets, name = nothing)
Group(by...; over, sets = sets, name = nothing)The Group node summarizes the input dataset.
Specifically, Group outputs all unique values of the given grouping key. This key partitions the input rows into disjoint groups that are summarized by aggregate functions Agg applied to the output of Group. The parameter sets specifies the grouping sets, either with grouping mode indicators :cube or :rollup, or explicitly as Vector{Vector{Symbol}}. An optional parameter name specifies the field to hold the group.
The Group node is translated to a SQL query with a GROUP BY clause:
SELECT ...
FROM $over
GROUP BY $by...Examples
Total number of patients.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Group() |>
Select(Agg.count());
julia> print(render(q, tables = [person]))
SELECT count(*) AS "count"
FROM "person" AS "person_1"Number of patients per year of birth.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Group(Get.year_of_birth) |>
Select(Get.year_of_birth, Agg.count());
julia> print(render(q, tables = [person]))
SELECT
"person_1"."year_of_birth",
count(*) AS "count"
FROM "person" AS "person_1"
GROUP BY "person_1"."year_of_birth"The same example using an explicit group name.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Group(Get.year_of_birth, name = :person) |>
Select(Get.year_of_birth, Get.person |> Agg.count());
julia> print(render(q, tables = [person]))
SELECT
"person_1"."year_of_birth",
count(*) AS "count"
FROM "person" AS "person_1"
GROUP BY "person_1"."year_of_birth"Number of patients per year of birth and the total number of patients.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Group(Get.year_of_birth, sets = :cube) |>
Select(Get.year_of_birth, Agg.count());
julia> print(render(q, tables = [person]))
SELECT
"person_1"."year_of_birth",
count(*) AS "count"
FROM "person" AS "person_1"
GROUP BY CUBE("person_1"."year_of_birth")Distinct states across all available locations.
julia> location = SQLTable(:location, columns = [:location_id, :state]);
julia> q = From(:location) |>
Group(Get.state);
julia> print(render(q, tables = [location]))
SELECT DISTINCT "location_1"."state"
FROM "location" AS "location_1"Highlight
FunSQL.Highlight — MethodHighlight(; over = nothing; color)
Highlight(color; over = nothing)Highlight over with the given color.
The highlighted node is printed with the selected color when the query containing it is displayed.
Available colors can be found in Base.text_colors.
Examples
julia> q = From(:person) |>
Select(Get.person_id |> Highlight(:bold))
let q1 = From(:person),
q2 = q1 |> Select(Get.person_id)
q2
endIterate
FunSQL.Iterate — MethodIterate(; over = nothing, iterator)
Iterate(iterator; over = nothing)Iterate generates the concatenated output of an iterated query.
The over query is evaluated first. Then the iterator query is repeatedly applied: to the output of over, then to the output of its previous run, and so on, until the iterator produces no data. All these outputs are concatenated to generate the output of Iterate.
The iterator query may explicitly refer to the output of the previous run using From(^) notation.
The Iterate node is translated to a recursive common table expression:
WITH RECURSIVE iterator AS (
SELECT ...
FROM $over
UNION ALL
SELECT ...
FROM $iterator
)
SELECT ...
FROM iteratorExamples
Calculate the factorial.
julia> q = Define(:n => 1, :f => 1) |>
Iterate(From(^) |>
Where(Get.n .< 10) |>
Define(:n => Get.n .+ 1, :f => Get.f .* (Get.n .+ 1)));
julia> print(render(q))
WITH RECURSIVE "__1" ("n", "f") AS (
SELECT
1 AS "n",
1 AS "f"
UNION ALL
SELECT
("__2"."n" + 1) AS "n",
("__2"."f" * ("__2"."n" + 1)) AS "f"
FROM "__1" AS "__2"
WHERE ("__2"."n" < 10)
)
SELECT
"__3"."n",
"__3"."f"
FROM "__1" AS "__3"*Calculate the factorial, with implicit From(^).
julia> q = Define(:n => 1, :f => 1) |>
Iterate(Where(Get.n .< 10) |>
Define(:n => Get.n .+ 1, :f => Get.f .* (Get.n .+ 1)));
julia> print(render(q))
WITH RECURSIVE "__1" ("n", "f") AS (
SELECT
1 AS "n",
1 AS "f"
UNION ALL
SELECT
("__2"."n" + 1) AS "n",
("__2"."f" * ("__2"."n" + 1)) AS "f"
FROM "__1" AS "__2"
WHERE ("__2"."n" < 10)
)
SELECT
"__3"."n",
"__3"."f"
FROM "__1" AS "__3"Join
FunSQL.CrossJoin — MethodAn alias for Join(...; ..., on = true).
FunSQL.Join — MethodJoin(; over = nothing, joinee, on, left = false, right = false, optional = false)
Join(joinee; over = nothing, on, left = false, right = false, optional = false)
Join(joinee, on; over = nothing, left = false, right = false, optional = false)Join correlates two input datasets.
The Join node is translated to a query with a JOIN clause:
SELECT ...
FROM $over
JOIN $joinee ON $onYou can specify the join type:
INNER JOIN(the default);LEFT JOIN(left = trueorLeftJoin);RIGHT JOIN(right = true);FULL JOIN(bothleft = trueandright = true);CROSS JOIN(on = true).
When optional is set, the JOIN clause is omitted if the query does not depend on any columns from the joinee branch.
To make a lateral join, apply Bind to the joinee branch.
Use As to disambiguate output columns.
Examples
Show patients with their state of residence.
julia> person = SQLTable(:person, columns = [:person_id, :location_id]);
julia> location = SQLTable(:location, columns = [:location_id, :state]);
julia> q = From(:person) |>
Join(:location => From(:location),
Get.location_id .== Get.location.location_id) |>
Select(Get.person_id, Get.location.state);
julia> print(render(q, tables = [person, location]))
SELECT
"person_1"."person_id",
"location_1"."state"
FROM "person" AS "person_1"
JOIN "location" AS "location_1" ON ("person_1"."location_id" = "location_1"."location_id")FunSQL.LeftJoin — MethodAn alias for Join(...; ..., left = true).
Limit
FunSQL.Limit — MethodLimit(; over = nothing, offset = nothing, limit = nothing)
Limit(limit; over = nothing, offset = nothing)
Limit(offset, limit; over = nothing)
Limit(start:stop; over = nothing)The Limit node skips the first offset rows and then emits the next limit rows.
To make the output deterministic, Limit must be applied directly after an Order node.
The Limit node is translated to a query with a LIMIT or a FETCH clause:
SELECT ...
FROM $over
OFFSET $offset ROWS
FETCH NEXT $limit ROWS ONLYExamples
Show the oldest patient.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Order(Get.year_of_birth) |>
Limit(1);
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
ORDER BY "person_1"."year_of_birth"
FETCH FIRST 1 ROW ONLYLit
FunSQL.Lit — MethodLit(; val)
Lit(val)A SQL literal.
In a context where a SQL node is expected, missing, numbers, strings, and datetime values are automatically converted to SQL literals.
Examples
julia> q = Select(:null => missing,
:boolean => true,
:integer => 42,
:text => "SQL is fun!",
:date => Date(2000));
julia> print(render(q))
SELECT
NULL AS "null",
TRUE AS "boolean",
42 AS "integer",
'SQL is fun!' AS "text",
'2000-01-01' AS "date"Order
FunSQL.Order — MethodOrder(; over = nothing, by)
Order(by...; over = nothing)Order sorts the input rows by the given key.
The Ordernode is translated to a query with an ORDER BY clause:
SELECT ...
FROM $over
ORDER BY $by...Specify the sort order with Asc, Desc, or Sort.
Examples
List patients ordered by their age.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Order(Get.year_of_birth);
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
ORDER BY "person_1"."year_of_birth"Over
FunSQL.Over — MethodOver(; over = nothing, arg, materialized = nothing)
Over(arg; over = nothing, materialized = nothing)base |> Over(arg) is an alias for With(base, over = arg).
Examples
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> condition_occurrence =
SQLTable(:condition_occurrence, columns = [:condition_occurrence_id,
:person_id,
:condition_concept_id]);
julia> q = From(:condition_occurrence) |>
Where(Get.condition_concept_id .== 320128) |>
As(:essential_hypertension) |>
Over(From(:person) |>
Where(Fun.in(Get.person_id, From(:essential_hypertension) |>
Select(Get.person_id))));
julia> print(render(q, tables = [person, condition_occurrence]))
WITH "essential_hypertension_1" ("person_id") AS (
SELECT "condition_occurrence_1"."person_id"
FROM "condition_occurrence" AS "condition_occurrence_1"
WHERE ("condition_occurrence_1"."condition_concept_id" = 320128)
)
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE ("person_1"."person_id" IN (
SELECT "essential_hypertension_2"."person_id"
FROM "essential_hypertension_1" AS "essential_hypertension_2"
))Partition
FunSQL.Partition — MethodPartition(; over, by = [], order_by = [], frame = nothing, name = nothing)
Partition(by...; over, order_by = [], frame = nothing, name = nothing)The Partition node relates adjacent rows.
Specifically, Partition specifies how to relate each row to the adjacent rows in the same dataset. The rows are partitioned by the given key and ordered within each partition using order_by key. The parameter frame customizes the extent of related rows. These related rows are summarized by aggregate functions Agg applied to the output of Partition. An optional parameter name specifies the field to hold the partition.
The Partition node is translated to a query with a WINDOW clause:
SELECT ...
FROM $over
WINDOW w AS (PARTITION BY $by... ORDER BY $order_by...)Examples
Enumerate patients' visits.
julia> visit_occurrence =
SQLTable(:visit_occurrence, columns = [:visit_occurrence_id, :person_id, :visit_start_date]);
julia> q = From(:visit_occurrence) |>
Partition(Get.person_id, order_by = [Get.visit_start_date]) |>
Select(Agg.row_number(), Get.visit_occurrence_id);
julia> print(render(q, tables = [visit_occurrence]))
SELECT
(row_number() OVER (PARTITION BY "visit_occurrence_1"."person_id" ORDER BY "visit_occurrence_1"."visit_start_date")) AS "row_number",
"visit_occurrence_1"."visit_occurrence_id"
FROM "visit_occurrence" AS "visit_occurrence_1"The same example using an explicit partition name.
julia> visit_occurrence =
SQLTable(:visit_occurrence, columns = [:visit_occurrence_id, :person_id, :visit_start_date]);
julia> q = From(:visit_occurrence) |>
Partition(Get.person_id, order_by = [Get.visit_start_date], name = :visit_by_person) |>
Select(Get.visit_by_person |> Agg.row_number(), Get.visit_occurrence_id);
julia> print(render(q, tables = [visit_occurrence]))
SELECT
(row_number() OVER (PARTITION BY "visit_occurrence_1"."person_id" ORDER BY "visit_occurrence_1"."visit_start_date")) AS "row_number",
"visit_occurrence_1"."visit_occurrence_id"
FROM "visit_occurrence" AS "visit_occurrence_1"Calculate the moving average of the number of patients by the year of birth.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Group(Get.year_of_birth) |>
Partition(order_by = [Get.year_of_birth],
frame = (mode = :range, start = -1, finish = 1)) |>
Select(Get.year_of_birth, Agg.avg(Agg.count()));
julia> print(render(q, tables = [person]))
SELECT
"person_1"."year_of_birth",
(avg(count(*)) OVER (ORDER BY "person_1"."year_of_birth" RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING)) AS "avg"
FROM "person" AS "person_1"
GROUP BY "person_1"."year_of_birth"Select
FunSQL.Select — MethodSelect(; over; args)
Select(args...; over)The Select node specifies the output columns.
SELECT $args...
FROM $overSet the column labels with As.
Examples
List patient IDs and their age.
julia> person = SQLTable(:person, columns = [:person_id, :birth_datetime]);
julia> q = From(:person) |>
Select(Get.person_id,
:age => Fun.now() .- Get.birth_datetime);
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
(now() - "person_1"."birth_datetime") AS "age"
FROM "person" AS "person_1"Sort, Asc, and Desc
FunSQL.Asc — MethodAsc(; over = nothing, nulls = nothing)Ascending order indicator.
FunSQL.Desc — MethodDesc(; over = nothing, nulls = nothing)Descending order indicator.
FunSQL.Sort — MethodSort(; over = nothing, value, nulls = nothing)
Sort(value; over = nothing, nulls = nothing)
Asc(; over = nothing, nulls = nothing)
Desc(; over = nothing, nulls = nothing)Sort order indicator.
Use with Order or Partition nodes.
Examples
List patients ordered by their age.
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Order(Get.year_of_birth |> Desc(nulls = :first));
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
ORDER BY "person_1"."year_of_birth" DESC NULLS FIRSTVar
FunSQL.Var — MethodVar(; name)
Var(name)
Var.name Var."name" Var[name] Var["name"]A reference to a query parameter.
Specify the value for the parameter with Bind to create a correlated subquery or a lateral join.
Examples
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Where(Get.year_of_birth .> Var.YEAR);
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE ("person_1"."year_of_birth" > :YEAR)Where
FunSQL.Where — MethodWhere(; over = nothing, condition)
Where(condition; over = nothing)The Where node filters the input rows by the given condition.
Where is translated to a SQL query with a WHERE clause:
SELECT ...
FROM $over
WHERE $conditionExamples
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> q = From(:person) |>
Where(Fun(">", Get.year_of_birth, 2000));
julia> print(render(q, tables = [person]))
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE ("person_1"."year_of_birth" > 2000)With
FunSQL.With — MethodWith(; over = nothing, args, materialized = nothing)
With(args...; over = nothing, materialized = nothing)With assigns a name to a temporary dataset. The dataset content can be retrieved within the over query using the From node.
With is translated to a common table expression:
WITH $args...
SELECT ...
FROM $overExamples
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> condition_occurrence =
SQLTable(:condition_occurrence, columns = [:condition_occurrence_id,
:person_id,
:condition_concept_id]);
julia> q = From(:person) |>
Where(Fun.in(Get.person_id, From(:essential_hypertension) |>
Select(Get.person_id))) |>
With(:essential_hypertension =>
From(:condition_occurrence) |>
Where(Get.condition_concept_id .== 320128));
julia> print(render(q, tables = [person, condition_occurrence]))
WITH "essential_hypertension_1" ("person_id") AS (
SELECT "condition_occurrence_1"."person_id"
FROM "condition_occurrence" AS "condition_occurrence_1"
WHERE ("condition_occurrence_1"."condition_concept_id" = 320128)
)
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE ("person_1"."person_id" IN (
SELECT "essential_hypertension_2"."person_id"
FROM "essential_hypertension_1" AS "essential_hypertension_2"
))WithExternal
FunSQL.WithExternal — MethodWithExternal(; over = nothing, args, qualifiers = [], handler = nothing)
WithExternal(args...; over = nothing, qualifiers = [], handler = nothing)WithExternal assigns a name to a temporary dataset. The dataset content can be retrieved within the over query using the From node.
The definition of the dataset is converted to a Pair{SQLTable, SQLClause} object and sent to handler, which can use it, for instance, to construct a SELECT INTO statement.
Examples
julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]);
julia> condition_occurrence =
SQLTable(:condition_occurrence, columns = [:condition_occurrence_id,
:person_id,
:condition_concept_id]);
julia> handler((tbl, def)) =
println("CREATE TEMP TABLE ", render(ID(tbl.name)), " AS\n",
render(def), ";\n");
julia> q = From(:person) |>
Where(Fun.in(Get.person_id, From(:essential_hypertension) |>
Select(Get.person_id))) |>
WithExternal(:essential_hypertension =>
From(:condition_occurrence) |>
Where(Get.condition_concept_id .== 320128),
handler = handler);
julia> print(render(q, tables = [person, condition_occurrence]))
CREATE TEMP TABLE "essential_hypertension" AS
SELECT "condition_occurrence_1"."person_id"
FROM "condition_occurrence" AS "condition_occurrence_1"
WHERE ("condition_occurrence_1"."condition_concept_id" = 320128);
SELECT
"person_1"."person_id",
"person_1"."year_of_birth"
FROM "person" AS "person_1"
WHERE ("person_1"."person_id" IN (
SELECT "essential_hypertension_1"."person_id"
FROM "essential_hypertension" AS "essential_hypertension_1"
))SQLClause
FunSQL.AbstractSQLClause — TypeA component of a SQL syntax tree.
FunSQL.SQLClause — TypeAn opaque wrapper over an arbitrary SQL clause.
AGG
FunSQL.AGG — MethodAGG(; name, args = [], filter = nothing, over = nothing)
AGG(name; args = [], filter = nothing, over = nothing)
AGG(name, args...; filter = nothing, over = nothing)An application of an aggregate function.
Examples
julia> c = AGG(:max, :year_of_birth);
julia> print(render(c))
max("year_of_birth")julia> c = AGG(:count, filter = FUN(">", :year_of_birth, 1970));
julia> print(render(c))
(count(*) FILTER (WHERE ("year_of_birth" > 1970)))julia> c = AGG(:row_number, over = PARTITION(:year_of_birth));
julia> print(render(c))
(row_number() OVER (PARTITION BY "year_of_birth"))AS
FunSQL.AS — MethodAS(; over = nothing, name, columns = nothing)
AS(name; over = nothing, columns = nothing)An AS clause.
Examples
julia> c = ID(:person) |> AS(:p);
julia> print(render(c))
"person" AS "p"julia> c = ID(:person) |> AS(:p, columns = [:person_id, :year_of_birth]);
julia> print(render(c))
"person" AS "p" ("person_id", "year_of_birth")FROM
FunSQL.FROM — MethodFROM(; over = nothing)
FROM(over)A FROM clause.
Examples
julia> c = ID(:person) |> AS(:p) |> FROM() |> SELECT((:p, :person_id));
julia> print(render(c))
SELECT "p"."person_id"
FROM "person" AS "p"FUN
FunSQL.FUN — MethodFUN(; name, args = [])
FUN(name; args = [])
FUN(name, args...)An invocation of a SQL function or a SQL operator.
Examples
julia> c = FUN(:concat, :city, ", ", :state);
julia> print(render(c))
concat("city", ', ', "state")julia> c = FUN("||", :city, ", ", :state);
julia> print(render(c))
("city" || ', ' || "state")julia> c = FUN("SUBSTRING(? FROM ? FOR ?)", :zip, 1, 3);
julia> print(render(c))
SUBSTRING("zip" FROM 1 FOR 3)GROUP
FunSQL.GROUP — MethodGROUP(; over = nothing, by = [], sets = nothing)
GROUP(by...; over = nothing, sets = nothing)A GROUP BY clause.
Examples
julia> c = FROM(:person) |>
GROUP(:year_of_birth) |>
SELECT(:year_of_birth, AGG(:count));
julia> print(render(c))
SELECT
"year_of_birth",
count(*)
FROM "person"
GROUP BY "year_of_birth"HAVING
FunSQL.HAVING — MethodHAVING(; over = nothing, condition)
HAVING(condition; over = nothing)A HAVING clause.
Examples
julia> c = FROM(:person) |>
GROUP(:year_of_birth) |>
HAVING(FUN(">", AGG(:count), 10)) |>
SELECT(:person_id);
julia> print(render(c))
SELECT "person_id"
FROM "person"
GROUP BY "year_of_birth"
HAVING (count(*) > 10)ID
FunSQL.ID — MethodID(; over = nothing, name)
ID(name; over = nothing)
ID(qualifiers, name)A SQL identifier. Specify over or use the |> operator to make a qualified identifier.
Examples
julia> c = ID(:person);
julia> print(render(c))
"person"julia> c = ID(:p) |> ID(:birth_datetime);
julia> print(render(c))
"p"."birth_datetime"julia> c = ID([:pg_catalog], :pg_database);
julia> print(render(c))
"pg_catalog"."pg_database"JOIN
FunSQL.JOIN — MethodJOIN(; over = nothing, joinee, on, left = false, right = false, lateral = false)
JOIN(joinee; over = nothing, on, left = false, right = false, lateral = false)
JOIN(joinee, on; over = nothing, left = false, right = false, lateral = false)A JOIN clause.
Examples
julia> c = FROM(:p => :person) |>
JOIN(:l => :location,
on = FUN("=", (:p, :location_id), (:l, :location_id)),
left = true) |>
SELECT((:p, :person_id), (:l, :state));
julia> print(render(c))
SELECT
"p"."person_id",
"l"."state"
FROM "person" AS "p"
LEFT JOIN "location" AS "l" ON ("p"."location_id" = "l"."location_id")LIMIT
FunSQL.LIMIT — MethodLIMIT(; over = nothing, offset = nothing, limit = nothing, with_ties = false)
LIMIT(limit; over = nothing, offset = nothing, with_ties = false)
LIMIT(offset, limit; over = nothing, with_ties = false)
LIMIT(start:stop; over = nothing, with_ties = false)A LIMIT clause.
Examples
julia> c = FROM(:person) |>
LIMIT(1) |>
SELECT(:person_id);
julia> print(render(c))
SELECT "person_id"
FROM "person"
FETCH FIRST 1 ROW ONLYLIT
FunSQL.LIT — MethodLIT(; val)
LIT(val)A SQL literal.
In a context of a SQL clause, missing, numbers, strings and datetime values are automatically converted to SQL literals.
Examples
julia> c = LIT(missing);
julia> print(render(c))
NULLjulia> c = LIT("SQL is fun!");
julia> print(render(c))
'SQL is fun!'NOTE
FunSQL.NOTE — MethodNOTE(; over = nothing, text, postfix = false)
NOTE(text; over = nothing, postfix = false)A free-form prefix of postfix annotation.
Examples
julia> c = FROM(:p => :person) |>
NOTE("TABLESAMPLE SYSTEM (50)", postfix = true) |>
SELECT((:p, :person_id));
julia> print(render(c))
SELECT "p"."person_id"
FROM "person" AS "p" TABLESAMPLE SYSTEM (50)ORDER
FunSQL.ORDER — MethodORDER(; over = nothing, by = [])
ORDER(by...; over = nothing)An ORDER BY clause.
Examples
julia> c = FROM(:person) |>
ORDER(:year_of_birth) |>
SELECT(:person_id);
julia> print(render(c))
SELECT "person_id"
FROM "person"
ORDER BY "year_of_birth"PARTITION
FunSQL.PARTITION — MethodPARTITION(; over = nothing, by = [], order_by = [], frame = nothing)
PARTITION(by...; over = nothing, order_by = [], frame = nothing)A window definition clause.
Examples
julia> c = FROM(:person) |>
SELECT(:person_id,
AGG(:row_number, over = PARTITION(:year_of_birth)));
julia> print(render(c))
SELECT
"person_id",
(row_number() OVER (PARTITION BY "year_of_birth"))
FROM "person"julia> c = FROM(:person) |>
WINDOW(:w1 => PARTITION(:year_of_birth),
:w2 => :w1 |> PARTITION(order_by = [:month_of_birth, :day_of_birth])) |>
SELECT(:person_id, AGG(:row_number, over = :w2));
julia> print(render(c))
SELECT
"person_id",
(row_number() OVER ("w2"))
FROM "person"
WINDOW
"w1" AS (PARTITION BY "year_of_birth"),
"w2" AS ("w1" ORDER BY "month_of_birth", "day_of_birth")julia> c = FROM(:person) |>
GROUP(:year_of_birth) |>
SELECT(:year_of_birth,
AGG(:avg,
AGG(:count),
over = PARTITION(order_by = [:year_of_birth],
frame = (mode = :range, start = -1, finish = 1))));
julia> print(render(c))
SELECT
"year_of_birth",
(avg(count(*)) OVER (ORDER BY "year_of_birth" RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING))
FROM "person"
GROUP BY "year_of_birth"SELECT
FunSQL.SELECT — MethodSELECT(; over = nothing, top = nothing, distinct = false, args)
SELECT(args...; over = nothing, top = nothing, distinct = false)A SELECT clause. Unlike raw SQL, SELECT() should be placed at the end of a clause chain.
Set distinct to true to add a DISTINCT modifier.
Examples
julia> c = SELECT(true, false);
julia> print(render(c))
SELECT
TRUE,
FALSEjulia> c = FROM(:location) |>
SELECT(distinct = true, :zip);
julia> print(render(c))
SELECT DISTINCT "zip"
FROM "location"SORT, ASC, and DESC
FunSQL.ASC — MethodASC(; over = nothing, nulls = nothing)Ascending order indicator.
FunSQL.DESC — MethodDESC(; over = nothing, nulls = nothing)Descending order indicator.
FunSQL.SORT — MethodSORT(; over = nothing, value, nulls = nothing)
SORT(value; over = nothing, nulls = nothing)
ASC(; over = nothing, nulls = nothing)
DESC(; over = nothing, nulls = nothing)Sort order options.
Examples
julia> c = FROM(:person) |>
ORDER(:year_of_birth |> DESC()) |>
SELECT(:person_id);
julia> print(render(c))
SELECT "person_id"
FROM "person"
ORDER BY "year_of_birth" DESCUNION
FunSQL.UNION — MethodUNION(; over = nothing, all = false, args)
UNION(args...; over = nothing, all = false)A UNION clause.
Examples
julia> c = FROM(:measurement) |>
SELECT(:person_id, :date => :measurement_date) |>
UNION(all = true,
FROM(:observation) |>
SELECT(:person_id, :date => :observation_date));
julia> print(render(c))
SELECT
"person_id",
"measurement_date" AS "date"
FROM "measurement"
UNION ALL
SELECT
"person_id",
"observation_date" AS "date"
FROM "observation"VALUES
FunSQL.VALUES — MethodVALUES(; rows)
VALUES(rows)A VALUES clause.
Examples
julia> c = VALUES([("SQL", 1974), ("Julia", 2012), ("FunSQL", 2021)]);
julia> print(render(c))
VALUES
('SQL', 1974),
('Julia', 2012),
('FunSQL', 2021)VAR
FunSQL.VAR — MethodVAR(; name)
VAR(name)A placeholder in a parameterized query.
Examples
julia> c = VAR(:year);
julia> print(render(c))
:yearWHERE
FunSQL.WHERE — MethodWHERE(; over = nothing, condition)
WHERE(condition; over = nothing)A WHERE clause.
Examples
julia> c = FROM(:location) |>
WHERE(FUN("=", :zip, "60614")) |>
SELECT(:location_id);
julia> print(render(c))
SELECT "location_id"
FROM "location"
WHERE ("zip" = '60614')WINDOW
FunSQL.WINDOW — MethodWINDOW(; over = nothing, args)
WINDOW(args...; over = nothing)A WINDOW clause.
Examples
julia> c = FROM(:person) |>
WINDOW(:w1 => PARTITION(:year_of_birth),
:w2 => :w1 |> PARTITION(order_by = [:month_of_birth, :day_of_birth])) |>
SELECT(:person_id, AGG("row_number", over = :w2));
julia> print(render(c))
SELECT
"person_id",
(row_number() OVER ("w2"))
FROM "person"
WINDOW
"w1" AS (PARTITION BY "year_of_birth"),
"w2" AS ("w1" ORDER BY "month_of_birth", "day_of_birth")WITH
FunSQL.WITH — MethodWITH(; over = nothing, recursive = false, args)
WITH(args...; over = nothing, recursive = false)A WITH clause.
Examples
julia> c = FROM(:person) |>
WHERE(FUN(:in, :person_id,
FROM(:essential_hypertension) |>
SELECT(:person_id))) |>
SELECT(:person_id, :year_of_birth) |>
WITH(FROM(:condition_occurrence) |>
WHERE(FUN("=", :condition_concept_id, 320128)) |>
SELECT(:person_id) |>
AS(:essential_hypertension));
julia> print(render(c))
WITH "essential_hypertension" AS (
SELECT "person_id"
FROM "condition_occurrence"
WHERE ("condition_concept_id" = 320128)
)
SELECT
"person_id",
"year_of_birth"
FROM "person"
WHERE ("person_id" IN (
SELECT "person_id"
FROM "essential_hypertension"
))julia> c = FROM(:essential_hypertension) |>
SELECT(*) |>
WITH(recursive = true,
FROM(:concept) |>
WHERE(FUN("=", :concept_id, 320128)) |>
SELECT(:concept_id, :concept_name) |>
UNION(all = true,
FROM(:eh => :essential_hypertension) |>
JOIN(:cr => :concept_relationship,
FUN("=", (:eh, :concept_id), (:cr, :concept_id_1))) |>
JOIN(:c => :concept,
FUN("=", (:cr, :concept_id_2), (:c, :concept_id))) |>
WHERE(FUN("=", (:cr, :relationship_id), "Subsumes")) |>
SELECT((:c, :concept_id), (:c, :concept_name))) |>
AS(:essential_hypertension, columns = [:concept_id, :concept_name]));
julia> print(render(c))
WITH RECURSIVE "essential_hypertension" ("concept_id", "concept_name") AS (
SELECT
"concept_id",
"concept_name"
FROM "concept"
WHERE ("concept_id" = 320128)
UNION ALL
SELECT
"c"."concept_id",
"c"."concept_name"
FROM "essential_hypertension" AS "eh"
JOIN "concept_relationship" AS "cr" ON ("eh"."concept_id" = "cr"."concept_id_1")
JOIN "concept" AS "c" ON ("cr"."concept_id_2" = "c"."concept_id")
WHERE ("cr"."relationship_id" = 'Subsumes')
)
SELECT *
FROM "essential_hypertension"