(Apologies if this a duplicate - I have tried searching, but I may not know the right word for what I'm trying to achieve - feel free to correct me!)
The Background
So I have a PHP based app (Codeigniter, but I'm using normal SQL language for this part), that has a MySQL database, with 2 tables - 'contact' and 'order'.
For simplicity, let's assume that:
'contact' has 3 cols : Id, FirstName, LastName
'order' has 4 cols : Id, ContactId, ItemBought, ItemValidDate
Example of a row in 'order' table: 22, 11, Adult Membership, 2012/13
Id is primary key of both tables, ContactId is foreign key for 'contact' and ItemBought and ItemValidDate are both simple varchar (we're storing 'seasons' rather than dates -I know, its not ideal but its what the client wants)
At some point, I know, I am going to have to extend this for 3 tables and use an OrderItem table, to allow an order to have multiple items, so I'd like to find a solution that can be built on. But at present, I don't even understand the basics so I've kept it to 2 tables
The Problem
I want to create a search page that allows the user to find subsets of records based on lots of different criteria.
See screenshot of search page
This form submits as an array of criteria like this:
[order_type_operator] => Array
(
[0] => equal
[1] => equalor
[2] => notequal
)
[order_type] => Array
(
[0] => Adult Membership
[1] => Adult Membership
[2] => Adult Membership
)
[order_expire] => Array
(
[0] => 2005/06
[1] => 2006/07
[2] => 2010/11
)
[submit] => Start Search
I then cycle through this array, testing to see if values have been submitted, and build up my SQL query.
So, I hope I've explained it properly, so that its clear a user may use this form to search for records that match lots of different conditions - in theory, unlimited numbers of conditions - to end up with a list of contacts that match this criteria.
What I have Tried
Example 1 - simple WHERE
"find contact records that have an order record for 'Adult Membership' in '2009/10'"
i.e. SELECT * FROM contact
JOIN order ON contact.Id = order.ContactId WHERE (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2009/10')
This works fine.
Example 2 - WHERE OR WHERE
"find contact records that have an order record for 'Adult Membership' in '2009/10'" OR have a an order record for 'Adult Membership' in '2010/10'
i.e. SELECT * FROM contact
JOIN order ON contact.Id = order.ContactId WHERE (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2009/10') OR (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2010/11')
This works fine as long as EVERY condition the user is asking for is an OR query. I assume that I can build this query up using brackets and OR for as big as I like? E.g. find Adult membership in 2005/06, OR 2006/07, OR 2007/08, OR 2008/09 etc etc will be just like the above SQL with lots more brackets joined by 'OR'?
Example 3 - WHERE AND WHERE - I'm stuck!
"find contact records that have an order record for 'Adult Membership' in '2009/10' OR 2010/11 AND have a an order record for 'Adult Membership' in '2012/13'
At the moment, I've been trying UNION, however if there are more queries to follow this (e.g Adult membership in 2008 OR 2009 AND 2010) this means doing more than one SELECT. (Perhaps this is the answer?)
e.g. `SELECT * FROM contact
JOIN order ON contact.Id = order.ContactId WHERE (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2009/10') OR (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2010/11')
UNION
SELECT * FROM contact
JOIN order ON contact.Id = order.ContactId WHERE (order.ItemBought = 'Adult Membership' AND order.ItemValidDate = '2012/13)`
Example 4 - But does NOT have a record.... Blows my mind
"find contact records that have an order record for 'Adult Membership' in '2009/10' AND have an order record for 'Adult Membership' in '2010/10' BUT DO NOT have an order of 'Sponsorship' in 2007/08
I wondered about running these queries, storing the results in a PHP array and then doing a IN (*array of ids already selected*), but this just seems like I'm not using SQL properly.
So clever people - what am I doing wrong?
Thank you so much in advance for you help.
PS. Not asking you write the code for me!
PPS. If you know of any good tutorials then I'll happily follow them!
PPPS. If this is a duplicate, then please accept my apologies!
As ZorleQ says it can rapidly get to be a mess
For your 3rd question a possible solution using joins of subselects would be as follows
SELECT contact.*, order.*
FROM contact
INNER JOIN order
ON contact.Id = order.ContactId
INNER JOIN (SELECT DISTINCT ContactId
FROM order
WHERE (ItemBought = 'Adult Membership' AND ItemValidDate = '2009/10')
OR (ItemBought = 'Adult Membership' AND ItemValidDate = '2010/11')) Sub1
ON contact.Id = Sub1.ContactId
INNER JOIN (SELECT DISTINCT ContactId
FROM order
WHERE (ItemBought = 'Adult Membership' AND ItemValidDate = '2012/13')) Sub2
ON contact.Id = Sub2.ContactId
You could probably do this without using the subselects and just a plain join as follows
SELECT contact.*, order.*
FROM contact
INNER JOIN order
ON contact.Id = order.ContactId
LEFT OUTER JOIN order Sub1
ON contact.Id = Sub1.ContactId AND Sub1.ItemBought = 'Adult Membership' AND Sub1.ItemValidDate = '2009/10'
LEFT OUTER JOIN order Sub2
ON contact.Id = Sub2.ContactId AND Sub2.ItemBought = 'Adult Membership' AND Sub2.ItemValidDate = '2010/11'
INNER JOIN order Sub3
ON contact.Id = Sub3.ContactId AND Sub3.ItemBought = 'Adult Membership' AND Sub3.ItemValidDate = '2012/13'
WHERE Sub1.ContactId IS NOT NULL OR Sub2 IS NOT NULL
Your 4th question can be done using a LEFT OUTER JOIN to find a record with Sponsorship bought for 2007/08, and only returning rows where a match isn't found (ie, check the ContactId on the LEFT OUTER JOINed table is NULL).
SELECT contact.*, order.*
FROM contact
INNER JOIN order
ON contact.Id = order.ContactId
INNER JOIN order Sub1
ON contact.Id = Sub1.ContactId AND Sub1.ItemBought = 'Adult Membership' AND Sub1.ItemValidDate = '2009/10'
INNER JOIN order Sub2
ON contact.Id = Sub2.ContactId AND Sub2.ItemBought = 'Adult Membership' AND Sub2.ItemValidDate = '2010/10'
LEFT OUTER JOIN order Sub3
ON contact.Id = Sub3.ContactId AND Sub3.ItemBought = 'Sponsorship' AND Sub3.ItemValidDate = '2007/08'
WHERE Sub3.ContactId IS NULL
I think you have to take a step back and try to visualize your question on paper first. Examples 1 and 2 are pretty easy, but let's look at example 3.
For conditions where all your criteria are 'AND' or 'OR' - things are very simple. Just do a long WHERE, just liek before.
However, when you start mixing them you have to answer yourself a serious question:
How do you split the conditions?
Lets say someone picked up those criteria:
and A
or B
and C
This gives you so many permutations of your query! eg:
(A or B) and C
A or (B and C)
(A and C) or B
If you add one more 'OR' to it, you will end it with tens of combinations more! Leaving you in a place where you have to guess what to do. Don't even want to think what would happen if there is a NOT involved...
This is not a direct answer to your question, but more of a pointer towards a possible solution. The last time we had to do something similar, we've ended up grouping the conditions together into blocks.
You could either add a condition within a block or add a new search block. Think of the blocks as brackets in the example above. Everything in a block is an 'AND' or 'NOT AND', and between blocks you can specify 'and' or 'or'. This way you know straight away how to structure your query. This worked like a charm in a standalone application. Might be a bit tricky to implement it nicely on a page, but you catch the idea.
My solution to all issues like this where multiple criteria may or may not be provided by the user is the following... (this example is for oracle, but should be able to be done in MySQL as well)...
You pass in all the filter variables, regardless of whether they are null or filled with a value. In this example, I'll say I have 3 values the user may or may not fill that act as filters on the SELECT.
SELECT
*
FROM
table
WHERE
NVL2(InputVariable1, InputVariable1, Column1) = Column1
OR NVL2(InputVariable2, InputVariable2, Column2) = Column2
OR NVL2(InputVariable3, InputVariable3, Column3) = Column3
NVL2 - This is an oracle function. If the first value is not null, it returns the second value, otherwise it returns the third value. If you aren't using oracle, and there is no equivalent function for NVL2, simply write the function yourself.
So, using the above example, the code ALWAYS passes all three InputVariables into the select statement, even if they are NULL. By using NVL2 or an equivalent function, the comparison is between the InputVariable and the Column ONLY if the InputVariable is not null; otherwise it is between the Column and the Column, which will of course always be true, thereby effectively ignoring that filter variable, which is what you want (i.e. a null filter value matches all rows - i.e. if user does not specify LastName, then include all LastNames).
This solution allows you to use many filter variables without having to do a lot of processing up front - just pass them all down into the SELECT every time, whether they are null or not.
If you have sets of filter variables (i.e. the user enables a set of input values via a checkbox or some similar mechanism), you can do the above inside of a CASE statement. Each case should check the enable value for a given set, and return the result of evaluating the entire set of filter variables (exactly like the above). You then compare the result of the entire CASE structure to 1, as in...
WHERE
CASE [ expression ]
WHEN enableSet1
THEN NVL2(InputVariable1, InputVariable1, Column1) = Column1
OR NVL2(InputVariable2, InputVariable2, Column2) = Column2
OR NVL2(InputVariable3, InputVariable3, Column3) = Column3
WHEN condition_2 THEN result_2
...
WHEN condition_n THEN result_n
END = 1
This works because the value of a CASE structure is the result of the THEN block which was evaluated.
This will allow you to do ALL or MOST of your desired filtering within the confines of a single SELECT statement - again, without having to do a lot of pre-processing to build the SELECT.
Related
I am running this query on my website in order to find a ToDo list based on specific criteria. But it runs too slow and it is probably possible to write it in another way.
SELECT * FROM lesson WHERE
id IN
(SELECT `lesson_id` FROM `localization_logging`
WHERE `language_id` = 2 AND `action_id` = 1)
AND `id` NOT IN
(SELECT `lesson_id` FROM `localization_logging`
WHERE `language_id` = 2 AND `part_id` = 1 AND `action_id` = 6)
What the query does is that it looks in the lesson table to find all lesson list names and then checks if a specific task is done. If the task is done in one todo than show it in the next. Action 1 is done but not action 6 in this case.
I hope I'm explaining this good enough. On my local machine the query takes 1.8 seconds, and sometimes I have to print multiple lists next to each others and then it takes 1.8 times the lists which makes the page load super slow.
Something like this for mark id as completed:
SELECT l.*, SUM(ll.action_id=6) completed FROM lesson l
INNER JOIN localization_logging ll ON ll.lesson_id = l.id
WHERE ll.language_id = 2 AND
(
ll.action_id = 1
OR
ll.action_id = 6 AND ll.part_id == 1
)
GROUP BY l.id
And now we can wrap it with:
SELECT t.* FROM (...) t WHERE t.completed = 0
You'll usually get faster queries filtering rows with INNER/LEFT JOIN, but you need to test it.
SELECT lesson.* FROM lesson
INNER JOIN localization_logging task1
ON lesson.id = task1.lesson_id
LEFT JOIN localization_logging task2
ON lesson.id = task2.lesson_id
AND task2.language_id = 2
AND task2.part_id = 1
AND task2.action_id = 6
WHERE task1.language_id = 2
AND task1.action_id = 1
AND task2.lesson_id IS NULL
Second table is joined on multiple conditions, but have to list them within ON clause because only results that were in result "force joined" as nulls (left join means left side stays no matter what) are required.
Btw. You'll get multiple rows from lesson if task1 condition is not limiting results to one row - GROUP BY lesson.id then.
I am trying to only show unique userIds (userIds are (0,1,2,3,4,5,6,7,8,9 etc...) for the query I am running. I tried using DISTINCT in my query, but it only shows me unique values of the rows that have 2 or more of the same userId.
Is there a way I can use php to only show the unique values. My weak points are arrays and it makes it more complicated because its using data from a MySQLi query.
Example right now I have with the query now (lets say its GROUP BY rentPaid DESC and the rent total is 800.00 for all users):
userID rentPaid rentMonth
2--------800.00------April
1--------500.00------April
3--------400.00------April
3--------400.00------April
1--------200.00------April
1--------100.00------April
Example desired output:
userID rentPaid rentMonth
2--------800.00------April
1--------500.00------April
3--------400.00------April
Can I do this with MYSQL because I tried DISTINCT and it wouldn't work, how about PHP?
Query:
SELECT
properties.*,
leases.*,
users.userId, users.primaryPhone,
CONCAT(users.userFirstName,' ',users.userLastName) AS user,
admins.adminName, payments.*
FROM
properties
LEFT JOIN leases ON properties.propertyId = leases.propertyId
LEFT JOIN assigned ON properties.propertyId = assigned.propertyId
LEFT JOIN admins ON assigned.adminId = admins.adminId
LEFT JOIN users ON properties.propertyId = users.propertyId
LEFT JOIN payments ON properties.propertyId = payments.propertyId
WHERE
payments.rentMonth = '$currentMonth' AND
payments.rentYear = '$currentYear'
Edit: Please excuse my formatting, this is my first post.
Edit: Added query....its long, but works lol. I only want unique userIds (no double or triple userIds etc...)
I suspect this is what you want:
SELECT userID, MAX(rentPaid) AS maxRentPaid, rentMonth
FROM yourTable
WHERE rentMonth = "April"
GROUP BY userID
ORDER BY maxRentPaid
i'm having a good time coding a little visitor counter. it's a PHP5/SQLite3 mix.
made two database tables, one for the visitors, and one for the hits. structure and sample data:
CREATE TABLE 'visitors' (
'id' INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT,
'ip' TEXT DEFAULT NULL,
'hash' TEXT DEFAULT NULL,
UNIQUE(ip)
);
INSERT INTO "visitors" ("id","ip","hash") VALUES ('1','1.2.3.4','f9702c362aa9f1b05002804e3a65280b');
INSERT INTO "visitors" ("id","ip","hash") VALUES ('2','1.2.3.5','43dc8b0a4773e45deab131957684867b');
INSERT INTO "visitors" ("id","ip","hash") VALUES ('3','1.2.3.6','9ae1c21fc74b2a3c1007edf679c3f144');
CREATE TABLE 'hits' (
'id' INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT,
'time' INTEGER DEFAULT NULL,
'visitor_id' INTEGER DEFAULT NULL,
'host' TEXT DEFAULT NULL,
'location' TEXT DEFAULT NULL
);
INSERT INTO "hits" ("id","time","visitor_id","host","location") VALUES ('1','1418219548','1','localhost','/some/path/example.php');
INSERT INTO "hits" ("id","time","visitor_id","host","location") VALUES ('2','1418219550','1','localhost','/some/path/example.php');
INSERT INTO "hits" ("id","time","visitor_id","host","location") VALUES ('3','1418219553','1','localhost','/some/path/example.php');
INSERT INTO "hits" ("id","time","visitor_id","host","location") VALUES ('4','1418219555','2','localhost','/some/path/example.php');
INSERT INTO "hits" ("id","time","visitor_id","host","location") VALUES ('5','1418219557','1','localhost','/some/path/example.php');
INSERT INTO "hits" ("id","time","visitor_id","host","location") VALUES ('6','1418219558','3','localhost','/some/path/example.php');
i now want to fetch the visitors data, but only from those who where active in the last 30 seconds for example. i need the following data as output, here with user id 1 as example:
$visitor = Array(
[id] => 1
[ip] => 1.2.3.4
[hash] => f9702c362aa9f1b05002804e3a65280b
[first_hit] => 1418219548
[last_hit] => 1418219557
[last_host] => localhost
[last_location] => /some/path/example.php
[total_hits] => 4
[idle_since] => 11
)
i'll get this with my current query, all good, but as you can see i need a lot of sub-selects for this:
SELECT
visitors.id,
visitors.ip,
visitors.hash,
(SELECT hits.time FROM hits WHERE hits.visitor_id = visitors.id ORDER BY hits.id ASC LIMIT 1) AS first_hit,
(SELECT hits.time FROM hits WHERE hits.visitor_id = visitors.id ORDER BY hits.id DESC LIMIT 1) AS last_hit,
(SELECT hits.host FROM hits WHERE hits.visitor_id = visitors.id ORDER BY hits.id DESC LIMIT 1) AS last_host,
(SELECT hits.location FROM hits WHERE hits.visitor_id = visitors.id ORDER BY hits.id DESC LIMIT 1) AS last_location,
(SELECT COUNT(hits.id) FROM hits WHERE hits.visitor_id = visitors.id) AS total_hits,
(SELECT strftime('%s','now') - hits.time FROM hits WHERE hits.visitor_id = visitors.id ORDER BY hits.id DESC LIMIT 1) AS idle_since
FROM visitors
WHERE idle_since < 30
ORDER BY last_hit DESC
so, is this ok for my use case or do you know a better approach to get this data out of those two tables? i already played around with JOINS, but no matter how i tweaked it, COUNT() gave me wrong outputs, like user id 1 has only one total hit for example.
i probably have to re-model the database, if i wanna use JOINS properly, i guess.
Update: based on AeroX' Answer i've built the new query. it basically had just one little bug. you can't have MAX() in a WHERE clause. using HAVING now after the GROUPING.
i also tested both the old and the new one with EXPLAIN and EXPLAIN QUERY PLAN. looks much better. Thank you guys!
SELECT
V.id,
V.ip,
V.hash,
MIN(H.time) AS first_hit,
MAX(H.time) AS last_hit,
strftime('%s','now') - MAX(H.time) AS idle_since,
COUNT(H.id) AS total_hits,
LH.host AS last_host,
LH.location AS last_location
FROM visitors AS V
INNER JOIN hits AS H ON (V.id = H.visitor_id)
INNER JOIN (
SELECT visitor_id, MAX(id) AS id
FROM hits
GROUP BY visitor_id
) AS L ON (V.id = L.visitor_id)
INNER JOIN hits AS LH ON (L.id = LH.id)
GROUP BY V.id, V.ip, V.hash, LH.host, LH.location
HAVING idle_since < 30
ORDER BY last_hit DESC
You probably want to clean this up but this should give you the idea of how to make the joins and how to use the GROUP BY statement to aggregate your hits table for each visitor. This should be more efficient then using lots of sub-queries.
I've included comments on the joins so that you can see why I'm making them.
SELECT
V.id,
V.ip,
V.hash,
MIN(H.time) AS first_hit,
MAX(H.time) AS last_hit,
COUNT(H.id) AS total_hits,
strftime('%s','now') - MAX(H.time) AS idle_since,
LH.host AS last_host,
LH.location AS last_location
FROM visitors AS V
-- Join hits table so we can calculate aggregates (MIN/MAX/COUNT)
INNER JOIN hits AS H ON (V.id = H.visitor_id)
-- Join a sub-query as a table which contains the most recent hit.id for each visitor.id
INNER JOIN (
SELECT visitor_id, MAX(id) AS id
FROM hits
GROUP BY visitor_id
) AS L ON (V.id = L.visitor_id)
-- Use the most recent hit.id for each visitor.id to fetch that most recent row (for last_host/last_location)
INNER JOIN hits AS LH ON (L.id = LH.id)
GROUP BY V.id, V.ip, V.hash, LH.host, LH.location
HAVING idle_since < 30
ORDER BY last_hit DESC
One of the best ways to measure query performance is using explain.
From sqlite
The EXPLAIN QUERY PLAN SQL command is used to obtain a high-level
description of the strategy or plan that SQLite uses to implement a
specific SQL query. Most significantly, EXPLAIN QUERY PLAN reports on
the way in which the query uses database indices. This document is a
guide to understanding and interpreting the EXPLAIN QUERY PLAN output.
Background information is available separately:
Notes on the query optimizer.
How indexing works.
The next generation query planner.
An EXPLAIN QUERY PLAN command returns zero or more rows of four
columns each. The column names are "selectid", "order", "from",
"detail". The first three columns contain an integer value. The final
column, "detail", contains a text value which carries most of the
useful information.
EXPLAIN QUERY PLAN is most useful on a SELECT statement, but may also
be appear with other statements that read data from database tables
(e.g. UPDATE, DELETE, INSERT INTO ... SELECT).
An example of an explain query is:
EXPLAIN SELECT * FROM COMPANY WHERE Salary >= 20000;
http://www.tutorialspoint.com/sqlite/sqlite_explain.htm
Below are more complex usage examples.
How can I analyse a Sqlite query execution?
I've been working on a project that until now has only needed to find 1 row from the joined table. But now I need to grab multiple rows..
So as it stand my sql works something like:
Select rows for each company for this particular project which alone would find company details (name, id, telephone.. blah).
Then I join a table that contains form data submitted for each company (multiple forms - so multiple records)
Until now i have been specifying one formid to look for in the join, but now i need to specify multiple ones.
If I use WHERE form_id = 1 OR form_id = 2 OR form_id = 3 ... I get a result of only the first form match that is found per company..
If I mix up the query so it looks for the forms 1st and returns multiple records for each company with different form data - that works in this sense..
But I am then looping through this array in a view and creating a table row per record (previously each row was a new company) but using the latter would cause multiple records to show for the same company.
Any way I can do this? I tried group by with the latter method but this results in only 1 record again.
SELECT DISTINCT p.project_company_has_user_id, p.project_company_has_user_project_id, p.project_company_has_user_user_id, c.company_id, c.company_hall_no, c.company_company_name, c.company_type, c.company_country, c.company_stand_number, c.company_image_file_1, p2.project_id, p2.project_name, u.user_id, u.user_username, o.orders_id, o2.order_detail_id, o2.order_detail_product_id, f2.form_question_has_answer_id, f2.form_question_has_answer_request, f2.form_question_has_answer_form_id, f2.form_question_has_answer_user_id
FROM project_company_has_user p
INNER JOIN company c ON p.project_company_has_user_company_id = c.company_id
INNER JOIN project p2 ON p.project_company_has_user_project_id = p2.project_id
INNER JOIN user u ON p.project_company_has_user_user_id = u.user_id
INNER JOIN form f ON p.project_company_has_user_project_id = f.form_project_id
LEFT JOIN orders o ON p.project_company_has_user_user_id = o.orders_user_id
LEFT JOIN order_detail o2 ON ((o2.order_detail_orders_id = o.orders_id AND (o2.order_detail_product_id = 65 OR o2.order_detail_product_id = 68 OR o2.order_detail_product_id = 64)))
LEFT JOIN form_question_has_answer f2 ON ((f2.form_question_has_answer_form_id = 297 AND f2.form_question_has_answer_user_id = p.project_company_has_user_user_id))
WHERE (f.form_template_name = "custom" AND p.project_company_has_user_garbage_collection = 0 AND p.project_company_has_user_project_id = 48) AND (LCASE(c.company_country) LIKE "%uk%" OR LCASE(c.company_country) LIKE "%uk%") ORDER BY company_company_name asc
you need another field in order_detail as o2 . this field is row_index(position),etc for positioning record
LEFT JOIN order_detail o2 ON (o2.row_index=1 AND (o2.order_detail_orders_id = o.orders_id AND (o2.order_detail_product_id = 65 OR o2.order_detail_product_id = 68 OR o2.order_detail_product_id = 64)))
Personally I would use an Outer Join for the table of which elements you need to list all matches. Should you them need to clean up that data you can build the logic into the Join Condition (as step 2). Depending on the volume of data you are handling and whether or not you need to reuse it later in the same proc, you may want to post that primary dataset into a temp table and use that as source (primary) for your later logic.
Hope that helps. If you need the code, let me know, but it is pretty straight forward.
Regards
Mac
I am a new programmer and I don't know how to do this.
I have:
a table named customers (cust_id, name)
a table named agents (agent_id, agent_name)
a table named authorizations (cust_id, agent_id)
a table named product_services (p_s_id, cust_id, agent_id, item_name, item_id)
About the authorizations table: the agent_id is the agent who the is authorised salesperson for cust_id. So, I need to be able to pull only results for this particular cust_id under this agent_id.
I need to do a query that will return all the products and services that the customer have under all other agents. In the search, it should also return products/services that they have under me as well.
This is what I have tried so far:
$sql = "SELECT
product_services.item_name, agents.agent_name,
customers.name, agents.agent_id, product_services.item_id
FROM
product_services
LEFT JOIN agents ON product_services.agent_id = agents.agent_id
LEFT JOIN customers ON product_services.cust_id = customers.cust_id
WHERE authorizations.agent_id = '$aid'
AND product_services.item_name LIKE '%$q%'
ORDER BY product_services.id DESC
LIMIT $start, $limit";
KPO,
Below is a query I used to connect 2 tables. It would be the same for your query, notice the syntax used starting at "LEFT JOIN"
SELECT
user.username,
groups.group_id, groups.group_name,
sign.last_connected, sign.sign_id, sign.sign_name, sign.resolution_x, sign.resolution_y, LEFT(sign.sign_name, 1) AS first_char
FROM
user
LEFT JOIN(
groups, sign
)ON(
user.user_id = groups.userID AND
groups.group_id = sign.groupID
)
WHERE
username = ? AND
UPPER(sign.sign_name) BETWEEN "A" AND "Z"
OR sign.sign_name BETWEEN "0" AND "9" ORDER BY sign.sign_name
Considering your SQL query is initially correct this should work for your LEFT JOIN portion:
LEFT JOIN(agents, customers
)ON( product_services.agent_id = agents.agent_id
AND product_services.cust_id = customers.cust_id)
Typically, a query would start with the core table at the root of your criteria... Such as a single customer and join from that... You also state you want ALL products/services from ALL agents including the "me" agent, you would not want to have a filter on your '$aid' criteria.
SELECT STRAIGHT_JOIN
c.cust_id,
c.name,
ag.agent_id,
ag.agent_name,
ps.item_name,
ps.p_s_id,
ps.item_id
from
customers c
join authorizations au
on c.cust_id = au.cust_id
[[ AND au.agent_id = '$aid' ]]
join agents ag
on au.agent_id = ag.agent_id
join product_services ps
on c.cust_id = ps.cust_id
AND au.agent_id = ps.agent_id
[[ AND ps.item_name like '%$q%' ]]
order by
ps.p_s_id DESC
limit
$start, $limit
In the query above, I've put in section where you could apply your
[[ AND agent criteria ]]
or product/service criteria. But as you stated, you wanted ALL activity of ALL agents, so I've left it out... Likewise with a possible
[[ AND product / service ]]
criteria which may/may not be provided and you can very simply strip it out...
-- EDIT PER COMMENT FEEDBACK.
As per your inquiry on how to add more "criteria", its based on the origin of the table. If its the FIRST table in the "FROM" clause, you'll add a WHERE clause and put criteria to that table... As for the others, like the agents and product services, where I had the [[ criteria ]], you could just expand your entire criteria there (per respective table its joined with).
Such as your [[ product service criteria ]], I could have added something like
AND ( ( ps.Item like '%$something'
OR ps.Item like '%$another'
OR ps.Item like '%$more' )
AND ps.OtherField = whatever )
Keep your primary "join" conditions which identify the relationship between the tables first and foremost... only THEN do you want to add your restricting criteria... As you can see in my sample above, I've wrapped the entire AND ( ) clause within parenthesis to see it as a single "unit" condition... such as to the product/service table.
Hope this sample helps you in future querying.