T-SQL Self Join in combination with aggregate function - tsql

I have the following table:
CREATE TABLE [dbo].[Tree](
[AutoID] [int] IDENTITY(1,1) NOT NULL,
[Category] [varchar](10) NULL,
[Condition] [varchar](10) NULL,
[Description] [varchar](50) NULL,
CONSTRAINT [PK_Tree] PRIMARY KEY CLUSTERED
(
[AutoID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
The data looks like this:
INSERT INTO [Test].[dbo].[Tree]
([Category]
,[Condition]
,[Description])
VALUES ('1','Alpha','Type 1')
INSERT INTO [Test].[dbo].[Tree]
([Category]
,[Condition]
,[Description])
VALUES ('1','Alpha','Type 1')
INSERT INTO [Test].[dbo].[Tree]
([Category]
,[Condition]
,[Description])
VALUES ('2','Alpha','Type 2')
INSERT INTO [Test].[dbo].[Tree]
([Category]
,[Condition]
,[Description])
VALUES ('2','Alpha','Type 2')
GO
I try now to do the following:
SELECT Category,COUNT(*) as CategoryCount FROM Tree where Condition = 'Alpha' group by Category
However, I also wish to get the Description for each Element. I tried several subqueries, self joins etc., but I always come to the problem that the subquery cannot return more than one record.
The problem is caused by a poor database design which I cannot change and I have run out of ideas as to how to get this done in a single query.

If you need description, you have to include it in the aggregate
SELECT Category ,
[Description] ,
COUNT(*) AS CategoryCount
FROM Tree
WHERE Condition = 'Alpha'
GROUP BY Category ,
[Description]

Related

Check to see if a row or an ID already exist before doing a mass insert on a table

I'm trying to an insert from one database table to another like this:
INSERT INTO factoryDB_Development.dbo.engineList
SELECT *
FROM factoryDB_Staging.dbo.engineList
WHERE engineID NOT IN (SELECT engineID
FROM factoryDB_Development.dbo.engineList)
But I am getting this error:
Cannot insert duplicate key in object 'dbo.engineList'
I am assume it's because that ID already exists in factoryDB_Development.dbo.engineList.
When I checked online at the Microsoft site, I just saw this:
To work around this issue, enable trace flag 8690 to disable the Spool operation
Is there a way to check to see if a row or ID already exists before doing the insert?
Thanks!
If I understood your problem correctly, you want to avoid inserting record in target table if that record with the same ID already exists.
If the ID column in question is a primary key, you can simply set IGNORE_DUP_KEY = ON by recreating the index.
Below is the sample example.
CREATE TABLE [dbo].[Source](
[ID] [int] NULL,
[Name] [varchar](5) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[Target](
[ID] [int] NOT NULL,
[Name] [varchar](5) NULL,
PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = ON, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
--input sample values in source
INSERT INTO [Source] VALUES (1, 'A') , (1, 'A') , (2, 'B')
--insert data from source to target
INSERT INTO Target
SELECT * FROM Source
DROP TABLE SOURCE
DROP TABLE TARGET

Does the order in which you add records to DbSets in EF has any effect in raising Foreign Key conflicts?

In working with EF Database first, I am not being able to insert records into a specific table. I get the INSERT statement conflicted with the Foreign Key... error.
My question:
Assuming EF requires you to perform only a single dbContext.SaveChanges() at the end of your changing process, does the order you add records to the DbSets has effect on this ? Or it does not matter since the DbContext keeps track of all the changes made to the data and is itself responsible to apply them in the proper order ?
To illustrate, for 3 tables A, B and C, where B has a FK on A and C a FK on B, if I manually loop over all records I have to add to C, B and A - and do DbSet<C>.Add() before I issue DbSet<B>.Add() is problematic ?
Is it required to issue a dbContext.SaveChanges() for each table additions ?
Adding the create sql script for all 3 tables:
CREATE TABLE [dbo].[A](
[Id] [int] NOT NULL,
[a1] [int] IDENTITY(1,1) NOT NULL,
[a2] [nvarchar](50) NULL,
CONSTRAINT [PK_A] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [IX_A] UNIQUE NONCLUSTERED
(
[a1] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[B](
[Id] [int] IDENTITY(1,1) NOT NULL,
[a1] [int] NULL,
[b1] [int] NULL,
[b2] [nvarchar](50) NULL,
CONSTRAINT [PK_B] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [IX_B] UNIQUE NONCLUSTERED
(
[b1] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[B] WITH CHECK ADD CONSTRAINT [FK_B_A1] FOREIGN KEY([a1])
REFERENCES [dbo].[A] ([a1])
GO
ALTER TABLE [dbo].[B] CHECK CONSTRAINT [FK_B_A1]
GO
CREATE TABLE [dbo].[C](
[Id] [int] IDENTITY(1,1) NOT NULL,
[b1] [int] NULL,
[c1] [int] NULL,
[c2] [nvarchar](50) NULL,
CONSTRAINT [PK_C] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[C] WITH CHECK ADD CONSTRAINT [FK_C_B] FOREIGN KEY([b1])
REFERENCES [dbo].[B] ([b1])
GO
ALTER TABLE [dbo].[C] CHECK CONSTRAINT [FK_C_B]
GO
I know what the error aforementioned means, but I could not find yet which data is incorrectly linked/inconsistent.
And by the way, why such errors (INSERT statement conflicted with the Foreign Key...) are not "caught" in GetValidationErrors() ?
Update:
The problem must be the way I am trying to populate the tables. But how should this be done then ?
For the scenario below:
private static void Main(string[] args)
{
//Validate_Xml_Against_Xsd.ValidateXmlAgainstXsd example = new Validate_Xml_Against_Xsd.ValidateXmlAgainstXsd();
//example.Run();
TestEntities t = new TestEntities();
A a = new A { a1 = 1000, a2 = "aaa" };
B b = new B { a1 = 1000, b1 = 87141, b2 = "bbb" };
C c = new C { b1 = 87141, c1 = 1, c2 = "ccc" };
t.C.Add(c);
t.B.Add(b);
t.A.Add(a);
//changing the order does not fix it
//t.A.Add(a);
//t.B.Add(b);
//t.C.Add(c);
int s = t.SaveChanges();
Console.WriteLine(s);
}
How should the tables be populated ? Both my DbSet.Add() attempts fail with
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_B_A1". The conflict occurred in database "Test", table "dbo.A", column 'a1'.
The statement has been terminated.
No, the order is not important. The problem here is that you seem to have foreign keys but you lack navigation properties and you never set relations between entities.
Thus, when B is persisted, it doesn't point to any valid A. A correct code should look somehow like
A a = new A() { ....
B b = new B() { a = a, ...
assuming that your navigation property from B to A is called a.

T-SQL Insert into multiple linked tables using a condition and without using a cursor

T-SQL Insert into multiple linked tables using a condition and without using a cursor.
Hello,
I have the following tables
CREATE TABLE [dbo].[TestMergeQuote](
[uid] [uniqueidentifier] NOT NULL,
[otherData] [nvarchar](50) NULL,
CONSTRAINT [PK_TestMergeQuote] PRIMARY KEY CLUSTERED
(
[uid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[TestMergeQuote] ADD CONSTRAINT [DF_TestMergeQuote_uid] DEFAULT (newid()) FOR [uid]
--=============
CREATE TABLE [dbo].[TestMergeClient](
[id] [int] IDENTITY(1,1) NOT NULL,
[otherData] [nvarchar](50) NULL,
CONSTRAINT [PK_TestMergeClient] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
--==============
CREATE TABLE [dbo].[TestMergeDocument](
[id] [int] NOT NULL,
[uid_quote] [uniqueidentifier] NOT NULL,
[id_owner] [int] NOT NULL,
[id_keeper] [int] NULL,
[otherData] [nvarchar](50) NULL
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[TestMergeDocument] WITH CHECK ADD CONSTRAINT [FK_TestMergeDocument_TestMergeClient_Keeper] FOREIGN KEY([id_keeper])
REFERENCES [dbo].[TestMergeClient] ([id])
GO
ALTER TABLE [dbo].[TestMergeDocument] CHECK CONSTRAINT [FK_TestMergeDocument_TestMergeClient_Keeper]
GO
ALTER TABLE [dbo].[TestMergeDocument] WITH CHECK ADD CONSTRAINT [FK_TestMergeDocument_TestMergeClient_Owner] FOREIGN KEY([id_owner])
REFERENCES [dbo].[TestMergeClient] ([id])
GO
ALTER TABLE [dbo].[TestMergeDocument] CHECK CONSTRAINT [FK_TestMergeDocument_TestMergeClient_Owner]
GO
ALTER TABLE [dbo].[TestMergeDocument] WITH CHECK ADD CONSTRAINT [FK_TestMergeDocument_TestMergeQuote] FOREIGN KEY([uid_quote])
REFERENCES [dbo].[TestMergeQuote] ([uid])
GO
ALTER TABLE [dbo].[TestMergeDocument] CHECK CONSTRAINT [FK_TestMergeDocument_TestMergeQuote]
GO
AND also table X with other various data.
I want to insert into these three tables the data that already exists in these 3 tables, but giving it different id's, and also replacing some of the data within the X table.
It's a sort of a "copy the data from last year", but add new info.
The condition is that id_keeper is sometimes null, and no insert should be done for it.
I am aware that I have to use OUTPUT and MERGE, but I have no ideea how to achieve something this complex.
The CRUDE code for this using a cursor would be:
DECLARE #OldIdDocument INT, #NewIdDocument INT
DECLARE #OldIdOwner INT, #NewIdOwner INT
DECLARE #OldIdKeeper INT, #NewIdKeeper INT
DECLARE #OldIdQuote UNIQUEINDETIFIER, #NewIdQuote UNIQUEINDETIFIER,
INSERT INTO TestMergeQuote(otherData)
SELECT TOP(1) otherData FROM TestMergeQuote WHERE uid = #OldIdQuote
SET #NewIdQuote = ##IDENTITY
INSERT INTO TestMergeClient(otherData)
SELECT TOP(1) otherData FROM TestMergeClient WHERE uid = #OldIdOwner
SET #NewIdOwner = ##IDENTITY
IF(#OldIdKeeper IS NOT NULL)
BEGIN
INSERT INTO TestMergeClient(otherData)
SELECT TOP(1) otherData FROM TestMergeClient WHERE uid = #OldIdKeeper
SET #NewIdKeeper = ##IDENTITY
END
INSERT INTO TestMergeDocument([uid_quote], [id_owner] , [id_keeper], otherData)
SELECT TOP(1) #NewIdQuote , #NewIdOwner , #NewIdKeeper ,otherData FROM TestMergeDocument WHERE uid = #OldIdDocument
SET #NewIdDocument = ##IDENTITY
You shouldn't have to use a cursor. What I would try is to first pump the data out into separate tables so you can manipulate the data to your heart's content.
Something like this first:
select * into TestMergeQuote_Temp from TestMergeQuote
That will make a new table with the data you want to copy. Of course you can add a where clause to filter the data so you aren't copying a very large table.
Then you can add values, change values, delete values on the _Temp versions.
When you are ready you can insert the data back. Of course you might have to turn auto id off if you have a primary key that is auto-incrementing. Or if you just want new id's and don't want to make id's manually, you should be able to insert the new records just fine and have new id's created for you.
But as a start, try pumping the data into new tables and then worry about inserting after that.

joining tables in view

I am now concerned that I have not normalised the tables correctly as now I see no way to join them;
I have 1 table with 2 columns called Questions, and another table called answers with 10 columns, one for the userId then 9 columns which hold all the answers (int) for each user.
Everything is working well for inserts and updates, however I am having a heck of a time trying to create a view which will show all questions, each user and each of their responses to each question.
Question table;
CREATE TABLE [dbo].[Questions](
[questionId] [int] IDENTITY(1,1) NOT NULL,
[question] [nvarchar](max) NULL,
CONSTRAINT [PK_Questions] PRIMARY KEY CLUSTERED
(
[questionId] ASC
)WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
) ON [PRIMARY]
Answer table;
CREATE TABLE [dbo].[Answers](
[empID] [nvarchar](10) NOT NULL,
[q1] [int] NULL,
[q2] [int] NULL,
[q3] [int] NULL,
[q4] [int] NULL,
[q5] [int] NULL,
[q6] [int] NULL,
[q7] [int] NULL,
[q8] [int] NULL,
[q9] [int] NULL,
CONSTRAINT [PK_Answers] PRIMARY KEY CLUSTERED
(
[empID] ASC
)WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
) ON [PRIMARY]
Nothing I have tried works, so any ideas from you all that can help me avoid re-doing the tables would be so greatly appreciated!
Thanks in advance for any help.
Alex
Seems to me that your answer table should have a foreign key to the question table. Unless an answer can refer to multiple questions, in which case I would say you should consider something like a mapping/relation table which has FKs to both answer and question tables.
I think you might need to look into pivot tables to accomplish your task.

How to identify one-to-one (one-to-?) relationship using INFORMATION_SCHEMA or sys views in Sql Server

The Problem Domain http://www.freeimagehosting.net/uploads/6e7aa06096.png
So here is the problem.. Using TSQL and INFORMATION_SCHEMA or sys views, how can I identify a 1:0-1 relationship, such as FK_BaseTable_InheritedTable?
In a concrete example, imagine a simple DTO - FK_JoinTable_ParentTable would be rendered as a collection of JoinTable on the ParentTable object, whereas FK_BaseTable_InheritedTable would either be rendered as an InheritedTable object on the BaseTable object (inheritance was a bad choice for example, I know, but not going back).
The best I can come up with is one-to-many, same as FK_JoinTable_ParentTable. I have tried a lot of approaches, including (trying to) comparing keys and am coming up short.
Here is the script. Problem is to, USING INFO_SCHEMA or sys views , identify FK_JoinTable_ParentTable and FK_JoinTable_Child as one-to-many and FK_BaseTable_InheritedTable as one-to-one/none.
The litmus is being able to differentiate FK_BaseTable_InheritedTable from FK_JoinTable_ParentTable
CREATE TABLE [dbo].[Child](
[ChildId] [int] NOT NULL,
CONSTRAINT [PK_Child] PRIMARY KEY CLUSTERED
(
[ChildId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[ParentTable](
[ParentId] [int] NOT NULL,
CONSTRAINT [PK_ParentTable] PRIMARY KEY CLUSTERED
(
[ParentId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[JoinTable](
[PId] [int] NOT NULL,
[CId] [int] NOT NULL,
CONSTRAINT [PK_JoinTable] PRIMARY KEY CLUSTERED
(
[PId] ASC,
[CId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[InheritedTable](
[InheritedId] [int] NOT NULL,
CONSTRAINT [PK_InheritedTable] PRIMARY KEY CLUSTERED
(
[InheritedId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[BaseTable](
[BaseId] [int] NOT NULL,
CONSTRAINT [PK_BaseTable] PRIMARY KEY CLUSTERED
(
[BaseId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[JoinTable] WITH CHECK ADD CONSTRAINT [FK_JoinTable_Child] FOREIGN KEY([CId])
REFERENCES [dbo].[Child] ([ChildId])
ALTER TABLE [dbo].[JoinTable] CHECK CONSTRAINT [FK_JoinTable_Child]
ALTER TABLE [dbo].[JoinTable] WITH CHECK ADD CONSTRAINT [FK_JoinTable_ParentTable] FOREIGN KEY([PId])
REFERENCES [dbo].[ParentTable] ([ParentId])
ALTER TABLE [dbo].[JoinTable] CHECK CONSTRAINT [FK_JoinTable_ParentTable]
ALTER TABLE [dbo].[BaseTable] WITH CHECK ADD CONSTRAINT [FK_BaseTable_InheritedTable] FOREIGN KEY([BaseId])
REFERENCES [dbo].[InheritedTable] ([InheritedId])
ALTER TABLE [dbo].[BaseTable] CHECK CONSTRAINT [FK_BaseTable_InheritedTable]
EDIT: fixed small typo in the foreign key query that does not affect the answer but returns the wrong unique table
The original conclusion I came to but was never able to properly execute is:
If the base side columns of the
constraint fit into any one of the
base side table's unique constraints
AND the column count is the same then
it is a one-to-one/none.
The same could be said if the all of the FK columns fit one of the
unique constraints which has more columns IF all of those columns comprise yet
another FK, but this is outside the scope of the question at hand.
It was an execution issue on my part.
I worked out a solution that produces correct results but in that I am no SQL guru I fear it is rather kludgy. Perhaps I will put it up as another question for review.
Anyway - this script will identify all the 1:? relationships in the db.
/*
Will identify immediate 1:? fk relationships
*/
-- TODO: puzzle: work out the set-based equivalent
SET NOCOUNT ON
BEGIN -- Get a table full of PK and UQ columns
DECLARE #unique_keys TABLE
(
-- contains PK and UQ indexes
[schema_name] NVARCHAR(128),
table_name NVARCHAR(128),
index_name NVARCHAR(128),
column_id INT,
column_name NVARCHAR(128),
is_primary_key BIT,
is_unique_constraint BIT,
is_unique BIT
)
INSERT INTO #unique_keys
(
[schema_name],
table_name,
index_name,
column_id,
column_name,
is_primary_key,
is_unique_constraint,
is_unique
)
-- selects PK and UQ indexes
SELECT S.name AS [schema_name],
T.name AS table_name,
IX.name AS index_name,
IC.column_id,
C.name AS column_name,
IX.is_primary_key,
IX.is_unique_constraint,
IX.is_unique
FROM sys.tables AS T
INNER JOIN sys.schemas AS S ON T.schema_id = S.schema_id
INNER JOIN sys.indexes AS IX ON T.object_id = IX.object_id
INNER JOIN sys.index_columns AS IC ON IX.object_id = IC.object_id
AND IX.index_id = IC.index_id
INNER JOIN sys.columns AS C ON IC.column_id = C.column_id
AND IC.object_id = C.object_id
WHERE ( IX.is_unique = 1 )
AND ( T.name <> 'sysdiagrams' )
AND IX.is_unique = 1
ORDER BY schema_name,
table_name,
index_name,
C.column_id
END
BEGIN -- Get a table full of FK columns
DECLARE #foreign_key_columns TABLE
(
constraint_name NVARCHAR(128),
base_schema_name NVARCHAR(128),
base_table_name NVARCHAR(128),
base_column_id INT,
base_column_name NVARCHAR(128),
unique_schema_name NVARCHAR(128),
unique_table_name NVARCHAR(128),
unique_column_id INT,
unique_column_name NVARCHAR(128)
)
INSERT INTO #foreign_key_columns
(
constraint_name,
base_schema_name,
base_table_name,
base_column_id,
base_column_name,
unique_schema_name,
unique_table_name,
unique_column_id,
unique_column_name
)
SELECT FK.name AS constraint_name,
S.name AS base_schema_name,
T.name AS base_table_name,
C.column_id AS base_column_id,
C.name AS base_column_name,
US.name AS unique_schema_name,
UT.name AS unique_table_name,
UC.column_id AS unique_column_id,
UC.name AS unique_column_name
FROM sys.tables AS T
INNER JOIN sys.schemas AS S ON T.schema_id = S.schema_id
INNER JOIN sys.foreign_keys AS FK ON T.object_id = FK.parent_object_id
INNER JOIN sys.foreign_key_columns AS FKC ON FK.object_id = FKC.constraint_object_id
INNER JOIN sys.columns AS C ON FKC.parent_object_id = C.object_id
AND FKC.parent_column_id = C.column_id
INNER JOIN sys.columns AS UC ON FKC.referenced_object_id = UC.object_id
AND FKC.referenced_column_id = UC.column_id
INNER JOIN sys.tables AS UT ON FKC.referenced_object_id = UT.object_id
INNER JOIN sys.schemas AS US ON UT.schema_id = US.schema_id
WHERE ( T.name <> 'sysdiagrams' )
ORDER BY base_schema_name,
base_table_name
END
DECLARE #constraint_name NVARCHAR(128),
#base_schema_name NVARCHAR(128),
#base_table_name NVARCHAR(128),
#unique_schema_name NVARCHAR(128),
#unique_table_name NVARCHAR(128)
-- The foreign key side of the constraint is always singular, we need to check from the perspective
-- of the unique side of the constraint.
-- for each FK constraint in DB
DECLARE tmpC CURSOR READ_ONLY
FOR SELECT DISTINCT
constraint_name,
base_schema_name,
base_table_name,
unique_schema_name,
unique_table_name
FROM #foreign_key_columns
OPEN tmpC
FETCH NEXT FROM tmpC INTO #constraint_name, #base_schema_name, #base_table_name, #unique_schema_name, #unique_table_name
WHILE ##FETCH_STATUS = 0
BEGIN
-- get the columns in the base side of the FK constraint
DECLARE #fkc TABLE
(
column_name NVARCHAR(128)
)
DELETE FROM #fkc
INSERT INTO #fkc ( column_name )
SELECT base_column_name
FROM #foreign_key_columns
WHERE constraint_name = #constraint_name
-- check for one to one/none
-- If the base side columns of the constraint fit into any one of the base side tables unique constraints
-- AND the column count is the same then we have a one-to-one/none and should be realized as a singular
-- object reference
-- I realize that if the base side unique constraint has more columns than the unique side unique constraint
-- AND all of those columns DO represent a 1:? that would actually qualify but it seems like an edge case and
-- beyond the scope of this question.
DECLARE #uk_schema_name NVARCHAR(128),
#uk_table_name NVARCHAR(128),
#uk_index_name NVARCHAR(128),
#is_may_have_a BIT
SET #is_may_have_a = 0
-- have to open another cursor over the unique keys of the base table - i want
-- a distinct list of unique constraints for the base table
DECLARE cKey CURSOR READ_ONLY
FOR SELECT DISTINCT
[schema_name],
table_name,
index_name
FROM #unique_keys
WHERE [schema_name] = #base_schema_name
AND table_name = #base_table_name
OPEN cKey
FETCH NEXT FROM cKey INTO #uk_schema_name, #uk_table_name, #uk_index_name
WHILE ##FETCH_STATUS = 0
BEGIN
-- get the unique constraint columns
DECLARE #pkc TABLE
(
column_name NVARCHAR(128)
)
DELETE FROM #pkc
INSERT INTO #pkc ( column_name )
SELECT column_name
FROM #unique_keys
WHERE [schema_name] = #uk_schema_name
AND table_name = #uk_table_name
AND index_name = #uk_index_name
-- if count is same and columns are same
DECLARE #count1 INT, #count2 INT
SELECT #count1 = COUNT(*) FROM #fkc
SELECT #count2 = COUNT(*) FROM #pkc
IF #count1 = #count2
BEGIN
-- select all from both on name and exclude mismatches
SELECT #count1 = COUNT(*)
FROM #fkc F
FULL OUTER JOIN #pkc P ON f.column_name = p.column_name
WHERE NOT p.column_name IS NULL AND NOT f.column_name IS NULL
IF #count1 = #count2
BEGIN
-- the base side of the fk constraint corresponds exactly to
-- at least on unique constraint making it effectively 1:?
SET #is_may_have_a = 1
BREAK
END
END
FETCH NEXT FROM cKey INTO #uk_schema_name, #uk_table_name, #uk_index_name
END
CLOSE cKey
DEALLOCATE cKey
IF #is_may_have_a = 1
PRINT 'for ' + #unique_schema_name + '.' + #unique_table_name + ' constraint ' + + #constraint_name + ' is 1:? '
FETCH NEXT FROM tmpC INTO #constraint_name, #base_schema_name, #base_table_name, #unique_schema_name, #unique_table_name
END
CLOSE tmpC
DEALLOCATE tmpC
For a look at the results on a more elaborate test db see TSQL: Identify a 1:? relationship