Why can't I exclude this row based on a condition? - mysql

http://sqlfiddle.com/#!3/3ec1f/119
Here's my fiddle...I want the result to look like this but the query I'm using doesn't do that:
My problem with the query is that I can't seem to exclude "The Kingdom of the Crystal Skull" using the exclusion_flag condition. I also don't know why it seems that Contract 3 (Raiders of the Lost Arc) is not showing up either. I have been toiling with this for hours and have no idea what the problem is. I tried looking into subqueries, but I'm not sure that's the solution...

There's a couple of questions/issues there so I'll try to address them individually.
1) You can't exclude "The Kingdom of the Crystal Skull" using the exclusion_flag because contract_sid 7 and 8 both refer to product_list_sid 3 which includes "The Kingdom of the Crystal Skull" - you would need to create a separate product_list_sid if you wanted a contract which excluded it.
2) "Raiders of the Lost Arc" (contract_sid 3) isn't showing up because it's a "single product" contract, and your query only joins from scope to product_list_join using product_list_id - contract_sid 3 is in the product_sid column so you need a separate join to cater for contracts that use product_sid instead of product_list_sid (I assume that a contract can't use both). This is a pretty dodgy schema design but here's a query that solves that issue. Notice the use of LEFT OUTER JOIN to indicate that the table being joined to might not contain any rows (for example when scope.product_list_sid is NULL but scope.product_sid is not).
SELECT s.contract_sid,
c.contract_description,
ISNULL(p.product_description, p2.product_description) AS product_description
FROM scope s
JOIN contracts c ON (c.contract_sid = s.contract_sid)
LEFT OUTER JOIN
product_list_join plj ON (plj.product_list_sid = s.product_list_sid)
LEFT OUTER JOIN
products p ON (p.product_sid = plj.product_sid)
LEFT OUTER JOIN
products p2 ON (p2.product_sid = s.product_sid)
WHERE s.exclusion_flag = 'N'
ORDER BY s.contract_sid;
Here's the SQLFiddle for my solution: http://sqlfiddle.com/#!3/fc62e/10
Edit: After posting this I realised what you're actually trying to do - the scope table not only provides the details of contracts but also provides specific products to exclude from contracts. Again, this is bad schema design and there should be a separate scope_exclusions table or something, but here's a query that does that and excludes "The Kingdom of the Crystal Skull" as requested:
SELECT inner_query.contract_description,
inner_query.product_description
FROM (
SELECT s.contract_sid,
c.contract_description,
ISNULL(p.product_sid, p2.product_sid) AS product_sid,
ISNULL(p.product_description, p2.product_description) AS product_description
FROM scope s
JOIN contracts c ON (c.contract_sid = s.contract_sid)
LEFT OUTER JOIN
product_list_join plj ON (plj.product_list_sid = s.product_list_sid)
LEFT OUTER JOIN
products p ON (p.product_sid = plj.product_sid)
LEFT OUTER JOIN
products p2 ON (p2.product_sid = s.product_sid)
WHERE s.exclusion_flag = 'N'
) inner_query
WHERE NOT EXISTS ( SELECT 1
FROM scope
WHERE exclusion_flag = 'Y'
AND contract_sid = inner_query.contract_sid
AND product_sid = inner_query.product_sid )
ORDER BY inner_query.contract_description;
SQL Fiddle: http://sqlfiddle.com/#!3/fc62e/14

Related

Why will this query run in MySQL 5 but not in MySQL 8

We are in the process of migrating from MySQL 5 to 8 and I found a query that will work in 5 but not 8.
Senario: I'm trying to find records that have been flagged as not having breast screening related data, but actually do have this data in the system. My main record is a Cycle record (t_cycle table). Each of the other related records have a link to the cycle table. In this simplified version of the query I'm looking for cycles that do not have a mammogram record but do have either an office visit or an MRI. But I'm only interested if the office visit had a procedure done with a specific result. I'm getting the list of acceptable results by querying another result table. In the actual query I have multiple other tables joined in but this demonstrates the problem.
Here is the query that works in 5 but not version 8:
SELECT
cyc.f_enroll_id,
cyc.f_cycle_number,
ov.f_cbe_result,
mri.f_uid as 'MRI'
FROM
t_cycle cyc LEFT JOIN
t_mam_rpt mam ON cyc.f_uid = mam.f_cycle_uid LEFT JOIN
t_office_visit ov ON cyc.f_uid = ov.f_cycle_uid AND ov.f_cbe_result IN (SELECT f_code FROM t_lk_cbe_result WHERE f_mde_code IN (1,2)) LEFT JOIN
t_mri mri ON cyc.f_uid = mri.f_cycle_uid
WHERE
cyc.f_mam_indication = 5 AND
mam.f_uid IS NULL AND
(ov.f_uid IS NOT NULL OR
mri.f_uid IS NOT NULL);
In version 5 I get about 20 records back. In version 8 I get over 7000 and most of those have office visits where the CBE result is not in the list of acceptable results.
There are 2 variations that appear to work in 8:
/* appears to work (removed the OR mri.f_uid IS NOT NULL) */
SELECT
cyc.f_enroll_id,
cyc.f_cycle_number,
ov.f_cbe_result,
mri.f_uid as 'MRI'
FROM
t_cycle cyc LEFT JOIN
t_mam_rpt mam ON cyc.f_uid = mam.f_cycle_uid LEFT JOIN
t_office_visit ov ON cyc.f_uid = ov.f_cycle_uid AND ov.f_cbe_result IN (SELECT f_code FROM t_lk_cbe_result WHERE f_mde_code IN (1,2)) LEFT JOIN
t_mri mri ON cyc.f_uid = mri.f_cycle_uid
WHERE
cyc.f_mam_indication = 5 AND
mam.f_uid IS NULL AND
ov.f_uid IS NOT NULL ;
In the above version I modify the WHERE clause to not have the 'OR mri.f_uid IS NOT NULL)' but in the actual use, this is not an option.
This also seems to work:
/* appears to work - replaced the SELECT in the JOIN with hard coded values that are returned in the select) */
SELECT
cyc.f_enroll_id,
cyc.f_cycle_number,
ov.f_cbe_result,
mri.f_uid as 'MRI'
FROM
t_cycle cyc LEFT JOIN
t_mam_rpt mam ON cyc.f_uid = mam.f_cycle_uid LEFT JOIN
t_office_visit ov ON cyc.f_uid = ov.f_cycle_uid AND ov.f_cbe_result IN (2,30,31,33,34,35,38,39,40,42,43,44,45,46,47) LEFT JOIN
t_mri mri ON cyc.f_uid = mri.f_cycle_uid
WHERE
cyc.f_mam_indication = 5 AND
mam.f_uid IS NULL AND
(ov.f_uid IS NOT NULL OR
mri.f_uid IS NOT NULL);
In the above case I replaced by sub-SELECT in the office visit join with a list of acceptable results. In reality, I cannot do this because that list is going to vary.
I guess my question boils down to why are office visit records being included in the result set even when they don't have the correct result code?
I don't post often here, so hopefully I've provided enough information.
TIA for any help!
-Carolyn
Additional Information: 8/4/21
I tried to get a small subset of data to recreate the problem but it only seems to show up with a large set of data in the office visit table (the one that has the join that includes a subquery).
If I remove the subquery from the join and instead left join my list of codes and then adjust the WHERE clause to do the check there, it works correctly. This is what the modified code looks like:
SELECT
cyc.f_enroll_id,
cyc.f_cycle_number,
ov.f_cbe_result,
mri.f_uid as 'MRI'
FROM
t_cycle cyc LEFT JOIN
t_office_visit ov ON cyc.f_uid = ov.f_cycle_uid LEFT JOIN
t_lk_cbe_result lk ON ov.f_cbe_result = lk.f_code LEFT JOIN
t_mam_rpt mam ON cyc.f_uid = mam.f_cycle_uid LEFT JOIN
t_mri mri ON cyc.f_uid = mri.f_cycle_uid
WHERE
cyc.f_mam_indication = 5 AND
mam.f_uid IS NULL AND
((ov.f_uid IS NOT NULL AND lk.f_mde_code IN (1,2)) OR
mri.f_uid IS NOT NULL)
We are using version 8.0.18 so it could very likely be a bug that has already been fixed as mentioned in the comments. We are going to upgrade again and see if that takes care of it. Thanks to all who looked at this and feel free to continue to reply if you know this had been a known issue. If there is a place to find known issues with mySQL I'd appreciate that link as well.

How to Join to a table where the result can sometimes lead with a - sign?

Hopefully i can explain this well enough. I have a bit of a unique issue where the customer system we use can change a ID in the database in the background based on the products status.
What this means is when i want to report old products we don't use anymore along side active products there ID differs between the two key tables depending on there status. This means Active products in the product table match that of the stock item table with both showing as 647107376 but when the product is no long active the StockItem table will present as 647107376 but the table that holds the product information the id presents as -647107376
This is proving problematic for me when i comes to joining the tables together to get the information needed. Originally i had my query set up like this:
SELECT
Company_0.CoaCompanyName
,SopProduct_0.SopStiStockItemCode AS hbpref
,SopProduct_0.SopStiCustomerStockCode AS itemref
,SopProduct_0.SopDescription AS ldesc
,StockMovement_0.StmOriginatingEntityID AS Goodsin
FROM
SBS.PUB.StockItem StockItem_0
LEFT JOIN SBS.PUB.SopProduct SopProduct_0 ON StockItem_0.StockItemID = SopProduct_0.StockItemID
LEFT JOIN SBS.PUB.Company Company_0 ON SopProduct_0.CompanyID = Company_0.CompanyID
LEFT JOIN SBS.PUB.StockMovement StockMovement_0 ON StockItem_0.StockItemID = StockMovement_0.StockItemID
WHERE
Company_0.CoaCompanyName = ?
AND StockMovement_0.MovementTypeID = '173355'
AND StockMovement_0.StmMovementDate >= ? AND StockMovement_0.StmMovementDate <= ?
AND StockMovement_0.StmQty <> 0
AND StockMovement_0.StockTypeID ='12049886'
Unfortunately though what this means is any of the old product will not show because there is no matching id due to the SopProduct table presenting the StockItemID with a leading -
So from this i thought best to use a case when statement with a nested concat and left in it to bring through the results but this doesn't appear to work either sample of the join below:
LEFT JOIN SBS.PUB.SopProduct SopProduct_0 ON (CASE WHEN LEFT(SopProduct_0.StockItemID,1) = "-" THEN CONCAT("-",StockItem_0.StockItemID) ELSE StockItem_0.StockItemID END) = SopProduct_0.StockItemID
Can anyone else think of a way around this issue? I am working with a Progress OpenEdge ODBC.
Numbers look like numbers. If they are, you can use abs():
ON StockItem_0.StockItemID = ABS(SopProduct_0.StockItemID)
Otherwise a relatively simple method is:
ON StockItem_0.StockItemID IN (SopProduct_0.StockItemID, CONCAT('-', SopProduct_0.StockItemID))
Note that non-equality conditions often slow down JOIN operations.
Using an or in the join should work:
LEFT JOIN SBS.PUB.SopProduct SopProduct_0
ON SopProduct_0.StockItemID = StockItem_0.StockItemID
OR
SopProduct_0.StockItemID = CONCAT("-", StockItem_0.StockItemID)
You might need to cast the result of the concat to a number (if the ids are stored as numbers).
Or you could use the abs function too (assuming the ids are numbers):
LEFT JOIN SBS.PUB.SopProduct SopProduct_0
ON SopProduct_0.StockItemID = abs(StockItem_0.StockItemID)

Using an alias to form an inner join

I have a problem which I cant figure out, and have looked on google and similar questions on here, but they are just not quite the same.
I am trying to build a MySQL Query which has two parts, the first is easy and i have done this fine, as it uses existing relationships, see...
SELECT
clientsites.SiteName,
clients.ClientName,
pafaddresses.PostTown,
pafaddresses.PostCode,
CONCAT("XXXXXXX", Replace(UPPER(pafaddresses.PostCode),' ','')) AS JouneyKeytemp,
clientsites.SiteType
FROM clientsites
INNER JOIN clients ON clientsites.ClientFk = clients.ClientPk
INNER JOIN pafaddresses ON clients.ActualPAF = pafaddresses.id
You will see from this code that an alias is generated which concats two postcodes and looks like xxxxxxxyyyyyy, this does work but for obvious reasons ive removed the actual postcodes.
What I now what to do is to bring in two fields from an unrelated table called Journeys:
SELECT
JourneyKey,
SingleDistance,
SingleTime
FROM journeys
I want to bring in SingleDistance and SingleTime, where the Journey Key = Generated Alias of JourneyKeyTemp.
I have tried adding the following:
INNER JOIN journeys ON JouneyKeytemp = journeys.JourneyKey
But I just keep getting a syntax error.
Any help would be appreciated.
Repeat the expression in join predicate:
INNER JOIN journeys
ON CONCAT("XXXXXXX", Replace(UPPER(pafaddresses.PostCode),' ','')) = journeys.JourneyKey
Or you can create a subquery:
select * from(
SELECT
clientsites.SiteName,
clients.ClientName,
pafaddresses.PostTown,
pafaddresses.PostCode,
CONCAT("XXXXXXX", Replace(UPPER(pafaddresses.PostCode),' ','')) AS JouneyKeytemp,
clientsites.SiteType
FROM clientsites
INNER JOIN clients ON clientsites.ClientFk = clients.ClientPk
INNER JOIN pafaddresses ON clients.ActualPAF = pafaddresses.id)t
INNER JOIN journeys ON t.JouneyKeytemp = journeys.JourneyKey

Using results from a query within the same query

I have a rather big SQL statement that I am working with in MS Access 2010. Here it goes:
SELECT
W.ID AS wid,
W.wpt_ty AS ty,
W.wpt_num AS num,
W.wpt_nxt AS nxt,
W.latdeg AS lat,
W.londeg AS lon,
W.alt AS alt,
W.mission_id AS mid,
W.ctg1 AS ctg1,
W.ctg2 AS ctg2,
W.ctg3 AS ctg3,
W.ctg4 AS ctg4,
W.wpt_index AS indx,
W.vel AS vel,
W.tu AS tu,
R.route_num AS rnum,
R.AC_num AS ac,
R.route_type AS rtype,
R.LastUpdatedOn AS d8,
R.LastUpdatedBy AS auth,
R.flight_wpt_count AS wfcount,
M.mission_name AS msnName,
V.Description AS vstatus,
R.disallowed_reason_id AS did,
CW.wpt_num AS c1num,
CR.matching_route_id AS c1mrid,
CW.wpt_index AS c1indx,
CRU.runway_name AS c1rnwy,
CR.route_num AS c1rnum
FROM Validation AS V
(RIGHT JOIN Runways AS CRU
INNER JOIN (Routes CR
INNER JOIN Waypoints CW ON CR.ID = CW.route_id)
ON Runways.ID = Routes.runway_id
INNER JOIN ((Missions as M
INNER JOIN Routes AS R ON M.ID = R.mission_id)
INNER JOIN Waypoints AS W ON (R.ID = W.route_id)
AND (M.ID = W.mission_id)) ON
V.ID = R.validated
WHERE (((R.matching_route_id)=307543) AND ((R.validated) <> 0 ))
AND (((CW.mission_id)=mid) AND ((CW.wpt_num) = (ctg1))))
If you look at the bottom, you can see am I referencing the values ctg1 and mid on a Right Join while the Inner Joins reference other literal values. Eventually I will want to do the same for ctg2, ctg3, and ctg4
Right now I am running these as 2 separate queries but finding it to be way too slow. If I can join combine the queries (sort of like how I am showing here) it could speed things up greatly. But I am at a loss for how to:
Using select values earlier in said query from the Inner/Left join and push them into values needed on the Right join.
I may be using joins incorrectly, but I thought they had to do with combining data from possible the same tables, just on different pivot points.
How to use the MS Access GUI to help write a query like this.
I know this is for MS Access but I am tagging for MySQL just in case there are similar queries there which can be ported to MS Access?
Have you tried using UNION for this?
It would allow you to execute this query (As two queries, which you mentioned as a possibility), and join the results for your output.
Be warned, it will eat up (only show one of) your duplicates in the results set.
I also suggest reading up on the different types of joins for your own benefit, in the following answer:
MYSQL Joins

Extracting Member who did not contribute

How do you find out who did not contribute to a particular fund raiser that we all just did. There are many titles to the different charities, I however just want to extract the non-contributors for a particular charity title. Is there anyway to do this? When I do the the syntax below it comes up as an empty set. The search is done by way of the table Id matching and left joins. Please see below.
SELECT
moiid,
trim(concat(name.fname,' ' ,name.mname,' ',name.lname)) as Brother,
name.moiid as Members_ID,
sum(otherpay.othpayamt) as NO_Contribution,
quadlt.ltfname as quad
FROM name
LEFT JOIN OTHERPAY ON name.moiid = otherpay.othpaymoiid
LEFT JOIN quadlt ON name.quadlt = quadlt.ltid
WHERE Otherpay.othpaytitle like '%food drive%'
AND otherpay.othpaymoiid IS NULL
AND name.type = 'BOI'
AND name.type <> 'jrboi'
AND name.city = 'SUFFOLK'
GROUP BY brother
ORDER BY name.quadlt, brother
When you add conditions to the where clause for tables that are left joined, you effectively turn them into an inner join, requiring them to return records.
You can move the conditions to the join itself:
SELECT moiid, trim(concat(name.fname,' ' ,name.mname,' ',name.lname)) as Brother, name.moiid as Members_ID, sum(otherpay.othpayamt) as NO_Contribution, quadlt.ltfname as quad
FROM name
LEFT JOIN OTHERPAY
ON name.moiid = otherpay.othpaymoiid
AND Otherpay.othpaytitle like '%food drive%'
LEFT JOIN quadlt ON name.quadlt = quadlt.ltid
WHERE
otherpay.othpaymoiid IS NULL
AND name.type = 'BOI'
AND name.type <> 'jrboi'
AND name.city = 'SUFFOLK'
GROUP BY brother
ORDER BY name.quadlt, brother