Need help about joining tables - php

I have a MySQL database and I'm trying to create a web interface to manage tickets, right now I'm trying to list the tickets like so:
[title][name of the person that created the ticket][priority][date created][peoples that are in charge of this ticket]
so I have a table named tickets with the title, the id of the person that created the ticket, the priority, the date.
I have another table named users where you can find the first and last name and some other informations with their ID (you can link the two tables with that ID)
I have another table named tickets_users where you can find the ID of the peoples that are in charge of the tickets
My problem is I don't know how to link all of this in one request, it would be simple if only one people was in charge of a ticket but there can be multiple persons, I tried some queries but I always get tickets titles etc in double when there is more that one people in charge of a ticket.
Thanks in advance
EDIT
Example of the tables:
tickets:
-id = 523 | title = help with internet explorer | priority = 3 | date = 2013-10-10 11:20:51
users:
-id = 25 | firstname = John | lastname = Light
-id = 35 | firstname = Dwight | lastname = Night
-id = 53 | firstname = Maria | lastname = Sun
tickets_users :
-ticketid = 523 | userid = 25 | type = 1
-ticketid = 523 | userid = 35 | type = 2
-ticketid = 523 | userid = 53 | type = 2
And I'd like to be able to do a request to display:
[help with internet explorer][John Light][3][2013-10-10 11:20:51][Maria Sun - Dwight Night]
In one line (per ticket) and for all the tickets in my DB

You can use the group_concat aggregate function to group the names of the linked persons into a single field in the result. Since I don't have your exact table structure, I've made up the names of the fields and tables.
select
t.title,
group_concat(
case when tu.type = 1 then
concat(u.firstname, ' ', u.lastname)
end) as creator,
t.priority,
t.date,
group_concat(
case when tu.type = 2 then
concat(u.firstname, ' ', u.lastname)
end SEPARATOR ' - ') as users
from
tickets t
inner join tickets_users tu on tu.ticketid = t.id
inner join users u on u.id = tu.userid
group by
t.id;
If there is indeed only one creator for a ticket (which makes sense), then I would give ticket a creatoruserid to refer to John. In that case, John doesn't need to be in the junction table, and you actually don't need the type column any more.

I worked on problem and get the expected result.
select t.title,
group_concat(
case when tu.type = 1 then
concat(u.firstname, ' ', u.lastname)
end) as creator,
t.priority,
t.date,
group_concat(
case when tu.type = 2 then
concat(u.firstname, ' ', u.lastname)
end SEPARATOR ' - ') as users
from tickets t
inner join tickets_users tu on t.id=tu.ticketid
inner join users u on u.id=tu.userid
where t.id=523;

Related

mysql group and order rows

I have following table 'persons' with same persons in different rows
id | firstname | surname | date_created
------------------------------------------------------
3 | Nelli | Schaller | 2017-08-22 20:57:19
------------------------------------------------------
4 | Carl | Schaller | 2019-06-21 08:29:45
------------------------------------------------------
48 | Nelli | Schaller | 2020-06-25 13:06:09
------------------------------------------------------
49 | Carl | Schaller | 2020-06-25 13:06:09
What I want to get are all unique Schallers with the biggest id / newest date_created value.
I tried this
SELECT id, CONCAT(surname, ", ", firstname) AS person, date_created
FROM persons
WHERE
surname LIKE "schall%"
GROUP by firstname, surname
ORDER BY date_createdDESC, surname ASC LIMIT 0, 10
but get only as expected the first two entries (id 3 and 4) but I need 48 and 49.
As mentioned in some comment in this case the LIKE statement isn't necessary but in real live it will be the source for an autocomplete field so I need the LIKE
Any idea how to manage that?
Use NOT EXISTS:
SELECT p.id, CONCAT(p.surname, ', ', p.firstname) AS person, p.date_created
FROM persons p
WHERE p.surname LIKE '%schall%'
AND NOT EXISTS (SELECT 1 FROM persons WHERE firstname = p.firstname AND surname = p.surname AND id > p.id)
ORDER BY p.date_created DESC, person
If the condition to pick the latest of each group is the column date_created then change:
...AND id > p.id
with
...AND date_created > p.date_created
You could use subquery with group for max id
select t.max_id, t.person, m.date_created
from (
SELECT max(id) max_id, CONCAT(surname, ", ", firstname) AS person
FROM persons
WHERE surname LIKE "schall%"
ORDER BY date_createdDESC, surname ASC
GROUP BY CONCAT(surname, ", ", firstname)
) t
inner join persons m ON CONCAT(m.surname, ", ", m.firstname) = t.person
and m-id = t.max_id
SELECT p.*
FROM persons p
LEFT JOIN persons p2 ON p2.firstname = p.firstname
AND p2.lastname = p.lastname
AND p2.date_created > p.date_created
WHERE p2.id IS NULL
This is SQL Server syntax but MySQL is probably similar.
I'm assuming your id field doesn't need to be checked as well as the date_created since it's an identity column and would be larger anyway for the latter created records, but obviously adjust to your actual data.

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

Count and concatenate MySQL entries

Essentially, I have a table that is like this:
FirstName, LastName, Type
Mark, Jones, A
Jim, Smith, B
Joseph, Miller, A
Jim, Smith, A
Jim, Smith, C
Mark, Jones, C
What I need to do is be able to display these out in PHP/HTML, like:
Name | Total Count Per Name | All Type(s) Per Name
which would look like...
Mark Jones | 2 | A, C
Jim Smith | 3 | B, A, C
Joseph Miller | 1 | A
Jim Smith | 3 | B, A, C
Jim Smith | 3 | B, A, C
Mark Jones | 2 | A, C
I have spent time trying to create a new table based off the initial one, adding these fields, as well as looking at group_concat, array_count_values, COUNT, and DISTINCT, along with other loop/array options, and cannot figure this out.
I've found a number of answers that count and concatenate, but the problem here is I need to display each row with the total count/concatenation on each, instead of shortening it.
How about doing it like this?
SELECT aggregated.* FROM table_name t
LEFT JOIN (
SELECT
CONCAT(FirstName, ' ', LastName) AS Name,
COUNT(Type) AS `Total Count Per Name`,
GROUP_CONCAT(Type SEPARATOR ',') AS `All Type(s) Per Name`
FROM table_name
GROUP BY Name) AS aggregated
ON CONCAT(t.FirstName, ' ', t.LastName) = aggregated.Name
Without an ORDER BY clause, the order the rows will be returned in is indeterminate. Nothing wrong with that, by my personal preference is to have the result to be repeatable.
We can use an "inline view" (MySQL calls it a derived table) to get the count and the concatenation of the Type values for (FirstName,LastName).
And then perform a join operation to match the rows from the inline view to each row in the detail table.
SELECT CONCAT(d.FirstName,' ',d.LastName) AS name
, c.total_coount_per_name
, c.all_types_per_name
FROM mytable d
JOIN ( SELECT b.FirstName
, b.LastName
, GROUP_CONCAT(DISTINCT b.Type ORDER BY b.Type) AS all_types_per_name
, COUNT(*) AS total_count_per_name
FROM mytable b
GROUP
BY b.FirstName
, b.LastName
) c
ON c.FirstName = d.FirstName
AND c.Last_name = d.LastName
ORDER BY d.FirstName, d.LastName
If you have an id column or some other "sequence" column, you can use that to specify the order the rows are to be returned; same thing in the GROUP_CONCAT function. You can omit the DISTINCT keyword from the GROUP_CONCAT if you want repeated values... 'B,A,B,B,C',

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.

Left Join duplicates

I have several tables I need to query in order to get all the rows for a certain user.
The tables basically look like this
contact
=======
id_contact PK
firstName
lastName
...
contact_phone
===============
id_contact_phone, PK
id_contact, FK
id_phone_type, FK
phone
...
phone_type
============
id_phone_type PK
phone_type
....
And there's a bunch of tables similar to those except they are for email, phone etc. I need to display all that information for a given contact, I'm using several LEFT JOIN but I'm unsure on how to output the information.
This is my query
SELECT contact.id_contact, contact.lastName, contact.firstName, contact_email.email, email_type.email_type, contact_phone.phone, phone_type.phone_type, contact_company.contact_title, company.company_name
FROM contact
LEFT JOIN contact_email
ON contact.id_contact = contact_email.id_contact
LEFT JOIN email_type
ON contact_email.id_email_type = email_type.id_email_type
LEFT JOIN contact_phone
ON contact.id_contact = contact_phone.id_contact
LEFT JOIN phone_type
ON contact_phone.id_phone_type = phone_type.id_phone_type
LEFT JOIN contact_company
ON contact.id_contact = contact_company.id_contact
LEFT JOIN company
ON contact_company.id_company = company.id_company
WHERE contact.id_contact = $cid
My problem is that if a certain contact has several phone numbers, emails etc. the query will obviously return more than 1 row so I'm not exactly sure how to display the information since most of the columns will be duplicates of each others. Here's an example of what that query might return
+===========================================================================================+
| id_contact | lastName | firstName | email | email_type | phone | phone_type |
+===========================================================================================+
| 1 | Doe | John | john.doe#123.com | Work | 555-1234 | Work |
+------------+----------+-----------+------------------+------------+----------+------------+
| 1 | Doe | John | john.doe#123.com | Work | 555-2222 | Mobile |
+-------------------------------------------------------------------+----------+------------+
| 1 | Doe | John | jdoe#email.com | Personal | 555-1234 | Work |
+------------+----------+-----------+------------------+------------+----------+------------+
| 1 | Doe | John | jdoe#email.com | Personal | 555-2222 | Mobile |
+-------------------------------------------------------------------+----------+------------+
How can I display the information in php without having redundant data and can my query be optimized?
mysql has a wonderful group_concat function. Combined with GROUP BYid_contact`, this will do what you want. For your example:
SELECT contact.id_contact, contact.lastName, contact.firstName,
GROUP_CONCAT(CONCAT(contact_email.email, ' : ', email_type.email_type) SEPARATOR ', ') AS email,
GROUP_CONCAT(CONCAT(contact_phone.phone, ' : ', phone_type.phone_type) SEPARATOR ', ') AS phone,
contact_company.contact_title, company.company_name
FROM contact
LEFT JOIN contact_email
ON contact.id_contact = contact_email.id_contact
LEFT JOIN email_type
ON contact_email.id_email_type = email_type.id_email_type
LEFT JOIN contact_phone
ON contact.id_contact = contact_phone.id_contact
LEFT JOIN phone_type
ON contact_phone.id_phone_type = phone_type.id_phone_type
LEFT JOIN contact_company
ON contact.id_contact = contact_company.id_contact
LEFT JOIN company
ON contact_company.id_company = company.id_company
WHERE contact.id_contact = $cid
Note that I've never used GROUP_CONCAT around a normal CONCAT, but I see no reason why it wouldn't work.
If your email and phone types are consistent you might could flatten it to one row by returning the different types as projected columns, for example for the email types:
SELECT contact.id_contact, contact.lastName, contact.firstName,
contact_company.contact_title, company.company_name, work_email.email AS work_email,
personal_email.email as personal_email, mobile_phone.phone as mobile_phone,
work_phone.phone as work_phone
FROM contact
LEFT JOIN contact_email AS work_email
ON (contact.id_contact = work_email.id_contact AND
work_email.id_email_type = email_type.id_email_type AND
email_type.email_type = 'Work')
LEFT JOIN contact_email AS personal_email
ON (contact.id_contact = personal_email.id_contact AND
personal_email.id_email_type = email_type.id_email_type AND
email_type.email_type = 'Personal')
LEFT JOIN contact_phone AS mobile_phone
ON (contact.id_contact = mobile_phone.id_contact AND
mobile_phone.id_phone_type = phone_type.id_phone_type AND
phone_type.phone_type = 'Mobile')
LEFT JOIN contact_phone AS work_phone
ON (contact.id_contact = work_phone.id_contact AND
work_phone.id_phone_type = phone_type.id_phone_type AND
phone_type.phone_type = 'Work')
WHERE contact.id_contact = $cid
If you have an inconsistent/unknown number of emails or phone numbers, etc, then you might be better off retrieving the data via multiple select statements. It really all depends on how you want to use the data on the web server side. Odds are that you don't actually need it in this table format though.
If you're worried about the performance of running multiple queries, remember that you can sometimes put multiple statements in a single query and have multiple result sets returned. I don't know if this is something that you can do with PHP but I would assume it is.
Since the types are unknown you can use some grouping on the PHP side by expanding on another answer in a similar question on PHP: Grouping Records from While Loop
First be sure that you're ordering on the id_contact and types in the query:
...
WHERE contact.id_contact = $cid
ORDER BY contact.id_contact, email_type.email_type, phone_type.phone_type
Then do a manual grouping on the row displays while looping through the result rows:
$contact_id = 0;
while($row = mysql_fetch_array($result))
{
if( $row['contact_id'] != $contact_id)
{
echo '<hr><br><h3>Contact: ' . $row['first_name'] . $row['last_name'] . '</h3>';
$contact_id= $row['contact_id'];
$phone_type = null;
$email_type = null;
}
if ($row['email_type] != $email_type)
{
echo $row['email_type'] . ' Email: ' . $row['email'];
$email_type = $row['email_type']
}
if ($row['phone_type] != $phone_type)
{
echo $row['phone_type'] . ' Phone: ' . $row['phone'];
$phone_type = $row['phone_type']
}
}

Categories