Finding Cross Dependency Tables in SQL - sql

I need to List all the tables which depends on each other
Example
TableA has foreign Key relation to TableB
TableB has foreign Key relation to TableA
So far I got the script to find the foreign key relations for the tables but how can I filter them down to get just the cross table reference one.
my script so are is:
SELECT CAST(p.name AS VARCHAR(255)) AS [Primary Table] ,
CAST(c.name AS VARCHAR(255)) AS [Foreign Table]
FROM sysobjects f
INNER JOIN sysobjects c ON f.parent_obj = c.id
INNER JOIN sysreferences r ON f.id = r.constid
INNER JOIN sysobjects p ON r.rkeyid = p.id
INNER JOIN syscolumns rc ON r.rkeyid = rc.id
AND r.rkey1 = rc.colid
INNER JOIN syscolumns fc ON r.fkeyid = fc.id
AND r.fkey1 = fc.colid
LEFT JOIN syscolumns rc2 ON r.rkeyid = rc2.id
AND r.rkey2 = rc.colid
LEFT JOIN syscolumns fc2 ON r.fkeyid = fc2.id
AND r.fkey2 = fc.colid
WHERE f.type = 'F'
ORDER BY CAST(p.name AS VARCHAR(255))

This simple select statement returns the circular direct foreign key references in a database:
IF OBJECT_ID('dbo.d') IS NOT NULL DROP TABLE dbo.d;
IF OBJECT_ID('dbo.c') IS NOT NULL DROP TABLE dbo.c;
IF OBJECT_ID('dbo.b') IS NOT NULL
BEGIN
ALTER TABLE dbo.a DROP CONSTRAINT [dbo.a(bid)->dbo.b(bid)];
DROP TABLE dbo.b;
END
IF OBJECT_ID('dbo.a') IS NOT NULL DROP TABLE dbo.a;
CREATE TABLE dbo.a(aid INT PRIMARY KEY CLUSTERED, bid INT);
CREATE TABLE dbo.b(aid INT CONSTRAINT [dbo.b(aid)->dbo.a(aid)] REFERENCES dbo.a(aid), bid INT PRIMARY KEY CLUSTERED);
ALTER TABLE dbo.a ADD CONSTRAINT [dbo.a(bid)->dbo.b(bid)] FOREIGN KEY(bid) REFERENCES dbo.b(bid);
CREATE TABLE dbo.c(cid INT PRIMARY KEY CLUSTERED);
CREATE TABLE dbo.d(did INT PRIMARY KEY CLUSTERED, cid INT CONSTRAINT [dbo.d(cid)->dbo.c(cid)] REFERENCES dbo.c(cid));
SELECT *
FROM sys.foreign_keys fk1
JOIN sys.foreign_keys fk2
ON fk1.parent_object_id = fk2.referenced_object_id
AND fk2.parent_object_id = fk1.referenced_object_id;
From here you can join to the DMVs sys.tables and sys.columns to get the additional information like table and column names.
Two things to be aware of:
You should stop using the compatibility views. They are there to support old scripts that where written for SQL 2000 and should not be used in new development.
You can have up to 16 columns in a foreign key. Your script supports only two. However, you are not even returning the column names so you should not join to sys.columns at all, if you don't need the column names.
If you just need the names of the tables you can get away without an additional join by using this select statement instead:
SELECT
QUOTENAME(OBJECT_SCHEMA_NAME(fk1.parent_object_id))+'.'+QUOTENAME(OBJECT_NAME(fk1.parent_object_id))+
' <-> '+
QUOTENAME(OBJECT_SCHEMA_NAME(fk1.referenced_object_id))+'.'+QUOTENAME(OBJECT_NAME(fk1.referenced_object_id))
FROM sys.foreign_keys fk1
JOIN sys.foreign_keys fk2
ON fk1.parent_object_id = fk2.referenced_object_id
AND fk2.parent_object_id = fk1.referenced_object_id
AND fk1.parent_object_id < fk1.referenced_object_id;
I also added an additional condition to the WHERE clause of the query to include each pair only once.

Related

Join two tables so foreign key column data (integer) changes to text data from parent table (concerned column in parent table is not primary key)

I am a beginner with SQLite, and I am still a little unfamiliar with joining tables (and the limitations therein). I would like to know how to join 2 tables so that the column data in Table B (several columns) changes to reflect data from Table A.
CREATE TABLE "A" (
"person_id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"person_name" TEXT NOT NULL,
"email" TEXT NOT NULL
);
CREATE TABLE "B" (
"company_id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"company_name" TEXT NOT NULL,
"contact_one" INTEGER NOT NULL,
"contact_two" INTEGER,
"contact_three" INTEGER,
FOREIGN KEY("contact_one") REFERENCES "A"("person_id") ON DELETE SET NULL
FOREIGN KEY("contact_two") REFERENCES "A"("person_id") ON DELETE SET NULL
FOREIGN KEY("contact_three") REFERENCES "A"("person_id") ON DELETE SET NULL
);
How do I write a query so that the resulting table shows columns "company_name", "contact_one", "contact_two" and "contact_three" BUT with the contact tables showing the contact name rather than the integer (as it would appear in Table B).
See below an image representing Tables A and B and the desired output of SQLite query:
When I do a left join, the query succeeds only for a single contact column.
SELECT B.company_name, A.person_name
FROM B
LEFT JOIN A ON B.contact_one = A.person_id
I tried to add an "OR" to the query (I know "AND" will not work), but I get an "ambiguous column name: A.person_name" error when I try to run the query:
SELECT B.company_name, A.person_name
FROM B
LEFT JOIN A ON B.contact_one = A.person_id
OR LEFT JOIN A ON B.contact_two = A.person_id
OR LEFT JOIN A ON B.contact_three = A.person_id
How do I write the query so that I can get contact_two and contact_three also in the resulting table, with all three contacts' names displayed?
Any guidance will be greatly appreciated!
You need to join the table A 3 times like this:
SELECT B.company_name,
a1.person_name name1,
a2.person_name name2,
a3.person_name name3
FROM B
LEFT JOIN A a1 ON B.contact_one = a1.person_id
LEFT JOIN A a2 ON B.contact_two = a2.person_id
LEFT JOIN A a3 ON B.contact_three = a3.person_id

SQL count rows from two different tables where both have same foreign key from entirely different table

I have 3 tables like this:
CREATE TABLE Main
(MainId INT PRIMARY KEY IDENTITY,
Name nvarchar(50))
CREATE TABLE Foo
(FooId INT PRIMARY KEY IDENTITY,
MainId int,
FooAnotherColumn int,
FOREIGN KEY (MainId) REFERENCES Main(MainId)
)
CREATE TABLE Bar
(BarId INT PRIMARY KEY IDENTITY,
MainId INT,
BarAnotherColumn INT,
FOREIGN KEY (MainId) REFERENCES Main(MainId)
)
I want count from boo and bar tables with Name = something. Here is the query I have came so far.
DECLARE #fooCount int, #barCount int;
SELECT
#fooCount = (SELECT
(COUNT(*))
FROM Foo o
INNER JOIN Main s
ON o.MainId = s.MainId
WHERE s.Name = 'something')
SELECT
#barCount = (SELECT
(COUNT(*))
FROM Bar o
INNER JOIN Main s
ON o.MainId = s.MainId
WHERE s.Name = 'something')
SELECT
#fooCount + #barCount;
Is this can be optimized?
In terms of performance, your queries probably have the best plan. You can combine them into a single query:
SELECT f.foocount + b.barcount,
FROM (SELECT COUNT(*) as foocount,
FROM Foo o INNER JOIN
Main s
ON o.MainId = s.MainId
WHERE s.Name = 'something'
) f CROSS JOIN
(SELECT COUNT(*) as barcount
FROM Bar o INNER JOIN
Main s
ON o.MainId = s.MainId
WHERE s.Name = 'something'
) b;

Write a query using INNER JOIN AND OR

I have a form and an insert button and when i click on the button - the fields goes in to these tables (i didn't put here all the fields because they are not important for my question).
The Tables:
CREATE TABLE SafetyAct (
SafetyAct_id int identity(1,1),
Username varchar(50),
SafetyType_id int,
constraint pk_SafetyAct_id
primary key (SafetyAct_id),
constraint fk_Users_SafetyAct
foreign key(Username)
references Users(Username)
on delete cascade
)
CREATE TABLE Product (
Product_id int identity(1,1) primary key,
SafetyAct_id int,
Cause_id int,
constraint fk_SafetyAct_Product
foreign key(SafetyAct_id)
references SafetyAct(SafetyAct_id)
on delete cascade,
constraint fk_Cause_Product
foreign key(Cause_id)
references Cause(Cause_id)
on delete cascade
)
CREATE TABLE SafetyIntervention (
SafetyIntervention_id int identity(1,1) primary key,
SafetyAct_id int,
Cause_id int,
constraint fk_SafetyAct_SafetyIntervention
foreign key(SafetyAct_id)
references SafetyAct(SafetyAct_id)
on delete cascade,
constraint fk_Cause_SafetyIntervention
foreign key(Cause_id)
references Cause(Cause_id)
on delete cascade
)
CREATE TABLE Cause (
Cause_id int primary key,
Cause_name varchar(80)
)
I want to write a query that shows the fields - SafetyAct_id and Cause_name.
in the Cause_name field i have a problem because i want that the query will show me the cause name drom the Product table or from the SafetyIntervension table (of course to connect it to the Cause table because i have only the cause_id - foriegn key in these tables) and i don't know how to write INNER JOIN and OR at the same query.
I am new with this so plase be patient.
Thank you!
SELECT SA.SafetyAct_id,
C.Cause_name
FROM SafetyAct SA
LEFT JOIN Product P
ON SA.SafetyAct_id = P.SafetyAct_id
LEFT JOIN SafetyIntervention SI
ON SA.SafetyAct_id = SI.SafetyAct_id
LEFT JOIN Cause C
ON ISNULL(P.Cause_id,SI.Cause_id) = C.Cause_id
An or is easy. So, based on Lamak's code:
SELECT SA.SafetyAct_id,
C.Cause_name
FROM SafetyAct SA LEFT JOIN
Product P
ON SA.SafetyAct_id = P.SafetyAct_id LEFT JOIN
SafetyIntervention SI
ON SA.SafetyAct_id = SI.SafetyAct_id LEFT JOIN
Cause C
ON C.Cause_id = P.Cause_id OR C.Cause_Id = SI.Cause_id;
--------------------------------^
You can just use OR in the ON condition.
However, it is often challenging for a SQL engine (SQL Server included) to optimize a join when the on condition includes or or functions on the columns used for the join. Hence, the following is often more efficient:
SELECT SA.SafetyAct_id,
COALESCE(Cp.Cause_name, Csi.Cause_Name) as Cause_Name
FROM SafetyAct SA LEFT JOIN
Product P
ON SA.SafetyAct_id = P.SafetyAct_id LEFT JOIN
SafetyIntervention SI
ON SA.SafetyAct_id = SI.SafetyAct_id LEFT JOIN
Cause Cp
ON Cp.Cause_id = P.Cause_id LEFT JOIN
Cause Csi
ON Csi.Cause_Id = SI.Cause_id;
If only some of the records have a cause of either type, then add:
WHERE Cp.Cause_Id IS NOT NULL OR Csi.Cause_Id IS NOT NULL;

Find referenced field(s) of foreign key constraint

I have a table Users with a field called org_id which is a foreign key to a table organisation, with primary key field organisation_id. Knowing the table name (users) and the field name (users.org_id), is there a query that can tell me the name and field that org_id references?
I've found a Stackoverflow post similar to this where a query was provided to determine the referenced table name, but I also need to know the field name that is referenced:
SELECT c.confrelid::regclass::text AS referenced_table
,c.conname AS fk_name
,pg_get_constraintdef(c.oid) AS fk_definition
FROM pg_attribute a
JOIN pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum)
WHERE a.attrelid = '"Schema"."Users"'::regclass -- table name
AND a.attname = 'org_id' -- column name
AND c.contype = 'f'
ORDER BY conrelid::regclass::text, contype DESC;
So the above query would return the name of the table (organisation), the fk name and fk definition. Is there a way to also get the name of the field that is referenced? I know I could probably perform another query to determine the name of pk given a table but I would like to avoid performing multiple queries for this.
This query adds the referenced column(s) for the foreign key constraint:
SELECT c.confrelid::regclass::text AS referenced_table
,string_agg(f.attname, ', ') AS referenced_columns
,c.conname AS fk_name
,pg_get_constraintdef(c.oid) AS fk_definition
FROM pg_attribute a
JOIN pg_constraint c ON (c.conrelid, c.conkey[1]) = (a.attrelid, a.attnum)
JOIN pg_attribute f ON f.attrelid = c.confrelid
AND f.attnum = ANY (confkey)
WHERE a.attrelid = '"Schema"."Users"'::regclass -- table name
AND a.attname = 'org_id' -- column name
AND c.contype = 'f'
GROUP BY c.confrelid, c.conname, c.oid;
A fk constraint can reference multiple columns. That's the reason for the aggregate function string_agg() in the query.

Using tsql how to determine which column of a compound foreign key corresponds to which column of a compound primary/unique key?

I want to put together a sql query that will show me which columns of compound foreign keys correspond to which columns of their primary/unique key.
For example, if the database had
CREATE TABLE TA
(
B int,
C int
)
ALTER TABLE TA ADD CONSTRAINT [UK_CB] UNIQUE NONCLUSTERED
(
C ASC,
B ASC
)
CREATE TABLE TB
(
D int,
E int
)
ALTER TABLE TB ADD CONSTRAINT [FK_TA] FOREIGN KEY (D, E) REFERENCES TA(C, B)
Then I would want the query to return
| Pk/Uk | PK/UK Column | FK | FK Column |
--------------------------------------------
| UK_CB | C | FK_TA | D |
| UK_CB | B | FK_TA | E |
If possible, I would prefer to use only INFORMATION_SCHEMA views.
I am aware of INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE, but that only gives me the columns in the particular constraint.
I could join to INFORMATION_SCHEMA.COLUMNS and do something with ordinal position, but that makes the false assumption that key reference are in the ordinal order.
Is there a way to query the database to get the column correspondence between compound keys?
I can't find a verified column order in the INFORMATION_SCHEMA views. However, if you can use the system catalog views (instead of the INFORMATION_SCHEMA views) you could do the following (Here's a sql fiddle). I don't see a direct way to relate it to the pk / uk, but you can find the columns on the pk/uk table:
SELECT
fk.name as constraint_name,
sfkc.constraint_column_id,
sfkc.parent_column_id,
fkt.name as fk_table,
fkc.name as fk_column,
sfkc.referenced_column_id,
pkt.name as pk_table,
pkc.name as pk_column
FROM sys.foreign_keys AS fk
JOIN sys.foreign_key_columns AS sfkc
ON fk.object_id = sfkc.constraint_object_id
JOIN sys.tables AS fkt
ON sfkc.parent_object_id = fkt.object_id
JOIN sys.columns as fkc
ON fkt.object_id = fkc.object_id
AND sfkc.parent_column_id = fkc.column_id
JOIN sys.tables AS pkt
ON sfkc.referenced_object_id = pkt.object_id
JOIN sys.columns as pkc
ON pkt.object_id = pkc.object_id
AND sfkc.referenced_column_id = pkc.column_id