Kesalahan: "Pernyataan INSERT EXEC tidak dapat disarangkan." dan "Tidak dapat menggunakan pernyataan ROLLBACK dalam pernyataan INSERT-EXEC". Bagaimana mengatasi ini?

98

Saya memiliki tiga prosedur tersimpan Sp1, Sp2dan Sp3.

Yang pertama ( Sp1) akan mengeksekusi yang kedua ( Sp2) dan menyimpan data yang dikembalikan ke @tempTB1dan yang kedua akan mengeksekusi yang ketiga ( Sp3) dan menyimpan data ke @tempTB2.

Jika saya menjalankannya, Sp2itu akan berfungsi dan itu akan mengembalikan semua data saya dari Sp3, tetapi masalahnya ada di Sp1, ketika saya menjalankannya, itu akan menampilkan kesalahan ini:

Pernyataan INSERT EXEC tidak dapat disarangkan

Saya mencoba untuk mengubah tempat execute Sp2dan itu menampilkan saya kesalahan lain:

Tidak dapat menggunakan pernyataan ROLLBACK dalam pernyataan INSERT-EXEC.

HAJJAJ
sumber

Jawaban:

101

Ini adalah masalah umum saat mencoba 'menggelembung' data dari rangkaian prosedur yang tersimpan. Batasan di SQL Server adalah Anda hanya dapat memiliki satu INSERT-EXEC yang aktif dalam satu waktu. Saya sarankan melihat Bagaimana Membagikan Data Antara Prosedur Tersimpan yang merupakan artikel yang sangat menyeluruh tentang pola untuk mengatasi jenis masalah ini.

Misalnya, solusi untuk mengubah Sp3 menjadi fungsi bernilai Tabel.

eddiegroves
sumber
1
tautan rusak ATAU situs tidak responsif.
SouravA
6
apakah Anda tahu alasan teknis untuk tidak mengizinkannya? Saya tidak dapat menemukan info apa pun tentang ini.
jtate
1
Sayangnya ini seringkali bukan merupakan pilihan. Banyak jenis informasi penting hanya tersedia secara andal dari prosedur tersimpan sistem (karena dalam kasus tertentu tampilan manajemen masing-masing berisi data yang tidak dapat diandalkan / usang; contohnya adalah informasi yang dikembalikan oleh sp_help_jobactivity).
GSerg
21

Ini adalah satu-satunya cara "sederhana" untuk melakukan ini di SQL Server tanpa beberapa fungsi besar yang dibuat berbelit-belit atau panggilan string sql yang dijalankan, keduanya merupakan solusi yang buruk:

  1. buat tabel temp
  2. openrowset data prosedur tersimpan Anda ke dalamnya

CONTOH:

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

Catatan : Anda HARUS menggunakan 'set fmtonly off', DAN Anda TIDAK DAPAT menambahkan sql dinamis ke ini baik di dalam panggilan openrowset, baik untuk string yang berisi parameter prosedur tersimpan Anda atau untuk nama tabel. Itulah mengapa Anda harus menggunakan tabel temp daripada variabel tabel, yang akan lebih baik, karena tabel temporer dalam banyak kasus.

Mitch Stokely
sumber
Ini bukan suatu keharusan untuk menggunakan SET FMTONLY OFF. Anda bisa menambahkan IF (1 = 0) yang mengembalikan tabel kosong dengan tipe data yang sama yang biasanya dikembalikan oleh prosedur.
Guillermo Gutiérrez
1
Tabel Tabel dan variabel Tabel menyimpan datanya secara berbeda. Variabel tabel seharusnya digunakan untuk kumpulan hasil kecil karena pengoptimal kueri tidak memelihara statistik pada variabel tabel. Jadi untuk kumpulan data besar hampir selalu lebih baik menggunakan tabel Temp. Ini adalah artikel blog yang bagus tentang itu mssqltips.com/sqlservertip/2825/…
gh9
@ gh9 ya, tapi ini adalah ide yang buruk untuk kumpulan hasil yang besar. Statistik dan penggunaan tabel aktual dalam database temp dapat menyebabkan overhead yang signifikan. Saya memiliki prosedur yang mengembalikan kumpulan data dengan 1 baris nilai saat ini (menanyakan beberapa tabel) dan prosedur yang menyimpannya dalam variabel tabel dan membandingkannya dengan nilai di tabel lain dengan format yang sama. Mengubah dari tabel temp ke variabel tabel mempercepat waktu rata-rata dari 8ms menjadi 2ms, yang penting ketika dipanggil beberapa kali per detik sepanjang hari dan 100.000 kali dalam proses malam.
Jason Goemaat
Mengapa Anda ingin statistik dipertahankan pada variabel tabel? Intinya adalah membuat tabel sementara di RAM yang akan dihancurkan setelah kueri selesai. Menurut definisi, statistik apa pun yang dibuat pada tabel seperti itu tidak akan pernah digunakan. Secara umum, fakta bahwa data dalam variabel tabel tetap dalam RAM sedapat mungkin membuatnya lebih cepat daripada Tabel Temp dalam skenario apa pun di mana data Anda lebih kecil dari jumlah RAM yang tersedia untuk SQL Server (yang pada saat ini 100GB + kumpulan memori untuk SQL kami Server, hampir selalu)
Geoff Griswald
Ini tidak berfungsi untuk prosedur tersimpan yang diperpanjang. Kesalahannya adalah Metadata tidak dapat ditentukan karena pernyataan 'LAKUKAN <procedurename> @ KELUARAN retval' dalam prosedur ... 'memanggil prosedur yang disimpan diperpanjang .
GSerg
11

Oke, didorong oleh jimhark berikut ini contoh pendekatan tabel hash tunggal lama: -

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

*/
Matt Luckham
sumber
Saya menggunakan solusi ini juga. Terima kasih atas idenya!
SQL_Guy
Pekerjaan yang fantastis. Ini membantu saya mempelajari lebih lanjut tentang pelingkupan tabel temp. Misalnya, saya tidak menyadari bahwa Anda dapat menggunakan tabel temp dalam string dynsql jika dideklarasikan di luarnya. Konsep serupa di sini. Terima kasih banyak.
jbd
9

Pekerjaan saya untuk masalah ini selalu menggunakan prinsip bahwa tabel temporer hash tunggal berada dalam ruang lingkup untuk setiap procs yang disebut. Jadi, saya memiliki opsi sakelar di parameter proc (default disetel ke nonaktif). Jika ini diaktifkan, proc yang dipanggil akan memasukkan hasilnya ke dalam tabel temp yang dibuat di proc pemanggil. Saya pikir di masa lalu saya telah mengambil langkah lebih jauh dan meletakkan beberapa kode dalam proc yang dipanggil untuk memeriksa apakah tabel hash tunggal ada dalam ruang lingkup, jika tidak maka masukkan kode, jika tidak kembalikan set hasil. Tampaknya berfungsi dengan baik - cara terbaik untuk meneruskan kumpulan data besar antarprocs.

Matt Luckham
sumber
1
Saya suka jawaban ini dan saya yakin Anda akan mendapatkan lebih banyak suara jika Anda memberi dan memberi contoh.
jimhark
Saya telah melakukan ini selama bertahun-tahun. Apakah masih diperlukan di SQL Azure?
Nick Allan
6

Trik ini berhasil untuk saya.

Anda tidak mengalami masalah ini di server jarak jauh, karena di server jarak jauh, perintah penyisipan terakhir menunggu hasil dari perintah sebelumnya untuk dieksekusi. Ini tidak terjadi di server yang sama.

Dapatkan untung dari situasi itu sebagai solusi.

Jika Anda memiliki izin yang tepat untuk membuat Server Tertaut, lakukanlah. Buat server yang sama dengan server tertaut.

  • di SSMS, masuk ke server Anda
  • pergi ke "Objek Server
  • Klik kanan pada "Server Tertaut", lalu "Server Tertaut Baru"
  • pada dialog, berikan nama server tertaut Anda: mis .: THISSERVER
  • jenis server adalah "Sumber data lain"
  • Penyedia: Penyedia DB OLE Microsoft untuk SQL server
  • Sumber data: IP Anda, bisa juga hanya titik (.), Karena itu localhost
  • Buka tab "Keamanan" dan pilih yang ketiga "Dibuat menggunakan konteks keamanan login saat ini"
  • Anda dapat mengedit opsi server (tab ke-3) jika Anda mau
  • Tekan OK, server tertaut Anda telah dibuat

sekarang perintah Sql Anda di SP1 adalah

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

Percayalah, ini berfungsi bahkan Anda memiliki sisipan dinamis di SP2

ainasiart
sumber
4

Saya menemukan solusi untuk mengubah salah satu prods menjadi fungsi nilai tabel. Saya menyadari itu tidak selalu mungkin, dan memperkenalkan keterbatasannya sendiri. Namun, saya selalu dapat menemukan setidaknya satu prosedur sebagai kandidat yang baik untuk ini. Saya suka solusi ini, karena tidak ada "peretasan" untuk solusi tersebut.

Roman K
sumber
tetapi kerugiannya adalah masalah dengan penanganan pengecualian jika fungsinya kompleks, bukan?
Muflix
2

Saya mengalami masalah ini saat mencoba mengimpor hasil Proc Tersimpan ke dalam tabel temporer, dan Proc Tersimpan itu dimasukkan ke dalam tabel temporer sebagai bagian dari operasinya sendiri. Masalahnya adalah bahwa SQL Server tidak mengizinkan proses yang sama untuk menulis ke dua tabel temp yang berbeda pada waktu yang sama.

Jawaban OPENROWSET yang diterima berfungsi dengan baik, tetapi saya harus menghindari penggunaan SQL Dinamis atau penyedia OLE eksternal dalam proses saya, jadi saya memilih rute yang berbeda.

Satu solusi mudah yang saya temukan adalah mengubah tabel sementara dalam prosedur tersimpan saya menjadi variabel tabel. Ia bekerja persis sama seperti yang dilakukannya dengan tabel temp, tetapi tidak lagi bertentangan dengan sisipan tabel temp saya yang lain.

Hanya untuk menghadang komentar saya tahu bahwa beberapa dari Anda akan menulis, peringatan me off Tabel Variabel sebagai pembunuh kinerja ... Yang bisa saya katakan kepada Anda adalah bahwa pada tahun 2020 itu membayar dividen tidak perlu takut Tabel Variabel. Jika ini tahun 2008 dan Database saya di-host di server dengan RAM 16GB dan menjalankan HDD 5400RPM, saya mungkin setuju dengan Anda. Tapi ini tahun 2020 dan saya memiliki array SSD sebagai penyimpanan utama saya dan ratusan pertunjukan RAM. Saya dapat memuat seluruh database perusahaan saya ke variabel tabel dan masih memiliki banyak RAM tersisa.

Variabel Tabel kembali ke menu!

Geoff Griswald
sumber
1

Saya memiliki masalah dan kekhawatiran yang sama tentang kode duplikat di dua atau lebih sprocs. Saya akhirnya menambahkan atribut tambahan untuk "mode". Ini memungkinkan kode umum ada di dalam satu sproc dan aliran yang diarahkan mode serta kumpulan hasil dari sproc.

phoenixAZ
sumber
1

bagaimana dengan hanya menyimpan output ke tabel statis? Suka

-- 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

ini tidak ideal, tetapi sangat sederhana dan Anda tidak perlu menulis ulang semuanya.

UPDATE : solusi sebelumnya tidak berfungsi dengan baik dengan query paralel (async dan multiuser accessing) oleh karena itu sekarang saya menggunakan tabel temp

-- 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

spGetDatakonten prosedur tersimpan bersarang

-- 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
Muflix
sumber
Secara umum, Anda tidak dapat membuat ad-hoc SProc seperti yang Anda bisa dengan Tabel. Anda perlu memperluas contoh Anda dengan lebih banyak referensi, karena pendekatan ini tidak terlalu mudah diketahui atau diterima. Juga, ini lebih menyerupai Ekspresi Lambda daripada eksekusi SProc, yang ANSI-SQL tidak mengizinkan pendekatan Ekspresi Lambda.
GoldBishop
Ini berfungsi tetapi saya menemukan bahwa itu tidak berfungsi dengan baik dengan kueri paralel (async dan akses multi pengguna) juga. Oleh karena itu sekarang Iam menggunakan pendekatan tabel temp. Saya memperbarui jawaban saya.
Muflix
1
Logika tabel Temp bagus, itu adalah referensi SProc yang saya perhatikan. Sproc secara inheren tidak dapat ditanyai secara langsung. Table-Valued Functions dapat langsung ditanyakan. Harus seperti yang Anda singgung dalam logika Anda yang diperbarui, pendekatan terbaik adalah Tabel Temp, sesi, contoh, atau global, dan beroperasi dari titik itu.
GoldBishop
0

Deklarasikan variabel kursor output ke bagian dalam sp:

@c CURSOR VARYING OUTPUT

Kemudian nyatakan kursor cursor untuk memilih yang ingin Anda kembalikan. Lalu buka kursornya. Kemudian atur referensinya:

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

JANGAN menutup atau mengalokasikan ulang.

Sekarang panggil sp bagian dalam dari bagian luar yang memasok parameter kursor seperti:

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

Setelah sp bagian dalam dijalankan, Anda @cOUTsiap untuk mengambil. Putar ulang, lalu tutup dan alokasikan.

Stefanos Zilellis
sumber
0

Jika Anda dapat menggunakan teknologi terkait lainnya seperti C #, saya sarankan menggunakan perintah SQL bawaan dengan parameter Transaksi.

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

Saya telah membuat Aplikasi Konsol sederhana yang menunjukkan kemampuan ini yang dapat ditemukan di sini: https://github.com/hecked12/SQL-Transaction-Using-C-Sharp

Singkatnya, C # memungkinkan Anda untuk mengatasi batasan ini di mana Anda dapat memeriksa keluaran dari setiap prosedur tersimpan dan menggunakan keluaran tersebut sesuka Anda, misalnya Anda dapat memasukkannya ke prosedur tersimpan lainnya. Jika hasilnya baik-baik saja, Anda dapat melakukan transaksi, jika tidak, Anda dapat mengembalikan perubahan menggunakan rollback.

spidernet12
sumber
-1

Di SQL Server 2008 R2, saya mengalami ketidakcocokan di kolom tabel yang menyebabkan galat Rollback. Itu hilang ketika saya memperbaiki variabel tabel sqlcmd saya yang diisi oleh pernyataan insert-exec agar sesuai dengan yang dikembalikan oleh proc yang disimpan. Org_code hilang. Dalam file cmd windows, itu memuat hasil dari prosedur yang disimpan dan memilihnya.

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"
pengguna3448451
sumber
OP menanyakan tentang kesalahan yang terjadi saat menggunakan pernyataan insert-exec dalam prosedur tersimpan bersarang. Masalah Anda akan mengembalikan kesalahan yang berbeda, seperti "Daftar pilih untuk pernyataan INSERT berisi lebih sedikit item daripada daftar sisipkan. Jumlah nilai SELECT harus cocok dengan jumlah kolom INSERT."
Losbear
Ini lebih merupakan peringatan bahwa mungkin saja menerima pesan ini secara keliru.
pengguna3448451