简体   繁体   中英

SQL for returning values from a many-to-many relationship

I am stuck while attempting to retrieve data via PHP from a MySQL database when there are multiple keywords in the SQL statement. My expertise is mostly in the frontend so its been quite a struggle to get this going.

I have a screen where i want to show several skills(these are keywords), all retrieved from a single table in the DB. When a user selects on one or more skills and clicks on a link, i want to return the mapped projects for the selected skills, all via the right backend querying(ie another option i had was to return all data to the frontend and use JS to filter as desired, but somehow i don't currently find this optimal for my current scenario)

I have a MySQL table called web_projects which lists several projects with columns like project_id , project_name etc etc

Another table called web_skills lists several skills with columns like skill_id , skill_name etc etc

Since there is a many-to-many relationship (each project can have several skills and each skill can be included in several projects) I have a third table to manage this relationship. This table web_project_skills has 3 columns id , project_id , skill_id .

The visual view, with the mapping:

在此处输入图片说明

[please don't mind the data types, its just a quick table created and will be modified later]

I post data from my application via:

http://localhost/am/get.php?q=getprojectsforskill&skill=HTML5

and retrieve the string:

$q = trim($_GET["skill"]);

The following SQL(it is a researched effort) helps me get values for this request and it works fine:

$st = "SELECT web_projects.* 
           FROM web_projects 
            JOIN web_project_skills 
            ON web_projects.project_id=web_project_skills.project_id 
            WHERE web_project_skills.skill_id = 
                (SELECT web_skills.skill_id 
                FROM web_skills 
                WHERE web_skills.skill_name = '".$q."' 
                )";

Now my post method has changed and I get a space separated string, like:

http://localhost/am/get.php?q=getprojectsforskill&skill=HTML5%2CCSS3

My query now includes:

$q = trim($_GET["skill"]);
$pieces = explode(",", $q);

$subQuery = "SELECT web_skills.skill_id 
        FROM web_skills 
        WHERE web_skills.skill_name = '$pieces[0]'";

if(count($pieces)>1){
    for($i=1;$i<count($pieces);$i++){
        $subQuery.=" OR web_skills.skill_name = '$pieces[$i]' ";
    }
}   
$st = "SELECT web_projects.* 
           FROM web_projects 
            JOIN web_project_skills 
            ON web_projects.project_id=web_project_skills.project_id 
            WHERE web_project_skills.skill_id = ($subQuery)";

For a request with a single value it works fine since there is no addition to the SQL statement via the for loop.

But for request with more than one i get errors. Example URL(same as above):

http://localhost/am/get.php?q=getprojectsforskill&skill=HTML5%2CCSS3

The errors I get with the 3 statement changes I attempted in the subQuery variable:

 $subQuery.=" OR web_skills.skill_name = '$pieces[$i]' ";

Above statement returns an error: An Error occured! SQLSTATE[21000]: Cardinality violation: 1242 Subquery returns more than 1 row An Error occured! SQLSTATE[21000]: Cardinality violation: 1242 Subquery returns more than 1 row

$subQuery.=" UNION WHERE web_skills.skill_name='$pieces[$i]' ";

Above statement returns an error: An Error occured! SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WHERE web_skills.skill_name='CSS3' )' at line 7 An Error occured! SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WHERE web_skills.skill_name='CSS3' )' at line 7

 $subQuery.=" AND web_skills.skill_name = '$pieces[$i]' ";

Above statement returns an empty array

You don't need a sub-query for this, you can simply just add another join.

SELECT web_projects.* 
           FROM web_projects 
            JOIN web_project_skills 
            ON web_projects.project_id=web_project_skills.project_id 
            JOIN web_skills 
            ON web_skills.skill_id=web_project_skills.skill_id 
            WHERE skill_name = '".$q."'

That should return all projects that are associated with a given skill.

Based on the accepted answer by @juakali92, to anybody curious to know how i got this to work for my current scenario:

[For a request like http://localhost/am/get.php?q=getprojectsforskill&skill=HTML5%2CCSS3]

$q = trim($_GET["skill"]);

$pieces = explode(",", $q);
    $st= "SELECT DISTINCT web_projects.* 
    FROM web_projects 
    JOIN web_project_skills 
    ON web_projects.project_id=web_project_skills.project_id 
    JOIN web_skills 
    ON web_skills.skill_id=web_project_skills.skill_id 
    WHERE web_skills.skill_name = '$pieces[0]'";


if(count($pieces)>1){
    for($i=1;$i<count($pieces);$i++){
        $st.="OR skill_name = '".$pieces[$i]."'";
    }
}

I had to do a SELECT DISTINCT since i was getting multiple results from the tables. I guess its because i have the OR condition and there are multiple matches in the tables. The AND returns zero results with or without DISTINCT .

Note: There are security concerns that will need to be addressed separately.

EDIT/UPDATE:

Without the for :

$q = trim($_GET["skill"]);

$pieces = explode(",", $q);
$pieces1 = "'" . implode("','", $pieces) . "'";

$st= "SELECT DISTINCT web_projects.* 
FROM web_projects 
JOIN web_project_skills 
ON web_projects.project_id=web_project_skills.project_id 
JOIN web_skills 
ON web_skills.skill_id=web_project_skills.skill_id 
WHERE web_skills.skill_name IN ($pieces1)";

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM