Referential integrity between tables in SQL Server - sql

I have 2 tables, Members and Enrollments. Both tables can be joined using primary key Member ID.
I need to write a query which returns all the members in the Members table which don't have a corresponding row in the Enrollments table and vice versa.
This is what I have so far:
IF OBJECT_ID('tempdb..#memberswithoutenrollments') IS NOT NULL
DROP TABLE #memberswithoutenrollments
SELECT m.*
INTO #memberswithoutenrollments
FROM ABC_Members m
LEFT OUTER JOIN ABC_MemEnrollment e ON m.MemberID = MemberID

FULL JOIN is a simple method for comparing lists between two tables:
SELECT COALESCE(e.MemberID, m.MemberID),
(CASE WHEN e.MemberID IS NULL THEN 'No Enrollments' ELSE 'No Member' END)
FROM ABC_Members m FULL JOIN
ABC_MemEnrollment e
ON m.MemberID = e.MemberID
WHERE e.MemberID IS NULL OR m.MemberID IS NULL;
But if you have proper foreign key relationships, then you should never have enrollments without members.

You can use NOT IN to your benefit here.
WITH
-- Create a list of all of the matches
in_table AS
(
SELECT
Member_ID
FROM
Enrollments
WHERE
Members.MemberID = Enrollments.Member_ID
),
result_table AS
(
SELECT
*
FROM
Members
-- Grab only the values from members that DO NOT APPEAR in in_table
WHERE
MemberID NOT IN (SELECT DISTINCT FROM in_table)
)
-- Grab all results
SELECT * FROM result_table

Related

How to work in case in join condition

How to find city when ContactID is provided and condition is if ContactID is coming as 123 then it will look whether it is P or C, If P then it will go to Person table and returns City(USA) as output and If C then it will go to Company table and gives City(AUS) as output.
NB: all tables contain thousands of record and City value comes from run time.
Unless you're dynamically generating the query (i.e. using some language other than SQL to execute it) then you need to join on both tables anyway. If you're joining on both tables then there's no need for a CASE statement:
select *
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
You'll start noting an issue here, for every column from PERSON and COMPANY you're going to have to add some business logic to work out which table you want the information from. This can get very tiresome
select co.contactid
, case when p.id is not null then p.name else c.name end as name
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
Your PERSON and COMPANY tables seem to have exactly the same information in them. If this is true in your actual data model then there's no need to split them up. You make the determination as to whether each entity is a person or a company in your CONTACTS table.
Creating additional tables to store data in this manner is only really helpful if you need to store additional data. Even then, I'd still put the data that means the same thing for a person or a companny (i.e. name or address) in a single table.
If there's a 1-2-1 relationship between CONTACTID and PID and CONTACTID and CID, which is what your sample data implies, then you have a number of additional IDs, which have no value.
Lastly, if you're not restricting that only companies can go in the COMPANY table and individuals in the PERSON table. You need the PERSON_COMPANY column to exist in both PERSON and COMPANY, though as a fixed string. It would be more normal to set up this data model as something like the following:
create table contacts (
id integer not null
, contact_type char(1) not null
, name varchar2(4000) not null
, city varchar2(3)
, constraint pk_contacts primary key (id)
, constraints uk_contacts unique (id, contact_type)
);
create table people (
id integer not null
, contact_type char(1) not null
, some_extra_info varchar2(4000)
, constraint pk_people primary key (id)
, constraint fk_people_contacts
foreign key (id, contact_type)
references contacts (id, contact_type)
, constraint chk_people_type check (contact_type = 'P')
);
etc.
you can LEFT JOIN all 3 tables and the using a CASE statement select the one that you need based on the P or C value
SELECT
CASE c.[Person/Company]
WHEN 'P' THEN p.NAME
WHEN 'C' THEN a.Name
END AS Name
FROM Contact c
LEFT JOIN Person p on p.ContactId = c.ContactId
LEFT JOIN Company a on a.ContachId = c.ContactId
Ben's answer is almost right. You might want to check that the first join has no match before doing the second one:
select c.*, coalesce(p.name, c.name) as p.name
from contacts c left outer join
person p
on c.contactid = p.contactid and
c.person_company = 'P' left join
company co
on c.contactid = co.contactid and
c.person_company = 'C' and
p.contactid is null;
This may not be important in your case. But in the event that the second join matches multiple rows and the first matches a single row, you might not want the additional rows in the output.

Heavily polymorphed table

I have a table events. Each event can be 'initiated' and/or 'received' by a User, Visitor or a Team and I want to model these associations.
I am thinking something like
Event
type
user_actor_id
user_subject_id
visitor_actor_id
visitor_subject_id
team_actor_id
team_subject_id
Where the actor/subject refers to who initiated/received the event
Is this the correct approach? Seems like I store a lot of redundant data and I'd have to do a lot of conditional joins as it would like to query the table and get a result like
actor_id:
actor_type (either user, visitor or team)
UPDATE:
Then i'd do a select query like this
select
coalesce(ua.id, va.id, ta.id) as actor_id,
(CASE WHEN ua.id IS NOT NULL THEN 'user' WHEN va.id IS NOT NULL THEN 'visitor' ELSE 'team' END) as author_type,
(CASE WHEN ua.id IS NOT NULL THEN ua.display_name WHEN va.id IS NOT NULL THEN va.name ELSE ta.name END) as author_name,
(CASE WHEN ua.id IS NOT NULL THEN ua.avatar WHEN va.id IS NOT NULL THEN va.avatar ELSE ta.icon END) as author_name
from events e
left join users ua on ua.id = e.user_actor_id
left join users us on us.id = e.user_sibject_id
left join visitors va on va.id = e.visitor_actor_id
left join visitors vs on vs.id = e.visitor_sibject_id
left join teams ta on ta.id = e.team_actor_id
left join teams ts on ts.id = e.team_sibject_id
I would do the following:
create table tPeople ( -- contains as many rows as there are people
int ID,
nvarchar(max) Name
)
create table tRole ( -- contains three rows: Visitor, Team, User
int ID,
nvarchar(max) Name
)
create table tPeopleRole ( -- associates people with roles
int People_ID, -- FK to tPeople.ID
int Role_ID -- FK to tRole.ID
)
create table tEvent (
int ID,
int Type_ID,
int InitiatedPeople_ID, -- FK to tPeople.ID
int ReceivedPeople_ID -- FK to tPeople.ID
)
Then you can query tEvent and join on tPeople / tPeopleRole to get the initiator and receiver's names and/or roles.

SQL Server Compare two rows to identify ID

Here is what I am trying to do.
I have one column of data that is the ID of every person. I have a second column of data that is the ID of just supervisors. I also have a third column of data that identifies an ID as staff or faculty.
I need to take the Supervisor column and compare it against the ID column. When the supervisor's ID is located then I need to identify that row as a staff of faculty supervisor in a separate column. If the ID is not in the supervisor column then they just need to be marked as staff or faculty based off of the third column.
So the three columns that I have are ID, Supervisor ID and Class Type.
Any help would be appreciated.
Here is the code that I currently have
select distinct ODS_PERSON.ID "Cient_ID",
ODS_PERSON.LAST_NAME "Last_Name",
CASE
WHEN H17_PERSON.NICKNAME is not null
THEN H17_PERSON.NICKNAME
ELSE ODS_PERSON.FIRST_NAME
END "First_Name",
H17_PERSON.H17_PER_USERNAME + '#highpoint.edu' "Email",
CASE
WHEN ODS_HRPER.HRP_EFFECT_TERM_DATE is null
THEN '1'
ELSE '0'
END "User_Status",
CASE
WHEN SPT_POSITION.POS_CLASS = 'FACL' AND (ODS_PERSON.ID = SPT_PERPOS.PERPOS_SUPERVISOR_HRP_ID)
THEN 'FACSUP'
ELSE 'NOPE'
END "Employee_Type",
SPT_PERPOS.PERPOS_SUPERVISOR_HRP_ID "Manager",
SPT_POSITION.DEPARTMENT_DESC "Department",
SPT_PERPOS.PERPOS_POS_SHORT_TITLE "Position_Title",
SPT_POSITION.POS_CLASS "Position_Class"
from ( ( ( ( ( SPT_PERPOSWG SPT_PERPOSWG left join ODS_HRPER ODS_HRPER on SPT_PERPOSWG.PPWG_HRP_ID = ODS_HRPER.HRPER_ID ) left join SPT_PERPOS SPT_PERPOS on SPT_PERPOSWG.PPWG_HRP_ID = SPT_PERPOS.PERPOS_HRP_ID ) left join SPT_PERSTAT SPT_PERSTAT on SPT_PERPOSWG.PPWG_HRP_ID = SPT_PERSTAT.PERSTAT_HRP_ID ) left join ODS_PERSON ODS_PERSON on SPT_PERPOSWG.PPWG_HRP_ID = ODS_PERSON.ID ) left join SPT_POSITION SPT_POSITION on SPT_PERPOS.PERPOS_POSITION_ID = SPT_POSITION.POSITION_ID ) left join H17_PERSON H17_PERSON on SPT_PERPOSWG.PPWG_HRP_ID = H17_PERSON.ID
where ODS_HRPER.HRP_EFFECT_TERM_DATE is null
and SPT_PERPOS.PERPOS_END_DATE is null
order by ODS_PERSON.ID
SELECT ID, Supervisor_ID, Class_Type,
CASE
WHEN SuperVisor_ID is null and Class_Type is not null THEN Class_Type
WHEN (SuperVisor_ID = ID or SuperVisor_ID is not null) THEN 'Supervisor'
END
from tableID
Not sure if this is what you're looking for. In the future it would be helpful to know how many tables are involved, and if the data types in each column can be compared directly or if you have to cast the data types.
You may also want to put whether join conditions are necessary to get the information and what columns the tables would join on.
Hope this helps

SQL Loop/Crawler

I am trying to figure out some ways to accomplish this script. I import an excel sheet and then I need to populate 5 different tables based on this excel sheet. However for this example I just need help with the initial loop then I think I can work through the rest.
select distinct Department from IPACS_New_MasterList
where Department is not null
This provides me a list of 7 different departments.
Dep1, Dep2, Dep3, Dep4, Dep5, Dep6, Dep7
For each of these departments I need to perform some code.
Step #1:
Insert the department into table_one
I then need to keep the SCOPE_IDENTITY() for the rest of the code.
Step #2
perform the second loop (inserting all functions in that department into table2.
I'm not sure how to really do a foreach row in this select statement loop, or if I need to do something completely different. I've looked at several answers but can't seem to find exactly what I'm looking for.
Sample Data:
Source Table
Dep1, func1, process1, procedure1
dep1, func1, process1, procedure2
dep1, func1, process2, procedure3
dep1, func1, process2, procedure4
dep1, func1, process2, procedure5
dep1, func2, process3, procedure6
dep2, func3, process4, procedure7
My Tables:
My first table is a list of every department from the above query. With a key on the departmentID. Each department can have many functions.
My second table is a list of all functions with a key on functionID and a foreign key on departmentID. Each function must have 1 department and can have many processes
My third table is a list of all processes with a key on processID and a foreign key on functionID. Each process must have 1 function and can have many procedures.
There are two approaches you can use without a loop.
1) If you have candidate keys in your source (department name) just join your source table back to the table you inserted
e.g.
INSERT INTO Department
(Name)
SELECT DISTINCT Dep1
FROM SOURCE;
INSERT INTO Functions
(
Name,
DepartmentID)
SELECT DISTINCT
s.Func1,
d.DepartmentID
FROM
source s
INNER JOIN Department d
on s.dep1 = d.name;
INSERT INTO
processes
(
name,
FunctionID,
[Procedure]
)
SELECT
s.process1,
f.FunctionID,
s.procedure1
FROM
source s
INNER JOIN Department d
on s.dep1 = d.name
INNER JOIN Functions f
on d.DepartmentID = f.departmentID
and s.func1 = f.name;
SQL Fiddle
2) If you don't have candidate keys in your source then you can use the output clause
For example here if a department weren't guaranteed to be unique this would correctly find only the newly add
DECLARE #Department TABLE
(
DepartmentID INT
)
DECLARE #Functions TABLE
(
FunctionID INT
)
INSERT INTO Department
(Name)
OUTPUT INSERTED.DepartmentID INTO #Department
SELECT DISTINCT Dep1
FROM SOURCE
INSERT INTO Functions
(
Name,
DepartmentID)
OUTPUT INSERTED.FunctionID INTO #FunctionID
SELECT DISTINCT
s.Func1,
d.DepartmentID
FROM
source s
INNER JOIN Department d
on s.dep1 = d.name
INNER JOIN #Department d2
ON d.departmentID = d2.departmentID;
INSERT INTO
processes
(
name,
FunctionID,
[Procedure]
)
SELECT
s.process1,
f.FunctionID,
s.procedure1
FROM
source s
INNER JOIN Department d
on s.dep1 = d.name
INNER JOIN Functions f
on d.DepartmentID = f.departmentID
and s.func1 = f.name
INNER JOIN #Functions f2
ON f.Functions = f2.Functions
SELECT * FROM Department;
SELECT * FROm Functions;
SELECT * FROM processes;
SQL Fiddle
If I am understanding what you are trying to do... yes you can use a loop. Its not really talked about and I bet I am going to get some feedback from other SQL developers that its not a best practice. But if you really need to do a loop
DECLARE #rowcount as int
DECLARE #numberOfRows as int
SET #rowcount = 0
SET #numberOfRows = SELECT COUNT(*) from tablename --put in anything to get the number of times to loop.
WHILE #numberOfRows <= #rowcount
BEGIN
--Put whatever process you need to repeat here
SET #rowcount = #rowcount + 1
END
Assuming you have tables set up with an IDENTITY field set for the Primary Key, you can populate each successive table's foreign key by joining to the previous table and the source table, something like:
INSERT INTO Table1
SELECT DISTINCT Department
FROM SourceTable
GO
INSERT INTO Table2
SELECT DISTINCT b.Deptartment_ID, a.Function
FROM SourceTable a
JOIN Table1 b
ON a.Department = b.Department
GO
INSERT INTO Table3
SELECT DISTINCT b.Function_ID, a.Process
FROM SourceTable a
JOIN Table2 b
ON a.Function = b.Function
GO
INSERT INTO Table4
SELECT DISTINCT b.Process_ID, a.Procedure
FROM SourceTable a
JOIN Table3 b
ON a.Process = b.Process
GO

Find the latest date of two tables with matching primary keys

I have two tables tables, each with primary keys for different people and the contact dates in each category.I am trying to find the most recent contact date for each person, regardless of what table its in. For example:
CustomerService columns: CustomerKey, DateContacted
CustomerOutreach columns: CustomerKey, DateContacted
And I'm just trying to find the very latest date for each person.
Use something like this.
You need to combine the two tables. You can do this by a union. There will be duplicates, but you just group by the customerKey and then find the Max DateContacted
SELECT * INTO #TEMP FROM (
SELECT
CustomerKey
, DateContacted
FROM CustomerService CS
UNION
SELECT
CustomerKey
, DateContacted
FROM CustomerOutreach CS
)
SELECT
CustomerKey
, MAX(DateContacted)
FROM #TEMP
GROUP BY
CustomerKey
Join your tables on primary keys and make a conditional projection.
Select cs.CustomerKey,
CASE WHEN cs.DateContacted <= co.DateContacted
THEN co.DateContacted
ELSE cs.DateContacted END
from CustomerService cs inner join CustomerOutreach co
on cs.CustomerKey = co.CustomerKey
I would do something like this.
Select b.customerKey, b.dateContacted
from (
select a.customerKey, a.DateContacted, Row_Number() over (Partition by customerKey order by DateContacted desc) as RN
from (
Select c.customerKey,
case when (s.DateContacted > o.dateContacted) then s.dateContacted else o.datecontacted end as DateContacted
from Customer c
left outer join customerService s on c.customerKey = s.customerKey
left outer join customerOutreach o on c.customerKey = s.customerKey
where s.customerKey is not null or o.customerKey is not null
)a
)b
where b.RN = 1
This solution should take care of preventing the case of having duplicates if both tables have the same max DateContacted.
http://sqlfiddle.com/#!3/ca968/1