Speedier alternative to SUBSTRING() in MySQL? - mysql

I have a query that uses SUBSTRING() as a criteria:
SELECT p.name p_name,
pa.line1 p_line1,
pa.zip p_zip,
c.name c_name,
ca.line1 c_line1,
ca.zip c_zip
FROM bank b
JOIN import_bundle ib ON ib.bank_id = b.id
JOIN generic_import gi ON gi.import_bundle_id = ib.id
JOIN account_import ai ON ai.generic_import_id = gi.id
JOIN account a ON a.account_import_id = ai.id
JOIN account_address aa ON aa.account_id = a.id
JOIN address ca ON aa.address_id = ca.id
JOIN address pa ON pa.zip = ca.zip OR (pa.zip = ca.zip AND pa.line1 = ca.line1)
JOIN prospect p ON p.address_id = pa.id
JOIN customer c ON a.customer_id = c.id
WHERE b.name = 'M'
AND ib.active = 1
AND gi.active = 1
AND SUBSTRING(p.name, 1, 12) = SUBSTRING(c.name, 1, 12)
LIMIT 100
As you can see, it's just comparing the first 12 characters of p.name and c.name. Unfortunately, adding this query to the WHERE clause makes my query unbearably slow. Are there any tricks out there to do this same comparison, or is my best bet to add another column to each table that contains the first 12 characters of the customer's name? I hope it's not the latter because that would be a lot of work and I'll ultimately be doing several comparisons like this.

Add the extra columns and set up an update trigger to populate them automatically. Be sure to create indexes on the new columns, of course.

Related

MYSQL Merge two columns from two tables and still use LEFT JOIN

So I'm having a slight problem with having to save price on a product in two different tables due to a few reasons. Is it possible to merge two columns into one? I know UNION exists but does it work with LEFT JOIN's?
Any pointers is much appreciated.
Best Regards
SELECT
si.id AS shop_item_id,
si.item_price,
s.logo_file_name,
p.cat_id AS category_id,
api.item_price AS api_price,
MAX(c.campaign_desc) AS campaignDesc,
MAX(c.campaign_type_id) AS campaignType,
MAX(c.shop_id) AS campaign_shop_id,
MAX(ct.image_name) AS campaignLogo
FROM
shop_item si
LEFT JOIN
shop s ON
s.id = si.shop_id
LEFT JOIN
product p ON
si.product_id = p.id
LEFT JOIN
campaign_category cc ON
cc.category_id = p.cat_id
LEFT JOIN
campaign c ON
c.id = cc.campaign_id AND
c.shop_id = si.shop_id AND
c.show_in_pricetable = 1 AND
NOW() BETWEEN c.date_from and c.date_to
LEFT JOIN
campaign_type ct ON
c.campaign_type_id = ct.id
LEFT JOIN
shop_api_item api ON
si.rel_feed_api = api.unique_id AND si.shop_id = api.shop_id
WHERE
si.`product_id` = 586 AND
s.`active_shop` = 1
GROUP BY
s.name,
si.id ,
si.item_price
ORDER BY
si.`item_price`,
si.`shop_id`,
c.`campaign_desc` DESC
It looks like you would benefit from the COALESCE() function.
SELECT
si.id AS shop_item_id,
COALESCE(si.item_price, api.item_price) AS coalesced_price,
...
COALESCE() takes multiple arguments, and returns the first argument that is not NULL.

SQL - return rows that do not have a certain value

looking for a bit of help here if possible?
I have the following query:-
On or database we have a table called Linkfile, in this table are "Types" all beginning with "YG". I need to return those rows that do not have the type of "YG8" but just cannot seem to do it. I know ill need to use a sub query but am stuck!
This is my code and the fields I need to return. I just need to only show those that do not have the lk.type of "YG8"
select distinct l.description, p.displayname AS Temp, p.compliance_status As 'Compliant', lk.displayname, lk.type
from event e
inner join organisation o on e.organisation_ref = o.organisation_ref
inner join opportunity opp on e.opportunity_ref = opp.opportunity_ref
inner join event_role ev on ev.event_ref = e.event_ref
inner join address a on a.address_ref = opp.address_ref
inner join person p on ev.person_ref = p.person_ref
inner join lookup l on p.responsible_team = l.code
inner join person_type pt on p.person_ref = pt.person_ref
inner join linkfile lk on lk.parent_object_ref = pt.person_ref
where o.displayname LIKE '%G4S%' and p.compliance_category = '$016'
and lk.type like 'YG%' and l.code_type = '2'
and a.displayname LIKE '%MOJ%'
and pt.status = 'A'
order by l.description, p.displayname, lk.type
Use below query :
select distinct l.description, p.displayname AS Temp, p.compliance_status As 'Compliant', lk.displayname, lk.type,lk.parent_object_ref
from event e
inner join organisation o on e.organisation_ref = o.organisation_ref
inner join opportunity opp on e.opportunity_ref = opp.opportunity_ref
inner join event_role ev on ev.event_ref = e.event_ref
inner join address a on a.address_ref = opp.address_ref
inner join person p on ev.person_ref = p.person_ref
inner join lookup l on p.responsible_team = l.code
inner join person_type pt on p.person_ref = pt.person_ref
left join (select displayname, type,parent_object_ref from linkfile where lk.type like 'YG8%' )lk on lk.parent_object_ref = pt.person_ref
where o.displayname LIKE '%G4S%' and p.compliance_category = '$016' and lk.parent_object_ref is null
and l.code_type = '2'
and a.displayname LIKE '%MOJ%'
and pt.status = 'A'
order by l.description, p.displayname, lk.type;
I've used left join on linkfile with type like 'YG8%' and fetching the only records which are not matched
I think you can just replace the
lk.type like 'YG%'
with the following:
(lk.type >= 'YG' and lk.type <'YG8') or (lk.type > 'YG8' and lk.type <='YGZ')
this should accomplish what you are trying to do and also avoid using "like" which is less efficient (assuming you have an index on lk.type, at least).
You may refine this a bit by knowing which are the possible values of lk.type of course. I.e. what are the extremes for the YG "subtype"? YG00-YG99? YG-YGZ?
(Be especially careful if you may have YG81 or YG87 for example, because then my clause will not work properly... on the other hand if your YG subtype can have values like YG34 it would have been better to use YG08 instead of YG8)

Retrieve list of ids by comparing total -vs- specified status

I have a list of clients. Each client can have several activities (0..*). Each activity contains a status `is_completed` which is a Boolean (True/False).
I need to retrieve the list of clients that have all activities completed:
if a client has all its activities completed, I keep him.
if a client has not all its activities completes, I ignore him.
I wrote an SQL query that does the job but I am not convinced that it is optimized:
SELECT DISTINCT cc.client_id
FROM clients_clientactivity AS cc
LEFT JOIN clients_client AS c ON (c.id = cc.client_id)
WHERE c.client_type_id = 2
AND (
SELECT COUNT(cc1.id) FROM clients_clientactivity AS cc1 WHERE cc1.client_id = cc.client_id
) = (
SELECT COUNT(cc2.id) FROM clients_clientactivity AS cc2 WHERE cc2.is_completed = True AND cc2.client_id = cc.client_id
);
How can I improve it ?
Thank you for your help.
You could use a not in select for the not true
SELECT DISTINCT cc.client_id
FROM clients_clientactivity AS cc
LEFT JOIN clients_client AS c ON (c.id = cc.client_id)
WHERE c.client_type_id = 2
AND cc.client_id NOT IN (
SELECT cc2.client_id
FROM clients_clientactivity AS cc2
WHERE cc2.is_completed != True
)
I would use aggregation and having:
SELECT c.id
FROM clients_clientactivity ca JOIN
clients_client c
ON c.id = ca.client_id
WHERE c.client_type_id = 2
GROUP BY c.id
HAVING COUNT(*) = SUM(ca.iscompleted)
Your WHERE clause converts the LEFT JOIN to an INNER JOIN, so I removed the LEFT JOIN.
Let's simplify even further:
SELECT client_id
FROM clients_clientactivity
WHERE MIN(is_completed) = TRUE
GROUP BY client_id
(TRUE==1, FALSE==0)
Subqueries are often slow. NOT IN ( SELECT ... ) is really bad (unless the optimizer has magically gotten smarter).
You did not explain how client_type_id = 2, but maybe something like:
clients_client
SELECT a.client_id
FROM clients_client AS c
JOIN clients_clientactivity AS a ON (c.id = a.client_id)
WHERE MIN(a.is_completed) = TRUE
AND c.client_type_id = 2
GROUP BY a.client_id
If performance is a problem, then:
c needs INDEX(client_type_id, id)
a needs INDEX(client_id, is_completed)

Outer Join with Multiple Inner Join In a Single Query

I want to use Outer Join with inner Join in a single query
Query:
select d.unit_name, a.tour_code, a.hub_code, b.name, c.pp_no, c.dte_of_expiry
from bkng_mst a , bkng_pax b, bkng_cust c, unit_mst d
where a.bkng_id = b.bkng_id
and b.unit_cde = d.unit_cde
and a.unit_cde = d.unit_cde
and b.cust_id = c.cust_id
and a.bkng_stat = 'CNF'
and b.bkng_pax_cancel_flg = 'N'
and a.bkng_id = 'XXXX'
Use Outer Join from Table pax_dtl pd on a.bkng_id=pd.bkng_id along with above query
UPDATED :
I think that, taking into account the information provided in your comments, the following query should be helpful:
SELECT DISTINCT
d.unit_name, a.tour_code, a.hub_code, b.name, c.pp_no, c.dte_of_expiry,
pd.bkng_id, pd.unit_name, pd.tour_code, pd.pax_name, pd.pnr_no, pd.fare_base, pd.is_block, pd.is_system
FROM
bkng_mst a
INNER JOIN bkng_pax b
ON a.bkng_id = b.bkng_id
INNER JOIN bkng_cust c
ON b.cust_id = c.cust_id
INNER JOIN unit_mst d
ON b.unit_cde = d.unit_cde
AND a.unit_cde = d.unit_cde
LEFT OUTER JOIN pax_dtl pd
ON a.bkng_id=pd.bkng_id
WHERE
a.bkng_stat = 'CNF'
AND b.bkng_pax_cancel_flg = 'N'
AND a.bkng_id = 'XXXX'
Because of 1 to many relation between bkng_mst and pax_dtl tables, the columns d.unit_name, a.tour_code, a.hub_code, b.name, c.pp_no, c.dte_of_expiry from above query will repeat only if for 1 particular bkng_id value there will be at least one different value among the columns pd.bkng_id, pd.unit_name, pd.tour_code, pd.pax_name, pd.pnr_no, pd.fare_base, pd.is_block.
I hope it might help you, but in case of any doubts please write.

MySQL / PHP - 2 different arguments for 1 table

I have the following SQL:
$queryString = "
SELECT
iR.lastModified,
d.*,
c2.title as stakeholderTitle,
u.username as authorUsername,
c.title as authorContactName,
GROUP_CONCAT(iR.stakeholderRef) AS participants
FROM
informationRelationships iR,
contacts c2
INNER JOIN
debriefs d ON
d.id = iR.linkId
LEFT JOIN
users u ON
u.id = iR.author
LEFT JOIN
contacts c ON
c.ref = u.contactId
LEFT JOIN
debriefs d2 ON
d2.stakeholder = c2.ref
WHERE
(
iR.clientRef = '$clientRef' OR
iR.contactRef = '$contactRef'
)
AND
iR.projectRef = '$projectRef' AND
iR.type = 'Debrief'
GROUP BY
iR.linkId
ORDER BY
d.dateOfEngagement
";
notice how I require 2 different bits of data for the the contacts table.
So at one point, I need to match
c.ref = u.contactId
This will return one bit of information
but I also need a completely different grouping:
d2.stakeholder = c2.ref
Problem is that the title is the column i'm interested in for both:
c2.title as stakeholderTitle,
...
c.title as authorContactName
How do I go about doing this?
My current try is returning:
Error: Unknown column 'iR.linkId' in 'on clause'
I'm not sure I really understand what is happening here:
how to join two tables on common attributes in mysql and php?
EDIT::::---ANSWERED--zerkms
$queryString = "
SELECT
iR.lastModified,
d.*,
c2.title as stakeholderTitle,
u.username as authorUsername,
c.title as authorContactName,
GROUP_CONCAT(iR.stakeholderRef) AS participants
FROM
informationRelationships iR
INNER JOIN
debriefs d ON
d.id = iR.linkId
INNER JOIN
contacts c2 ON
d.stakeholder = c2.ref
LEFT JOIN
users u ON
u.id = iR.author
LEFT JOIN
contacts c ON
c.ref = u.contactId
WHERE
(
iR.clientRef = '$clientRef' OR
iR.contactRef = '$contactRef'
)
AND
iR.projectRef = '$projectRef' AND
iR.type = 'Debrief'
GROUP BY
iR.linkId
ORDER BY
d.dateOfEngagement
";
By re-ordering my query I have managed to get both columns in... Thanks zerkms!
You cannot mix implicit joins and explicit joins in a single query in mysql.
So
FROM informationRelationships iR,
contacts c2
should be rewritten to
FROM informationRelationships iR
INNER JOIN contacts c2 ON ...
Do not use cartesian product and joins in the same query (not subquery), here, use only joins (CROSS JOIN is the same as cartesian product).