How to handle LEFT OUTER JOIN results efficiently with PHP / PDO - php

Long time answer finder, first time asker here. I tried looking for an answer to this, but either it doesn't exists (doubtful) or I just can't seem to come up with the proper terminology to search with (most likely).
I'm using PHP / PDO to get data from a MariaDB database. The database has tables with a many-to-many relationship and a junction table between them like this:
people attendees events
+----+------+ +------+------+ +----+-------------------------+
| id | name | | p_id | e_id | | id | name |
+----+------+ +------+------+ +----+-------------------------+
| 1 | Bob | | 1 | 1 | | 1 | Company Christmas party |
| 2 | Anne | | 2 | 1 | | 2 | Christmas party cleanup |
| 3 | John | | 3 | 1 | | 3 | John's birthday party |
+----+------+ | 3 | 3 | +----+-------------------------+
+------+------+
I'm using a LEFT OUTER JOIN to select the events and their attendees:
SELECT events.id, events.name, people.id, people.name
FROM events
LEFT OUTER JOIN attendees
ON events.id = attendees.e_id
LEFT OUTER JOIN people
ON attendees.p_id = people.id
Now the "problem" is that the query returns multiples of the same event for each attendee as follows:
+----+-------------------------+------+------+
| id | name | id | name |
+----+-------------------------+------+------+
| 1 | Company Christmas party | 1 | Bob |
| 1 | Company Christmas party | 2 | Anne |
| 1 | Company Christmas party | 3 | John |
| 2 | Christmas party cleanup | NULL | NULL |
| 3 | John's birthday party | 3 | John |
+----+-------------------------+------+------+
Now I'm somewhat stuck with looping though the results. I want to create event objects from the results, but I want one event only as one object, adding attendees to it.
I could loop through my array of existing event objects and check if the id already exists when going through the resultset, but this seems a little inefficient. Is there a more elegant solution, possibly built into PDO?

I would do something like this:
Slightly modify your query (now we have different names for event name and person name):
SELECT events.id, events.name as event, people.id, people.name as person
FROM events
LEFT OUTER JOIN attendees
ON events.id = attendees.e_id
LEFT OUTER JOIN people
ON attendees.p_id = people.id
And parse results into array:
//$queryResult is the result returned by your query
while($row = $queryResult->fetch(PDO::FETCH_ASSOC)) {
$results[$row['event']][] = $row['person'];
}
In the end, you have an associative array that stores events and people.
print_r($results['Company Christmas party']) will return you
Array
(
[0] => Array
(
[0] => Bob
[1] => Anne
[2] => John
)
)
Regarding efficiency, I don't see anything inefficient here, linear complexity.

Related

MySQL select row from one table with multiple rows in a second table

I have one table containing "Client" information, and another including "Payment" information for each client.
Simplified client table
client_id | client_name | client_status
----------+----------------+--------------
1 | John St. Peter | In Arrears
2 | Peter St. John | Up-to-date
Simplified payments table
payment_id | client_id | payment_date | payment_amount
-----------+-----------+--------------+---------------
1 | 1 | 2017-12-12 | 123.45
2 | 2 | 2017-12-15 | 234.56
3 | 1 | 2017-12-17 | 23.45
4 | 1 | 2017-12-21 | 54.32
5 | 2 | 2017-12-23 | 34.56
With the above two tables, I want to produce a single table with a single query with all the pertinent information to generate a search grid where I can filter by any column in either of the two tables, namely, filter by "client_status" from "client" table, or "client_id" from "payments" table.
payment_id | client_id | client_name | client_status | payment_date | payment_amount
-----------+-----------+----------------+---------------+--------------+---------------
1 | 1 | John St. Peter | In Arrears | 2017-12-12 | 123.45
2 | 2 | Peter St. John | Up-to-date | 2017-12-15 | 234.56
3 | 1 | John St. Peter | In Arrears | 2017-12-17 | 23.45
4 | 1 | John St. Peter | In Arrears | 2017-12-21 | 54.32
5 | 2 | Peter St. John | Up-to-date | 2017-12-23 | 34.56
So - in essence, I want to "duplicate records" in the clients table for as many times as necessary for corresponding records in the payments table to facilitate the search. I am using a premade grid tool (DataTables) which is used all over the system with a custom function made by the developer to generate the grid (so I don't want to mess with that function in fear of breaking the rest of the system), so a single query to get that data sounds like the more pragmatic approach. I also have an "Edit" feature for each row, which will edit the "client" table, and put a tabbed structure in the edit screen for the "payments" table, which currently works fine with two queries (one for the client, one getting all the payments for that client).
I have tried UNION as well as various JOIN statements (probably incorrect...), but either get syntax errors or a single result per "client" row, meaning it does not pick up in the filters.
Apologies if this is a duplicate question - I have searched, but could not find an answer that answers this scenario for me.
here is a join...
select *
from clientsTable c,
paymentsTable p
where c.client_id = p.client_id
order by p.payment_id;
that should give you everything.
edit: for empties... payments with no clients...
select *
from clientsTable c,
paymentsTable p
where c.client_id = p.client_id
or p.client_id = null
order by p.payment_id;

Get all the data from a table and sometimes relate that table with another

My question is: Can I do this?
I try many things, some of them are:
Search 1
Search 2
Search 3
Search 4
I need all the info form Table A, and sometimes I need to join this table with Table B. My problem is that when I join both tables, if an specific parameter in Table A is not in Table B just give me the records when the specific parameter are, but I want all the records.
Table A
|--------------------------------|
| Table A |
|-------------------|------------|
| id_table_A | name | id_table_B |
|------------|------|------------|
| 1 | Joe | 1 |
|------------|------|------------|
| 2 | Ben | |
|------------|------|------------|
| 3 | Lya | |
|------------|------|------------|
| 4 | luis | 2 |
|------------|------|------------|
Table B
|----------------------|
| Table B |
|----------------------|
| id_table_B | Elements|
|------------|---------|
| 1 | Car |
|------------|---------|
| 2 | Byke |
|------------|---------|
| 3 | Moto |
|------------|---------|
What I want to show in my View is this:
|------------|------|------------|
| id_table_A | name | Elements |
|------------|------|------------|
| 1 | Joe | Car |
|------------|------|------------|
| 2 | Ben | |
|------------|------|------------|
| 3 | Lya | |
|------------|------|------------|
| 4 | luis | Byke |
|------------|------|------------|
My model
In my model this is what I tried:
"SELECT * FROM table_A, table_B where table_A.id_table_B = table_B.id_table_B"
But this query only show me data 1 and 4.
This can be done or not?
Thanks in advance.
You can use left join
SELECT *
FROM table_A
left join table_B on table_A.id_table_B = table_B.id_table_B
Left join is used when the keys between tables may not always match. In this case, the left join retrieves the correct match where it's possible and the values become NULL when not possible.
SQL LEFT JOIN Documentation
You need an explicit LEFT JOIN as opposed to the implicit INNER JOIN that is used when you simply list the tables like that. https://www.w3schools.com/sql/sql_join_left.asp

Mysql join query multiple values

I have a table called facility.
Structure looks as follows:
id | name
---------
1 | Hotel
2 | Hospital
3 | medical shop
I have an other table which is taking data from the above table and keeping multiple values in one column. View looks like below:
id | facilities
---------------
1 | Hospital~~medical shop~~Hotel
2 | Hospital~~Hotel
3 | medical shop~~Hotel
If I want to join these two tables how does the query look like?
I tried this, but it didn't work:
select overview.facilities as facility
from overview join facility on facility.id=overview.facilities;
you can do this with a bit of hackery
select o.facilities as facility
from overview o
join facility f on find_in_set(f.facilities, replace(o.facilities, '~~', ','));
I would highly recommend you change the way you are storing data. currently it is considered un normalized and that quickly becomes a monster to deal with
you should change your table structure to look something more like this
+----------+--------------+
| facility |
+----------+--------------+
| id | name |
+----------+--------------+
| 1 | Hotel |
| 2 | Hospital |
| 3 | medical shop |
+----------+--------------+
+-----------+-------------+
| overview |
+-----------+-------------+
| id | facility_id |
+-----------+-------------+
| 1 | 2 |
| 2 | 3 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
| 6 | 3 |
| 7 | 1 |
+-----------+-------------+
Code Explanation:
basically you are wanting to find the matching facilities in the overview. one handy function MySQL has is FIND_IN_SET() that allows you to find an item in a comma separated string aka find_in_set(25, '11,23,25,26) would return true and that matching row would be returned... you are separating your facilities with the delimiter ~~ which wont work with find_in_set... so I used REPLACE() to change the ~~ to a comma and then used that in the JOIN condition. you can go from here in multiple ways.. for instance lets say you want the facility id's for the overview.. you just add in the select GROUP_CONCAT(f.id) and you have all of the id's... note if you do that you need to add a GROUP BY at the end of your query to tell it how you want the results grouped

PHP: Fetching data from *strange* MySQL database

I have a closed-source software written in C#/.NET from a VoIP company which is impossible to customize and wanted to create custom front-end using PHP. I gained access to the database and now see how it functions. I wanted to output the user his 'speed dial' numbers, but having issue solving it. Here are the tables structures:
'customer' table
+-------------------------------------------------------------------------+
| CustomerID | FirstName | LastName | Balance | Email | Password | Status |
|-------------------------------------------------------------------------|
| 1 | Homer | Simpson | 5.00 | h#s.s | iheartm | 1 |
| 2 | Marge | Simpson | 3.00 | m#s.s | ihearth | 1 |
+-------------------------------------------------------------------------+
'calls' table
+------------------------------------------------------------------------+
| CallID | Caller | Callee | ServiceID | Duration | Cost | CustomerID |
|------------------------------------------------------------------------|
| 1 | 1234567 | 7654321 | 30 | 60 | 1.00 | 1 |
| 2 | 7654321 | 1234567 | 45 | 120 | 2.00 | 2 |
+------------------------------------------------------------------------+
'ani' (speed-dial) table
+---------------------------------------+
| PhoneNumber | ServiceID | ContactName |
|---------------------------------------|
| 1234567 | 45 | Homer |
| 7654321 | 30 | Marge |
+---------------------------------------+
As you can see, 1234567 is Homer's phone number and in Marge's speed dial list and 7654321 is Marge's number in Homer's list. Just like I can pull up customer's balance when logged in using: $current_user['Balance'];, is there way to show user his 'speed dial' numbers in PHP?
This request doesn't achieve what you want ?
SELECT
a.CustomerID, a.FirstName, a.LastName, a.Balance, a.Email, a.Status,
b.ServiceID,
(SELECT GROUP_CONCAT(CONCAT(ContactName,':',PhoneNumber)) FROM ani GROUP BY PhoneNumber WHERE ServiceID = b.ServiceID)
FROM customer a
LEFT JOIN calls b ON a.CustomerID = b.CustomerID
WHERE a.CustomerID = 'replace_by_customer_id'
This should fetch the data of the customer table, plus a string that results from concatenating the speed-dial numbers of the customer connected.
I assume that a customerid corresponds to one unique serviceid found in calls, and the serviceid in the table ani indicates the owner of the speed-dial number. But it seems a weird architecture, so you should give us more data or informations about the tables...
There seems to be a missing relation from ServiceID to a specific entry in customer. It would seem strange that the calls table would provide that relation, it should merely use it.
With only the information you supplied you can only join calls to link the CustomerID with the ServiceID which I suppose you have. The query would look like this:
SELECT ContactName, PhoneNumber FROM ani
LEFT JOIN calls ON ani.ServiceID=calls.ServiceID
WHERE calls.CustomerID=xxx

query to imlement block list

I have two tables a and b as follows to implement a simple block list where users can block other users.....
Table A
+------------+--------------+------+
| Name | phone |userid|
+------------+--------------+------+
| Mr Sasi | 01225 708225 | 1 |
| Miss Brown | 01225 899360 | 2 |
| Mr Black | 01380 724040 | 3 |
+------------+--------------+------+
Table B
+------------+--------------+
| blockedbyid| blockedid |
+------------+--------------+
| 1 | 2 |
| 2 | 3 |
| 1 | 3 |
+------------+--------------+
"blockedbyid" is id of user who has blocked the user in "blockedid".
I need to join the two tables and fetch all records from table A such that the result has all users who are not blocked by a particular user [ie blockedbyid='XXX'].. Can you guys give the SQL query so that i can fetch the records as a recordset??? I dont want to fetch two different rowsets and compare it in php....
Something like this should work
Parameter :USERID
SELECT * FROM TABLEA WHERE userid NOT IN (SELECT blockedid FROM TABLEB WHERE blockedbyid = :USERID)
Using join
SELECT u.* FROM TABLEB b, TABLEA u WHERE b.blockedbyid = 'XXX' AND b.blockedid = NULL
It may work like that, give it a try.
Roadie57 solutions seems better though.

Categories