Classic ASP中SQL Server INSERT OUTPUT INTO返回结果异常问题
问题原因
你遇到的问题核心在于ADODB.Recordset默认只会读取TSQL执行产生的第一个结果集,而你的代码在插入新用户时,TSQL里的CREATE TABLE #newUserTable语句会生成一个空的结果集(这是SQL Server对DDL语句的默认行为),后续的SELECT @responseMessage其实是第二个结果集。此时你的Recordset指向的是第一个空结果集,自然找不到responseMessage字段。
而当用户已存在时,代码不会执行CREATE TABLE和INSERT逻辑,只有最后的SELECT @responseMessage作为唯一结果集,所以Recordset能正常读取到字段。这也解释了为什么直接在SSMS里执行TSQL能正常返回结果——SSMS会显示所有生成的结果集,你看到的是最后一个有效的结果。
修正方法
有两种可靠的解决方式,推荐第一种(更简洁且避免额外结果集):
方法1:用表变量代替临时表,消除额外结果集
把临时表#newUserTable替换为表变量@newUserTable,因为表变量的DECLARE语句不会产生额外的结果集,这样整个TSQL执行后只会返回最后一个SELECT的结果集。
修改后的TSQL代码如下:
tsql="DECLARE @newUserTable TABLE(UserID int); " & vbCrLf & _ "DECLARE @responseMessage NVARCHAR(50); " & vbCrLf & _ "DECLARE @userID INT; " & vbCrLf & _ "IF EXISTS (SELECT TOP 1 UserID FROM [dbo].[UserLogins] WHERE LoginName='" & username & "') " & vbCrLf & _ "BEGIN " & vbCrLf & _ "SET @responseMessage='User already exists' " & vbCrLf & _ "END " & vbCrLf & _ "ELSE " & vbCrLf & _ "BEGIN " & vbCrLf & _ "DECLARE @salt UNIQUEIDENTIFIER=NEWID(); " & vbCrLf & _ "BEGIN TRY " & vbCrLf & _ "INSERT INTO dbo.UserLogins (LoginName,PasswordHash,Salt) " & vbCrLf & _ "OUTPUT INSERTED.UserID INTO @newUserTable " & vbCrLf & _ "VALUES ('" & username & "',HASHBYTES('SHA2_512', '" & password & "'+CAST(@salt AS NVARCHAR(36))),@salt); " & vbCrLf & _ "SET @responseMessage=" & vbCrLf & _ "CAST((SELECT userID FROM @newUserTable) AS NVARCHAR(50)); " & vbCrLf & _ "END TRY " & vbCrLf & _ "BEGIN CATCH " & vbCrLf & _ "SET @responseMessage=ERROR_MESSAGE() " & vbCrLf & _ "END CATCH " & vbCrLf & _ "END " & vbCrLf & _ "SELECT @responseMessage AS N'responseMessage'; "
这里去掉了最后的DROP TABLE,因为表变量会在批处理结束后自动销毁,不需要手动删除。
方法2:在ASP代码中遍历所有结果集
如果你不想修改TSQL,可以在ASP代码中调用rs.NextRecordset()方法,跳过前面的空结果集,直到找到包含responseMessage字段的结果集:
rs.open tsql, con ' 跳过所有空的结果集,直到找到有responseMessage的或者到最后 Do While Not rs Is Nothing ' 检查当前结果集是否有目标字段 On Error Resume Next ' 临时禁用错误捕获 Dim fieldExists fieldExists = False For Each f In rs.Fields If f.Name = "responseMessage" Then fieldExists = True Exit For End If Next On Error GoTo 0 ' 恢复错误捕获 If fieldExists Then Exit Do Else Set rs = rs.NextRecordset() End If Loop If Not rs Is Nothing And Not rs.EOF Then response.write rs("responseMessage") End If rs.close
不过这种方法不如第一种简洁,而且需要额外的错误处理,所以更推荐方法1。
额外提醒
虽然你提到熟悉Classic ASP且能满足需求,但还是要注意:你的代码存在SQL注入风险——直接把username和password拼接到TSQL中是非常危险的。建议改用参数化查询,通过ADODB.Command对象传递参数,避免注入攻击。比如:
Set cmd = Server.CreateObject("ADODB.Command") cmd.ActiveConnection = con cmd.CommandText = "DECLARE @newUserTable TABLE(UserID int); DECLARE @responseMessage NVARCHAR(50); DECLARE @userID INT; IF EXISTS (SELECT TOP 1 UserID FROM [dbo].[UserLogins] WHERE LoginName=@username) BEGIN SET @responseMessage='User already exists' END ELSE BEGIN DECLARE @salt UNIQUEIDENTIFIER=NEWID(); BEGIN TRY INSERT INTO dbo.UserLogins (LoginName,PasswordHash,Salt) OUTPUT INSERTED.UserID INTO @newUserTable VALUES (@username,HASHBYTES('SHA2_512', @password+CAST(@salt AS NVARCHAR(36))),@salt); SET @responseMessage=CAST((SELECT userID FROM @newUserTable) AS NVARCHAR(50)); END TRY BEGIN CATCH SET @responseMessage=ERROR_MESSAGE() END CATCH END SELECT @responseMessage AS N'responseMessage';" cmd.Parameters.Append cmd.CreateParameter("@username", 200, 1, 50, username) ' adVarChar=200, adParamInput=1 cmd.Parameters.Append cmd.CreateParameter("@password", 200, 1, 255, password) Set rs = cmd.Execute If Not rs.EOF Then response.write rs("responseMessage") End If rs.close
内容的提问来源于stack exchange,提问作者vurtual




