Pivot(透视)是SQL中的一种数据转换操作,它将行数据转换为列数据,通常用于数据汇总和交叉分析。子查询则是嵌套在另一个SQL查询中的查询语句。
当两者结合使用时,可以在Pivot操作中使用子查询来动态生成透视表的列或提供透视所需的数据。
SELECT * FROM (
SELECT department, employee_name, salary
FROM employees
WHERE hire_date > '2020-01-01'
) AS source_data
PIVOT (
AVG(salary) FOR department IN ('IT', 'HR', 'Finance')
) AS pivot_table;
应用场景:当需要对源数据进行预处理或过滤后再进行透视时使用。
DECLARE @columns NVARCHAR(MAX) = '';
DECLARE @sql NVARCHAR(MAX) = '';
-- 使用子查询动态获取列名
SELECT @columns = @columns + '[' + department + '],'
FROM (SELECT DISTINCT department FROM employees) AS depts;
SET @columns = LEFT(@columns, LEN(@columns) - 1);
SET @sql = '
SELECT * FROM (
SELECT employee_name, department, salary
FROM employees
) AS source_data
PIVOT (
AVG(salary) FOR department IN (' + @columns + ')
) AS pivot_table;';
EXEC sp_executesql @sql;
应用场景:当列名不确定或需要从数据中动态获取时使用。
SELECT * FROM employees
PIVOT (
AVG(
SELECT salary
FROM salary_history
WHERE salary_history.emp_id = employees.id
AND year = 2023
)
FOR department IN ('IT', 'HR', 'Finance')
) AS pivot_table;
应用场景:当需要对每个透视值进行复杂计算时使用。
原因:在Pivot的聚合函数中使用的子查询返回了多行数据。
解决方案:确保子查询只返回单行单列,或使用TOP/LIMIT限制结果。
-- 错误示例
PIVOT (
(SELECT salary FROM salary_history WHERE emp_id = employees.id)
FOR department IN ('IT', 'HR')
)
-- 正确示例
PIVOT (
(SELECT AVG(salary) FROM salary_history WHERE emp_id = employees.id)
FOR department IN ('IT', 'HR')
)
原因:直接拼接SQL字符串可能导致注入攻击。
解决方案:使用参数化查询或严格验证输入。
-- 不安全的方式
SET @sql = '... FOR department IN (' + @columns + ') ...'
-- 更安全的方式(SQL Server示例)
DECLARE @ParmDefinition NVARCHAR(500) = '@Columns NVARCHAR(MAX)';
SET @sql = '... FOR department IN (' + @columns + ') ...'
EXEC sp_executesql @sql, @ParmDefinition, @Columns = @columns;
原因:复杂的子查询可能导致Pivot操作性能下降。
解决方案:
-- 优化示例:使用临时表
SELECT department, employee_name, salary
INTO #temp_employees
FROM employees
WHERE hire_date > '2020-01-01';
SELECT * FROM #temp_employees
PIVOT (
AVG(salary) FOR department IN ('IT', 'HR', 'Finance')
) AS pivot_table;
DROP TABLE #temp_employees;
-- 使用子查询获取最近3个月的销售数据并透视
SELECT * FROM (
SELECT
p.product_name,
DATENAME(MONTH, s.sale_date) AS month,
s.quantity * s.unit_price AS sales_amount
FROM sales s
JOIN products p ON s.product_id = p.id
WHERE s.sale_date >= DATEADD(MONTH, -3, GETDATE())
) AS source_data
PIVOT (
SUM(sales_amount)
FOR month IN ([January], [February], [March])
) AS pivot_table;
-- 动态生成科目列名
DECLARE @subjects NVARCHAR(MAX);
DECLARE @sql NVARCHAR(MAX);
SELECT @subjects = STRING_AGG(QUOTENAME(subject_name), ',')
FROM (SELECT DISTINCT subject_name FROM courses) AS subj;
SET @sql = N'
SELECT student_name, ' + @subjects + '
FROM (
SELECT s.student_name, c.subject_name, sc.score
FROM student_courses sc
JOIN students s ON sc.student_id = s.id
JOIN courses c ON sc.course_id = c.id
WHERE sc.semester = ''Fall 2023''
) AS source_data
PIVOT (
AVG(score) FOR subject_name IN (' + @subjects + ')
) AS pivot_table;';
EXEC sp_executesql @sql;
SQL中的Pivot与子查询结合使用可以创建灵活、动态的数据透视表,特别适合处理需要动态列名或复杂数据预处理的情况。使用时需要注意性能优化和安全性问题,合理使用临时表和索引可以提高查询效率。
没有搜到相关的文章