Not allowing column values other than what is found in other table - sql-server

I have 2 tables
Table A
Column A1 Column A2 and
Table B
Column B1 Column B2
Column A1 is not unique and not the PK, but I want to put a constraint on column B1 that it cannot have values other than what is found in Column A1, can it be done?

It cannot be done using FK. Instead you can use a check constraint to see if B value is available in A.
Example:
alter table TableB add constraint CK_BValueCheck check dbo.fn_ValidateBValue(B1) = 1
create function dbo.fn_ValidateBValue(B1 int)
returns bit as
begin
declare #ValueExists bit
select #ValueExists = 0
if exists (select 1 from TableA where A1 = B1)
select #ValueExists = 1
return #ValueExists
end

You can not have dynamic constraint to limit the values in Table B. Instead you can either have trigger on TableB or you need to limit all inserts or updates on TbaleB to select values from Column A only:
Insert into TableB
Select Col from Table where Col in(Select ColumnA from TableA)
or
Update TableB
Set ColumnB= <somevalue>
where <somevalue> in(Select columnA from TableA)
Also, I would add its a very design practice and can not guarantee accuracy all the time.

Long way around but you could add an identity to A and declare the PK as iden, A1.
In B iden would just be an integer (not identity).
You asked for any other ways.
Could create a 3rd table that is a FK used by both but that does not assure B1 is in A.

Here's the design I'd go with, if I'm free to create tables and triggers in the database, and still want TableA to allow multiple A1 values. I'd introduce a new table:
create table TableA (ID int not null,A1 int not null)
go
create table UniqueAs (
A1 int not null primary key,
Cnt int not null
)
go
create trigger T_TableA_MaintainAs
on TableA
after insert, update, delete
as
set nocount on
;With UniqueCounts as (
select A1,COUNT(*) as Cnt from inserted group by A1
union all
select A1,COUNT(*) * -1 from deleted group by A1
), CombinedCounts as (
select A1,SUM(Cnt) as Cnt from UniqueCounts group by A1
)
merge into UniqueAs a
using CombinedCounts cc
on
a.A1 = cc.A1
when matched and a.Cnt = -cc.Cnt then delete
when matched then update set Cnt = a.Cnt + cc.Cnt
when not matched then insert (A1,Cnt) values (cc.A1,cc.Cnt);
And test it out:
insert into TableA (ID,A1) values (1,1),(2,1),(3,2)
go
update TableA set A1 = 2 where ID = 1
go
delete from TableA where ID = 2
go
select * from UniqueAs
Result:
A1 Cnt
----------- -----------
2 2
Now we can use a genuine foreign key from TableB to UniqueAs. This should all be relatively efficient - the usual FK mechanisms are available between TableB and UniqueAs, and the maintenance of this table is always by PK reference - and we don't have to needlessly rescan all of TableA - we just use the trigger pseudo-tables.

Related

SQL SSMS CREATE TABLE use value from another column

I have an exercise to create 2 tables in one code. I am creating two tables and now what I need is to use a value from the first table in the second table. Here is how it looks like:
CREATE TABLE a(
[hours]int NOT NULL,
)
CREATE TABLE b(
[example] varchar(60) NOT NULL PRIMARY KEY,
[simple_name] AS (CASE WHEN example = 'name' THEN 40
WHEN example = 'name2' THEN 45 END) /*THIS IS WHAT I WANT TO DO: * [a](hours) */,
)
How can I use the hours from the fist table in the second table? What I want is to multiply 40 or 45 by hours from the first table.
I think a view could be your answer:
CREATE TABLE a ([hours]int NOT NULL);
CREATE TABLE b ([example] varchar(60) NOT NULL PRIMARY KEY)
GO
CREATE VIEW v as
SELECT
[example],
[simple_name] = CASE
WHEN example = 'name' THEN 40
WHEN example = 'name2' THEN 45
END
* a.hours
FROM b
CROSS JOIN a -- this is a cross join and perhaps it has to be replaced with some other types of join: inner, left, right, full?
And then use the view as a regular table:
select * from v

Show all and only rows in table 1 not in table 2 (using multiple columns)

I have one table (Table1) that has several columns used in combination: Name, TestName, DevName, Dept. When each of these 4 columns have values, the record is inserted into Table2. I need to confirm that all of the records with existing values in each of these fields within Table1 were correctly copied into Table 2.
I have created a query for it:
SELECT DISTINCT wr.Name,wr.TestName, wr.DEVName ,wr.Dept
FROM table2 wr
where NOT EXISTS (
SELECT NULL
FROM TABLE1 ym
WHERE ym.Name = wr.Name
AND ym.TestName = wr. TestName
AND ym.DEVName = wr.DEVName
AND ym. Dept = wr. Dept
)
My counts are not adding up, so I believe that this is incorrect. Can you advise me on the best way to write this query for my needs?
You can use the EXCEPT set operator for this one if the table definitions are identical.
SELECT DISTINCT ym.Name, ym.TestName, ym.DEVName, ym.Dept
FROM table1 ym
EXCEPT
SELECT DISTINCT wr.Name, wr.TestName, wr.DEVName, wr.Dept
FROM table2 wr
This returns distinct rows from the first table where there is not a match in the second table. Read more about EXCEPT and INTERSECT here: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/set-operators-except-and-intersect-transact-sql?view=sql-server-2017
Your query should do the job. It checks anything that are in Table1, but not Table2
SELECT ym.Name, ym.TestName, ym.DEVName, ym.Dept
FROM Table1 ym
WHERE NOT EXISTS (
SELECT 1
FROM table2
WHERE ym.Name = Name AND ym.TestName = TestName AND ym.DEVName = DEVName AND ym. Dept = Dept
)
If the structure of both tables are the same, EXCEPT is probably simpler.
IF OBJECT_ID(N'tempdb..#table1') IS NOT NULL drop table #table1
IF OBJECT_ID(N'tempdb..#table2') IS NOT NULL drop table #table2
create table #table1 (id int, value varchar(10))
create table #table2 (id int)
insert into #table1(id, value) VALUES (1,'value1'), (2,'value2'), (3,'value3')
--test here. Comment next line
insert into #table2(id) VALUES (1) --Comment/Uncomment
select * from #table1
select * from #table2
select #table1.*
from #table1
left JOIN #table2 on
#table1.id = #table2.id
where (#table2.id is not null or not exists (select * from #table2))

sql insert query clause

I have a table A which contains fields
ChangeID DistributionID OutletBrandID
and Table B contains
ID DistributionID OutletBrandID
I need to insert data in table A from table B only if the distributionID and OutletBrandID combination doesn't exist already. Therefore I can't simply use the IN clause as it needs to be a combination.
Assuming that ChangeID and ID should match between the tables:
INSERT INTO TableA (ChangeID, DistributionID, OutletBrandID)
SELECT b.ID, b.DistributionID, b.OutletBrandID FROM TableB b
LEFT OUTER JOIN TableA a ON a.DistributionID=b.DistributionID
AND a.OutletBrandID = b.OutletBrandID
WHERE
a.OutletBrandID IS NULL
AND
a.DistributionID IS NULL

Insert into table from another table after sorting data of first table

I am doing something like this in SP,
insert into #FilteredTbl select * from #MasterTbl
but now problem is that I want data of #MasterTbl sorted before adding into #FilteredTbl,
How can I do that ?
I found this question and the answer was this :
INSERT INTO #FilteredTbl
SELECT *
FROM #MasterTbl AS tbl
ORDER BY tbl.OrderbyColumn
but this code didn't work , Any kind of help is appreciated
this is the code for the test :
declare #table1 table
(
a1 int ,
b1 varchar(20)
)
insert into #table1 values
(1,'maza') , (2,'sari') , (3,'ahvaz') , (4,'rasht')
declare #table2 table
(
a2 int ,
b2 varchar(20)
)
insert into #table2
select *
from #table1 t1
order by t1.b1
select *
from #table2
I am supposed to see the column b2 be sorted , but it is not
As I've written in my comment - tables in relational databases are unsorted by nature.
This means that your test is incorrect - since your select statement does not have an order by clause it's basically returning the rows in an arbitrary order.
A correct test would include an identity column in #table2 that will record the order of the record inserted into this table:
declare #table1 table
(
a1 int ,
b1 varchar(20)
)
insert into #table1 values
(1,'maza') , (2,'sari') , (3,'ahvaz') , (4,'rasht')
declare #table2 table
(
a2 int ,
b2 varchar(20),
c2 int identity(1,1)
)
insert into #table2
select *
from #table1 t1
order by t1.b1
select *
from #table2
order by c2
Results:
a2 b2 c2
----------- -------------------- -----------
3 ahvaz 1
1 maza 2
4 rasht 3
2 sari 4
If the order by clause in the insert into...select statement was t1.a1, the values of a2 and c2 would be identical.
It is not necessary in T-SQL to add an identity column to your table to sort it. The problem here is that you're inserting the data in a somewhat specific order, but not retrieving it in a specific order in the SELECT. I added a single line at the end of your query that would do the trick: ORDER BY B1 (i.e. B1 or some other column). You could also get fancier with functions like ROW_NUMBER to do things like simulate identity columns on the fly, without actually having one on the storage side. Doing an INSERT in the presence of an identity column would indeed mean that you could return the results in the same order as they were inserted - but that wouldn't mean anything if there are duplicates in B1, since the ORDER BY in the INSERT would also be undefined. An identity column helps in the case of poorly specified queries, because without it the table would be treated as a heap and a SELECT without a proper ORDER BY might return results in different, unpredictable orders from one query to the next. If the ORDER BY clause in the retrieval query is specific enough, however, then the identity column becomes irrelevant. For example, if there are dupes in B1 but each combination of B1 and A1 is unique, then a clause like ORDER BY B1, A1 is specific enough to guarantee a well-defined order, one that doesn't change unpredictably from one query to the next. It's a moot point in the case of the 4 values you've provided, however, since all of them are unique for both B1 and A1; ORDER BY B1 should get the job done here. I hope that helps.
declare #table1 table
(
a1 int ,
b1 varchar(20)
)
insert into #table1 values
(1,'maza') , (2,'sari') , (3,'ahvaz') , (4,'rasht')
declare #table2 table
(
a2 int ,
b2 varchar(20)
)
insert into #table2
select *
from #table1 t1
order by t1.b1
select *
from #table2
ORDER BY B1
A select has NO guaranteed order if there is no sort?
select * from #table2 has NO sort
If you want THAT select sorted then simple
select * from #table2 order by b2
The order you put them in does NOT effect the order they come out

SQL Server 2008: Unique constraint for values non-related with columns

I have a simple problem. How can I add a unique constraint for a table, without relating the values to their columns? For example, I have this table
ID_A ID_B
----------
1 2
... ...
In that example, I have the record (1,2). For me, (1,2) = (2,1). So i don't want to allow my database to store both values. I know I can accomplish it using, triggers or checks and functions. But i was wondering if there is any instruccion like
CREATE UNIQUE CONSTRAINT AS A SET_CONSTRAINT
You could write a view like that:
select 1 as Dummy
from T t1
join T t2 on t1.ID1 = t2.ID2 AND t1.ID2 = t2.ID1 --join to corresponding row
cross join TwoRows
And create a unique index on Dummy. TwoRows is a table that contains two rows with arbitrary contents. It is supposed to make the unique index fail if there ever is a row in it. Any row in this view indicates a uniqueness violation.
You can do this using Instead of Insert trigger.
Demo
Table Schema
CREATE TABLE te(ID_A INT,ID_B INT)
INSERT te VALUES ( 1,2)
Trigger
Go
CREATE TRIGGER trg_name
ON te
instead OF INSERT
AS
BEGIN
IF EXISTS (SELECT 1
FROM inserted a
WHERE EXISTS (SELECT 1
FROM te b
WHERE ( ( a.id_a = b.id_b
AND a.id_b = b.id_a )
OR ( a.id_a = b.id_a
AND a.id_b = b.id_b ) )))
BEGIN
PRINT 'duplciate record'
ROLLBACK
END
ELSE
INSERT INTO te
SELECT Id_a,id_b
FROM inserted
END
SELECT * FROM te
Insert Script
INSERT INTO te VALUES (2,1) -- Duplicate
INSERT INTO te VALUES (1,2) --Duplicate
INSERT INTO te VALUES (3,2) --Will work

Resources