0

The question has been resolved. But if you have a "better" or another way to do it then feel free to add a comment! Thanks all for reading! :)

I'm trying to make a dynamic query. Everything is working perfectly except for one thing. I've Google'd for days but I can't figure out how I can make the following work;

SELECT project.name, project.description, track.name, track.description
   , SDG.position, SDG.title, SDG.description
   , sprint_numbers.number, sprint_options.option
   , resources.name, resources.description
   , URLs.URL 
FROM project INNER JOIN track ON project.track_id = track.id 
INNER JOIN project_SDG ON project.id = project_SDG.project_id 
INNER JOIN SDG ON project_SDG.SDG_id = SDG.id
INNER JOIN sprint ON sprint.project_id = project.id 
INNER JOIN sprint_numbers ON sprint_numbers.id = sprint.sprint_number_id 
INNER JOIN sprint_options ON sprint_options.id = sprint.sprint_option_id 
INNER JOIN resources ON project.id = resources.project_id 
INNER JOIN URLs ON URLs.id = resources.id 
WHERE 1=1 
   AND MATCH (project.name) AGAINST (:name_project) 
   AND MATCH (project.description) AGAINST (:description_project) 
   AND SDG.id = :SDG_1 
   AND SDG.id = :SDG_2

The query executes but does not return anything. The problem is that the SDG.id can't be true to both :SDG_1 and :SDG_2.

Using the OR operator works, but that does not return it the way I want. It must "act" as an AND operator. (:SDG_1 & :SDG_2 are the names of the PHP variables that bind to the SQL statement parameters.)

The query should filter for both values. The values given to :SDG_1 and :SDG_2 must both exist in the SDG.id column of the project_SDG table. If the value of :SDG_1 exists, but :SDG_2 not, then the query should not return anything.

I found this on StackOverflow but it did not work for me: SELECTING with multiple WHERE conditions on same column

I hope someone can help me out.

EDIT: minimal reproducible example

QUERY:

SELECT * FROM project
INNER JOIN project_SDG ON project.id = project_SDG.project_id 
INNER JOIN SDG ON project_SDG.SDG_id = SDG.id
WHERE SDG.id = 1 AND SDG.id = 7 AND SDG.id = 14 AND SDG.id = 17

Project table

+------------------+---------------------------+------------+
|     id  name     |        description        |  track_id  |
+------------------+---------------------------+------------+
| 1   project name | This is a description   2 |            |
+------------------+---------------------------+------------+

SDG table

+-----+-----------+-------------+---------------------------------------------+
| id  | position  |   title     |                 description                 |
+-----+-----------+-------------+---------------------------------------------+
|   1 |         1 | SDG 1 to 17 | There're multiple SDGs ranging from 1 to 17 |
|  17 |        17 | SDG 1 to 17 | There're multiple SDGs ranging from 1 to 17 |
+-----+-----------+-------------+---------------------------------------------+

project.SDG (bridge-table)

+------------+--------+
| project.id | SDG.id |
+------------+--------+
|          1 |      1 |
|          1 |      2 |
|          1 |      3 |
+------------+--------+
May
  • 3
  • 2
  • 1
    https://stackoverflow.com/help/minimal-reproducible-example - Please post your table structure and records. – Kamal Joshi Dec 24 '19 at 17:48
  • Correct. A single field in a table row cannot have more than one value. Usually these kinds of questions are wanting something like "tableA rows that have tableB rows with every value supplied", but that leads to the question in this case of which "B row" should be used for the values in the SELECT clause. – Uueerdo Dec 24 '19 at 18:00

3 Answers3

0

Could you provide a minimal reproducible example for your query?
Generally speaking, one field cannot be equal to two different values in the same time. So, you have either mixed up the logical operators or you need two different fields.

I can assume that in your case there may be several related records with different values. In this case, you need to join the same table twice with different aliases. Let's say as SDG1 and SDG2. After that you can compare

... `SDG1`.id = :SDG_1 AND `SDG2`.id = :SDG_2

Update:

The win trick is groupping. You can enumerate all required SDG IDs and count how many of them is in group. Just for example in case of two IDs:

SELECT project.id
FROM project
JOIN project_SDG ON project_SDG.project_id = project.id
JOIN SDG ON SDG.id = project_SDG.SDG_id
WHERE SDG.id IN(1,2)
GROUP BY project.id
HAVING COUNT(*) = 2

See my sandbox here: https://www.db-fiddle.com/f/pixe3Zcs75Mq2PyCYPk913/0

If you need all project's fields, you have to put this into sub-query as

... WHERE id IN ( subquery here )

Subquery example: https://www.db-fiddle.com/f/pixe3Zcs75Mq2PyCYPk913/1

Community
  • 1
  • 1
artoodetoo
  • 918
  • 10
  • 55
  • I did update my question with more information. Joining tables multiple times is not really feasible. I can't create 17 separate tables for 17 SDGs. (Sorry for the confusion, there're more than 2, 17 in total) – May Dec 24 '19 at 18:41
  • @May thank you for update. I will update my answer accordingly. – artoodetoo Dec 24 '19 at 19:19
  • I see, thanks for your reply. Your Fiddle seems to work fine! But when I try to add everything after the "WHERE clause" I get an error message #1055. I do think your solution is better than the other one but I can't seem to figure out why it won't work with my own query yet. I have to do research on #1055 and nonagrgregated columns before I can implement yours into my own query. Thanks for your help! I really appreciate all the trouble you went through! :) – May Dec 24 '19 at 20:46
0

You want for each project.id both values :SDG_1 and :SDG_2 to exist for SDG.id, so use this in the WHERE clause:

WHERE 1=1 
    AND MATCH (project.name) AGAINST (:name_project) 
    AND MATCH (project.description) AGAINST (:description_project) 
    AND project.id IN (
        SELECT project_id
        FROM project_SDG
        WHERE SDG_id IN (:SDG_1, :SDG_2)
        GROUP BY project_id
        HAVING COUNT(DISTINCT SDG_id) = 2
    ) 
forpas
  • 160,666
  • 10
  • 38
  • 76
  • Yes, you are correct. "both values or more". It depends on what the user selects. In my example I used :SDG_1 and SDG_2 but it ranges from 1 to 17. I tried this query but it does not return anything. (I tried the exact same thing before posting) I replaced the PHP variables (:SDG_1 & :SDG_2) with (1, 2) these values exist within the column but I don't get a result back. – May Dec 24 '19 at 18:47
  • The subquery after IN returns all the project.ids for which there are both values :SDG_1 and :SDG_2. I tried to simplify it without any joins, so I used only the table project_SDG which contains the column project_id and the column SDG_id. – forpas Dec 24 '19 at 18:50
  • 1
    I apologize. You have my gratitude! It works! But you did give me a harder time making my query dynamic. I was hoping that there would be a more "elegant" solution. But if it works, then it works! Thanks again! I can figure the rest out from here. – May Dec 24 '19 at 19:02
0

I have already answered here, but I have another approch.

1. Find bunch of IDs assotiated with some project

To find project IDs we can test lonely pivot table without any join:

  SELECT project_id FROM project_SDG
  WHERE SDG_id IN(1,2,6)
  GROUP BY project_id HAVING COUNT(*) = 3

it gives us list of Project IDs

2. Access all project fields and add extra conditions

SELECT project.*
FROM project
JOIN (
  SELECT project_id FROM project_SDG
  WHERE SDG_id IN(1,2,6)
  GROUP BY project_id HAVING COUNT(*) = 3
) AS ids ON ids.project_id = project.id
WHERE
  MATCH(project.name) AGAINST ('project') AND
  MATCH(project.description) AGAINST ('sit')

you can play with it here: https://www.db-fiddle.com/f/pixe3Zcs75Mq2PyCYPk913/3

3. Prepare query on the PHP side

I will use known technique to prepare SQL statement.

$ids = [1, 2, 6]; // it can come from request parameters
$text1 = 'project';
$text2 = 'sit';
// build ?,?,?,... pattern
$qmarks = implode(',', array_fill(0, count($ids), '?'));
// Use SQL query above
$sth = $dbh->prepare("
SELECT project.*
FROM project
JOIN (
  SELECT project_id FROM project_SDG
  WHERE SDG_id IN({$qmarks})
  GROUP BY project_id HAVING COUNT(*) = ?
) AS ids ON ids.project_id = project.id
WHERE
  MATCH(project.name) AGAINST (?) AND
  MATCH(project.description) AGAINST (?)
");
$sth->execute(array_merge($ids, [count($ids), $text1, $text2]));
$records = $sth->fetchAll();
Community
  • 1
  • 1
artoodetoo
  • 918
  • 10
  • 55