SQL to catch incremental entries from a view - sql

I have a historic table that won't be updated with new inserts, and a have a view that everyday will be update with new inserts. So I need to know if my SQL is correct.
This SQL needs to get all entries that inside in FATO_Proposta_Planilha table (Table 1)
and to add the entries not similar that are in the FATO_Proposta_View table (Table 2).
So, this SQL must have all entries from Table 1 more all entries from Table 2 that are not repeated in the Table 1. Can you give a opinion about this SQL, please?
SELECT vw.[DescPac] [PA]
,vw.[DescRegional] [Regional]
,vw.[DescSuperintendencia] [Superintendencia]
,vw.[NUM_CPF_CNPJ] [Documento_Numero]
,pla.[Nome] [Nome]
,pla.[Produto] [Produto]
,pla.[Modalidade] [Modalidade]
,vw.[NUM_CONTRATO_CREDITO] [Contrato]
,vw.[DESC_FINALIDADE_OPCRED] [Finalidade]
,vw.[DATA_OPERACAO] [Data_operacao]
,pla.[Data_mov_entrada] [Data_mov_entrada]
,vw.[DATA_VENC_OPCRED] [Data_vencimento]
,vw.[VALOR_CONTRATO_OPCRED] [Valor_contrato]
,pla.[Processo_Lecon] [Processo_Lecon]
,CASE WHEN ISNULL(pla.Origem, '') = ''
THEN 'Esteira Convencional'
ELSE pla.Origem
END [Origem]
FROM Proposta_View vw
LEFT JOIN FATO_Proposta_Planilha pla
ON vw.NUM_CONTRATO_CREDITO = pla.Contrato
UNION
SELECT [PA] [PA]
,[Regional] [Regional]
,[Superintendencia] [Superintendencia]
,[Documento_Numero] [Documento_Numero]
,[Nome] [Nome]
,[Produto] [Produto]
,[Modalidade] [Modalidade]
,[Contrato] [Contrato]
,[Finalidade] [Finalidade]
,[Data_operacao] [Data_operacao]
,[Data_mov_entrada] [Data_mov_entrada]
,[Data_vencimento] [Data_vencimento]
,[Valor_contrato] [Valor_contrato]
,[Processo_Lecon] [Processo_Lecon]
,CASE WHEN ISNULL(Origem, '') = ''
THEN 'Esteira Convencional'
ELSE Origem
END [Origem]

If you are only inserting rows through the view you can add an extra column with a DEFAULT value to distinguish the old rows from the new ones.
For example if you have a table t as:
create table t (a int primary key not null);
insert into t (a) values (123), (456);
You can add the extra column as:
alter table t add is_new int default 1;
update t set is_new = 0;
create view v as select a from t;
Then each insert through the view won't see that new column and will insert with value 1.
insert into v (a) values (789), (444);
Then it's easy to find the new rows:
select * from t where is_new = 1;
Result:
a is_new
---- ------
444 1
789 1
Se running example at db<>fiddle.

Related

Merge sql throws Unique constraint violation error

I have below two table for which when i query table TEST_RUA:
select CLASS, ID_LL, ID_UU, TKR from TEST_RUA where ID_UU= 'GV9999B12M0'
it returns:
CLASS ID_LL ID_UU TKR
Bond (null) GV9999B12M0 WIB
When i query table TEST_RUA_MER:
select CLASS, ID_LL, ID_UU, TKR from TEST_RUA_MER where ID_UU= 'GV9999B12M0'
it returns:
CLASS ID_LL ID_UU TKR
Bond (null) GV9999B12M0 WIB
You can see both the values are same for table where ID_UU= 'GV9999B12M0'. The table TEST_RUA_MER has unique index on columns ID_LL, ID_UU, TKR.
Now i have below merge query which throws error as ORA-00001: unique constraint violated and i dont understand how can i avoid this error as both the table values are same then in this case this merge query should try to update and not to insert in table TEST_RUA_MER .
merge into TEST_RUA_MER h using (
select distinct r.CLASS, r.ID_LL, r.ID_UU, r.TKR from TEST_RUA r ) s
on (s.ID_LL=h.ID_LL and s.ID_UU=h.ID_UU and s.TKR=h.TKR) when matched then
update set h.CLASS = s.CLASS, h.ID_LL = s.ID_LL, h.ID_UU = s.ID_UU, h.TKR = s.TKR
when not matched then insert values (s.CLASS, s.ID_LL, s.ID_UU, s.TKR);
Looks like NULL causes problems; it isn't "equal" to anything, so this:
on (s.ID_LL=h.ID_LL
fails.
Try with
on (nvl(s.ID_LL, -1) = nvl(h.ID_LL, -1)
(depending on what ID_LL column's datatype is; I presumed it is a number).
Yes, As mentioned in the other answer also, cause of the error is s.ID_LL=h.ID_LL. You can update it as
( s.ID_LL=h.ID_LL OR (s.ID_LL is null and h.ID_LL is null) )
OR
( s.ID_LL=h.ID_LL OR coalesce(s.ID_LL, h.ID_LL) is null )

Using result of select inside alter table statement in postgres

I have a postgres table defined as below:
CREATE TABLE public.Table_1
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1
START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 )
)
Due to data migration, the id column is messed up and the value for id that is being generated on INSERT is not unique. Hence, I need to reset the id column as below
SELECT MAX(id) + 1 From Table_1;
ALTER TABLE Table_1 ALTER COLUMN id RESTART WITH 935074;
Right now I run the first query to get the Max(id) + 1 value and then I need to substitute it in the ALTER query.
Is there a way to store the result of SELECT and just use the variable inside ALTER statement?
Here is one way to do it:
select setval(pg_get_serial_sequence('Table_1', 'id'), coalesce(max(id),0) + 1, false)
from Table_1;
Rationale:
pg_get_serial_sequence() returns the name of the sequence for the given table and column
set_val() can be used to reset the sequence
this can be wrapped in a select that gives you the current maximum value of id in the table (or 1 if the table is empty)

Why is this SQL function working differently within an insert statement?

I'm getting a primary key violation in the column 'voorwerpnummer'. The duplicate value is 1, but the function should never give the same number twice. When I try testing the function without anything in the 'Voorwerp' table, it returns 1, and if I test it with one row in the 'Voorwerp' table it returns 2; like it should. Why is it returning 1 twice within this insert query? I'm using SQL Server 2016.
-- Empty tables
DELETE
FROM Bestand
DELETE
FROM Voorwerp_in_rubriek
DELETE
FROM Voorwerp
-- Delete function if exists
IF EXISTS (SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[GetVoorwerpnummer]')
AND type IN ( N'FN', N'IF', N'TF', N'FS', N'FT' ))
DROP FUNCTION [dbo].[GetVoorwerpnummer]
GO
-- Returns the next item number
CREATE FUNCTION dbo.GetVoorwerpnummer()
RETURNS INTEGER
AS BEGIN
DECLARE #Highest INTEGER
-- Find the highest number
SET #Highest = (SELECT TOP 1 voorwerpnummer FROM EenmaalAndermaal.dbo.Voorwerp ORDER BY voorwerpnummer DESC)
-- Take 0 if there is none
SET #Highest = ISNULL(#Highest, 0)
-- Add 1
SET #Highest = (#Highest + 1);
RETURN #Highest
END
GO
INSERT INTO EenmaalAndermaal.dbo.Voorwerp (looptijd, looptijdbegin, startprijs, verzendkosten, verkoopprijs, beschrijving, betalingsinstructie, betalingswijzenaam, landnaam, plaatsnaam, titel, verzendinstructies, voorwerpnummer, wel_niet_indicator, verkoper, koper)
SELECT
10 AS looptijd,
GETDATE() AS looptijdbegin,
LEFT(Prijs,8) AS startprijs,
'1,20' AS verzendkosten,
NULL AS verkoopprijs,
'Beschrijving van product' AS beschrijving,
NULL AS betalingsinstructie,
'paypal' AS betalingswijzenaam,
LEFT(land,40) AS landnaam,
LEFT(Locatie,40) AS plaatsnaam,
LEFT(Titel,40) AS titel,
NULL AS verzendinstructies,
dbo.GetVoorwerpnummer() AS voorwerpnummer,
0 AS wel_niet_indicator,
LEFT(Verkoper, 40) AS Verkoper,
NULL AS koper
FROM EBAY.dbo.Items
That is because SQL works set-based, not row-based.
The SELECT statement is executed first. That results in a data set, all with the same voorwerpnummer (since nothing has been added to EenmaalAndermaal.dbo.Voorwerp yet). You can test this by executing just the SELECT statement. That entire set is than INSERTed into your target table, so a subsequent call to GetVoorwerpnummer() will result in a higher voorwerpnummer (obviously if the INSERT would be successful, which it won't be if voorwerpnummer is the PK).
To correct it, I would do the following:
-- Execute function once to fill variable, so it isn't needlessly executed for every row
DECLARE #Voorwerp INT = dbo.GetVoorwerpnummer();
INSERT INTO EenmaalAndermaal.dbo.Voorwerp (looptijd, looptijdbegin, startprijs, verzendkosten, verkoopprijs, beschrijving, betalingsinstructie, betalingswijzenaam, landnaam, plaatsnaam, titel, verzendinstructies, voorwerpnummer, wel_niet_indicator, verkoper, koper)
SELECT
10 AS looptijd,
GETDATE() AS looptijdbegin,
LEFT(Prijs,8) AS startprijs,
'1,20' AS verzendkosten,
NULL AS verkoopprijs,
'Beschrijving van product' AS beschrijving,
NULL AS betalingsinstructie,
'paypal' AS betalingswijzenaam,
LEFT(land,40) AS landnaam,
LEFT(Locatie,40) AS plaatsnaam,
LEFT(Titel,40) AS titel,
NULL AS verzendinstructies,
COALESCE(#Voorwerp, 0) + ROW_NUMBER() OVER(ORDER BY id /*Choose a logical, preferably unique column here*/) AS voorwerpnummer,
0 AS wel_niet_indicator,
LEFT(Verkoper, 40) AS Verkoper,
NULL AS koper
FROM EBAY.dbo.Items
As Stilgar below commented, it is better to define the PK as an IDENTITY attribute.
Also, personally (I'm not sure if it's 'official' best practices), I prefer to keep my Primary Key columns as first columns of the table.
Mostly likely a matter of not committing the INSERT; until committed, the data doesn't "really" exist.
The following works for me:
CREATE TABLE dbo.PK_Test
(
PK INT
,TXT VARCHAR(0032)
);
BEGIN TRANSACTION
INSERT INTO dbo.PK_Test VALUES(05, 'First');
COMMIT TRANSACTION;
CREATE FUNCTION dbo.F_PK_Test()
RETURNS INTEGER
AS BEGIN
DECLARE #Highest INTEGER
-- Find the highest number
SET #Highest = (SELECT TOP 1 PK FROM PK_Test ORDER BY PK DESC)
-- Take 0 if there is none
SET #Highest = ISNULL(#Highest, 0)
-- Add 1
SET #Highest = (#Highest + 1);
RETURN #Highest
END;
SELECT * FROM dbo.PK_Test;
BEGIN TRANSACTION
INSERT INTO Adhoc_Area.dbo.PK_Test (PK, Txt) VALUES (Adhoc_Area.dbo.F_PK_Test(), Adhoc_Area.dbo.F_PK_Test());
INSERT INTO Adhoc_Area.dbo.PK_Test (PK, Txt) VALUES (Adhoc_Area.dbo.F_PK_Test(), Adhoc_Area.dbo.F_PK_Test());
COMMIT TRANSACTION;
SELECT * FROM dbo.PK_Test;

How to use identity column value of target table to insert in another column of same table

Pardon the title of question if it is confusing. Table E is source and Table S is target. S.SEA_ID is identity column (int, not null). Whenever those join conditions are NOT MATCHED AND E.ACCT_NUM IS NULL, I want to insert the value 'EY|' + CAST(S.SEA_ID AS VARCHAR) in column S.ACCT_NUM. But I am getting the error
The multi-part identifier "S.SEA_ID" could not be bound.
Is it possible to insert identity column value of target table to populate another column of the same target table? If it is not possible in below MERGE statement, what are my other options?
MERGE INTO STG_EXTERNAL_ACCT S
USING ##EYDUPLICATES E
ON E.COUNTERPARTY_NAME = S.COUNTERPARTY_NAME
AND E.COUNTERPARTY_ADDRESS = S.COUNTERPARTY_ADDRESS
AND E.COUNTERPARTY_STATE = S.COUNTERPARTY_STATE
AND E.COUNTERPARTY_COUNTRY = S.COUNTERPARTY_COUNTRY
AND E.COUNTERPARTY_CITY = S.COUNTERPARTY_CITY
WHEN NOT MATCHED AND ISNULL(E.ACCT_NUM,'')=''
THEN INSERT (ACCT_NUM, COUNTERPARTY_NAME, COUNTERPARTY_ADDRESS,
COUNTERPARTY_STATE, COUNTERPARTY_COUNTRY, COUNTERPARTY_CITY)
VALUES ('EY|' + CAST(S.SEA_ID AS VARCHAR),E.COUNTERPARTY_NAME,
E.COUNTERPARTY_ADDRESS, E.COUNTERPARTY_STATE,
E.COUNTERPARTY_COUNTRY, E.COUNTERPARTY_CITY);
I suggest that change ACCT_NUM Column as calculated column.
ALTER Table STG_EXTERNAL_ACCT ALTER Column ACCT_NUM as 'EY|'+CAST(S.SEA_ID AS VARCHAR)
Also you can set this column in trigger.
Create Trigger TiggerName ON STG_EXTERNAL_ACCT
INSTEAD OF INSERT
AS Begin
Insert Into STG_EXTERNAL_ACCT (ACCT_Num, ...)
Select 'EY|'+CAST(S.SEA_ID AS VARCHAR), ...
From Inserted
End

Find the last value in a "rolled-over" sequence with a stored procedure?

Suppose I had a set of alpha-character identifiers of a set length, e.g. always five letters, and they are assigned in such a way that they are always incremented sequentially (GGGGZ --> GGGHA, etc.). Now, if I get to ZZZZZ, since the length is fixed, I must "roll over" to AAAAA. I might have a contiguous block from ZZZAA through AAAAM. I want to write a sproc that will give me the "next" identifier, in this case AAAAN.
If I didn't have this "rolling over" issue, of course, I'd just ORDER BY DESC and grab the top result. But I'm at a bit of a loss now -- and it doesn't help at all that SQL is not my strongest language.
If I have to I can move this to my C# calling code, but a sproc would be a better fit.
ETA: I would like to avoid changing the schema (new column or new table); I'd rather just be able to "figure it out". I might even prefer to do it brute force (e.g. start at the lowest value and increment until I find a "hole"), even though that could get expensive. If you have an answer that does not modify the schema, it'd be a better solution for my needs.
Here's code that I think will give you your Next value. I created 3 functions. The table is just my simulation of the table.column with your alpha ids (I used MyTable.AlphaID). I assume that it's as you implied and there is one contiguous block of five-character uppercase alphabetic strings (AlphaID):
IF OBJECT_ID('dbo.MyTable','U') IS NOT NULL
DROP TABLE dbo.MyTable
GO
CREATE TABLE dbo.MyTable (AlphaID char(5) PRIMARY KEY)
GO
-- Play with different population scenarios for testing
INSERT dbo.MyTable VALUES ('ZZZZY')
INSERT dbo.MyTable VALUES ('ZZZZZ')
INSERT dbo.MyTable VALUES ('AAAAA')
INSERT dbo.MyTable VALUES ('AAAAB')
GO
IF OBJECT_ID('dbo.ConvertAlphaIDToInt','FN') IS NOT NULL
DROP FUNCTION dbo.ConvertAlphaIDToInt
GO
CREATE FUNCTION dbo.ConvertAlphaIDToInt (#AlphaID char(5))
RETURNS int
AS
BEGIN
RETURN 1+ ASCII(SUBSTRING(#AlphaID,5,1))-65
+ ((ASCII(SUBSTRING(#AlphaID,4,1))-65) * 26)
+ ((ASCII(SUBSTRING(#AlphaID,3,1))-65) * POWER(26,2))
+ ((ASCII(SUBSTRING(#AlphaID,2,1))-65) * POWER(26,3))
+ ((ASCII(SUBSTRING(#AlphaID,1,1))-65) * POWER(26,4))
END
GO
IF OBJECT_ID('dbo.ConvertIntToAlphaID','FN') IS NOT NULL
DROP FUNCTION dbo.ConvertIntToAlphaID
GO
CREATE FUNCTION dbo.ConvertIntToAlphaID (#ID int)
RETURNS char(5)
AS
BEGIN
RETURN CHAR((#ID-1) / POWER(26,4) + 65)
+ CHAR ((#ID-1) % POWER(26,4) / POWER(26,3) + 65)
+ CHAR ((#ID-1) % POWER(26,3) / POWER(26,2) + 65)
+ CHAR ((#ID-1) % POWER(26,2) / 26 + 65)
+ CHAR ((#ID-1) % 26 + 65)
END
GO
IF OBJECT_ID('dbo.GetNextAlphaID','FN') IS NOT NULL
DROP FUNCTION dbo.GetNextAlphaID
GO
CREATE FUNCTION dbo.GetNextAlphaID ()
RETURNS char(5)
AS
BEGIN
DECLARE #MaxID char(5), #ReturnVal char(5)
SELECT #MaxID = MAX(AlphaID) FROM dbo.MyTable
IF #MaxID < 'ZZZZZ'
RETURN dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(#MaxID)+1)
IF #MaxID IS NULL
RETURN 'AAAAA'
SELECT #MaxID = MAX(AlphaID)
FROM dbo.MyTable
WHERE AlphaID < dbo.ConvertIntToAlphaID((SELECT COUNT(*) FROM dbo.MyTable))
IF #MaxID IS NULL
RETURN 'AAAAA'
RETURN dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(#MaxID)+1)
END
GO
SELECT * FROM dbo.MyTable ORDER BY dbo.ConvertAlphaIDToInt(AlphaID)
GO
SELECT dbo.GetNextAlphaID () AS 'NextAlphaID'
By the way, if you don't want to assume contiguity, you can do as you suggested and (if there's a 'ZZZZZ' row) use the first gap in the sequence. Replace the last function with this:
IF OBJECT_ID('dbo.GetNextAlphaID_2','FN') IS NOT NULL
DROP FUNCTION dbo.GetNextAlphaID_2
GO
CREATE FUNCTION dbo.GetNextAlphaID_2 ()
RETURNS char(5)
AS
BEGIN
DECLARE #MaxID char(5), #ReturnVal char(5)
SELECT #MaxID = MAX(AlphaID) FROM dbo.MyTable
IF #MaxID < 'ZZZZZ'
RETURN dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(#MaxID)+1)
IF #MaxID IS NULL
RETURN 'AAAAA'
SELECT TOP 1 #MaxID=M1.AlphaID
FROM dbo.Mytable M1
WHERE NOT EXISTS (SELECT 1 FROM dbo.MyTable M2
WHERE AlphaID = dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(M1.AlphaID) + 1 )
)
ORDER BY M1.AlphaID
IF #MaxID IS NULL
RETURN 'AAAAA'
RETURN dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(#MaxID)+1)
END
GO
You'd have to store the last allocated identifier in the sequence.
For example, store it in another table that has one column & one row.
CREATE TABLE CurrentMaxId (
Id CHAR(6) NOT NULL
);
INSERT INTO CurrentMaxId (Id) VALUES ('AAAAAA');
Each time you allocate a new identifier, you'd fetch the value in that tiny table, increment it, and store that value in your main table as well as updating the value in CurrentMaxId.
The usual caveats apply with respect to concurrency, table-locking, etc.
I think I'd have tried to store the sequence as an integer, then translate it to string. Or else store a parallel integer column that is incremented at the same time as the alpha value. Either way, you could sort on the integer column.
A problem here is that you can't really tell from the data where the "last" entry is unless there is more detail as to how the old entries are deleted.
If I understand correctly, you are wrapping around at the end of the sequence, which means you must be deleting some of your old data to make space. However if the data isn't deleted in a perfectly uniform manner, you'll end up with fragments, like below:
ABCD HIJKL NOPQRS WXYZ
You'll notice that there is no obvious next value...D could be the last value created, but it might also be L or S.
At best you could look for the first or last missing element (use a stored procedure to perform a x+1 check just like you would to find a missing element in an integer sequence), but it's not going to provide any special result for rolled-over lists.
Since I don't feel like writing code to increment letters, I'd create a table of all valid IDs (AAAAAA through ZZZZZZ) with an integer from 1 to X for those IDs. Then you can use the following:
SELECT #max_id = MAX(id) FROM Possible_Silly_IDs
SELECT
COALESCE(MAX(PSI2.silly_id), 'AAAAAA')
FROM
My_Table T1
INNER JOIN Possible_Silly_IDs PSI1 ON
PSI1.silly_id = T1.silly_id
INNER JOIN Possible_Silly_IDs PSI2 ON
PSI2.id = CASE WHEN PSI1.id = #max_id THEN 1 ELSE PSI1.id + 1 END
LEFT OUTER JOIN My_Table T2 ON
T2.silly_id = PSI2.silly_id
WHERE
T2.silly_id IS NULL
The COALESCE is there in case the table is empty. To be truly robust you should calculate the 'AAAAAA' (SELECT #min_silly_id = silly_id WHERE id = 1) in case your "numbering" algorithm changes.
If you really wanted to do things right, you'd redo the database design as has been suggested.
I think the lowest-impact solution for my needs is to add an identity column. The one thing I can guarantee is that the ordering will be such that entries that should "come first" will be added first -- I'll never add one with identifier BBBB, then go back and add BBBA later. If I didn't have that constraint, obviously it wouldn't work, but as it stands, I can just order by the identity column and get the sort I want.
I'll keep thinking about the other suggestions -- maybe if they "click" in my head, they'll look like a better option.
To return the next ID for a given ID (with rollover), use:
SELECT COALESCE
(
(
SELECT TOP 1 id
FROM mytable
WHERE id > #id
ORDER BY
id
),
(
SELECT TOP 1 id
FROM mytable
ORDER BY
id
)
) AS nextid
This query searches for the ID next to the given. If there is no such ID, it returns the first ID.
Here are the results:
WITH mytable AS
(
SELECT 'AAA' AS id
UNION ALL
SELECT 'BBB' AS id
UNION ALL
SELECT 'CCC' AS id
UNION ALL
SELECT 'DDD' AS id
UNION ALL
SELECT 'EEE' AS id
)
SELECT mo.id,
COALESCE
(
(
SELECT TOP 1 id
FROM mytable mi
WHERE mi.id > mo.id
ORDER BY
id
),
(
SELECT TOP 1 id
FROM mytable mi
ORDER BY
id
)
) AS nextid
FROM mytable mo
id nextid
----- ------
AAA BBB
BBB CCC
CCC DDD
DDD EEE
EEE AAA
, i. e. it returns BBB for AAA, CCC for BBB, etc., and, finally, AAA for EEE which is last in the table.