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

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

Related

Join two tables, each with multiple rows with the same key values on just the latest version of each

I'm trying to list the codes and descriptions that are used in a "person" table. There are many codes in the "code_table" but I don't want to list unused codes.
I have a "person" table that has a record for each time a person is changed... in particular, I'm interested in the "person_code" field. Every person record has a date_revised value that indicates when the change was made. The person table has an identity primary key (person_id) and a person identifier (person_int) for each person.
I have a "code_table" which has records for each time it's changed, again containing a date_revised field. It has a "code_type" field that matches person_code in the person table and "code_data" which is the description of the code. Code_table also has a identity primary key and an identifier (code_group + code_type)
So, the data might look like:
person:
person_id person_int person_code person_first person_last date_revised
1 1234 JPM John Doe 1/2/2021
232 1234 JPM Johnathan Doe 2/11/2021
432 1234 PM Johnny Doe 4/24/2021
code_table:
code_id code_group code_type code_description date_revised
2 person_code PM Project Manager 1/9/2021
32 person_code PM Senior Project Manager 3/16/2021
So, I'm looking for the "PM" and "Senior Project Manager" code to be listed. If there were only one row for each, I could simply join them with:
select distinct code_type,code_data from person
join code_table on code_group='person_code' and person_code = code_type
order by code_data
Obviously, I get a ton of duplicates. So, how do I write a query so that I link the latest person record with the latest code_table record and list the latest code_table values? The result should show only the unique code_table entries.
EDIT:
I've never used db fiddle and didn't see how to save it. Here are the statements to create a sample:
create table person (
person_id int,
person_int int, person_code varchar(55),
person_first varchar(55),
person_last varchar(55),
date_revised datetime)
insert into person select 1, 1234, 'JPM', 'John', 'Doe', '1/2/2021'
insert into person select 12, 1234, 'JPM', 'Johnathn', 'Doe', '2/2/2021'
insert into person select 2231, 1234, 'PM', 'Johnny', 'Doe', '2/2/2021'
insert into person select 2232, 222, 'PM', 'Billy', 'Bob', '2/2/2021'
select * from person
create table code_table (
code_id int,
code_group varchar(55),
code_type varchar(55),
code_description varchar(55),
date_revised datetime)
insert into code_table select 1, 'person_code', 'JPM', 'Junior Project Manager', '1/9/2021'
insert into code_table select 2, 'person_code', 'PM', 'Project Manager', '1/9/2021'
insert into code_table select 3, 'person_code', 'MGR', 'Manager', '1/9/2021'
insert into code_table select 32, 'person_code', 'PM', 'Senior Project Manager', '2/9/2021'
insert into code_table select 33, 'person_code', 'SUP', 'Supervisor', '2/19/2021'
select * from code_table
The only thing that should result is "PM Senior Project Manager"
I was thinking that this gets really complex. What would be wrong with creating two views that only select the latest rows and then use a regular join to get the data?
create view view_code_table
as
WITH ct AS
(select *, ROW_NUMBER() OVER (PARTITION BY code_group,code_seq,code_type,code_language ORDER BY date_revised DESC) AS rn from code_table)
SELECT * FROM ct WHERE rn = 1
create view view_person
as
WITH p AS
(select *, ROW_NUMBER() OVER (PARTITION BY org_int,person_int ORDER BY date_revised DESC) AS rn from person)
SELECT * FROM p WHERE rn = 1
Then join the views with whatever constraints you want.
select distinct code_type,code_data from view_person
join view_code_table on code_group='person_code' and person_code = code_type
order by code_data
Assuming that the date_revised will be unique per person, I think the following code will work with you:
select distinct
c.code_type,
c.code_data
from person p with(nolock)
inner join code_table c on c.code_group='person_code' and p.person_code = c.code_type
inner join (
select person_int, max(date_revised) date_revised from person a with(nolock) group by a.person_int
) x on x.date_revised = p.date_revised and x.person_int = p.person_int
order by c.code_data

JSON_VALUE is not working in where clause in SQL Server

I have JSON data in a column in my table. I am trying to apply where condition on the JSON column and fetch records.
Employee table:
Here is my SQL query:
SELECT ID, EMP_NAME
FROM EMPLOYEE
WHERE JSON_VALUE(TEAM, '$') IN (2, 3, 4, 5, 7, 10)
I am getting an empty result when I use this query. Any help on how to do this?
You need to parse the JSON in the TEAM column with OPENJSON():
Table:
CREATE TABLE EMPLOYEE (
ID int,
EMP_NAME varchar(50),
TEAM varchar(1000)
)
INSERT INTO EMPLOYEE (ID, EMP_NAME, TEAM)
VALUES
(1, 'Name1', '[2,11]'),
(2, 'Name2', '[2,3,4,5,7,10]'),
(3, 'Name3', NULL)
Statement:
SELECT DISTINCT e.ID, e.EMP_NAME
FROM EMPLOYEE e
CROSS APPLY OPENJSON(e.TEAM) WITH (TEAM int '$') j
WHERE j.TEAM IN (2,3,4,5,7,10)
Result:
ID EMP_NAME
1 Name1
2 Name2
As an additional option, if you want to get the matches as an aggregated text, you may use the following statement (SQL Server 2017 is needed):
SELECT e.ID, e.EMP_NAME, a.TEAM
FROM EMPLOYEE e
CROSS APPLY (
SELECT STRING_AGG(TEAM, ',') AS TEAM
FROM OPENJSON(e.TEAM) WITH (TEAM int '$')
WHERE TEAM IN (2,3,4,5,7,10)
) a
WHERE a.TEAM IS NOT NULL
Result:
ID EMP_NAME TEAM
1 Name1 2
2 Name2 2,3,4,5,7,10
JSON_VALUE returns a scalar value, not a data set, which you appaer to think it would. If you run SELECT JSON_VALUE('[2,3,4,5,7,10]','$') you'll see that it returns NULL, so yes, no rows will be returned.
You need to treat the JSON like a data set, not a single value:
SELECT ID, EMP_NAME
FROM EMPLOYEE E
WHERE EXISTS (SELECT 1
FROM OPENJSON (E.TEAM) OJ
WHERE OJ.Value IN (2,3,4,5,7,10))

SQL Server table design to define WHERE condition

I have an existing Stored procedure which has lots of hard-coding with IF conditions. The procedure checks the values of following input fields and displays relevant message: The fields are:
• BrandId
• ProductId
• SchemeId
• RegionId
The existing Message table:
MsgId MsgText
1 AAAA
2 BBBB
3 CCCC
4 MMMM
Existing stored proc. pseudo code:
IF(BrandId in (5,10))
IF(#ProductId in (5))
SELECT ‘BBBB’ as MsgText
END IF
END IF
IF(SchemeId in (1,5,10))
SELECT ‘AAAA’ as MsgText
IF(SchemeId =2 AND #RegionId=4)
SELECT ‘BBBB’ as MsgText
IF (#RegionId=6)
SELECT ‘MMMM’ as MsgText
In order to remove hard-coding and re-writing the procedure cleanly from scratch, I want to design new tables which will store "MsgId"s against a BrandId/ProdId/PlanId/SchemeId value or against a combination of these fields (e.g SchemeId =2 AND RegionId=4).With this kind of design I can directly fetch the relevant MsgId against a specific field or combination of fields.
Could anybody suggest table designs to meet the requirement?
Based on your responses to the comments, this might work out.
create table dbo.[Messages] (
MessageId int not null
, MessageText nvarchar(1024) not null
, constraint pk_Messages primary key clustered (MessageId)
);
insert into dbo.[Messages] (MessageId,MessageText) values
(1,'AAAA')
, (2,'BBBB')
, (13,'MMMM');
create table dbo.Messages_BrandProduct (
BrandId int not null
, ProductId int not null
, MessageId int not null
, constraint pk_Messages_BrandProduct primary key clustered
(BrandId, ProductId, MessageId)
);
insert into dbo.Messages_BrandProduct (BrandId, ProductId, MessageId) values
(5,5,2)
,(10,5,2);
create table dbo.Messages_SchemeRegion (
SchemeId int not null
, RegionId int not null
, MessageId int not null
, constraint pk_Messages_SchemeRegion primary key clustered
(SchemeId, RegionId, MessageId)
);
insert into dbo.Messages_SchemeRegion (SchemeId, RegionId, MessageId)
select SchemeId = 1, RegionId , MessageId = 1 from dbo.Regions
union all
select SchemeId = 5, RegionId , MessageId = 1 from dbo.Regions
union all
select SchemeId = 10, RegionId , MessageId = 1 from dbo.Regions
union all
select SchemeId = 2, RegionId = 4, MessageId = 2
union all
select SchemeId , RegionId = 6, MessageId = 13 from dbo.Schemes;
In your procedure you could pull the messages like this:
select MessageId
from dbo.Messages_BrandProduct mbp
inner join dbo.[Messages] m on mbp.MessageId=m.MessageId
where mbp.BrandId = #BrandId and mbp.ProductId = #ProductId
union -- union all if you don't need to deduplicate messages
select MessageId
from dbo.Messages_SchemeRegion msr
inner join dbo.[Messages] m on msr.MessageId=m.MessageId
where msr.SchemeId = #SchemeId and msr.RegionId = #RegionId;
This should do it.
CREATE TABLE [dbo].[IDs](
[BrandID] [int] NOT NULL,
[ProductID] [int] NOT NULL,
[SchemeID] [int] NOT NULL,
[RegionID] [int] NOT NULL,
[MsgID] [int] NOT NULL
)
You can adjust the table and column names as needed. Cheers.

How to replace SELECT statement inside IF statement for it to work [duplicate]

This question already has answers here:
Oracle: how to INSERT if a row doesn't exist
(9 answers)
Closed 9 years ago.
I have a simple question - for examples sake let's have the table
CITY(ID,Name).
An idea would be that when I want to add new city I first make sure it's not already in the table CITY.
Code example would be:
IF cityName NOT IN (SELECT name FROM city) THEN
INSERT INTO City(ID, NAME) VALUES(100, cityName);
ELSE
Raise namingError;
END IF;
However I can't have that subquery inside if statement so what should I replace it with? Any kind of list or variable or trick that I could use?
IF NOT EXISTS(SELECT 1 FROM CITY WHERE NAME = <CITYNAME>)
INSERT INTO City(ID, NAME) VALUES(100, cityName);
OR
INSERT INTO City
SELECT 100,'cityName'
FROM dual
WHERE NOT EXISTS (SELECT 1
FROM CITY
WHERE name = cityname
)
I read the second query here
I don't have a database to try this out, but this should work
You could use a merge command to perform the insert into the table. While the merge command is used to perform an insert if the data is not present or an update if the data is present in this case since you just have two fields it will just preform the insert for you.
This is useful if you want to take data from one or more tables and combine them into one.
MERGE INTO city c
USING (SELECT * FROM city_import ) h
ON (c.id = h.id and c.city = h.city)
WHEN MATCHED THEN
WHEN NOT MATCHED THEN
INSERT (id, city)
VALUES (h.id, h.city);
http://www.oracle-base.com/articles/9i/merge-statement.php
If it was me I'd probably do something like
DECLARE
rowCity CITY%ROWTYPE;
BEGIN
SELECT * INTO rowCity FROM CITY c WHERE c.NAME = cityName;
-- If we get here it means the city already exists; thus, we raise an exception
RAISE namingError;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- cityName not found in CITY; therefore we insert the necessary row
INSERT INTO City(ID, NAME) VALUES(100, cityName);
END;
Share and enjoy.
Two options:
One using INSERT INTO ... SELECT with a LEFT OUTER JOIN; and
The other using MERGE
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE city (
ID NUMBER(2) PRIMARY KEY,
NAME VARCHAR2(20)
);
INSERT INTO city
SELECT 1, 'City Name' FROM DUAL;
CREATE TABLE city_errors (
ID NUMBER(2),
NAME VARCHAR2(20),
TS TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ERROR VARCHAR2(20)
);
Query 1:
DECLARE
city_id CITY.ID%TYPE := 2;
city_name CITY.NAME%TYPE := 'City Name';
namingError EXCEPTION;
PRAGMA EXCEPTION_INIT( namingError, -20001 );
BEGIN
INSERT INTO city ( id, name )
SELECT city_id,
city_name
FROM DUAL d
LEFT OUTER JOIN
city c
ON ( c.name = city_name )
WHERE c.id IS NULL;
IF SQL%ROWCOUNT = 0 THEN
RAISE namingError;
END IF;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
-- Do something when duplicate ID found.
INSERT INTO city_errors ( ID, NAME, ERROR ) VALUES ( city_id, city_name, 'Duplicate ID' );
WHEN namingError THEN
-- Do something when duplicate Name found.
INSERT INTO city_errors ( ID, NAME, ERROR ) VALUES ( city_id, city_name, 'Duplicate Name' );
END;
Results:
Query 2:
DECLARE
city_id CITY.ID%TYPE := 3;
city_name CITY.NAME%TYPE := 'City Name';
namingError EXCEPTION;
PRAGMA EXCEPTION_INIT( namingError, -20001 );
BEGIN
MERGE INTO city c
USING ( SELECT city_id AS id,
city_name AS name
FROM DUAL ) d
ON ( c.Name = d.Name )
WHEN NOT MATCHED THEN
INSERT VALUES ( d.id, d.name );
IF SQL%ROWCOUNT = 0 THEN
RAISE namingError;
END IF;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
-- Do something when duplicate ID found.
INSERT INTO city_errors ( ID, NAME, ERROR ) VALUES ( city_id, city_name, 'Duplicate ID' );
WHEN namingError THEN
-- Do something when duplicate Name found.
INSERT INTO city_errors ( ID, NAME, ERROR ) VALUES ( city_id, city_name, 'Duplicate Name' );
END;
Results:
Query 3:
SELECT * FROM City
Results:
| ID | NAME |
|----|-----------|
| 1 | City Name |
Query 4:
SELECT * FROM City_Errors
Results:
| ID | NAME | TS | ERROR |
|----|-----------|--------------------------------|----------------|
| 2 | City Name | January, 02 2014 20:01:49+0000 | Duplicate Name |
| 3 | City Name | January, 02 2014 20:01:49+0000 | Duplicate Name |

Inner Joining two tables based on all "Key/Value" Pairs matching exactly

Lets say I have two tables - Person and Clothes and both of these tables have associated Key/Value tables which store attributes about a Person and an item of Clothing.
A joined version of Person to Attributes might look like:
PersonID | AttributeKey | AttributeValue
1 'Age' '20'
1 'Size' 'Large'
2 'Age' '20'
A joined version of Clothing to Attributes might look like:
ClothingID | AttributeKey | AttributeValue
99 'Age' '20'
99 'Color' 'Blue'
60 'Age' '20'
Given a specifc Piece of clothing I want to find the Person entries which match EXACTLY ALL pairs of Attributes. For example, given ClothingID 60 I want to get ONLY PersonID 2 even though PersonID 1 did have a matching AGE but it had extra attributes. And basically the opposite has the same effect.
Given Clothing 99 I would expect NO results since no Person entries have a Color attribute.
An INNER JOIN obviously gives me the Attributes of Clothing which matching specific Attributes of People. But I want to only return rows where ALL possible matches did indeed match and throw out the others if there were extra. An OUTER JOIN will give me NULL values for ones that match but how I detect this and throw out all Person rows if 1 row had NULLS?
SELECT *
FROM persons p
WHERE NOT EXISTS
(
SELECT NULL
FROM (
SELECT key, value
FROM clothing_attributes
WHERE clothing_id = 99
) ca
FULL JOIN
(
SELECT key, value
FROM person_attributes
WHERE person_id = p.id
) pa
ON ca.key = pa.key
AND ca.value = pa.value
WHERE ca.key IS NULL OR pa.key IS NULL
)
You can use a subquery to verify that all requirements have been met. For each combination of PersonID and ClothingID, the inner join should have a count(*) that equals the number of conditions in the person table.
Example query:
select p.PersonID, c.ClothingID
from #person p
inner join #clothing c
on p.AttributeKey = c.AttributeKey
and p.AttributeValue = c.AttributeValue
group by p.PersonID, c.ClothingID
having count(*) = (
select count(*)
from #person p2
where p.PersonID = p2.PersonID
)
Output:
PersonID ClothingID
2 60
2 99
Data used to test the query:
declare #person table (PersonID int, AttributeKey varchar(30),
AttributeValue varchar(30))
declare #clothing table (ClothingID int, AttributeKey varchar(30),
AttributeValue varchar(30))
insert into #person select 1, 'Age', '20'
insert into #person select 1, 'Size', 'Large'
insert into #person select 2, 'Age', '20'
insert into #clothing select 99, 'Age', '20'
insert into #clothing select 99, 'Color', 'Blue'
insert into #clothing select 60, 'Age', '20'
Something like this:
SELECT p.*,c.* People p INNER JOIN Cloths c ON (P.key=c.key AND p.value=c.value) WHERE c.id=99

Resources