I have a query
$query = "SELECT DISTINCT report_date,weekreportDate FROM contract_sales a
INNER JOIN contract b ON a.contract_UUID = b.UUID
INNER JOIN geoPoint c ON b.customer_UUID = c.customerUUID
WHERE c.com_UUID = '$com' AND a.report_date >= Date('$dateafter')
AND c.city_UUID = '$cit' ORDER BY `report_date`";
What I need to do is first get rid of all the results via date filtering but as you can see I get everything and then do my date sorting in the checks..
I am inner join all of them - is there a better way to do this?
I have a report for each date - and have two years of data - I want to get only dates in 2014 so as you can see I have 700+ dates that are useless to me right away but I have to go through all of them can check the other string UUID as well... what can I do to speed up my (working - albeit slow implementation)?
Explain information as requested:
Generation Time: Feb 20, 2014 at 06:48 PM
Generated by: phpMyAdmin 3.3.10.4 / MySQL 5.1.53-log
SQL query: EXPLAIN SELECT DISTINCT report_date,weekreportDate FROM contract_sales a INNER JOIN contract b ON a.contract_UUID = '1234' INNER JOIN geoPoint c ON b.customer_UUID = '1234' WHERE c.com_UUID = '1234' AND a.report_date >= Date('2014-01-01') AND c.city_UUID = '1234' ORDER BY `report_date`;
Rows: 3
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE a ref uuid_conlcs uuid_conlcs 110 const 1 Using where; Using temporary; Using filesort
1 SIMPLE b ref uuid_cust uuid_cust 110 const 1 Using where; Using index; Distinct
1 SIMPLE c ref uuid_gargp,uuid_citgp uuid_citgp 110 const 1 Using where; Distinct
First, a rewrite of your query. I would not suggest using aliases just a, b, c, but something closer to context of table "cs" for contract_sales, "con" for contract, and "gp" for geoPoint... especially easier in larger more complex queries.
Also, ALWAYS try to qualify your query with table.column (or alias.column) as the WeekReportDate is not clear, but it appears to be associated with your contract sales table.
As for indexes in this construct, I would have an index on (report_date, weekreportDate, contract_uuid). This way, its a covering index to handle the columns being retrieved, the where clause, order by, and the join to the contract table without having to go back to the raw data pages.
The contract table, I would have an index on ( UUID, customer_UUID), also to be a covering index for the join from contract sales, and also to support the join to the geoPoint table.
Finally, your geoPoint table, an index on ( customerUUID, com_uuid, city_uuid ) to also cover the join and your filtering criteria.
SELECT DISTINCT
cs.report_date,
cs.weekreportDate
FROM
contract_sales cs
INNER JOIN contract con
ON cs.contract_UUID = con.UUID
INNER JOIN geoPoint gp
ON con.customer_UUID = gp.customerUUID
AND gp.com_UUID = '$com'
AND gp.city_UUID = '$cit'
WHERE
cs.report_date >= Date('$dateafter')
ORDER BY
cs.report_date
Now that being said, and I don't know the makeup of your tables for volume, but if you are looking for stuff for a particular COM/City, I would suspect that records qualifying that would be a much smaller set than ALL COM/City for the date range in question. So, I would reverse the query as below hoping the smaller dataset might query faster, but you would have to obviously try both out.
SELECT DISTINCT
cs.report_date,
cs.weekreportDate
FROM
geoPoint gp
INNER JOIN contract con
ON gp.customerUUID = con.customer_UUID
JOIN contract_sales cs
ON con.UUID = cs.contract_UUID
AND cs.report_date >= Date('$dateafter')
WHERE
gp.com_UUID = '$com'
AND gp.city_UUID = '$cit'
ORDER BY
cs.report_date
Actually the geoPoint index should be on your WHERE criteria first, then the customer UUID for the join to the next table (com_uuid, city_uuid, customeruuid ). The contract_sales index on ( contract_UUID, report_date ), and the contract table index on (customer_UUID, UUID ) to match the flow of joins of this query.
Related
I'm not sure why but I'm hitting an absolute wall trying to come up with this select statement. Maybe there is a PHP/MYSQL function that I'm not familiar with that would help. The idea is simple for this user management software: there are managers, and managers can (but do not have to) share clients. Amongst the manager and shared client relationship, one of the managers can be assigned as a lead. So here's how the basic example of what the database looks like for 1 client that is shared between 2 managers and assigned, and another client that is also shared but NOT assigned (represented by zero).
DROP TABLE IF EXISTS clients;
CREATE TABLE clients
(client_id SERIAL PRIMARY KEY
,client_name VARCHAR(12) UNIQUE
);
INSERT INTO clients VALUES
(555,'Jimmy'),
(789,'Tyler');
DROP TABLE IF EXISTS managers;
CREATE TABLE managers
(manager_id SERIAL PRIMARY KEY
,manager_name VARCHAR(12)UNIQUE
);
INSERT INTO managers VALUES
(123,'Michael'),
(456,'David');
DROP TABLE IF EXISTS relationships;
CREATE TABLE relationships
(client_id INT NOT NULL
,manager_id INT NOT NULL
,assigned INT NOT NULL
,PRIMARY KEY(client_id,manager_id)
);
INSERT INTO relationships VALUES
(555, 123, 0),
(555, 456, 1),
(789, 123, 0),
(789, 456, 0);
To get to the point: the statement I'm trying to make is for a manager to be shown all the clients that he has a relationship with, but are NOT assigned to him or anyone else on his team, i.e. select all of my clients where no one is assigned as the lead.
Expected input: Show all clients that manager 123 has a relationship with, but have yet to be assigned to any manager
Expected result: client 789
Happy to clarify as I can see this being overtly confusing as described.
SELECT c.*
FROM managers m
JOIN relationships r
ON r.manager_id = m.manager_id
JOIN clients c
ON c.client_id = r.client_id
LEFT
JOIN relationships x
ON x.client_id = c.client_id
AND x.assigned = 1
WHERE m.manager_id = 123
AND r.assigned = 0
AND x.client_id IS NULL;
+-----------+-------------+
| client_id | client_name |
+-----------+-------------+
| 789 | Tyler |
+-----------+-------------+
So you will have to start with finding all clientid's from the manager in RELATIONS, but remove all that have a manager assigned already.
Depending on the size of the table you might want to rewrite this, but here is one approach:
1) Get all clientids that have no manager:
SELECT R1.client_id, SUM(R1.assigned) as sumassigned FROM relationships AS R1 GROUP BY R1.client_id HAVING ( SUM(R1.assigned) = 0)
Now it is easier, you just join, eg:
SELECT R2.client_id, R2.manager_id FROM relationships AS R2
INNER JOIN
(SELECT R1.client_id, SUM(R1.assigned) as sumassigned FROM relationships AS R1 GROUP BY R1.client_id HAVING ( SUM(R1.assigned) = 0) ) AS DRVNOMANAGER
ON (R2.client_id = DRVNOMANAGER.client_id)
WHERE (R2.manager_id = 123)
Not tested. (meaning you might have to fix it)
The idea is to create a derived (temp) table containing all clients without a manager, then do an inner join on your original question ("what clients does this manager 123 know that do not have another assigned)
Does that solve your problem?
PS: Such things are much easier solved in PHP, but if your dataset is huge, that is not feasible.
OP asked for clientnames, so just add that:
SELECT R2.client_id, R2.manager_id, C.client_name FROM relationships AS R2
INNER JOIN
(SELECT R1.client_id, SUM(R1.assigned) as sumassigned FROM relationships
AS R1 GROUP BY R1.client_id HAVING ( SUM(R1.assigned) = 0) ) AS DRVNOMANAGER
ON (R2.client_id = DRVNOMANAGER.client_id)
INNER JOIN clients AS C ON (C.client_id = R2.client_id)
WHERE (R2.manager_id = 123)
And last request: removed deleted relations:
Simply add a WHERE clause to the inner DRVNOMANAGER with your restriction on the rows that are used in the GROUP BY. eg:
SELECT R2.client_id, R2.manager_id, C.client_name FROM relationships AS R2
INNER JOIN
(SELECT R1.client_id, SUM(R1.assigned) as sumassigned FROM relationships
AS R1 WHERE (NOT(R1.deleted = 1) ) GROUP BY R1.client_id HAVING ( SUM(R1.assigned) = 0) ) AS DRVNOMANAGER
ON (R2.client_id = DRVNOMANAGER.client_id)
INNER JOIN clients AS C ON (C.client_id = R2.client_id)
WHERE (R2.manager_id = 123)
==============================================
THIS WAS MY OLD ANSWER. NOT RELEVANT ANYMORE.
"select all of my clients where no one is assigned as the lead."
If I read you well that means: Get all clientid from RELATIONS where some managerid is given, AND assigned=0. (assigned=0 meaning "no one is assigned as the lead.")
Is that correct?
Then you end up with something like this (for managerid 123):
SELECT R.clientid, C.clientname FROM RELATIONSHIPS AS R WHERE ( (R.managerid = 123) AND (R.assigned=0))
INNER JOIN CLIENTS AS C ON (C.clientid = R.clientid)
I removed the spaces in the columnnames because I hate spaces in columnnames.
I have three tables named issue_details, nature_payments, and rci_records. Now I have this query which joins this three tables.
SELECT issue_details.issue_date AS Date,
issue_details.check_no AS Check_No,
payees.payee_name AS Name_payee,
nature_payments.nature_payment AS Nature_of_Payment,
issue_details.issue_amount AS Checks_issued,
issue_details.nca_balance AS Nca_balance
FROM
issue_details
INNER JOIN
nature_payments ON
issue_details.nature_id = nature_payments.nature_id
INNER JOIN
payees ON
issue_details.payee_id = payees.payee_id
ORDER BY Date Asc, Check_no ASC
On my column in Nca_balance, this is a computed differences of every issuances of check. But you may not know what really the process of how I got the difference but to make it simple, let's say that I have another query
that dynamically get also the difference of this nca_balance column. Here is the query:
SELECT r.*,
(#tot := #tot - issue_amount) as bank_balance
FROM (SELECT #tot := SUM(nca_amount) as nca_total FROM nca
WHERE account_type = 'DBP-TRUST' AND
year(issue_date) = year('2015-01-11') AND
month(issue_date) = month('2015-01-11')
)
vars CROSS JOIN issue_details r
WHERE r.account_type = 'DBP-TRUST' AND
r.issue_date = '2015-01-11'
ORDER BY r.issue_date, r.check_no
I know it you may not get my point but I just want to replace the first query of the line
issue_details.nca_balance AS Nca_balance
with my own computation on my second query.
Please help me combine those two query into a single query. Thanks
I have an instrument list and teachers instrument list.
I would like to get a full instrument list with id and name.
Then check the teachers_instrument table for their instruments and if a specific teacher has the instrument add NULL or 1 value in a new column.
I can then take this to loop over some instrument checkboxes in Codeigniter, it just seems to make more sense to pull the data as I need it from the DB but am struggling to write the query.
teaching_instrument_list
- id
- instrument_name
teachers_instruments
- id
- teacher_id
- teacher_instrument_id
SELECT
a.instrument,
a.id
FROM
teaching_instrument_list a
LEFT JOIN
(
SELECT teachers_instruments.teacher_instrument_id
FROM teachers_instruments
WHERE teacher_id = 170
) b ON a.id = b.teacher_instrument_id
my query would look like this:
instrument name id value
--------------- -- -----
woodwinds 1 if the teacher has this instrument, set 1
brass 2 0
strings 3 1
One possible approach:
SELECT i.instrument_name, COUNT(ti.teacher_id) AS used_by
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
GROUP BY ti.teacher_instrument_id
ORDER BY i.id;
Here's SQL Fiddle (tables' naming is a bit different).
Explanation: with LEFT JOIN on instrument_id we'll get as many teacher_id values for each instrument as teachers using it are - or just a single NULL value, if none uses it. The next step is to use GROUP BY and COUNT() to, well, group the result set by instruments and count their users (excluding NULL-valued rows).
If what you want is to show all the instruments and some flag showing whether or now a teacher uses it, you need another LEFT JOIN:
SELECT i.instrument_name, NOT ISNULL(teacher_id) AS in_use
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
AND ti.teacher_id = :teacher_id;
Demo.
Well this can be achieved like this
SELECT
id,
instrument_name,
if(ti.teacher_instrument_id IS NULL,0,1) as `Value`
from teaching_instrument_list as til
LEFT JOIN teachers_instruments as ti
on ti.teacher_instrument_id = til.id
Add a column and check for teacher_instrument_id. If found set Value to 1 else 0.
I'm currently trying to join two tables with a left join:
--portal--
id_portal (index)
id_venue
name_portal
--access--
id_access (index)
id_event
id_portal
id_tickets
scan_access
'access' contains a number of ticket types per portal for each event. I need to combine these to get the sum total of the scan_access column for each portal but include the portals that have 'null' scan_access to come up with '0'. To achieve this I've used a left join:
SELECT portal.name_portal, SUM(access.scan_access) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal
WHERE portal.id_venue = $venueId
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
which means I get the following:
Portal 1 - Null
Portal 2 - 40
Portal 3 - 33
Portal 4 - Null
but I have an issue when I need to also get the above result when taking into account the event (id_event) because when I use the following:
SELECT portal.name_portal, SUM(access.scan_access) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal
WHERE portal.id_venue = $venueId AND access.id_event = 20
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
I get:
Portal 2 - 40
Portal 3 - 33
which makes sense as those are the only two rows that have an id_event value. But how can I take this col into account without losing the other portals? also, is there a way in sql to make the 'null' a zero when returning a result? (I can fix the null after with php but wanted to see if it was possible)
By putting access.id_event = 20 in your WHERE clause, you turn your LEFT JOIN into an INNER JOIN. Move access.id_event = 20 into your join criteria to preserve your LEFT JOIN. As #echo_me mentioned, you can use COALESCE() to get rid of your zeroes. I'd put it around the SUM(), instead of inside.
SELECT portal.name_portal, COALESCE( SUM(access.scan_access), 0 ) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal AND access.id_event = 20
WHERE portal.id_venue = $venueId
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
to convert NULL to 0 use this
COALESCE(col, 0)
in your example it will be
SUM(COALESCE(access.scan_access, 0)) AS total_scan
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