基于Crystal Report的学生月度考勤报表制作问题咨询
问题描述
我正在开发学生考勤项目,考勤信息存储在Attend表中,表结构如下:
CREATE TABLE Attend ( NAME VARCHAR(50), DATE DATETIME , PRESENT_STATUS VARCHAR(10) )
已编写生成月度考勤报表的存储过程GET_ATTENDANCEREPORT,代码如下:
CREATE PROCEDURE GET_ATTENDANCEREPORT @STARTDATE DATETIME, @ENDDATE DATETIME AS BEGIN WITH DATERANGE AS ( SELECT DT = DATEADD(DD,0, @STARTDATE) WHERE DATEADD(DD, 1, @STARTDATE) <= @ENDDATE UNION ALL SELECT DATEADD(DD, 1, DT) FROM DATERANGE WHERE DATEADD(DD, 1, DT) <= @ENDDATE ) SELECT * INTO #TMP_DATES FROM DATERANGE DECLARE @COLUMN VARCHAR(MAX) SELECT @COLUMN = ISNULL(@COLUMN + ',', '') + '[' + CAST(CONVERT(DATE, T.DT) AS VARCHAR) + ']' FROM #TMP_DATES T DECLARE @Columns2 VARCHAR(MAX) SET @Columns2 = SUBSTRING((SELECT DISTINCT ', ISNULL(['+ CAST(CONVERT(DATE, DT) as varchar) + '],''N/A'') AS ['+CAST(CONVERT(DATE , DT) as varchar )+']' FROM #TMP_DATES GROUP BY dt FOR XML PATH('')), 2, 8000) DECLARE @QUERY VARCHAR(MAX) SET @QUERY = 'SELECT NAME, ' + @Columns2 + ' FROM (SELECT A.NAME, B.DT AS DATE, A.PRESENT_STATUS FROM Attend A RIGHT OUTER JOIN #TMP_DATES B ON A.DATE = B.DT) X PIVOT (MIN([PRESENT_STATUS]) FOR [DATE] IN (' + @COLUMN + ')) P WHERE ISNULL(NAME,'''')<>'''' ' EXEC (@QUERY) DROP TABLE #TMP_DATES END
现在需要在Crystal Report中制作指定格式的月度考勤报表,但直接调用该存储过程时,由于需要传入可变起止日期,导致报表无法正常生成,求可行实现方案。
可行解决方案
方案1:直接在Crystal Reports中配置存储过程参数
Crystal Reports原生支持为存储过程传入参数,步骤如下:
- 新建报表时选择存储过程作为数据源,选中
GET_ATTENDANCEREPORT。 - 报表会自动识别
@STARTDATE和@ENDDATE两个参数,在字段资源管理器的参数节点中确认参数类型为DateTime。 - 预览报表时,系统会弹出参数输入窗口,填入起止日期即可生成对应报表。
- 注意:如果存储过程返回动态列(不同日期范围的日期列数量不同),Crystal Reports会以首次获取的列结构为准,后续不同范围可能出现列不匹配问题,需结合方案2优化。
方案2:修改存储过程返回固定列结构
调整存储逻辑,让其返回固定数量的列(比如一个月最多31天),不在指定范围内的日期列填充N/A,确保Crystal Reports能识别稳定的数据源结构:
- 生成当月1-31日的虚拟日期集,而非仅传入的起止日期范围。
- 调整PIVOT逻辑,固定输出31列(命名为
[1日]至[31日]或完整日期格式),通过判断日期是否在参数范围内来决定显示考勤状态还是N/A。 - 在Crystal Reports中,可通过参数控制隐藏不需要的日期列。
核心修改示例:
-- 生成当月全量日期集 WITH DATERANGE AS ( SELECT DATEFROMPARTS(YEAR(@STARTDATE), MONTH(@STARTDATE), 1) AS DT UNION ALL SELECT DATEADD(DD, 1, DT) FROM DATERANGE WHERE DAY(DT) < 31 ) -- 后续PIVOT固定生成31列,增加日期范围判断逻辑
方案3:通过中间表传递参数
- 创建参数存储表:
CREATE TABLE ReportParams ( ParamID INT IDENTITY(1,1) PRIMARY KEY, StartDate DATETIME, EndDate DATETIME, CreatedAt DATETIME DEFAULT GETDATE() )
- 修改存储过程,不再接收参数,改为读取
ReportParams中最新的起止日期:
CREATE PROCEDURE GET_ATTENDANCEREPORT AS BEGIN DECLARE @STARTDATE DATETIME, @ENDDATE DATETIME SELECT TOP 1 @STARTDATE = StartDate, @ENDDATE = EndDate FROM ReportParams ORDER BY CreatedAt DESC -- 原存储过程逻辑不变,使用读取到的@STARTDATE和@ENDDATE -- ... 原代码省略 ... END
- 调用Crystal Reports前,先向
ReportParams插入目标起止日期,再让报表调用无参数的存储过程即可。
方案4:使用Crystal Reports交叉表功能(推荐)
放弃存储过程的PIVOT逻辑,直接在报表中处理原始数据:
- 让报表直接读取
Attend表,或编写无参数存储过程返回指定月份的原始考勤数据。 - 在报表中插入交叉表:
- 行字段选择
NAME - 列字段选择
DATE(转换为日期格式) - 汇总字段选择
PRESENT_STATUS,设置汇总方式为最小值(与存储逻辑一致)
- 行字段选择
- 添加报表参数
@STARTDATE和@ENDDATE,设置记录选择公式:{Attend.DATE} >= {?STARTDATE} AND {Attend.DATE} <= {?ENDDATE},交叉表会自动根据参数范围生成对应日期列。
这种方式无需依赖动态SQL,Crystal Reports会自动处理列的动态生成,同时支持参数过滤,适配性更强。
内容的提问来源于stack exchange,提问作者Mohamed Allam
相关产品推荐
相关产品推荐

