SQL Server : create a trigger on update of "current" table to "history" table - sql-server

I'm working on migrating from an Oracle 9i db to SQL Server and trying to work out and translate the one trigger we have to maintain a history of records. The History table has its own ID column and all other columns are the same as in the Current table.
Here is my trigger as is from the OraDb: (in the OraDb we are using a sequence to increment the "History" PK)
CREATE OR REPLACE TRIGGER "RC_CURRENT_T"
BEFORE INSERT OR UPDATE
ON RC_CURRENT
FOR EACH ROW
DECLARE var_date VARCHAR2(30 BYTE);
BEGIN
IF UPDATING THEN
var_date:=TO_CHAR(CURRENT_DATE, 'MM/DD/YYYY HH24:MI:SS');
INSERT
INTO RC_HISTORY
(
"ID" ,
REC_ID ,
STATUS_DESCR ,
REQBY_FULLNAME ,
REQBY_USERNAME ,
REQBY_EMAIL ,
REQBY_EMPLID ,
PCARD_TYPE ,
BUS_SGMT_DESCR ,
REQ_TYPE ,
REQ_CHNGAUTHBY ,
OP_CNTR_NAME ,
CARDHOLDER_NAME ,
CUSTODIAN_NAME ,
ED_ROLLUP_NUM ,
CARDHOLDER_ADDRESS ,
CARDHOLDER_CITY ,
CARDHOLDER_STATE ,
CARDHOLDER_ZIP ,
CARD_NUMBER ,
GL_CODE ,
GL_LOCATION ,
GL_DEPARTMENT ,
GL_ACCOUNT ,
GL_SVCSCODE ,
MONTH_LIMIT ,
LIMIT_RESTRICTIONS ,
CARD_LIMIT ,
REASON_COMMENTS ,
CREATE_DATE ,
APPROVER1_NAME ,
APPROVER1_USERNAME ,
APPROVER1_EMAIL ,
APPROVER1_EMPLID ,
APPROVER1_DATE ,
APPROVER2_NAME ,
APPROVER2_USERNAME ,
APPROVER2_EMAIL ,
APPROVER2_EMPLID ,
APPROVER2_DATE ,
ADMIN_LVL1 ,
ADMIN_LVL2 ,
ADMIN_LVL3 ,
ADMIN_LVL4 ,
ADMIN_LVL5 ,
ADMIN_LVL6 ,
KEYED_DATE ,
KEYED_BY_NAME ,
KEYER_CHANGE_TYPE ,
KEYER_COMMENTS ,
CARD_STATUS ,
KEYER_USERNAME ,
KEYER_EMPLID ,
USER_FULLNAME,
OPER_STREET,
OPER_CITY,
OPER_STATE,
OPER_ZIP,
USER_PHONE,
USER_EMAIL,
USER_HYCHY_SETUP,
USER_ASSIGNED_USERID,
USER_PROCESS_DATE,
USER_DISTRIBUTION,
USER_REQUESTTYPE,
REC_ENTRY_DATE
)
VALUES
(
RC_HISTORY_SEQ.nextval ,
:OLD.REC_ID ,
:OLD.STATUS_DESCR ,
:OLD.REQBY_FULLNAME ,
:OLD.REQBY_USERNAME ,
:OLD.REQBY_EMAIL ,
:OLD.REQBY_EMPLID ,
:OLD.PCARD_TYPE ,
:OLD.BUS_SGMT_DESCR ,
:OLD.REQ_TYPE ,
:OLD.REQ_CHNGAUTHBY ,
:OLD.OP_CNTR_NAME ,
:OLD.CARDHOLDER_NAME ,
:OLD.CUSTODIAN_NAME ,
:OLD.ED_ROLLUP_NUM ,
:OLD.CARDHOLDER_ADDRESS ,
:OLD.CARDHOLDER_CITY ,
:OLD.CARDHOLDER_STATE ,
:OLD.CARDHOLDER_ZIP ,
:OLD.CARD_NUMBER ,
:OLD.GL_CODE ,
:OLD.GL_LOCATION ,
:OLD.GL_DEPARTMENT ,
:OLD.GL_ACCOUNT ,
:OLD.GL_SVCSCODE ,
:OLD.MONTH_LIMIT ,
:OLD.LIMIT_RESTRICTIONS ,
:OLD.CARD_LIMIT ,
:OLD.REASON_COMMENTS ,
:OLD.CREATE_DATE ,
:OLD.APPROVER1_NAME ,
:OLD.APPROVER1_USERNAME ,
:OLD.APPROVER1_EMAIL ,
:OLD.APPROVER1_EMPLID ,
:OLD.APPROVER1_DATE ,
:OLD.APPROVER2_NAME ,
:OLD.APPROVER2_USERNAME ,
:OLD.APPROVER2_EMAIL ,
:OLD.APPROVER2_EMPLID ,
:OLD.APPROVER2_DATE ,
:OLD.ADMIN_LVL1 ,
:OLD.ADMIN_LVL2 ,
:OLD.ADMIN_LVL3 ,
:OLD.ADMIN_LVL4 ,
:OLD.ADMIN_LVL5 ,
:OLD.ADMIN_LVL6 ,
:OLD.KEYED_DATE ,
:OLD.KEYED_BY_NAME ,
:OLD.KEYER_CHANGE_TYPE ,
:OLD.KEYER_COMMENTS ,
:OLD.CARD_STATUS ,
:OLD.KEYER_USERNAME ,
:OLD.KEYER_EMPLID ,
:OLD.USER_FULLNAME,
:OLD.OPER_STREET,
:OLD.OPER_CITY,
:OLD.OPER_STATE,
:OLD.OPER_ZIP,
:OLD.USER_PHONE,
:OLD.USER_EMAIL,
:OLD.USER_HYCHY_SETUP,
:OLD.USER_ASSIGNED_USERID,
:OLD.USER_PROCESS_DATE,
:OLD.USER_DISTRIBUTION,
:OLD.USER_REQUESTTYPE,
var_date
) ;
END IF;
END;
I have attempted the follow but get the error message
The select list for the INSERT statement contains fewer items than the
insert list. The number of SELECT values must match the number of INSERT
columns.
Code:
CREATE TRIGGER RC_CURRENT_T
ON [dbo].[RC_CURRENT]
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[RC_HISTORY]
([REC_ID]
,[STATUS_DESCR]
,[REQBY_FULLNAME]
,[REQBY_USERNAME]
,[REQBY_EMAIL]
,[REQBY_EMPLID]
,[PCARD_TYPE]
,[BUS_SGMT_DESCR]
,[REQ_TYPE]
,[REQ_CHNGAUTHBY]
,[OP_CNTR_NAME]
,[CARDHOLDER_NAME]
,[CUSTODIAN_NAME]
,[ED_ROLLUP_NUM]
,[CARDHOLDER_ADDRESS]
,[CARDHOLDER_CITY]
,[CARDHOLDER_STATE]
,[CARDHOLDER_ZIP]
,[CARD_NUMBER]
,[GL_CODE]
,[GL_LOCATION]
,[GL_DEPARTMENT]
,[GL_ACCOUNT]
,[GL_SVCSCODE]
,[MONTH_LIMIT]
,[LIMIT_RESTRICTIONS]
,[CARD_LIMIT]
,[REASON_COMMENTS]
,[CREATE_DATE]
,[APPROVER1_NAME]
,[APPROVER1_USERNAME]
,[APPROVER1_EMAIL]
,[APPROVER1_EMPLID]
,[APPROVER1_DATE]
,[APPROVER2_NAME]
,[APPROVER2_USERNAME]
,[APPROVER2_EMAIL]
,[APPROVER2_EMPLID]
,[APPROVER2_DATE]
,[ADMIN_LVL1]
,[ADMIN_LVL2]
,[ADMIN_LVL3]
,[ADMIN_LVL4]
,[ADMIN_LVL5]
,[ADMIN_LVL6]
,[KEYED_DATE]
,[KEYED_BY_NAME]
,[KEYER_CHANGE_TYPE]
,[KEYER_COMMENTS]
,[CARD_STATUS]
,[KEYER_USERNAME]
,[KEYER_EMPLID]
,[USER_FULLNAME]
,[OPER_STREET]
,[OPER_CITY]
,[OPER_STATE]
,[OPER_ZIP]
,[USER_PHONE]
,[USER_EMAIL]
,[USER_HYCHY_SETUP]
,[USER_ASSIGNED_USERID]
,[USER_PROCESS_DATE]
,[USER_DISTRIBUTION]
,[USER_REQUESTTYPE]
,[REC_ENTRY_DATE]
)
SELECT * FROM RC_CURRENT
END
GO
I need to maintain the separate ID in the history table for reporting purposes.
Any assistance is very appreciated.
Thank you.

Looking at the old trigger (and the error message)RC_HISTORY has 1 extra field compared to RC_CURRENT so you have two options.
If [REC_ID] is an identity column with auto increment you can simply remove it from the list of columns to be inserted to
INSERT INTO [dbo].[RC_HISTORY]
([STATUS_DESCR]
,[REQBY_FULLNAME]
,[REQBY_USERNAME]
...
If [REC_ID] is not set to auto increment then you will have to provide a value to insert into the column
SELECT (SELECT MAX([REC_ID])+1 FROM RC_HISTORY), * FROM RC_CURRENT
I would suggest you try the first option and set the column to be an identity column if it isn't already, I also suggest you also use named columns instead of * for the select values.

Related

SQL Server - Loop records insert 100 at a time

I have an INSERT query inside a stored procedure that creates a set of parcels monthly named "MONTHLY_SET".
Sometimes the set gets too big, I need to be able to continue run the same insert query, but rather than insert, for example, 5000 records in a single set named "MONTHLY SET", I need to end up with 5 sets of 1000 each named: "MONTHLY_SET1", "MONTHLY_SET2", "MONTHLY_SET3", "MONTHLY_SET4", "MONTHLY_SET5"
I do not know how this can be achieved, I am not familiar with the use of cursor, and loops in T-SQL, or if those are the only available options to do this.
Would it be possible to ask for some help to understand how can I split a shingle set into smaller sets?
The INSERT query that needs to be inside a loop, currently looks like:
DECLARE #SETSEQ AS INT;
SET #SETSEQ = (SELECT MAX(SET_SEQ_NBR) FROM SETDETAILS);
INSERT INTO SETDETAILS
( SERV_PROV_CODE
, SET_SEQ_NBR
, SET_ID
, REC_DATE
, REC_FUL_NAM
, REC_STATUS
, SOURCE_SEQ_NBR
, L1_PARCEL_NBR
)
SELECT Top 10
'STRING'
, ROW_NUMBER() OVER(ORDER BY ParcelNumber ASC) + #SETSEQ
, 'MONTHLY_SET'
, GETDATE()
, 'USR'
, 'A'
, '155'
, ParcelNumber
FROM
dbo.Parcels
WHERE
Create = 1;
Thank you for your help.

SCD type 2 using SQL Server MERGE, how to capture counts?

New to SQL Server and MERGE.
I am working on a MERGE statement to populate a slowly changing dimension table. My example includes both type 1 and type 2 attributes. I see examples of how to use OUTPUT to capture counts of actions, and I understand how to use OUTPUT to pass values out to an INSERT statement. What I would like to do is take the following code and somehow capture the count of UPDATE and INSERT actions for audit/logging purposes.
Very confused reading articles on OUTPUT and OUTPUT INTO, but from what I can tell, I don't think I can do what I want to do, at least not using OUTPUT.
Is there a way to capture the ACTION counts from the below statement? Is there a better way to accomplish this?
Thank you
BEGIN
MERGE dbo.dimTable tgt
USING dbo.stgTable src
ON tgt.NaturalKey = src.NaturalKey
AND tgt.IsActiveRow = 'Y'
WHEN MATCHED
AND EXISTS
(SELECT src.SCD1Field
EXCEPT
SELECT tgt.SCD1Field
)
THEN
UPDATE SET
tgt.SCD1Field = src.SCD1Field ;
INSERT dbo.dimTable (
tgt.NaturalKey
, tgt.SCD1Field
, tgt.SCD2Field
, tgt.RowStartDate
, tgt.RowEndDate
, tgt.IsActiveRow
)
SELECT
NaturalKey
, SCD1Field
, SCD2Field
, RowStartDate
, RowEndDate
, IsActiveRow
FROM (
MERGE dbo.dimTable tgt
USING dbo.stgTable src
ON tgt.NaturalKey = src.NaturalKey
WHEN NOT MATCHED BY TARGET
THEN
INSERT (
NaturalKey
, SCD1Field
, SCD2Field
, RowStartDate
, RowEndDate
, IsActiveRow
)
VALUES (
src.NaturalKey
, src.SCD1Field
, src.SCD2Field
, GETDATE()
, NULL
, 'Y'
)
WHEN MATCHED
AND tgt.IsActiveRow = 'Y'
AND EXISTS
(
SELECT src.SCD2Field
EXCEPT
SELECT tgt.SCD2Field
)
THEN
UPDATE
SET IsActiveRow = 'N'
, RowEndDate = DATEADD(dd,-1,GETDATE())
OUTPUT $ACTION Action_Out
, src.NaturalKey
, src.SCD1Field
, src.SCD2Field
, GETDATE() RowStartDate
, NULL RowEndDate
, 'Y' IsActiveRow
)m
WHERE m.Action_Out = 'UPDATE'
END ;
Unfortunately, the MERGE command itself does not provide a way to capture the ACTION counts.
What you can do, however, is to add another step of counting the ACTION results from the table you are logging them to, assuming that you have some sort of audit key or date column you can use to separate the results from the most recent execution.

Using stored procedures to simplify XML generation

I am trying to write an SP to return invoice details in XML for transferring to a third party.
I have a working SP but it's a bit messy (simplified below):
SELECT (
SELECT GETDATE() AS HEADER_SLAStartTime
, DATEADD(HOUR, #SLA_HOURS, GETDATE()) AS HEADER_SLAEndTime
FOR XML PATH ('Header'), TYPE
) , (
SELECT ACCT AS CustomerCode
, ACCTNAME As CustomerName
, ADDR#1 As AddressLine1
, ADDR#2 AS AddressLine2
, ADDR#3 AS AddressLine3
, ADDR#4 AS AddressLine4
, POSTCODE AS AddressPostcode
, TELNO AS AddressTelno
FROM InvHdr
WHERE INVNO = #INVNO
FOR XML PATH('Customer'), TYPE
) , (
SELECT (
SELECT INVNO AS InvoiceNo
, [DATE] AS InvoiceDate
, [INVTYPE] AS InvoiceType
, CASE [SOURCE] WHEN 0 THEN 'Contract' WHEN 1 THEN 'Manual' WHEN 2 THEN 'Sales Order' ELSE '' END AS InvoiceSourceText
, THEIRREF AS CustomerReference
, YOURREF AS InternalReference
, (
SELECT ITEMNO AS ItemCode
, [ITEMDESC#1] AS ItemDesc
, [TYPE] AS ItemType
, [MEMO] AS ItemMemo
, [GOODS] AS ItemCharge
, [DISCOUNT] AS ItemDiscount
FROM InvItems
WHERE INVNO = HDR.INVNO
FOR XML PATH('InvItem'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceHeader'), TYPE
) , (
SELECT HDR.[GOODS] AS InvoiceNet
, HDR.VAT AS InvoiceVAT
, HDR.[GOODS] + HDR.VAT AS InvoiceGross
, (
SELECT VATCODE AS VATListCode
, VATAMT AS VATListAmount
, VATDESC AS VATListDescription
, VATRATE AS VATListRate
, VATGOODS AS VATListGoods
FROM InvVAT
WHERE InvVAT.INVNO = HDR.INVNO
ORDER BY VATAMT DESC
FOR XML PATH('VATSummary'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceFooter'), TYPE
)
FOR XML PATH('Invoices'), TYPE
)
FOR XML PATH(''), ROOT('Output')
This procedure works but I have to create lots of these to get different bits of information in different orders, I have tried creating separate SP's to get the data in sections, below is my first section SP:
CREATE PROCEDURE UDEF_DC_XML_INVOICEFOOTER(
#INVNO INT
)
AS
BEGIN
SELECT HDR.[GOODS] AS InvoiceNet
, HDR.VAT AS InvoiceVAT
, HDR.[GOODS] + HDR.VAT AS InvoiceGross
, (
SELECT VATCODE AS VATListCode
, VATAMT AS VATListAmount
, VATDESC AS VATListDescription
, VATRATE AS VATListRate
, VATGOODS AS VATListGoods
FROM InvVAT
WHERE InvVAT.INVNO = HDR.INVNO
ORDER BY VATAMT DESC
FOR XML PATH('VATSummary'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceFooter'), TYPE
END
When I try calling this:
SELECT UDEF_DC_XML_INVOICEFOOTER(#INVNO)
FOR XML PATH('Invoices'), TYPE
I get the error:
Msg 4121, Level 16, State 1, Line 1
Cannot find either column "dbo" or the user-defined function or aggregate "dbo.UDEF_DC_XML_INVOICEFOOTER", or the name is ambiguous.
In the end I'm hoping to be able to create multiple 4/5 line SP's that will call all the sections in the correct order. Either via calling the individual SP's in order or writing each section to variables and building the full XML afterwards.
Is it possible to call multiple stored procedures returning XML within a single statement?
Is it possible to call multiple stored procedures returning XML within
a single statement?
No, but you can use functions that return XML.
create function dbo.GetXML(#Value int) returns xml
as
begin
return (
select #Value as X
for xml path('Y'), type
)
end
Use like this:
select dbo.GetXML(1)
for xml path('Z')
Result:
<Z>
<Y>
<X>1</X>
</Y>
</Z>

Using T-SQL XML input element values for an IN Clause

I'm trying to write a procedure to sync users from Active directory into my local application database. From my code, I'm passing XML in the following format to the stored procedure:
<AdUsers>
<AdUser AccountSid="S-1-5-21-111111111-111111111-111111111-1111" DisplayName="Test User" EmailAddress="tuser#mail.local" ExchangeServerFk="4" ExchangeServer="https://mail.local" Department="" StatusFK="1" UserName="TUSER">
<AccountSids>
<Sid>S-1-5-21-111111111-111111111-111111111-1111</Sid>
</AccountSids>
</AdUser>
</AdUsers>
I'd like to do a sync between the XML and the rows in my tb_Mailboxes table with the following Stored Procedure:
#adUsers XML, #lastSyncBy VARCHAR (50), #lastSyncOn DATETIME, #defaultProfileId INT, #adDomainId INT
AS
begin try
BEGIN TRANSACTION
--First delete all the mailboxes exist in the database but not in the xml.
delete tb_Mailboxes
where AccountSid not in (
select
rtrim(element.value('text()[1]', 'varchar(100)')) as AccountSid
from
#adUsers.nodes('/AdUsers/AdUser/AccountSids/Sid') t(element)
) AND #adDomainId = AdDomainFk
--Then insert or update existing accounts
MERGE tb_Mailboxes as [target]
USING
(
select
rtrim(element.value('data(#AccountSid)', 'varchar(100)')) as AccountSid
,rtrim(element.value('data(#DisplayName)', 'varchar(100)')) as DisplayName
,rtrim(element.value('data(#EmailAddress)', 'varchar(500)')) as EmailAddress
,rtrim(element.value('data(#ExchangeServerFk)', 'varchar(100)')) as ExchangeServerFk
,rtrim(element.value('data(#ExchangeServer)', 'varchar(150)')) as ExchangeServer
,rtrim(element.value('data(#Department)', 'varchar(100)')) as Department
,rtrim(element.value('data(#StatusFK)', 'varchar(100)')) as StatusFK
,rtrim(element.value('data(#UserName)', 'varchar(100)')) as UserName
,element.query('AccountSids') as SidList
from
#adUsers.nodes('/AdUsers/AdUser') t(element)
) as [source]
on [target].AccountSid IN
(
SELECT rtrim(A.value('text()[1]', 'varchar(100)')) as CurSid
FROM [source].SidList.nodes('Sid') AS FN(A)
)
WHEN MATCHED THEN UPDATE SET
DisplayName = [source].DisplayName
,EmailAddress = [source].EmailAddress
,ExchangeServerFk = [source].ExchangeServerFk
,ExchangeServer = [source].ExchangeServer
,Department = [source].Department
,UserName = [source].UserName
/*,StatusFK = [source].StatusFK*/
,LastSyncOn = #lastSyncOn
,LastSyncBy = #lastSyncBy
WHEN NOT MATCHED THEN INSERT
(
AdDomainFk,
UserName,
DisplayName,
Department,
EmailAddress,
ExchangeServerFk,
ExchangeServer,
AccountSid,
IsAutoDeleteEnabled,
ProfileFk,
Settings,
QueueLastPickedUp,
QueueLastProcessed,
QueueLastFinished,
LastSyncOn,
LastSyncBy,
StatusFK
)
VALUES
(
#adDomainId
,[source].UserName
,[source].DisplayName
,[source].Department
,[source].EmailAddress
,[source].ExchangeServerFk
,[source].ExchangeServer
,[source].AccountSid
,0
,#defaultProfileId
,NULL
,NULL
,NULL
,NULL
,#lastSyncOn
,#lastSyncBy
,[source].StatusFK
);
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
However, the "NOT IN" in the delete section AND "IN" in the match section don't seem to work. Is this type of IN clause using multiple values in the XML even feasible? Is there a better approach to this problem that I'm missing?
The issue with your MERGE query is the join between the [source] and the [target] tables. Rather than joining the target and source tables using
ON [target].AccountSid IN
(
SELECT rtrim(A.value('text()[1]', 'varchar(100)')) as CurSid
FROM [source].SidList.nodes('Sid') AS FN(A)
)
use this instead:
ON [target].AccountSid = [source].AccountSid
[source] will be materialised as a table and you join to it like you would any other table. Your IN statement doesn't make much sense as it is a completely different entity so will equivalent to a kind of Cartesian join (FULL OUTER).
Another comment I would make is why the separate DELETE statement to remove mailboxes that no longer exist in the XML? Why not simply put the DELETE within the MERGE statement by using the following statement?
WHEN NOT MATCHED BY SOURCE
THEN DELETE
Applying all this, your MERGE statement becomes:
MERGE tb_Mailboxes AS [target]
USING
(SELECT RTRIM(element.value('data(#AccountSid)', 'varchar(100)')) AS AccountSid
, RTRIM(element.value('data(#DisplayName)', 'varchar(100)')) AS DisplayName
, RTRIM(element.value('data(#EmailAddress)', 'varchar(500)')) AS EmailAddress
, RTRIM(element.value('data(#ExchangeServerFk)', 'varchar(100)')) AS ExchangeServerFk
, RTRIM(element.value('data(#ExchangeServer)', 'varchar(150)')) AS ExchangeServer
, RTRIM(element.value('data(#Department)', 'varchar(100)')) AS Department
, RTRIM(element.value('data(#StatusFK)', 'varchar(100)')) AS StatusFK
, RTRIM(element.value('data(#UserName)', 'varchar(100)')) AS UserName
FROM #adUsers.nodes('/AdUsers/AdUser') t (element)) AS [source]
ON [target].AccountSid = [source].AccountSid
WHEN MATCHED
THEN UPDATE
SET DisplayName = [source].DisplayName
, EmailAddress = [source].EmailAddress
, ExchangeServerFk = [source].ExchangeServerFk
, ExchangeServer = [source].ExchangeServer
, Department = [source].Department
, UserName = [source].UserName
/*,StatusFK = [source].StatusFK*/
, LastSyncOn = #lastSyncOn
, LastSyncBy = #lastSyncBy
WHEN NOT MATCHED BY TARGET
THEN INSERT (AdDomainFk
, UserName
, DisplayName
, Department
, EmailAddress
, ExchangeServerFk
, ExchangeServer
, AccountSid
, IsAutoDeleteEnabled
, ProfileFk
, Settings
, QueueLastPickedUp
, QueueLastProcessed
, QueueLastFinished
, LastSyncOn
, LastSyncBy
, StatusFK)
VALUES (#adDomainId
, [source].UserName
, [source].DisplayName
, [source].Department
, [source].EmailAddress
, [source].ExchangeServerFk
, [source].ExchangeServer
, [source].AccountSid
, 0
, #defaultProfileId
, NULL
, NULL
, NULL
, NULL
, #lastSyncOn
, #lastSyncBy
, [source].StatusFK)
WHEN NOT MATCHED BY SOURCE
THEN DELETE;

ORA-06550 AND ORA-00917 : COMMA MISSING FOR INSERT STATEMENT

DECLARE
REG_NO VARCHAR2(20) := & REG_NO;
cursor c1 is select Registration_No ,PROVISIONAL_DIGONOSIS, Remark , Medicine_No ,Dignosis_Date , Precaution Medicine ,No_of_Doses ,Injection_Date ,Status from Patient_Diagnosis d, Patient_Medicine m , Patient_Injection_Dates i where d.Registration_No = REG_NO and d.Dignosis_No = m.Dignosis_No and m.dignosis_no = i.dignosis_no ;
BEGIN
FOR REC1 IN c1
loop
insert into D_DETAIL( reg_no, p_d, remark, m_no, d_date, p_med, doses, i_date, status) values ( REC1.Registration_No , REC1.PROVISIONAL_DIGONOSIS, REC1.Remark , REC1.Medicine_No, REC1.Dignosis_Date, REC1.Precaution Medicine ,REC1.No_of_Doses , REC1.Injection_Date , REC1.Status );
end loop;
END;
ORA-06550 AND ORA-00917 : COMMA MISSING FOR INSERT STATEMENT IS THE ERROR ....WHAT SHOULD I CHANGE ?
insert into D_DETAIL( reg_no, p_d, remark, m_no, d_date, p_med, doses, i_date, status) values ( REC1.Registration_No , REC1.PROVISIONAL_DIGONOSIS, REC1.Remark , REC1.Medicine_No, REC1.Dignosis_Date, REC1.Precaution Medicine ,REC1.No_of_Doses , REC1.Injection_Date , REC1.Status );
I'd say
REC1.Precaution Medicine
should be
REC1.Precaution_Medicine

Resources