I have many a times tried using nested query for MySQL in PHP, but it does not work. Is it not possible to do nested/Joins queries?
Just a Scenario:
I have two tables one table with user id and the other with data. User logins and with sessions I have to cross check two different tables with user id (user and data). Is it not possible to nest/join these two tables to write a single query statement.
In short is nesting or joining two or more tables permitted in PHP coding?
YES, it is possible to join two or more tables in MySQL (and therefore, also when using PHP).
You need to post your table schema, if you want us to show a relevant join query. You could, however, try something like:
SELECT * FROM user AS t1
CROSS JOIN data AS t2
ON t1.userid=t2.userid
WHERE t1.userid='154'
(This query presumes that there always will be one row with the userid in both tables. You should use LEFT JOIN instead of CROSS JOIN to return a row even if there is no row in data for the userid. 154 is just an example userid.)
Have a look at http://dev.mysql.com/doc/refman/5.5/en/join.html for information on the JOIN syntax.
users
| user_id | username | password | enabled |
|---------|----------|----------|---------|
| 1 | john | sgsd2gg | 1 |
| 2 | jane | sdshdhd | 0 |
users_data
|udata_id| user_id | some_column |
|--------|---------|-------------------|
| 1 | 1 | Some title |
| 2 | 2 | another title |
Since you haven't posted your table schema, I can't give you an exact solution. But supposing you have a users table and a users_data table, where users_data are owned by a user. You can do a join on the table to retrieve all the data.
SELECT * -- Don't select all fields unless you need it
FROM users U LEFT JOIN users_data UD ON U.user_id = UD.user_id
WHERE U.user_id = 1
This would pull all the records for user with an ID of 1. This is a very simplistic join, but it should give you an idea.
Here's an example that visually describes the different options you can use : SQL Join Differences
Related
I've got the following two tables (in MySQL):
Phone_book
+----+------+--------------+
| id | name | phone_number |
+----+------+--------------+
| 1 | John | 111111111111 |
+----+------+--------------+
| 2 | Jane | 222222222222 |
+----+------+--------------+
Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 1 | 0945 | 111111111111 |
+----+------+--------------+
| 2 | 0950 | 222222222222 |
+----+------+--------------+
| 3 | 1045 | 333333333333 |
+----+------+--------------+
How do I find out which calls were made by people whose phone_number is not in the Phone_book? The desired output would be:
Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 3 | 1045 | 333333333333 |
+----+------+--------------+
There's several different ways of doing this, with varying efficiency, depending on how good your query optimiser is, and the relative size of your two tables:
This is the shortest statement, and may be quickest if your phone book is very short:
SELECT *
FROM Call
WHERE phone_number NOT IN (SELECT phone_number FROM Phone_book)
alternatively (thanks to Alterlife)
SELECT *
FROM Call
WHERE NOT EXISTS
(SELECT *
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number)
or (thanks to WOPR)
SELECT *
FROM Call
LEFT OUTER JOIN Phone_Book
ON (Call.phone_number = Phone_book.phone_number)
WHERE Phone_book.phone_number IS NULL
(ignoring that, as others have said, it's normally best to select just the columns you want, not '*')
SELECT Call.ID, Call.date, Call.phone_number
FROM Call
LEFT OUTER JOIN Phone_Book
ON (Call.phone_number=Phone_book.phone_number)
WHERE Phone_book.phone_number IS NULL
Should remove the subquery, allowing the query optimiser to work its magic.
Also, avoid "SELECT *" because it can break your code if someone alters the underlying tables or views (and it's inefficient).
The code below would be a bit more efficient than the answers presented above when dealing with larger datasets.
SELECT *
FROM Call
WHERE NOT EXISTS (
SELECT 'x'
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number
);
SELECT DISTINCT Call.id
FROM Call
LEFT OUTER JOIN Phone_book USING (id)
WHERE Phone_book.id IS NULL
This will return the extra id-s that are missing in your Phone_book table.
I think
SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL
SELECT t1.ColumnID,
CASE
WHEN NOT EXISTS( SELECT t2.FieldText
FROM Table t2
WHERE t2.ColumnID = t1.ColumnID)
THEN t1.FieldText
ELSE t2.FieldText
END FieldText
FROM Table1 t1, Table2 t2
SELECT name, phone_number FROM Call a
WHERE a.phone_number NOT IN (SELECT b.phone_number FROM Phone_book b)
Alternatively,
select id from call
minus
select id from phone_number
Don't forget to check your indexes!
If your tables are quite large you'll need to make sure the phone book has an index on the phone_number field. With large tables the database will most likely choose to scan both tables.
SELECT *
FROM Call
WHERE NOT EXISTS
(SELECT *
FROM Phone_book
WHERE Phone_book.phone_number = Call.phone_number)
You should create indexes both Phone_Book and Call containing the phone_number. If performance is becoming an issue try an lean index like this, with only the phone number:
The fewer fields the better since it will have to load it entirely. You'll need an index for both tables.
ALTER TABLE [dbo].Phone_Book ADD CONSTRAINT [IX_Unique_PhoneNumber] UNIQUE NONCLUSTERED
(
Phone_Number
)
WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = ON) ON [PRIMARY]
GO
If you look at the query plan it will look something like this and you can confirm your new index is actually being used. Note this is for SQL Server but should be similar for MySQL.
With the query I showed there's literally no other way for the database to produce a result other than scanning every record in both tables.
So I have the following tables
**accounts**
id | iban
1 | ES80 2310 0001 1800 0001 2345
2 | ES91 2100 0418 4502 0005 1332
**acc_rel**
account_id | target_table | target_id
1 | users | 2
2 | clients | 5
**users**
id | username | password
2 | abc | cba
**clients**
id | company_name
5 | some_name
So the thing is that with acc_rel I have an account related with an user, a client, or whatever other table.
Because of reasons, not my reasons, I can't change this tables or the way they "works".
What I need to do is, retrieve the account information with the username or the company_name from the other tables, I need to do it in a single query so I can use a WHERE to filter, or ORDER BY and LIMIT. At least I think that I need it in a single query to do so.
This would be the perfect output:
account_id | account_owner
1 | abc
2 | some_name
So how can I do it? I don't know which tables I am going to do the JOIN or what column name I need, thought I can know it with PHP before doing the query.
A solution changing all the tables scheme would be appreciated too if the correct way to do this is another.
Based on #knowledge... answer I made this query and it works exactly like I need it.
SELECT AR.account_id,COALESCE(U.username, C.company_name) AS account_owner
FROM accounts AS A
INNER JOIN acc_rel AS AR ON A.id = AR.account_id
LEFT JOIN users AS U ON U.id = AR.target_id AND AR.target_table = 'users'
LEFT JOIN clients AS C ON C.id = AR.target_id AND AR.target_table = 'clients'
It is going to be a pain to maintain if I need to add new tables but well, it works the way I need.
select AR.account_id,COALESCE(U.username,C.company_name) as account_owner
from accounts A
join acc_rel AR on A.id=AR.account_id
left join users U on U.id=AR.traget_id and AR.target_table='user'
left join clients C on C.id=AR.traget_id and AR.target_table='clients'
I have two tables in my MySql database:
user
- sid
- userid
- username
log
- sid
- userid
- login_time
As you can guess, there's a lot more records in log tables than in user table.
I am using php to present these records on my website in a table format as shown below.
no | userid | username | number of login |
1 | inzo | harvey | 233 |
2 | chae | schmidts | 433 |
3 | hibro | swainy | 12 |
To get the number of login for each user, I can send another queries in a for statement. But it's consuming resources and making the server slow in the end.
Can I have this result in one single join query?
Yes you can, you have to use count the logins for each user with a group by
select t1.userid, t1.username, count(t2.sid)
from user t1
left join
log t2
on t1.userid = t2.userid
group by t1.userid, t1.username
The left join ensures you that users without logins will still be returned, wit 0 as count.
Edit
About the question in the comment: if you want to only count the logins with a specific flag value, you can just add where flag = x before the group by; if you want to have a separate count for each value of the flag, you have to add that flag to both group by and select.
I guess best and by that I mean least resource consuming way would be to add "number_of_login" to user table and just increase it every time he/she is logged in, because any other solution will require looping
SELECT TABLE_A.row_id, TABLE_A.category, TABLE_A.val_1, TABLE_B.val_2
FROM TABLE_B
LEFT OUTER JOIN TABLE_A ON TABLE_B.row_id = TABLE_A.row_id
ORDER BY row_id;
If you want all the results, you need an outer join, not an inner one.
select a.sid,a.userid,a.username,COUNT(b.sid) from user a
Left join log b ON b.sid =a.sid group by a.sid
Recently I've been working on a PHP/MySQL script which reads information from a database with user info and file info stored in two separate tables.
Below are the schema's for the tables.
Table1
UID | Username | PermissionLevel
1 | First | 1
2 | Next | 3
3 | More | 2
Table2
FID | Filename | FileLevel | UploadUsername
1 | file.txt | 2 | First
2 | hand.mp4 | 1 | First
3 | 1245.dds | 1 | Next
4 | beta.sql | 3 | More
For the purpose of this message I have omitted the passwords column and the file title/description column, since they play no part in the result I am trying to achieve.
So far I have come up with this SQL code
SELECT DISTINCT table2.*,
table1.*
FROM table2 JOIN table2 ON table2.FileLevel <= table1.PermissionLevel
WHERE table2.UploadUsername = table1.Username
ORDER BY FID DESC LIMIT 7
This generates the appropriate listing I want, but does not filter the level of content shown.
Any user with a PermissionLevel of 1 should only see Files with a FileLevel of 1. Users with PermissionLevel of "2" can see files of FileLevel of both 2 AND 1, and so on.
But at the current stage it seems to just want to display ALL results regardless of File/Permission Level.
I've been stuck at this issue for a couple of days now and just can't seem to get my head around this.
It's likely to be something simple I may have overlooked, but I hope that a fresh pair of eyes may help me.
I'm not sure if I understand the question fully, but this is what I could make of it. You have a query that returns all uploaded files. You now want to filter that list, so it sometimes shows less results, depending on the user's permission level. Please note that this user is the active user on the website, who is not necessarily the same user who uploaded a file, so this condition does not work on the table1 table of your query.
A common solution to this is to have a session variable storing your current user's id, and possibly other information.
$_SESSION['user'] = 'somebody';
$_SESSION['permissionLevel'] = 3;
If you don't have permissionLevel in a local variable, you will have to join on the table1 table twice, once to find the uploader and once to find the permission level of the current user. You also have a typo in your original query. The following will give you both users
SELECT *
FROM table2
JOIN table1 uploader WHERE table2.UploadUsername=table1.Username
JOIN table1 currentuser WHERE table2.PersmissionLevel<=table1.PermissionsLevel AND users.Username='$_SESSION[user]'
ORDER BY FID DESC
I am making something like an announcement board that requires readers to acknowledge that they read it, and was wondering if there is a more efficient way of doing this.
I have 3 Tables on MySQL side:
+-----------------+ +-----------------+ +-----------------+
| Announcements | | Acknowledgement | | User |
+-----------------+ +-----------------+ +-----------------+
| announce_id | | ack_id | | user_id |
| announce_msg | | announce_id | | user_name |
| ... | | user_id | | ... |
+-----------------+ +-----------------+ +-----------------+
When a user "reads" the announcement (by clicking a button), Acknowledgment table will be inserted with the Announcement ID and User ID. When a second user "reads" the same announcement, Acknowledgement table will be inserted again with same Announcement ID and the second User ID and so on...
+--------------------------------+
| Acknowledgement |
+--------+-------------+---------+
| ack_id | announce_id | user_id |
+--------+-------------+---------+
| 1 | 1 | 1 |
| 2 | 1 | 4 |
| 3 | 1 | 3 |
| 4 | 3 | 1 |
| 5 | 3 | 6 |
| 6 | 3 | 2 |
+--------+-------------+---------+
Now to the problem. On the front end, when I list all the announcements on a page, I would have to first query for all the announcements. Then, for each announcement, I would have to do another query for all the users that have read this announcement.
$sql = "select * from Announcements";
$result = $pdo->query($sql);
while ($row = $result->fetch())
{
$announce_id = $row['announce_id'];
$announce_msg = $row['annouce_msg'];
$readers = "";
$sql2 = "select u.user_name from Acknowledgement as a INNER JOIN User as u where announcement_id =".$annouce_id;
$result2 = $pdo->query($sql);
while ($row2 = $result2->fetch())
{
$readers .= $row2['user_name'].", ";
}
echo "id:".$annouce_id.", message:".$announce_msg.", Readers:".$readers;
}
So if there 10 announcements on the page, there will be 10 sub-queries for each announcement. What I have now does the job right now... but what if there is 1000 announcements? Then there will be 1000 sub-queries? Sounds like the database will be really hammered. So I'm hoping there is a better way of doing this.
Also, if 1000 people in the user table reads all 1000 announcements, the acknowledgement table will have 1000x1000 entries. seems like the acknowledgement table will become really really long. Will that be a problem as time goes by?
This is a really rough example of what I'm trying to do but it did take me a long time to write all this. If more details is needed let me know.
There is a better way. You can use a single query with group_concat:
select a.*, group_concat(u.user_name separator ', ') as AllUsers
from Announcements a join
Acknowledgement ak
on a.Announce_Id = ak.Announce_Id join
User u
on u.user_ID = ak.User_ID
group by a.announce_id
This uses the MySQL feature of hidden columns to group by only one column (announce_id) but still pull in a bunch of other columns with no aggregations (everything else pulled in by the "*").
If your purpose here is to filter out the announcements that your current user has read, you can do this an entirely different way. Instead of querying for every announcement, and then finding out all the users that have read those announcements and examining those results to find ones that your use has read and trimming them from the displayed list, you can just query in one go for everything a particular user (or list of users) have not yet read.
Change your query to this:
SELECT * FROM Announcements WHERE Announce_id NOT IN (SELECT ANNOUNCE_ID FROM Acknowledgement WHERE User_ID = <INSERT USER ID HERE>)
That should return all Announcement rows that this particular user has not yet acknowledged. If you change that final WHERE clause to be WHERE User_ID IN () then you can specify a list of user IDs.
EDIT: Given the comment you posted above, you could use this query to get all announcements that have been read by no one:
SELECT * FROM Announcements WHERE Announce_id NOT IN (SELECT ANNOUNCE_ID FROM Acknowledgement WHERE User_ID IN (SELECT User_ID FROM User))
The logic for putting together a query to find announcements that haven't been read by someone (if not everyone) is escaping me right this second.
EDIT THE SECOND: Every announcement, and everyone who has and has not read it, requires use of a different kind of join that you've used above, a FULL OUTER JOIN. Unfortunately MySQL doesn't have that feature IIRC, but it can be simulated with a union query
SELECT A.*, ACK.*, U.* FROM Announcements AS A
INNER JOIN Acknowledgement AS ACK ON A.Announce_ID = ACK.Announce_ID
LEFT OUTER JOIN User AS U ON ACK.User_ID = U.User_ID
WHERE U.User_ID IS NOT NULL
UNION ALL
SELECT A.*, ACK.*, U.* FROM Announcements AS A
LEFT OUTER JOIN Acknowledgement AS ACK ON A.Announce_ID = ACK.Announce_ID
RIGHT OUTER JOIN User AS U ON ACK.User_ID = U.User_ID
I think that should do it. No facilities to test at the moment, of course.