SQL Server 2008 - Create a View With an Extra Column 'HasMutual' - sql

I am trying to create a View with SQL Server 2008 that can enable me to see if user a and b have mutual friends.
Use a and b are stored in a table called Friendships as Sender and Recipient with a IsPending column indicating if the friendship has been approved by the Recipient.
Anyway the main table is like this:
Sender | Recipient | IsPending
(Stored on a single row.)
How can I create a view that returns an extra column called HasMutual with a 0 if false or 1 if true?
Thanks.

I wish I was smart enough to figure out this query!
I can't believe the first two answers each got an up-vote. And this is not trivial as Ben Thul has claimed!
Since this is so difficult to solve with a single query, would you consider a different approach?
How about a stored procedure that takes in a pair of users, and then lists out their mutual friends?
(I'm assuming IsPending = 1 means they aren't friends yet)
CREATE PROCEDURE find_mutual_friends
(
#user1 varchar(255),
#user2 varchar(255)
)
as
SELECT
Friends
FROM
(SELECT
Friendships.Sender as Friends
FROM
Friendships
WHERE
Friendships.Recipient = #user1
and Friendships.IsPending = 0
UNION ALL
SELECT
Friendships.Recipient as Friends
FROM
Friendships
WHERE
Friendships.Sender = #user1
and Friendships.IsPending = 0) FriendsTempTable
WHERE
FriendsTempTable.Friends in
(SELECT
Friendships.Sender as Friends
FROM
Friendships
WHERE
Friendships.Recipient = #user2
and Friendships.IsPending = 0
UNION ALL
SELECT
Friendships.Recipient as Friends
FROM
Friendships
WHERE
Friendships.Sender = #user2
and Friendships.IsPending = 0)

CREATE VIEW ViewName
AS
SELECT
Sender , Recipient , IsPending, IsPending AS HasMutual
FROM myTable

Related

Query friendships table (with pairs of rows) to find mutual statuses

I have the following schema
Relationship:
fromId (fkey)
toId (fkey)
isFriend (boolean)
isBlocked (boolean)
A user requests friendship by adding a record with fromId set to his id and toId set to the friend's id and isFriend=true.
To accept the request, the other user does the same but ids reversed.
They are only friends when both records exist and both have isFriend=true.
I'm trying to make a query that lists a users' friends given an ID (WHERE userId = x)
This is what I started with:
SELECT "a"."fromId" AS "userId1", "a"."toId" AS "userId2", "b"."fromId" AS "userId2", "b"."toId" AS "userId1"
FROM "Relationship" a
INNER JOIN "Relationship" b
ON "a"."fromId" = "b"."toId" AND "b"."fromId" = "a"."toId"
WHERE "a"."isFriend" = true AND "b"."isFriend" = true
This is the result I get with
With this, I'm able to get only the relationships that have records in both directions (friends), but what I'm trying to get (if possible), are only the IDs of the friends of a user
Thanks
select *
from "Relationship" a
where
a."fromId" = x and a."isFriend" and
exists (
select 1
from "Relationship" b
where
b."toId" = x /* b."toId" = a."fromId" */ and
b."isFriend");

How do I filter data between multiple columns into new columns after INNER JOINS?

Brand new to SQL, so apologies that I don't really know how to word the question or find an existing answer. Let me explain further. I'm creating a Chat app for fun with a DM system. I have a table (dms_history) setup that has a row gen. for every new distinct chat between 2 users w/ the last DM being sent between the 2 users. eg:
CREATE TABLE users (
uid SERIAL PRIMARY KEY,
pid VARCHAR(40),
uname VARCHAR(50) NOT NULL UNIQUE,
email valid_email,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE dms (
dmid SERIAL PRIMARY KEY,
uid INT NOT NULL REFERENCES users(uid),
recip INT NOT NULL REFERENCES users(uid),
msg TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- TABLE I'M TALKING ABOUT
CREATE TABLE dms_history (
user1 INT REFERENCES users(uid),
user2 INT REFERENCES users(uid),
last_dm INT REFERENCES dms(dmid),
PRIMARY KEY(user1, user2),
CHECK (user1 < user2)
);
Say I'm trying to get all the data w/ joins on this table (dms_history) for client user uid#2. eg:
SELECT
u1.uid as u1id,
u1.uname as u1name,
u2.uid as u2id,
u2.uname as u2name,
dms.dmid,
dms.msg
FROM dms_history h
INNER JOIN users u1 ON h.user1 = u1.uid
INNER JOIN users u2 ON h.user2 = u2.uid
INNER JOIN dms ON h.last_dm = dms.dmid
WHERE u1.uid = 2 OR u2.uid = 2;
That query I thought of is close to what I want, but what I really want is only to show the user opposite of the client user being queried (so NOT user uid#2 alice) along w/ the last message sent. How do I filter or UNION? those columns into a new column. Picture to explain what I want:
Think of Twitter DMs and how it shows the users you're DMing and a snippet of the last message sent.
Apologies if I did a poor job explaining, it's not my strong suit.
After doing some more reading the past few hours I found I could use subqueries with SELECTs and UNIONs to achieve what I was after. Not sure how efficient it is, but it works.
WITH chats AS (SELECT * FROM dms_history h WHERE h.user1 = 2 OR h.user2 = 2)
SELECT
chats2.user1 as recip_id,
u.uname as recip_uname,
chats2.last_dm as dmid,
dms.msg,
dms.created_at
FROM (
SELECT chats.user1, chats.last_dm FROM chats
UNION
SELECT chats.user2, chats.last_dm FROM chats
) AS chats2
INNER JOIN users u ON chats2.user1 = u.uid
INNER JOIN dms ON chats2.last_dm = dms.dmid
WHERE chats2.user1 != 2;

SQL extract unique data from two columns in same table

I have a table friends, which contains the following columns:
friend_id and friend_of (both are storing unique user ids)
lets say the table friends contains the following data:
friend_id | friend_of
-------------------------
123 | 456
456 | 789
456 | 123
So this means that:
user with id=123 have one friend with id=456
user with id=456 have two friends with ids=123 (friend_1) & 789(friend_2)
user with id=789 have one friwnd with id=456
I want to write a query that given a single user id shows every friend that this user has (with their ids).
For example:
if given user with id=123 the output would be users with ids=456
if given user with id=789 the output would be users with ids=456
if given user with id=456 the output would be users with ids=123 and 789
Can you help me with the query I need?
(select friend_id as all_friends from friends where friend_of=ID)
uninon
(select friend_of as all_friends from friends where friend_id=ID)
I suppose you are interested in the case where an id exists only in one of the columns. Above query would address this. Note that union is used here and not union all as unique values are required.
select friend_id, friend_of
where friend_id = '456'
just change ID to get desire ouput
Just use union
Declare #id int = 1;
select f.friendof from
#YourTableName as f where f.friendId = #id
union
select f.friendId from
#YourTableName as f where f.friendof = #id
You can use the query SELECT * FROM friends WHERE friend_id='456', which should get all of the friends of 456. Then do a join on your "users" table using the foreign key friend_of.
EDIT: I didn't realize friends was a two-way relationship. In that case, use a UNION first, some of the other responses talk about it. :)

SQL query to create a single record view of multiple records - Dynamic Stored Procedure?

Ok, I think there must be an easier way to do this, I would like to do it dynamically instead of hand writing every line. I am more familiar with MySQL than SQL Server 2008, but I'm not sure how I would do this in MySQL either.
I have 3 tables.
Table 1: USER id | email | password
Table 2: METADATA id | name (list of fields I need to know about user)
Table 3: USER_META id | uid | name | value (where I store the user meta data)
I do not hard code the METADATA because it changes for each instance of this application. but in this example, lets just say the meta data is "eyes", "phone" , "city" (there are actually many more, there might be 20 this week and 40 next week)
So the way it works is when a user registers, I build the registration form using the METADATA table to build the form. It creates a form with "eyes" , "phone" and "city".
When I save the form, I save a single record into the USER_META table for each data item.
So when registering, I get 3 inserts:
insert into USER_META(uid,name,value) values (5,"eyes","brown")
insert into USER_META(uid,name,value) values (5,"phone","555-1212")
insert into USER_META(uid,name,value) values (5,"city","San Francisco")
Now, I want to write a Stored Procedure that will return a record like this"
EXECUTE Get_Meta 5
returns:
uid | email | eyes | phone | city
5 x#x.com brown 555-1212 San Francisco;
I could write a long hard coded select statement like:
DECLARE #UID int;
SELECT id, email,
(select value from USER_META
where uid = #UID and name = 'EYES') as EYES,
(select value from USER_META
where uid = #UID and name = 'PHONE') as PHONE,
(select value from USER_META
where uid = #UID and name = 'CITY') as CITY,
FROM USER
where id = #UID;
But that kind of defeats the whole purpose, and I have to write it again every week whenever the metadata requirements change (eg when we launch another instance).
I would like to have something like this: (forgive me I am not very experienced with Advanced SQL, so maybe this is easy and I just don't know how to do it?) I will write it in basic code terms to try and explain my thinking:
DECLARE #UID int;
DECLARE #META_NAMES array;
#META_NAMES = (select NAME from METADATA);
SELECT id, email,
for each (#META_NAMES as $THIS_NAME) {
(select value from USER_META
where uid = #UID and name = '$THIS_NAME') as $THIS_NAME,
}
FROM USER
where id = #UID;
Is there a way to write this in SQL Server 2008?
You would USE FOR XML_PATH in SQL Server. A workaround for GROUP_CONCAT
SELECT
U.UID,
U.EMAIL,
stuff(
(
select cast(',' as varchar(max)) + name + '=' + Value
from USER_META
WHERE UID = U.UID
for xml path('')
), 1, 1, '') AS M
FROM
USERS U
SQL Fiddle

Is there a better way to write these queries? (bad SQL messaging schema inside...)

I've inherited a system using SQL Server (2008 R2) with two tables that cover system messaging. A message has a one to many relationship, it can be sent to multiple users.
Primary table, Messaging, looks like:
Id (PK, auto increment)
Subject
MessageBody
CreatorEmployeeId
ConversationId
IsConversationStarter (bool)
Secondary table, MessagingDetails, looks like:
ID (PK, auto increment)
MessageId (FK to Messaging PK)
RecipientEmployeeId
ConversationId
IsRead (bool)
If that's not clear, a new message is put into the Messaging table with an employee Id for who created the message, and new Conversation Id is created. Then X number of rows will be inserted into MessagingDetails, one per recipient, with references back to the Messaging table and using the same Conversation Id.
Any new replies will look the same, reply message goes into the Messaging table, recipient details go into MessagingDetails using the same Conversation Id, so that you can pull a whole message thread by one number.
This gets sloppy when you're trying to get a count of all unread messages that you've created or that have been sent to you. Here's an existing query that gets all of the unread messages sent to the logged-in user by other users in the system, where they created the message originally:
SELECT * FROM dbo.Messaging m
INNER JOIN dbo.MessagingDetails m1 on m.Id = m1.MessageId
INNER JOIN dbo.MessagingDetails m2 on m1.ConversationId = m2.ConversationId
WHERE m1.RecipientEmployeeId = #employeeId
AND m.IsConversationStarter = 1
AND m.ConversationId = m1.ConversationId
AND m2.IsRead = 0
AND m2.RecipientEmployeeId == #employeeId
Then to find all messages that the employee created but have unread replies, you have to do an almost identical query, swapping out the first part of the WHERE clause for:
m.CreatorEmployeeId = #employeeId
I loathe the inner joining the same table twice, once to itself. Given that schema outlined above, is there a better way to write these queries?
For "messages send by employee that have unsent replies", I think this solves the problem:
with EmployeeCreatedMessages as (
select distinct MessageId
from Messaging
where m.CreatorEmployeeId = #employeeId
)
select distinct MessageId
from MessageDetails md
where md.MessageId in (select MessageId from EmployeeCreatedMessages) and
md.IsRead = false
I'm not sure what information you want about the message. This just gives the message id.
The same approach works for all messages sent to users by others:
with OtherCreatedMessages as (
select distinct MessageId
from Messaging
where m.CreatorEmployeeId <> #employeeId
)
select distinct MessageId
from MessageDetails md
where md.MessageId in (select MessageId from OtherCreatedMessages) and
md.IsRead = false and
md.RecipientEmployeeId = #employeeid
Your description and your question don't mention conversionid. Is this relevant?
In order to get rid of that second join onto dbo.MessagingDetails, this query should be equivalent to yours.
SELECT * FROM dbo.Messaging m
INNER JOIN dbo.MessagingDetails m1 on m.Id = m1.MessageId
WHERE m1.RecipientEmployeeId = #employeeId
AND m.IsConversationStarter = 1
AND m.ConversationId = m1.ConversationId
AND EXISTS(SELECT * FROM dbo.MessagingDetails m2
WHERE m1.ConversationId = m2.ConversationId
AND m2.IsRead = 0 AND m2.RecipientEmployeeId == #employeeId)
I don't think it will be any more efficient though, you'd have to check the execution plan.