Heavily polymorphed table - sql

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.

Related

Find values from table whose column value determines which column from another table to be used for lookup

I have 2 tables with no relationship defined at DDL.
CREATE TABLE SOURCES(
ID VARCHAR2(25),
SOURCE VARCHAR(2),
VALUE_ID(VARCHAR2(25),
LAST_UPDATED TIMESTAMP);
CREATE TABLE USERS(
ID VARCHAR2(25),
USER_ID VARCHAR(25),
CLIENT_ID VARCHAR(25));
I need to find all those rows from table sources given an input value for column VALUE_ID. VALUE_ID is set to USERS.USER_ID or USERS.CLIENT_ID based on the SOURCE column of that row.
IF SOURCE = 'A' THEN VALUED_ID = USERS.USER_ID ELSE USERS.CLIENT_ID
Any help resolving this would be much appreciated.
You can use a CASE statement in your join:
Select *
FROM SOURCES S
INNER JOIN USERS U
on S.Value_ID = CASE WHEN S.SOURCE='A' then U.USER_ID ELSE U.CLIENT_ID END
Just use AND and OR in the join condition:
SELECT *
FROM sources s
INNER JOIN users u
ON ( (s.source = 'A' AND s.value_id = u.user_id )
OR (s.source <> 'A' AND s.value_id = u.client_id))

Inner join removes some rows unnecessarily

I have 3 tables defined like so
CREATE TABLE participants(
id SERIAL PRIMARY KEY,
Name TEXT NOT NULL,
Title TEXT NOT NULL
);
CREATE TABLE meetings (
id SERIAL PRIMARY KEY,
Subject TEXT NOT NULL,
Organizer TEXT NOT NULL,
StartTime TIMESTAMP NOT NULL,
EndTime TIMESTAMP NOT NULL
);
CREATE TABLE meetings_participants(
meeting_id int not null,
participant_id int not null,
primary key (meeting_id, participant_id),
foreign key(meeting_id) references meetings(id),
foreign key(participant_id) references participants(id)
);
I want to find meetings happening today with participants in them.
When I run this query I basically get them
SELECT * from meetings
INNER JOIN meetings_participants ON meetings.id = meetings_participants.meeting_id
INNER JOIN participants ON meetings_participants.participant_id = participants.id
WHERE starttime::date = NOW()::date;
Problem is this query discards meetings where there are no participants yet, I still wish to include them into my query result. How can I modify my query to work like that ?
You need a LEFT JOIN instead of INNER. Using ::date casting you are implying that you are only interested them to be taking place today, whether or not it might already ended. Still you should include EndTime in your query, taking into consideration that there might be meetings that span over several days:
SELECT * from meetings
left join meetings_participants on meetings.id = meetings_participants.meeting_id
left join participants on meetings_participants.participant_id = participants.id
WHERE starttime::date <= NOW()::date and endtime::date >= NOW()::date ;
DBFiddle demo here.
EDIT: Participants' name and title as JSON array:
SELECT id, subject, organizer, starttime, endtime, jsonb_pretty(tmp.participants)
from meetings m
left join lateral (
select jsonb_agg(row_to_json(tp)) as participants
from (select p.name, p.title
from meetings_participants mp
inner join participants p on mp.participant_id = p.id
where mp.meeting_id = m.id
) tp
) tmp on true
WHERE starttime::date <= NOW()::date
and endtime::date >= NOW()::date;
DBFiddle demo for participants added as JSON
You did not mention whether you want each participant on a separate row or as an aggregate (e.g. a comma separated list). If former then change inner to left join. For the latter case you could:
SELECT meetings.*, (
SELECT string_agg(participants.name, ', ')
FROM meetings_participants
JOIN participants ON meetings_participants.participant_id = participants.id
WHERE meetings_participants.meeting_id = meetings.id
) AS participants_list
FROM meetings
WHERE starttime::date = current_date

Referential integrity between tables in SQL Server

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

How to replace LEFT outer join with INNER join in SQL?

I have a view on which I need to provide cluster Indexing the problem is in order to provide cluster indexing the it should not have any of the left or right outer joins , and I want to replace the LEFT outer join with INNER join , one of the ways which I can think of is to insert a dummy value with lets say -1 in the right table and by doing this even if all the Ids from the left table wont match Ids from the right table in INNER JOIN but since we have inserted -1 in the right table and we are using IsNULL(u.UserId,-1) it should return all the values from the left table but somehow this approach is not working.
create table Users(
UserId int,
UserName nvarchar(255)
)
insert into Users values(1,'sid429')
insert into Users values(2,'ru654')
insert into Users values(3,'dick231')
create table managers
(
caseId int,
CaseName nvarchar(255),
UserId int
)
insert into managers values (100,'Case1',1)
insert into managers values (101,'Case2',2)
insert into managers values (-1,NULL,-1)
select username from users u inner join managers m on m.UserId=IsNULL(u.UserId,-1)
Don't talk about indexes, but I think you could replace LEFT JOIN by INNER JOIN + UNION
select username from users u inner join managers m on m.UserId= u.UserId
UNION ALL
select username from users u WHERE NOT EXISTS (SELECT 1 FROM managers m WHERE m.UserId = u.UserId)
IsNull(u.UserId,-1) doesn't seem right - u.UserId is never null, since the absence of data is in the managers table - in this case, u.UserId will always have a value, but m.UserId might not, so IsNull(u.UserId, -1) won't work.
I'm intrigued to see a better answer, but I don't think you can do that - I think you eventually need to substitute the value conditionally to -1 if it doesn't exist in the other table, like this:
select username from users u
inner join managers m on m.UserId =
case when not exists(select * from managers where UserId = u.UserId)
then -1 else u.UserId end
This has the desired effect, but looking at the execution plan, won't help your performance issue.
You can replace a LEFT OUTER JOIN with an INNER JOIN if you add the missing values in the related table.
It has not worked for you because you have added a -1 value. But the not matching value on your INNER JOIN is a 3, not a null or a -1.
You can do so at runtime with an UNION, no need to permanently create those values as you have tried to do (inserting that -1 value) :
with expanded_managers as (
select CaseId, CaseName, UserId
from managers
union
select null, null, UserId
from users
where not exists (select * from managers where managers.UserId = users.UserId)
)
select UserName, CaseName
from users
inner join expanded_managers on expanded_managers.UserId = users.UserId
if you require only username it should be simple:
select distinct username from users u inner join managers m on m.UserId=u.UserId OR ( m.UserId=-1 AND u.userId = u.userId)
I have cleaned-up this part a bit. I had to guess the logical model, given that you did not specify any constraints.
create table Users (
UserId int not null
, UserName nvarchar(255) not null
, constraint pk_users primary key (UserId)
, constraint ak_users unique (UserName)
);
create table Cases (
CaseId int not null
, CaseName nvarchar(255) not null
, UserId int not null
, constraint pk_cases primary key (CaseId)
, constraint ak_cases unique (CaseName)
, constraint fk_cases foreign key (UserId)
references Users (UserId)
);
insert into Users values(1,'sid429') ;
insert into Users values(2,'ru654') ;
insert into Users values(3,'dick231');
insert into Cases values (100,'Case1',1);
insert into Cases values (101,'Case2',2);
This is mostly self-explanatory, but you have to understand that candidate keys (unique) for the result are: {UserID, CaseId}, {UserName, CaseName}, {UserID, CaseName}, {UserName, CaseId}. Not sure if you were expecting that.
with
R_00 as (
select UserId from Users
except
select UserId from Cases
)
select u.UserId
, u.UserName
, c.CaseId
, c.CaseName
from Users as u
join Cases as c on u.UserId = c.UserId
union
select u.UserId
, u.UserName
, (-1) as CaseId
, 'n/a'as CaseName
from Users as u
join R_00 as r on r.UserId = u.UserID
;
Another version of this, similar to other examples in the post.
select u.UserId
, u.UserName
, c.CaseId
, c.CaseName
from Users as u
join Cases as c on u.UserId = c.UserId
union
select u.UserId
, u.UserName
, (-1) as CaseId
, 'n/a' as CaseName
from Users as u
where not exists (select 1 from Cases as c where c.UserId = u.userId)
;

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