SQL Query trouble - sql

I am trying to write an SQL query to retrieve all of a users pending events, however it is difficult with how my tables are structured.
My tables are as follows :
event {
event_id
name
group_id}
Pending {
GroupID
UserID
}
Users{
Username
UserID
}
Ever user is identified by a UserID, and every group by a GroupID. Events have in them a GroupID which points to a list of users. I need to retrieve all pending events for a certain user, so :
SELECT * FROM event
WHERE event.group_id = (SELECT GroupID FROM Pending)
But how do I then link this so only the Pending events for a user with a certain UserID are returned?

select e.* from event e
inner join pending p on
e.group_id = p.GroupID
inner join Users u
on p.UserID = u.UserID
where u.UserID = 123
actually you can skip the join with the Users-table if you already have the UserID:
select e.* from event e
inner join pending p on
e.group_id = p.GroupID
where p.UserID = 123
The typical way to write this is using inner join. Traditionally it has been better performing than sub selects, but modern DBMS:s optimize them into the same query. If you really want to write with a sub select you type like this
SELECT * FROM event
WHERE event.group_id in (SELECT GroupID FROM Pending WHERE UserID = 123)

You want to use the JOIN clause to link the tables together rather than using the WHERE clause to filter
SELECT u.username,
e.name
FROM users u
INNER JOIN pending p
ON u.userid = p.userid
INNER JOIN event e
ON p.groupid = e.groupid
WHERE
u.UserID = SomeID

SELECT
event.event_id
FROM event
LEFT JOIN Pending ON event.group_id = Pending.GroupID
LEFT JOIN Users USING (UserID)
WHERE Users.Username = 'foobar'
USING() can be utilized when columns on both sides of selection have the same name. It makes for easier reading. That is one of main reason why i would recommend to have same name for same data throughout the database. For example, if you Documents table and primary key document_id, then in all the other tables, where you are referencing the ID of a document, you use the same name for the column.
To learn more about JOINs : in mysql or postgresql read the links. And for visual representation of what each join does: this article.
Also you should get some book about your preferred RDBMS and learn all the basics, then you can expand your knowledge by reading SQL Antipatterns book. Or you could just carefully look through slides, made by book's author.

You can try :
select e.*, u.* from pending as p, users as u, event as e
WHERE
p.group_id = e.group_id
AND p.user_id = u.userID

SELECT * FROM event E,
pending P,
Users U
WHERE E.group_id = P.GroupID
AND P.UserID = U.UserID
AND U.UserID = (Some XYZ id)
XYZ should be some Integer value.

Related

Efficient SQL query for selecting records where a related record DOES NOT exist

I am trying to select a User where that User DOES NOT have an associated record with a certain value.
I have a User model, a User has_one Feed, a Feed has_many FeedTracks, a FeedTrack belongs_to a Track. I want to select Users only where they DO NOT have a FeedTrack with a certain TrackId.
I'm using rails but I would be open to strict SQL for this.
The best I've got is:
SELECT TOP 1000 *
FROM Users u
LEFT JOIN Feeds f ON f.user_id = u.id
LEFT JOIN FeedTracks ft ON ft.feed_id = f.id
ONLY IF ALL OF THOSE feedTracks' track_id !== <<track_id>>
Obviously that last part statement is not real SQL and that's what my question is. How would I say, hey, get me the Users where this related record doesn't exist. But if that record does exist, don't return that User.
In other words, if that user has a feed, with a feed track with that track id, don't return that User. But if it doesn't, do return that User.
You can use GROUP BY and HAVING:
SELECT u.id
FROM Users u
LEFT JOIN Feeds f
ON f.id = u.feed_id
LEFT JOIN FeedTracks ft
ON ft.feed_id = f.id
GROUP BY u.id
HAVING
SUM(CASE WHEN ft.track_id = #track_id THEN 1 ELSE 0 END) = 0
Modify the columns in the SELECT and GROUP BY as needed.
Try
User.joins(feed: :feed_tracks)
.where('feed_tracks.track_id <> ?', unwanted_track_id)
.order('id desc')
.limit(1000)
.to_sql
You should use not existst operation like this
SELECT TOP 1000 *
FROM Users u
LEFT JOIN Feeds f ON f.id = u.feed_id
LEFT JOIN FeedTracks ft ON ft.feed_id = f.id
where not exists
(select 1
from FeedTracks ft1
where ft1.feed_id = f.id
and ft1.track_id = <<track_id>>)

Using binary logic in PostgreSQL JOIN queries

I've got 3 tables that look vaguely like this:
Users
----------
UserID
Name
Phone
User Groups
-----------
GroupID
Activity
Group Membership
---------------
UserID
GroupID
Independent Actives
-------------------
UserID
Activity
The idea is that a user can perform an activity either as part of a group or on their own. What I want to do is return all the people that partake in a certain activity. What I have been able to write so far lets me return all the users which are in groups that undertake that activity. What I want to add to this is the ability to see the people that do the activity independently. This is what I have so far:
SELECT
users.name, users.phone, user_groups.activity
FROM users
INNER JOIN group_membership ON group_membership.userID = users.userID
INNER JOIN user_groups ON user_groups.groupID = group_membership.groupID
WHERE user_groups.activity = 'Knitting';
The above bit works fine and it shows all of the users that are part of groups that do knitting, but I also want it to show all the users that are knitting independently. This is what I have attempted to add:
SELECT
users.name, users.phone, user_groups.activity
FROM users
INNER JOIN group_membership ON group_membership.userID = users.userID
INNER JOIN user_groups ON user_groups.groupID = group_membership.groupID
INNER JOIN independent_activity ON independent_activity.userID = users.userID
WHERE user_groups.activity = 'Knitting' OR independent_activity.activity = 'Knitting';
The problem here is the syntax, I understand the algorithm that I'm trying to do but I don't know how to transfer it into sql and so any help is appreciated.
You could use a UNION in this case
SELECT users.NAME
,users.phone
,user_groups.activity
FROM users
INNER JOIN group_membership ON group_membership.userID = users.userID
INNER JOIN user_groups ON user_groups.groupID = group_membership.groupID
WHERE user_groups.activity = 'Knitting'
UNION
SELECT users.NAME
,users.phone
,independent_activity.activity
FROM users
INNER JOIN independent_activity ON independent_activity.userID = users.userID
WHERE independent_activity.activity = 'Knitting';
You also might want to lookup the differences between a UNION and a UNION ALL and decide the one that suites your requirement.
You've got a working answer from SoulTrain. However, for completeness sake I'd like to mention that you don't have to join all those tables. (You could use outer joins here and remove duplicate matches with DISTINCT, but that's not necessary. You don't have to query the users table twice either. And you don't need UNION for doing the distinct job.)
Simply select from the one table you want to display data from, i.e. the users table, and then use EXISTS or IN to get only those users that are either in one set or another.
select name, phone
from users
where userid in
(
select userid
from independent_actives
where activity = 'Knitting'
)
or userid
(
select userid
from group_membership
where groupid in (select groupid from user_groups where activity = 'Knitting')
)

Select all entities which do not have children -- getting a lot of rows back, is this correct?

I've got 84,000 rows in my Users table. Users are created automatically. So, I thought it would be nice to see how many users actually did anything after being created. I wrote this query:
SELECT COUNT(*) FROM Users u
JOIN Folders f ON UserId = u.Id
JOIN Playlists p ON FolderId = f.Id
WHERE 0 = (SELECT COUNT(*) FROM PlaylistItems WHERE PlaylistId = p.Id)
My intent is to only count users which have no playlist items in any of their playlists. This query returned 74,000 results which seems high.
I'm wondering if this query is selecting all users which have at least one playlist with no items in it. That is, if a user has two playlists -- one empty and one populated -- are they still counted in my query? And, if so, how can I modify it to select only users which have only empty playlists.
If that's vastly more difficult then I might try my hand at counting only users with 1 playlist which is empty.
The database structure is:
Many users. 1:1 user:folder, 1:many folder:playlists, 1:many playlists:playlistItems
A better pattern than counting every single playlist and comparing is simply finding all the users who don't have anything in any playlist. I like NOT EXISTS for this:
SELECT COUNT(u.Id)
FROM dbo.Users AS u
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.PlayLists AS pl
INNER JOIN dbo.PlayListItems AS pli
ON pl.id = pli.PlayListID
INNER JOIN dbo.Folders AS f
ON p.FolderID = f.ID
WHERE f.UserID = u.Id
);
As an aside, calling a column Id in its primary table and something else everywhere else might seem like a good idea, but I find it quite confusing. Why isn't a FolderID called a FolderID everywhere in the data model?
Break down your query:
SELECT u.id, COUNT(*) FROM Users u
JOIN Folders f ON UserId = u.Id
JOIN Playlists p ON FolderId = f.Id
join PlaylistItems on PlaylistId = p.Id
group by u.id
This should provide you with a list of all users and the count of the number of rows in playlists by userID. a couple ways to go...
Take a count of all users not in that list:
select count(*) from users where id not in (SELECT u.id FROM Users u
JOIN Folders f ON UserId = u.Id
JOIN Playlists p ON FolderId = f.Id
join PlaylistItems on PlaylistId = p.Id
group by u.id)
MySQL performs poorly on that...same thing using left join:
select count(*)
from users u left join (SELECT u.id, COUNT(*) FROM Users u
JOIN Folders f ON UserId = u.Id
JOIN Playlists p ON FolderId = f.Id
join PlaylistItems on PlaylistId = p.Id
group by u.id)a
on a.id = u.id
where a.id is null

Multiple WHERE clause query doesn't work

EDIT: I've edited this question.
Here, $id = $_SESSION['id'] and let's say $id = 1 (Paul).
EVENTS
sender recipient type
1 2 message
2 4 like
USERS
id firstname
1 paul
2 anna
CONNECTION
subscribing subscribed
1 2
Here's where I am at right now:
SELECT events.sender, events.recipient, events.type, users.firstname, users.lastname,
connection.subscribing, connection.subscribed
FROM events, users, connection
WHERE events.sender=users.id
AND connection.subscribing =$id`
The goal of this query is to JOIN events and info from user 2 (Anna) since user 1 (Paul) has subscribed to her feed (in the connection list). User 1 should only see events + info from users he has subscribed to (here, Anna only)
My current query doesn't work for the following reasons:
1) Rows are duplicate
2) No matter the value of $id, all the rows from the table events show up: the filter
doesn't work.
Any ideas how to fix it?
I'm not sure about what is your goal, but here is some samples:
SELECT u.id, u.firstname, sender, recipient, post_id, type
FROM events e
inner join users u
ON (e.sender = $id) -- why you need additional table there?
inner join post_list p
ON (e.post_id = p.post_id)
Another example:
SELECT u.id, u.firstname, sender, recipient, post_id, type
FROM events e, users u, connection c, post_list p
where
e.sender=u.id AND
e.post_id=p.post_id AND
u.id=c.subscribed AND
u.id=$id
Last quest will return Cartesian product of all tables and "filter" only proper ones, you can safely replace query above with JOIN syntax (be careful with tables order):
SELECT u.id, u.firstname, sender, recipient, post_id, type
FROM events e
inner join post_list p
ON (e.post_id = p.post_id)
inner join users u
ON (e.sender = u.id AND u.id=$id)
inner join connection c
on (u.id = c.subscribed)
---- UPDATE
here is proper query:
select u.id, u.firstname, e.sender, e.recipient, e.type
from users u inner join events e on (u.id=.e.sender) -- this query return ALL events
-- next part "filters" results
where
u.id in (select subscribed from connection where subscribing=$id)
also you can move filter condition in ON clause
select u.id, u.firstname, e.sender, e.recipient, e.type
from users u inner join events e on (u.id=.e.sender and u.id in (
select subscribed from connection where subscribing=$id))
for performance reasons, I suggest to use another variant:
select e.* from events e
where e.sender in (select c.subscribing from connection c where c.subscribed=$id)
this query return all events, without user's information. All users should be stored in memcache (for example) and during output to page you can add user's names, avatars, etc
also, you can loop over results via php and get list of users which should be displayed, and fetch information from db only for them, sometimes this will be faster, try benchmarking all variants
If I'm understanding your question and table schemas correctly, this should work for you:
select u.id, u.firstname,
e.sender, e.recipient, e.post_id, e.type,
pl.post
from events e
join users u on e.sender != u.id
join connection c on c.subscribed = e.sender
join post_list pl on e.post_id = pl.post_id
where e.sender = $id
I don't exactly understand your relationship between events and users, but this should get you going in the right direction.
A Visual Explanation of SQL Joins

Selecting records in SQL based on another table's contents

I'm a bit new to SQL and have trouble constructing a select statement. I have two tables:
Table users
int id
varchar name
Table properties
int userID
int property
and I want all user records which have a certain property. Is there a way to get them in one SQL call or do I need to first get all userIDs from the properties table and then select each user individually?
Use a JOIN:
SELECT U.id, U.name, P.property FROM users U
INNER JOIN properties P ON P.userID = U.id
WHERE property = 3
If there's only one property row per user you want to select on, I think this is what you want:
select
users.*
from
users,
properties
where
users.id = properties.userID
and properties.property = (whatnot);
If you have multiple property rows matching "whatnot" and you only want one, depending your database system, you either want a left join or a distinct clause.
Check out the JOIN command. You could write a query like the following:
SELECT
name
FROM
users u
INNER JOIN properties p
ON u.id = p.userID
WHERE
p.property = <some value>
You're looking to JOIN tables.
Assuming the id and userID columns have the same meaning, it's like this:
select u.name
from users u inner join properties p
on u.id = p.userID
where p.property = :ValueToFind
SELECT [Name] FROM Users u
JOIN Properties p on p.UserID=u.ID
WHERE p.Property=1
Obviously it depends what flavour of RDBMS and TSQL you are using.