MySQL Join - Allowing users to attend events - mysql

I am wanting to create a simple system so that a user can attend an event. This will be done in three tables; user, event and attendance. I have created the user and event table.
User table
+---------+----------+
| User_id | Username |
+---------+----------+
| 1 | User1 |
+---------+----------+
| 2 | User2 |
+---------+----------+
_
Event table
+----------+------------+
| Event_id | Event_name |
+----------+------------+
| 1 | Event1 |
+----------+------------+
| 2 | Event2 |
+----------+------------+
Now I am wanting to create a table that shows the following on my webpage.
+-------+--------------+----------+---------+------------+---------------+
| At_id | At_attending | Event_id | User_id | Event_name | User_username |
+-------+--------------+----------+---------+------------+---------------+
| 1 | N | 1 | 1 | Event1 | User1 |
+-------+--------------+----------+---------+------------+---------------+
| 2 | N | 1 | 2 | Event1 | User2 |
+-------+--------------+----------+---------+------------+---------------+
| 3 | N | 2 | 1 | Event2 | User1 |
+-------+--------------+----------+---------+------------+---------------+
| 4 | N | 2 | 2 | Event2 | User2 |
+-------+--------------+----------+---------+------------+---------------+
Each user should have their own "At_attending" for each event where it will either be 'Y' or 'N'. By default it should automatically be 'N'. I'm unsure how to create the Attending table with suitable joins to receive the output I desire on the last table.
Many thanks.

You don't have to create a large table like that one you have put in.
At_id | At_attending | Event_id | User_id |
+------+--------------+----------+---------+
That's enough. The Event_id is then the foreign key to the event table, and User_id is a foreign key to the user table. This will give you an ability to link a row to a specific row on other tables. More on foreign keys.
CREATE TABLE Attenting
(
At_id int NOT NULL,
At_attending BIT(1) DEFAULT 0,
Event_id int,
User_id int,
PRIMARY KEY (At_id),
FOREIGN KEY (Event_id) REFERENCES Event(Event_id)
FOREIGN KEY (User_id) REFERENCES User(User_id)
)
At_attending is of type BIT which means that 0 can be interpret as FALSE and 1 as TRUE. Of course you can use BOOL or BOOLEAN but at MySQL, these types are synonyms for TINYINT(1). A value of zero is considered false. Non-zero values are considered true.
Or you can make yourself easier with CHAR(1) and supporting only T and F (or Y and N). But you have to define a constraint to ensure that the column only accepts both characters.
If you want to query the table as your example, just use JOIN - syntax.
SELECT
`User`.username AS User_username,
`Event`.Event_name,
`Attenting`.*
FROM
`Attenting`
INNER JOIN
`User`
ON
`Attenting`.User_id = `User`.User_id
INNER JOIN
`Event`
ON
`Attenting`.Event_id = `Event`.Event_id
The above will select all data from the three tables and will be shown as your example. The JOIN syntax ensures that other table can be connected to the current table.
Attenting.* will select all columns in the Attenting table.
If you want to select by specific parameters, just add a WHERE on the end.

You're going to need a so-called JOIN table relating users to events. Let's call it user_event. It will have these two columns.
user_id
event_id
That's all it needs. It doesn't need its own ID number or anything else. The two columns together are the primary key for the table. The presence of a row in user_event means the particular user is signed up to attend the particular event.
To generate the kind of display you showed, do this:
SELECT u.user_id,
u.username,
IF(ue.user_id IS NOT NULL, 'Y', 'N') AS attending,
e.event_id,
e.event_name
FROM event AS e
CROSS JOIN user AS u
LEFT JOIN user_event AS ue ON ue.event_id = e.event_id
AND ue.user_id = u.user_id
ORDER BY u.user_id, e.event_id
This will get you a row for every combination of user and event, and a Y or N stating whether the particular user is attending. Here's a SQLfiddle showing it.
http://sqlfiddle.com/#!2/2ac9c/1/0

Related

Mysql select distinct user with each of their own preference

Let's say I have a user and preference table, as well as a bridge table user_preference between the two:
/* user table: */
+----------+--------------+
| Field | Type |
+----------+--------------+
| id | int |
| username | varchar(255) |
+----------+--------------+
/* preference table: */
+------------+--------------+
| Field | Type |
+------------+--------------+
| preference | varchar(255) |
+------------+--------------+
/* user_preference table: */
+-----------------+--------------+
| Field | Type |
+-----------------+--------------+
| user_id | int |
| preference_name | varchar(255) |
+-----------------+--------------+
For instance there are 3 preferences to choose from: "swimming", "watching TV", "cycling". And one user can have zero or all 3 of the preferences, which is reflected on the user_preference table.
Now I want to query 10 different users, and with all of them each of their own preferences included, either null or mutiple preferences, how to construct a select statement for that?
So far I have tried something like this:
SELECT u.*, p.preference_name
FROM user u
LEFT JOIN user_preference p ON p.user_id = u.id
LIMIT 10;
/* Result: */
id | username | preference_name
1 | user1 | swimming
1 | user1 | cycling
2 | user2 | null
3 | user3 | watching TV
... /* rest of the result */
As you can see the result will return a duplicate user1, and it won't be 10 distinct users. I'm aware of the distinct and group by keywords, it doesn't solve the problem, as it will only return a single preference for a user, while the user can have multiple preferences.
How to do that with one single select statement?
Try this.
SELECT u.*,
GROUP_CONCAT(DISTINCT p.preference_name) AS prefs
FROM user u
LEFT JOIN user_preference p ON p.user_id = u.id
GROUP BY u.id
LIMIT 10;
The GROUP_CONCAT() will make a comma-separated list of preferences for each user.
Pro tip. When tables get very large, altering ENUMs to add more values gets very time-consuming. Plus, it's usually unwise to design a database so it needs lots of ALTER TABLE statements as it grows. So, the approach you have outlined is the right way to go if you want your possible preferences to be open-ended.

Get Field Based on FK

I didn't quite know how to word my issue, so I apologize for the odd title. I currently have two MySQL tables, business and users that look like the following respectively:
+-------------+----------+
| business_id | owner_id |
| 1 | 1 |
| 2 | 3 |
+-------------+----------+
+---------+-------------+----------------+
| user_id | business_id | email |
| 1 | 1 | a#domain.com |
| 2 | 1 | b#domain.com |
| 3 | 2 | c#domain.com |
+---------+-------------+----------------+
Right now, I need to get the email of the user where the business_id field in the user table matches that of the owner_id in the business table and I will always have the user_id (but it might not necessarily be the owner). To demonstrate what I mean, I can achieve what I want through this mess of a code:
SELECT
`email`
FROM
`user`
WHERE
`user_id` =(
SELECT
`owner_id`
FROM
`business`
WHERE
`business_id` =(
SELECT
`business_id`
FROM
`user`
WHERE
`user_id` = :user_id
)
)
So if I were to pass the a value of 1 or 2 for the user_id parameter, it would return a#domain.com and if I passed a value of 3 it would return c#domain.com.
I just feel as thought there is a better way!
Insead of subqueries you can use joins to achieve the same result by joining the user table twice with different aliases:
select u2.email
from user u1
inner join business b on u1.business_id=b.business_id
inner join user u2 on b.owner_id=u2.user_id
where u1.user_id=...
DEMO

I need to COUNT the number of leads a user has at any one time

I am trying to Count the leads that a user will have at any specific time but i am getting an error MySQL returned an empty result set (i.e. zero rows). But i have data in the users table and also inside the leads table.
Table Name: users (user_id is Primary Key)
+----------+
| user_id |
+----------+
| 1 |
| 2 |
| 3 |
+----------+
Table Name: leads (id is Primary Key and user_id is Foreign Key)
+-----------+----------+
| id | user_id |
+-----------+----------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+-----------+----------+
This is my code:
function user_leads_count() {
return mysql_result(mysql_query("SELECT users.user_id, COUNT(leads.id) AS NumberOfLeads FROM (leads INNER JOIN users ON leads.id=users.user_id) GROUP BY id HAVING COUNT(leads.id) > 0"));
}
Try this SQL
SELECT users.user_id, COUNT(leads.id) AS NumberOfLeads
FROM leads
JOIN users USING (user_id)
GROUP by user
HAVING NumberOfLeads > 0
Do you need to reference the users table, if not try this should work
SELECT COUNT(id) AS NumberOfLeads, user_id
FROM Leads
GROUP BY user_id
HAVING COUNT(leads.id) > 0

MySQL: How to fetch data with left-join if column contains multiple ids?

Assuming the following:
Table "info":
id | target_ids
----|------------
1 | 2
2 |
3 | 4,1
4 | 2,3,1
Table "targets":
id | value
----|------------
1 | dog
2 | cat
3 | tiger
4 | lion
Using left join, I'm expecting something like this:
id | target_ids | value
----|---------------------
1 | 2 | cat
2 | |
3 | 4,1 | lion,dog
4 | 2,3,1 | cat,tiger,dog
I've tried this:
select info.*, targets.value from info left join targets on info.target_ids = targets.id
The results I got is single values in "value" column
id | target_ids | value
----|---------------------
1 | 2 | cat
2 | |
3 | 4,1 | lion
4 | 2,3,1 | cat
How can I get results as it's showing in the 3rd table? Thanks
You need to use MySQL's FIND_IN_SET() function as the join criterion:
SELECT info.*, GROUP_CONCAT(targets.value) AS value
FROM info LEFT JOIN targets ON FIND_IN_SET(targets.id, info.target_ids)
GROUP BY info.id
See it on sqlfiddle.
However, you would probably be best to normalise your data structure and store your info-target relations in a separate table:
CREATE TABLE InfoTargets (
InfoID INT NOT NULL,
TargetID INT NOT NULL,
PRIMARY KEY (InfoID, TargetID),
FOREIGN KEY (InfoID) REFERENCES info (id),
FOREIGN KEY (TargetID) REFERENCES targets (id)
);
INSERT INTO InfoTargets VALUES
(1,2),
(3,4), (3,1),
(4,2), (4,3), (4,1);
ALTER TABLE Info DROP COLUMN target_ids;
Then you would do:
SELECT info.id,
GROUP_CONCAT(targets.id) AS target_ids,
GROUP_CONCAT(targets.value) AS value
FROM InfoTargets
LEFT JOIN info ON InfoID = InfoTargets.InfoID
LEFT JOIN targets ON TargetID = InfoTargets.TargetID
GROUP BY info.id
If the order of targets is important (and might differ between each info item), you would need to create an additional rank column in InfoTargets.

SQL LIMIT to get latest records

I am writing a script which will list 25 items of all 12 categories. Database structure is like:
tbl_items
---------------------------------------------
item_id | item_name | item_value | timestamp
---------------------------------------------
tbl_categories
-----------------------------
cat_id | item_id | timestamp
-----------------------------
There are around 600,000 rows in the table tbl_items. I am using this SQL query:
SELECT e.item_id, e.item_value
FROM tbl_items AS e
JOIN tbl_categories AS cat WHERE e.item_id = cat.item_id AND cat.cat_id = 6001
LIMIT 25
Using the same query in a loop for cat_id from 6000 to 6012. But I want the latest records of every category. If I use something like:
SELECT e.item_id, e.item_value
FROM tbl_items AS e
JOIN tbl_categories AS cat WHERE e.item_id = cat.item_id AND cat.cat_id = 6001
ORDER BY e.timestamp
LIMIT 25
..the query goes computing for approximately 10 minutes which is not acceptable. Can I use LIMIT more nicely to give the latest 25 records for each category?
Can anyone help me achieve this without ORDER BY? Any ideas or help will be highly appreciated.
EDIT
tbl_items
+---------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+-------+
| item_id | int(11) | NO | PRI | 0 | |
| item_name | longtext | YES | | NULL | |
| item_value | longtext | YES | | NULL | |
| timestamp | datetime | YES | | NULL | |
+---------------------+--------------+------+-----+---------+-------+
tbl_categories
+----------------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+------------+------+-----+---------+-------+
| cat_id | int(11) | NO | PRI | 0 | |
| item_id | int(11) | NO | PRI | 0 | |
| timestamp | datetime | YES | | NULL | |
+----------------+------------+------+-----+---------+-------+
Can you add indices? If you add an index on the timestamp and other appropriate columns the ORDER BY won't take 10 minutes.
First of all:
It seems to be a N:M relation between items and categories: a item may be in several categories. I say this because categories has item_id foreign key.
If is not a N:M relationship then you should consider to change design. If it is a 1:N relationship, where a category has several items, then item must constain category_id foreign key.
Working with N:M:
I have rewrite your query to make a inner join insteat a cross join:
SELECT e.item_id, e.item_value
FROM
tbl_items AS e
JOIN
tbl_categories AS cat
on e.item_id = cat.item_id
WHERE
cat.cat_id = 6001
ORDER BY
e.timestamp
LIMIT 25
To optimize performance required indexes are:
create index idx_1 on tbl_categories( cat_id, item_id)
it is not mandatory an index on items because primary key is also indexed.
A index that contains timestamp don't help as mutch. To be sure can try with an index on item with item_id and timestamp to avoid access to table and take values from index:
create index idx_2 on tbl_items( item_id, timestamp)
To increase performace you can change your loop over categories by a single query:
select T.cat_id, T.item_id, T.item_value from
(SELECT cat.cat_id, e.item_id, e.item_value
FROM
tbl_items AS e
JOIN
tbl_categories AS cat
on e.item_id = cat.item_id
ORDER BY
e.timestamp
LIMIT 25
) T
WHERE
T.cat_id between 6001 and 6012
ORDER BY
T.cat_id, T.item_id
Please, try this querys and come back with your comments to refine it if necessary.
Leaving aside all other factors I can tell you that the main reason why the query is so slow, is because the result involves longtext columns.
BLOB and TEXT fields in MySQL are mostly meant to store complete files, textual or binary. They are stored separately from the row data for InnoDB tables. Each time a query involes sorting (explicitly or for a group by), MySQL is sure to use disk for the sorting (because it can not be sure in advance how large any file is).
And it is probably a rule of thumb: if you need to return more than a single row of a column in a query, the type of the field is almost never should be TEXT or BLOB, use VARCHAR or VARBINARY instead.
UPD
If you can not update the table, the query will hardly be fast with the current indexes and column types. But, anyway, here is a similar question and a popular solution to your problem: How to SELECT the newest four items per category?