How to query the second column of a JOIN table? - php

I would like to query the name of the friends of person n°4, so the numbers in 'otherPerson_id', but my attempt only query the id or name in 'person_id'. How could I ask to query the informations about the friends of person n°4, and not the information about person n°4 itself?
Here is my attempt :
$q = "SELECT DISTINCT p.idperson, p.name FROM person p INNER JOIN people_friends pf ON p.idperson = pf.person_id AND p.idperson = 4";
$res = $connexion->query($q);
$res->setFetchMode(PDO::FETCH_COLUMN);
while($record = $res->fetch()) {
echo $record[name];
echo "<br/>";
}
the table : (so when I look for the friends of person n°4, I would like it to return 1 and 7 )
EDIT :
CREATE TABLE people_friends
(
person_id integer NOT NULL,
otherperson_id integer NOT NULL,
CONSTRAINT people_friends_pkey PRIMARY KEY (person_id, otherperson_id),
CONSTRAINT people_friends_person_id_fkey FOREIGN KEY (person_id)
REFERENCES person (idperson) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT people_friends_person_id_fkey1 FOREIGN KEY (person_id)
REFERENCES person (idperson) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE person
(
idperson integer NOT NULL,
name character varying(50),
map bytea,
CONSTRAINT person_pkey PRIMARY KEY (idperson)
)
Thanks

Try this query.
SELECT DISTINCT
pFriend.idperson, pFriend.name
FROM people_friends pf
INNER JOIN person pFriend on pf.otherperson_id = pFriend.idperson
WHERE
pf.person_id = 4

You just have to add a WHERE clause where you check the pf table.
SELECT DISTINCT p.idperson
,p.name
FROM person p
INNER JOIN people_friends pf
ON p.idperson = pf.person_id
WHERE pf.otherperson_id = 4

Related

Multiple JOINs to the same subquery expression

I've recently been teaching myself SQL, and have been working on a toy project to do so. Here is a sample schema:
CREATE TABLE user (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
name VARCHAR(50)
);
INSERT INTO user(name) VALUES
("User 1"),
("User 2"),
("User 3"),
("User 4"),
("User 5");
CREATE TABLE friendship (
uid_1 INT,
uid_2 INT,
accepted_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (uid_1, uid_2),
CONSTRAINT fk_uid_1 FOREIGN KEY (uid_1) REFERENCES user (id),
CONSTRAINT fk_uid_2 FOREIGN KEY (uid_2) REFERENCES user (id)
);
INSERT INTO friendship(uid_1, uid_2) VALUES
(1, 2),
(2, 1);
CREATE TABLE event (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
name VARCHAR(50),
owner_id INT,
CONSTRAINT fk_owner_id FOREIGN KEY (owner_id) REFERENCES user (id)
);
INSERT INTO event (name, owner_id) VALUES
("Event 1", 1),
("Event 2", 2),
("Event 3", 3),
("Event 4", 4),
("Event 5", 5),
("Event 6", 1);
CREATE TABLE invite (
event_id INT NOT NULL,
sent_from_id INT NOT NULL,
sent_to_id INT NOT NULL,
PRIMARY KEY (event_id, sent_to_id),
CONSTRAINT fk_event_id FOREIGN KEY (event_id) REFERENCES event (id),
CONSTRAINT fk_sent_from_id FOREIGN KEY (sent_from_id) REFERENCES user (id),
CONSTRAINT fk_sent_to_id FOREIGN KEY (sent_to_id) REFERENCES user (id)
);
INSERT INTO invite(event_id, sent_from_id, sent_to_id) VALUES
(1, 2, 3);
As part of this project, I have a query that gets a list of users, with information populated relative to the currently authenticated user.
A simplified version of the query looks like this:
$select_users_query = "
SELECT
user.id AS id,
user.name AS name,
friendship.accepted_time AS friend_since
FROM user
LEFT JOIN friendship
ON friendship.uid_1 = user.id AND friendship.uid_2 = $relative_to_id
";
Then, at some endpoints, I want to return objects which have one or more users as sub-objects. In order to do this, I've been JOINing tables to the above query as a subquery, but when the returned object has multiple users (e.g., an invite to an event has a sending user, a receiving user, and a user that owns the event in question), the resulting query can end up pretty repetitive:
$select_invites_query = "
SELECT
event.id AS event_id,
event.name AS event_name,
owner.id AS owner_id,
owner.name AS owner_name,
owner.friend_since AS owner_friend_since,
sent_to.id AS sent_to_id,
sent_to.name AS sent_to_name,
sent_to.friend_since AS sent_to_friend_since,
sent_from.id AS sent_from_id,
sent_from.name AS sent_from_name,
sent_from.friend_since AS sent_from_friend_since,
FROM invite
INNER JOIN event
ON event.id = invite.event_id
INNER JOIN ($select_users_query) owner
ON event.owner_id = owner.id
INNER JOIN ($select_users_query) sent_from
ON invite.sent_from_id = sent_from.id
INNER JOIN ($select_users_query) sent_to
ON invite.sent_to_id = sent_to.id
";
My questions are:
Is repeating a subquery like this a performance issue during execution, assuming that the INNER JOINs all match on just a single row?
If not, is the additional parsing required for $select_invites_query a significant concern at all (especially as $select_users_query grows big and complex)?
Would using a variable here be a good idea, or a bad idea? From my inspection of EXPLAIN it seems as though MySQL is able to handle these JOINs pretty efficiently, but would defining a variable force MySQL to pull the unfiltered result set into memory before JOINing?
See SQL Fiddle schema here.
Since you appear to need self joins of same query, consider a Common Table Expression (CTE) (available in MySQL 8.0+) and with PHP parameterization. Below demonstrates with PHP's mysqli API in object-oriented and procedural styles:
$select_invites_query = "
WITH sub AS (
SELECT u.id AS id, u.`name` AS `name`,
f.accepted_time AS friend_since
FROM user
LEFT JOIN friendship f
ON f.uid_1 = u.id AND f.uid_2 = ?
)
SELECT event.id AS event_id, event.`name` AS event_name,
owner.id AS owner_id, owner.`name` AS owner_name,
owner.friend_since AS owner_friend_since,
sent_to.id AS sent_to_id, sent_to.`name` AS sent_to_name,
sent_to.friend_since AS sent_to_friend_since,
sent_from.id AS sent_from_id, sent_from.`name` AS sent_from_name,
sent_from.friend_since AS sent_from_friend_since
FROM invite
INNER JOIN event
ON event.id = invite.event_id
INNER JOIN sub owner
ON event.owner_id = owner.id
INNER JOIN sub sent_from
ON invite.sent_from_id = sent_from.id
INNER JOIN sub sent_to
ON invite.sent_to_id = sent_to.id";
// OBJECT-ORIENTED STYLE
$conn = new mysqli("my_host", "my_user", "my_pwd", "my_db");
$stmt = $conn->prepare($select_invites_query))
$stmt->bind_param("i", $relative_to_id);
$stmt->execute();
...
// PROCEDURAL STYLE
$conn = mysqli_connect("my_host", "my_user", "my_pwd", "my_db");
$stmt = mysqli_prepare($conn, $select_invites_query);
mysqli_stmt_bind_param($stmt, "i", $relative_to_id);
mysqli_stmt_execute($stmt);
...

Accessing Row from Mysql DB by chaining query of foreign key from another table

I'm having difficulty trying to find the best way to get my results from a table. I want to get the targeted row from a table by one using the primary key from another using a foreign key.
The tables are would be set similar to this(minus a lot of other attributes for space):
user Table:
user_Id(pk)
name
type
venue_Id(unique/indexed)
venue Table:
venue_Id(fk)
rating
Logic flow is: user_Id is provided by a session variable. Query DB table 'user' to find that user. Go to type of user to identify if user is person or venue. Assuming user is venue, go to DB table 'venue' and query table for rating using foreign key from unique/indexed venue_Id from user table.
The query looks like
SELECT rating FROM `venue` WHERE `user_Id` = '$user_Id' AND `type` = 'venue'
Is this possible, and if so, what is the correct way to go about it?
You have a few ways to retrieve this information.
Using JOIN:
SELECT v.rating
FROM venue v INNER JOIN user u
ON v.venue_id= u.venue_id
AND u.`user_Id` = '$user_Id' AND u.`type` = 'venue'
Using an IN sub-query
SELECT rating
FROM venue
WHERE venue_id IN (SELECT venue_id FROM user
WHERE `user_Id` = '$user_Id' AND `type` = 'venue')
BTW, you should consider protect your code from potential SQL Injections
Its a bit unclear you explained that way.
From what I get, there is 2 table User and Venue.
In User table u have: user_id, venue_id, name, type.
While in Venue table u have: venue_id, rating.
You are expecting to get rating (Venue Table) while you use the WHERE clause in user_id and type which both stored on User Table.
Your Query:
SELECT rating FROM venue WHERE user_Id = '$user_Id' AND type = 'venue'
It is impossible to get it done like above because you are selecting from venue table while user_id and type is not from venue table. So it will make it unidentified even you have chaining the FK. Because FK will only to show and make some constraint to parent child table.
The query should be something like this:
SELECT rating FROM venue v JOIN user u on v.venue_id = u.venue_id WHERE u.user_Id = '$user_Id' AND u.type = 'venue'
Correct me if I am wrong..
Combining rows from two tables based on the tables having columns with equal values is called an equi-join operation, it's the pattern we typically use to "follow" foreign key relationships.
As an example:
$sql = "SELECT v.rating
FROM `venue` v
JOIN `user` s
ON s.venue_Id = v.venue_Id
AND s.type` = 'venue'
WHERE s.user_Id` = '" . mysqli_real_escape_string($con, $user_Id) ."'"
This isn't the only pattern, there are several other query forms that will return an equivalent result.
As an example of using an EXISTS predicate:
$sql = "SELECT v.rating
FROM `venue` v
WHERE EXISTS
( SELECT 1
FROM `user` s
WHERE s.venue_Id = v.venue_Id
AND s.type` = 'venue'
AND s.user_Id` = '"
. mysqli_real_escape_string($con, $user_Id)
."'"
)";
The original query appears to be vulnerable to SQL Injection; the example queries demonstrate the use of the mysqli_real_escape_string function to "escape" unsafe values and make them safe to include in SQL text. (That function would only be appropriate if you are using the mysqli interface. Using prepared statements with bind placeholders is another approach.

MySQL displaying results from a many-to-many relationship

I have set up a many-to-many relationship db with 3 tables:
CREATE TABLES Films (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
Title VARCHAR(255)),
CREATE TABLE Moods (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
mood_name VARCHAR(255))
CREATE TABLE Films_Moods (
film_id INT NOT NULL,
mood_id INT NOT NULL,
PRIMARY KEY (film_id, mood_id),
FOREIGN KEY (film_id) REFERENCES Films(id) ON UPDATE CASCADE,
FOREIGN KEY (mood_id) REFERENCES Moods(id) ON UPDATE CASCADE)";
So there might be a film with 2 or more moods. Let them be $mood1 and $mood2.
I want to display the selected film in a table as a single row, for example in the following way:
<table>
<tr>
<th>Title</th>
<th>Mood(s)</th>
</tr>
and the PHP code:
while($row = mysqli_fetch_array($result))
{
echo "<tr>";
echo "<td>" . $row['Title'] . "</td>";
echo "<td>" . $row['Moods'] . "</td>";
echo "</tr>";
}
The question is: how to obtain $row['Moods'] that would somehow combine two rows:
film_1 moodName_1
film_1 moodName_2
Or is there an alternative approach to what I am trying to achieve?
EDIT
Actually Im using tables Genres and Ambiences similarly to Moods and Im trying to do a search with specific Genres and Ambiences:
"SELECT *,GROUP_CONCAT(ambienceName SEPARATOR ' ') AS ambiences FROM Films AS f
INNER JOIN Films_Genres AS fg ON f.id = fg.film_id
INNER JOIN Genres AS g ON g.id = fg.genre_id
INNER JOIN Films_Ambiences as fa ON f.id = fa.film_id
INNER JOIN Ambiences AS a ON a.id = fa.ambience_id
WHERE g.Name LIKE '$genre' AND (a.ambienceName LIKE '$ambience1' OR a.ambienceName LIKE '$ambience2')"
SELECT title,GROUP_CONCAT(mood_name SEPARATOR ' ') AS moods
FROM films
JOIN films_moods ON films.id=films_moods.film_id
JOIN moods ON films_moods.mood_id=moods.id
GROUP BY title
You can fiddle with this by going to http://sqlfiddle.com/#!2/c6dd3/14/0 - I also fixed a couple of typos in your schema.

Simple Design of friends list database

I have a small issue with making a friendship system database.
now I have a table called friends
let's say:
table friends:
you friend approve since
_________________________________________________
wetube youtube 1 4-12-2012
facebook wetube 1 4-12-2012
and i have this query code to fetch the friends of user called wetube.
mysql_query("SELECT f.* FROM friends f inner join users u on u.username =
f.you WHERE f.friend = 'wetube' UNION ALL SELECT f.* FROM friends f inner join users u on
u.username = f.friend WHERE f.you = 'wetube'");
now what I want exactly is how to fetch the friens of wetube and show it to him on his page.
fixed:
Finally I fixed the problem.
so this is the table:
CREATE TABLE IF NOT EXISTS `friends` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`you` varchar(255) NOT NULL,
`friend` varchar(255) NOT NULL,
`type` varchar(255) NOT NULL,
`since` date NOT NULL,
`message` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
and this is the php code to fetch the user friends
<?php
$username = $_SESSION['username'];
$friends_sql = mysql_query("SELECT * FROM friends WHERE (friend = '$username' OR you = '$username') ");
while($friends_row = mysql_fetch_assoc($friends_sql)){
if ($username == $friends_row['you']) {
echo $friends_row['friend']."<br/>";
} elseif($username == $friends_row['friend']) {
echo $friends_row['you']."<br/>";
}
}
?>
try it yourself it works 100%
$result = mysql_query("SELECT * FROM friends WHERE friend='wetube'");
$row = mysql_fetch_array($result);
echo $row['friend'], ' - ', $row['since'];
Not a direct answer to your question, but a completely normalised database would have two tables for this system.
Since this is a many-to-many relationship, I would do something like this:
UsersTable
------------------
id: int, serial
name: varchar
email: varchar
...
And
RelationshipTable
------------------
id: int, serial
user1_id: int, foreign key on UsersTable.id
user2_id: int, foreign key on UsersTable.id
approved: boolean
since: date
With a properly designed and normalised database, it will be much more easy to manage and create your queries.
You mean just the rows that have wetube in the [friend] column? You might just be over-thinking the joins:
SELECT * FROM [friends] WHERE [friend] = 'wetube'
If you want where wetube is in either column:
SELECT * FROM [friends] WHERE [friend] = 'wetube' OR [you] = 'wetube'
Or:
SELECT * FROM [friends] WHERE [friend] = 'wetube'
UNION ALL
SELECT * FROM [friends] WHERE [you] = 'wetube'
All of the wetube's friends are:
SELECT * FROM friends WHERE (friend = 'wetube' OR you = 'wetube') AND approve = 1
I would suggest removing the column approve and instead keeping a table of requests. This will mean that all approved requests are in the friends table and all pending approvals are in the friend_request (or whatever) table.
This two table design is more efficient because you do not always have to check that approve = 1 when you want to show friends (which is probably pretty often).
Maybe not understanding this exactly.
Select * from Friends f
where f.you = 'wetube' or f.friend = 'wetube'
Guess your looking for user info as well, reason for inner joins.
Maybe removing the approval column, and have 2 records. The 2nd record is when the friend approves as a friend.
Then you can easily see who YOU are friends with, and they in return are friends with.
wetube --> youtube
facebook --> youtube
youtube --> wetube wetube would approve a friendship request for youtube, adding a record
wetube --> facebook
Then you could much easily ask who is friends of wetube.
Just an idea, probably not the answer you were looking for.

MySQL 3 table search with one table not directly related

I have 3 tables :
wp_users - stores main information about all users,
wp_usermeta - stores additional information about users(first/last name/etc),
wp_friends - stores information about friends from third party services related to a specific user from wp_users
If you are not familiar with WordPress, you can see the structure of both tables at http://codex.wordpress.org/images/9/9e/WP3.0-ERD.png
The structure of my custom table wp_friends is as follows:
CREATE TABLE wp_friends (
id bigint(20) unsigned NOT NULL auto_increment,
uid bigint(20) unsigned NOT NULL default '0',
fr_id VARCHAR (60) NOT NULL default '',
service VARCHAR (20) NOT NULL default '',
name VARCHAR (80) NOT NULL default '',
photo VARCHAR (255) NOT NULL default '',
PRIMARY KEY (id),
KEY uid (uid),
KEY fr_id (fr_id),
KEY service (service)
)`
The uid column is corresponds to the ID column in the wp_users table - this is how I determine which record corresponds to which user.
What I'm trying to do is to create a query that will look in all of the three tables for a match against a keyword. Here is what I've come with so far(the first part was generated by a search function of WordPress):
SELECT
wp_users.ID,wp_users.display_name,wp_users.user_login,
wp_users.user_email,fr.fr_id,fr.name,fr.photo,fr.service
FROM wp_users
INNER JOIN wp_usermeta ON (wp_users.ID = wp_usermeta.user_id)
LEFT JOIN wp_socialaccess_friends AS fr ON fr.uid = 2
WHERE
(
(user_login LIKE '%nik%' OR user_nicename LIKE '%nik%')
AND
(wp_usermeta.meta_key = 'wp_user_level' AND CAST(wp_usermeta.meta_value AS CHAR) != '0')
)
OR ( fr.uid = 2 AND (fr.fr_id LIKE '%nik%' OR fr.name LIKE '%nik%'))
GROUP BY wp_users.ID,fr.fr_id ORDER BY user_login ASC
In the above query, the keyword is "nik"(which also matches a user_login column). The fr.uid part is needed so the returned results are only for the current user. The query fails in the following ways:
It returns all rows from the wp_friends table(because the user_login column is matched as well), that have wp_friends.uid = 2
It returns rows that have wp_friends.uid = 2 but matched with users where wp_users.ID != 2
Is it possible to create a single query, that would return the selected columns, but will also prevent duplicates?
What about joining on a sub-select like:
SELECT
wp_users.ID,wp_users.display_name,wp_users.user_login,
wp_users.user_email
FROM wp_users
INNER JOIN wp_usermeta ON (wp_users.ID = wp_usermeta.user_id)
left join(
select fr_id, uid,name,photo,service from wp_socialaccess_friends where
uid = 2 and
(fr_id LIKE '%nik%' OR name LIKE '%nik%')
) AS fr ON wp_users.ID = fr.uid
WHERE
(
(user_login LIKE '%nik%' OR user_nicename LIKE '%nik%')
AND
(wp_usermeta.meta_key = 'wp_user_level' AND CAST(wp_usermeta.meta_value AS CHAR) != '0')
)
GROUP BY wp_users.ID,fr.fr_id ORDER BY user_login ASC

Categories