I'm new with SQL and just had my first assignment.
I have the following requirements:
Given is a database of two tables. The first one contains information about the user, like a unique ID per user, the phone number and the city. ID and phone number consist only of numeric digits. The second table contains data about so called „credits“, which a user can own. Again there is a column for the unique user ID, but also the number, the date and the type of credits. A user can have none, one or several entries in the credit table.
I'm still now sure if I got right the part where a user can have none, one or several entries in the credit table. I created these two tables:
CREATE table user
(
user_id INT NOT NULL UNIQUE AUTO_INCREMENT,
user_phone_number INT NOT NULL,
user_city VARCHAR(32) NOT NULL,
PRIMARY KEY (user_id)
);
CREATE table credit
(
credit_user_id INT FOREIGN KEY (user_id),
credit_date date,
credit_number double,
credit_type char(10),
CONSTRAINT chk_type CHECK (credit_type in ('None','A','B','C')),
);
After creating this, I was asked the following questions:
a) The phone number of all users, who own credits of type „A“
SELECT user_phone_number
FROM user, credit
WHERE credit_type = 'A';
b) Like a), but additionally the credit_number of the credits is smaller than 2 or greater than 4
SELECT user_phone_number
FROM user, credit
WHERE (credit_type ='A')
AND (credit_number < 2 OR credit_number > 4);
C) Like a), but additionally the users also own credits of at least one other type.
SELECT user_phone_number
FROM user, credit
WHERE credit_type = 'A'
AND (
SELECT DISTINCT c1.credit_type FROM credit AS c1
JOIN credit a1 ON (c1.credit_type=a1.credit_type)
JOIN credit a2 ON (c1.credit_type=a2.credit_type)
WHERE a2.credit_type<>a1.credit_type);
My problem is that I can't make letter C work, even if both selects seem to work separately. Any ideas or suggestions would be appreciated, thank you!
I'm not sure to understand what you want in C) but many things to say.
You should't use a database name like 'user' because it can be ambigous (reserved word) for SGBD.
You should prefer 'join' instead of 'from table1, table2' and / or mix both.
Have a look here.
You've got ';' in your request in C) which must be only for specify the end of your request.
You can use nested resquet but not like that, not directly after 'AND' because AND is for condition like a comparison. You've got many possibilites : in select fied, after 'FROM', after 'IN', with join, in condition...
Quick search on google.
From another post:
PRIMARY KEY(x), UNIQUE(x) -- Since a PRIMARY KEY is by definition (in MySQL) UNIQUE...
Since you want to find everyone with 2 kinds of credit, I'd try to make a query like if I was looking for duplicates, here's two ways to do that:
With subquery
Find duplicate records in MySQL
Without
Finding duplicate values in MySQL
Welcome to SO! Here's an approach using the nested query style you're trying to use. I've used explicit JOINs rather than FROM user, credit in the FROM clause, because this makes it clearer that it's a join.
Say your users table looks like this -
user_id user_phone_number user_city
6 75771 Leeds
7 75772 Wakefield
8 75773 Dewsbury
9 75774 Heckmondwike
10 75775 Huddersfield
And your credit table looks like this -
credit_user_id credit_date credit_number credit_type
7 2017-02-13 2 A
7 2017-02-13 2 B
6 2017-02-13 2 A
8 2017-02-13 4 B
The nested query in the AND clause returns records where the credit_type is not A, and the WHERE in the main query selects all records where the credit_type is is A, so if the record appears in both, the user must have two types of credit -
SELECT user_phone_number
FROM [user] AS u
JOIN credit AS c ON u.user_id = c.credit_user_id
WHERE credit_type = 'A'
AND u.user_id IN (
SELECT user_id
FROM [user] AS u
JOIN credit AS c ON u.user_id = c.credit_user_id
WHERE credit_type <> 'A')
As you can see from the tables, the user with the id of 7 has credit both of type A and B, so we end up with -
user_phone_number
75772
I'd agree that you might want to consider some of the points others have raised above, but won't repeat.
Related
I have a table called followers and I want to be able to find the current users followers, display that list, and then compare those values against another user ID to see if they are following them or not from that same table
This is the table I'm using currently to get the list of followers for a specific user:
followers
-------
followId - primary key, the unique id for the follow relationship
userId - the user that is following someone
orgId - that someone that the user is following
I tried using a union query but I wouldn't want to due to performance reasons (table may contain high volume of records) and because it isn't scalable (I think)?
The expected output should be the list of orgId's for which the user(the user I am checking against) is following, and another column that shows whether my user(my userId that I provide) is following that orgId value (i.e a following column).
Hmmm, if I understand correctly, you have two users and you want to know which orgs followed by the first are also followed by the second:
select f.orgid,
(exists (select 1
from followers f2
where f2.userId = $seconduserid and
f2.orgid = f.orgid
)
) as seconduserflag
from followers f
where f.userId = $firstuserid
This question already has an answer here:
How can I use FIND_IN_SET within a JOINED table?
(1 answer)
Closed 5 years ago.
Got 2 tables - contacts and messages:
contact_id | contact_email
1 | some#mail.com
2 | other#mail.com
3 | no#nono.com
message_id | message_recipients
1 | 1,2,3
2 | 3
message_recipients field contains ID(s) of contact(s) message was assigned to. Each message can have one or more IDs assigned, so they are separated by , symbol.
I need to show all contacts, and count of messages are assigned to each contact. Since message_recipients field may contain multiple IDs, I can't run a query like SELECT * FROM contacts, messages WHERE contacts.contact_id=messages.message_recipients because it won't work properly.
If I run SELECT * FROM contacts FULL JOIN messages, it returns many duplicated rows from contacts table. Sure thing, I can run SELECT * FROM contacts FULL JOIN messages GROUP BY contact_id, but this one returns only 1st message from messages table.
I know that in order to count how many messages each contact has assigned to, I will probably need to explode message_recipients field from each row into array and use code like if (in_array($contact_id, $message_recipients_array)) {$total++;} or similar. Now my main concern is how to all I need by writing as simple query as possible.
Fix your table structure. Do not store multiple values in one cell. See Normalization
For now, you can use FIND_IN_SET:
select c.contact_id,
c.contact_email,
count(*) no_of_messages
from messages m
join contacts c on find_in_set(c.contact_id, m.message_recipients) > 0
group by c.contact_id,
c.contact_email
But this will be slow as it can't use any index on the contact_id or message_recipients.
To actually fix the issues, don't include recipient_id in the messages table.
You should have stored single recipient in one row in a separate mapping table with many to many relation with (maybe) the following structure.
messages_recipients (
id int PK,
message_id int FK referring message(message_id),
message_recipient_id int FK referring contacts(contact_id)
)
Then all you had to do was:
select c.contact_id,
c.contact_email,
count(*) no_of_messages
from messages_recipients m
join contacts c on c.contact_id = m.message_recipient_id
group by c.contact_id,
c.contact_email
This query is Sargable and will be faster.
Fix your data structure! Storing ids in strings is a really bad idea. Why?
Numbers should be stored as numbers not strings.
SQL does not offer very good string functions.
Foreign key constraints should be properly expressed.
The query optimizer cannot use indexes or partitions.
SQL has a great method for storing lists: it is called a "table".
Sometimes, we are stuck with other people's really, really bad design decisions. MySQL does offer a method for doing what you want, find_in_set(). This is a hack to get around the short-comings of a bad data layout:
select . . .
from contacts c join
messages m
on find_in_set(c.contact_id, m.message_recipients) > 0
So I have the following:
A lookup table that has two columns and looks like this for example:
userid moduleid
4 4
I also have a users table that has a primary key userid which the lookup table references. The user table has a few users lets say, and looks like this:
userid
1
2
3
4
In this example, it show that the user with ID 4 has a match with module ID 4. The others are not matched to any moduleid.
I need a query that gets me data from the users table WHERE the moduleid is not 4. In my application, I know the module but I don't know the user. So the query should return the other userids apart from 4, because 4 is already matched with module ID 4.
Is this possible to do?
I think I understand your question correctly. You can use a sub-query to cross-check the data between both tables using the NOT IN() function.
The following will select all userid records from the user_tbl table that do not exist in the lookup_tbl table:
SELECT `userid`
FROM `user_tbl`
WHERE `userid` NOT IN (
SELECT DISTINCT(`userid`) FROM `lookup_tbl` WHERE moduleid = 4
)
There are several ways to do this, one pretty intuitive way (in my opinion) is the use an in predicate to exclude the users with moduleid 4 in the lookup table:
SELECT * FROM Users WHERE UserID NOT IN (SELECT UserID FROM Lookup WHERE ModuleID = 4)
There are other ways, with possibly better performance (using a correlated not exists query or a join for instance).
One other option is to use a LEFT JOIN so that you can get the values from both tables, even when there is not a match. Then, pick the rows where there is no userid value from the lookup table.
SELECT u.userid
FROM usersTable u
LEFT JOIN lookupTable lt ON u.userid = lt.userid
WHERE lt.userid IS NULL
Are you looking for a query like this?
select userid from yourtablename where moduleid<>4
So, let's say I have a hash/relational table that connects users, teams a user can join, and challenges in which teams participate (teams_users_challenges), as well as a table that stores entered data for all users in a given challenge (entry_data). I want to get the average scores for each user in the challenge (the average value per day in a given week). However, there is a chance that a user will somehow join more than one team erroneously (which shouldn't happen, but does on occasion). Here is the SQL query below that gets a particular user's score:
SELECT tuc.user_id, SUM(ed.data_value) / 7 as value
FROM teams_users_challenges tuc
LEFT JOIN entry_data ed ON (
tuc.user_id = ed.user_id AND
ed.entry_date BETWEEN '2013-09-16' AND '2013-09-22'
)
WHERE tuc.challenge_id = ___
AND tuc.user_id = ___
If a user has mistakenly joined more than one team, (s)he would have more than one entry in teams_users_challenges, which would essentially duplicate the data retrieved. So if a user is on 3 different teams for the same challenge, (s)he would have 3 entries in teams_users_challenges, which would multiply their average value by 3, thanks to the LEFT JOIN that automatically takes in all records, and not just one.
I've tried using GROUP BY, but that doesn't seem to restrict the data to only one instances within teams_users_challenges. Does anybody have any ideas as to how I could restrict the query to only take in one record within teams_users_challenges?
ADDENDUM: The columns within teams_users_challenges are team_id, user_id, and challenge_id.
If this is a new empty table, you can express your 'business rule' that a user should only join one team per challenge as a unique constraint in SQL:
alter table teams_users_challenges
add constraint oneUserPerTeamPerChallenge
unique (
user_id
, team_id
, challenge_id
);
If you can't change the table, you'll need to group by user and team and pick a single challenge from each group in the query result. Maybe pick just the latest challenge.
I can't test it, but if you can't clean up the data as Yawar suggested, try:
SELECT tuc.user_id, SUM(ed.data_value) / 7 as value
FROM entry_data ed
LEFT JOIN
(
select tuc.user_id, tuc.challenge_id from teams_users_challenges tuc group by tuc.user_id, tuc.challenge_id
) AS SINGLE_TEAM
ON SINGLE_TEAM.user_id = ed.user_id AND
ed.entry_date BETWEEN '2013-09-16' AND '2013-09-22'
WHERE tuc.challenge_id = ___
AND tuc.user_id = ___
I'm making a SNS that users can follow each other. If user A follows user B and user B also follows user A, they become friends.
Also consider that some popular people(like movie stars) may be followed by hundreds of thousands times, but a user can follow 1000 people max.
So given the table below, what is the best SQL query to fetch all friends' ids of user 1?
PS: I'm using MySQL 5.5.
Here is what I have done so far:
SELECT followee_id AS friend_id FROM follow
WHERE follower_id = 1 AND
followee_id IN (SELECT follower_id FROM follow
WHERE followee_id = 1);
CREATE TABLE follow
(
follower_id INT UNSIGNED NOT NULL,
followee_id INT UNSIGNED NOT NULL,
PRIMARY KEY (follower_id, followee_id),
INDEX (followee_id, follower_id)
);
Assuming that by 'best' you mean most performant, and given that a following must be mutual in order to meet your 'friend' criteria:
A filter using followee_id will hit your index better than a filter on follower_id
select
me.follower_id
from
follow me inner join
follow you
on
me.follower_id = you.followee_id
and me.followee_id = you.follower_id
where
me.followee_id = #user
(although note that RDBMS's like MSSQL will default to using your Primary Key as a clustered index, in which case its much of a muchness really.)