Procedure in PL/SQL with create and cursor - database

Can we have a Procedure with
First create a table suppose
create table INCOME_GROUP(income_compare_groups varchar(100)) ;
Then insert data into this table.
insert into INCOME_GROUP values (10-20);
Then Use this table into a cursor.
CURSOR c1 IS(select *from INCOME_GROUP);
For Example I am doing this.
BEGIN
create table INCOME_GROUP(income_compare_groups varchar(100)) ;
DECLARE
CURSOR c1 IS(select * income_Group);
BEGIN
FOR acct IN c1 LOOP -- process each row one at a time
INSERT INTO temp_test
VALUES (acct.income_compare_groups);
END LOOP;
COMMIT;
END;
END;
But I am getting some Error.
ORA-06550: line 2, column 4:
PLS-00103: Encountered the symbol "CREATE" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
After reading the comments I tried this -
BEGIN
EXECUTE IMMEDIATE 'create table INCOME_GROUP
(
income_compare_groups varchar(100)
)';
DECLARE
CURSOR c1 IS
(select * from
INCOME_GROUP
);
BEGIN
FOR acct IN c1 LOOP -- process each row one at a time
INSERT INTO temp_test
VALUES (acct.income_compare_groups, null);
END LOOP;
COMMIT;
END;
END;
But seems it is not creating table.!!!!

You can do it like this:
create or replace procedure cpy_inc_comp_grps
as
cur_1 sys_refcursor;
compare_group varchar2(100);
begin
execute immediate 'create table income_group(income_compare_groups varchar2(100))';
open cur_1 for 'select income_compare_groups from income_group';
LOOP
FETCH cur_1 INTO compare_group;
DBMS_OUTPUT.PUT_LINE('INSERT INTO temp_test VALUES (rec.income_compare_groups');
EXIT WHEN cur_1%NOTFOUND;
END LOOP;
close cur_1;
execute immediate 'drop table income_group';
end;
And test it with the following code:
begin
cpy_inc_comp_grps;
end;
You have to replace the dbms_output.put_line(...) part with whatever inserts you want to do.

It must be like this:
DECLARE
cur SYS_REFCURSOR;
v_income_compare_groups VARCHAR(100);
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE INCOME_GROUP(income_compare_groups VARCHAR(100))';
OPEN cur FOR 'SELECT * income_Group';
LOOP
FETCH cur INTO v_income_compare_groups;
EXIT WHEN cur%NOTFOUND;
INSERT INTO temp_test VALUES (v_income_compare_groups);
END LOOP;
CLOSE cur;
COMMIT;
END;
You have to use dynamic Cursor because when you compile the package then the table INCOME_GROUP does not exist yet and you would get an error at CURSOR c1 IS(select * income_Group);
However, there are several issue:
You will get an error if the table already exist. You have to check this first or write an exception handler.
The procedure is useless because you first create an (empty) table and then you select it - it will never select anything!

Try this.
execute immediate 'create table INCOME_GROUP(income_compare_groups varchar(100))';

Related

SQL Server stored procedure , loop through table and get values?

In the code shown below, I loop through a table (in this case the disciplines table) and want to add records to another table, I'm rather new to this
How can I retrieve values from those tables for the insert command?
CREATE PROCEDURE cdisc
-- check disciplines
AS
BEGIN
DECLARE test CURSOR FAST_FORWARD FOR
SELECT *
FROM tbl_portfolio
WHERE show = 'Ja' ;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE disc CURSOR FAST_FORWARD FOR
SELECT *
FROM disciplines;
WHILE ##fetch_status = 0
BEGIN
INSERT INTO ProjectDisciplines (Proj_id, discipline_ID)
VALUES (disc!ID, test!id)
END
CLOSE disc
DEALLOCATE disc
END
CLOSE test
DEALLOCATE test
END

Stored procedure inserting the same record repeatedly instead of looping through list from SELECT

I am fairly new at writing procedures (beyond the basics)
I am trying to write a stored procedure that inserts into a table (dbo.billing_batch) based on a select statement that loops through the list of results (#DealerID FROM dbo.vehicle_info).
The SELECT DISTINCT... statement on its own works perfectly and returns a list of 54 records.
The result of the SELECT statement is dynamic and will change from week to week, so I cannot count on 54 records each time.
I am trying to use WHILE #DealerID IS NOT NULL to loop through the INSERT routine.
The loop is supposed to update dbo.billing_batch, however it is inserting the same 1st record (BillingBatchRosterID, DealerID) over and over and over to infinity.
I know I must be doing something wrong (I have never written a stored procedure that loops).
Any help would be greatly appreciated!
Here is the stored procedure code:
ALTER PROCEDURE [dbo].[sp_billing_batch_set]
#varBillingBatchRosterID int
AS
SET NOCOUNT ON;
BEGIN
DECLARE #DealerID int
SELECT DISTINCT #DealerID = vi.DealerID
FROM dbo.vehicle_info vi
LEFT JOIN dbo.dealer_info di ON di.DealerID = vi.DealerID
WHERE di.DealerActive = 1
AND (vi.ItemStatusID < 4 OR vi.ItemStatusID = 5 OR vi.ItemStatusID = 8)
END
WHILE #DealerID IS NOT NULL
BEGIN TRY
INSERT INTO dbo.billing_batch (BillingBatchRosterID, DealerID)
VALUES(#varBillingBatchRosterID, -- BillingBatchRosterID - int
#DealerID) -- DealerID - int
END TRY
BEGIN CATCH
SELECT ' There was an error: ' + error_message() AS ErrorDescription
END CATCH
You have the same problems as another recent post here: Iterate over a table with a non-int id value
Why do a loop? Just do it as a single SQL statement
If you must use a loop, you will need to update your #Dealer value at each run (e.g., to the next DealerId) otherwise it will just infinitely loop with the same DealerID value
Don't do a loop.
Here's an example not needing a loop.
ALTER PROCEDURE [dbo].[P_billing_batch_set]
#varBillingBatchRosterID int
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
INSERT INTO dbo.billing_batch (DealerID, BillingBatchRosterID)
SELECT DISTINCT vi.DealerID, #varBillingBatchRosterID
FROM dbo.vehicle_info vi
INNER JOIN dbo.dealer_info di ON di.DealerID = vi.DealerID
WHERE di.DealerActive = 1
AND (vi.ItemStatusID < 4
OR vi.ItemStatusID = 5
OR vi.ItemStatusID = 8
);
END TRY
BEGIN CATCH
SELECT ' There was an error: ' + error_message() AS ErrorDescription;
END CATCH;
END;
Note I
Changed the LEFT JOIN to an INNER JOIN as your WHERE clause needs the record to exist in the dealer_info table
Moved the SET NOCOUNT ON; to be within the BEGIN-END section
Moved the END to the end
Renamed your stored procedure as per the excellent comment from #marc_s (on the question itself)

Can a T-SQL stored procedure select a value from a sub-select

I wanna make a stored procedure which shall copy an item from a table and the subitems from the dependent tables.
In my example i have a table called "Instance" which has the key "InstanceID" and has multiple subitems in the table "CustomField".
On its part "CustomField" has multiple subitems in the table "CustomFieldData" joined by the key field "CustomFieldID".
So i made a stored procedure to copy the entries from CustomField and CustomFieldData, which runs after the stored procedure which copies the instance and Returns the new InstanceID.
The first part - the copying of CustomField works.
But then comes the part with the copy of CustomFieldData.
Here i want to use in the INSERT a value (=customfieldidentity) from the following sub-select.
Is this possible?
The actual name customfieldidentity doesn't work.
Thanks in advance!
USE OneWhoIsWho;
IF OBJECT_ID('dbo.sp_CopyCustomFieldsToInstance') IS NULL -- Check if SP Exists
EXEC ('CREATE PROCEDURE dbo.sp_CopyCustomFieldsToInstance AS SET NOCOUNT ON;') -- Create dummy/empty SP
GO
ALTER PROCEDURE dbo.sp_CopyCustomFieldsToInstance
#instanceId int,
#newInstanceId int
AS
BEGIN
BEGIN TRANSACTION T1
BEGIN TRY
-- Copy custom fields
INSERT INTO [dbo].[CustomField]
([InstanceID]
,[iFieldType]
,[iFieldPosition]
,[iVisibility]
,[txtGUIDescription])
SELECT
#newInstanceId
,[iFieldType]
,[iFieldPosition]
,[iVisibility]
,[txtGUIDescription]
FROM [dbo].[CustomField]
WHERE [dbo].[CustomField].[InstanceID]=#instanceId
-- Copy custom field data
INSERT INTO [dbo].[CustomFieldData]
([CustomFieldID]
,[txtCustomFieldData])
SELECT
customfieldidentity
,[txtCustomFieldData]
FROM dbo.CustomFieldData
WHERE EXISTS
(SELECT [CustomFieldID] AS customfieldidentity
,[InstanceID]
,[iFieldType]
,[iFieldPosition]
,[iVisibility]
,[txtGUIDescription]
FROM [dbo].[CustomField]
WHERE dbo.CustomField.InstanceID = #newInstanceId)
COMMIT TRANSACTION T1
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION T1
END CATCH
END;
GO
Any data inside a sub-query which is being used for EXISTS operator like EXISTS (Sub-Query) is not visible for the outer query.
The exists operator only check for the existence of a row/record and returns True or False it does not actually return any data. You will need to do a JOIN instead of an EXISTS in the following query .
-- Copy custom field data
INSERT INTO [dbo].[CustomFieldData]
([CustomFieldID]
,[txtCustomFieldData])
SELECT
customfieldidentity
,[txtCustomFieldData]
FROM dbo.CustomFieldData
WHERE EXISTS --<-- Replace this with a join
(SELECT [CustomFieldID] AS customfieldidentity
,[InstanceID]
,[iFieldType]
,[iFieldPosition]
,[iVisibility]
,[txtGUIDescription]
FROM [dbo].[CustomField]
WHERE dbo.CustomField.InstanceID = #newInstanceId)
I have resolved the problem.
In my case i have to loop with a cursor and call other stored procedures, which add a single entry to the actual keys within the loop.
Per example:
I wanna copy an instance from the table "Instance".
An instance has multiple custom fields in the table "CustomField".
At first i copy the instance by the given ID of the selected instance:
ALTER PROCEDURE [dbo].[sp_CopyInstance]
#instanceId int,
#txtInstanceDescription nvarchar(100),
#iHidden int,
#newInstanceId int OUTPUT
AS
BEGIN
BEGIN TRANSACTION TranCopyInstance
BEGIN TRY
-- Inserts a new instance with the given data
INSERT INTO [dbo].[Instance]
([txtInstanceDescription]
,[iHidden])
VALUES
(#txtInstanceDescription
,#iHidden)
-- Gets the InstanceID of the new instance
SELECT #newInstanceId = SCOPE_IDENTITY()
SELECT #newInstanceId AS InstanceID
-- Tries to copy the customfields which belong to the original instance
EXECUTE [dbo].[sp_CopyCustomFields] #instanceId, #newInstanceId
COMMIT TRANSACTION TranCopyInstance
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION TranCopyInstance
END CATCH
END;
As You can see, i call after the copying of the instance the stored procedure sp_CopyCustomFields to copy the entries from the table "CustomField" which belong to the original instance.
ALTER PROCEDURE [dbo].[sp_CopyCustomFields]
#instanceId int,
#newInstanceId int
AS
BEGIN
BEGIN TRANSACTION TranCopyCustomFields
BEGIN TRY
DECLARE customFieldCursor CURSOR
FOR
SELECT [dbo].[CustomField].[CustomFieldID]
FROM [dbo].[CustomField]
WHERE [dbo].[CustomField].[InstanceID] = #instanceId
DECLARE #customFieldId int
-- Loops through all custom fields with the old InstanceID and makes a copy
OPEN customFieldCursor
FETCH NEXT FROM customFieldCursor INTO #customFieldId
WHILE ##FETCH_STATUS = 0
BEGIN
EXECUTE [dbo].[sp_CopyCustomField] #instanceId, #newInstanceId, #customFieldId
FETCH NEXT FROM customFieldCursor INTO #customFieldId
END
CLOSE customFieldCursor
DEALLOCATE customFieldCursor
COMMIT TRANSACTION TranCopyCustomFields
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION TranCopyCustomFields
END CATCH
END;
Here i look for all entries in the table with the original InstanceID and then i copy each entry with the stored procedure sp_CopyCustomField.
ALTER PROCEDURE [dbo].[sp_CopyCustomField]
#instanceId int,
#newInstanceId int,
#customFieldId int
AS
BEGIN
BEGIN TRANSACTION TranCopyCustomField
BEGIN TRY
-- Placeholder for the new CustomFieldID from the new row after the copy action
DECLARE #newCustomFieldId int
-- Makes a copy of the customfield with the old InstanceID and the CustomFieldID
INSERT INTO [dbo].[CustomField]
([InstanceID]
,[iFieldType]
,[iFieldPosition]
,[iVisibility]
,[txtGUIDescription])
SELECT
#newInstanceId
,[iFieldType]
,[iFieldPosition]
,[iVisibility]
,[txtGUIDescription]
FROM [dbo].[CustomField]
WHERE [dbo].[CustomField].[InstanceID] = #instanceId
AND [dbo].[CustomField].[CustomFieldID] = #customFieldId
-- Finds out the new CustomFieldID
SELECT #newCustomFieldId = SCOPE_IDENTITY()
SELECT #newCustomFieldId AS CustomFieldID
-- Loops through all custom lists with the old CustomFieldID and makes a copy
DECLARE customListCursor CURSOR
FOR
SELECT [dbo].[CustomList].[CustomListID]
FROM [dbo].[CustomList]
WHERE [dbo].[CustomList].[CustomFieldID] = #customFieldId
DECLARE #customListId int
OPEN customListCursor
FETCH NEXT FROM customListCursor INTO #customListId
WHILE ##FETCH_STATUS = 0
BEGIN
EXECUTE [dbo].[sp_CopyCustomList] #customFieldId, #newCustomFieldId, #customListId
FETCH NEXT FROM customListCursor INTO #customListId
END
CLOSE customListCursor
DEALLOCATE customListCursor
COMMIT TRANSACTION TranCopyCustomField
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION TranCopyCustomField
END CATCH
END;

Select a column from a row and update the column to another value

I am trying to write a stored procedure that reads a column in a particular row of a table, then updates that column with a new value. The orig. is returned.
I want it to lock the row from others till I am done. What is the process?
I have something like
CREATE PROCEDURE [dbo].[aptc_Prt_NextDocumentNumberGet]
(#_iFormatConfigID INT, #_oNextDocumentNumber FLOAT OUTPUT)
AS
BEGIN
DECLARE #FrameworkConfig XML
SET #_oNextDocumentNumber = - 1
DECLARE #NewNextDocumentID FLOAT
SELECT
#_oNextDocumentNumber = FrameworkConfig.value('(/Parameters/Parameter[#Name="NextDocNo.NextDocumentNumber"])[1]', 'float')
FROM
[ttcPrtFormatConfig] WITH (ROWLOCK)
WHERE
FormatConfigID = #_iFormatConfigID
-- Select the Next Doc num out of the xml field
-- increment appropriate control and set output
IF #_iFormatConfigID IS NOT NULL
BEGIN
-- set what will be the "next" doc number after we add this current txn
IF (ABS(#_oNextDocumentNumber - 99999999999999999) < 0.0001)
BEGIN
SELECT #NewNextDocumentID = 1
END
ELSE
BEGIN
SELECT #NewNextDocumentID = #_oNextDocumentNumber + 1
END
UPDATE [ttcPrtFormatConfig]
WITH (ROWLOCK)
SET FrameworkConfig.modify('
replace value of
(/Parameters/Parameter[#Name="NextDocNo.NextDocumentNumber"]/text())[1]
with sql:variable("#NewNextDocumentID")')
WHERE FormatConfigID = #_iFormatConfigID
END
END
This should get you close to what you want.
DECLARE #MyValue INT
--You need a transaction so that the scope of your lock is well defined
BEGIN TRANSACTION
BEGIN TRY
--Get the value you are interested in, This select will lock the row so other people will not even be able to read it until you are finished!!!!!
SELECT #MyValue = MyValue
FROM MyTable WITH (UPDLOCK HOLDLOCK)
WHERE MyValue = SomeValue
--Do your checks and updates. You can take as long as you like as you are the only person who can do a read or update of this data.
IF
BEGIN
UPDATE MyTable
END
--Make sure you commit or rollback! this will release the lock
END TRY
BEGIN CATCH
--Oh no bad stuff! give up and put it back to how it was
PRINT ERROR_MESSAGE() + N' Your message here'
--Check there is a transaction that we can rollback
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK;
END
--You may want to return some error state and not throw!
THROW;
--RETURN -1 --(for example)
END CATCH;
--yay it all worked and your lock will be released
COMMIT
--Do what you like with the old value
RETURN #MyValue

SQL trigger on update or delete

I have to have one single trigger that fires on either the UPDATE OR DELETE operations. I have the trigger working fine for when one certain column is updated. However, I need different logic for when a DELETE operation was fired. How would I have both logic inside of one trigger? Here is what I have so far:
ALTER TRIGGER [dbo].[Audit_Emp_Trigger]
ON [dbo].[EMPLOYEE]
AFTER UPDATE, DELETE
AS
BEGIN
--Only execute the trigger if the Dno field was updated or deleted
IF UPDATE(Dno)
BEGIN
--If the Audit_Emp_Record table does not exist already, we need to create it
IF OBJECT_ID('dbo.Audit_Emp_Record') IS NULL
BEGIN
--Table does not exist in database, so create table
CREATE TABLE Audit_Emp_Record
(
date_of_change smalldatetime,
old_Lname varchar (50),
new_Lname varchar (50),
old_ssn int,
new_ssn int,
old_dno int,
new_dno int
);
--Once table is created, insert the values of the update operation into the table
INSERT INTO Audit_Emp_Record(date_of_change, old_Lname, new_Lname, old_ssn, new_ssn, old_dno, new_dno) SELECT GETDATE(), D.Lname, I.Lname, D.Ssn, I.Ssn, D.Dno, I.Dno FROM inserted I JOIN deleted D ON I.Ssn = D.Ssn
END
ELSE
BEGIN
--The table already exists, so simply insert the new values of the update operation into the table
INSERT INTO Audit_Emp_Record(date_of_change, old_Lname, new_Lname, old_ssn, new_ssn, old_dno, new_dno) SELECT GETDATE(), D.Lname, I.Lname, D.Ssn, I.Ssn, D.Dno, I.Dno FROM inserted I JOIN deleted D ON I.Ssn = D.Ssn
END
END
END
You can test for the type of operation by seeing which of the magic-/pseudo-tables -- INSERTED and DELETED have data in them. I prefer to use something like the following:
DECLARE #Operation CHAR(1);
IF (EXISTS(SELECT * FROM inserted))
BEGIN
IF (EXISTS(SELECT * FROM deleted))
BEGIN
-- rows in both has to be an UPDATE
SET #Operation = 'U';
END;
ELSE
BEGIN
-- no rows in "deleted" has to be an INSERT
SET #Operation = 'I';
END;
END;
ELSE
BEGIN
-- no rows in "inserted" has to be a DELETE
SET #Operation = 'D';
END;
You can then use the #Operation variable in an IF statement to do one or the other of those operations.
Something like:
IF (#Operation = 'U')
BEGIN
--Only execute the trigger if the Dno field was updated or deleted
IF UPDATE(Dno)
BEGIN
{your current code here}
END;
END;
ELSE
BEGIN
{what to do if the operation is a DELETE goes here}
END;
Technically you don't need the ELSE condition that sets #Operation = 'I';, but if you are going to copy/paste this code into various triggers or keep around as a template then no harm in it handling all three conditions.
Also, just as a side-note, you don't need the ELSE condition of the IF OBJECT_ID('dbo.Audit_Emp_Record') IS NULL statement, nor the INSERT INTO Audit_Emp_Record that is just after the CREATE TABLE but before the END. Just do the CREATE TABLE if it doesn't exist and then do the INSERT outside of that test. Meaning:
IF UPDATE(Dno)
BEGIN
--If the Audit_Emp_Record table does not exist already, we need to create it
IF OBJECT_ID('dbo.Audit_Emp_Record') IS NULL
BEGIN
--Table does not exist in database, so create table
CREATE TABLE Audit_Emp_Record
...
END
INSERT INTO Audit_Emp_Record(...)
END

Resources