Sqorn

Sqorn

  • Github
  • Documentation
  • Demo

›Queries

Introduction

  • About
  • Setup
  • Quick Start

Queries

  • Manual
  • Select
  • Delete
  • Insert
  • Update

Expressions

  • Expressions
  • Operations

Execution

  • Executing Queries
  • Transactions

Misc

  • Configuration
  • API
  • FAQ
Edit

Select Queries

Reference: Postgres, SQLite, MySQL, T-SQL, Oracle

Overview

  • With .with .withRecursive
  • Select .return
  • Distinct .distinct .distinctOn
  • From .from .join .leftJoin .rightJoin .fullJoin .crossJoin .naturalJoin .naturalLeftJoin .naturalRightJoin .naturalFullJoin .on .using
  • Where .where
  • Group By .groupBy .rollup .cube .groupingSets
  • Having .having
  • Sets .union .intersect .except .unionAll .intersectAll .exceptAll
  • Order By .orderBy
  • Limit .limit
  • Offset .offset

From

.from builds from clauses.

sq.from`book`.query

{ text: 'select * from book',
  args: [] }

Multiple .from calls are joined with ', '.

sq.from`book`.from`person`.query

{ text: 'select * from book, person',
  args: [] }

.from accepts strings. To prevent SQL injection, never source strings from user input.

sq.from('book', 'author').query

{ text: 'select * from book, author',
  args: [] }

.from accepts Table Expressions.

// Postgres-only query
sq.from(e.unnest([3, 2, 1])).query

{ text: 'select * from unnest($1)',
  args: [[3, 2, 1]] }

.from accepts Fragments.

// Postgres-only query
sq.from(sq.txt`unnest(array[1, 2, 3])`).query

{ text: 'select * from unnest(array[1, 2, 3])',
  args: [] }

Pass .from objects in the form { alias: table } to construct table alias clauses.

Tables can be strings. To prevent SQL injection, never source strings from user input.

sq.from({ b: 'book', p: 'person' }).query

{ text: 'select * from book b, person p',
  args: [] }

Tables can be Table Expressions.

// Postgres-only query
sq.from({ countDown: e.unnest([3, 2, 1]) }).query

{ text: 'select * from unnest($1) count_down',
  args: [[3, 2, 1]] }

Tables can be Fragments.

// Postgres-only query
sq.from({ countDown: sq.txt`unnest(${[3, 2, 1]})` }).query

{ text: 'select * from unnest($1) count_down',
  args: [[3, 2, 1]] }

Tables can be Subqueries.

sq.from({ a: sq.sql`select * from author`, b: sq.from`book` }).query

{ text: 'select * from (select * from author) a, (select * from book) b',
  args: [] }

Tables can be Arrays of Values. Column names are inferred from all keys.

Sqorn converts input object keys to snake_case by default.

sq.from({
  people: [{ age: 7, firstName: 'Jo' }, { age: 9, firstName: 'Mo' }]
}).query

{ text: 'select * from (values ($1, $2), ($3, $4)) people(age, first_name)',
  args: [7, 'Jo', 9, 'Mo'] }

Construct join tables manually or learn about building joins.

sq.from`book left join author on book.author_id = author.id`.query

{ text: 'select * from book left join author on book.author_id = author.id',
  args: [] }

Where

.where builds where clauses.

sq.from`book`.where`genre = ${'Fantasy'}`.query

{ text: 'select * from book where (genre = $1)',
  args: ['Fantasy'] }

Multiple .where calls are joined with ' and '.

sq.from`book`.where`genre = ${'Fantasy'}`.where`year = ${2000}`.query

{ text: 'select * from book where (genre = $1) and (year = $2)',
  args: ['Fantasy', 2000] }

Conditions can be Boolean Expressions.

sq.from`book`.where(e`year`.gt(2010).or(e`year`.lt(2018))).query

{ text: 'select * from book where ((year > $1) or (year < $2))',
  args: [2010, 2018] }

Conditions can be Fragments.

sq.from`book`.where(sq.txt`genre = ${'Fantasy'}`).query

{ text: 'select * from book where (genre = $12)',
  args: ['Fantasy'] }

Conditions can be Subqueries.

sq.from`book`.where(sq.sql`select true`).query

{ text: 'select * from book where (select true)',
  args: [] }

Conditions can be objects in the form { field: value }.

Each property generates a field = value clause.

sq.from`book`.where({ genre: 'Fantasy', year: 2000 }).query

{ text: 'select * from book where ((genre = $1) and (year = $2))',
  args: ['Fantasy', 2000] }

Values can be Expressions.

sq.from`person`.where({ age: e.add(10, 20) }).query

{ text: 'select * from person where (age = ($1 + $2))',
  args: [10, 20] }

Values can be Fragments.

sq.from`person`.where({ age: sq.txt`20` }).query

{ text: 'select * from person where (age = 20)',
  args: [] }

Values can be Subqueries.

sq.from`test`.where({ moo: sq.sql`select true` }).query

{ text: 'select * from test where (moo = (select true))',
  args: [] }

Values can be Raw Arguments.

sq.from('book', 'author').where({ 'book.id': sq.raw('author.id') }).query

{ text: 'select * from book, author where (book.id = author.id)',
  args: [] }

null values generate a field is null expression.

sq.from`book`.where({ author: null }).query

{ text: 'select * from book where (author is null)',
  args: [] }

undefined values are invalid.

sq.from`oops`.where({ field: undefined }).query // throws error

array arguments generate a field in values expression.

sq.from`book`.where({ id: [7, 8, 9] }).query

{ text: 'select * from book where (id in ($1, $2, $3))',
  args: [7, 8, 9] }

Sqorn converts input object keys to snake_case by default.

sq.from('person').where({ firstName: 'Kaladin' }).query

{ text: 'select * from person where (first_name = $1)',
  args: ['Kaladin'] }

Multiple arguments passed to .where are joined with ' and '.

sq.from('person').where({ name: 'Rob' }, sq.txt`(name = ${'Bob'})`).query

{ text: 'select * from person where (name = $1) and (name = $2)',
  args: ['Rob', 'Bob'] }

Use Expressions to build complex conditions with e.and, e.or and e.not.

sq.from('person').where(
  e.and(
    e.eq`first_name`('Mohammed'),
    e.eq`last_name`('Ali'),
    e.gt`age`(30).not
  )
).query

{ text: 'select * from person where ((first_name = $1) and (last_name = $2) and not((age > $3)))',
  args: ['Mohammed', 'Ali', 30] }

Select

.return builds select clauses.

sq.return`${1} a, ${2} b, ${1} + ${2} sum`.query

{ text: 'select $1 a, $2 b, $3 + $4 sum',
  args: [1, 2, 1, 2] }

Multiple .return calls are joined with ', '.

sq.from`book`.return`title, author`.return`id`.query

{ text: 'select title, author, id from book',
  args: [] }

.return accepts strings. To prevent SQL injection, never source strings from user input.

sq.from('book').return('title', 'author').query

{ text: 'select title, author from book',
  args: [] }

.return accepts Fragments.

sq.return(sq.txt('moo'), sq.txt`now()`).query

{ text: 'select $1, now()',
  args: ['moo'] }

.return accepts Subqueries.

sq.return(sq.sql`select now()`, sq.return(e(8))).query

{ text: 'select (select now()), (select $1)',
  args: [8] }

.return accepts Expressions.

sq.return(e`genre`.eq('fantasy')).from('book').query

{ text: 'select (genre = $1) from book',
  args: ['fantasy'] }

.return accepts objects in the form { alias: value }. Each property generates a value alias clause.

Values can be strings. To prevent SQL injection, never source strings from user input.

sq.return({ name: 'person.name' , age: 'person.age' }).from('person').query

{ text: 'select person.name name, person.age age from person',
  args: [] }

Values can be Expressions.

sq.return({ hello: e('world'), sum: e.add(1, 2) }).query

{ text: 'select $1 hello, ($2 + $3) sum',
  args: ['world', 1, 2] }

Values can be Fragments.

sq.return({ sum: sq.txt`${2} + ${3}`, firstName: sq.txt('Bob') }).query

{ text: 'select $1 + $2 sum, $3 first_name',
  args: [2, 3, 'Bob'] }

Values can be Subqueries.

sq.return({
  time: sq.sql`select now()`,
  eight: sq.return(e(8))
}).query

{ text: 'select (select now()) time, (select $1) eight',
  args: [8] }

Distinct

Call .distinct to get only one row per group of duplicates.

sq.from('book').return('genre', 'author').distinct.query

{ text: 'select distinct genre, author from book',
  args: [] }

.distinct is idempotent.

sq.from('book').return('genre', 'author').distinct.distinct.query

{ text: 'select distinct genre, author from book',
  args: [] }

Distinct On

Postgres only: Call .distinctOn to get only the first rows distinct on the given columns.

sq.from`weather`
  .return`location, time, report`
  .distinctOn`location`
  .query

{ text: 'select distinct on (location) location, time, report from weather',
  args: [] }

.distinctOn can be called multiple times.

sq.from`weather`
  .return`location, time, report`
  .distinctOn`location`
  .distinctOn`time`
  .query

{ text: 'select distinct on (location, time) location, time, report from weather',
  args: [] }

.distinctOn accepts strings. To prevent SQL injection, never source strings from user input.

sq.from('weather')
  .return('location', 'time', 'report')
  .distinctOn('location', 'time')
  .query

{ text: 'select distinct on (location, time) location, time, report from weather',
  args: [] }

.distinctOn accepts Expressions.

sq.return`n`
  .distinctOn(e`n`.mod`2`)
  .from({ n: e.unnest([1, 2, 3, 4, 5]) })
  .query

{ text: 'select distinct on ((n % 2)) n from unnest($1) n',
  args: [[1, 2, 3, 4, 5]] }

.distinctOn accepts Fragments.

sq.from('generate_series(0, 10) n')
  .return('n')
  .distinctOn(sq.txt`n / 3`)
  .query

{ text: 'select distinct on (n / 3) n from generate_series(0, 10) n',
  args: [] }

.distinctOn accepts Subqueries.

sq.from('generate_series(0, 10) n')
  .return('n')
  .distinctOn(sq.return`n / 3`)
  .query

{ text: 'select distinct on ((select n / 3)) n from generate_series(0, 10) n',
  args: [] }

Express

The first, second and third calls of sq are equivalent to calling .from, .where and .return respectively.

The following are three sets of equivalent queries:

// select * from person
sq`person`
sq('person')
sq.from`person`

// select * from person where (name = $1)
sq`person``name = ${'Jo'}`
sq`person`({ name: 'Jo' })
sq.from`person`.where`name = ${'Jo'}`

// select age from person where (name = $1)
sq`person``name = ${'Jo'}``age`
sq.from`person`.where`name = ${'Jo'}`.return`age`
sq.from('person').where({ name: 'Jo' }).return('age')

Extend

Construct new queries by extending existing queries with .extend.

const FantasyBook = sq.from('book').where({ genre: 'fantasy' })
const Title = sq.return('title')

sq.extend(FantasyBook, Title).query

{ text: 'select title from book where (genre = $1)',
  args: ['fantasy'] }

.extend can be called in the middle of a query chain.

sq.from('book').extend(sq.where({ genre: 'fantasy' })).return('title').query

{ text: 'select title from book where (genre = $1)',
  args: ['fantasy'] }

Every query chain has its own Express state.

sq`author`.extend(
  sq`book``book.author_id = author.id``title`,
  sq`publisher``publisher.id = book.publisher_id``publisher`
)`author.id = 7``first_name`.query

{ text: 'select title, publisher, first_name from author, book, publisher where (book.author_id = author.id) and (publisher.id = book.publisher_id) and (author.id = 7)',
  args: [] }

Group By

.groupBy builds group by clauses.

sq.from`person`
  .groupBy`age`
  .return`age, count(*)`
  .query

{ text: 'select age, count(*) from person group by age',
  args: [] }

Multiple .groupBy calls are joined with ', '.

sq.from`person`
  .groupBy`age`.groupBy`last_name`
  .return`age, last_name, count(*)`
  .query

{ text: 'select age, last_name, count(*) from person group by age, last_name',
  args: [] }

.groupBy accepts strings.

sq.from('person')
  .groupBy('age', 'last_name')
  .return('age', 'last_name', 'count(*)')
  .query

{ text: 'select age, last_name, count(*) from person group by age, last_name',
  args: [] }

.groupBy accepts Expressions.

sq.from(sq.txt`generate_series(${1}, ${10}) n`)
  .groupBy(e.mod`n`(2))
  .return(e.mod`n`(2), 'sum(n)')
  .query

{ text: "select (n % $1), sum(n) from generate_series($2, $3) n group by (n % $4)",
  args: [2, 1, 10, 2] }

.groupBy accepts Fragments.

sq.from('book')
  .groupBy(sq.txt`genre`)
  .return('count(*)')
  .query

{ text: 'select count(*) from book group by genre',
  args: [] }

.groupBy accepts Subqueries.

sq.from('book')
  .groupBy(sq.return`genre = 'Fantasy'`)
  .return('count(*)')
  .query

{ text: "select count(*) from book group by (select genre = 'Fantasy')",
  args: [] }

Parenthesize arguments by wrapping them in arrays. Arrays can be nested.

sq.from('person')
  .groupBy('age', [[sq.txt`last_name`], 'first_name'])
  .return('count(*)')
  .query

{ text: 'select count(*) from person group by age, ((last_name), first_name)',
  args: [] }

Rollup

Postgres Only: .groupBy accepts rollup arguments. .rollup accepts the same arguments as .groupBy except rollup, cube or grouping sets arguments.

sq.from('t').groupBy(sq.rollup('a', ['b', sq.txt`c`], 'd')).query

// postgres
{ text: 'select * from t group by rollup (a, (b, c), d)',
  args: [] }

Cube

Postgres Only: .groupBy accepts cube arguments. .cube accepts the same arguments as .rollup.

sq.from('t').groupBy(sq.cube('a', ['b', sq.txt`c`], 'd')).query

// postgres
{ text: 'select * from t group by cube (a, (b, c), d)',
  args: [] }

Grouping Sets

Postgres Only: .groupBy accepts grouping sets arguments. .groupingSets accepts the same arguments as .groupBy.

sq.from('t').groupBy(sq.groupingSets(['a', 'b', 'c'], sq.groupingSets(['a', 'b']), ['a'], [])).query

// postgres
{ text: 'select * from t group by grouping sets ((a, b, c), grouping sets ((a, b)), (a), ())',
  args: [] }

Having

Filter groups with .having. .having accepts the same arguments as .where.

sq.from`person`.groupBy`age`.having`age < ${20}`.query

{ text: 'select * from person group by age having (age < $1)',
  args: [20] }

Multiple calls to .having are joined with ' and '.

sq.from`person`.groupBy`age`.having`age >= ${20}`.having`age < ${30}`.query

{ text: 'select * from person group by age having (age >= $1) and (age < $2)',
  args: [20, 30] }

Build complex having conditions with Expressions.

sq.from('book')
  .groupBy('genre')
  .having(e.or(
    e.gt`count(*)`(10),
    e.lte`count(*)`(100)
  ))
  .return('genre', 'count(*)')
  .query

{ text: 'select genre, count(*) from book group by genre having (((count(*) > $1) or (count(*) <= $2)))',
  args: [10, 100] }

Order By

Specify row ordering with .orderBy.

sq.from`book`.orderBy`title asc nulls last`.query

{ text: 'select * from book order by title asc nulls last',
  args: [] }

Multiple calls to .orderBy are joined with ', '.

sq.from`book`.orderBy`title`.orderBy`year`.query

{ text: 'select * from book order by title, year',
  args: [] }

.orderBy accepts strings. To prevent SQL injection, never source strings from user input.

sq.from('book').orderBy('sales / 1000', 'title').query

{ text: 'select * from book order by sales / 1000, title',
  args: [] }

.orderBy accepts Expressions.

sq.from('book').orderBy(e`sales`.div(1000), 'title').query

{ text: 'select * from book order by (sales / $1), title',
  args: [1000] }

.orderBy accepts Fragments.

sq.from('book').orderBy(sq.txt`sales / ${1000}`, 'title').query

{ text: 'select * from book order by sales / $1, title',
  args: [1000] }

.orderBy accepts Subqueries.

sq.from('book').orderBy(sq.return`sales / ${1000}`, 'title').query

{ text: 'select * from book order by (select sales / $1), title',
  args: [1000] }

.orderBy accepts objects.

Property by is used for ordering. It can be a string, Expression, Fragment or Subqueries.

sq.from('book').orderBy({ by: e`sales`.div(1000) }, { by: 'title' }).query

{ text: 'select * from book order by (sales / $1), title',
  args: [1000] }

Set property sort to either 'asc' or 'desc'. SQL defaults to ascending.

sq.from('book').orderBy({ by: 'title', sort: 'desc' }).query

{ text: 'select * from book order by title desc',
  args: [] }

Postgres Only: Set property using to a comparison operator. Do not set both properties sort and using

sq.from`person`.orderBy({ by: 'first_name', using: '~<~' }).query

{ text: 'select * from person order by first_name using ~<~',
  args: [] }

Postgres Only: Set property nulls to 'first' or 'last' to select null ordering. SQL defaults to nulls first.

sq.from('book').orderBy({ by: 'title', nulls: 'last' }).query

{ text: 'select * from book order by title nulls last',
  args: [] }

Limit

Pass .limit the maximum number of rows to fetch.

sq.from('person').limit(8).query

{ text: 'select * from person limit $1',
  args: [8] }

Only the last call to .limit is used.

sq.from('person').limit(7).limit(5).query

{ text: 'select * from person limit $1',
  args: [5] }

.limit can be called a template tag.

sq.from`person`.limit`1 + 7`.query

{ text: 'select * from person limit 1 + 7',
  args: [] }

.limit accepts Number Expressions.

sq.from('person').limit(e(1).add(7)).query

{ text: 'select * from person limit ($1 + $2)',
  args: [1, 7] }

.limit accepts Fragments.

sq.from('person').limit(sq.txt`1 + 7`).query

{ text: 'select * from person limit 1 + 7',
  args: [] }

.limit accepts Subqueries.

sq.from('person').limit(sq.return`1 + 7`).query

{ text: 'select * from person limit (select 1 + 7)',
  args: [] }

Offset

Pass .offset the number of rows to skip before returning rows.

sq.from('person').offset(8).query

{ text: 'select * from person offset $1',
  args: [8] }

Only the last call to .offset is used.

sq.from('person').offset(7).offset(5).query

{ text: 'select * from person offset $1',
  args: [5] }

.offset can be called a template tag.

sq.from`person`.offset`1 + 7`.query

{ text: 'select * from person offset 1 + 7',
  args: [] }

.offset accepts Number Expressions.

sq.from('person').offset(e(1).add(7)).query

{ text: 'select * from person offset ($1 + $2)',
  args: [1, 7] }

.offset accepts Fragments.

sq.from('person').offset(sq.txt`1 + 7`).query

{ text: 'select * from person offset 1 + 7',
  args: [] }

.offset accepts Subqueries.

sq.from('person').offset(sq.return`1 + 7`).query

{ text: 'select * from person offset (select 1 + 7)',
  args: [] }

Joins

.join, .leftJoin, .rightJoin and .fullJoin build inner, left, right and full joins respectively. Either .on or .using must be called immediately after.

sq.from`book`.join`author`.on`book.author_id = author.id`.query

{ text: 'select * from book join author on (book.author_id = author.id)',
  args: [] }

.naturalJoin, .naturalLeftJoin, .naturalRightJoin and .naturalFullJoin build natural joins. Calling .on or .using after a natural join is invalid.

sq.from`book`.naturalRightJoin`author`.query

{ text: 'select * from book natural right join author',
  args: [] }

.crossJoin builds a cross join.

sq.from`book`.crossJoin`author`.query

{ text: 'select * from book cross join author',
  args: [] }

Join methods accept the same arguments as .from.

sq.from({ b: 'book' })
  .naturalFullJoin({ a: 'author' })
  .naturalRightJoin('publisher')
  .query

{ text: 'select book b natural full join author a natural right join publisher',
  args: [] }

On

.on specifies join conditions. It accepts the same arguments as .where. .on must be called exactly once.

sq.from({ b: 'book' })
  .join({ a: 'author'}).on({ 'b.author_id': sq.raw('a.id') })
  .query

{ text: 'select * from book b join author a on (b.author_id = a.id)',
  args: [] }

Build complex join conditions with Expressions.

sq.from({ t: 'ticket' })
  .leftJoin({ p: 'person' })
  .on(e.or(
    e.eq`p.first_name``t.first_name`,
    e.eq`p.last_name``t.last_name`
  ))
  .query

{ text: 'select * from ticket t left join person p on ((p.first_name = t.first_name) or (p.last_name = t.last_name))',
  args: [] }

Using

Alternatively, specify join columns with .using. .using must be called exactly once.

sq.from`book`.join`author`.using`author_id`.query

{ text: 'select * from book join author using (author_id)',
  args: [] }

.using accepts strings.

sq.from('a').join('b').using('x', 'y', 'z').query

{ text: 'select * from a join b using (x, y, z)',
  args: [] }

Sets

Union, Intersect, Except

Pass select subqueries to .union, .intersect and .except to perform set operations.

const Person = sq.from`person`
const Young = Person.where`age < 30`
const Middle = Person.where`age >= 30 and age < 60`
const Old = Person.where`age >= 60`

Person.except(Young).query

{ text: 'select * from person except (select * from person where (age < 30))',
  args: [] }

Young.union(Middle, Old).query

{ text: 'select * from person where (age < 30) union (select * from person where (age >= 30 and age < 60)) union (select * from person where (age >= 60))',
  args: [] }

Union All, Intersect All, Except All

.unionAll, .intersectAll and .exceptAll can be used to prevent duplicate elimination.

Young.unionAll(Old).query

{ text: 'select * from person where (age < 30) union all (select * from person where (age >= 60))',
  args: [] }

Set operators can be chained.

Person.except(Young).intersect(Person.except(Old)).query

{ text: 'select * from person except (select * from person where (age < 30)) intersect (select * from person except (select * from person where (age >= 60)))',
  args: [] }

With

Construct CTEs (Common Table Expressions) with .with.

sq.with`n (select ${20} age)`.from`n`.return`age`.query

{ text: 'with n (select $1 age) select age from n',
  args: [20] }

.with can be called multiple times.

sq.with`width (select ${10} n)`
  .with`height (select ${20} n)`
  .return`width.n * height.n area`
  .query

{ text: 'with width (select $1 n), height (select $2 n) select width.n * height.n area',
  args: [10, 20] }

.with accepts objects in the form { alias: table }. Tables can be Subqueries.

sq.with({
    width: sq.return({ n: 10 }),
    height: sq.sql`select ${20} n`
  })
  .return({ area: sq.txt`width.n * height.n` })
  .query

{ text: 'with width (select $1 n), height (select $2 n) select width.n * height.n area',
  args: [10, 20] }

Tables can be arrays of row objects. A values clause is generated. Column names are inferred from all keys.

const people = [{ age: 7, name: 'Jo' }, { age: 9, name: 'Mo' }]
sq.with({ people }).return`max(age)`.from`people`.query

{ text: 'with people(age, name) (values ($1, $2), ($3, $4)) select max(age) from people',
  args: [7, 'Jo', 9, 'Mo'] }

Recursive CTEs

.withRecursive creates a recursive CTE.

const one = sq.return`1`
const next = sq.return`n + 1`.from`t`.where`n < 100`
sq.withRecursive({ 't(n)': one.unionAll(next) })
  .from('t')
  .return('sum(n)')
  .query

{ text: 'with recursive t(n) (select 1 union all (select n + 1 from t where (n < 100))) select sum(n) from t',
  args: [] }
← ManualDelete →
  • Overview
  • From
  • Where
  • Select
  • Distinct
    • Distinct On
  • Express
  • Extend
  • Group By
    • Rollup
    • Cube
    • Grouping Sets
  • Having
  • Order By
  • Limit
  • Offset
  • Joins
    • On
    • Using
  • Sets
    • Union, Intersect, Except
    • Union All, Intersect All, Except All
  • With
    • Recursive CTEs
Copyright © 2018 Sufyan Dawoodjee