I am trying to run a stored procedure from a trigger to ensure orders are printed in the order they were inserted into the database. If I run the stored procedure manually it completes in less than 20 seconds. If I run it from the trigger it never completes.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ARITHABORT OFF
GO
CREATE PROCEDURE [dbo].[usr_despatch_m2] #order VARCHAR(10)
AS
SET XACT_ABORT ON
SET NOCOUNT ON
--DROP TABLE #bundle (bundle int)
DECLARE #sql VARCHAR(1000), #result INT, #text varchar(1000)
SET #sql = 'D:\csserver\bin\runvtorddesp_m.bat '+#order
-- set #sql = 'command.com /c sdespatch '+ #order
EXEC #result = master..xp_cmdshell #sql, no_output
IF #result in (0,1010)
BEGIN
EXEC manufg.dbo.op_populate_shipping_data_dev #order
IF #order LIKE '[T,Y,R,S]%'
BEGIN
EXEC [SERVER2].sp_manufg.[dbo].[pod_print_can_inv] #order
END
SET #sql = 'command.com /c PRINT /D:\\SERVER1\MEM-SHIPPING-INVOICE \\SERVER1\e$\PODPDF\'+rtrim(#order)+'.PDF'
EXEC #result = master..xp_cmdshell #sql, NO_OUTPUT
END
BEGIN
SET #text = (SELECT
CASE #result
WHEN 0 THEN 'Order despatched successfully'
WHEN 1010 THEN 'Order despatched successfully'
WHEN 1 THEN 'Order error not found in order header'
WHEN 2 THEN 'Order at incorrect status'
WHEN 3 THEN 'Order despatch held'
WHEN 4 THEN 'Order is not web order'
WHEN 5 THEN 'Order not fully processed by DataLinx'
WHEN 6 THEN 'Order is in error'
WHEN 7 THEN 'Order is in error'
WHEN 8 THEN 'Order header is locked'
WHEN 9 THEN 'Stock item is locked'
WHEN 13 THEN 'Order number not found'
WHEN 33 THEN 'Unable to lock stock allocations (stallocm)'
WHEN 34 THEN 'Unable to lock stock batch file (stquem)'
WHEN 40 THEN 'Unable to update EIPOLASTDT system key'
WHEN 50 THEN 'Unable to unlock EDI order'
ELSE 'Order in error'
END )
SET #text = rtrim(#order) +' ' + #text
SET #sql = 'echo ' + #text + ' > E:\DespatchErrors\Manufg\'+rtrim(#order)+'.txt'
EXEC #result = master..xp_cmdshell #sql, no_output
SET #sql = 'command.com /c PRINT /D:\\SERVER1\MEM-SHIPPING-INVOICE \\SERVER1\e$\DespatchErrors\MANUFG\'+rtrim(#order)+'.txt'
EXEC #result = master..xp_cmdshell #sql, NO_OUTPUT
END
The trigger is simply
ALTER TRIGGER [dbo].[usr_order_despatch_m_c] on [dbo].[pod_mfg_inv]
instead of insert
as
SET XACT_ABORT ON
SET NOCOUNT ON
DECLARE #order varchar(10)
SET #order = (select order_no from inserted)
EXEC usr_despatch_m2 'T568138'
The stored procedure that appears to be bogging things down is
GO
SET QUOTED_IDENTIFIER ON
SET XACT_ABORT ON
SET ARITHABORT ON
GO
ALTER PROCEDURE [dbo].[pod_print_can_inv]
#myOrder CHAR(10)
AS
DECLARE
#mySql VARCHAR(1000),
#result INT
BEGIN
--Select the right report since Trade invoices are different to Retail dispatch notes
IF #myOrder LIKE 'R%'
BEGIN
SET #mySql = 'dtsrun /S SERVER2 /E /N PODCanRetailInvPDF /A "myFilter":"8"="(lfdinv = ""' + RTRIM(#myOrder) + '"")"'
END
-- Canadian Battle For Vedros Invoices
IF #myOrder LIKE 'Y%'
BEGIN
SET #mySql = 'dtsrun /S SERVER2 /E /N PODVedrosInvoiceWithTerms /A "myFilter":"8"="(lfdinv = ""' + RTRIM(#myOrder) + '"")"'
END
-- Canadian Trade Invoices
ELSE
BEGIN
SET #mySql = 'dtsrun /S SERVER2 /E /N PODCanTradeInvWithTerms /A "myFilter":"8"="(lfdinv = ""' + RTRIM(#myOrder) + '"")"'
END
EXEC #result = master..xp_cmdshell #mySql, NO_OUTPUT
IF #result = 0 BEGIN
WAITFOR DELAY '00:00:01'
SET #mySql = 'DEL \\SERVER1\E$\PODPDF\TST\' + RTRIM(#myOrder) + '.pdf'
EXEC #result = master..xp_cmdshell #mySql, NO_OUTPUT
WAITFOR DELAY '00:00:01'
SET #mySql = 'REN \\SERVER1\E$\PODPDF\TST\PDFCreatorDocument.pdf ' + RTRIM(#myOrder) + '.pdf'
EXEC #result = master..xp_cmdshell #mySql, NO_OUTPUT
WAITFOR DELAY '00:00:01'
-- Stop MSAccess on the server
SET #mySql = 'PSKILL msaccess.exe'
EXEC master..xp_cmdshell #mySql, NO_OUTPUT
WAITFOR DELAY '00:00:10'
/*
IF #myOrder LIKE 'Y%'
BEGIN
SET #mySql = 'COMMAND AcroRd32.exe /t "\\SERVER1\E$\PODPDF\TST\' + RTRIM(#myOrder) + '.pdf" "\\SERVER1\BACKOFFICE" "Canon iR-ADV C5235/5240 PCL5c" "IP_172.16.235.100"'
EXEC master..xp_cmdshell #mySql, NO_OUTPUT
END*/
SET #mySQL = 'MOVE \\SERVER1\E$\PODPDF\TST\'+RTRIM(#myOrder)+'.pdf \\SERVER1\E$\PODPDF'
EXEC #result = master..xp_cmdshell #mySQL, NO_OUTPUT
WAITFOR DELAY '00:00:05'
END
END
The DTS package is opening an access database on the remote server and generating a form. This works lightening quick if I just run the stored procedure but if I use the trigger it never completes.
HELP!!!
David Brown answered this for me.
You can't do this in a trigger because the data isn't committed yet. In any supported version of SQL Server you would need to write those commands into another table with an IDENTITY key, and have a background task read that table in order and execute the commands. Whether that also works in SQL 2000 I can't say :)
Related
Is there something wrong with my code? I get an exception:
Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
I'm trying to update a dynamic table with dynamic columns depending on the searched facility of user
ALTER PROCEDURE SP_UPDATE_FACILITY
#featid int,
#facilityid int,
#updatetbl as TABLE_UPDATE_FACILITYDETAILS READONLY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #tblname AS NVARCHAR(255);
SET #tblname = (SELECT dbo.FNC_Search_GetSearchTable(#facilityid));
DECLARE #key varchar(255);
DECLARE #value varchar(255);
DECLARE #MyCursor CURSOR
SET #MyCursor = CURSOR FOR
SELECT col_key, col_value FROM #updatetbl
OPEN #MyCursor
FETCH NEXT FROM #MyCursor INTO #key , #value
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#key != '' and #value != '')
BEGIN
BEGIN TRY
SET #value = (CAST ( #value AS varchar(255)));
END TRY
BEGIN CATCH
SET #value = (CAST ( #value AS float));
END CATCH;
DECLARE #sSQL NVARCHAR(500);
SET #sSQL = 'UPDATE ' + QUOTENAME(#tblname) + ' SET ' + QUOTENAME(#key) + ' = #value WHERE FEATID = #featid'
EXEC sp_executesql #sSQL , N'#value VARCHAR(255), #featid INT', #key, #featid
FETCH NEXT FROM #MyCursor INTO #key, #value
END
END;
CLOSE #MyCursor ;
DEALLOCATE #MyCursor;
END
GO
Is there a way to loop through rows of table then get the values per rows of table
SQL Server does not have any "execution timeout", while the clients can set it.
If you use C#, the default Execution timeout is 30 seconds that is too small.
Of course you should investigate what your query is waiting for using sys.dm_os_waiting_tasks, but if 30 seconds are not enough to your code to complete, just change this Execution timeout value to smth else (0 = infinite)
i have construct a dynamic sql and it is bit big. so when i am trying to execute it then i am getting error like
Msg 156, Level 15, State 1, Procedure sp_InstallListenerNotification_1, Line 31
Incorrect syntax near the keyword 'END'.
Msg 102, Level 15, State 1, Procedure sp_InstallListenerNotification_1, Line 118
Incorrect syntax near 'END'.
i am not being able to find out where is the problem and where to fix. so help me to compile it if possible. here is my dynamic sql script.
DECLARE #msg VARCHAR(MAX)
DECLARE #crlf CHAR(1)
SET #crlf = CHAR(10)
SET #msg = 'Current user must have following permissions: '
SET #msg = #msg + '[CREATE PROCEDURE, CREATE SERVICE, CREATE QUEUE, SUBSCRIBE QUERY NOTIFICATIONS, CONTROL, REFERENCES] '
SET #msg = #msg + 'that are required to start query notifications. '
SET #msg = #msg + 'Grant described permissions with following script: ' + #crlf
SET #msg = #msg + 'GRANT CREATE PROCEDURE TO [<username>];' + #crlf
SET #msg = #msg + 'GRANT CREATE SERVICE TO [<username>];' + #crlf
SET #msg = #msg + 'GRANT CREATE QUEUE TO [<username>];' + #crlf
SET #msg = #msg + 'GRANT REFERENCES ON CONTRACT::[DEFAULT] TO [<username>];' + #crlf
SET #msg = #msg + 'GRANT SUBSCRIBE QUERY NOTIFICATIONS TO [<username>];' + #crlf
SET #msg = #msg + 'GRANT CONTROL ON SCHEMA::[<schemaname>] TO [<username>];'
PRINT #msg
IF OBJECT_ID ('[bba-reman].sp_InstallListenerNotification_1', 'P') IS NULL
BEGIN
EXEC ('
CREATE PROCEDURE [bba-reman].sp_InstallListenerNotification_1
AS
BEGIN
-- Service Broker configuration statement.
-- Setup Service Broker
IF EXISTS (SELECT * FROM sys.databases
WHERE name = ''bbareman'' AND (is_broker_enabled = 0 OR is_trustworthy_on = 0))
BEGIN
IF (NOT EXISTS(SELECT * FROM sys.fn_my_permissions(NULL, ''SERVER'')
WHERE permission_name = ''CONTROL SERVER''))
BEGIN
DECLARE #msg VARCHAR(MAX)
SET #msg = ''Current user doesn''''t have CONTROL SERVER permission to enable service broker. ''
SET #msg = #msg + ''Grant sufficient permissions to current user or ''
SET #msg = #msg + ''execute ALTER DATABASE [<dbname>] SET ENABLE_BROKER with admin rights.''
RAISERROR (#msg, 16, 1)
END
ELSE
BEGIN
--ALTER DATABASE [bbareman] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
--ALTER DATABASE [bbareman] SET ENABLE_BROKER;
--ALTER DATABASE [bbareman] SET MULTI_USER WITH ROLLBACK IMMEDIATE
-- FOR SQL Express
--ALTER AUTHORIZATION ON DATABASE::[bbareman] TO [sa]
--ALTER DATABASE [bbareman] SET TRUSTWORTHY ON;
END
END
-- Create a queue which will hold the tracked information
IF NOT EXISTS (SELECT * FROM sys.service_queues WHERE name = ''ListenerQueue_1'')
CREATE QUEUE [bba-reman].[ListenerQueue_1]
-- Create a service on which tracked information will be sent
IF NOT EXISTS(SELECT * FROM sys.services WHERE name = ''ListenerService_1'')
CREATE SERVICE [ListenerService_1] ON QUEUE [bba-reman].[ListenerQueue_1] ([DEFAULT])
-- Notification Trigger check statement.
IF OBJECT_ID (''[bba-reman].tr_Listener_1'', ''TR'') IS NOT NULL
RETURN;
-- Notification Trigger configuration statement.
DECLARE #triggerStatement NVARCHAR(MAX)
DECLARE #select NVARCHAR(MAX)
DECLARE #sqlInserted NVARCHAR(MAX)
DECLARE #sqlDeleted NVARCHAR(MAX)
SET #triggerStatement = N''
CREATE TRIGGER [tr_Listener_1]
ON [bba-reman].[ContentChangeLog]
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON;
--Trigger ContentChangeLog is rising...
IF EXISTS (SELECT * FROM sys.services WHERE name = ''''ListenerService_1'''')
BEGIN
DECLARE #message NVARCHAR(MAX)
SET #message = N''''<root/>''''
IF ( EXISTS(SELECT 1))
BEGIN
DECLARE #retvalOUT NVARCHAR(MAX)
%inserted_select_statement%
IF (#retvalOUT IS NOT NULL)
BEGIN SET #message = N''''<root>'''' + #retvalOUT END
%deleted_select_statement%
IF (#retvalOUT IS NOT NULL)
BEGIN
IF (#message = N''''<root/>'''') BEGIN SET #message = N''''<root>'''' + #retvalOUT END
ELSE BEGIN SET #message = #message + #retvalOUT END
END
IF (#message != N''''<root/>'''') BEGIN SET #message = #message + N''''</root>'''' END
END
--Beginning of dialog...
DECLARE #ConvHandle UNIQUEIDENTIFIER
--Determine the Initiator Service, Target Service and the Contract
BEGIN DIALOG #ConvHandle
FROM SERVICE [ListenerService_1] TO SERVICE ''''ListenerService_1'''' ON CONTRACT [DEFAULT] WITH ENCRYPTION=OFF, LIFETIME = 60;
--Send the Message
SEND ON CONVERSATION #ConvHandle MESSAGE TYPE [DEFAULT] (#message);
--End conversation
END CONVERSATION #ConvHandle;
END
''
SET #select = STUFF((SELECT '','' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = ''ContentChangeLog'' AND TABLE_CATALOG = ''bbareman''
FOR XML PATH ('''')
), 1, 1, '''')
SET #sqlInserted =
N''SET #retvalOUT = (SELECT '' + #select + N''
FROM INSERTED
FOR XML PATH(''''row''''), ROOT (''''inserted''''))''
SET #sqlDeleted =
N''SET #retvalOUT = (SELECT '' + #select + N''
FROM DELETED
FOR XML PATH(''''row''''), ROOT (''''deleted''''))''
SET #triggerStatement = REPLACE(#triggerStatement
, ''%inserted_select_statement%'', #sqlInserted)
SET #triggerStatement = REPLACE(#triggerStatement
, ''%deleted_select_statement%'', #sqlDeleted)
EXEC sp_executeSql #triggerStatement
END
')
END
You can't have an empty BEGIN ...END block. You need to add at least one executable statement, for example a PRINT
BEGIN
--ALTER DATABASE [bbareman] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
--ALTER DATABASE [bbareman] SET ENABLE_BROKER;
--ALTER DATABASE [bbareman] SET MULTI_USER WITH ROLLBACK IMMEDIATE
-- FOR SQL Express
--ALTER AUTHORIZATION ON DATABASE::[bbareman] TO [sa]
--ALTER DATABASE [bbareman] SET TRUSTWORTHY ON;
PRINT 'Dummy Output'
END
It also looks like you are missing an END. You need three END statements after the dummy PRINT.
I want to pass table name as parameter and I want to that parameter in where clause
CREATE PROC [dbo].[bb_GetPrepositionInfo]
#userid INT,#propId INT,#tabName varchar(50)
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRAN
SELECT *
FROM #tblname
WHERE ([acq_id] = #propId AND [user_id] = #userid)
COMMIT
GO
Not tested but You need to use Dynamic SQL.
CREATE PROC [dbo].[bb_GetPrepositionInfo]
#userid INT,#propId INT,#tabName varchar(50)
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRAN
DECLARE #SQL varchar(250)
SELECT #SQL = 'SELECT * FROM ' + QuoteName(#tabName) + ' where acq_id=' + Quotename(#propId) + ' AND user_id=' + Quotename(#userid)
EXEC (#SQL)
COMMIT
GO
CREATE PROC [dbo].[bb_GetPrepositionInfo]
DECLARE #userid INT
DECLARE #propId INT
DECLARE #tabName varchar(50)
DECLARE #sqlCommand varchar(200)
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRAN
SET #sqlCommand = 'SELECT * from ' + #tabName +'WHERE [acq_id]='+ #propId +'AND [user_id] = '+ #userid
EXEC (#sqlCommand)
COMMIT
GO
I am using T-SQL
I have a few excel files located here: C:\MyFiles\
I want to remove all the apostrophes in the file names in that directory.
Now to remove apostrophes one would use code like this.
update MyTable
set FileName= replace(FileName, '''', '')
If I had all the file name in the DB it would be easy to do with the code above. But I need to update the file names that are located on disk.
How would I go about doing this with T-SQL?
It must be T-SQL because I need to add it to my existing code in my Stored Procedure.
SET NOCOUNT ON;
CREATE TABLE #FileList
(
FileID INT IDENTITY(1, 1)
,Line VARCHAR(512)
)
CREATE TABLE #temp
(
isFileThere BIT
,isDirectory BIT
,parentDirExists BIT
)
DECLARE #Command VARCHAR(1024)
, #RowCount INT
, #counter INT
, #FileName VARCHAR(1024)
, #FileExists BIT
SET #Command = 'dir C:\MyFiles\ /A-D /B'
PRINT #Command
INSERT #FileList
EXEC master.dbo.xp_cmdshell #Command
DELETE FROM #FileList
WHERE Line IS NULL
SELECT #RowCount = COUNT(*)
FROM [#FileList]
SET #counter = 1
WHILE ( #counter <= #RowCount )
BEGIN
SELECT #FileName = [Line]
FROM [#FileList]
WHERE [FileID] = #counter
SET #Command = 'C:\MyFiles\' + #FileName + ''
PRINT #Command
INSERT [#temp]
EXEC master.dbo.xp_fileExist #Command
SELECT #FileExists = [isFileThere]
FROM [#temp]
IF #FileExists = 1
AND CHARINDEX('''', #FileName) > 0
SET #Command = 'REN "C:\MyFiles\' + #FileName + '" "'
+ REPLACE(#FileName, '''', '') + '"'
ELSE
SET #Command = ''
SET #counter = #counter + 1
PRINT #Command
IF LEN(#Command) > 0
EXEC master.dbo.xp_cmdshell #Command
END
DROP TABLE #FileList
DROP TABLE [#temp]
First you need to enable xp_cmdshell
EXEC sp_configure 'show advanced options', 1
GO
RECONFIGURE
GO
EXEC sp_configure 'xp_cmdshell', 1
GO
RECONFIGURE
GO
Then you can use sp_cmdshell to retrieve the files names from the directory
Declare #Directory TABLE (Files Varchar(MAX))
Declare #File TABLE (Name varchar(MAX))
INSERT INTO #Directory
EXEC XP_CMDSHELL 'DIR "D:"'
Insert into #File
Select reverse(LEFT(reverse(Files),charindex(' ' ,reverse(Files)))) from #Directory
Select * from #FILE
Now you get the file names in the table variable #FILE and use function like replace or your own custom function to replace apostrophes with the exact filename
Try this it will work for you
1) Create a sp as mentioned below
CREATE PROCEDURE dbo.ListPathsXML
#FileSpec VARCHAR(2000),
#order VARCHAR (80) = '/O-D',--sort by date time oldest first
#xmlFileList XML OUTPUT
AS
DECLARE #myfiles TABLE (MyID INT IDENTITY(1,1) PRIMARY KEY, FullPath VARCHAR(2000))
DECLARE #CommandLine VARCHAR(4000)
IF #order IS NOT NULL -- abort if the order is silly
BEGIN
SELECT #CommandLine =LEFT('dir "' + #FileSpec + '" /A-D /B /S '+#order,4000)
INSERT INTO #MyFiles (FullPath)
EXECUTE xp_cmdshell #CommandLine
DELETE FROM #MyFiles WHERE fullpath IS NULL
OR fullpath = 'File Not Found'
END
SET #xmlFileList = (SELECT fullpath FROM #MyFiles
FOR
XML PATH('thefile'),
ROOT('thefiles'),
TYPE)
2) and then give a name of directory where you want to replace the name of files
DECLARE #LotsOfText NVARCHAR(MAX),
#ii INT,
#iiMax INT,
#File VARCHAR(2000),
#Command NVARCHAR(4000)
DECLARE #files TABLE (MyID INT IDENTITY(1,1) PRIMARY KEY, [Path] VARCHAR(2000))
DECLARE #FileList XML
EXECUTE ListPathsXML 'D:\QAconfig\',
DEFAULT , #XMLFileList = #FileList OUTPUT
INSERT INTO #files(path)
SELECT x.thefile.value('fullpath[1]', 'varchar(2000)') AS [path]
FROM #FileList.nodes('//thefiles/thefile') AS x ( thefile )
--don't look at the current errorlog!
SELECT #ii=1, #iiMax=MAX(MyID) FROM #Files
WHILE #ii<=#iiMax
BEGIN
SELECT #File= [path] FROM #files WHERE MyID=#ii
print #File
SELECT #command='EXEC master..xp_cmdshell' + '''MOVE '+ Replace(#FILE,'''','''''') + ' ' +REPLACE(#FILE,'''','') +''''
print #command
EXECUTE sp_ExecuteSQL #command--, N'#lotsOfText nvarchar(max) output ',#lotsoftext output
SELECT #ii=#ii+1
END
Say I want to run the following:
update users set age = 10
on databases:
db1, db2, db3
All on the same server, I want to loop through and perform the same action.
Currently I am doing this manually using management studio via the dropdown.
Hoping there is a better way.
You could probably do it with dynamic SQL. Something like so:
create table #dbs (db_name sysname not null)
insert into #dbs values ('db1'),('db2'),('db3')
declare curs cursor for
select db_name from #dbs
declare #db sysname, #sql nvarchar(max)
open curs
while(1=1)
begin
fetch next from curs into #db
if (##fetch_status <> 0)
break
set #sql = 'update ' + quotename(#db) + '.dbo.users set age = 10'
exec(#sql)
end
close curs
deallocate curs
drop table #dbs
Not sure about doing it 'dynamically', i.e. a FOR-EACH style loop on all the databases in a server, but this should work:
USE db1
update users set age = 10
GO
USE db2
update users set age = 10
GO
USE db3
update users set age = 10
Designate a server as a central management server and then add the other servers to the server group. Then you can run the update on all databases within the group. http://msdn.microsoft.com/en-us/library/bb934126.aspx
use [WWAUTHxxx__] -- a db containing active databases.
set nocount on
declare #Catalog as nvarchar(32)
declare #LibraryName as varchar(255)
declare #dbtable as varchar(50)
declare #retval as nvarchar(50)
declare #sSQL as nvarchar(max)
declare #parmdef as nvarchar(500)
declare #retvalout as nvarchar(50)
Declare Library_Cursor Cursor for
select top(1000) xCatalog, xLibraryName
from Active_DBs
order by xcatalog
Open Library_Cursor;
Fetch Next from Library_Cursor into #Catalog, #LibraryName
while ##Fetch_status = 0
begin
set #dbTable = #Catalog + '.dbo.las_circperiods'
set #ParmDef = N'#retvalOUT int OUTPUT';
set #sSQL = N'Select #retvalout = count(*) from ' + #dbtable
+ ' where xlastcircdate is null'
exec sp_executesql #ssql,#parmdef,#retvalout=#retval output
if #retval > 0 -- check/print Sql and then activate.
-- I like checking to see the potentially affected databases.
begin
print #Catalog + ',' + #LibraryName + ',' + #retval
set #ssql = N'update ' + #dbTable
+ ' set xlastcircdate = '''' '
+ ' where xlastcircdate is null'
-- print #ssql -- View what you might will do
exec sp_executesql #ssql -- Do it.
end
Fetch Next from Library_Cursor into #Catalog, #LibraryName
end;
close Library_cursor
Deallocate Library_cursor