Lỗi: “Không thể lồng nhau câu lệnh INSERT EXEC.” và “Không thể sử dụng câu lệnh ROLLBACK trong câu lệnh INSERT-EXEC.” Làm thế nào để giải quyết điều này?


98

Tôi có ba thủ tục được lưu trữ Sp1, Sp2Sp3.

Cái đầu tiên ( Sp1) sẽ thực thi cái thứ hai ( Sp2) và lưu dữ liệu trả về vào @tempTB1và cái thứ hai sẽ thực thi cái thứ ba ( Sp3) và lưu dữ liệu vào @tempTB2.

Nếu tôi thực thi Sp2nó sẽ hoạt động và nó sẽ trả lại cho tôi tất cả dữ liệu của tôi từ Sp3, nhưng vấn đề là ở chỗ Sp1, khi tôi thực thi nó sẽ hiển thị lỗi này:

Câu lệnh INSERT EXEC không được lồng vào nhau

Tôi đã cố gắng thay đổi vị trí execute Sp2và nó hiển thị cho tôi một lỗi khác:

Không thể sử dụng câu lệnh ROLLBACK trong câu lệnh INSERT-EXEC.

Câu trả lời:


99

Đây là một vấn đề phổ biến khi cố gắng làm 'bong bóng' dữ liệu từ một chuỗi các thủ tục được lưu trữ. Một hạn chế trong SQL Server là bạn chỉ có thể có một INSERT-EXEC hoạt động tại một thời điểm. Tôi khuyên bạn nên xem Cách chia sẻ dữ liệu giữa các thủ tục được lưu trữ , đây là một bài viết rất kỹ lưỡng về các mẫu để giải quyết loại vấn đề này.

Ví dụ, một công việc xung quanh có thể là biến Sp3 thành một hàm có giá trị Bảng.


1
liên kết bị hỏng HOẶC trang web không phản hồi.
SouravA

6
bạn có biết lý do kỹ thuật không cho phép nó là gì không? Tôi không thể tìm thấy bất kỳ thông tin nào về điều này.
jtate

Thật không may, điều này thường không phải là một lựa chọn. Nhiều loại thông tin quan trọng chỉ có sẵn một cách đáng tin cậy từ các thủ tục được lưu trữ của hệ thống (vì trong một số trường hợp nhất định, chế độ xem quản lý tương ứng chứa dữ liệu không đáng tin cậy / lỗi thời; ví dụ là thông tin được trả về sp_help_jobactivity).
GSerg

21

Đây là cách "đơn giản" duy nhất để thực hiện việc này trong SQL Server mà không cần một số hàm được tạo phức tạp khổng lồ hoặc lệnh gọi chuỗi sql được thực thi, cả hai đều là các giải pháp khủng khiếp:

  1. tạo một bảng tạm thời
  2. openrowset đặt dữ liệu thủ tục đã lưu trữ của bạn vào đó

THÍ DỤ:

INSERT INTO #YOUR_TEMP_TABLE
SELECT * FROM OPENROWSET ('SQLOLEDB','Server=(local);TRUSTED_CONNECTION=YES;','set fmtonly off EXEC [ServerName].dbo.[StoredProcedureName] 1,2,3')

Lưu ý : Bạn PHẢI sử dụng 'set fmtonly off' VÀ bạn KHÔNG THỂ thêm sql động vào lệnh này bên trong lệnh gọi openrowset, cho chuỗi chứa các tham số thủ tục được lưu trữ của bạn hoặc cho tên bảng. Đó là lý do tại sao bạn phải sử dụng bảng tạm thời thay vì các biến bảng, điều này sẽ tốt hơn, vì nó thực hiện bảng tạm thời trong hầu hết các trường hợp.


Không bắt buộc phải sử dụng SET FMTONLY OFF. Bạn chỉ có thể thêm IF (1 = 0) để trả về một bảng trống có cùng kiểu dữ liệu mà thủ tục thường trả về.
Guillermo Gutiérrez

1
Các biến Bảng tạm thời và Bảng lưu trữ dữ liệu của chúng theo cách khác nhau. Các biến Bảng được cho là được sử dụng cho các tập kết quả nhỏ vì trình tối ưu hóa truy vấn không duy trì thống kê trên các biến bảng. Vì vậy, đối với các tập dữ liệu lớn, hầu như luôn luôn tốt hơn khi sử dụng bảng Temp. Đây là một bài blog hay về nó mssqltips.com/sqlservertip/2825/…
gh9

@ gh9 vâng, nhưng dù sao thì đây cũng là một ý tưởng kinh khủng đối với các tập kết quả lớn. Việc thống kê và sử dụng một bảng thực tế trong cơ sở dữ liệu tạm thời có thể gây ra chi phí đáng kể. Tôi có một thủ tục trả về một tập bản ghi với 1 hàng giá trị hiện tại (truy vấn một số bảng) và một thủ tục lưu trữ nó trong một biến bảng và so sánh nó với các giá trị trong một bảng khác có cùng định dạng. Việc thay đổi từ bảng tạm thời sang biến bảng đã tăng thời gian trung bình từ 8ms lên 2ms, điều này rất quan trọng khi nó được gọi nhiều lần trong một giây trong ngày và 100.000 lần trong một quy trình hàng đêm.
Jason Goemaat

Tại sao bạn muốn thống kê được duy trì trên một biến bảng? Toàn bộ vấn đề là tạo một bảng tạm thời trong RAM sẽ bị hủy sau khi kết thúc truy vấn. Theo định nghĩa, bất kỳ số liệu thống kê nào được tạo trên một bảng như vậy sẽ không bao giờ được sử dụng. Nói chung, thực tế là dữ liệu trong một biến bảng vẫn còn trong RAM bất cứ nơi nào có thể làm cho chúng nhanh hơn Bảng tạm trong bất kỳ trường hợp nào mà dữ liệu của bạn nhỏ hơn dung lượng RAM có sẵn cho SQL Server (trong những ngày này, vùng nhớ 100GB + cho SQL của chúng tôi Máy chủ, hầu như luôn luôn)
Geoff Griswald

Tuy nhiên, điều này không hoạt động đối với các thủ tục được lưu trữ mở rộng. Lỗi là Không thể xác định siêu dữ liệu vì câu lệnh 'EXECUTE <procedurename> @retval OUTPUT' trong thủ tục ... 'gọi một thủ tục được lưu trữ mở rộng .
GSerg

11

OK, được khuyến khích bởi jimhark đây là một ví dụ về cách tiếp cận bảng băm đơn cũ: -

CREATE PROCEDURE SP3 as

BEGIN

    SELECT 1, 'Data1'
    UNION ALL
    SELECT 2, 'Data2'

END
go


CREATE PROCEDURE SP2 as

BEGIN

    if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
        INSERT INTO #tmp1
        EXEC SP3
    else
        EXEC SP3

END
go

CREATE PROCEDURE SP1 as

BEGIN

    EXEC SP2

END
GO


/*
--I want some data back from SP3

-- Just run the SP1

EXEC SP1
*/


/*
--I want some data back from SP3 into a table to do something useful
--Try run this - get an error - can't nest Execs

if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
    DROP TABLE #tmp1

CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))

INSERT INTO #tmp1
EXEC SP1


*/

/*
--I want some data back from SP3 into a table to do something useful
--However, if we run this single hash temp table it is in scope anyway so
--no need for the exec insert

if exists (select  * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#tmp1'))
    DROP TABLE #tmp1

CREATE TABLE #tmp1 (ID INT, Data VARCHAR(20))

EXEC SP1

SELECT * FROM #tmp1

*/

Tôi cũng đã sử dụng công việc này. Cảm ơn bạn cho ý tưởng!
SQL_Guy

Làm việc tuyệt vời xung quanh. Điều này đã giúp tôi tìm hiểu thêm về phạm vi bảng tạm thời. EG, tôi không nhận ra rằng bạn có thể sử dụng bảng tạm thời trong một chuỗi dynsql nếu nó được khai báo bên ngoài nó. Khái niệm tương tự ở đây. Cảm ơn rất nhiều.
jbd

9

Công việc của tôi cho vấn đề này luôn là sử dụng nguyên tắc rằng các bảng tạm thời băm đơn có phạm vi đối với bất kỳ procs nào được gọi. Vì vậy, tôi có một công tắc tùy chọn trong các thông số proc (mặc định được đặt thành tắt). Nếu điều này được bật, proc được gọi sẽ chèn kết quả vào bảng tạm thời được tạo trong proc đang gọi. Tôi nghĩ rằng trong quá khứ, tôi đã tiến thêm một bước nữa và đặt một số mã vào proc được gọi để kiểm tra xem bảng băm đơn có tồn tại trong phạm vi hay không, nếu có thì chèn mã, nếu không thì trả về tập kết quả. Có vẻ hoạt động tốt - cách tốt nhất để chuyển các tập dữ liệu lớn giữa các procs.


1
Tôi thích câu trả lời này và tôi cá là bạn sẽ nhận được nhiều phiếu bầu hơn nếu bạn cung cấp và ví dụ.
jimhark

Tôi đã làm điều này trong nhiều năm. Nó vẫn cần thiết trong SQL Azure?
Nick Allan

6

Thủ thuật này phù hợp với tôi.

Bạn không gặp sự cố này trên máy chủ từ xa, vì trên máy chủ từ xa, lệnh chèn cuối cùng đợi kết quả của lệnh trước đó thực thi. Nó không phải là trường hợp trên cùng một máy chủ.

Lợi nhuận cho tình huống đó để có cách giải quyết khác.

Nếu bạn có quyền tạo Máy chủ được Liên kết, hãy làm điều đó. Tạo máy chủ giống như máy chủ được liên kết.

  • trong SSMS, đăng nhập vào máy chủ của bạn
  • chuyển đến "Đối tượng máy chủ
  • Nhấp chuột phải vào "Máy chủ được liên kết", sau đó nhấp vào "Máy chủ được liên kết mới"
  • trên hộp thoại, hãy cung cấp bất kỳ tên nào của máy chủ được liên kết của bạn: ví dụ: ThisSERVER
  • loại máy chủ là "Nguồn dữ liệu khác"
  • Nhà cung cấp: Nhà cung cấp Microsoft OLE DB cho máy chủ SQL
  • Nguồn dữ liệu: IP của bạn, nó cũng có thể chỉ là một dấu chấm (.), Vì đó là localhost
  • Chuyển đến tab "Bảo mật" và chọn tab thứ 3 "Được thực hiện bằng ngữ cảnh bảo mật hiện tại của đăng nhập"
  • Bạn có thể chỉnh sửa các tùy chọn máy chủ (tab thứ 3) nếu bạn muốn
  • Nhấn OK, máy chủ liên kết của bạn đã được tạo

bây giờ lệnh Sql của bạn trong SP1 là

insert into @myTempTable
exec THISSERVER.MY_DATABASE_NAME.MY_SCHEMA.SP2

Tin tôi đi, nó hoạt động ngay cả khi bạn có chèn động trong SP2


4

Tôi tìm thấy một công việc xung quanh là chuyển đổi một trong những sản phẩm thành một hàm có giá trị bảng. Tôi nhận ra rằng điều đó không phải lúc nào cũng có thể thực hiện được và đưa ra những hạn chế của riêng nó. Tuy nhiên, tôi luôn có thể tìm thấy ít nhất một trong những thủ tục phù hợp cho việc này. Tôi thích giải pháp này, vì nó không giới thiệu bất kỳ "hack" nào cho giải pháp.


nhưng một bất lợi là một vấn đề với xử lý ngoại lệ nếu hàm phức tạp, phải không?
Muflix

2

Tôi gặp sự cố này khi cố gắng nhập kết quả của Proc đã lưu trữ vào bảng tạm thời và Proc đã lưu trữ đó được chèn vào bảng tạm thời như một phần hoạt động của chính nó. Vấn đề là SQL Server không cho phép cùng một quá trình ghi vào hai bảng tạm thời khác nhau cùng một lúc.

Câu trả lời OPENROWSET được chấp nhận hoạt động tốt, nhưng tôi cần tránh sử dụng bất kỳ SQL động hoặc nhà cung cấp OLE bên ngoài nào trong quy trình của mình, vì vậy tôi đã đi một con đường khác.

Một cách giải quyết dễ dàng mà tôi tìm thấy là thay đổi bảng tạm thời trong thủ tục được lưu trữ của tôi thành một biến bảng. Nó hoạt động giống hệt như đã làm với bảng tạm, nhưng không còn xung đột với chèn bảng tạm khác của tôi nữa.

Chỉ để bắt đầu nhận xét, tôi biết rằng một vài người trong số các bạn sắp viết, cảnh báo tôi rằng Biến Bảng là kẻ giết hiệu suất ... Tất cả những gì tôi có thể nói với bạn là vào năm 2020, nó sẽ trả cổ tức đừng sợ Bảng Biến. Nếu đây là năm 2008 và Cơ sở dữ liệu của tôi được lưu trữ trên máy chủ có RAM 16GB và chạy trên ổ cứng 5400RPM, tôi có thể đồng ý với bạn. Nhưng đó là năm 2020 và tôi có một dãy SSD làm bộ nhớ chính và hàng trăm hợp đồng RAM. Tôi có thể tải toàn bộ cơ sở dữ liệu của công ty mình vào một biến bảng và vẫn còn nhiều RAM để dự phòng.

Bảng biến đã trở lại trên menu!


1

Tôi đã có cùng một vấn đề và lo ngại về mã trùng lặp trong hai hoặc nhiều mầm. Tôi đã kết thúc việc thêm một thuộc tính bổ sung cho "mode". Điều này cho phép mã chung tồn tại bên trong một mầm và dòng chảy được định hướng theo chế độ và tập hợp kết quả của mầm.


1

những gì về chỉ lưu trữ đầu ra vào bảng tĩnh? Giống

-- SubProcedure: subProcedureName
---------------------------------
-- Save the value
DELETE lastValue_subProcedureName
INSERT INTO lastValue_subProcedureName (Value)
SELECT @Value
-- Return the value
SELECT @Value

-- Procedure
--------------------------------------------
-- get last value of subProcedureName
SELECT Value FROM lastValue_subProcedureName

nó không lý tưởng, nhưng nó rất đơn giản và bạn không cần phải viết lại mọi thứ.

CẬP NHẬT : giải pháp trước đây không hoạt động tốt với các truy vấn song song (truy cập không đồng bộ và đa người dùng) do đó bây giờ tôi đang sử dụng bảng tạm thời

-- A local temporary table created in a stored procedure is dropped automatically when the stored procedure is finished. 
-- The table can be referenced by any nested stored procedures executed by the stored procedure that created the table. 
-- The table cannot be referenced by the process that called the stored procedure that created the table.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NULL
CREATE TABLE #lastValue_spGetData (Value INT)

-- trigger stored procedure with special silent parameter
EXEC dbo.spGetData 1 --silent mode parameter

spGetDatanội dung thủ tục được lưu trữ lồng nhau

-- Save the output if temporary table exists.
IF OBJECT_ID('tempdb..#lastValue_spGetData') IS NOT NULL
BEGIN
    DELETE #lastValue_spGetData
    INSERT INTO #lastValue_spGetData(Value)
    SELECT Col1 FROM dbo.Table1
END

 -- stored procedure return
 IF @silentMode = 0
 SELECT Col1 FROM dbo.Table1

Nói chung, bạn không thể tạo SProc ad-hoc như bạn có thể làm với Tables. Bạn sẽ cần mở rộng ví dụ của mình với nhiều tài liệu tham khảo hơn, vì phương pháp này không thực sự được biết đến hoặc chấp nhận. Ngoài ra, nó giống với Biểu thức Lambda hơn là thực thi SProc, ANSI-SQL không cho phép các phương pháp tiếp cận Biểu thức Lambda.
GoldBishop

Nó hoạt động nhưng tôi thấy rằng nó không hoạt động tốt với các truy vấn song song (không đồng bộ và nhiều người dùng truy cập). Do đó bây giờ tôi đang sử dụng phương pháp tiếp cận bảng tạm thời. Tôi đã cập nhật câu trả lời của mình.
Muflix

1
Logic của bảng Temp là tốt, đó là tham chiếu SProc mà tôi quan tâm. Sproc's vốn dĩ không thể được truy vấn từ trực tiếp. Các hàm được định giá trong bảng có thể được truy vấn trực tiếp từ. Phải giống như bạn đã ám chỉ trong logic cập nhật của mình, cách tiếp cận tốt nhất là Bảng tạm thời, phiên, phiên bản hoặc toàn cục và hoạt động từ thời điểm đó.
GoldBishop

0

Khai báo một biến con trỏ đầu ra cho sp bên trong:

@c CURSOR VARYING OUTPUT

Sau đó khai báo một con trỏ c đến vùng chọn mà bạn muốn quay lại. Sau đó, mở con trỏ. Sau đó đặt tham chiếu:

DECLARE c CURSOR LOCAL FAST_FORWARD READ_ONLY FOR 
SELECT ...
OPEN c
SET @c = c 

KHÔNG đóng hoặc phân bổ lại.

Bây giờ gọi sp bên trong từ sp bên ngoài cung cấp tham số con trỏ như:

exec sp_abc a,b,c,, @cOUT OUTPUT

Khi sp bên trong thực thi, @cOUT đã sẵn sàng để tìm nạp. Vòng lặp, sau đó đóng và phân bổ.


0

Nếu bạn có thể sử dụng các công nghệ liên quan khác như C #, tôi khuyên bạn nên sử dụng lệnh SQL tích hợp sẵn với tham số Giao dịch.

var sqlCommand = new SqlCommand(commandText, null, transaction);

Tôi đã tạo một Ứng dụng Console đơn giản thể hiện khả năng này. Bạn có thể tìm thấy khả năng này tại đây: https://github.com/hecked12/SQL-Transaction-Using-C-Sharp

Tóm lại, C # cho phép bạn khắc phục hạn chế này khi bạn có thể kiểm tra đầu ra của từng thủ tục được lưu trữ và sử dụng đầu ra đó theo cách bạn muốn, ví dụ như bạn có thể đưa nó vào một thủ tục được lưu trữ khác. Nếu đầu ra là ổn, bạn có thể thực hiện giao dịch, nếu không, bạn có thể hoàn nguyên các thay đổi bằng cách sử dụng khôi phục.


-1

Trên SQL Server 2008 R2, tôi gặp sự cố không khớp trong các cột trong bảng gây ra lỗi Rollback. Nó đã biến mất khi tôi sửa biến bảng sqlcmd của mình được điền bởi câu lệnh insert-execute để khớp với biến được trả về bởi proc được lưu trữ. Nó bị thiếu org_code. Trong tệp cmd windows, nó tải kết quả của thủ tục được lưu trữ và chọn nó.

set SQLTXT= declare @resets as table (org_id nvarchar(9), org_code char(4), ^
tin(char9), old_strt_dt char(10), strt_dt char(10)); ^
insert @resets exec rsp_reset; ^
select * from @resets;

sqlcmd -U user -P pass -d database -S server -Q "%SQLTXT%" -o "OrgReport.txt"

OP đã hỏi về một lỗi xảy ra khi sử dụng câu lệnh insert-execute trong các thủ tục được lưu trữ lồng nhau. Sự cố của bạn sẽ trả về một lỗi khác, chẳng hạn như "Danh sách chọn cho câu lệnh INSERT chứa ít mục hơn danh sách chèn. Số lượng giá trị CHỌN phải khớp với số cột CHÈN."
Losbear

Đây là một cảnh báo nhiều hơn rằng bạn có thể nhận nhầm thông báo này.
user3448451 Ngày
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.