中文 English
qb-mysql
是一款 服务于mysql的 query builder 工具,其通过链式调用的方式将结构化数据组装成用于mysql的查询对象。qb-mysql
不仅体积非常小巧,而且功能十分强大,API丰富多样,可以轻松应对企业级的应用开发
背景
日常server开发中,在处理表关系稍微复杂的应用时,或做查询性能优化时,使用ORM工具库处理关联表会稍显捉襟见肘,这些时候会发现裸写sql更舒适,更便捷。但裸写sql一旦犯懒就很可能埋下注入的坑。
虽然 mysql js 提供了 escaping-query-values 和 escaping-query-identifiers 的方式,但你依旧可能会使用这种 select a.name, a.score, b.name from tableA a inner join TableB b on a.bId = b.id where a.id=${aId} and b.id=${bId}
懒惰但快捷的方式拼接你的query语句。因为写成防止注入的版本escape版,需要写更多重复的代码,如下
const aId = 1
const bId = 1
const sql = 'select ??.??, ??.??, ??.?? from ?? ?? inner join ?? ?? on ??.?? = ??.?? where ??.?? = ? and ??.?? = ?'
const values = ['a', 'name', 'a', 'score', 'b', 'name', 'tableA', 'a', 'tableB', 'b', 'a', 'bId', 'b', 'id', 'a', 'id', aId, 'b', 'id', bId]
写起来是不是很崩溃,尤其是复杂些的查询场景,sql语句非常长,escape的字段不仅多,而且重复度很高,看的眼都花了。
这种情况下,使用 qb-mysql
就可以轻松搞定了
安装与使用
npm i -S qb-mysql
const Querybuilder = require('qb-mysql')
const aId = 1
const bId = 1
const qb = new Querybuild()
qb.select({ tableA: ['name', 'score'], tableB: ['name'] })
.from('tableA')
.innerJoin('tableB', { id: bId })
.where({ tableA: { id: aId }, tableB: { id: bId } })
connection.query(...qb.toQuery())
API
select
参数类型
- type field = string | number | boolean
- type cb = (qb: queryBuilder, tableName?: string) => queryBuilder | { sql: string, values: any[] } | void
- type renane = string
- type asArr = [field, rename]
- type fieldArr = (field | asArr | cb)[]
- type asObj = { [key: string]: rename }
- type fieldObj = asObj | { [key: string]: cb }
- type table = { tableName: valObj | valArr | cb }
函数
select(params: field) => qb | 单个字段 |
select(params: fieldArr | fieldObj) => qb | 多个字段 |
select(params: table) => qb | 字段中带表名,支持多个表 |
select(params: cb) => qb | 使用函数,参数是一个新的queryBuilder对象,返回值可有可无 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.select('a')
qb.select(1)
qb.select()
qb.select(['a', 'b'])
qb.select([['a', 'r']])
qb.select({ a: 'ra', b: 'rb' })
qb.select({})
qb.select({ t: ['a'] })
qb.select({ t1: { a: 'ra' }, t2: [['a', 'ra'], 'b'] })
qb.select({ t: [] })
qb.select({ min: qb => qb.min('a') })
qb.select([() => ({ sql: 'max(??) as ??', values: ['a', 'maxA'] }), 'b'])
qb.select({ t: { avg(qb, tableName) { this.avg([tableName, 'a'], 'a') }, b: 'b' } })
count max min avg sum
参数类型
- type name = string
- type tableName = string
函数
qb-mysql
只内置了count
, max
, min
, avg
, sum
这几个常用的函数,且一般要配合select的子函数使用
count(as?: name) => qb | 无 |
max(field: name, as?: name) => qb | 无 |
min(field: name, as?: name) => qb | 无 |
avg(field: name, as?: name) => qb | 无 |
sum(field: name, as?: name) => qb | 无 |
max([tableName, name], as?: name) => qb | 含表名 |
min([tableName, name], as?: name) => qb | 含表名 |
avg([tableName, name], as?: name) => qb | 含表名 |
sum([tableName, name], as?: name) => qb | 含表名 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.count('count')
qb.avg('a')
qb.max('a', 'maxA')
qb.min(['t', 'a'], 'min')
qb.select((sub) => sub.sum('a', 'sum'))
from
参数类型
函数
from(tableName: name, rename?: name) => qb | 无 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.from('t')
qb.from('t', 'r')
join
参数类型
- type name = string
- type targetTableKey = string
- type sourceTableKey = string
- type mapping = { [key: targetTableKey]: sourceTableKey }
函数
包含join
, innerJoin
, leftJoin
, rightJoin
, crossJoin
, outerJoin
,当使用join
函数时参数列表多最后一个prefix参数,可以用于添加join前的修饰词,以弥补内置函数不够用的情况
from(sourcetable: name).join(targetTable: name, mapping: mapping) => qb | 配合from一起使用,使用前两个参数即可,如果要指定prefix,则需要补齐4个参数 |
join(targetTable: name, mapping: mapping, sourceTabel: name, prefix?: string) => qb | 独立使用,需要第三个参数 |
join(params: { target: name, mapping: mapping, source: name }, prefix?: string) => qb | 独立使用,第一个参数使用object类型 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.from('t1').join('t2', { t1_id: 'id' })
qb.from('t1').join('t2', { t1_id: 'id' }).join('t3', { t2_id: id }, 't2')
qb.join({ source: 't1', target: 't2', mapping: { t1_id: id } }) =>
where
参数类型
- type name = string
- type cb = (qb: queryBuilder) => queryBuilder | { sql: string, values: any[] } | void
- type value = string | number | boolean | cb | null | undefined
- type valueArr = value[]
- type ops = 'in' | 'not in' | 'like' | 'not like' | 'is' | 'is not' | '!=' | '>' | '<' | '>=' | '<=' | '=' | '<>' | '<=>'
- type opVal = { operator: ops, value: value, prefix?: boolean, suffix?: boolean }
- type opsInObj = 'in' | 'not in' | 'like' | 'not like' | '%like' | 'not %like' | 'like%' | 'not like%' | 'is' | 'is not' | '!=' | '>' | '<' | '>=' | '<=' | '=' | '<>' | '<=>'
- type opObj = { [key: opsInObj]: val }
- type conditionObj = { [key: name]: value | valueArr | opVal | opObj }
- type conditionObjWithOr = { [anyKey: string]: conditionObj[] }
- type withTable = { [table: name]: conditionObje | conditionObjWithOr }
- type conditionArr = (conditionObj | conditionObjWithOr | withTable)[]
函数
where(condition: conditionObj, tableName?: name) => qb | 最外层使用and连接,且只有and连接 |
where(condition: conditionObjWithOr, tableName?: name) => qb | 最外层使用and连接,中间含or,and子句 |
where(condition: withTable) => qb | 多个表联合过滤 |
where(condition: conditionArr, tableName?: name) => qb | 最层使用or连接,中间含and,or子句 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.where({ a: 1, b: 'b', c: true })
qb.where({ a: [1, 2 ,3], b: 'str' }, 't')
qb.where({ a: sub => sub.select('f1').from('t').where({ f2: 1 }), b: 2 })
qb.where({ a: { operator: '>', value: 1 }, b: { operator: 'like', value: 'str', prefix: true } })
qb.where({ a: { operator: 'in', value: sub => sub.select('f1').from('t').where({ f2: 1 }) }, b: 1 })
qb.where({ a: { in: [1, 2, 3] }, b: { '>': 1, '<': 10 } })
qb.where({ a: { in: sub => sub.select('f1').from('t').where({ f2: 1 }) }, b: 1 })
qb.where({
a: { is: undefined },
or1: [
{ b: { '<': 2, '>': 0 } },
{ b: [1, 2] },
],
or2: [
{ c: 1 },
{ d: { 'like%': str } },
{ e: '3', f: true, or: [{ g: { is: null } }, { g: { like: 'gg' } }] },
],
h: 'str',
}, 't')
qb.where({ t1: { a: { '=': 1 } }, t2: { b: { like: 'a' } } }, 't')
qb.where({
t1: { a: { is: undefined } },
or1: [
{ t1: { b: { '<': 2, '>': 0 } } },
{ t2: { b: [1, 2] } },
],
or2: [
{ t3: { c: 1 } },
{ t3: { d: { '<': 10 } } },
{
t3: { e: '3' },
t4: { f: true },
or: [
{ t4: { g: { is: null } } },
{ t4: { g: { like: 'gg' } } },
],
},
],
t5: { h: 'str' },
})
qb.where([{ a: { '>': 0, '<': 10 } }, { a: 15 }])
qb.where([{ a: { '>': 0, '<': 10 } }, { a: 15 }], 't')
qb.where([
{ t1: { a: { '>': 0, '<': 10 } } },
{ t1: { a: 15 } },
{
t2: { c: { is: undefined } },
or1: [
{ t3: { b: { '<': 2, '>': 0 } } },
{ t3: { b: [1, 2] } },
],
or2: [
{ t4: { d: 20 } },
{ t5: { d: { '<': 10 } } },
{
t6: { e: '3' },
t7: { f: true },
or: [
{ t7: { g: { is: null } } },
{ t8: { g: { like: 'gg' } } },
],
},
],
t9: { h: 'str' },
},
])
order by
参数类型
- type field = string
- type order = 'desc' | 'asc' | 'DESC' | 'ASC'
- type fieldOrder = string
- type fieldsOrder = { [key: field]: order }
函数
order(param: fieldOrder) => qb | 对一个字段排序,排序方式与字段间使用空格连接,可以没有排序方式 |
order(param: fieldsOrder) => qb | 对多个字段排序,key是字段,value是排序方式,value是空值表示不填排序方式 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.order('a')
qb.order('a desc')
qb.order({ a: '', b: 'desc' })
qb.order({ a: 'DESC', b: 'ASC' })
page limit offset
参数类型
函数
limit(size: val) => qb | size需大于0 |
offset(size: val) => qb | size需大于0 |
page(size: val, page?: val) => qb | page是对limit和offse的组合 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.limit(1)
qb.offset(1)
qb.page(10)
qb.page(10, 1)
qb.page(10, 2)
group by
参数类型
- type name = string
- type table = { [key: name]: name | name[] }
函数
| 列表 | 说明 |
|-------------------------------------------------|---------------------------|
| group(field: name, tableName?: name) => qb | 对单个字段聚合 |
| group(fields: name[], tableName?: string) => qb | 对多个字段聚合 |
| group(fields: table) => qb | 对多个表的字段聚合 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.group('a')
qb.group('a', 't')
qb.group(['a', 'b'])
qb.group(['a', 'b'], 't')
qb.group({ t: 'a' })
qb.group({ t1: ['a', 'b'], t2: 'c' })
insert
参数类型
- type name = string
- type data = { [key: string]: string | number | boolean | null | undefined }
函数
insert(tableName: name, data: data) => qb | 插入单行数据 |
insert(tableName: name, data: data[]) => qb | 插入多行数据 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.insert('t', { a: 1, b: '2', c: true })
qb.insert('t', [{ a: 1, b: 'b' }, { a: 2, b: 'b' }])
update
参数类型
- type name = string
- type data = { [key: string]: string | number | boolean | null | undefined }
- type where = Array | Object // 参考where部分
函数
update(tableName: name, data: data, where?: where) => qb | 第三个where参数选填,与where函数参数类型相同 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.update('t', { a: true, b: 'str', c: null, d: 1 })
qb.update('t', { a: true, b: 'str', c: null }, [{ id: 1 }, { f: 2 }])
delete
参数类型
- type name = string
- type where = Array | Object // 参考where部分
函数
delete(tableName: name, where?: where) => qb | 第二个where参数选填,与where函数参数类型相同 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.delete('t')
qb.delete('t', { id: 1 })
sub padEnd
当内置函数不能满足需求时,可通过sub
高阶函数自定义sql,sub
只接受一个回调函数类型的参数, 该回调函数有一个参数qb
。
qb
是一个全新的queryBuilder
对象,当回调函数使用非箭头函数时,this
指向这个queryBuilder
对象,因此使用this
与使用qb
是等效的
回调函数的返回值可有可无,如果有返回值,返回值必须是包含 { sql: string, values: [] }
的对象,qb-mysql
内部会使用sql
和values
的值用于生成query对象,如果没有返回值,则会直接使用回调函数参数queryBuilder
对象中的值。
padEnd
是辅助型函数,用于向queryBuilder.sql
和queryBuilder.value
上追加内容,一般会配合sub
函数一起使用。
参数类型
- type cb = (qb: queryBuilder) => queryBuilder | { sql: string, values: any[] } | void
- type values = any[]
- type val = string | values
函数
sub(callback: cb) => qb | cb函数参数是一个新的queryBuilder对象,返回值可有可无 |
padEnd(str: val, values?: values) => qb | 向queryBuilder.sql追加string,第2个参数向queryBuilder.values追加值 |
例子
以下的结果均通过调用 console.log(mysql.format(...qb.toQuery()))
得到
qb.select('f1')
.from('t1')
.padEnd(' where ?? in (', ['f2'])
.sub((builder) => builder.select('f3').from('t2')
.padEnd(')')
clear
qb-mysql
支持了多query查询,当使用多个语句(即多次调用select, insert, update, delete)时,会自动在各条语句间插入分号(;)
clear
用于清空queryBuilder中已经构建的query语句,当需要新建一条query时,可以使用 const queryBuilder = new QueryBuilder()
的方式,也可以在已经实例化的queryBuilder
对象上调用queryBuilder.clear()
参数类型
函数
clear() => qb | 一般用于同一作用域中多次调用函数生成query时,清除前面生成的内容 |
例子
const qb = new QueryBuilder()
qb.select('a').from('t').select(qb => qb.count()).form('t')
qb.clear().select(qb => qb.count()).form('t')
qb.select(qb => qb.count()).form('t')
注意事项
-
调用各个子函数时,调用顺序要严格按照 sql 语句的语法,为了最小化代码体积,qb-mysql
并未做语法分析和检测,因此及时语句有错误也会生成query对象。
- 比如 qb.from('t').select('a') 会生成
from `t`select `a`
- 尤其要特别注意
order
, limit
, offset
的顺序,写反了会导致sql查询异常
-
qb-mysql
支持了多query查询,当使用多个语句时,会自动在各个语句间插入分号(;
),但要注意这不是事务安全的,例如
qb.select('a').from('t').select(qb => qb.count()).form('t')
qb.update('t', { a: 'a' }, { id: 1 } ).select().form('t').where({ id: 1 })