sql join repeating results - php

I need to make a list printing all the clients ordered by the gym name, but it's repeating the gym name the same number of gym's clients. If gym1 have 4 clients, the echo is printed 4 times.
The tables/columns are:
members (id, gym, name, etc...)
and
gym (gymID, gym_name, etc...).
member.gym is to know to what gym the client belongs (gym.gymID)
if ($stmt = $mysqli->prepare(" SELECT DISTINCT g.*, m.*
FROM gym g
INNER JOIN members m ON m.gym = g.gymID")) {
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_array()) {
echo 'Sport center: ' . $row['gym_name'] . '<br>';
// here print the gym's clients list
}
}
DISTINCT is not working... What is the problem??

That's the normal behavior.
Example.
Consider the following tables
Table "gym"
-----------
gym_id | gym_name
-------+----------
1 | Gym A
2 | Gym B
Table "members"
---------------
member_id | gym_id | member_name
----------+--------+------------
1 | 1 | Bob
2 | 1 | Jeff
And now, execute this query:
select g.gym_id, g.gim_name, m.member_id, m.member_name
from gym as g
inner join members as m on g.gym_id = m.gym_id;
Result:
gym_id | gym_name | member_id | member_name
-------+----------+-----------+-------------
1 | Gym A | 1 | Bob
1 | Gym B | 2 | Jeff
That happens because each row in the gym table is matched with a row in the members name. Even if you use select distinct, the result would be the same, because every row is different.
I think what you want is an output like this:
Gym A
Bob
Jeff
Although that can be done directly in SQL, it's easier to handle it directly with PHP, because doing it in SQL would be a real pain in the neck would require writing quite a complex query. I'm not quite good with PHP, but it could be something like this:
/*
You don't need "DISTINCT", but you need "ORDER BY" to make this work
*/
if ($stmt = $mysqli->prepare(" SELECT g.*, m.*
FROM gym g
INNER JOIN members m ON m.gym = g.gymID
ORDER BY g.gymID")) {
$stmt->execute();
$result = $stmt->get_result();
$gym = "";
while ($row = $result->fetch_array()) {
if($row['gym_name'] != $gym)
echo 'Sport center: ' . $row['gym_name'] . '<br>';
echo ' Member: ' . $row['member_name'] . '<br>';
$gym = $row['gym_name'];
}
}

First, drop the DISTINCT and slap in an ORDER BY:
SELECT g.*, m.*
FROM gym g
INNER JOIN members m ON m.gym = g.gymID
ORDER BY g.name;
Now, adjust your PHP code to only print the gym name if it's different from the last gym you printed.

SELECT g.*, m.*
FROM gym g
INNER JOIN members m ON m.gym = g.gymID
ORDER BY g.name,gym_clients;
I dont know the name of Gym Name column & gym Client Column So, Please Change the name if they are not correctly spelled.
I hope this will work for you

This should work:
SELECT *
FROM gym g
LEFT JOIN members m
ON g.gym_ID=m.gym_id
GROUP BY g.gym_name

Related

How to join and sum a column in two tables based on another column using mysqli and php (just using query)

I have 2 tables about blood bank:
donates
orders
in donates table I have 2 fields showing how many donations we have:
------------------------
| blood_group | amount |
------------------------
| A+ | 2 |
| B- | 3 |
| O+ | 4 |
| A+ | 3 |
| O+ | 1 |
in orders table I have 2 column that how many requests we submit based on blood group:
------------------------
| blood_group | amount |
------------------------
| A+ | 4 |
| B- | 3 |
| O+ | 4 |
| AB- | 6 |
My problem is I want to use mysqli query to get an array that show me this result based on these conditions:
show how many we need group by blood_group
if we don't need any blood_group or we don't have any request for that blood type show zero (not showing null)
not showing negative number for our blood shortage
I manage to do this so far:
<?php
$con = mysqli_connect("localhost", "root", "", "test");
// Check connection
if (mysqli_connect_errno()) {
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$sql ="SELECT donates.blood_group as blood_group,
donates.amount as donates_amount,
orders.amount as orders_amount,
FROM `donates`
LEFT JOIN `orders`
ON donates.blood_group = orders.blood_group
GROUP BY donates.blood_group";
// Perform queries
$result = mysqli_query($con, $sql);
if (!$result = mysqli_query($con, $sql)) {
echo "SQLSTATE error: " . mysqli_sqlstate($con);
echo "<br>";
echo "SQLSTATE error: " . mysqli_error($con);
exit;
}
$result = mysqli_fetch_all($result, MYSQLI_ASSOC);
var_dump($result);
mysqli_close($con);
That query shows me sum of blood_groups but here is the main question:
So here are the main questions:
how to subtract (donates_amount and orders_amount)
how to make them positive (subtract which one first)
how to show the result even if one blood group is not presented on the other (full join)
Use union all and group by:
select blood_group, sum(donate_amount) as donate_amount,
sum(order_amount) as order_amount
from ((select blood_group, amount as donate_amount, 0 as order_amount
from donates
) union all
(select blood_group, 0 as donate_amount, amount as order_amount
from orders
)
) od
group by blood_group;
The only caveat is that a blood group needs to be in one of the tables. If you have a separate table of all of them, you should use that. For instance:
select bg.*,
coalesce(donate_amount, 0) as donate_amount,
coalesce(order_amount, 0) as order_amount
from blood_groups bg left join
(select blood_group, sum(amount) as donate_amount
from donates
group by blood_group
) d
on d.blood_group = bg.blood_group left join
(select blood_group, sum(amount) as order_amount
from donates
group by blood_group
) o
on o.blood_group = bg.blood_group ;
In either of these queries, you can get the difference using - and show negative numbers as 0 using greatest(). For instance:
greatest(sum(donate_amount) - sum(order_amount), 0)
To answer your first question :
how to subtract (donates_amount and orders_amount)
You must use SUM() function with a minus sign:
SUM(donates.amount - orders.amount);
this will subtract the total sum of two tables
But we have some problem here: you may have null values (because you may not have some of the blood groups present in one of tables) that give the wrong result. you must change the null values to zero with COALESCE() function:
SUM(COALESCE(donates.amount,0) - COALESCE(orders.amount,0))
We must extra check if the result does not equal to null:
COALESCE(SUM(COALESCE(donates.amount,0) - COALESCE(orders.amount,0)),0)
how to make them positive (subtract which one first)
And at last if you want to avoid negative numbers you must use mysqli math functions named ABS() that give you absulute value:
ABS(COALESCE(SUM(COALESCE(donates.amount,0) - COALESCE(orders.amount,0)),0))
so your query will look like this:
$sql = "SELECT donates.blood_group as blood_group,
COALESCE(donates.amount,0) as donates_amount,
COALESCE(orders.amount,0) as orders_amount,
ABS(COALESCE(SUM(COALESCE(donates.amount,0) - COALESCE(orders.amount,0)),0)) as needed_amount
FROM `donates`
LEFT JOIN `orders`
ON donates.blood_group = orders.blood_group
GROUP BY donates.blood_group";
how to show the result even if one blood group is not presented on
the other (full join)
In order to make full join you must use union with the invers form of your query. so that you find other records in orders table and unite the results into one results:
$sql = "SELECT donates.blood_group as blood_group,
COALESCE(donates.amount,0) as donates_amount,
COALESCE(orders.amount,0) as orders_amount,
ABS(COALESCE(SUM(COALESCE(donates.amount,0) - COALESCE(orders.amount,0)),0)) as needed_amount
FROM `donates`
LEFT JOIN `orders`
ON donates.blood_group = orders.blood_group
GROUP BY donates.blood_group
UNION
SELECT orders.blood_group as blood_group,
COALESCE(donates.amount,0) as donates_amount,
COALESCE(orders.amount,0) as orders_amount,
ABS(COALESCE(SUM(COALESCE(orders.amount,0) - COALESCE(donates.amount,0)),0)) as needed_amount
FROM `orders`
LEFT JOIN `donates`
ON orders.blood_group = donates.blood_group
GROUP BY orders.blood_group";

SQL joining other table with condition

I have this query:
SELECT *
FROM `classes`
JOIN `classes_students`
ON `classes`.`id` = `classes_students`.`class`
And I need to add condition for selecting just classes, in which are not currently logged student (user ID is not in classes_students connected with class id) and also count how many students are in that class.
Table structure:
classes: id, name, etc
classes_students: class_id, user_id, etc
Table data:
classes:
1 | test
2 | test2
3 | test3
classes_students:
1 | 1
1 | 2
2 | 3
3 | 4
3 | 5
Expected output if im user with id 1:
classes names (with number of students in):
2 (1 student)
3 (2 students)
All this in one query. It is possible? If yes, how?
Select classid, count(*)
from class
left join student on student.classid = class.classid
group by classid
Glad help for you
Try this query:
$user_id = 1; // current user_id
$query = "SELECT `classes`.`id`, `classes`.`name`, COUNT(*) as students FROM `classes`
JOIN `classes_students`
ON `classes`.`id` = `classes_students`.`class_id`
AND `classes_students`.`user_id` != $user_id
GROUP BY `classes_students`.`class_id`
";
I figured it out! :)
Thank you guys for ur help.
$user_id = 1; // current user_id
$query = "SELECT `classes`.`id`, `classes`.`name`, COUNT(*) as students FROM `classes`
LEFT JOIN `classes_students`
ON `classes`.`id` = `classes_students`.`class_id`
WHERE `classes`.`id` NOT IN (SELECT `class_id` FROM `classes_students` WHERE `user_id`='.$user_id.')
GROUP BY `classes_students`.`class_id`
";

MySQL selecting row for attendance case

I have these tables:
table 1 : attendance
-------------------------------
ID | DATE | EMPLOYEE_ID |
-------------------------------
1 2013-09-10 1
2 2013-09-10 2
3 2013-09-10 3
-------------------------------
table 2: employee
---------------
ID | NAME |
---------------
1 Smith
2 John
3 Mark
4 Kyle
5 Susan
6 Jim
---------------
My actual code to show employee option.
while($row=mysql_fetch_array($query)){
echo "<option value='$row[employee_id]'>$row[first_name] $row[last_name]</option>";
}
?>
How can i show the list of employee that not registered in table 1?
The condition is if an employee already registered in table 1, they won't appear on the option.
I want to show the list in <option>'s of <select> element. So it will return: kyle, susan, jim.
Please tell me the correct query or if there is any better option, it'll be good too. Please give some solution and explain. Thank you very much
UPDATE / EDIT:
it also based on current date, if in table 1 have no latest date e.g. today it's 2013-09-15. It will show all of employee.
You can do this with a left join and then checking for no matches:
select e.*
from employee e left outer join
attendance a
on e.id = a.employee_id
where a.employee_id is null;
This is probably the most efficient option in MySQL.
EDIT:
To include a particular date, add the condition to the on clause:
select e.*
from employee e left outer join
attendance a
on e.id = a.employee_id and a.date = date('2013-09-20')
where a.employee_id is null;
If I understood correctly this should work, get all employees whose ID is not in the attendance table.
SELECT * FROM employee WHERE employee.ID NOT IN
(SELECT EMPLOYEE_ID FROM attendance)

Need help about joining tables

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;

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