SQL 选择
Drizzle 为您提供了从数据库中获取数据的最 SQL 风格的方法,同时保持类型安全和可组合性。
它原生支持几乎每种方言的所有查询特性和能力,
而其尚不支持的功能,可以通过强大的 sql
操作符由用户添加。
假设您有一个如下面定义的 users
表:
import { pgTable , serial , text } from 'drizzle-orm/pg-core' ;
export const users = pgTable ( 'users' , {
id : serial ( 'id' ) .primaryKey () ,
name : text ( 'name' ) .notNull () ,
age : integer ( 'age' ) ,
});
import { mysqlTable , serial , text , int } from 'drizzle-orm/mysql-core' ;
export const users = mysqlTable ( 'users' , {
id : serial ( 'id' ) .primaryKey () ,
name : text ( 'name' ) .notNull () ,
age : int ( 'age' ) ,
});
import { sqliteTable , integer , text } from 'drizzle-orm/sqlite-core' ;
export const users = sqliteTable ( 'users' , {
id : integer ( 'id' ) .primaryKey () ,
name : text ( 'name' ) .notNull () ,
age : integer ( 'age' ) ,
});
import { singlestoreTable , serial , text , int } from 'drizzle-orm/singlestore-core' ;
export const users = singlestoreTable ( 'users' , {
id : int ( 'id' ) .primaryKey () ,
name : text ( 'name' ) .notNull () ,
age : int ( 'age' ) ,
});
基本选择
从一个表中选择所有行,包括所有列:
const result = await db .select () .from (users);
/*
{
id: number;
name: string;
age: number | null;
}[]
*/
select "id" , "name" , "age" from "users" ;
注意结果类型是根据表的定义自动推断的,包括列的可空性。
Drizzle 始终在 select
子句中明确列出列,而不是使用 select *
。
这是内部要求的,以保证查询结果中的字段顺序,也是通常认为的良好实践。
部分选择
在某些情况下,您可能只想选择表中的一部分列。
您可以通过向 .select()
方法提供选择对象来实现:
const result = await db .select ({
field1 : users .id ,
field2 : users .name ,
}) .from (users);
const { field1 , field2 } = result[ 0 ];
select "id" , "name" from "users" ;
与 SQL 一样,您可以使用任意表达式作为选择字段,而不仅仅是表列:
const result = await db .select ({
id : users .id ,
lowerName : sql < string > `lower( ${ users .name } )` ,
}) .from (users);
select "id" , lower ( "name" ) from "users" ;
IMPORTANT
通过指定 sql<string>
,您告诉 Drizzle 该字段的 预期 类型为 string
。
如果您错误地指定(例如,对将作为字符串返回的字段使用 sql<number>
),运行时值将与预期类型不匹配。
Drizzle 不能根据提供的类型泛型执行任何类型转换,因为该信息在运行时不可用。
如果您需要对返回值应用运行时转换,您可以使用 .mapWith()
方法。
条件选择
您可以基于某个条件拥有动态选择对象:
async function selectUsers (withName : boolean ) {
return db
.select ({
id : users .id ,
... (withName ? { name : users .name } : {}) ,
})
.from (users);
}
const users = await selectUsers ( true );
不重复选择
您可以使用 .selectDistinct()
代替 .select()
以仅从数据集中检索唯一行:
await db .selectDistinct () .from (users) .orderBy ( usersTable .id , usersTable .name);
await db .selectDistinct ({ id : users .id }) .from (users) .orderBy ( usersTable .id);
select distinct "id" , "name" from "users" order by "id" , "name" ;
select distinct "id" from "users" order by "id" ;
在 PostgreSQL 中,您还可以使用 distinct on
子句指定唯一行的确定方式:
IMPORTANT
distinct on
子句仅在 PostgreSQL 中受支持。
await db .selectDistinctOn ([ users .id]) .from (users) .orderBy ( users .id);
await db .selectDistinctOn ([ users .name] , { name : users .name }) .from (users) .orderBy ( users .name);
select distinct on ( "id" ) "id" , "name" from "users" order by "id" ;
select distinct on ( "name" ) "name" from "users" order by "name" ;
高级选择
借助 TypeScript,Drizzle API 让您以各种灵活的方式构建选择查询。
高级部分选择的预览,更多详细的高级用法示例请参阅我们专门的指南 。
import { getTableColumns , sql } from 'drizzle-orm' ;
await db .select ({
... getTableColumns (posts) ,
titleLength : sql < number > `length( ${ posts .title } )` ,
}) .from (posts);
import { getTableColumns } from 'drizzle-orm' ;
const { content , ... rest } = getTableColumns (posts); // 排除 "content" 列
await db .select ({ ... rest }) .from (posts); // 选择所有其他列
await db . query . posts .findMany ({
columns : {
title : true ,
} ,
});
await db . query . posts .findMany ({
columns : {
content : false ,
} ,
});
---
过滤器
您可以使用 过滤操作符 在 .where()
方法中过滤查询结果:
import { eq , lt , gte , ne } from 'drizzle-orm' ;
await db .select () .from (users) .where ( eq ( users .id , 42 ));
await db .select () .from (users) .where ( lt ( users .id , 42 ));
await db .select () .from (users) .where ( gte ( users .id , 42 ));
await db .select () .from (users) .where ( ne ( users .id , 42 ));
...
select "id" , "name" , "age" from "users" where "id" = 42 ;
select "id" , "name" , "age" from "users" where "id" < 42 ;
select "id" , "name" , "age" from "users" where "id" >= 42 ;
select "id" , "name" , "age" from "users" where "id" <> 42 ;
所有过滤操作符都使用 sql
函数实现。
您可以自行使用它编写任意 SQL 过滤器,或构建自己的操作符。
有关灵感,您可以查看 Drizzle 提供的操作符是如何实现 的。
import { sql } from 'drizzle-orm' ;
function equals42 (col : Column ) {
return sql ` ${ col } = 42` ;
}
await db .select () .from (users) .where ( sql ` ${ users .id } < 42` );
await db .select () .from (users) .where ( sql ` ${ users .id } = 42` );
await db .select () .from (users) .where ( equals42 ( users .id));
await db .select () .from (users) .where ( sql ` ${ users .id } >= 42` );
await db .select () .from (users) .where ( sql ` ${ users .id } <> 42` );
await db .select () .from (users) .where ( sql `lower( ${ users .name } ) = 'aaron'` );
select "id" , "name" , "age" from "users" where 'id' < 42 ;
select "id" , "name" , "age" from "users" where 'id' = 42 ;
select "id" , "name" , "age" from "users" where 'id' = 42 ;
select "id" , "name" , "age" from "users" where 'id' >= 42 ;
select "id" , "name" , "age" from "users" where 'id' <> 42 ;
select "id" , "name" , "age" from "users" where lower ( "name" ) = 'aaron' ;
所有提供给过滤操作符和 sql
函数的值都会自动参数化。
例如,这个查询:
await db .select () .from (users) .where ( eq ( users .id , 42 ));
将被翻译为:
select "id" , "name" , "age" from "users" where "id" = $ 1 ; -- params: [42]
使用 not
操作符反转条件:
import { eq , not , sql } from 'drizzle-orm' ;
await db .select () .from (users) .where ( not ( eq ( users .id , 42 )));
await db .select () .from (users) .where ( sql `not ${ users .id } = 42` );
select "id" , "name" , "age" from "users" where not ( "id" = 42 );
select "id" , "name" , "age" from "users" where not ( "id" = 42 );
您可以安全地更改模式,重命名表和列
并且由于模板插值,它将在您的查询中自动反映出来,
而不是在编写原始 SQL 时硬编码列或表名。
组合过滤器
您可以逻辑上结合过滤操作符与 and()
和 or()
操作符:
import { eq , and , sql } from 'drizzle-orm' ;
await db .select () .from (users) .where (
and (
eq ( users .id , 42 ) ,
eq ( users .name , 'Dan' )
)
);
await db .select () .from (users) .where ( sql ` ${ users .id } = 42 and ${ users .name } = 'Dan'` );
select "id" , "name" , "age" from "users" where "id" = 42 and "name" = 'Dan' ;
select "id" , "name" , "age" from "users" where "id" = 42 and "name" = 'Dan' ;
import { eq , or , sql } from 'drizzle-orm' ;
await db .select () .from (users) .where (
or (
eq ( users .id , 42 ) ,
eq ( users .name , 'Dan' )
)
);
await db .select () .from (users) .where ( sql ` ${ users .id } = 42 or ${ users .name } = 'Dan'` );
select "id" , "name" , "age" from "users" where "id" = 42 or "name" = 'Dan' ;
select "id" , "name" , "age" from "users" where "id" = 42 or "name" = 'Dan' ;
高级过滤器
结合 TypeScript,Drizzle API 为您提供强大而灵活的方式来组合查询中的过滤器。
条件过滤的预览,更多详细的高级用法示例请参阅我们专门的指南 。
const searchPosts = async (term ?: string ) => {
await db
.select ()
.from (posts)
.where (term ? ilike ( posts .title , term) : undefined );
};
await searchPosts ();
await searchPosts ( 'AI' );
const searchPosts = async (filters : SQL []) => {
await db
.select ()
.from (posts)
.where ( and ( ... filters));
};
const filters : SQL [] = [];
filters .push ( ilike ( posts .title , 'AI' ));
filters .push ( inArray ( posts .category , [ 'Tech' , 'Art' , 'Science' ]));
filters .push ( gt ( posts .views , 200 ));
await searchPosts (filters);
---
限制与偏移
使用 .limit()
和 .offset()
向查询添加 limit
和 offset
子句 - 例如,实现分页:
await db .select () .from (users) .limit ( 10 );
await db .select () .from (users) .limit ( 10 ) .offset ( 10 );
select "id" , "name" , "age" from "users" limit 10 ;
select "id" , "name" , "age" from "users" limit 10 offset 10 ;
排序
使用 .orderBy()
向查询添加 order by
子句,按指定字段对结果进行排序:
import { asc , desc } from 'drizzle-orm' ;
await db .select () .from (users) .orderBy ( users .name);
await db .select () .from (users) .orderBy ( desc ( users .name));
// 按多个字段排序
await db .select () .from (users) .orderBy ( users .name , users .name2);
await db .select () .from (users) .orderBy ( asc ( users .name) , desc ( users .name2));
select "id" , "name" , "age" from "users" order by "name" ;
select "id" , "name" , "age" from "users" order by "name" desc ;
select "id" , "name" , "age" from "users" order by "name" , "name2" ;
select "id" , "name" , "age" from "users" order by "name" asc , "name2" desc ;
高级分页
由 TypeScript 驱动,Drizzle APIs 允许您实现所有可能的 SQL 分页和排序方法。
高级分页的预览,更多详细的高级使用示例 -
请参见我们专门的 限制偏移分页 和
游标分页 指南。
await db
.select ()
.from (users)
.orderBy ( asc ( users .id)) // order by 是必须的
.limit ( 4 ) // 返回的行数
.offset ( 4 ); // 跳过的行数
const getUsers = async (page = 1 , pageSize = 3 ) => {
await db . query . users .findMany ({
orderBy : (users , { asc }) => asc ( users .id) ,
limit : pageSize ,
offset : (page - 1 ) * pageSize ,
});
};
await getUsers ();
const getUsers = async (page = 1 , pageSize = 10 ) => {
const sq = db
.select ({ id : users .id })
.from (users)
.orderBy ( users .id)
.limit (pageSize)
.offset ((page - 1 ) * pageSize)
.as ( 'subquery' );
await db .select () .from (users) .innerJoin (sq , eq ( users .id , sq .id)) .orderBy ( users .id);
};
const nextUserPage = async (cursor ?: number , pageSize = 3 ) => {
await db
.select ()
.from (users)
.where (cursor ? gt ( users .id , cursor) : undefined ) // 如果提供了游标,则获取该游标之后的行
.limit (pageSize) // 返回的行数
.orderBy ( asc ( users .id)); // 排序
};
// 传递前一页最后一行的游标 (id)
await nextUserPage ( 3 );
---
WITH 子句
使用 with
子句可以通过将复杂查询拆分成较小的子查询(称为公共表表达式(CTEs))来简化查询:
const sq = db .$with ( 'sq' ) .as ( db .select () .from (users) .where ( eq ( users .id , 42 )));
const result = await db .with (sq) .select () .from (sq);
with sq as ( select "id" , "name" , "age" from "users" where "id" = 42 )
select "id" , "name" , "age" from sq;
若要选择 CTE 中的任意 SQL 值作为字段并在其他 CTE 或主查询中引用它们,
您需要为它们添加别名:
const sq = db .$with ( 'sq' ) .as ( db .select ({
name : sql < string > `upper( ${ users .name } )` .as ( 'name' ) ,
})
.from (users));
const result = await db .with (sq) .select ({ name : sq .name }) .from (sq);
如果您不提供别名,字段类型将变为 DrizzleTypeError
,您将无法在其他查询中引用它。
如果忽略类型错误并仍然尝试使用该字段,
您将得到运行时错误,因为没有方法可以在没有别名的情况下引用该字段。
从子查询中选择
就像在 SQL 中一样,您可以通过使用子查询 API 将查询嵌套到其他查询中:
const sq = db .select () .from (users) .where ( eq ( users .id , 42 )) .as ( 'sq' );
const result = await db .select () .from (sq);
select "id" , "name" , "age" from ( select "id" , "name" , "age" from "users" where "id" = 42 ) "sq" ;
子查询可以在任何可以使用表的位置使用,例如在联接中:
const sq = db .select () .from (users) .where ( eq ( users .id , 42 )) .as ( 'sq' );
const result = await db .select () .from (users) .leftJoin (sq , eq ( users .id , sq .id));
select "users" . "id" , "users" . "name" , "users" . "age" , "sq" . "id" , "sq" . "name" , "sq" . "age" from "users"
left join ( select "id" , "name" , "age" from "users" where "id" = 42 ) "sq"
on "users" . "id" = "sq" . "id" ;
---
聚合
使用 Drizzle,您可以通过使用 sum
、count
、avg
等函数进行聚合,
通过 .groupBy()
和 .having()
分别进行分组和过滤,就像在原始 SQL 中一样:
import { gt } from 'drizzle-orm' ;
await db .select ({
age : users .age ,
count : sql < number > `cast(count( ${ users .id } ) as int)` ,
})
.from (users)
.groupBy ( users .age);
await db .select ({
age : users .age ,
count : sql < number > `cast(count( ${ users .id } ) as int)` ,
})
.from (users)
.groupBy ( users .age)
.having (({ count }) => gt (count , 1 ));
select "age" , cast ( count ( "id" ) as int )
from "users"
group by "age" ;
select "age" , cast ( count ( "id" ) as int )
from "users"
group by "age"
having cast ( count ( "id" ) as int ) > 1 ;
cast(... as int)
是必要的,因为 count()
在 PostgreSQL 中返回 bigint
,在 MySQL 中返回 decimal
,这些被视为字符串值而不是数字。
或者,您可以使用 .mapWith(Number)
在运行时将值转换为数字。
如果您需要计数聚合 - 建议使用我们的 $count
API
聚合助手
Drizzle 具有一系列封装的 sql
函数,因此您无需为应用中的常见情况编写
sql
模板。
请记住,聚合函数通常与 SELECT 语句的 GROUP BY 子句一起使用。
因此,如果您在一个查询中选择使用聚合函数和其他列,
请确保使用 .groupBy
子句。
count
返回 expression
中的值的数量。
import { count } from 'drizzle-orm'
await db .select ({ value : count () }) .from (users);
await db .select ({ value : count ( users .id) }) .from (users);
select count ( "*" ) from "users" ;
select count ( "id" ) from "users" ;
// 相当于写
await db .select ({
value : sql `count('*'))` .mapWith (Number)
}) .from (users);
await db .select ({
value : sql `count( ${ users .id } )` .mapWith (Number)
}) .from (users);
countDistinct
返回 expression
中非重复值的数量。
import { countDistinct } from 'drizzle-orm'
await db .select ({ value : countDistinct ( users .id) }) .from (users);
select count ( distinct "id" ) from "users" ;
// 相当于写
await db .select ({
value : sql `count( ${ users .id } )` .mapWith (Number)
}) .from (users);
avg
返回 expression
中所有非空值的平均值(算术平均数)。
import { avg } from 'drizzle-orm'
await db .select ({ value : avg ( users .id) }) .from (users);
select avg ( "id" ) from "users" ;
// 相当于写
await db .select ({
value : sql `avg( ${ users .id } )` .mapWith (String)
}) .from (users);
avgDistinct
返回 expression
中所有非空值的平均值(算术平均数)。
import { avgDistinct } from 'drizzle-orm'
await db .select ({ value : avgDistinct ( users .id) }) .from (users);
select avg ( distinct "id" ) from "users" ;
// 相当于写
await db .select ({
value : sql `avg(distinct ${ users .id } )` .mapWith (String)
}) .from (users);
sum
返回 expression
中所有非空值的和。
import { sum } from 'drizzle-orm'
await db .select ({ value : sum ( users .id) }) .from (users);
select sum ( "id" ) from "users" ;
// 相当于写
await db .select ({
value : sql `sum( ${ users .id } )` .mapWith (String)
}) .from (users);
sumDistinct
返回 expression
中所有非空和非重复值的和。
import { sumDistinct } from 'drizzle-orm'
await db .select ({ value : sumDistinct ( users .id) }) .from (users);
select sum ( distinct "id" ) from "users" ;
// 相当于写
await db .select ({
value : sql `sum(distinct ${ users .id } )` .mapWith (String)
}) .from (users);
max
返回 expression
中的最大值。
import { max } from 'drizzle-orm'
await db .select ({ value : max ( users .id) }) .from (users);
select max ( "id" ) from "users" ;
// 相当于写
await db .select ({
value : sql `max( ${ expression } )` .mapWith ( users .id)
}) .from (users);
min
返回 expression
中的最小值。
import { min } from 'drizzle-orm'
await db .select ({ value : min ( users .id) }) .from (users);
select min ( "id" ) from "users" ;
// 相当于写
await db .select ({
value : sql `min( ${ users .id } )` .mapWith ( users .id)
}) .from (users);
更高级的示例:
const orders = sqliteTable ( 'order' , {
id : integer ( 'id' ) .primaryKey () ,
orderDate : integer ( 'order_date' , { mode : 'timestamp' }) .notNull () ,
requiredDate : integer ( 'required_date' , { mode : 'timestamp' }) .notNull () ,
shippedDate : integer ( 'shipped_date' , { mode : 'timestamp' }) ,
shipVia : integer ( 'ship_via' ) .notNull () ,
freight : numeric ( 'freight' ) .notNull () ,
shipName : text ( 'ship_name' ) .notNull () ,
shipCity : text ( 'ship_city' ) .notNull () ,
shipRegion : text ( 'ship_region' ) ,
shipPostalCode : text ( 'ship_postal_code' ) ,
shipCountry : text ( 'ship_country' ) .notNull () ,
customerId : text ( 'customer_id' ) .notNull () ,
employeeId : integer ( 'employee_id' ) .notNull () ,
});
const details = sqliteTable ( 'order_detail' , {
unitPrice : numeric ( 'unit_price' ) .notNull () ,
quantity : integer ( 'quantity' ) .notNull () ,
discount : numeric ( 'discount' ) .notNull () ,
orderId : integer ( 'order_id' ) .notNull () ,
productId : integer ( 'product_id' ) .notNull () ,
});
db
.select ({
id : orders .id ,
shippedDate : orders .shippedDate ,
shipName : orders .shipName ,
shipCity : orders .shipCity ,
shipCountry : orders .shipCountry ,
productsCount : sql < number > `cast(count( ${ details .productId } ) as int)` ,
quantitySum : sql < number > `sum( ${ details .quantity } )` ,
totalPrice : sql < number > `sum( ${ details .quantity } * ${ details .unitPrice } )` ,
})
.from (orders)
.leftJoin (details , eq ( orders .id , details .orderId))
.groupBy ( orders .id)
.orderBy ( asc ( orders .id))
.all ();
$count
db.$count()
is a utility wrapper of count(*)
, it is a very flexible operator which can be used as is or as a subquery, more details in our GitHub discussion .
const count = await db .$count (users);
// ^? number
const count = await db .$count (users , eq ( users .name , "Dan" )); // works with filters
select count ( * ) from "users" ;
select count ( * ) from "users" where "name" = 'Dan' ;
It is exceptionally useful in subqueries :
const users = await db .select ({
... users ,
postsCount : db .$count (posts , eq ( posts .authorId , users .id)) ,
}) .from (users);
usage example with relational queries
const users = await db . query . users .findMany ({
extras : {
postsCount : db .$count (posts , eq ( posts .authorId , users .id)) ,
} ,
});
---
迭代器
如果您需要从查询中返回大量行而不希望将它们全部加载到内存中,可以使用 .iterator()
将查询转换为异步迭代器:
const iterator = await db .select () .from (users) .iterator ();
for await ( const row of iterator) {
console .log (row);
}
它也适用于准备好的语句:
const query = await db .select () .from (users) .prepare ();
const iterator = await query .iterator ();
for await ( const row of iterator) {
console .log (row);
}
---
使用索引
USE INDEX
提示告诉优化器在处理查询时考虑哪些索引。优化器不会强制使用这些索引,但如果它们合适,会优先使用它们。
export const users = mysqlTable ( 'users' , {
id : int ( 'id' ) .primaryKey () ,
name : varchar ( 'name' , { length : 100 }) .notNull () ,
} , () => [usersTableNameIndex]);
const usersTableNameIndex = index ( 'users_name_index' ) .on ( users .name);
await db .select ()
.from (users , { useIndex : usersTableNameIndex })
.where ( eq ( users .name , 'David' ));
您还可以在任何您想要的连接上使用此选项
await db .select ()
.from (users)
.leftJoin (posts , eq ( posts .userId , users .id) , { useIndex : usersTableNameIndex })
.where ( eq ( users .name , 'David' ));
忽略索引
IGNORE INDEX
提示告诉优化器避免使用特定的索引来处理查询。MySQL 将考虑所有其他索引(如果有的话),或在必要时执行全表扫描。
export const users = mysqlTable ( 'users' , {
id : int ( 'id' ) .primaryKey () ,
name : varchar ( 'name' , { length : 100 }) .notNull () ,
} , () => [usersTableNameIndex]);
const usersTableNameIndex = index ( 'users_name_index' ) .on ( users .name);
await db .select ()
.from (users , { ignoreIndex : usersTableNameIndex })
.where ( eq ( users .name , 'David' ));
您还可以在任何您想要的连接上使用此选项
await db .select ()
.from (users)
.leftJoin (posts , eq ( posts .userId , users .id) , { useIndex : usersTableNameIndex })
.where ( eq ( users .name , 'David' ));
强制索引
FORCE INDEX
提示强制优化器使用指定的索引进行查询。如果指定的索引无法使用,MySQL不会回退到其他索引;它可能会改为进行全表扫描。
export const users = mysqlTable ( 'users' , {
id : int ( 'id' ) .primaryKey () ,
name : varchar ( 'name' , { length : 100 }) .notNull () ,
} , () => [usersTableNameIndex]);
const usersTableNameIndex = index ( 'users_name_index' ) .on ( users .name);
await db .select ()
.from (users , { forceIndex : usersTableNameIndex })
.where ( eq ( users .name , 'David' ));
您还可以在任何您想要的连接上使用此选项
await db .select ()
.from (users)
.leftJoin (posts , eq ( posts .userId , users .id) , { useIndex : usersTableNameIndex })
.where ( eq ( users .name , 'David' ));