如何制作伪动态存储过程/CTE

How can I make a pseudo-dynamic Stored procedure / CTE

我正在努力做一个好的“网友”,尽可能简单地解释我的问题。
这是我用来说明的一些示例代码。动态 SQL returns 是我需要看到的结果,但在我可用的工具中,我有文本大小和功能限制。

实际上,我想将动态 SQL 包装到 CTE、参数化视图或存储过程中,这样我就可以说 SELECT * FROM GetStudentCourseCompletionByProgram('2') 或 EXEC GetStudentCourseCompletionByProgram ('2') 以获得我需要的动态结果,但我不知道如何到达那里。如果需要,我可以删除它的 'dynamic' 方面并将其硬编码到 return 10 个课程列(因此总共 12 个),并且假设没有程序将与超过 10 个课程相关联。

感谢大家的帮助!

USE [tempdb]
GO

IF EXISTS (SELECT 1 FROM SYS.tables WHERE name = 'STUDENTS') DROP TABLE STUDENTS
CREATE TABLE STUDENTS ( ID INT PRIMARY KEY, NAME VARCHAR(50))

IF EXISTS (SELECT 1 FROM SYS.tables WHERE name = 'PROGRAMS') DROP TABLE PROGRAMS
CREATE TABLE PROGRAMS ( ID INT PRIMARY KEY, NAME VARCHAR(50))

IF EXISTS (SELECT 1 FROM SYS.tables WHERE name = 'COURSES') DROP TABLE COURSES
CREATE TABLE COURSES (ID INT PRIMARY KEY, NAME VARCHAR(50)) 

IF EXISTS (SELECT 1 FROM SYS.tables WHERE name = 'STUPROGRAMS') DROP TABLE STUPROGRAMS
CREATE TABLE STUPROGRAMS (ID INT PRIMARY KEY, STUDENT_ID INT, PROGRAM_ID INT) 

IF EXISTS (SELECT 1 FROM SYS.tables WHERE name = 'PROGCOURSES') DROP TABLE PROGCOURSES
CREATE TABLE PROGCOURSES (ID INT PRIMARY KEY, PROGRAM_ID INT, COURSE_ID INT)

IF EXISTS (SELECT 1 FROM SYS.tables WHERE name = 'ENROLLMENTS') DROP TABLE ENROLLMENTS
CREATE TABLE ENROLLMENTS (ID INT PRIMARY KEY, STUDENT_ID INT, COURSE_ID INT)

INSERT INTO STUDENTS VALUES (1,'Caimbul'),(2,'Quassnoi'),(3,'Zohar'),(4,'Mike K'),(5,'Jeff');
INSERT INTO PROGRAMS VALUES (1,'Program1'),(2,'Program2');
INSERT INTO COURSES VALUES (1,'Course1'),(2,'Course2'),(3,'Course3'),(4,'Course4'),(5,'Course5');
INSERT INTO STUPROGRAMS VALUES (1,1,1),(2,2,1),(3,3,1),(4,4,1),(5,1,2),(6,2,2),(7,5,2);
INSERT INTO PROGCOURSES VALUES (1,1,1),(2,1,2),(3,1,3),(4,2,1),(5,2,3),(6,2,4),(7,2,5);
INSERT INTO ENROLLMENTS VALUES (1,1,1),(2,1,2),(3,1,3),(4,1,4),(5,1,5),(7,2,2),(8,2,3),(11,3,1),(12,3,2),(14,3,4),(16,4,1),(18,4,3),(19,4,4),(23,5,3),(25,5,5);

-- Dynamic PIVOT Gives me the results I want. 
--But I need it in a contained CTE Select query with a Program_ID parameter, or a Stored Procedure (with P.ID Param)
DECLARE @T AS TABLE(y INT NOT NULL PRIMARY KEY);

DECLARE
@cols AS NVARCHAR(MAX),
@y    AS INT,
@sql  AS NVARCHAR(MAX);

-- Construct the column list for the IN clause
SET @cols = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()]
FROM (SELECT C.ID AS Y FROM PROGCOURSES PC JOIN COURSES C ON PC.COURSE_ID=C.ID WHERE PC.PROGRAM_ID = '2' ) AS Y
ORDER BY y   FOR XML PATH('')),1, 1, N'');

-- Construct the full T-SQL statement
-- and execute dynamically
SET @sql = N'SELECT * FROM 
(SELECT S.NAME, S.ID AS StudentID, PC.COURSE_ID, E.ID
FROM STUPROGRAMS SP JOIN STUDENTS S ON SP.STUDENT_ID = S.ID 
JOIN PROGRAMS P ON SP.PROGRAM_ID = P.ID
JOIN PROGCOURSES PC ON SP.PROGRAM_ID = PC.PROGRAM_ID
OUTER APPLY (
  SELECT TOP 1 INE.*, C.NAME
  FROM COURSES C 
  LEFT JOIN ENROLLMENTS INE ON C.ID = INE.COURSE_ID AND INE.STUDENT_ID = S.ID
  WHERE PC.COURSE_ID = C.ID
ORDER BY INE.ID DESC) AS E
WHERE P.ID = ''2'') SOURCE
PIVOT (
  COUNT(SOURCE.ID)
  FOR COURSE_ID IN (' + @cols + N') ) AS PIV
ORDER BY NAME;';

EXEC sp_executesql @sql;
GO

删除了之前对我的问题的编辑/解释。

好吧,这似乎行得通:

SET QUOTED_IDENTIFIER, ANSI_NULLS ON 
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'dbo.GetStudentCourseCompletionByProgram') AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
DROP PROCEDURE dbo.GetStudentCourseCompletionByProgram
GO  

CREATE PROC dbo.GetStudentCourseCompletionByProgram (@program INT)
AS

DECLARE
    @cols NVARCHAR(MAX),
    @y    INT,
    @sql  NVARCHAR(MAX);

 -- Construct the column list for the IN clause
 SET @cols = STUFF(
            (SELECT N',' + QUOTENAME(y) AS [text()]
       FROM (SELECT C.ID AS Y
               FROM PROGCOURSES PC
               JOIN COURSES C ON PC.COURSE_ID=C.ID
              WHERE PC.PROGRAM_ID = @program ) AS Y
           ORDER BY y
       FOR XML PATH('')),1, 1, N'');

-- Construct the full T-SQL statement
-- and execute dynamically
SET @sql = N'SELECT * FROM 
(SELECT S.NAME, S.ID AS StudentID, PC.COURSE_ID, E.ID
FROM STUPROGRAMS SP JOIN STUDENTS S ON SP.STUDENT_ID = S.ID 
JOIN PROGRAMS P ON SP.PROGRAM_ID = P.ID
JOIN PROGCOURSES PC ON SP.PROGRAM_ID = PC.PROGRAM_ID
OUTER APPLY (
SELECT TOP 1 INE.*, C.NAME
FROM COURSES C 
LEFT JOIN ENROLLMENTS INE ON C.ID = INE.COURSE_ID
AND INE.STUDENT_ID = S.ID
WHERE PC.COURSE_ID = C.ID
ORDER BY INE.ID DESC) AS E
WHERE P.ID =' + cast(@program as nvarchar) + ') SOURCE
PIVOT (
COUNT(SOURCE.ID)
FOR COURSE_ID IN (' + @cols + N') ) AS PIV
ORDER BY NAME;';

EXEC sp_executesql @sql;

GO 

GRANT EXECUTE ON dbo.GetStudentCourseCompletionByProgram TO standard

然后用 EXEC GetStudentCourseCompletionByProgram 2(或 1 或任何您喜欢的方式调用它)。