I'm currently in a scenario where we have a clunky, old, monolith of a database powering one or two of our less user-friendly systems (this is controlled by a third-party, so I can't change that).
Going forward, I'm looking to push the necessary data into a new (better structured) database and implement a mechanism to keep the data in sync between the two.
One of the quirky traits of this old database is that, instead of just using null values, it uses empty strings and treats them as if they're null.
In the new database, I want to use nulls where there's no data (because I can't think of any good reasons not to).
My issue is when I'm pulling the data from the old database to the new one (using MERGE statements), I'm using a NULLIF([myCol], '') to check there's actually any data - and just treating it as a null if not.
For example, when syncing the data for students I'm planning on doing something along the lines of (the name of the databases and schemas isn't relevant to the question so I'm anonymizing the structure a bit):
USE [NewDB];
DROP TABLE IF EXISTS #myTempTable;
SELECT *
INTO #myTempTable
FROM [OldDB].[schemaName].[Students];
MERGE [schema].[Person] p
USING #myTempTable s
ON TRIM(s.STUD_ISN) = p.StudentDBID
--WHEN MATCHED and not equal, update
WHEN NOT MATCHED
THEN INSERT (
[Surname] ,
[PreferredSurname] ,
[FormerSurname] ,
[Forename] ,
[MiddleNames] ,
[PreferredForename] ,
[DoB] ,
[Gender] ,
[GenderIdentity] ,
[Title] ,
[Deceased] ,
[DeceasedDate] ,
[StudentDBID])
VALUES (
NULLIF(TRIM([s].[STUD_Surname]), '') ,
NULLIF(TRIM([s].[STUD_Preferred_Surname]), '') ,
NULLIF(TRIM([s].STUD_Former_Surname), '') ,
NULLIF(TRIM([s].[STUD_Forename_1]), '') ,
NULLIF(TRIM([s].[STUD_Forename_2]), '') ,
NULLIF(TRIM([s].[STUD_Known_As]), '') ,
[s].[STUD_DOB] ,
NULLIF(TRIM([s].[STUD_Gender]), '') ,
NULLIF(TRIM([s].STUD_Gender_Identity), '') ,
NULLIF(TRIM([s].STUD_Title), '') ,
[s].STUD_Deceased ,
[s].STUD_Date_of_Death ,
TRIM([s].STUD_ISN)
);
Now, obviously this current query only deals with the following scenario:
The old database has data and the new one doesn't
Because I'm having some grief with the logic for the following scenario:
The new database has the corresponding record, but it needs updating.
For the text based fields there's 3 different scenarios where the "new" data would need updating:
Neither value is null/empty, but they're not equal
The record in the "old" database is null/empty and the "new" one isn't
The record in the "new" database is null and the "old" one isn't
I was hoping to do something relatively simple like:
WHEN MATCHED AND (
(NULLIF(TRIM([s].[STUD_Surname]), '') IS NOT NULL
AND p.Surname IS NULL)
OR (NULLIF(TRIM([s].[STUD_Surname]), '') IS NULL
AND p.Surname IS NOT NULL)
OR (NULLIF(TRIM([s].[STUD_Surname]), '') != p.Surname)
) -- do the same for the other columns
THEN UPDATE SET
Surname = s.STUD_Surname
But, NULLIF(TRIM([s].[STUD_Surname]), '') IS NOT NULL is apparently not valid?
Is my best option to just switch out the NULLIF for an ISNULL in the WHEN MATCHED clause... Or am I missing something that'll make it more efficient?
The checks for the update can be simplified.
Since the source temp table doesn't have NULL's.
You just need to compare the trimmed values.
MERGE Person t
USING #tmpTable s
ON s.STUD_ISN = t.StudentDBID
WHEN NOT MATCHED THEN
INSERT (
StudentDBID
, Forename
, PreferredForename
, Surname
)
VALUES (
NULLIF(TRIM(s.STUD_ISN), '')
, NULLIF(TRIM(s.STUD_Forename), '')
, NULLIF(TRIM(s.STUD_Forename_1), '')
, NULLIF(TRIM(s.STUD_Surname), '')
)
WHEN MATCHED
AND (
COALESCE(t.Forename, '') != TRIM(s.STUD_Forename)
OR COALESCE(t.PreferredForename, '') != TRIM(s.STUD_Forename_1)
OR COALESCE(t.Surname, '') != TRIM(s.STUD_Surname)
)
THEN
UPDATE SET
UpdatedOn = SYSDATETIME()
, Forename = NULLIF(TRIM(s.STUD_Forename), '')
, PreferredForename = NULLIF(TRIM(s.STUD_Forename_1), '')
, Surname = NULLIF(TRIM(s.STUD_Surname), '')
;
SELECT *
FROM Person
StudentDBID | Forename | PreferredForename | Surname | UpdatedOn | CreatedOn
:---------- | :------- | :---------------- | :------ | :-------------------------- | :--------------------------
B9701 | Bob | Bobby | Modest | 2022-01-14 12:58:05.9862269 | 2022-01-14 12:58:05.9862269
J0402 | Jane | JD | Doe | 2022-01-14 12:58:05.9862269 | 2022-01-14 12:58:05.9862269
J0504 | Jim | Jimmy | Savage | null | 2022-01-14 12:58:05.9862269
J7201 | John | null | Doe | null | 2022-01-14 12:58:05.9862269
M0803 | Mike | null | Hammer | 2022-01-14 12:58:05.9862269 | 2022-01-14 12:58:05.9862269
Demo on db<>fiddle here
In the end, my solution was to apply the various functions within the SELECT .... Into .... block. Like so:
ALTER PROCEDURE [NGSync].[spFullStudentSync]
AS
BEGIN
SET NOCOUNT ON;
DROP TABLE IF EXISTS #students
SELECT [STUD_ISN],
NULLIF(TRIM([STUD_Student_ID]), '') AS [STUD_Student_ID] ,
NULLIF(TRIM([STUD_Surname]), '') AS [STUD_Surname] ,
NULLIF(TRIM([STUD_Forename_1]), '') AS [STUD_Forename_1] ,
NULLIF(TRIM([STUD_Forename_2]), '') AS [STUD_Forename_2] ,
NULLIF(TRIM([STUD_Known_As]), '') AS [STUD_Known_As] ,
[STUD_DOB] ,
NULLIF(TRIM([STUD_Gender]) , '') AS [STUD_Gender],
NULLIF(TRIM([STUD_Title]) , '') AS [STUD_Title],
NULLIF(TRIM([STUD_Ethnicity]) , '') AS [STUD_Ethnicity],
NULLIF(TRIM([STUD_LDDHP]) , '') AS [STUD_LDDHP],
NULLIF(TRIM([STUD_Home_Telephone_No] ) , '') AS [STUD_Home_Telephone_No] ,
NULLIF(TRIM([STUD_Daytime_Telephone] ) , '') AS [STUD_Daytime_Telephone] ,
NULLIF(TRIM([STUD_Mobile_Telephone] ) , '') AS [STUD_Mobile_Telephone] ,
NULLIF(TRIM([STUD_EMail_Address] ) , '') AS [STUD_EMail_Address] ,
NULLIF(TRIM([STUD_Former_Surname] ) , '') AS [STUD_Former_Surname] ,
CAST( CASE WHEN NULLIF(TRIM([STUD_Deceased] ) , '') = 'D' THEN 1 else 0 end AS bit ) AS [STUD_Deceased] ,
NULLIF(TRIM([STUD_Deletion_Flag] ) , '') AS [STUD_Deletion_Flag] ,
[STUD_Delete_Merge] ,
NULLIF(TRIM([STUD_Photo_filename] ) , '') AS [STUD_Photo_filename] ,
NULLIF(TRIM([STUD_Nationality] ) , '') AS [STUD_Nationality] ,
[STUD_Date_of_Entry_in_UK] ,
NULLIF(TRIM([STUD_Student_Type_FESR] ) , '') AS [STUD_Student_Type_FESR] ,
[STUD_School_ISN] ,
NULLIF(TRIM([STUD_Home_LEA] ) , '') AS [STUD_Home_LEA] ,
NULLIF(TRIM([STUD_Employer_Code] ) , '') AS [STUD_Employer_Code] ,
NULLIF(TRIM([STUD_Religion] ) , '') AS [STUD_Religion] ,
NULLIF(TRIM([STUD_Location] ) , '') AS [STUD_Location] ,
[STUD_TPS_Include] ,
[STUD_QOE_Complete] ,
NULLIF(TRIM([STUD_UCAS_Application_Code] ) , '') AS [STUD_UCAS_Application_Code] ,
[STUD_MIAP_Consent_Status] ,
[STUD_MIAP_Verification_Type] ,
NULLIF(TRIM([STUD_MIAP_Other_Verification] ) , '') AS [STUD_MIAP_Other_Verification] ,
NULLIF(TRIM([STUD_Bank_Sort_Code] ) , '') AS [STUD_Bank_Sort_Code] ,
NULLIF(TRIM([STUD_Bank_AC_No] ) , '') AS [STUD_Bank_AC_No] ,
[STUD_Bank_ISN] ,
NULLIF(TRIM([STUD_Bank_Postcode] ) , '') AS [STUD_Bank_Postcode] ,
NULLIF(TRIM([STUD_Bank_AC_Holders_Name] ) , '') AS [STUD_Bank_AC_Holders_Name] ,
NULLIF(TRIM([STUD_Current_Tutor_Group] ) , '') AS [STUD_Current_Tutor_Group] ,
NULLIF(TRIM([STUD_Current_PostCode] ) , '') AS [STUD_Current_PostCode] ,
NULLIF(TRIM([STUD_Doctor_Name] ) , '') AS [STUD_Doctor_Name] ,
NULLIF(TRIM([STUD_Doctor_Telephone] ) , '') AS [STUD_Doctor_Telephone] ,
[STUD_ULN] ,
NULLIF(TRIM([STUD_College_Email_Address] ) , '') AS [STUD_College_Email_Address] ,
[STUD_Date_Due_to_Leave_UK] ,
[STUD_UK_Residence] ,
NULLIF(TRIM([STUD_Parish] ) , '') AS [STUD_Parish] ,
NULLIF(TRIM([STUD_Area] ) , '') AS [STUD_Area] ,
[STUD_Bus_Pass] ,
NULLIF(TRIM([STUD_Bus_Route] ) , '') AS [STUD_Bus_Route] ,
[STUD_Eng_1st_Lang] ,
NULLIF(TRIM([STUD_Language] ) , '') AS [STUD_Language] ,
[STUD_High_Achiever] ,
[STUD_Is_Staff] ,
[STUD_Is_Staff_ISN] ,
[STUD_Excluded] ,
[STUD_RUI_3_no_contact] ,
[STUD_RUI_1_courses] ,
[STUD_RUI_2_surveys] ,
[STUD_PMC_C1_post] ,
[STUD_PMC_C2_phone] ,
[STUD_PMC_C3_email] ,
[STUD_Created_Date] ,
NULLIF(TRIM([STUD_Created_User] ) , '') AS [STUD_Created_User] ,
NULLIF(TRIM([STUD_Created_Prog] ) , '') AS [STUD_Created_Prog] ,
[STUD_Modified_Date] ,
NULLIF(TRIM([STUD_Modified_User] ) , '') AS [STUD_Modified_User] ,
NULLIF(TRIM([STUD_Modified_Prog] ) , '') AS [STUD_Modified_Prog] ,
[STUD_OK_to_use_Image] ,
NULLIF(TRIM([STUD_Sexual_Orientation] ) , '') AS [STUD_Sexual_Orientation] ,
NULLIF(TRIM([STUD_Gender_Identity] ) , '') AS [STUD_Gender_Identity] ,
[STUD_Visa_Proof_Produced] ,
[STUD_Visa_Proof_Produced_Date] ,
NULLIF(TRIM([STUD_Visa_Proof_Produced_Details] ) , '') AS [STUD_Visa_Proof_Produced_Details] ,
NULLIF(TRIM([STUD_Visa_Type] ) , '') AS [STUD_Visa_Type] ,
[STUD_Visa_Expiry_Date] ,
[STUD_Visa_Letter_Issued] ,
[STUD_Asylum_Seeker] ,
[STUD_Refugee] ,
[STUD_Entered_UK_for_Education] ,
[STUD_Restrictions_on_Stay] ,
NULLIF(TRIM([STUD_Add_To_Portal_Title_Bar] ) , '') AS [STUD_Add_To_Portal_Title_Bar] ,
[STUD_RUI_5] ,
NULLIF(TRIM([STUD_Visa_Reference]) , '') AS [STUD_Visa_Reference],
NULLIF(TRIM([STUD_Visa_Note] ) , '') AS [STUD_Visa_Note] ,
[STUD_FA_Bank_ISN] ,
NULLIF(TRIM([STUD_FA_Bank_Sort_Code] ) , '') AS [STUD_FA_Bank_Sort_Code] ,
NULLIF(TRIM([STUD_FA_Bank_AC_No] ) , '') AS [STUD_FA_Bank_AC_No] ,
NULLIF(TRIM([STUD_FA_Bank_AC_Holders_Name]) , '') AS [STUD_FA_Bank_AC_Holders_Name],
NULLIF(TRIM([STUD_Marital_Status] ) , '') AS [STUD_Marital_Status] ,
NULLIF(TRIM([STUD_Country_of_Birth] ) , '') AS [STUD_Country_of_Birth] ,
NULLIF(TRIM([STUD_On_Supervision] ) , '') AS [STUD_On_Supervision] ,
[STUD_S_IraqDEAS] ,
[STUD_S_Pending_SCN] ,
NULLIF(TRIM([STUD_Primary_LLDDCode] ) , '') AS [STUD_Primary_LLDDCode] ,
NULLIF(TRIM([STUD_A2C_Language] ) , '') AS [STUD_A2C_Language] ,
NULLIF(TRIM([STUD_Signature_Filename] ) , '') AS [STUD_Signature_Filename] ,
[STUD_S_ILA_Expiry_Date] ,
[STUD_Date_Of_Death] ,
[STUD_S_SQA_Exception] ,
NULLIF(TRIM([STUD_S_SQA_VID] ) , '') AS [STUD_S_SQA_VID] ,
[STUD_S_SQA_Registration_Date] ,
NULLIF(TRIM([STUD_FA_Bank_AC_Ref] ) , '') AS [STUD_FA_Bank_AC_Ref] ,
NULLIF(TRIM([STUD_Bank_AC_Ref] ) , '') AS [STUD_Bank_AC_Ref] ,
[STUD_Anonymised] ,
NULLIF(TRIM([STUD_Quarantine_Status] ) , '') AS [STUD_Quarantine_Status] ,
[STUD_KeycloakID] ,
NULLIF(TRIM([STUD_Preferred_Surname] ) , '') AS [STUD_Preferred_Surname] ,
[STUD_Exclude_From_Balancing] ,
NULLIF(TRIM([STUD_Corresp_Preference] ) , '') AS [STUD_Corresp_Preference] ,
NULLIF(TRIM([STUD_HESA_ID] ) , '') AS [STUD_HESA_ID] ,
NULLIF(TRIM([STUD_FEPUS_ID] ) , '') AS [STUD_FEPUS_ID] ,
NULLIF(TRIM([STUD_PEV_PIN] ) , '') AS [STUD_PEV_PIN] ,
NULLIF(TRIM([STUD_Photo_GUID_Filename] ) , '') AS [STUD_Photo_GUID_Filename]
INTO #students
FROM [NG].[dbo].[STUDstudent];
MERGE [people].[Person] AS [p]
USING #students AS [s]
ON [s].[STUD_ISN] = [p].[StudentDBID]
WHEN MATCHED
AND (
[s].[STUD_Surname] != [p].[Surname]
OR [s].[STUD_Preferred_Surname] != [p].[PreferredSurname]
OR [s].[STUD_Former_Surname] != [p].[FormerSurname]
OR [s].[STUD_Forename_1] != [p].[Forename]
OR [s].[STUD_Forename_2] != [p].[MiddleNames]
OR [s].[STUD_Known_As] != [p].[PreferredForename]
OR [s].[STUD_DoB] != [p].[DoB]
OR [s].[STUD_Gender] != [p].[Gender]
OR [s].[STUD_Gender_Identity] != [p].[GenderIdentity]
OR [s].[STUD_Title] != [p].[Title]
OR [s].[STUD_Deceased] != [p].[Deceased]
OR [s].[STUD_Date_of_Death] != [p].[DeceasedDate]
OR [s].[STUD_Is_Staff_ISN] != [p].[StaffDBID]
)
THEN UPDATE SET
[Surname] = [s].[STUD_Surname] ,
[PreferredSurname] = [s].[STUD_Preferred_Surname] ,
[FormerSurname] = [s].[STUD_Former_Surname],
[Forename] = [s].[STUD_Forename_1] ,
[MiddleNames] = [s].[STUD_Forename_2] ,
[PreferredForename] = [s].[STUD_Known_As],
[DoB] = [s].[STUD_DoB] ,
[Gender] = [s].[STUD_Gender] ,
[GenderIdentity] = [s].[STUD_Gender_Identity] ,
[Title] = [s].[STUD_Title] ,
[Deceased] = [s].[STUD_Deceased],
[StaffDBID] = [s].[STUD_Is_Staff_ISN]
WHEN NOT MATCHED
THEN INSERT (
[Surname] ,
[PreferredSurname] ,
[FormerSurname] ,
[Forename] ,
[MiddleNames] ,
[PreferredForename] ,
[DoB] ,
[Gender] ,
[GenderIdentity] ,
[Title] ,
[Deceased] ,
[DeceasedDate] ,
[StudentDBID] ,
[StaffDBID])
VALUES (
[s].[STUD_Surname] ,
[s].[STUD_Preferred_Surname] ,
[s].[STUD_Former_Surname] ,
[s].[STUD_Forename_1] ,
[s].[STUD_Forename_2] ,
[s].[STUD_Known_As] ,
[s].[STUD_DOB] ,
[s].[STUD_Gender] ,
[s].[STUD_Gender_Identity] ,
[s].[STUD_Title] ,
[s].[STUD_Deceased] ,
[s].[STUD_Date_of_Death] ,
[s].[STUD_ISN] ,
[s].[STuD_IS_Staff_ISN]
);
END
It could probably be made more efficient but, considering it's going to part of a nightly task and run when people aren't using the system, it handles ~35000 records in ~2 seconds so it's "efficient enough".
You can use the Coalesce Function available in SQL to achieve the desired result
It will return the first Non-Null value from the passed parameters
SELECT COALESCE( NULL ,'First Non Null','Alex')
COALESCE Keyword
Related
I have a "flat file" with structure as below:
machineCode,Key,Ip_Name_No,Share_Percent,Account_Name,Account_No
"ygh048GT",4767,534293748,"100.00","cderfgdsc Publishing International Ltd","160102040"
"xcd064HW",6380,65424090,"100.00","dascdfrgh snm skion","00090382478"
"000065AN",6402,65424090,"100.00","xcdertn,john sean","00090382478"
.....
The first row are the column headings. As can be seen, the fields are separated by a comma.
The requirement is to split the single string into separate fields.
This could be done by excel and then uploaded to a DB table using the data to columns option with comma as delimiter but the Account_Name field can contain commas within the values itself.
So, I came up with the below SQL. Question is, does this look correct ? Also, there must be some easier way to do this, any suggestions ?
WITH POS AS (
select
LOCATE_IN_STRING ( DATA , ',' , 2 ) - 1 AS TUNECODE_END ,
LOCATE_IN_STRING (DATA, ',' , LOCATE_IN_STRING ( DATA , ',' , 2 ) + 1) - 1 AS WORKKEY_END,
LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING (DATA, ',' , LOCATE_IN_STRING ( DATA , ',' , 2 ) + 1) + 1) ) - 1 AS IPNN_END,
LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING (DATA, ',' , LOCATE_IN_STRING ( DATA , ',' , 2 ) + 1) + 1) ) + 1) ) - 1 AS PERC_END,
CASE WHEN
SUBSTR ( DATA ,
(
LOCATE_IN_STRING ( DATA , ',' ,
(LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING (DATA, ',' , LOCATE_IN_STRING ( DATA , ',' , 2 ) + 1) + 1) ) + 1) ) + 1 ) ) + 1),
1) <> '"'
THEN
LOCATE_IN_STRING ( DATA , ',' ,
(LOCATE_IN_STRING ( DATA , ',' ,
(LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING (DATA, ',' , LOCATE_IN_STRING ( DATA , ',' , 2 ) + 1) + 1) ) + 1) ) + 1 ) ) + 1)) - 1
ELSE
LOCATE_IN_STRING ( DATA , ',' ,
(LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING( DATA , ',' , (LOCATE_IN_STRING (DATA, ',' , LOCATE_IN_STRING ( DATA , ',' , 2 ) + 1) + 1) ) + 1) ) + 1 ) ) - 1
END AS ACNAME_END,
RRN(P) ROWN
FROM PLDWRK P
) SELECT
CAST ( SUBSTR( DATA , 1, TUNECODE_END ) AS CHAR(25))AS MACHINECODE ,
CAST ( SUBSTR( DATA , TUNECODE_END + 2 , WORKKEY_END - (TUNECODE_END + 1) ) AS DEC(12,0)) AS KEY,
CAST(SUBSTR( DATA , WORKKEY_END + 2, IPNN_END - (WORKKEY_END + 1) ) AS DEC(12, 0 )) AS IP_NN,
CAST (SUBSTR( DATA, IPNN_END + 2, PERC_END - (IPNN_END + 1)) AS CHAR(8))AS PERCENTAGE,
CAST (SUBSTR( DATA, PERC_END + 2, ACNAME_END - (PERC_END + 1)) AS CHAR(100)) AS ACCOUNT_NAME,
CAST (SUBSTR( DATA, ACNAME_END + 2 ) AS CHAR(30)) as ACCOUNT_NUMBER
FROM PLDWRK P JOIN POS ON ROWN = RRN(P)
It doesn't work properly for the last row, since one token contains , inside...
select
t.str
, regexp_substr (t.str, '[^,]+', 1, 1) as tok1
, regexp_substr (t.str, '[^,]+', 1, 2) as tok2
---, ...
, regexp_substr (t.str, '[^,]+', 1, 6) as tok6
from
(
values
(1, 'machineCode,Key,Ip_Name_No,Share_Percent,Account_Name,Account_No')
, (2, '"ygh048GT",4767,534293748,"100.00","cderfgdsc Publishing International Ltd","160102040"')
, (3, '"xcd064HW",6380,65424090,"100.00","dascdfrgh snm skion","00090382478"')
, (4, '"000065AN",6402,65424090,"100.00","xcdertn,john sean","00090382478"')
) t (id, str)
order by t.id
STR
TOK1
TOK2
TOK6
machineCode,Key,Ip_Name_No,Share_Percent,Account_Name,Account_No
machineCode
Key
Account_No
"ygh048GT",4767,534293748,"100.00","cderfgdsc Publishing International Ltd","160102040"
"ygh048GT"
4767
"160102040"
"xcd064HW",6380,65424090,"100.00","dascdfrgh snm skion","00090382478"
"xcd064HW"
6380
"00090382478"
"000065AN",6402,65424090,"100.00","xcdertn,john sean","00090382478"
"000065AN"
6402
john sean"
Lots of things are easier than raw SQL...
Why not simply use Copy From Import File (CPYFRMIMPF) that's what it's designed for.
CPYFRMIMPF FROMSTMF('/inbound/somedata.csv') TOFILE(MYLIB/MYTABLE) MBROPT(*REPLACE) RCDDLM(*CRLF) DTAFMT(*DLM) STRDLM(*DBLQUOTE) RMVCOLNAM(*YES)
You'll have to transfer the stream data into the IFS (where it really belongs) instead of a DB table.
IBM's Access Client Solutions (ACS) includes data transfer functionality that can understand .CSV files. This can be automated and can in fact run on either a PC or the IBM i itself.
Another great option would be an RPG program, back in 2008 Scott Klement wrote a CSV parser in RPG. He's since enhanced it to make it easier to use by taking advantage of RPG's DATA-INTO op-code.
Lastly it's 2023...node.js, PHP, Python are all available on the IBM i and all of them have libraries/packages to handle CSV and write to a DB table.
What should I check why Oracle server takes more then 20 sec to return UNIQUE constraint violation error for specific data?
One of our processes is processing over 30000 data one day with multi process and some time gets UNIQUE constraint violation error in 1 sec
but it takes more then 20 sec to return UNIQUE constraint violation error for specific data.
Query is same as below. (Modified only table name)
MERGE
INTO TableA S
USING (
SELECT NVL(:sccm_cd , ' ') SCCM_CD
, NVL(:oder_dt , ' ') ODER_DT
, NVL(:mrkt_dstn_cd, ' ') MRKT_DSTN_CD
, NVL(:oder_no , ' ') ODER_NO
, NVL(:cncd_unpr , 0) CNCD_UNPR
, B.SLBY_FEE_GRD_CD
, B.ACCT_MNGR_EMPL_NO
, C.AO_FEE_GRD_CD
FROM DUAL A
, TableB B
, TableC C
WHERE 1 = 1
AND B.SCCM_CD = :sccm_cd
AND B.ACNO = :acno
AND C.SCCM_CD(+) = B.SCCM_CD
AND C.EMPL_NO(+) = B.ACCT_MNGR_EMPL_NO
) T
ON ( S.sccm_cd = T.sccm_cd
AND S.oder_dt = T.oder_dt
AND S.mrkt_dstn_cd = T.mrkt_dstn_cd
AND S.oder_no = T.oder_no
AND S.cncd_unpr = T.cncd_unpr
)
WHEN MATCHED THEN
UPDATE
SET S.cncd_qty = S.cncd_qty + NVL(:cncd_qty ,0)
, S.slby_fee = S.slby_fee + NVL(:slby_fee ,0)
, S.slby_fee_srtx = S.slby_fee_srtx + NVL(:slby_fee_srtx,0)
, S.idx_fee_amt = S.idx_fee_amt + NVL(:idx_fee_amt ,0)
, S.cltr_fee = S.cltr_fee + NVL(:cltr_fee ,0)
, S.trtx = S.trtx + NVL(:trtx ,0)
, S.otc_fee = S.otc_fee + NVL(:otc_fee ,0)
, S.wht_fee = S.wht_fee + NVL(:wht_fee ,0)
WHEN NOT MATCHED THEN
INSERT (
sccm_cd
, oder_dt
, mrkt_dstn_cd
, oder_no
, cncd_unpr
, acno
, item_cd
, slby_dstn_cd
, md_dstn_cd
, cncd_qty
, stlm_dt
, trtx_txtn_dstn_cd
, proc_cmpl_dstn_cd
, item_dstn_cd
, slby_fee_grd_cd
, slby_fee
, slby_fee_srtx
, idx_fee_amt
, cltr_fee
, trtx
, wht_fee
, otc_fee
, acct_mngr_empl_no
, ao_fee_grd_cd
)
VALUES
( T.sccm_cd
, T.oder_dt
, T.mrkt_dstn_cd
, T.oder_no
, T.cncd_unpr
, :acno
, :item_cd
, :slby_dstn_cd
, :md_dstn_cd
, NVL(:cncd_qty ,0)
, DECODE(:mrkt_dstn_cd, 'TN', T.oder_dt, :stlm_dt)
, :trtx_txtn_dstn_cd
, '0'
, :item_dstn_cd
, NVL(:slby_fee_grd_cd, T.SLBY_FEE_GRD_CD)
, NVL(:slby_fee ,0)
, NVL(:slby_fee_srtx ,0)
, NVL(:idx_fee_amt ,0)
, NVL(:cltr_fee ,0)
, NVL(:trtx ,0)
, NVL(:wht_fee , 0)
, NVL(:otc_fee , 0)
, T.acct_mngr_empl_no
, T.ao_fee_grd_cd
)
There could be multiple reasons for it. I will list here some of the possible causes for this behavior.
Concurrency issue
Your insert might be waiting for other operations, like other inserts or updated or deletions.
Network issues
It is possible that for some reason your network is overwhelmed with requests or, if the server is remote, this could be an internet speed issue as well.
Server load
The server might be overwhelmed with lots of jobs to do.
Slow query
It's also possible that the select you use in your insert command is very slow. It would make sense to test its speed. Also, it would make sense to test insert speed as well.
I have a situation where an order can contain multiple license purchases - and if the order does contain multiple licenses, I want to display the license descriptions in a single cell with the values separated by commas. If we were on SQL 2017, I could use STRING_AGG but we are on SQL 2016 so I am trying the tried and true STUFF / FOR XML Path method.
From the screenshot below, Customer 4341073 had two license purchases on Order ID 18519173:
When I add the STUFF / FOR XML Path to the T-SQL, I am not able to achieve the desired result of showing the license description in the same record - each license still has it's own row.
SELECT x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate ,
STUFF ( (
SELECT ',' + lt.description
FROM dbo.LicenseTypes AS lt
--INNER JOIN #XMLPATH ON lt.id = x.OrderLicenseTypeID
WHERE lt.id = x.OrderLicenseTypeID
--GROUP BY ',' + lt.description
FOR XML PATH ( '' )
) , 1 , 1 , '' ) AS Licenses
FROM #XMLPATH AS x
--GROUP BY x.CustomerID ,
-- x.ATOLicenseTypeID ,
-- x.ATOLicense ,
-- x.AuthorizationBeginDate ,
-- x.AuthorizationEndDate ,
-- x.OrderID ,
-- x.OrderDate ,
-- x.OrderLicenseTypeID;
I've tried different ways to join the sub-query to the outer query and added and removed GROUP BY to achieve the desired result but nothing is working for me.
Any suggestions on where I am going wrong with this query?
Sample dataset:
DROP TABLE IF EXISTS #XMLPATH;
CREATE TABLE #XMLPATH
(
CustomerID INT ,
ATOLicenseTypeID INT ,
ATOLicense VARCHAR (500) ,
AuthorizationBeginDate DATE ,
AuthorizationEndDate DATE ,
OrderID INT ,
OrderDate DATETIME ,
OrderLicenseTypeID INT
);
INSERT INTO #XMLPATH
VALUES ( 4341073, 52, 'Temporary Resident Fishing', N'2019-01-07T00:00:00', N'2019-01-07T00:00:00', 18519136, N'2019-01-07T12:01:55.317', 2141 ) ,
( 4341073, 52, 'Temporary Resident Fishing', N'2019-01-07T00:00:00', N'2019-01-07T00:00:00', 18519173, N'2019-01-07T12:34:13.107', 204 ) ,
( 4341073, 52, 'Temporary Resident Fishing', N'2019-01-07T00:00:00', N'2019-01-07T00:00:00', 18519173, N'2019-01-07T12:34:13.107', 2141 );
SELECT * FROM #XMLPATH;
SELECT x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate ,
STUFF ( (
SELECT ',' + lt.description
FROM dbo.LicenseTypes AS lt
--INNER JOIN #XMLPATH ON lt.id = x.OrderLicenseTypeID
WHERE lt.id = x.OrderLicenseTypeID
--GROUP BY ',' + lt.description
FOR XML PATH ( '' )
) , 1 , 1 , '' ) AS Licenses
FROM #XMLPATH AS x
GROUP BY x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate ,
x.OrderLicenseTypeID;
In order to get all rows of one OrderID as one result-row, you must not include the separating information (the OrderLicenseTypeID) into the GROUP BY. But then you have the issue you've encountered: You cannot use this ID within your FOR XML construct.
The trick is (as your out-commented trials show), to add the source table to the sub-select and filter there with a grouped column. But you have to use different aliases to deal with them as two different sets. Try this:
(I had to add one more temp table to test this...)
SELECT x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate ,
STUFF ( (
SELECT ',' + lt.description
FROM #XMLPATH x2
INNER JOIN #LicenseTypes AS lt ON lt.id=x2.OrderLicenseTypeID
WHERE x2.OrderID = x.OrderID --you might need to add more columns here....
--in most cases we want to add an ORDER BY
FOR XML PATH ( '' )
) , 1 , 1 , '' ) AS Licenses
FROM #XMLPATH AS x
GROUP BY x.CustomerID ,
x.ATOLicenseTypeID ,
x.ATOLicense ,
x.AuthorizationBeginDate ,
x.AuthorizationEndDate ,
x.OrderID ,
x.OrderDate;
Btw: Starting with v2017 there is STRING_AGG(), which makes this much easier...
If you could please help with this. The code generates an error:
Msg 8156, Level 16, State 1, Line 236
The column 'Classification_Value_Id' was specified multiple times for 'piv1'.
I am doing this on SQL Server. The steps for the code is as follows:
1. Unpivot the data from the source table DB.[dbo].[Classification] into one column
2. Join this unpivoted data to a table called DB.dbo.[Classification_Value] to return/add the column 'cv.Classification_Name' to the data set
3. Pivot this dataset (This is the part returning the error)
CODE:
SELECT
activityCode
, actjvPartnerRef
, actMonth
, actSalesChannel
, addCBPCharge
, agentId
, appType
, areaCode
--SELECT
--polRef,[Arrangement_Id],UnpivotedData.Classification_Value_Id,UnpivotedData.Classification_Scheme_Id,ColValues, ColNames,cv.Classification_Name
FROM
(
SELECT top 10
[polRef]
, [Arrangement_Id]
, [Classification_Scheme_Id]
, [Classification_Value_Id]
-- ,[Arrangement_Classification_Type_Id]
-- ,[Effective_TimeStamp]
-- ,[End_date]
, CAST((ISNULL([character_measure],'')) AS NVARCHAR(MAX)) AS character_measure
, CAST((ISNULL([datetime_measure],'')) AS NVARCHAR(MAX)) AS datetime_measure
, CAST([decimal_measure] AS NVARCHAR(MAX)) AS decimal_measure
, CAST((ISNULL([integer_measure],'')) AS NVARCHAR(MAX)) AS integer_measure
, CAST((ISNULL([logical_measure],'')) AS NVARCHAR(MAX)) AS logical_measure
, CAST((ISNULL([charmax_measure],'')) AS NVARCHAR(MAX)) AS charmax_measure
, CAST((ISNULL([long_measure],'')) AS NVARCHAR(MAX)) AS long_measure
FROM DB.[dbo].[Classification]
) AS SrcDataConverted
UNPIVOT
(
ColValues FOR ColNames IN
(
character_measure
, datetime_measure
, decimal_measure
, integer_measure
, logical_measure
, charmax_measure
, long_measure
)
) AS UnpivotedData
LEFT JOIN DB.dbo.[Classification_Value] cv
ON cv.[Classification_Scheme_Id] = UnpivotedData.[Classification_Scheme_Id]
AND cv.Classification_Value_Id = UnpivotedData.Classification_Value_Id
PIVOT
(MAX(ColValues) for Classification_Name in (
activityCode
, actjvPartnerRef
, actMonth
, actSalesChannel
, addCBPCharge
, agentId
, appType
, areaCode
)) AS piv1;
Any help would be much appreciated
Thank you
StuarLC:
An additional derived table needs to wrap the results of the UNPIVOT before commencing the re-PIVOT, as the join introduces a duplicated Classification_Value_Id and Classification_Scheme_id, which is needed for the join.
select
activityCode
, actjvPartnerRef
, actMonth
, actSalesChannel
, addCBPCharge
, agentId
, appType
, areaCode
from (
SELECT polRef
, [Arrangement_Id]
, UnpivotedData.Classification_Value_Id
, UnpivotedData.Classification_Scheme_Id
, ColValues
, ColNames
, Classification_Name
FROM (
SELECT [polRef]
, [Arrangement_Id]
, [Classification_Scheme_Id]
, [Classification_Value_Id]
, CAST((ISNULL([character_measure],'')) AS NVARCHAR(MAX)) AS character_measure
, CAST((ISNULL([datetime_measure],'')) AS NVARCHAR(MAX)) AS datetime_measure
, CAST([decimal_measure] AS NVARCHAR(MAX)) AS decimal_measure
, CAST((ISNULL([integer_measure],'')) AS NVARCHAR(MAX)) AS integer_measure
, CAST((ISNULL([logical_measure],'')) AS NVARCHAR(MAX)) AS logical_measure
, CAST((ISNULL([charmax_measure],'')) AS NVARCHAR(MAX)) AS charmax_measure
, CAST((ISNULL([long_measure],'')) AS NVARCHAR(MAX)) AS long_measure
FROM DB.[dbo].[Classification]
) AS SrcDataConverted
UNPIVOT
(
ColValues FOR ColNames IN
(
character_measure
, datetime_measure
, decimal_measure
, integer_measure
, logical_measure
, charmax_measure
, long_measure
)
) AS UnpivotedData
LEFT JOIN
DB.dbo.[Classification_Value] cv
ON cv.[Classification_Scheme_Id] = UnpivotedData.[Classification_Scheme_Id]
AND cv.Classification_Value_Id = UnpivotedData.Classification_Value_Id
) as src
PIVOT
(
MAX(ColValues) for Classification_Name in (
activityCode
, actjvPartnerRef
, actMonth
, actSalesChannel
, addCBPCharge
, agentId
, appType
, areaCode
)
) AS piv1;
Why does this query return "Incorrect syntax near ')' " on the last parenthesis when changing from openquery to a subquery on a local database?
You can see the commented lines where the openquery started before (and worked)
select right(rtrim(a.vouchernumber),9) as voucherkey
, a.vouchernumber
, vendorkey
, rtrim(invoicenumber) as invoicenumber
, invoicedate
, duedate
, documenttype
, PostDate
, qty
, amt =
CASE
WHEN documenttype = 'D' THEN -(amt)
WHEN b.transactiontype = 'V' THEN -(amt)
ELSE
amt
End
, extendedlineamt
, intercompanyid
, acct
, NaturalAcct
, c.segmentdescription as NaturalAcctDesc
, Dept
, f.segmentdescription as DeptDesc
, Site
, d.segmentdescription as SiteDesc
, Project
, e.segmentdescription as ProjDesc
, b.*
, rtrim(vendorname) as vendorname
, rtrim(vendoraddress1) as vendoraddress1
, vendoraddress2
, vendoraddress3
, vendorcity
, rtrim(vendorstate) as vendorstate
, rtrim(vendorzipcode) as vendorzipcode
, rtrim(vendoraddress1) + ' ' + rtrim(vendoraddress2) +' ' + rtrim(vendorcity) + ', ' + rtrim(vendorstate) as VendorAddress
, Dept + '.' + Site + '.' + Project as ProjectId
--from openquery (LinkedServer,
-- 'select a.vouchernumber
from (
select a.vouchernumber
, a.vendorkey
, a.invoicenumber
, a.invoicedate
, a.duedate
, a.documenttype
, a.recdate as PostDate
, b.qty
, b.amt
, b.extendedlineamt
, b.intercompanyid
, b.acct
, Substring(Acct, 1, 4) as NaturalAcct
, Substring(Acct,8, 3 ) as Site
, Substring(Acct,11, 4 ) as Project
, Substring(Acct,5,3) as Dept
, v.vendorname
, v.vendoraddress1
, v.vendoraddress2
, v.vendoraddress3
, v.vendorcity
, v.vendorstate
, v.vendorzipcode
from aphdr a
join aplin b
on a.vouchernumber = b.vouchernumber
left join apvend v
on a.vendorkey = v.vendorkey
where a.recdate between '2011-01-01' and '2012-10-02'
and cast(Substring(Acct,11, 4 ) as varchar(4)) like 'VIR'
and left(a.vouchernumber,1) <> 'G'
and Company = 'ASSFD'
)
You need to alias your subquery.
put a alias name at the end of your query.
You need to give the subquery an alias (or name).
here i've just added tbl1 to the very end
select right(rtrim(a.vouchernumber),9) as voucherkey
, a.vouchernumber
, vendorkey
, rtrim(invoicenumber) as invoicenumber
, invoicedate
, duedate
, documenttype
, PostDate
, qty
, amt =
CASE
WHEN documenttype = 'D' THEN -(amt)
WHEN b.transactiontype = 'V' THEN -(amt)
ELSE
amt
End
, extendedlineamt
, intercompanyid
, acct
, NaturalAcct
, c.segmentdescription as NaturalAcctDesc
, Dept
, f.segmentdescription as DeptDesc
, Site
, d.segmentdescription as SiteDesc
, Project
, e.segmentdescription as ProjDesc
, b.*
, rtrim(vendorname) as vendorname
, rtrim(vendoraddress1) as vendoraddress1
, vendoraddress2
, vendoraddress3
, vendorcity
, rtrim(vendorstate) as vendorstate
, rtrim(vendorzipcode) as vendorzipcode
, rtrim(vendoraddress1) + ' ' + rtrim(vendoraddress2) +' ' + rtrim(vendorcity) + ', ' + rtrim(vendorstate) as VendorAddress
, Dept + '.' + Site + '.' + Project as ProjectId
--from openquery (LinkedServer,
-- 'select a.vouchernumber
from (
select a.vouchernumber
, a.vendorkey
, a.invoicenumber
, a.invoicedate
, a.duedate
, a.documenttype
, a.recdate as PostDate
, b.qty
, b.amt
, b.extendedlineamt
, b.intercompanyid
, b.acct
, Substring(Acct, 1, 4) as NaturalAcct
, Substring(Acct,8, 3 ) as Site
, Substring(Acct,11, 4 ) as Project
, Substring(Acct,5,3) as Dept
, v.vendorname
, v.vendoraddress1
, v.vendoraddress2
, v.vendoraddress3
, v.vendorcity
, v.vendorstate
, v.vendorzipcode
from aphdr a
join aplin b
on a.vouchernumber = b.vouchernumber
left join apvend v
on a.vendorkey = v.vendorkey
where a.recdate between '2011-01-01' and '2012-10-02'
and cast(Substring(Acct,11, 4 ) as varchar(4)) like 'VIR'
and left(a.vouchernumber,1) <> 'G'
and Company = 'ASSFD'
) tbl1