Get Field Based on FK - mysql

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

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.

Insert data in table using two or more tables

I have two existing table and wants to create third table with help of few columns. The fist two tables are;
Table one: users
|id | name | sid |
| 1 | demo | test1 |
| 2 | anu | test2 |
Table one: insights
| id | description| name |
| 1 | yes | demoone|
| 2 | no | demotwo|
I want to insert data in new table called insight_owner. As per my knowledge, I made below query but that is giving me below error
ERROR 1242 (21000): Subquery returns more than 1 row
The query used is
insert into insight_owner (column_one, column_two, column_three, column_four, column_five) VALUES ('1', '0', NULL, (select u.id from users u where u.sid='test1'), (select i.id from insights i)) ;
Expected output is
| column_one| column_two| column_three| column_four| column_five| column_six |
+----+-----------------+--------------------+---------------+-----------+--------------------+
| 1 | 1 | 1 | NULL | 1 | 1 |
| 2 | 1 | 1 | NULL | 1 | 2 |
column_five = Users id
column_six = Insight id
INSERT...SELECT syntax is what you're looking for (instead of INSERT...VALUES, which is limited to single values per column in each value list). That allows you to select the data directly from the table(s) concerned, using normal SELECT and JOIN syntax. You can also hard-code values which you want to appear on every row, just as you can in a normal SELECT statement. Basically, write the SELECT statement, get it to output what you want. Then stick an INSERT at the start of it and it sends the output to the desired table.
insert into insight_owner (column_one, column_two, column_three, column_four, column_five)
select '1', '0', NULL, (select u.id from users u where u.sid='test1'), i.id
from insights i
You are using
insert into insight_owner (column_one, column_two, column_three, column_four, column_five) VALUES ('1', '0', NULL, (select u.id from users u where u.sid='test1'), (select i.id from insights i));
Which basically inserts one row in your new table.
So, when you add subquery
select i.id from insights i
It will return all rows from insights table an you actually want just one value.
The result you will get is
| id |
| 1 |
| 2 |
And you want
| id |
| 1 |
So, you should be adding conditional that will make sure you are getting only one result as you are doing with first query (where u.sid='test1'), or limit.
I hope this helps.

Left Join takes very long time on 150 000 rows

I am having some difficulties to accomplish a task.
Here is some data from orders table:
+----+---------+
| id | bill_id |
+----+---------+
| 3 | 1 |
| 9 | 3 |
| 10 | 4 |
| 15 | 6 |
+----+---------+
And here is some data from a bills table:
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
+----+
I want to list all the bills that have no order associated with.
In order to achieve that, I thought that the use of LEFT JOIN was appropriated so I wrote this request:
SELECT * FROM bills
LEFT JOIN orders
ON bills.id = orders.bill_id
WHERE orders.bill_id IS NULL;
I thought that I would have the following result:
+----------+-----------+----------------+
| bills.id | orders.id | orders.bill_id |
+----------+-----------+----------------+
| 2 | NULL | NULL |
| 5 | NULL | NULL |
+----------+-----------+----------------+
But I can't reach the end of the request, it has run more than 5 minutes without result, I stopped the request because this can't be a production time anyway.
My real dataset has more than 150 000 orders and 100 000 bills. Is the dataset too big?
Is my request wrong somewhere?
Thank you very much for your tips!
EDIT: side note, the tables have no foreign keys defined... *flies away*
Your query is fine. I would use table aliases in writing it:
SELECT b.*
FROM bills b LEFT JOIN
orders o
ON b.id = o.bill_id
WHERE o.bill_id IS NULL;
You don't need the NULL columns from orders, probably.
You need an index on orders(bill_id):
create index idx_orders_billid on orders(bill_id);
By your where statement, I assume your looking for orders that have no bills.
If that's the case you don't need to do a join to the bills table as they would by definition not exist.
You will find
SELECT * FROM orders
WHERE orders.bill_id IS NULL;
A much better performing query.
Edit:
Sorry I missed your "I want to list all the bills that have no order associated with." when reading the question. As #gordon pointed out an index would certainly help. However if changing the scheme is feasible I would rather have a nullable bill.order_id column instead of a order.bill_id because you won't need a left join, an inner join would suffice to get order bills as it would be a quicker query for your other assumed requirements.

MySQL Join - Allowing users to attend events

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

INNER or LEFT Joining Multiple Table Records Into A Single Row

Phone Table
+----------------+-------------+
| Field | Type |
+----------------+-------------+
| f_id | int(15) |
| f_client_id | int(11) |
| f_phone_type | varchar(50) |
| f_phone_number | varchar(13) |
+----------------+-------------+
Clients Table
+-----------------------------+--------------+------+-----+
| Field | Type | Null | Key |
+-----------------------------+--------------+------+-----+
| f_id | int(15) | NO | PRI |
| f_first_name | varchar(13) | YES | MUL |
| f_mi | char(1) | YES | |
| f_last_name | varchar(20) | NO | MUL |
+-----------------------------+--------------+------+-----+
Assumptions:
Each record in 'Phone Table' belongs to one record in 'Clients Table'.
Each record in 'Clients Table' can have 0 or more records in 'Phone Table'.
Simple Translation: A client can have 0 or more phone numbers
With a standard LEFT or INNER join, I get something like this:
+------------+------------+--------------+
| name | Phone Type | Phone Number |
+------------+------------+--------------+
| John Smith | Home | 712-555-6987 |
| John Smith | Work | 712-555-1236 |
+------------+------------+--------------+
I need a query that will give me the work and home numbers that belong to a given client:
+------------+----------------+--------------+
| Name | Work Number | Home Number |
+------------+----------------+--------------+
| John Smith | 712-555-1236 | 712-555-6987 |
+------------+----------------+--------------+
Is it possible to do a LEFT or INNER join and then merge those results into a single row? I've seen similiar questions on this, but the examples given were much more complex than what I'm after:
Similiar Questions
MySQL Need help constructing query: join multiple tables into single row
SQL left join with multiple rows into one row
Thanks
Though you can join several numbers (in any) into a single field:
SELECT
CONCAT(f_first_name, ' ', f_last_name) as Client_Name,
GROUP_CONCAT(IF(phone_type='work',f_phone_number, NULL)) as Work_Numbers,
GROUP_CONCAT(IF(phone_type='home',f_phone_number, NULL)) as Home_Numbers
FROM clients
JOIN phone
USING (f_id)
WHERE phone_type IN ('home', 'work')
GROUP BY f_id;
Are there limits on how many Work or Home numbers a particular Client record can have? If it can be many, then no, there is no way to make a single row. If there can be at most 1 of each, then you can just join on the phone numbers table twice.
SELECT CONCAT(c.f_first_name, ' ', c.f_last_name) as Client_Name,
wp.f_phone_number as Work_Number,
hp.f_phone_number as Home_Number
FROM clients c
LEFT OUTER JOIN phone hp
ON hp.f_client_id = c.f_id
AND
hp.phone_type = 'home'
LEFT OUTER JOIN phone wp
ON wp.f_client_id = c.f_id
AND
wp.phone_type = 'work'
With LEFT OUTER JOINs you will still get rows for clients with missing numbers. If you don't want to see those, change to INNER JOINs.
Edit: As Nick kindly reminds me, this will return multiple rows for clients with multiple phone numbers. Once you have the data you need, you're then faced with presentation issue. You can handle that in the application layer, or make a sacrifice to the SQL gods and look into MySQL's GROUP_CONCAT() function.