conflict detection and resolution for unique field in SymmetricDS - symmetricds

I have this 2 sample data with PK(primary key) set to id and email,phone field set to UNIQUE field :
id: 1
email: jhon.doe#mailinator.com
phone: 123
last_modified: 14-9-2017
id: 23
email: jhon.doe#mailinator.com
phone: 678
last_modified: 23-10-2017
The question is when both data do some synchronization it will got unique field constraint violation, because of the same email data, how do symmetricds get solve this situation?

assuming the table name is user insert into sym_conflict:
insert into symmetric_ds.sym_conflict(
conflict_id,
source_node_group_id,
target_node_group_id,
target_table_name,
detect_type,
resolve_type,
ping_back,
create_time,
last_update_time
) values (
'conflict_user',
'replace_with_source_node_group_id',
'replace_with_target_node_group_id',
'user',
'USE_CHANGED_DATA',
'FALLBACK',
'OFF',
current_timestamp,
current_timestamp
);

Related

SQL Server Link foreign key on join using each row on the right table only once

I have two tables
CREATE TABLE remote_clients (
id INT NOT NULL IDENTITY PRIMARY KEY
first_name VARCHAR(255),
last_name VARCHAR(255),
date_of_birth DATE
)
and
CREATE TABLE local_clients (
id INT NOT NULL IDENTITY PRIMARY KEY
first_name VARCHAR(255),
last_name VARCHAR(255),
date_of_birth DATE,
remote_client_id INT FOREIGN KEY REFERENCES remote_clients(id)
)
I want to link all the local_clients with a row from remote_client with matching name and date of birth, but each remote_client can only be linked with one local_client.
How can I do this in one update query?
Examples
INSERT INTO local_clients
(first_name, last_name, date_of_birth)
VALUES
('foo', 'bar', '2020-01-01'),
('foo', 'bar', '2020-01-01'),
('baz', 'lurman', '2020-01-01'),
('steve', 'last', '2020-01-01'),
('steve', 'last', '2020-01-01'),
('aaron', 'something', '2020-01-01')
INSERT INTO remote_clients
(first_name, last_name, date_of_birth)
VALUES
('foo', 'bar', '2020-01-01'),
('foo', 'bar', '2020-01-01'),
('baz', 'lurman', '2020-01-01'),
('baz', 'lurman', '2020-01-01'),
('steve', 'last', '2020-01-01'),
After the update, my local_clients should look like this
id
first_name
last_name
date_of_birth
remote_client_id
1
foo
bar
2020-01-01
1
2
foo
bar
2020-01-01
2
3
baz
lurman
2020-01-01
3
4
aaron
something
2020-01-01
NULL
5
steve
last
2020-01-01
5
6
steve
last
2020-01-01
NULL
UPDATE local_clients
SET remote_client_id = subQuery.id
FROM local_clients
INNER JOIN (
SELECT
local.id,
remote.id,
---Get the row number, paritioned by the local ID so it's only joined once
ROW_NUMBER() OVER(PARTITION BY local.id ORDER BY local.id) AS row_number
FROM local_clients AS local
INNER JOIN remote_clients AS remote ON
local.first_name = remote.first_name AND
local.last_name = remote.last_name AND
local.date_of_birth = remote.date_of_birth
WHERE NOT EXISTS ( ---The remote client isn't already linked
SELECT remote_client_id
FROM local_clients
WHERE local_clients.remote_client_id = remote_clients.id
)
) AS subQuery
WHERE NOT EXISTS ( ---The local client isn't already linked
SELECT * FROM remote_clients WHERE local_clients.remote_client_id = remote_clients.id
)
--- If there are multiple remotes, only get the first
AND subQuery.row_number = 1

T-SQL prepare dynamic COALESCE

As attached in screenshot, there are two tables.
Configuration:
Detail
Using Configuration and Detail table I would like to populate IdentificationType and IDerivedIdentification column in the Detail table.
Following logic should be used, while deriving above columns
Configuration table has order of preference, which user can change dynamically (i.e. if country is Austria then ID preference should be LEI then TIN (in case LEI is blanks) then CONCAT (if both blank then some other logic)
In case of contract ID = 3, country is BG, so LEI should be checked first, since its NULL, CCPT = 456 will be picked.
I could have used COALESCE and CASE statement, in case hardcoding is allowed.
Can you please suggest any alternation approach please ?
Regards
Digant
Assuming that this is some horrendous data dump and you are trying to clean it up here is some SQL to throw at it. :) Firstly, I was able to capture your image text via Adobe Acrobat > Excel.
(I also built the schema for you at: http://sqlfiddle.com/#!6/8f404/12)
Firstly, the correct thing to do is fix the glaring problem and that's the table structure. Assuming you can't here's a solution.
So, here it is and what it does is unpivots the columns LEI, NIND, CCPT and TIN from the detail table and also as well as FirstPref, SecondPref, ThirdPref from the Configuration table. Basically, doing this helps to normalize the data although it's costing you major performance if there are no plans to fix the data structure or you cannot. After that you are simply joining the tables Detail.ContactId to DerivedTypes.ContactId then DerivedPrefs.ISOCountryCode to Detail.CountrylSOCountryCode and DerivedTypes.ldentificationType = DerivedPrefs.ldentificationType If you use an inner join rather than the left join you can remove the RANK() function but it will not show all ContactIds, only those that have a value in their LEI, NIND, CCPT or TIN columns. I think that's a better solution anyway because why would you want to see an error mixed in a report? Write a separate report for those with no values in those columns. Lastly, the TOP (1) with ties allows you to display one record per ContactId and allows for the record with the error to still display. Hope this helps.
CREATE TABLE Configuration
(ISOCountryCode varchar(2), CountryName varchar(8), FirstPref varchar(6), SecondPref varchar(6), ThirdPref varchar(6))
;
INSERT INTO Configuration
(ISOCountryCode, CountryName, FirstPref, SecondPref, ThirdPref)
VALUES
('AT', 'Austria', 'LEI', 'TIN', 'CONCAT'),
('BE', 'Belgium', 'LEI', 'NIND', 'CONCAT'),
('BG', 'Bulgaria', 'LEI', 'CCPT', 'CONCAT'),
('CY', 'Cyprus', 'LEI', 'NIND', 'CONCAT')
;
CREATE TABLE Detail
(ContactId int, FirstName varchar(1), LastName varchar(3), BirthDate varchar(4), CountrylSOCountryCode varchar(2), Nationality varchar(2), LEI varchar(9), NIND varchar(9), CCPT varchar(9), TIN varchar(9))
;
INSERT INTO Detail
(ContactId, FirstName, LastName, BirthDate, CountrylSOCountryCode, Nationality, LEI, NIND, CCPT, TIN)
VALUES
(1, 'A', 'DES', NULL, 'AT', 'AT', '123', '4345', NULL, NULL),
(2, 'B', 'DEG', NULL, 'BE', 'BE', NULL, '890', NULL, NULL),
(3, 'C', 'DEH', NULL, 'BG', 'BG', NULL, '123', '456', NULL),
(4, 'D', 'DEi', NULL, 'BG', 'BG', NULL, NULL, NULL, NULL)
;
SELECT TOP (1) with ties Detail.ContactId,
FirstName,
LastName,
BirthDate,
CountrylSOCountryCode,
Nationality,
LEI,
NIND,
CCPT,
TIN,
ISNULL(DerivedPrefs.ldentificationType, 'ERROR') ldentificationType,
IDerivedIdentification,
RANK() OVER (PARTITION BY Detail.ContactId ORDER BY
CASE WHEN Pref = 'FirstPref' THEN 1
WHEN Pref = 'SecondPref' THEN 2
WHEN Pref = 'ThirdPref' THEN 3
ELSE 99 END) AS PrefRank
FROM
Detail
LEFT JOIN
(
SELECT
ContactId,
LEI,
NIND,
CCPT,
TIN
FROM Detail
) DetailUNPVT
UNPIVOT
(IDerivedIdentification FOR ldentificationType IN
(LEI, NIND, CCPT, TIN)
)AS DerivedTypes
ON DerivedTypes.ContactId = Detail.ContactId
LEFT JOIN
(
SELECT
ISOCountryCode,
CountryName,
FirstPref,
SecondPref,
ThirdPref
FROM
Configuration
) ConfigUNPIVOT
UNPIVOT
(ldentificationType FOR Pref IN
(FirstPref, SecondPref, ThirdPref)
)AS DerivedPrefs
ON DerivedPrefs.ISOCountryCode = Detail.CountrylSOCountryCode
and DerivedTypes.ldentificationType = DerivedPrefs.ldentificationType
ORDER BY RANK() OVER (PARTITION BY Detail.ContactId ORDER BY
CASE WHEN Pref = 'FirstPref' THEN 1
WHEN Pref = 'SecondPref' THEN 2
WHEN Pref = 'ThirdPref' THEN 3
ELSE 99 END)

how to conditionally update table

I have one address table (containing k_id-PK, address) and one add_hist log table(containing k_id, address,change date) i.e. it has all address per id and on which date address change.
I want to make an update query which will update address column in address table so,fetching latest address from add_hist table will do the job.I am almost done with my query. Its fetching correct result too. But I want if address table is already updated, then dont update it.Here goes my query.Please review and correct it to get the desired result.
update address a set k_add =
(select kad from (
select h.k_id kid, h.k_add kad, h.chg_dt from add_hist h,
(select k_id, max(chg_dt) ch from add_hist
group by k_id
) h1
where h1.k_id = h.k_id
and h1.ch=h.chg_dt
) h2
where h2.kid = a.k_id)
;
You could use a merge instead of an update:
merge into address a
using (
select k_id, max(k_add) keep (dense_rank last order by chg_dt) as k_add
from add_hist
group by k_id
) h
on (a.k_id = h.k_id)
when matched then
update set a.k_add = h.k_add
where (a.k_add is null and h.k_add is not null)
or (a.k_add is not null and h.k_add is null)
or a.k_add != h.k_add;
The query in the using clause finds the most recent address for each ID from the history table. When a matching ID exists on the main table that is updated - but only if the value is different, because of the where clause.
With some dummy data:
create table address (k_id number primary key, k_add varchar2(20));
create table add_hist (k_id number, k_add varchar2(20), chg_dt date);
insert into address (k_id, k_add) values (1, 'Address 1');
insert into address (k_id, k_add) values (2, 'Address 2');
insert into address (k_id, k_add) values (3, null);
insert into address (k_id, k_add) values (4, null);
insert into add_hist (k_id, k_add, chg_dt) values (1, 'Address 1', date '2017-01-01');
insert into add_hist (k_id, k_add, chg_dt) values (1, 'Address 2', date '2017-01-02');
insert into add_hist (k_id, k_add, chg_dt) values (1, 'Address 1', date '2017-01-03');
insert into add_hist (k_id, k_add, chg_dt) values (2, 'Address 1', date '2017-01-01');
insert into add_hist (k_id, k_add, chg_dt) values (2, 'Address 2', date '2017-01-02');
insert into add_hist (k_id, k_add, chg_dt) values (2, 'Address 3', date '2017-01-03');
insert into add_hist (k_id, k_add, chg_dt) values (3, 'Address 1', date '2017-01-01');
insert into add_hist (k_id, k_add, chg_dt) values (3, null, date '2017-01-02');
insert into add_hist (k_id, k_add, chg_dt) values (4, 'Address 1', date '2017-01-01');
commit;
running your update statement gets:
4 rows updated.
select * from address;
K_ID K_ADD
---------- --------------------
1 Address 1
2 Address 3
3
4 Address 1
After rolling back to the starting state, running the merge gets:
2 rows merged.
select * from address;
K_ID K_ADD
---------- --------------------
1 Address 1
2 Address 3
3
4 Address 1
Same final result, but 1 row merged rather than 2 rows updated.
(If you run the merge without the where clause, all four rows are still affected; without the null checks only row with ID 2 is updated).
You can achieve the desired result with an UPDATE statement. Specifically, you need to "update through a join." The syntax has to be precise though. Update with joins
Using the same setup as in Alex's answer, the following update statement will update one row.
EDIT: See Alex Poole's comments below this Answer. The solution proposed here will work only in Oracle 12.1 and above. The problem is not the "update through join" concept, but the source rowset being the result of an aggregation. It has to do with the way in which Oracle knows, at compile time, that the "join" column in the source rowset is unique (it has no duplicates). In older versions of Oracle, an explicit unique or primary key constraint or index was required. Of course, when we GROUP BY <col>, the <col> will be unique in the result set of an aggregation, but it will not have a unique constraint or index on it. It seems Oracle recognized this situation, and since 12.1 it allows update through join where the source table is the result of an aggregation, as shown in this example.
update
( select a.k_add as current_address, q.new_address
from (
select k_id,
min(k_add) keep (dense_rank last order by chg_dt) as new_address
from add_hist
group by k_id
) q
join
address a on a.k_id = q.k_id
)
set current_address = new_address
where current_address != new_address
or current_address is null and new_address is not null
or current_address is not null and new_address is null
;

Why does SQL Server explicit predicate locking disallow INSERT statements outside of the predicate lock

Assuming we have the following database tables:
create table department (
id bigint not null,
budget bigint not null,
name varchar(255),
primary key (id)
)
create table employee (
id bigint not null,
name varchar(255),
salary bigint not null,
department_id bigint,
primary key (id)
)
alter table employee
add constraint FKbejtwvg9bxus2mffsm3swj3u9
foreign key (department_id) references department
And we have 3 department rows:
insert into department (name, budget, id)
values ('Department 1', 100000, 1)
insert into department (name, budget, id)
values ('Department 2', 75000, 2)
insert into department (name, budget, id)
values ('Department 3', 90000, 3)
And we also have 3 employee rows:
insert into employee (department_id, name, salary, id)
values (1, 'CEO', 30000, 1)
insert into employee (department_id, name, salary, id)
values (1, 'CTO', 30000, 2)
insert into employee (department_id, name, salary, id)
values (2, 'CEO', 30000, 3)
Assuming we have two concurrent users: Alice and Bob.
First, Alice locks all the employee belonging to the 1st department:
SELECT *
FROM employee
WITH (HOLDLOCK)
WHERE department_id = 1
Now, in the meanwhile, it's expected that Bob cannot insert a new employee using the same department_id:
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id)
VALUES (1, 'Carol', 9000, 6)
The insert statement above ends with Lock request time out period exceeded which is fine since Alice locked that particular range.
However, why is the following insert blocked as well:
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id)
VALUES (3, 'Dave', 9000, 7)
This insert statement uses a department_id value which is beyond the range of Alice's predicate lock. But then, this insert statement also ends up with a Lock request time out period exceeded exception.
Why does the SQL Server HOLDLOCK predicate lock extend beyond its range?
UPDATE
By adding an index to the FK:
create index IDX_DEPARTMENT_ID on employee (department_id)
And increasing the number of employee entries in the 1st and 2nd department to 1000, I managed to see that the predicate lock behaves as expected.
The only way that the SELECT query could be satisfied was by performing a table scan. There's no natural resource by which it could locate and lock based only on department_id, and so it ends up locking all rows and preventing any insertions.
In this toy example, just adding an index on department_id will not help since the optimizer will still choose to perform a table scan. However, on a larger more realistic table, I believe that adding such an index would allow the query to apply locks in a more targeted fashion.
What Damien said was correct..When you dont have index on department id (predicate column) ,the range increases and HoldLock means
HOLDLOCK means SERALIZABLE and therefore allows SELECTS, but blocks UPDATE and DELETES of the rows selected by T1, as well as any INSERT in the range selected by T1 .
so in this case, an index of the below form would help and my test confirms the same
Below is a sample test i did
in session1:
create index nci_department_id on dbo.employee(department_id)
include
(id,name,salary)
go
begin tran
SELECT *
FROM employee
WITH (HOLDLOCK)
WHERE department_id = 1
in session2:
INSERT INTO employee WITH(NOWAIT) (department_id, name, salary, id)
VALUES (3, 'Dave', 9000, 7)
Now the above insert succeeds
References:
https://stackoverflow.com/a/7845331/2975396

Finding the topmost item in a denormalized hierarchy

I'm working with a set of tables that establishes a denormalized location structure with the following schema:
Location (Name,Id)
Sublocation1 (Name,Id,LocationId)
Sublocation2 (Name,Id,Sublocation1Id)
Sublocation3 (Name,Id,Sublocation2Id)
And a table that tracks the association between a user and each level:
UserLocation (User,LocationId,Sublocation1Id,Sublocation2Id,Sublocation3Id)
Access to a higher level location grants access to any level under it, so the second row in the following example is superfluous, but the third row is not:
User Location Sublocation1 Sublocation2 Sublocation3
----------------------------------------------------------------
Joe Houston Plant West Building NULL NULL
Joe Houston Plant West Building Third Floor Room 42
Joe Houston Plant East Building Second Floor Room 21
The third row grants Joe access to just room 21, but not other Sublocation3s under Second Floor
Question: How can I find all the records that grant the highest level of access without granting additional permissions to Joe? My goal is to be able to trim all these extraneous entries out of my database.
I can see a number of ways to solve this, but nothing that I've been able to translate into set-based logic to make a good query.
Given the following table:
CREATE TABLE UserLocation(
UserId int,
LocationId int,
Sublocation1Id int,
Sublocation2Id int,
Sublocation3Id int
)
The following query gives you the highest level accesses:
-- Highest level access:
SELECT * -- Level 3 grants with no higher level grants
FROM UserLocation UL
WHERE
UL.Sublocation3Id IS NOT NULL
AND NOT EXISTS (
SELECT * -- higher level grant
FROM UserLocation UL1
WHERE
UL1.Sublocation3Id IS NULL
AND (UL1.Sublocation2Id IS NULL OR UL1.Sublocation2Id = UL.Sublocation2Id)
AND (UL1.Sublocation1Id IS NULL OR UL1.Sublocation1Id = UL.Sublocation1Id)
AND (UL1.LocationId = UL.LocationId)
)
UNION ALL
SELECT * -- Level 2 grants with no higher level grants
FROM UserLocation UL
WHERE
UL.Sublocation3Id IS NULL
AND UL.Sublocation2Id IS NOT NULL
AND NOT EXISTS (
SELECT * -- higher level grant
FROM UserLocation UL1
WHERE
UL1.Sublocation3Id IS NULL
AND UL1.Sublocation2Id IS NULL
AND (UL1.Sublocation1Id IS NULL OR UL1.Sublocation1Id = UL.Sublocation1Id)
AND (UL1.LocationId = UL.LocationId)
)
UNION ALL
SELECT * -- Level 1 grants with no higher level grants
FROM UserLocation UL
WHERE
UL.Sublocation3Id IS NULL
AND UL.Sublocation2Id IS NULL
AND UL.Sublocation1Id IS NOT NULL
AND NOT EXISTS (
SELECT * -- higher level grant
FROM UserLocation UL1
WHERE
UL1.Sublocation3Id IS NULL
AND UL1.Sublocation2Id IS NULL
AND UL1.Sublocation1Id IS NULL
AND (UL1.LocationId = UL.LocationId)
)
SELECT * -- Level 0 grants
FROM UserLocation UL
WHERE
UL.Sublocation3Id IS NULL
AND UL.Sublocation2Id IS NULL
AND UL.Sublocation1Id IS NULL
The following query shows you the superflous grants:
-- Superflous grants (there is higher level grants)
SELECT * -- Level 3 grants with higher level grants
FROM UserLocation UL
WHERE
UL.Sublocation3Id IS NOT NULL
AND EXISTS (
SELECT * -- higher level grant
FROM UserLocation UL1
WHERE
UL1.Sublocation3Id IS NULL
AND (UL1.Sublocation2Id IS NULL OR UL1.Sublocation2Id = UL.Sublocation2Id)
AND (UL1.Sublocation1Id IS NULL OR UL1.Sublocation1Id = UL.Sublocation1Id)
AND (UL1.LocationId = UL.LocationId)
)
UNION ALL
SELECT * -- Level 2 grants with higher level grants
FROM UserLocation UL
WHERE
UL.Sublocation3Id IS NULL
AND UL.Sublocation2Id IS NOT NULL
AND EXISTS (
SELECT * -- higher level grant
FROM UserLocation UL1
WHERE
UL1.Sublocation3Id IS NULL
AND UL1.Sublocation2Id IS NULL
AND (UL1.Sublocation1Id IS NULL OR UL1.Sublocation1Id = UL.Sublocation1Id)
AND (UL1.LocationId = UL.LocationId)
)
UNION ALL
SELECT * -- Level 1 grants with higher level grants
FROM UserLocation UL
WHERE
UL.Sublocation3Id IS NULL
AND UL.Sublocation2Id IS NULL
AND UL.Sublocation1Id IS NOT NULL
AND EXISTS (
SELECT * -- higher level grant
FROM UserLocation UL1
WHERE
UL1.Sublocation3Id IS NULL
AND UL1.Sublocation2Id IS NULL
AND UL1.Sublocation1Id IS NULL
AND (UL1.LocationId = UL.LocationId)
)
I assume that if the id of a location level L is null then the id of the location level L+1 is null.
Let's presume that UserLocation table has an Id column.
If it doesn't you can generate one using ROW_NUMBER.
In order to identify strong rules (the minimum set of existing access rules that is equivalent to all existing rules) or weak rules (a set of existing rules that are redundant because they are also covered by other "stronger" rules) you can join the UserLocation table with itself:
with access as (
select * from ( values
(1, 'Joe', 'Houston Plant', 'West Building', NULL, NULL ),
(2, 'Joe', 'Houston Plant', 'West Building', 'Third Floor', 'Room 42'),
(3, 'Joe', 'Houston Plant', 'East Building', 'Second Floor', 'Room 21'),
(4, 'Mark', 'Houston Plant', 'West Building', NULL, NULL ),
(5, 'Mark', 'Houston Plant', 'West Building', 'Third Floor', 'Room 42'),
(6, 'Mark', 'Houston Plant', 'West Building', 'Second Floor', 'Room 21'),
(7, 'Bob', null, null, NULL, NULL ),
(8, 'Bob', 'Houston Plant', 'West Building', 'Third Floor', 'Room 42'),
(9, 'Bob', 'Houston Plant', 'West Building', 'Second Floor', 'Room 21')
) as v(Id, Usr, Location1, Location2, Location3, Location4)
)
select distinct
weak.* -- or strong.*
from access strong
inner JOIN
access weak on weak.Usr = strong.Usr
and weak.Id <> strong.Id
and (strong.Location1 is null or weak.Location1 = strong.Location1)
and (strong.Location2 is null or weak.Location2 = strong.Location2)
and (strong.Location3 is null or weak.Location3 = strong.Location3)
and (strong.Location4 is null or weak.Location4 = strong.Location4)
Edit:
I figured out that there might be rules that are unique for user like:
(10, 'John', 'Houston Plant', 'West Building', 'Third Floor', 'Room 42'),
(11, 'Ana', 'Houston Plant', 'West Building', NULL, NULL )
Since the above query uses an INNER JOIN these rules are not reported in any set (weak or strong) but if you think about it you can consider that these rules are neutral so to speak because:
they do not override other rules
they are not overridden by other rules

Resources