Max value in one table does not match value in another - sql-server

I reviewed many answers about pulling a max value with a corresponding value from another column in the same table, but not when the corresponding column lives in another table.
Consider:
I'd like to pull one row for the max bucket_id (62715659), with it's corresponding payor_name value (HSN). The payor_name, however, lives in another table.
Like so:
Instead, when I run this query:
select
hsp_account_id
,bucket_id
,epm.payor_name
from hsp_bucket bkt
left join clarity_epm epm on bkt.payor_id = epm.payor_id
where bucket_id in (select max(bucket_id) from hsp_bucket)
I return 0 rows.
Here is some sample data from both tables:
CREATE TABLE hsp_bucket
(
hsp_account_id VarChar(50),
bucket_id NUMERIC(18,0),
payor_id NUMERIC(18,0)
);
INSERT INTO hsp_bucket
VALUES
('A', 10706486, NULL),
('A', 10706487, NULL),
('A', 10706488, NULL),
('A', 10706491, 1118),
('A', 10706489, 3004),
('A', 10706490, 4001),
('A', 62715659, 4001)
CREATE TABLE clarity_epm
(payor_id NUMERIC(18,0),
payor_name VarChar(50)
);
INSERT INTO clarity_epm
VALUES
(1118, 'BMCHP ALLI ACO'),
(3004, 'MEDICAID LIMITED'),
(4001, 'HSN')

Related

Validate number of rows depending on master row field value

I need to add a constraint to validate that number of rows referencing master table is lower than value in master row, e.g we have a table
master(master_id int pk, max_val int) and slave(slave_id int pk, master_id fk ref master(master_id)) (so slave is de facto a colection of something), and I want that count(master_id) in slave is <= than max_val for this master_id. I have a constraint
constraint NO_MORE_PASS check ((select count(head_id) from parts p where
p.head_id = head_id) <= (select max_val from head where id = head_id));
(not sure if it is correct, however SQL Server tells that subqueries are not allowed (sql server 2017) so...).
I have also read Check Constraint - Subqueries are not allowed in this context, so the question: is there any other alternative (I would like to avoid using trigger)?.
I'am using this in spring app with spring data jpa (and hibernate) - may be useful, but would like to make it on db side rather than in the app.
Nethertheless entity it is like:
#Entity
#Table(name = "route_parts")
data class RoutePart(
#Id
#Column(name = "route_part_id")
#GeneratedValue(strategy = GenerationType.AUTO)
var id: Long? = null,
//...
#Column(nullable = false)
var slots: Int? = null,
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "route_part_passengers",
joinColumns = [(JoinColumn(name = "route_part_id"))],
inverseJoinColumns = [(JoinColumn(name = "user_id"))]
)
var passengers: Set<ApplicationUser> = setOf()
)
and in that case ApplicationUser is a slave (or better - another table will be created, and actually this will be that slave table) limited by slots value.
So the question is...
How can I achieve limiting number of ApplicationUser attached to each RoutePart
If you want your check constraints to be based on queries, you must use a user defined function for the check constraint to work with.
Here is a quick example:
Tables:
CREATE TABLE dbo.Parent
(
Id int,
MaxNumberOfChildren int NOT NULL
);
CREATE TABLE dbo.Child
(
Id int,
ParentId int
);
User defined function (All it does is return the difference between the MaxNumberOfChildren and the number of records in the Child table with the same ParentId):
CREATE FUNCTION dbo.RestrictNumbrOfChildren
(
#ParentId int
)
RETURNS int
AS
BEGIN
RETURN
(
SELECT MaxNumberOfChildren
FROM dbo.Parent
WHERE Id = #ParentId
)
-
(
SELECT COUNT(Id)
FROM dbo.Child
WHERE ParentId = #ParentId
)
END;
Add the check constraint to the Child table:
ALTER TABLE dbo.Child
ADD CONSTRAINT chk_childCount CHECK (dbo.RestrictNumbrOfChildren(ParentId) >= 0);
And that's basically all you need, unless MaxNumberOfChildren is nullable.
In that case, you should add ISNULL() to the first query, with either 0 if null means no children are allowed, or the maximum value of int (2,147,483,647) if null means no restriction on the number of children - so it becomes SELECT ISNULL(MaxNumberOfChildren, 0)... or SELECT ISNULL(MaxNumberOfChildren, 2147483647)....
To test the script, let's insert some data to the Parent table:
INSERT INTO Parent (Id, MaxNumberOfChildren) VALUES
(1, 3), (2, 2), (3, 1);
And insert some valid data to the Child table:
INSERT INTO Child (Id, ParentId) VALUES
(1, 1), (2, 2);
So far, we have not exceeded the maximum number of records allowed. Now let's try to do that by insert some more data to the Child table:
INSERT INTO Child (Id, ParentId) VALUES
(3, 1), (4, 1), (5, 1);
Now, this insert statement will fail with the error message:
The INSERT statement conflicted with the CHECK constraint "chk_childCount". The conflict occurred in database "<your database name here>", table "dbo.Child", column 'ParentId'.
You can see a live demo on rextester.

Fetching multiple row columns different values into single row based on a criteria

I have two tables
Files,
FileTypes
Files will hold the data of both files types internal and external for all clients
i want to fetch a client's file in a single row with internal file path and extenral filepath.
Below is the metadata
create table filetypes (
filetypeid int primary key,
typename varchar(20)
);
create table files (
fileid int primary key,
filetypeid int,
fiilepath nvarchar(200),
client_id int
);
insert into files values (10, 1,'\testpath9\\InternalFoldername', 1);
insert into files values (11, 2,'\testpath2\\ExternalFoldername', 1);
insert into files values (12, 1,'\testpath5\\InternalFoldername', 2);
insert into files values (13, 2,'\testpath6\\ExternalFoldername', 3);
insert into filetypes values (1,'Internal');
insert into filetypes values (2,'External');
i want to have SQL to fetch results like below
client_id InternalPath ExternalPath
----------- ------------------------------ ------------------------------
1 \testpath9\\InternalFoldername \testpath2\\ExternalFoldername
Try this sql statement,
select t1.internal_client as client_id, t1.InternalPath as InternalPath,
t2.ExternalPath as ExternalPath from
(select client_id as internal_client,fiilepath as InternalPath from files where filetypeid=1 ) t1
left join
(select client_id as external_client, fiilepath as ExternalPath from files where filetypeid=2 )t2
on t1.internal_client = t2.external_client
where t1.InternalPath IS NOT NULL and t2.ExternalPath IS NOT NULL

Check constraint based on information in another table

Given two tables:
TableA
(
id : primary key,
type : tinyint,
...
)
TableB
(
id : primary key,
tableAId : foreign key to TableA.id,
...
)
There is a check constraint on TableA.type with permitted values of (0, 1, 2, 3). All other values are forbidden.
Due to the known limitations, records in TableB can exist only when TableB.TableAId references the record in TableA with TableA.type=0, 1 or 2 but not 3. The latter case is forbidden and leads the system into an invalid state.
How can I guarantee that in such case the insert to TableB will fail?
Cross-table constraint using an empty indexed view:
Tables
CREATE TABLE dbo.TableA
(
id integer NOT NULL PRIMARY KEY,
[type] tinyint NOT NULL
CHECK ([type] IN (0, 1, 2, 3))
);
CREATE TABLE dbo.TableB
(
id integer NOT NULL PRIMARY KEY,
tableAId integer NOT NULL
FOREIGN KEY
REFERENCES dbo.TableA
);
The 'constraint view'
-- This view is always empty (limited to error rows)
CREATE VIEW dbo.TableATableBConstraint
WITH SCHEMABINDING AS
SELECT
Error =
CASE
-- Error condition: type = 3 and rows join
WHEN TA.[type] = 3 AND TB.id = TA.id
-- For a more informative error
THEN CONVERT(bit, 'TableB cannot reference type 3 rows in TableA.')
ELSE NULL
END
FROM dbo.TableA AS TA
JOIN dbo.TableB AS TB
ON TB.id = TA.id
WHERE
TA.[type] = 3;
GO
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.TableATableBConstraint (Error);
Online demo:
-- All succeed
INSERT dbo.TableA (id, [type]) VALUES (1, 1);
INSERT dbo.TableA (id, [type]) VALUES (2, 2);
INSERT dbo.TableA (id, [type]) VALUES (3, 3);
INSERT dbo.TableB
(id, tableAId)
VALUES
(1, 1),
(2, 2);
-- Fails
INSERT dbo.TableB (id, tableAId) VALUES (3, 3);
-- Fails
UPDATE dbo.TableA SET [type] = 3 WHERE id = 1;
This is similar in concept to the linked answer to Check constraints that ensures the values in a column of tableA is less the values in a column of tableB, but this solution is self-contained (does not require a separate table with more than one row at all times). It also produces a more informational error message, for example:
Msg 245, Level 16, State 1
Conversion failed when converting the varchar value 'TableB cannot reference type 3 rows in TableA.' to data type bit.
Important notes
The error condition must be completely specified in the CASE expression to ensure correct operation in all cases. Do not be tempted to omit conditions implied by the rest of the statement. In this example, it would be an error to omit TB.id = TA.id (implied by the join).
The SQL Server query optimizer is free to reorder predicates, and makes no general guarantees about the timing or number of evaluations of scalar expressions. In particular, scalar computations can be deferred.
Completely specifying the error condition(s) within a CASE expression ensures the complete set of tests is evaluated together, and no earlier than correctness requires. From an execution plan perspective, this means the Compute Scalar associated with the CASE tests will appear on the indexed view delta maintenance branch:
The light shaded area highlights the indexed view maintenance region; the Compute Scalar containing the CASE expression is dark-shaded.

SQL - How to INSERT a foreign key as a value for a column

I know this is rather basic, and i've searched for answers for quite some time, but I'm troubled.
I don't know how to make my coding readable on here but here it is.
Here's the query for making the table in question:
CREATE TABLE customer
( customer_id INT NOT NULL CONSTRAINT customer_pk PRIMARY KEY IDENTITY,
first_name VARCHAR(20) NOT NULL,
surname VARCHAR(20) NOT NULL,
dob DATETIME NOT NULL,
home_address VARCHAR(50) NOT NULL,
contact_number VARCHAR(10) NOT NULL,
referrer_id INT NULL FOREIGN KEY REFERENCES customer(customer_id),
);
And here's the problem code:
--fill customer table
INSERT INTO customer
VALUES ( 'Harold', 'Kumar', '2010-07-07 14:03:54', '3 Blue Ln, Perth', 0812391245, NULL )
INSERT INTO customer
VALUES ( 'Bingo', 'Washisnameoh', '2010-09-21 12:30:07', '3 Red St, Perth', 0858239471, NULL )
INSERT INTO customer
VALUES ( 'John', 'Green', '2010-11-07 14:13:34', '4 Blue St, Perth', 0423904823, NULL )
INSERT INTO customer
VALUES ( 'Amir', 'Blumenfeld', '2010-11-01 11:03:04', '166 Yellow Rd, Perth', 0432058323, NULL)
INSERT INTO customer
VALUES ( 'Hank', 'Green', '2010-07-07 16:04:24', '444 Orange Crs, Perth', 0898412429, 8)
(Specifically the line with the 8 value at the end.)
When executing the second query it responds with this:
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted
with the FOREIGN KEY SAME TABLE constraint
"FK_customer_referr__5772F790". The conflict occurred in database
"master", table "dbo.customer", column 'customer_id'. The statement
has been terminated.
Appreciate your help with this.
1)
You have a primary key on customer_id - and your insert statements do not have value for customer id
2)
You have a self referencing foreign key in the form of referrer_id referring to customer_id.
When you are inserting a record with referrer_id which is not null, in your case which is '8', make sure you already inserted a record with customer_id '8'
How do you know that the referrer_id is supposed to be 8 ??
What you need to do is catch the value of the customer_id inserted, and then used that in your second query:
DECLARE #referToID INT
INSERT INTO dbo.Customer(first_name, surname, dob, home_address, contact_number, referrer_id)
VALUES ('Harold', 'Kumar', '2010-07-07 14:03:54', '3 Blue Ln, Perth', 0812391245, NULL)
SELECT #ReferToID = SCOPE_IDENTITY() ; -- catch the newly given IDENTITY ID
INSERT INTO dbo.Customer(first_name, surname, dob, home_address, contact_number, referrer_id)
VALUES ('Hank', 'Green', '2010-07-07 16:04:24', '444 Orange Crs, Perth', 0898412429, #ReferToID)
I don't know which row you want to refer to (you didn't specify) - but I hope you understand the mechanism:
insert the new row into your table
get the newly inserted ID by using SCOPE_IDENTITY
insert the next row which refers to that first row and use that value returned by SCOPE_IDENTITY
Update: if you really want to have a given row reference itself (strange concept.....), then you'd need to do it in two steps:
insert the new row into your table
get the newly inserted ID by using SCOPE_IDENTITY
update that row to set the referrer_id
Something like this:
DECLARE #NewCustomerID INT
INSERT INTO dbo.Customer(first_name, surname, dob, home_address, contact_number)
VALUES ('Hank', 'Green', '2010-07-07 16:04:24', '444 Orange Crs, Perth', 0898412429)
SELECT #NewCustomerID = SCOPE_IDENTITY() ; -- catch the newly given IDENTITY ID
UPDATE dbo.Customer
SET referrer_id = #NewCustomerID
WHERE customer_id = #NewCustomerID
The only problem you have here is the identity must have a seed value which can be like Identity(1,1) where the first 1 is the starting point and the send 1 is the auto seed number...the re run your insert statement

XQuery adding or replacing attribute in single SQL update command

I have a Table with an XML column,
I want to update the xml to insert attribute or to change the attribute value if the attribute already exists.
Let's say the starting xml is: < d />
Inserting:
UPDATE Table
set XmlCol.modify('insert attribute att {"1"} into /d[1]')
Changing:
UPDATE Table
set XmlCol.modify('replace value of /d[1]/#att with "1"')
insert will fail if the attribute already exists, replace will fail if the attribute doesn't exists.
I have tried to use 'if' but I don't think it can work, there error I get: "XQuery [modify()]: Syntax error near 'attribute', expected 'else'."
IF attempt
UPDATE Table
set XmlCol.modify('if empty(/d[1]/#att)
then insert attribute att {"1"} into /d[1]
else replace value of /d[1]/#att with "1"')
Currently I select the xml into a variable and then modify it using T-SQL and then updating the column with new xml, this requires me to lock the row in a transaction and is probably more expensive for the DB.
From what I can tell, you can't do this with single statement. You can use the exist() method to accomplish that with two update statements.
DECLARE #TestTable TABLE
(
Id int,
XmlCol xml
);
INSERT INTO #TestTable (Id, XmlCol)
VALUES
(1, '<d att="1" />'),
(2, '<d />'),
(3, '<d att="3" />');
SELECT * FROM #TestTable;
UPDATE #TestTable
SET XmlCol.modify('replace value of /d[1]/#att with "1"')
WHERE XmlCol.exist('(/d[1])[not(empty(#att))]') = 1;
UPDATE #TestTable
SET XmlCol.modify('insert attribute att {"1"} into /d[1]')
WHERE XmlCol.exist('(/d[1])[empty(#att)]') = 1;
SELECT * FROM #TestTable;
The output from the final select is:
Id XmlCol
----------- -------------------
1 <d att="1" />
2 <d att="1" />
3 <d att="1" />
There is a slightly better way than Tommys:
DECLARE #TestTable TABLE
(
Id int,
XmlCol xml
);
INSERT INTO #TestTable (Id, XmlCol)
VALUES
(1, '<UserSettings> </UserSettings>'),
(2, '<UserSettings><timeout>3</timeout> </UserSettings>'),
(3, '<UserSettings> </UserSettings>');
UPDATE #TestTable
SET XmlCol.modify('replace value of (/UserSettings/timeout/text())[1] with "1"')
WHERE Id = 3 and XmlCol.exist('/UserSettings/timeout') = 1;
IF ##ROWCOUNT=0
UPDATE #TestTable
SET XmlCol.modify('insert <timeout>5</timeout> into (/UserSettings)[1] ')
WHERE Id = 3;
SELECT * FROM #TestTable;
The solution is a combination of Tommys and simple SQL and required only 1 SQL UPDATE if the column exist. Tommys always recuire two updates.

Resources