Filter out one to one relationships - sql-server

I have the following table
CREATE TABLE Advisors (
AdvisorID int not null IDENTITY(1,1) PRIMARY KEY,
FirstName varchar(50) not null,
LastName varchar(50) not null,
);
CREATE TABLE Students (
StudentID int not null IDENTITY(1,1) PRIMARY KEY,
AdvisorID int FOREIGN KEY REFERENCES Advisors(AdvisorID),
FirstName varchar(50) not null,
LastName varchar(50) not null,
);
INSERT INTO Advisors (FirstName,LastName) VALUES ('Joy', 'Frank');
INSERT INTO Advisors (FirstName,LastName) VALUES ('Franklin', 'Johnson');
INSERT INTO Advisors (FirstName,LastName) VALUES ('Mary', 'Ronald');
INSERT INTO Students (FirstName, LastName, AdvisorID) VALUES ('Danny', 'Sean', 3);
INSERT INTO Students (FirstName, LastName, AdvisorID) VALUES ('Ashford', 'combs', 1);
INSERT INTO Students (FirstName, LastName, AdvisorID) VALUES ('George', 'Stoute', 3);
INSERT INTO Students (FirstName, LastName, AdvisorID) VALUES ('Ben', 'Johnson', 3);
I am trying to write a query that will filter out any advisor with more than one student
My query:
SELECT Students.StudentID, Students.FirstName, Students.LastName, Advisors.AdvisorID, Advisors.FirstName,Advisors.LastName
FROM Students
LEFT JOIN Advisors ON Students.AdvisorID = Advisors.AdvisorID
WHERE Students.AdvisorID <= 2
I am doing it wrong. My query does filter out the advisors with more than one student because I know which Advisor ID has one student.
I am trying to filter out one too many relationships and have the one too one relationship in the results

select AdvisorID
from students
group by AdvisorID
having count(s.StudentID) <= 1

Thanks for helping me out, I got the answer to my question
SELECT Advisors.AdvisorID,Students.TotalStudentsAssigned, Advisors.FirstName + ' ' +
Advisors.LastName AS AdvisorName
FROM Advisors
INNER JOIN
(
SELECT adv.AdvisorID, COUNT(*) AS TotalStudentsAssigned
FROM Advisors adv
LEFT JOIN Students s
ON adv.AdvisorID = s.AdvisorID
GROUP BY adv.AdvisorID
HAVING COUNT(*) < 2
) Students
ON Advisors.AdvisorID = Students.AdvisorID;

Related

How can insert a subquery into the table using Declare

I am trying to code and run a full table so it can run sucessfully without error.
I am also making the sql table, by using Address and Person Table.
Here is the error
Msg 102, Level 15, State 1, Line 159
Incorrect syntax near '='.
/*
Foundation Lab
Creating Objects in SQL
Purpose: Create objects in SQL and set all the properties to achieve a robust well performing database: tables, views, sequences
Each comment describes a task
Write SQL statement(s) for the task under the comment
The script must be able to run repeatedly (which is why you will need to drop tables so that you can recreate them)
The SQL script meu be able to run completely and correctly on another database.
*/
/*
Tidy up any previous runs by dropping (deleting) any objects that this script creates.
Use the IF EXISTS clause for brevity.
*/
/*
Drop an existing table "Address"
Drop an existing View "AddressView"
Drop an existing table "Person"
Drop an existing view "Person"
*/
DROP TABLE IF EXISTS [dbo].[Person]
DROP TABLE IF EXISTS [dbo].[Address]
DROP VIEW IF EXISTS[dbo].[PersonView]
DROP VIEW IF EXISTS [dbo].[AddressView]
GO
/*
Create an Address table with columns:
AddressKey INT
HouseNumber INT
StreetName VARCHAR(100)
PostCode VARCHAR(8)
Set the AddressKey column as the primary key
Set the AddressKey column as an identity column
All columns must be not NULL
*/
/*
Add three rows to the table for the addresses
with the following values for HouseNumber, StreetName and PostCode
*/
CREATE TABLE Address
(
AddressKey INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
HouseNumber INT NOT NULL,
StreetName VARCHAR(100) NOT NULL,
PostCode VARCHAR(8) NOT NULL
);
/*
Add three rows to the table for the addresses
with the following values for HouseNumber, StreetName and PostCode
32, 'Acacia Avenue', 'SL1 1AA'
52, 'Cambray Road', 'SW12 9ES'
10, 'Downing Street', 'SW1A 2AA'
Use a single statement, not three statements
*/
INSERT INTO Address
(
HouseNumber,
StreetName,
PostCode
)
VALUES
(10, 'Downing Street', 'SW1A 2AA'),
(52, 'Cambray Road', 'SW12 9ES'),
(32, 'Acacia Avenue', 'SL1 1AA');
-- Check that the data in the Address tables is as expected
SELECT *
FROM Address;
/*
Create a view, named AddressView, that has HouseNumber and PostCode columns only
*/
GO
CREATE VIEW AddressView
AS
SELECT HouseNumber,
PostCode
FROM Address;
GO
-- Check the view works as expected.
SELECT *
FROM AddressView;
/*
Create a Person table with columns:
PersonKey INT
AddressKey INT
FirstName VARCHAR(100),
LastName VARCHAR(100)
DateOfBirth DATE
Set the PersonKey column as the primary key.
Set the PersonKey column as an identity column
All columns must be NOT NULL.
*/
CREATE TABLE Person
(
Personkey INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
AddressKey INT NOT NULL,
FirstName VARCHAR(100) NOT NULL,
LastName VARCHAR(100) NOT NULL,
DateofBirth DATE NOT NULL
);
-- Check that the rows are now in the Person table
SELECT *
FROM Person;
/*
Create a foreign key relationship
so that AddressKey in the Person table references the AddressKey column in the Address table.
*/
--ALTER TABLE Person ADD FOREIGN KEY (AddressKey) REFERENCES Address (AddressKey);
ALTER TABLE Person
ADD
FOREIGN KEY (AddressKey) REFERENCES Address (AddressKey);
/*
Add two sample rows to the table
* Boris Johnson in Downing Street (use variables)
* Theresa May in Cambray Road (use a SQL sub query)
*/
--'1959-01-19')
DECLARE #AddressKeyPM INT;
SELECT #AddressKeyPM = a.AddressKey FROM Address a WHERE a.PostCode = 'SW1A 2AA' AND a.HouseNumber = 10;
SELECT #AddressKeyPM
INSERT INTO Person
(
AddressKey,
FirstName,
LastName,
DateofBirth
)
VALUES
(#AddressKeyPM, 'Boris', 'Johnson', '1964-06-19');
INSERT INTO Person
(
AddressKey,
FirstName,
LastName,
DateofBirth
)
VALUES
(#AddressKeyPM = a.AddressKey FROM AddressKey a WHERE a.Postcode = 'SL1 1AA' AND a.HouseNumber = 32, 'Theresa', 'May', '1956-10-01');
-- Check that the Person table now has these two rows of data.
SELECT *
FROM Person;
/*
Show that the foreign key constraint is active
Try insert a row with a value for Addresskey that is not in the Address table
e.g. Kier Starmer, born on '1963-01-19', with AddressKey 12345
Note the error message
*/
/*INSERT INTO Person
(AddressKey, FirstName, LastName, DateofBirth)
VALUES
('12345', 'Kier', 'Starmer', '1963-01-19');
*/
-- It won't work because it needed updating with the main table with is the address table. --
-- Create a PersonView view that the FirstName and LastName (but not the DateOfBirth)
GO
CREATE VIEW PersonView
AS
SELECT FirstName,
LastName
FROM Person;
GO
/*
Opens the person view before altering
*/
SELECT *
FROM PersonView;
GO
-- Extend the view to include the House Number and PostCode from the Address table
SELECT * FROM Address a INNER JOIN Person p on p.AddressKey = a.AddressKey
GO
-- Alter the view to show only Boris and Theresa First, Last, HouseNumber and Postcode
Alter VIEW dbo.PersonView
AS
SELECT p.FirstName, p.LastName, a.HouseNumber, a.PostCode
FROM Person p INNER JOIN Address a on p.AddressKey = a.AddressKey
-- Check that the view is working correctly
GO
SELECT *
FROM PersonView;
So If you would like to teach me how to correct and fix the coding, then that would be great!
If you want to write a sub query then you can try this
INSERT INTO Person
(
AddressKey,
FirstName,
LastName,
DateofBirth
)
SELECT a.AddressKey, 'Theresa', 'May', '1956-10-01' FROM Address a WHERE a.PostCode = 'SL1 1AA' AND a.HouseNumber = 32;
Try this:
INSERT INTO Person
SELECT
AddressKey = (SELECT AddressKey FROM Address WHERE Postcode = 'SW1A 2AA' AND HouseNumber = 10)
, FirstName = 'Boris'
, LastName = 'Johnson'
, DateofBirth = '1964-06-19'
;
INSERT INTO Person
SELECT
AddressKey = (SELECT AddressKey FROM Address WHERE Postcode = 'SL1 1AA' AND HouseNumber = 32)
, FirstName = 'Theresa'
, LastName = 'May'
, DateofBirth = '1956-10-01'
;

How to get records which has more than one entries on another table

An example scenario for my question would be:
How to get all persons who has multiple address types?
Now here's my sample data:
CREATE TABLE #tmp_1 (
ID uniqueidentifier PRIMARY KEY
, FirstName nvarchar(max)
, LastName nvarchar(max)
)
CREATE TABLE #tmp_2 (
SeedID uniqueidentifier PRIMARY KEY
, SomeIrrelevantCol nvarchar(max)
)
CREATE TABLE #tmp_3 (
KeyID uniqueidentifier PRIMARY KEY
, ID uniqueidentifier REFERENCES #tmp_1(ID)
, SeedID uniqueidentifier REFERENCES #tmp_2(SeedID)
, SomeIrrelevantCol nvarchar(max)
)
INSERT INTO #tmp_1
VALUES
('08781F73-A06B-4316-B6A5-802ED58E54BE', 'AAAAAAA', 'aaaaaaa'),
('4EC71FCE-997C-46AA-B119-6C5A2545DDC2', 'BBBBBBB', 'bbbbbbb'),
('B0726ABF-738E-48BC-95CB-091C9D731A0E', 'CCCCCCC', 'ccccccc'),
('6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', 'DDDDDDD', 'ddddddd')
INSERT INTO #tmp_2
VALUES
('4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'Value1'),
('4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'Value2'),
('6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'Value3')
INSERT INTO #tmp_3
VALUES
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdgdfbgcv'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'asdfadsas'),
(NEWID(), '08781F73-A06B-4316-B6A5-802ED58E54BE', '4C891FE9-60B6-41BE-A64B-11A9A8B58AB2', 'xxxxxeeeeee'),
(NEWID(), '4EC71FCE-997C-46AA-B119-6C5A2545DDC2', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'sdfsdfsd'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'zxczxcz'),
(NEWID(), 'B0726ABF-738E-48BC-95CB-091C9D731A0E', '6F6EFED6-8EA0-4F70-A63F-6A103D0A71BD', 'eerwerwe'),
(NEWID(), '6C6CE284-A63C-49D2-B2CC-F25C9CBC8FB8', '4D10B4EC-C929-4D6B-8C94-11B680CF2221', 'vbcvbcvbcv')
Which gives you:
This is my attempt:
SELECT
t1.*
, Cnt -- not really needed. Just added for visual purposes
FROM #tmp_1 t1
LEFT JOIN (
SELECT
xt.ID
, COUNT(1) Cnt
FROM (
SELECT
#tmp_3.ID
, COUNT(1) as Cnt
FROM #tmp_3
GROUP BY ID, SeedID
) xt
GROUP BY ID
) t2
ON t1.ID = t2.ID
WHERE t2.Cnt > 1
Which gives:
ID FirstName LastName Cnt
B0726ABF-738E-48BC-95CB-091C9D731A0E CCCCCCC ccccccc 2
08781F73-A06B-4316-B6A5-802ED58E54BE AAAAAAA aaaaaaa 2
Although this gives me the correct results, I'm afraid that this query is not the right way to do this performance-wise because of the inner queries. Any input is very much appreciated.
NOTE:
A person can have multiple address of the same address types.
"Person-Address" is not the exact use-case. This is just an example.
The Cnt column is not really needed in the result set.
The way you have named your sample tables and data help little in understanding the problem.
I think you want all IDs which have 2 or more SomeIrrelevantCol values in the last table?
This can be done by:
select * from #tmp_1
where ID in
(
select ID
from #tmp_3
group by ID
having count(distinct SomeIrrelevantCol)>=2
)

SQL Server trigger only on changes to update a calculated column

For a time scheduling project, I have two tables: tbl_timeslots which holds available times with slotid as the primary key and totalmembers which counts the number of appointments made for this slot, and tbl_appointments with primary key apptid which holds the actual appointments, with slotid as a foreign key linking to the slot information.
I need to automatically update the totalmembers column any time an appointment is created/deleted/changed. The trigger I wrote (shown below) does not update the correct number of appointments in the tbl_timeslots column totalmembers.
CREATE TABLE tbl_timeslots
(
slotid int ,
fromdate datetime ,
todate datetime ,
totalmembers int
)
INSERT tbl_timeslots (slotid, fromdate, todate, totalmembers)
VALUES (1, '2016-01-01 10:00:00', '2016-01-01 11:00:00', 0)
INSERT tbl_timeslots (slotid, fromdate, todate, totalmembers)
VALUES (2, '2016-01-01 11:00:00', '2016-01-01 12:00:00', 0)
CREATE TABLE tbl_appointments
(
apptid int ,
slotid int ,
firstname varchar(10) ,
lastname varchar(10)
)
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (1, 1, 'Mark', 'Twain')
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (2, 1, 'Thomas', 'Jefferson')
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (3, 2, 'Donald', 'Duck')
CREATE TRIGGER [dbo].[tr_totalmembers]
ON [dbo].[TBL_appointments]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
UPDATE tbl_timeslots
SET totalmembers = (SELECT COUNT(1)
FROM tbl_appointments a
WHERE tbl_timeslots.slotid = a.slotid)
FROM inserted i
INNER JOIN deleted d ON i.apptid = d.apptid
WHERE
d.slotid <> i.slotid
AND (tbl_timeslots.slotid = i.slotid OR tbl_timeslots.slotid = d.slotid)
END
I just modified the trigger a bit and it worked for me :
CREATE TRIGGER [dbo].[tr_totalmembers]
ON [dbo].[TBL_appointments]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
UPDATE tbl_timeslots
SET totalmembers = a.cnt
from
(
SELECT slotid,COUNT(1) as cnt
FROM tbl_appointments
group by slotid
) a
WHERE
tbl_timeslots.slotid=a.slotid
END
Or in case if there is a specific problem please mention so that we could look into it .
A better way to compute totalmembers is to create and use an indexed view:
CREATE VIEW dbo.vw_timeslots_with_totalmembers
WITH SCHEMA_BINDING
AS
SELECT a.slotid, COUNT_BIG(*) AS totalmembers
FROM dbo.tbl_appointments a
GROUP BY a.slotid
GO
CREATE UNIQUE CLUSTERED INDEX IUC_vw_timeslots_with_totalmembers_slotid
ON dbo.vw_timeslots_with_totalmembers (slotid)
GO
DECLARE #slotid INT = 123
SELECT totalmembers
FROM dbo.vw_timeslots_with_totalmembers WITH(NOEXPAND) -- This table hint is needed in order to force usage of indexed view
WHERE slotid = #slotid
GO
Note: please read following notes regarding the proper configuration of SETtings (see section Required SET Options for Indexed Views): https://msdn.microsoft.com/en-us/library/ms191432.aspx .
Note #2: if last SELECT statement returns 0 rows this means that current slot doesn't have appointments (I assume that current slot is valid).

SQL Server pivot specified columns

I'm struggling with a query output issue. Please see sample data :
CREATE TABLE #Subject (ID INT PRIMARY KEY IDENTITY, Name NVARCHAR(50))
CREATE TABLE #Student (ID INT PRIMARY KEY IDENTITY, Name NVARCHAR(50))
CREATE TABLE #Grade (ID INT PRIMARY KEY IDENTITY,
StudentID INT REFERENCES #Student(ID),
SubjectID INT REFERENCES #Subject(ID),
Grade NVARCHAR(50), GradeText NVARCHAR(50))
INSERT INTO #Subject ( Name ) VALUES
(N'Maths'),
(N'Physics'),
(N'English')
INSERT INTO #Student ( Name ) VALUES
(N'Joe'),
(N'Tom'),
(N'Sally'),
(N'Fred'),
(N'Kim')
INSERT INTO #Grade
( StudentID, SubjectID, Grade, GradeText ) VALUES
(1,1,'Current','A'),
(2,3,'Expected','C'),
(3,2,'Mid','F'),
(4,1,'Final','B'),
(5,2,'Pre','C'),
(2,3,'Start','A'),
(3,1,'Current','A'),
( 1,2,'Expected','B'),
( 4,1,'Final','D'),
( 5,3,'Mid','E')
SELECT * FROM #Student
SELECT * FROM #Subject
SELECT * FROM #Grade
For the grade output I want to set some of the important grade types in the grade column to be their OWN columns. i.e. Current, Final I would like to be created as their own columns with associated grades, but the others can just be listed as they're not as important. This is a very simple example, the data I'm working with is much more complicated.
Is their a way to specify important columns to be created as their own columns and other data to just be listed as per normal? Also, all the pivot examples I've seen are querying from one table. What happens when you're query has many joins?
Are you aiming at something like this:
;with x as (
select *
from (
select StudentID, SubjectID, Grade, GradeText, Grade as grade_1, GradeText as GradeText_1
from (
select *
from #Grade
) as x
) as source
pivot (
max(GradeText_1)
for Grade_1 in ([Current], [Final])
) as pvt
)
select sub.Name as Subject, st.Name as Student, Grade, GradeText, [Current], Final
from x
inner join #Subject sub on x.SubjectID = sub.ID
inner join #Student st on x.StudentID = st.ID

Best way to get multiple newly created key values from table inserts using SQL Server?

The function Scope_Identity() will provide the last generated primary key value from a table insert. Is there any generally accepted way to get multiple keys from an insertion of a set (an insert resulting from a select query)?
In SQL Server 2005 onwards, you can use the OUTPUT clause to get a returned set of values. From the linked article:
The following example creates the
EmployeeSales table and then inserts
several rows into it using an INSERT
statement with a SELECT statement to
retrieve data from source tables. The
EmployeeSales table contains an
identity column (EmployeeID) and a
computed column (ProjectedSales).
Because these values are generated by
the SQL Server Database Engine during
the insert operation, neither of these
columns can be defined in #MyTableVar.
USE AdventureWorks ;
GO
IF OBJECT_ID ('dbo.EmployeeSales', 'U') IS NOT NULL
DROP TABLE dbo.EmployeeSales;
GO
CREATE TABLE dbo.EmployeeSales
( EmployeeID int IDENTITY (1,5)NOT NULL,
LastName nvarchar(20) NOT NULL,
FirstName nvarchar(20) NOT NULL,
CurrentSales money NOT NULL,
ProjectedSales AS CurrentSales * 1.10
);
GO
DECLARE #MyTableVar table(
LastName nvarchar(20) NOT NULL,
FirstName nvarchar(20) NOT NULL,
CurrentSales money NOT NULL
);
INSERT INTO dbo.EmployeeSales (LastName, FirstName, CurrentSales)
OUTPUT INSERTED.LastName,
INSERTED.FirstName,
INSERTED.CurrentSales
INTO #MyTableVar
SELECT c.LastName, c.FirstName, sp.SalesYTD
FROM HumanResources.Employee AS e
INNER JOIN Sales.SalesPerson AS sp
ON e.EmployeeID = sp.SalesPersonID
INNER JOIN Person.Contact AS c
ON e.ContactID = c.ContactID
WHERE e.EmployeeID LIKE '2%'
ORDER BY c.LastName, c.FirstName;
SELECT LastName, FirstName, CurrentSales
FROM #MyTableVar;
GO
SELECT EmployeeID, LastName, FirstName, CurrentSales, ProjectedSales
FROM dbo.EmployeeSales;
GO
Use the row count and last identity value....
DECLARE #LastID int
DECLARE #Rows int
--your insert from a select here
SELECT #LastID=##IDENTITY, #Rows=##ROWCOUNT
--set of rows you want...
SELECT * FROM YourTable Where TableID>#LastID-#Rows AND TableID<=#LastID

Resources