是否可以组合视图和存储过程中的列?

Posted

技术标签:

【中文标题】是否可以组合视图和存储过程中的列?【英文标题】:Is it possible to combine columns from a view and a stored procedure? 【发布时间】:2020-04-08 16:28:43 【问题描述】:

我正在使用 SQL Server 2017。我正在尝试从 SQL Server 返回一组数据,其格式最终将进入 Excel 电子表格。数据很复杂,虽然我可以将两组数据输出到 Excel 中,然后使用代码创建 VLOOKUP 以将数据集结合在一起,但我试图避免增加的复杂性。

为了稍微简化,我为学生所学课程设置了一组分数。该表可能如下所示:

CREATE TABLE TempStudentMarks 
(
    StudentID VARCHAR(4),
    CourseName VARCHAR(50),
    Mark INT
)

INSERT INTO TempStudentMarks (StudentID, CourseName, Mark)
VALUES ('1234', 'English', 78),
        ('1234', 'Maths', 68),
        ('1234', 'Science', 58),
        ('4321', 'English', 66),
        ('4321', 'Maths', 76),
        ('4321', 'French', 86),
        ('5555', 'Maths', 69),
        ('5555', 'Science', 49),
        ('5555', 'French', 69),
        ('6666', 'English', 33),
        ('6666', 'Maths', 44),
        ('6666', 'Science', 55),
        ('6666', 'French', 66)

我创建了一个存储过程,它在一行中输出每个学生,在一列中输出每个课程。它使用动态 SQL,因为课程数量因年而异,并且取决于许多其他因素:

CREATE PROCEDURE spTemp_StudentMarksPivot
AS
    DECLARE @SQL NVARCHAR(MAX)
    DECLARE @PivotCols NVARCHAR(MAX)

    SELECT 
        @PivotCols = COALESCE(@PivotCols + ',', '') + 
                     QUOTENAME(CourseName) 
    FROM 
        (SELECT DISTINCT CourseName 
         FROM TempStudentMarks) AS HeaderData

    PRINT @PivotCols

    SET @SQL = 'SELECT StudentID, ' + @PivotCols + ' 
                FROM (SELECT * FROM TempStudentMarks) SourceData
                PIVOT (MAX(Mark) FOR CourseName IN (' + @PivotCols + ')) AS PivotData'

    PRINT @SQL

    EXEC sys.sp_executesql @SQL

这很好用;我得到的输出:

EXEC spTemp_StudentMarksPivot

看起来像这样:

StudentID English     French      Maths       Science
--------- ----------- ----------- ----------- -----------
1234      78          NULL        68          58
4321      66          86          76          NULL
5555      NULL        69          69          49
6666      33          66          44          55

我还有一个视图可以进行一些统计分析。简化版如下所示:

CREATE VIEW vwStudentAggregates
AS
    SELECT tsm.StudentID, AVG(Mark) AS Average, MAX(Mark) AS BestMark
    FROM TempStudentMarks tsm
    GROUP BY tsm.StudentID

当我针对该视图运行 SELECT 语句时,我得到以下信息:

StudentID Average     BestMark
--------- ----------- -----------
1234      68          78
4321      76          86
5555      62          69
6666      49          66

所有这些都很好。

但我的问题是将两者结合起来,以实现如下输出:

StudentID English     French      Maths       Science     Average    BestMark
--------- ----------- ----------- ----------- ----------- ---------- ----------
1234      78          NULL        68          58          68         78
4321      66          86          76          NULL        76         76
5555      NULL        69          69          49          62         69
6666      33          66          44          55          49         66

这可能吗?

鉴于我事先不知道列的数量或名称,我看不到如何创建临时表。我在这里查看了解决方案:

Insert results of a stored procedure into a temporary table

虽然我认为这可能会完成这项工作,但由于一些用户通过 Windows 身份验证登录,而其他用户必须使用 SQL 身份验证,所以情况变得复杂,因此,虽然该解决方案使用 OpenRowset,但我在有点说

SELECT * 
INTO #MyTempTable 
FROM OPENROWSET('SQLNCLI', 'Server=(local)\SQL2008;Trusted_Connection=yes;',
     'EXEC getBusinessLineHistory')

因为我不知道如何创建一个适合所有人的连接字符串。

[编辑] 我只是在这个想法上玩了一点,并得到了一大堆错误,从

开始

“过程 sp_configure,第 105 行用户无权执行此操作。”

并以

结尾

“SQL Server 阻止访问 STATEMENT 'OpenRowset/OpenDatasource”

所以看起来不太好...(这项工作适用于 ICT 部门的安全性相当严格的客户,并且不太可能希望授予额外权限...

如果有人可以提供帮助或指导,我将不胜感激。

非常感谢 安德鲁

【问题讨论】:

为什么不只计算动态数据透视表中所需的一切?而不是使用引用限制 PIVOT 运算符使用条件聚合,你可以做所有事情。 您好 - 感谢您的回复。你能解释一下它是如何工作的吗?如果只有三四门课程,是的,我可以看到使用 CASE 语句,但我可能有 60 门课程,数百名学生,以及其他变量,如 YearOfStudy 和 CalendarYear 需要考虑,所以我不考虑'认为那行不通。你在想我错过的其他方式吗?你能给我一个代码示例,这样我就可以看到你在想什么?非常感谢 您是说您当前的动态PIVOT 还没有处理所有这些吗? 对不起 - 我很明显很密集。我不知道你的意思...我的动态 Pivot 处理所有什么?!显然,它在适当的标题下方显示了动态创建的数字,但它没有像我想要的那样在末尾添加聚合标记。但我以为你是在建议我不要使用 PIVOT,因为它具有限制性...... 【参考方案1】:
SET @SQL = '
SELECT PivotData.StudentID, ' + @PivotCols + ', vst.Average, vst.BestMark 
FROM (SELECT * FROM TempStudentMarks) SourceData
PIVOT (MAX(Mark) FOR CourseName IN (' + @PivotCols + ')) AS PivotData
join vwStudentAggregates as vst on PivotData.StudentID = vst.StudentID
';
--or
SET @SQL = '
select *
from
(
SELECT StudentID, ' + @PivotCols + '
FROM (SELECT * FROM TempStudentMarks) SourceData
PIVOT (MAX(Mark) FOR CourseName IN (' + @PivotCols + ')) AS PivotData
) as pd
join vwStudentAggregates as vst on pd.StudentID = vst.StudentID
';
--you can even parameterize the procedure, to return or not the studentaggregates and conditionally construct the executed @SQL.

【讨论】:

完美 - 非常感谢。我没有意识到我可以在枢轴之后继续加入表格。这真的很有帮助。 -再次感谢。【参考方案2】:

您正在为不同的表编写 sql,您希望结果在一个表中。 table joining 是这样做的方法。

查询:

select TempStudentMarks.StudentID, avg(TempStudentMarks.mark) as 
average,max(TempStudentMarks.mark) as maximum,
Science.mark as science,French.Mark as french,Math.mark as math,
English.Mark as english from TempStudentMarks
LEFT JOIN 
    (
        select StudentID,mark  from TempStudentMarks where  CourseName='Science'
    ) AS Science
    on Science.StudentID=TempStudentMarks.StudentID
LEFT JOIN 
    (
        select StudentID,mark  from TempStudentMarks where  CourseName='French'
    ) AS French
    on French.StudentID=TempStudentMarks.StudentID

LEFT JOIN 
    (
        select StudentID,mark  from TempStudentMarks where  CourseName='Maths'
    ) AS Math
    on Math.StudentID=TempStudentMarks.StudentID

LEFT JOIN 
    (
        select StudentID,mark  from TempStudentMarks where  CourseName='English'
    ) AS English
    on English.StudentID=TempStudentMarks.StudentID

group by TempStudentMarks.StudentID,French.Mark,Science.Mark,English.Mark,Math.Mark



我不想写一个循环。如果你愿意,你可以自己试一试

【讨论】:

嗨 - 感谢您的建议。我不认为这对我有用,因为课程太多,而且课程列表每年都在变化,所以这个查询每年都需要重新编写。我想我可以走上使用游标并以这种方式循环的道路,但我想让它尽可能简单,而且游标似乎是最好避免的开销。还是谢谢。

以上是关于是否可以组合视图和存储过程中的列?的主要内容,如果未能解决你的问题,请参考以下文章

Tsql如何在链接服务器中获取存储过程中使用的列

视图,触发器,存储过程综合

MySQL基础篇(04):存储过程和视图,用法和特性详解

MySQL基础篇(04):存储过程和视图,用法和特性详解

计算具有相同 id 的列的值的存储过程

MySQL 之 视图触发器存储过程函数事物与数据库锁