I am trying to learn normalization on a EDIT: SQL Server 15.x database. My initial table would look like this:
| TeacherId(PK) | FirstName | LastName | Course | GroupCode |
| 1 | Smith | Jane | AAA,BBB | A1,A2,B2 |
| 2 | Smith | John | BBB,CCC | A2,B1,B2 |
After normalization I ended up with three tables
| TeacherId(PK) | FirstName | LastName | | Course(PK)(FK) | | GroupCode(PK)(FK) |
| 1 | Smith | Jane | | AAA | | A1 |
| 2 | Smith | John | | BBB | | A2 |
| CCC | | B1 |
| B2 |
and two joining tables
| TeacherId(PK) | Course(PK) | | TeacherId(PK) | GroupCode(PK) |
| 1 | AAA | | 1 | A1 |
| 1 | BBB | | 1 | A2 |
| 2 | BBB | | 1 | B2 |
| 2 | CCC | | 2 | A2 |
| 2 | B1 |
| 2 | B2 |
The code for these tables is:
CREATE TABLE [dbo].[Teachers]
(
[TeacherId] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[FirstName] [nchar](10) NOT NULL,
[LastName] [nchar](10) NOT NULL
)
GO
CREATE TABLE [dbo].[Courses]
(
[Course] [nchar](10) NOT NULL PRIMARY KEY
)
GO
CREATE TABLE [dbo].[GroupCodes]
(
[GroupCode] [nchar](10) NOT NULL PRIMARY KEY
)
GO
CREATE TABLE [dbo].[TeacherCourse]
(
[TeacherId] [int] NOT NULL,
[Course] [nchar](10) NOT NULL,
PRIMARY KEY (TeacherId, Course),
CONSTRAINT FK_TeacherCourses FOREIGN KEY (TeacherId)
REFERENCES Teachers(TeacherId),
CONSTRAINT FK_TeachersCourse FOREIGN KEY (Course)
REFERENCES Courses(Course)
)
GO
CREATE TABLE [dbo].[TeacherGroup]
(
[TeacherId] [int] NOT NULL,
[GroupCode] [nchar](10) NOT NULL,
PRIMARY KEY (TeacherId, GroupCode),
CONSTRAINT FK_TeacherGroups FOREIGN KEY (TeacherId)
REFERENCES Teachers(TeacherId),
CONSTRAINT FK_TeachersGroup FOREIGN KEY (GroupCode)
REFERENCES GroupCodes(GroupCode)
)
GO
INSERT INTO Teachers(FirstName,LastName)
VALUES ('Smith','Jane'),('Smith','John')
GO
INSERT INTO Courses(Course)
VALUES ('AAA','BBB','CCC')
GO
INSERT INTO GroupCodes(GroupCode)
VALUES ('A1','A2','B1','B2')
GO
INSERT INTO TeacherCourse(TeacherId,Course)
VALUES ('1','AAA'),('1','BBB'),('2','BBB'),('2','CCC')
GO
INSERT INTO TeacherGroup(TeacherId,GroupCode)
VALUES ('1','A1'),('1','A2'),('1','B2'),('2','A2'),('2','B1'),('2','B2')
GO
I need to come up with a query that returns each teacher entry in the same form as my initial table:
| 1 | Smith | Jane | AAA,BBB | A1,A2,B2 |
So, my question is: how do I make the two joins?
I have tried
SELECT t.TeacherId AS TeacherId, t.FName AS FirstName, t.LName AS LastName,
c.Course AS Course, g.GroupCode AS GroupCode
FROM TeacherCourse tc, TeacherGroup tg
JOIN Teachers t ON tc.TeacherId=t.TeacherId
JOIN Courses c ON tc.Course=c.Course
JOIN Teachers t ON tg.TeacherId=t.TeacherId
JOIN GroupCodes g ON tg.GroupCode=g.GroupCode
ORDER BY TeacherId
with no success.
Thank you.
EDIT:
I managed to sort out the concatenation using this idea enter link description here
In case this helps anyone, the select code I used is:
SELECT t.TeacherId AS TeacherId, t.FirstName AS FirstName, t.LastName AS LastName,
(SELECT STUFF((SELECT DISTINCT ', ' + LTRIM(RTRIM(tc.Course)) FROM TeacherCourse tc
INNER JOIN Courses c ON tc.Course = c.Course WHERE tc.TeacherId = t.TeacherId
FOR XML PATH('')),1,1,(''))) AS Courses,
(SELECT STUFF((SELECT DISTINCT ', ' + LTRIM(RTRIM(tg.GroupCode)) FROM TeacherGroup tg
INNER JOIN GroupCodes g ON tg.GroupCode = g.GroupCode WHERE tg.TeacherId = t.TeacherId
FOR XML PATH('')),1,1,(''))) AS Group_Codes
FROM TeacherCourse tc
JOIN TeacherGroup tg ON
tc.TeacherId = tg.TeacherId
JOIN Teachers t ON
tc.TeacherId=t.TeacherId AND tg.TeacherId=t.TeacherId
JOIN Courses c ON
tc.Course=c.Course
JOIN GroupCodes g ON
tg.GroupCode=g.GroupCode
/* WHERE clause can be inserted here specific results. Example:
WHERE T.LastName = 'Jane'
*/
GROUP BY t.TeacherId, t.FirstName, t.LastName
ORDER BY TeacherId
Thank you very much for all your help.
Your normalization scheme seems to be incorrect.
You create 3 tables (teacher, course, group) - this is correct.
Now you'd create a table which creates a relation between course and group. This course_group table must be treated as one more entity. This entity stores what course the group should study.
Then you'd create a table which creates a relation between teacher and course_group. And this sets what teacher will study this group with this course.
Also I'd create one more table - the table which sets the relation between teacher and course. This table will store the data about what course the teacher may teach for. This is a pattern, not entity, table. It is used for client-side consistency checking. If a teacher is assigned to teach for a course which not present in this table then the operator will receive a warning that this course is not one registered for this teacher. But the operator may assign nevertheless.
It needs a lot of cleaning to solve your problem. please let's go step by step.
I created your tables here again. At the end, I changed your code in some critical places.
Please keep in mind :) MySQL != MSSQL Server
CREATE TABLE Teachers (
TeacherId int NOT NULL AUTO_INCREMENT,
FirstName char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
LastName char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (TeacherId)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE Courses (
Course char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (Course)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE GroupCodes (
GroupCode char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (GroupCode)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE TeacherGroup (
TeacherId int NOT NULL,
GroupCode char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (TeacherId,GroupCode),
KEY FK_TeachersGroup (GroupCode),
CONSTRAINT FK_TeacherGroups FOREIGN KEY (TeacherId) REFERENCES Teachers (TeacherId),
CONSTRAINT FK_TeachersGroup FOREIGN KEY (GroupCode) REFERENCES GroupCodes (GroupCode)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE TeacherCourse (
TeacherId int NOT NULL,
Course char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (TeacherId,Course),
KEY FK_TeachersCourse (Course),
CONSTRAINT FK_TeacherCourses FOREIGN KEY (TeacherId) REFERENCES Teachers (TeacherId),
CONSTRAINT FK_TeachersCourse FOREIGN KEY (Course) REFERENCES Courses (Course)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
End Result:
Here I corrected some joins, put in group by to summarize the result set by TeacherId, FirstName, and LastName.
In the select list I aggregated group codes and course by GROUP_CONCAT() function which does the job well.
SELECT t.TeacherId AS TeacherId, t.FirstName AS FirstName, t.LastName AS LastName,
GROUP_CONCAT(DISTINCT c.Course separator ', ') as courses,
GROUP_CONCAT(DISTINCT g.GroupCode separator ', ') as group_codes
FROM TeacherCourse tc
JOIN TeacherGroup tg ON
tc.TeacherId = tg.TeacherId
JOIN Teachers t ON
tc.TeacherId=t.TeacherId AND tg.TeacherId=t.TeacherId
JOIN Courses c ON
tc.Course=c.Course
JOIN GroupCodes g ON
tg.GroupCode=g.GroupCode
GROUP BY TeacherId, FirstName, LastName
ORDER BY TeacherId;
Resulting Screenshoot is your initial dataset:
Related
i am not expert with indexing.
I would like to create a composite key unique constraint. How to create if one of the column is nullable?
CREATE UNIQUE CLUSTERED INDEX [IX_User_Email_PeronsId] ON [dbo].[User]
(
[Email] ASC,
[PersonId] ASC
)
GO
PersonId is nullable column
In fact you can create a unique clustered index with nullable columns, just tried it:
USE tempdb;
CREATE TABLE dbo.[User]
(
Email nvarchar(50) NOT NULL,
PersonID int NULL
);
CREATE UNIQUE CLUSTERED INDEX [IX_User_Email_PersonID]
ON dbo.[User]
(
Email,
PersonID
);
Commands completed successfully.
You didn't mention what exactly you are trying to achieve, so let me have a guess. I think you want to achieve, that the combination of Email and PersonID has to be unique, except for the rows where PersonID is null.
In this case, using a clustered index is not useful, but you can use a filtered nonclustered index:
USE tempdb;
-- Create the test table
CREATE TABLE dbo.[User]
(
Email nvarchar(50) NOT NULL,
PersonID int NULL
);
-- Create a filtered unique index
CREATE UNIQUE NONCLUSTERED INDEX [IX_User_Email_PersonID]
ON dbo.[User]
(
Email,
PersonID
)
WHERE PersonID IS NOT NULL;
-- Insert test data
INSERT INTO dbo.[User]
(
Email,
PersonId
)
VALUES
( N'a#mydomain.com', 1 ),
( N'b#mydomain.com', 2 ),
( N'b#mydomain.com', 3 ),
( N'c#mydomain.com', 3 ),
( N'c#mydomain.com', 4 ),
( N'd#mydomain.com', NULL ),
( N'e#mydomain.com', NULL ),
( N'f#mydomain.com', NULL );
Test whether you can insert which data:
-- Works
INSERT INTO dbo.[User] ( Email, PersonId )
VALUES ( N'c#mydomain.com', 5 );
-- Fails
INSERT INTO dbo.[User] ( Email, PersonId )
VALUES ( N'c#mydomain.com', 5 );
-- Works
INSERT INTO dbo.[User] ( Email, PersonId )
VALUES ( N'f#mydomain.com', NULL );
-- Works
INSERT INTO dbo.[User] ( Email, PersonId )
VALUES ( N'f#mydomain.com', NULL );
Content of the table after step-by-step execution:
| Email | PersonID |
| ----------------- | -------- |
| a#mydomain.com | 1 |
| b#mydomain.com | 2 |
| b#mydomain.com | 3 |
| c#mydomain.com | 3 |
| c#mydomain.com | 4 |
| d#mydomain.com | NULL |
| e#mydomain.com | NULL |
| f#mydomain.com | NULL |
| c#mydomain.com | 5 |
| f#mydomain.com | NULL |
| f#mydomain.com | NULL |
I have a SQL table:
Table code:
CREATE TABLE Gender
(
GenderID int primary key identity,
Gender char(20)
)
I would like to ignore or remove duplicate rows in Gender, whilst maintaining the auto incrementation of GenderID (specified in my create table code), so that it results in:
----------------
| 1 | Male |
----------------
| 2 | Female |
----------------
My attempt:
DELETE
FROM Gender
WHERE GenderID NOT IN (
SELECT MIN(GenderID)
FROM Gender
GROUP BY Gender)
Returns:
image
You can create new table and load the values as given below.
DECLARE #datasource TABLE(id int identity(1,1), gender CHAR(10))
INSERT INTO #datasource(gender)
SELECT * FROM
(
VALUES
('male'),
('male'),
('male'),
('female'),
('female'),
('female')
) as t(gender)
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT null)) as id, gender
INTO NewTableName
FROM #datasource
group by gender
Result Set
+----+-----------+
| id | gender |
+----+-----------+
| 1 | female |
| 2 | male |
+----+-----------+
I suggest you drop and re-create the table.
Add a unique index with IGNORE_DUP_KEY=ON to prevent duplicates.
[Can I set ignore_dup_key on for a primary key?
I'm using MariaDB 10.3. Is there a way to delete history for a specific record? I have a situation where once I delete a record, I'm under contract to remove all records (including historical ones).
Consider the following table:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) DEFAULT NULL,
`start_trxid` bigint(20) unsigned GENERATED ALWAYS AS ROW START,
`end_trxid` bigint(20) unsigned GENERATED ALWAYS AS ROW END,
PRIMARY KEY (`id`,`end_trxid`),
PERIOD FOR SYSTEM_TIME (`start_trxid`, `end_trxid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1 WITH SYSTEM VERSIONING
And consider the following commands run against that table:
insert into users (name, email) values ('cory', 'name#corycollier.com'), ('bob', 'bob#gmail.com');
UPDATE users set name='Cory' where id=1;
UPDATE users set name='Cory Collier' where id=1;
UPDATE users set name='Cory C' where id=1;
UPDATE users set name='Cory' where id=1;
That leaves me with the following history:
select * from (select * from users FOR SYSTEM_TIME BETWEEN (NOW() - INTERVAL 1 DAY) and (NOW())) as history where id=1;
+----+--------------+-----------------------------+-------------+----------------------+
| id | name | email | start_trxid | end_trxid |
+----+--------------+-----------------------------+-------------+----------------------+
| 1 | cory | corycollier#corycollier.com | 697377 | 697384 |
| 1 | Cory Collier | corycollier#corycollier.com | 697384 | 697391 |
| 1 | Cory C | corycollier#corycollier.com | 697391 | 697394 |
| 1 | Cory | corycollier#corycollier.com | 697394 | 697401 |
I don't have a way to delete history for that user. I'd like to.
Ok so I have 2 new tables: Client and Contract. I'm gonna focus on the first one as they have the same structure. Client looks like:
+-----------+---------+
| client_id | name |
+-----------+---------+
| Value 1 | Value 2 |
+-----------+---------+
And created like this:
CREATE TABLE Client (
client_id varchar2(15) NOT NULL,
name varchar2(100) NOT NULL,
CONSTRAINT Client_pk PRIMARY KEY (client_id)
) ;
Also I have an old table: old_contracts looking like:
+------------+----------+------+
| contractid | clientid | name |
+------------+----------+------+
| con1 | cli1 | n1 |
| con2 | cli2 | n2 |
| con3 | cli2 | n2 |
| con4 | cli3 | n3 |
| con5 | cli3 | n3 |
+------------+----------+------+
Defined:
CREATE TABLE old_contracts(
contractid varchar2(15) NOT NULL
clientid varchar2(15) NOT NULL,
name varchar2(100) NOT NULL
) ;
I want to take the data from old_contract and insert it into Client.
This old_contracts table has rows with duplicate clientid (one client can have more than one contract) but I don't want to have duplicates on Client table so I am doing this:
INSERT INTO Client (
client_id,
name
) SELECT DISTINCT
clientid,
name
FROM old_contracts;
to not get duplicates. Anyway, I'm getting this error:
Error SQL: ORA-00001: unique constraint (USER.CLIENT_PK) violated
00001.00000 - "unique constraint (%s.%s) violated"
What's going on? I believe the DISTINCT keyword was going to do the thing.
I've also tried adding a WHERE NOT EXISTS clause as suggested in related posts (i.e. this one), but the result I'm getting is the same error.
Most likely, the name is not always the same for a given clientid.
Try this instead:
INSERT INTO Client (
client_id,
name
) SELECT clientid,
max(name)
FROM old_contracts
GROUP BY clientid;
I'm trying to write a stored procedure which takes in a table type parameter and inserts into two tables at once.
I have an entity table which is a base table holding the id for various tables, below is the entity table and a sample Site table.
------ Entity Table ------------------------------------------
| Id | bigint | NOT NULL | IDENTITY(1,1) | PRIMARY KEY
| TypeId | tinyint | NOT NULL |
| Updated | datetime | NULL |
| Created | datetime | NOT NULL |
| IsActive | bit | NOT NULL |
------- Site Table ---------------------------------------
| EntityId | bigint | NOT NULL | PRIMARY KEY
| ProductTypeCode | nvarchar(8) | NOT NULL | PRIMARY KEY
| SupplierCode | nvarchar(8) | NOT NULL | PRIMARY KEY
| Name | nvarchar(128) | NOT NULL |
| Description | nvarchar(max) | NULL |
And here is my table type used to pass into the stored procedure
------- Site Table Type ----------------------------------
| EntityTypeId | tinyint | NOT NULL |
| ProductTypeCode | nvarchar(8) | NOT NULL | PRIMARY KEY
| SupplierCode | nvarchar(8) | NOT NULL | PRIMARY KEY
| Name | nvarchar(128) | NOT NULL |
| Description | nvarchar(max) | NULL |
The idea is that I will pass in a table type parameter into the stored procedure and insert multiple rows at once to save looping inserting one row at a time.
Here's what I have so far
CREATE PROCEDURE InsertSites
#Sites SiteTypeTable READONLY
AS
BEGIN
-- Insert into Entity & Site Tables here, using the Id from the Entity Table in the Site table
INSERT INTO Entity (TypeId, Updated, Created, IsActive)
OUTPUT [inserted].[Id], S.ProductTypeCode, S.SupplierCode, S.Name, S.Description
INTO Site
SELECT EntityTypeId, NULL, GETDATE(), 1
FROM #Sites S
END
I've read about using insert and output together but cannot get this to work. I've also read about merge but also cannot get this to work.
Any help or pointers you can give will be greatly appreciated.
Thanks
Neil
---- Edit ----
Could I do something like this? I'm not sure how to finish this off...
CREATE PROCEDURE InsertSites
#Sites SiteTypeTable READONLY
AS
BEGIN
-- First insert enough rows into Entity table, saving the inserted Ids to a table variable
DECLARE #InsertedOutput TABLE (EntityId bigint)
INSERT INTO Entity (TypeId, Updated, Created, IsActive)
OUTPUT [inserted].[id]
INTO #InsertedOutput
SELECT EntityTypeId, NULL, GETDATE(), 1
FROM #Sites S
-- Use the Ids in #InsertedOutput against the rows in #Sites to insert into Sites
END