Mysql query with multiple ID columns and php - php

I have 2 joined tables, each one has a primary key column named id.
SELECT t1.*, t2.* from t1 join t2 on t1.fk_id=t2.id
When I run the query above, both id fields are selected (t1.id and t2.id). My question is, how can I select the correct ID while I am looping through the result set? If I select $result->id, I will get the t2.id. Is there any way that I can get the t1.id also without explicitly selecting it in the query (i.e. t1.id as t1_id?) Also, please, let us know about some of your practices when it comes to naming the primary key columns.
Thanks!

SELECT t1.id as id1, t2.id as id2, t1.*, t2.* from t1 join t2 on t1.fk_id=t2.id

You are probably using mysqli_result::fetch_assoc to return each row of your result set as an associative array. MySQL will let you have two columns with the same name in a query, but these do not map to an associative array the way you want them to—even though the associative array is doing exactly as it should.
Assume two tables, book and author, linked by the junction table book_author. In MySQL, you can run the following query, which returns two id columns:
SELECT b.*, a.*
FROM book AS b
JOIN book_author AS ba ON ba.book_id = b.id
JOIN author AS a ON a.id = ba.author_id
LIMIT 2;
+----+-----------------+----+--------------+
| id | title | id | name |
+----+-----------------+----+--------------+
| 1 | Design Patterns | 1 | Erich Gamma |
| 1 | Design Patterns | 2 | Richard Helm |
+----+-----------------+----+--------------+
If you try to map one of these rows to an associative array, you end up with a single id element in your array:
$row = $result->fetch_assoc();
print_r($row);
Array
(
[id] => 1
[title] => Design Patterns
[name] => Erich Gamma
)
The last id column in the row will overwrite any that precede it. Here’s the second row from the result set:
Array
(
[id] => 2
[title] => Design Patterns
[name] => Richard Helm
)
This is just the same as modifying the value of an element in an associative array;
$row = array();
$row['id'] = 1;
print_r($row);
Array
(
[id] => 1
)
$row['id'] = 2;
print_r($row);
Array
(
[id] => 2
)
If you give each column a unique name in your query, either by doing so in the table itself, or giving it an alias in the query, the problem is avoided:
SELECT b.id AS book_id, b.title,
a.id AS author_id, a.name
FROM book AS b
JOIN book_author AS ba ON ba.book_id = b.id
JOIN author AS a ON a.id = ba.author_id
LIMIT 2;
+---------+-----------------+-----------+--------------+
| book_id | title | author_id | name |
+---------+-----------------+-----------+--------------+
| 1 | Design Patterns | 1 | Erich Gamma |
| 1 | Design Patterns | 2 | Richard Helm |
+---------+-----------------+-----------+--------------+
$row = $result->fetch_assoc();
print_r($row);
Array
(
[book_id] => 1
[title] => Design Patterns
[author_id] => 1
[name] => Erich Gamma
)
Alternatively, you could (and almost certainly should) use prepared statements instead. Although this can get round the problem of duplicate column names, using unique column names in your queries still makes things much easier to read and debug:
$sql = 'SELECT b.*, a.* ' .
'FROM book AS b ' .
'JOIN book_author AS ba ' .
'ON ba.book_id = b.id ' .
'JOIN author AS a ' .
'ON a.id = ba.author_id';
$stmt = $mysqli->prepare($sql);
$stmt->execute();
$stmt->bind_result($book_id, $book_title, $author_id, $author_name);
while ($stmt->fetch()) {
printf("%s, %s, %s, %s\n",
$book_id,
$book_title,
$author_id,
$author_name);
}

You'll often see the primary key for table XXX named xxx_id. This keeps the name of the same "information identifier" the same everywhere: for example in another table YYY, you'll have YYY.xxx_id with a foreign key constraint to XXX.xxx_id. This makes joins easier (you don't have to specify the "on" constraint at all in many databases) and it solves the problem you're running into as well.
I'm not saying you should prefix every column name to create a faux-namespace, but in the case of "id" it is actually useful and descriptive. It is, after all, not just any kind of ID, it's a user ID, site ID, game ID, contact ID, what have you.

Related

How can I filter one column by two AND conditions?

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 |
+------------+--------+
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
)
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
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();

SELECT results from another table AS column array

I can't figure out how to get results from 2 tables, in 1 query result (can't simple JOIN)
I have these 2 tables in my MySQL database:
Table 1: sales
id
name
info
Table 2: users
sale_id
user_id
Now, every sale have different number of assigned users. Some sale have 2 users, some sale have 10 users.
In single row, I need to have columns from sale table, and all assigned users to it (connected with same Sale_id)
I need result, something like this:
enter image description here
Try this :
SELECT s.*,
(SELECT GROUP_CONCAT(u.user_id SEPARATOR ', ')
FROM users u
WHERE u.sale_id = s.id) AS users
FROM sales s
Some insight on your programming language would have been nice.
And yes, as suggested by wogsland and icoder, one typically use joins and loop through results to build en array. But the use of GROUP_CONCAT, as Yoleth pointed out, is what you need. I don’t know if it was the goal here, but it can reduce memory used in the result because there is no row repetition.
SELECT info FROM Sales AS s,
(
SELECT sale_id, GROUP_CONCAT(user_id) AS assigned_users
FROM Users
GROUP BY sale_id) AS u
WHERE s.id=u.sale_id;
In a single query, with a fancy JOIN:
SELECT s.info AS info, u.sale_id AS sale_id, GROUP_CONCAT(u.user_id) AS assigned_users
FROM Sales AS s LEFT JOIN Users AS u
ON s.id=u.sale_id
WHERE sale_id IS NOT NULL GROUP BY u.sale_id;
You can simply join two tables and get query result set like this:
saleID | saleName | userID | userName
1 | Oct Sale | 5 | Tim
1 | Oct Sale | 6 | Nik
2 | Nov Sale | 7 | Bill
Then you can walk each row and build associative array from that data:
$sales = array();
while( $row = mysqli_fetch_assoc($result)) {
if (!array_key_exists($row['saleID'], $sales)) {
$sales[$row['saleID']] = array(
'saleID' => $row['saleID'],
'saleName' => $row['saleName'],
'users' => array()
);
}
array_push($sales[$row['saleID']]['users'], array(
'userID' => $row['userID'],
'userName' => $row['userName']
));
}
Well, MySQL isn't going to return you a nice nested array like that. But you can create it by looping through the result. Assuming your MySQL connection is named $mysqli then try something like
$sales = array();
$result = $mysqli->query("SELECT sales.*, users.user_id FROM sales, users WHERE sales.id = users.sales_id");
while ($row = $result->fetch_assoc()) {
$sales[$row->id]['sales_id'] = $row->id;
$sales[$row->id]['name'] = $row->name;
$sales[$row->id]['info'] = $row->info;
$sales[$row->id]['assigned_users'][] = $row->user_id;
}

i nf data table structure from json data

I have a database (mysql) table like
id | party | rights | m_id
---+---------+------------+---------
9 |abc | 3,5,6 | ["12,","15,"6"]
20 |xyz | 5,2 | ["6,","2,"9","12"]
21 |xyz 1 | 5,2 | ["6,","9,"12"]
Now I want to make my table in this way
search result for rights 5 is ["12,","15,"6"] ["6,","2,"12"] ["6,","9,"12"]
12 | abc , xyz,xyz1 |
15 | abc|
6 | abc , xyz,xyz1 |
9 | xyz,xyz1 |
Let's start with what I believe you already have. This is an sscce. If you adjust the mysql credentials it should run on your system, creating only a temporary MySQL table. It uses PDO to access the MySQL server. Which API you actually use is not important (i.e. as long as the other API is mysqli, because the mysql_* functions are depreacted ;-))
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
PDO::ATTR_EMULATE_PREPARES=>false,
PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));
setup($pdo);
$statement = $pdo->prepare('
SELECT
*
FROM
soFoo
WHERE
FIND_IN_SET(:right, rights)
');
$statement->execute( array(':right'=>5) );
/* in one way or another you have a loop where you fetch the records
having '5' in the `rights` field
*/
foreach( $statement as $row ) {
echo $row['party'], ' ', $row['show_ids'], "\r\n";
}
function setup($pdo) {
$pdo->exec('
CREATE TEMPORARY TABLE soFoo
(`id` int, `party` varchar(9), `exc` varchar(13), `rights` varchar(5), `show_ids` varchar(27))
');
$pdo->exec( <<< eos
INSERT INTO soFoo
(`id`, `party`, `exc`, `rights`, `show_ids`)
VALUES
(9, 'Percept', 'Non-Exclusive', '3,5,6', '["12,","15,"6"]'),
(20, 'Tata Sky', 'Non-Exclusive', '5,4,', '["6,","9,"11"]'),
(21, 'Tata Sky', 'Exclusive', '5,4', '["6,","13","15,"2","4","9"]'),
(22, 'Simbiotic', 'Exclusive', '6,2', '["12,","15,"1","6","7","8"]')
eos
);
}
this prints
Percept ["12,","15,"6"]
Tata Sky ["6,","9,"11"]
Tata Sky ["6,","13","15,"2","4","9"]
and is (as I understand the question) as far as you've already got.
Now let's decode the JSON array and check whether it contains the element 9. If it does add inforamtion from the current row to an array called $parties
$parties = array();
/* in one way or another you have a loop where you fetch the records
having '5' in the `rights` field
*/
foreach( $statement as $row ) {
$ids = json_decode($row['show_ids'], true);
if ( in_array('9', $ids) ) {
$parties[$row['id']] = $row['party'];
}
}
var_export($parties);
prints
array (
20 => 'Tata Sky',
21 => 'Tata Sky',
)
But ... from a relational database point of view this is ....suboptimal.
The FIND_IN_SET clause hinders MySQL from using indices effectively; you're searching (compound) data within a single field. It's amazing what the database server implementations can do to improve performance; but it has limits.
And you're also transfering possibly unnecessary data from the MySQL server to the php instance (those records that have 5 in rights but not 9 in show_ids). If possible, this should be avoided. Networks/Network stacks are fast and can be optimized, RAM is cheap ...but again, there are limits.
So, I suggest you look into Database normalization on the one hand and/or document-oriented databases on the other hand.
I am going to give a example below so you can write in your way,
If you are having table below
id | name
1 | a,b,c
2 | b
and expected output like this
id | name
1 | a
1 | b
1 | c
2 | b
Then follow below solutions
If you can create a numbers table, that contains numbers from 1 to the maximum fields to split, you could use a solution like this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
numbers inner join tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
Please see fiddle here.
If you cannot create a table, then a solution can be this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
(select 1 n union all
select 2 union all select 3 union all
select 4 union all select 5) numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
an example fiddle is here.

Join 1 Row to Multiple Rows in PDO

Here is my scenario:
Database Name: Children
+-------------+---------+---------+
| child_id | name | user_id |
+-------------+---------+---------+
1 Beyonce 33
2 Cher 33
3 Madonna 33
4 Eminem 33
Database Name: Parents
+-------------+---------+---------+
| parent_id | child_id | parent_name |
+-------------+---------+---------+
1 1 Obama
2 1 Michelle
3 4 50cents
4 4 Gaga
Desired Output:
+-------------+---------+---------+
| child_id | name | parent Name |
+-------------+---------+---------+
1 Beyonce Obama (Row 1) Michelle (Row 2)
PHP SQL Query in PDO:
$sql = "SELECT Children.child_id, Children.name, Parents.parent_name
FROM Children
LEFT JOIN Parents
ON Children.child_id = Parents.child_id
WHERE Children.user_id = ?
";
$stmt = $db_PDO->prepare($sql);
if($stmt->execute(array($userId))) // $userId defined earlier
{
// Loop through the returned results
$i = 0;
foreach ($stmt as $row) {
$fetchArray[$i] = array (
'childId' => $row['child_id'],
'childName' => $row['name'],
'parentName' => $row['parent_name'],
// How do I save the multiple parents from other rows here ????
);
$i++;
}
}
How can I run a query that Joins 1 row to multiple rows in second table in PDO? I have read other topics here but I am unsure. Is it easier to add a second query that gets the linked parents for each child_id separately in a loop? I am worried that will be too much query. Can someone help me solve this?
Well, took me some fiddling to test it all out but here you go.
Unfortunately one cannot easely pivot tables in mysql but there are alternatives.
http://sqlfiddle.com/#!9/1228f/26
SELECT GROUP_CONCAT(
CONCAT_WS(':', Parents.parent_id,Parents.parent_name) ) FROM Parents where Parents.child_id=1
;
SELECT
Children.child_id,
Children.name,
GROUP_CONCAT(
CONCAT_WS(':', Parents.parent_id,Parents.parent_name) ) as parents
FROM
Children
LEFT JOIN Parents
ON Children.child_id = Parents.child_id
WHERE Children.user_id = 33
Group by Children.child_id
This query uses the group concat to concatenate all resulsts we want into a colon seperated string with the values we want, and comma's between the individual fields.
We could do some tricky magic to make them individual fields but that would break our php because we wouldnt know how much fields each query would return(adopted, orphan, no known parents, etc...)
In php you could feed them into an object
$parents = array();
$loop1 = explode(',',$row['parents']);
foreach($loop1 as $parentset) {
$parentdetail = explode(":",$parentset);// decide yourself how much detail you want in here... I jsut went with name and id.
$parent = new stdClass();
$parent->id = $parentdetail[0];
$parent->name = $parentdetail[1];
array_push($parents,$parent);
}
var_dump($parents);
Execute the below query . You will get the output as required, i just used the group by which will group the records as per the selected column
select a.child_id, name ,group_concat(parent_name) from children a, parents b where a.child_id =b.child_id group by a.child_id
HI this query works only if you are passing child id ,
select a.child_id, name ,group_concat(parent_name ) parent_name from children a, parents b where a.child_id =b.child_id and a.child_id=1
here i am using a function called group_concat which is used for concatinating the rows.It automatically takes the rows whose count is greater than 1.So no need of the extra code again

How can I retrieve a comma-separate list of linked ids in a 3-way MYSQL join?

I'm creating a book tagging system (i'm sure this has been done tons of times before), and I'd like to create a view that gives me each book, with its tags' names.
I have three tables:
books ( id, name)
tags (id, name)
bookTags (id, book, tag)
And I'd like to have one view
booksInfo (books.id, books.name, [comma-separated-tags.names], [tagids])
With my current view (in the sql fiddle below), I get duplicate rows, one for each tag-book pair.
I'd love to get something like this:
BOOKID NAME TAGS TAGIDS
------ ----------- -------------------- ---------
1 1984 Dystopian, Political 3, 4
2 White Fang Dogs, Nature 5, 9
3 Bible Religion, History 6, 10
4 1776 Political, History 4, 10
I created a sqlfiddle here: http://sqlfiddle.com/#!2/74b57/1/0
I could do this with PHP after my select, then go through it and create a separate array, but that seems unnecessary. I find with MySQL there's almost always a query-way to do something.
Use GROUP_CONCAT with GROUP BY
select
b.id 'id',
b.name 'name',
GROUP_CONCAT(t.name) 'tag',
GROUP_CONCAT(t.id) 'tagid'
from
bookTags bt
left join
books b ON b.id = bt.book
left join
tags t ON t.id = bt.tag
GROUP BY bt.book;
Result for your fiddle
+------+------------------+------------------+-------+
| id | name | tag | tagid |
+------+------------------+------------------+-------+
| 1 | 1984 | Government | 2 |
| 2 | Huckelberry Finn | Adventure | 1 |
| 3 | The bible | Religion | 3 |
| 4 | White Fang | Adventure,Nature | 1,4 |
+------+------------------+------------------+-------+
4 rows in set (0.00 sec)
Check
create view booksInfo as
select
b.id 'id',
b.name as 'name',
GROUP_CONCAT(t.name) as 'tag',
GROUP_CONCAT(t.id) 'tagid'
from bookTags bt
left join books b
on b.id = bt.book
left join tags t
on t.id = bt.tag
group by bt.book;
http://sqlfiddle.com/#!2/68cdd/1
You can use the group_concat function to transform a series of values on different rows to a coma delimited values:
SELECT b.id AS book_id, b.name AS book_name,
GROUP_CONCAT(t.name) AS tags,
GROUP_CONCAT(t.id) AS tag_ids
FROM bookTags bt
LEFT JOIN books b ON b.id = bt.book
LEFT JOIN tags t ON t.id = bt.tag
GROUP BY b.id, b.name
GROUP_CONCAT(exp) Function is Dedicated for the comma separated
http://www.w3resource.com/mysql/aggregate-functions-and-grouping/aggregate-functions-and-grouping-group_concat.php
SELECT b.id 'id',b.name 'name',
GROUP_CONCAT(t.name) 'tag',
GROUP_CONCAT(t.id) 'tagid'
FROM
bookTags bt
LEFT JOIN
books b ON (b.id = bt.book)
LEFT JOIN
tags t ON (t.id = bt.tag) GROUP BY bt.book;
You shouldn't manipulate your results in your query. It would be much nicer to return your list with duplicates and roll up on ID.
foreach ($rows as $key => $row) {
$out[$row['id']]['id'] = $row['id'];
$out[$row['id']]['name'] = $row['name'];
$out[$row['id']]['tags'] .= ', '.$row['tags'];
$out[$row['id']]['tagids'] = ', '.$row['name'];
}
This is only quick, but you could roll this up into a nice little function that didn't reference keys directly, didn't repeat commas etc etc etc. (i.e. this is really ugly code and I apologise).
But the moment you try and scale a large amount of data with groups, you'll have another problem to solve.

Categories