PostgreSQL 11.1 PgAdmin 4.1
This works some of the time:
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
WITH _in(trx, lastname, firstname, birthdate, old_disp, old_medname, old_sig, old_form, new_disp, new_medname, new_sig, new_form, new_refills) AS (
VALUES ('2001-06-07 00:00:00'::timestamp,
UPPER(TRIM('JONES')), UPPER(TRIM('TOM')), '1952-12-30'::date,
64::integer,
LOWER(TRIM('adipex 37.5mg tab')), LOWER(TRIM('one tab po qd')), LOWER(TRIM('tab')),
63::integer,
LOWER(TRIM('adipex 37.5mg tab')), LOWER(TRIM('one tab po qd')), LOWER(TRIM('tab')),
33::integer
)
),
_s AS ( -- RESOLVE ALL SURROGATE KEYS.
SELECT n.*, d1.recid as old_medication_recid, d2.recid as new_medication_recid, pt.recid as patient_recid
FROM _in n
JOIN medications d1 ON (n.old_medname, n.old_sig, n.old_form) = (d1.medname, d1.sig, d1.form)
JOIN medications d2 ON (n.new_medname, n.new_sig, n.new_form) = (d2.medname, d2.sig, d2.form)
JOIN patients pt ON (pt.lastname, pt.firstname, pt.birthdate) = (n.lastname, n.firstname, n.birthdate)
),
_t AS ( -- REMOVE CONFLICTING RECORD, IF ANY.
DELETE FROM rx r
USING _s n
WHERE (r.trx::date, r.disp, r.patient_recid, r.medication_recid)=(n.trx::date, n.new_disp, n.patient_recid, n.new_medication_recid)
RETURNING r.*
),
_u AS( -- GET NEW SURROGATE KEY.
SELECT COALESCE(_t.recid, r.recid) as target_recid, r.recid as old_recid
FROM _s n
JOIN rx r ON (r.trx, r.disp, r.patient_recid, r.medication_recid) = (n.trx, n.old_disp, n.patient_recid, n.old_medication_recid)
LEFT JOIN _t ON (_t.trx::date, _t.disp, _t.patient_recid, _t.medication_recid) = (n.trx::date, n.new_disp, n.patient_recid, n.new_medication_recid)
)
UPDATE rx r -- UPDATE ORIGINAL RECORD WITH NEW VALUES.
SET disp = n.new_disp, medication_recid = n.new_medication_recid, refills = n.new_refills, recid = _u.target_recid
FROM _s n, _u
WHERE r.recid = _u.old_recid
RETURNING r.*;
COMMIT;
Where table rx is defined as:
CREATE TABLE phoenix.rx
(
recid integer NOT NULL DEFAULT nextval('rx_recid_seq'::regclass),
trx timestamp without time zone NOT NULL,
disp integer NOT NULL,
refills integer,
tprinted timestamp without time zone,
tstop timestamp without time zone,
modified timestamp without time zone DEFAULT now(),
patient_recid integer NOT NULL,
medication_recid integer NOT NULL,
dposted date NOT NULL,
CONSTRAINT pk_rx_recid PRIMARY KEY (recid),
CONSTRAINT rx_unique UNIQUE (dposted, disp, patient_recid, medication_recid)
DEFERRABLE,
CONSTRAINT rx_medication_fk FOREIGN KEY (medication_recid)
REFERENCES phoenix.medications (recid) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE RESTRICT
DEFERRABLE,
CONSTRAINT rx_patients FOREIGN KEY (patient_recid)
REFERENCES phoenix.patients (recid) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE RESTRICT
)
After many hours, it is found that the "Delete.." of a conflicting record works as expected, but the "COALESCE" STATEMENT seems to fail when deciding on the new surrogate key (primary key) of rx.recid -- it does not seem to receive the result of the delete. (Or maybe the timing is wrong???)
Any help would be most appreciated.
TIA
This is documented:
The sub-statements in WITH are executed concurrently with each other and with the main query. Therefore, when using data-modifying statements in WITH, the order in which the specified updates actually happen is unpredictable. All the statements are executed with the same snapshot (see Chapter 13, so they cannot “see” one another's effects on the target tables.
Don't use the same table twice in a statement with a CTE if it occurs in a DML statement. Rather, use DELETE ... RETURNING and use the returned values in the other parts of the statement.
If you cannot rewrite the statement like that, use more than one statement instead of putting everything into a single CTE.
#LaurenzAlbe is totally correct in his answer. Below is a working solution to my problem. There are a few things to note:
The unique constraint is formed on a column in rx defined as a date and created by a trigger on update/insert that casts the timestamp of trx to a date: as in trx::date. For reasons I am not clear on, using r.trx::date in place of r.dposted leads to many records being identified and not the one record I want. Not sure why???. So the first fix was to use r.dposted, not r.trx::date.
Although the cte's are designed to be independent of each other, by using "RETURNING..." and incorporating the cte's in a step-wise fashion, one can be built upon another to obtain a final result set.
The working code is:
WITH _in(trx, lastname, firstname, birthdate, old_disp, old_medname, old_sig, old_form, new_disp, new_medname, new_sig, new_form, new_refills) AS (
VALUES ('2001-06-07 00:00:00'::timestamp,
UPPER(TRIM('smith')), UPPER(TRIM('john')), '1957-12-30'::date,
28::integer,
LOWER(TRIM('test')), LOWER(TRIM('i am sig')), LOWER(TRIM('tab')),
28::integer,
LOWER(TRIM('test 1')), LOWER(TRIM('i am sig')), LOWER(TRIM('tab')),
8::integer
)
),
_m AS (
SELECT n.*, d1.recid as old_medication_recid, d2.recid as new_medication_recid, pt.recid as patient_recid
FROM _in n
JOIN patients pt ON (pt.lastname, pt.firstname, pt.birthdate) = (n.lastname, n.firstname, n.birthdate)
JOIN medications d1 ON (n.old_medname, n.old_sig, n.old_form) = (d1.medname, d1.sig, d1.form)
LEFT JOIN medications d2 ON (n.new_medname, n.new_sig, n.new_form) = (d2.medname, d2.sig, d2.form)
),
_t AS ( -- REMOVE CONFLICTING RECORD, IF ANY.
DELETE FROM rx r
USING _m
WHERE (r.dposted, r.disp, r.patient_recid, r.medication_recid) = (_m.trx::date,_m.new_disp, _m.patient_recid, _m.new_medication_recid)
RETURNING r.*
),
_s AS ( -- GET NEW SURROGATE KEY
SELECT _m.*, r1.recid as old_recid, r2.recid as new_recid, COALESCE(r2.recid, r1.recid) as target_recid
FROM _m
JOIN rx r1 ON (r1.dposted, r1.disp, r1.patient_recid, r1.medication_recid) = (_m.trx::date,_m.old_disp, _m.patient_recid, _m.old_medication_recid)
LEFT JOIN rx r2 ON (r2.dposted, r2.disp, r2.patient_recid, r2.medication_recid) = (_m.trx::date,_m.new_disp, _m.patient_recid, _m.new_medication_recid)
LEFT JOIN _t ON (_t.recid = r2.recid)
)
UPDATE rx -- UPDATE ORIGINAL RECORD WITH NEW VALUES.
SET disp = _s.new_disp, medication_recid = _s.new_medication_recid, refills = _s.new_refills, recid = _s.target_recid
FROM _s
WHERE rx.recid = _s.old_recid
RETURNING rx.*;
COMMIT;
Hope this helps somebody.
I wrote T-SQL MERGE query to merge staging data into a data warehouse (you can find it at the bottom).
If I uncomment the OUTPUT statement the I get error mentioned in the title.
However, if I do not include it, everything works perfectly fine and MERGE succeeds.
I know that there are some issue connected to the MERGE clause, however there are more connected to the type of merge.
I checked the following answer: [https://dba.stackexchange.com/questions/140880/why-does-this-merge-statement-cause-the-session-to-be-killed], however in my execution plan I cannot find exactly index insert followed by index merge.
Rather, what I see is the following execution plan
Code was developed on database attached to SQL Server 2012 (SP4) instance
I would really appreciate good explanation of this problem, ideally referencing steps from my execution plan.
Thank you.
declare #changes table (chgType varchar(50),Id varchar(18))
begin try
set xact_abort on
begin tran
;with TargetUserLogHsh as (select
hsh =hashbytes('md5',concat(coalesce([AccountName],'')
,coalesce([TaxNumber],'')))
,LastLoginCast = coalesce(CONVERT(datetime,LastLogin,103),getdate())
,* from
dw.table1)
,SourceUserLogHsh as (select
hsh =hashbytes('md5',concat(coalesce([AccountName],'')
,coalesce([TaxNumber],'')))
,LastLoginCast = coalesce(CONVERT(datetime,LastLogin,103),getdate())
,* from
sta.table1)
merge TargetUserLogHsh target
using SourceUserLogHsh source
on target.ContactId = source.ContactId and target.Lastlogincast >= source.LastLoginCast
when not matched then insert (
[AccountName]
,[TaxNumber]
,[LastLogin]
)
values (
source.[AccountName]
,source.[TaxNumber]
,source.[LastLogin]
)
when matched and target.lastlogincast = source.lastlogincast
and target.hsh != source.hsh then
update
set
[AccountName] = source.[AccountName]
,[TaxNumber] = source.[TaxNumber]
,[LastLogin] = source.[LastLogin]
output $action,inserted.contactid into #changes
;
commit tran
end try
begin catch
if ##TRANCOUNT > 0 rollback tran
select ERROR_MESSAGE()
end catch
I am trying to update our patient table within our system, but due to a connection we have with the state that sends modifications to that table to an external system I'm unable to do mass updates due to several triggers in our system (Microsoft SQL Server 2008 R2). The main piece of code in the trigger that is preventing
IF (#numrows > 1)
BEGIN
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION
SELECT #errmsg = OBJECT_NAME(##PROCID) + ' : more than one row is updated in table Patient'
RAISERROR(#errmsg,16,21)
RETURN
END
I cannot simply shut these triggers off where it would break a lot of things, so to do what would be a simple act of
update patient set security_level = '2'
where security_level = '1'
I used the following code which used to work in prior versions
declare #tmp_table table(
PRG int identity(1,1) Primary Key,
patient_id int
)
declare #start_value int = 1,
#finish_value int,
#patient_id int
Insert Into #tmp_table(patient_id ) Select patient_id From patient where security_level = '1'
Select #finish_value = max(PRG) From #tmp_table
While #start_value <= #finish_value
Begin
--now get a key for patient and store in variables
Select #patient_id = patient_id
From #tmp_table
Where PRG = #start_value
--.. and now update by key
Update patient
set security_level = '2'
Where patient_id = #patient_id
Set #start_value = #start_value + 1
End
I get the following error upon running that code
Msg 50000, Level 16, State 21, Procedure tU_Patient_HL7, Line 64
tU_Patient_HL7 : more than one row is updated in table Patient
Msg 3609, Level 16, State 1, Line 22
The transaction ended in the trigger. The batch has been aborted.
Any idea how I could tweak this or recode this to update the security level of all patients who have a security level set at 1 and switch it to 2?
Update
Would there be any way I could loop
Update top (1) patient
set security_level = '2'
where security_level = '1'
Until all rows are affected? That would work as well.
With out full code ,it is hard .I am guessing your update statement is conflicting with below peice of code..
IF (#numrows > 1)
even though you use
Update patient
set security_level = '2'
Where patient_id = #patient_id
Your update query may affect more than one row.So my best bet would be to tweak your update query to below or change trigger if you can which is desirable
Update top (1) patient
set security_level = '2'
Where patient_id = #patient_id
If you add to your update code a "GO 100", that should do the trick. You may need to put a delay in there as well if it updates too fast for the trigger.
Update top (1) patient
set security_level = '2'
where security_level = '1'
GO 100
The number of executions (i.e. 100 in my example) is up to you.
I am developing a TSQL stored proc using SSMS 2008 and am receiving the above error while generating a CTE. I want to add logic to this SP to return every day, not just the days with data. How do I do this? Here is my SP so far:
ALTER Proc [dbo].[rpt_rd_CensusWithChart]
#program uniqueidentifier = NULL,
#office uniqueidentifier = NULL
AS
DECLARE #a_date datetime
SET #a_date = case when MONTH(GETDATE()) >= 7 THEN '7/1/' + CAST(YEAR(GETDATE()) AS VARCHAR(30))
ELSE '7/1/' + CAST(YEAR(GETDATE())-1 AS VARCHAR(30)) END
if exists (
select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#ENROLLEES')
) DROP TABLE #ENROLLEES;
if exists (
select * from tempdb.dbo.sysobjects o where o.xtype in ('U') and o.id = object_id(N'tempdb..#DISCHARGES')
) DROP TABLE #DISCHARGES;
declare #sum_enrollment int
set #sum_enrollment =
(select sum(1)
from enrollment_view A
join enrollment_info_expanded_view C on A.enrollment_id = C.enroll_el_id
where
(#office is NULL OR A.group_profile_id = #office)
AND (#program is NULL OR A.program_info_id = #program)
and (C.pe_end_date IS NULL OR C.pe_end_date > #a_date)
AND C.pe_start_date IS NOT NULL and C.pe_start_date < #a_date)
select
A.program_info_id as [Program code],
A.[program_name],
A.profile_name as Facility,
A.group_profile_id as Facility_code,
A.people_id,
1 as enrollment_id,
C.pe_start_date,
C.pe_end_date,
LEFT(datename(month,(C.pe_start_date)),3) as a_month,
day(C.pe_start_date) as a_day,
#sum_enrollment as sum_enrollment
into #ENROLLEES
from enrollment_view A
join enrollment_info_expanded_view C on A.enrollment_id = C.enroll_el_id
where
(#office is NULL OR A.group_profile_id = #office)
AND (#program is NULL OR A.program_info_id = #program)
and (C.pe_end_date IS NULL OR C.pe_end_date > #a_date)
AND C.pe_start_date IS NOT NULL and C.pe_start_date >= #a_date
;WITH #ENROLLEES AS (
SELECT '7/1/11' AS dt
UNION ALL
SELECT DATEADD(d, 1, pe_start_date) as dt
FROM #ENROLLEES s
WHERE DATEADD(d, 1, pe_start_date) <= '12/1/11')
The most obvious issue (and probably the one that causes the error message too) is the absence of the actual statement to which the last CTE is supposed to pertain. I presume it should be a SELECT statement, one that would combine the result set of the CTE with the data from the #ENROLLEES table.
And that's where another issue emerges.
You see, apart from the fact that a name that starts with a single # is hardly advisable for anything that is not a local temporary table (a CTE is not a table indeed), you've also chosen for your CTE a particular name that already belongs to an existing table (more precisely, to the already mentioned #ENROLLEES temporary table), and the one you are going to pull data from too. You should definitely not use an existing table's name for a CTE, or you will not be able to join it with the CTE due to the name conflict.
It also appears that, based on its code, the last CTE represents an unfinished implementation of the logic you say you want to add to the SP. I can suggest some idea, but before I go on I'd like you to realise that there are actually two different requests in your post. One is about finding the cause of the error message, the other is about code for a new logic. Generally you are probably better off separating such requests into distinct questions, and so you might be in this case as well.
Anyway, here's my suggestion:
build a complete list of dates you want to be accounted for in the result set (that's what the CTE will be used for);
left-join that list with the #ENROLLEES table to pick data for the existing dates and some defaults or NULLs for the non-existing ones.
It might be implemented like this:
… /* all your code up until the last WITH */
;
WITH cte AS (
SELECT CAST('7/1/11' AS date) AS dt
UNION ALL
SELECT DATEADD(d, 1, dt) as dt
FROM cte
WHERE dt < '12/1/11'
)
SELECT
cte.dt,
tmp.[Program code],
tmp.[program_name],
… /* other columns as necessary; you might also consider
enveloping some or all of the "tmp" columns in ISNULLs,
like in
ISNULL(tmp.[Program code], '(none)') AS [Program code]
to provide default values for absent data */
FROM cte
LEFT JOIN #ENROLLEES tmp ON cte.dt = tmp.pe_start_date
;
There are two inserts in my trigger which is fired by an update. My Vendor_Hist table has a field called thID which is the primary key in Task_History table. thID gets its' value from mySeq.nextval.
INSERT INTO TASK_HISTORY
( thID, phId, LABOR, VERSION )
( select mySeq.NEXTVAL, mySeq2.CurrVal, LABOR, tmpVersion
from tasks t
where t.project_id = :new.project_ID );
select mySeq.currval into tmpTHID from dual; -- problem here!
INSERT INTO VENDOR_HIST
( vhID, thID, Amount, Position, version )
( select mySeq3.NEXTVAL, tmpTHID,
Amount, Position, tmpVersion
from vendors v2, tasks t2
where v2.myID = t2.myID
and t2.project_id = :new.project_ID );
Now, my problem is the tmpTHID always the latest value of mySeq.nextVal. So, if thID in task_history is 1,2,3, I get three inserts into vendor_hist table with 3,3,3. It has to be 1,2,3. I also tried
INSERT INTO TASK_HISTORY
( thID, phId, LABOR, VERSION )
( select mySeq.NEXTVAL, mySe2.CurrVal, LABOR, tmpVersion
from tasks t
where t.project_id = :new.project_ID ) returning thID into :tmpTHID;
but then I get a "warning compiled with errors" message when I execute the trigger. How do I make sure that the thID in first insert is also the same in my second insert?
Hope it makes sense.
for i in (select * from tasks t
where t.project_id = :new.project_id)
loop
insert into task_history
( thID, phId, LABOR, VERSION )
values
(mySeq.NEXTVAL, mySeq2.CurrVal, i.LABOR, i.tmpVersion);
for each j in (select * from vendors v
where i.myId = v.myId)
loop
insert into vendor_history
( vhID, thID, Amount, Position, version )
values
(mySeq3.NEXTVAL, mySeq.CURRVAL, j.Amount, j.Position, j.tmpVersion)
end loop;
end loop;
I'm assuming the columns inserted in the second insert are from the VENDORS table; if not, the referencing cursor (i or j) should be used as appropriate.
Instead of the currVal, it works with the following subselect.
( select min(thID) from task_history t3
where t3.project_id = t2.project_id
and t3.myID = t2.myID
and t3.version = tmpVersion ),