SQL动态查询指的是根据运行时传入的参数动态调整查询的条件、表名、排序规则等内容,从而适配不同的查询需求,在实际业务场景中应用非常广泛,比如电商平台的商品筛选、后台管理系统的多条件检索等功能都需要用到动态查询。

什么是SQL动态查询
静态SQL是在编写代码时就确定了完整的查询语句,运行时无法修改查询逻辑。而动态查询则是在程序运行阶段,根据传入的参数动态拼接或者调整SQL语句的内容,最终生成符合当前查询需求的完整SQL再执行。比如用户可以选择按商品名称、价格区间、分类等条件组合查询,动态查询就能根据用户输入的条件自动生成对应的WHERE子句。
SQL动态查询的常见构建方法
1. 存储过程中拼接动态SQL
很多数据库都支持在存储过程中编写逻辑来拼接动态SQL,以MySQL为例,我们可以使用CONCAT函数拼接SQL字符串,再通过PREPARE和EXECUTE执行动态语句。
以下是一个根据用户传入的商品名称和分类ID动态查询商品的存储过程示例:
-- 创建存储过程,传入商品名称、分类ID参数,无传入则查询全部
DELIMITER //
CREATE PROCEDURE query_products(
IN p_product_name VARCHAR(50),
IN p_category_id INT
)
BEGIN
-- 定义动态SQL变量
DECLARE v_sql VARCHAR(1000);
-- 初始化基础查询语句
SET v_sql = 'SELECT id, product_name, price, category_id FROM products WHERE 1=1';
-- 如果传入了商品名称,拼接名称条件
IF p_product_name IS NOT NULL AND p_product_name != '' THEN
SET v_sql = CONCAT(v_sql, ' AND product_name LIKE ''%', p_product_name, '%''');
END IF;
-- 如果传入了分类ID,拼接分类条件
IF p_category_id IS NOT NULL THEN
SET v_sql = CONCAT(v_sql, ' AND category_id = ', p_category_id);
END IF;
-- 预编译动态SQL
SET @final_sql = v_sql;
PREPARE stmt FROM @final_sql;
-- 执行预编译的语句
EXECUTE stmt;
-- 释放预编译语句
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
-- 调用存储过程,查询名称包含手机且分类ID为2的商品
CALL query_products('手机', 2);
-- 调用存储过程,查询所有商品
CALL query_products(NULL, NULL);
这种方法的优点是逻辑集中在数据库端,减少应用层和数据库的交互次数,但是需要注意SQL注入风险,拼接字符串时要对特殊字符做处理。
2. 应用层拼接SQL结合参数化查询
在应用代码中先根据参数拼接SQL的框架,再使用参数化查询传入条件值,避免直接拼接字符串带来的注入问题。以Java的JDBC为例,实现动态查询的代码如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class DynamicQueryDemo {
public static void queryProducts(String productName, Integer categoryId) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 获取数据库连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false", "root", "123456");
// 拼接SQL基础部分
StringBuilder sqlBuilder = new StringBuilder("SELECT id, product_name, price, category_id FROM products WHERE 1=1");
// 存储参数的列表
List<Object> params = new ArrayList<>();
// 根据条件拼接SQL和参数
if (productName != null && !productName.isEmpty()) {
sqlBuilder.append(" AND product_name LIKE ?");
params.add("%" + productName + "%");
}
if (categoryId != null) {
sqlBuilder.append(" AND category_id = ?");
params.add(categoryId);
}
// 创建预编译语句
pstmt = conn.prepareStatement(sqlBuilder.toString());
// 设置参数
for (int i = 0; i < params.size(); i++) {
pstmt.setObject(i + 1, params.get(i));
}
// 执行查询
rs = pstmt.executeQuery();
// 处理结果
while (rs.next()) {
System.out.println("商品ID:" + rs.getInt("id") + ",名称:" + rs.getString("product_name"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这种方式把动态逻辑放在应用层,灵活性更高,同时参数化查询能有效避免SQL注入,是目前最常用的动态查询实现方式。
3. 使用ORM框架的动态查询能力
主流的ORM框架比如MyBatis、Hibernate都提供了动态查询的支持,以MyBatis的if标签为例,可以在XML映射文件中编写动态SQL:
<select id="queryProducts" resultType="Product">
SELECT id, product_name, price, category_id
FROM products
WHERE 1=1
<if test="productName != null and productName != ''">
AND product_name LIKE CONCAT('%', #{productName}, '%')
</if>
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
</select>
ORM框架的动态查询不需要手动拼接字符串,框架会自动处理条件拼接和参数映射,开发效率更高,也减少了语法错误的可能。
动态查询的注意事项
- 避免SQL注入:尽量不要直接拼接用户输入的字符串到SQL中,优先使用参数化查询或者框架提供的动态SQL能力。
- 性能问题:动态拼接的SQL可能无法复用执行计划,频繁执行时可能影响性能,可以适当使用预编译语句优化。
- 条件合理性:要对传入的参数做校验,避免出现不合理的查询条件导致全表扫描,影响数据库性能。
不同方法的适用场景
如果是简单的查询逻辑,且主要在数据库内部使用,可以选择存储过程拼接动态SQL;如果是应用层的业务查询,优先选择应用层拼接结合参数化查询,或者使用ORM框架的动态查询能力,开发效率和安全性都更有保障。