Extracting Member who did not contribute - mysql

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

Related

MySQL Query - Object Queuing Based on Object Property to Skill Mapping

Okay, so I know the title is a bit cryptic so I'll do what I can to explain the "problem" I have and the solution I am currently using.
Problem:
An 'object' of work needs to be distributed to the apropriate user based on said object's properties.
The idea is that there is an object of work has properties. Those properties are mapped to skills. A user has skills and is able to work on an object which is within the user's skillset.
There are several [three] property definitions and I currently have the following table structures.
|-- Object to Property Set 1 -- Property Set 1 to Skill --|
Object Table -|-- Object to Property Set 2 -- Property Set 2 to Skill --|-- User Skill -- User Table
|-- Object to Property Set 3 -- Property Set 3 to Skill --|
The query may be a bit easier to understand:
SELECT counts.object_id,
COUNT(DISTINCT counts.object_skill) object_skill_count,
COUNT(DISTINCT counts.user_skill) user_skill_count
FROM
(SELECT object.object_id,
sp.skill_id object_skill,
us.skill_id user_skill
FROM object_table object
LEFT JOIN object_property op ON op.object_id = object.object_id
LEFT JOIN skill_property sp ON sp.property_id = op.property_id
LEFT JOIN user_skill us ON us.skill_id = sp.skill_id
AND us.active = 1
AND us.user_id = {$userid} -- <=- inserted from a PHP script
AND object.state = 1
UNION SELECT object.object_id,
sf.skill_id object_skill,
us.skill_id user_skill
FROM object_table object
LEFT JOIN object_flag obf ON obf.object_id = object.object_id
LEFT JOIN skill_flag sf ON sf.flag_id = obf.flag_id
LEFT JOIN user_skill us ON us.skill_id = sf.skill_id
AND us.active = 1
AND us.user_id = {$userid} -- <=- inserted from a PHP script
AND object.state = 1
UNION SELECT object.object_id,
svf.skill_id object_skill,
us.skill_id user_skill
FROM object_table object
LEFT JOIN object_creator oc ON oc.creator_id = object.creator_id
LEFT JOIN skill_creator sc ON sc.flag_id = oc.flag_id
LEFT JOIN user_skill us ON us.skill_id = sc.skill_id
AND us.active = 1
AND us.user_id = {$userid} -- <=- inserted from a PHP script
AND object.state = 1) counts
GROUP BY counts.object_id
Here we get a count of all the skills an object as well as count the number of skills the user has on that same object. If the two counts match, we know the user can work on the object. If the object's skill count exceeds the user's count, the object is beyond the user's capabilities and will not be assigned to that user.
While the above query works, it slows significantly when thrown at a large[r] table. Would like to know if there is a better way of doing things. And, since the internet is filled with amazing people, here we are.
Retroactive Update:
The Left joins in this case are there because objects can have no properties. This equates to the count 0-0 and thus makes the object workable by anyone.
It looks ok. Conditions placed on data joins instead of where clause, no order by's.
Possible options:
1) Check for missing indexes
http://basitaalishan.com/2013/03/13/find-missing-indexes-using-sql-servers-index-related-dmvs/
2) Change 'left joins' to 'inner joins'
INNER JOIN vs LEFT JOIN performance in SQL Server
3) Use 'UNION ALL' instead of 'Union'
performance of union versus union all

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

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

Taking one column from MySQL joined tables

I have a query in MySQL and I am making a crystal report by using this.
Now inside the query i have a column called scan_mode and it is coming from gfi_transaction table. This scan_mode I am using in report to suppress some sections. But some times this value is coming null for some transaction ids.
So now I want to take this scan_mode as separate query so that it will work.
Can any one please help how I can modify the below query to take only scan_mode column.
SELECT
cc.cost_center_code AS cccde,
cc.name AS ccnme,gf.scan_mode,
cc.cost_center_id AS ccid,
site.name AS siteme,
crncy.currency_locale AS currency_locale,
cntry.language AS LANGUAGE,
cntry.country_name AS cntrynm,
crncy.decimal_digits AS rnd,
gf.transaction_no AS Serial_No,
brnd.name AS brand_name,
rsn.description AS reason,
gf.comment AS COMMENT,
ts.status_description AS STATUS,
DATE_FORMAT(gf.created_date,'%d/%m/%Y') AS created_date,
gf.created_by AS created_by,
IFNULL(gf.approval_no,'Not authorized') AS Trans_no,
gf.approved_date AS approval_dt,
gf.approved_by AS approved_by,gf.status AS status1,
IFNULL(loc.cost_center_code,cc.cost_center_code) AS cur_location,
gf.document_ref_no,gf.document_ref_type,
,DATE_FORMAT(document_ref_date1,'%d/%m/%Y')) AS invoice_no
FROM
gfi_transaction gf
INNER JOIN gfi_instruction gfn ON (gf.transaction_id=gfn.transaction_id)
INNER JOIN gfi_document_instruction doc ON (gf.ref_transaction_no = doc.document_instruction_id)
INNER JOIN reason rsn ON (gf.reason_id = rsn.reason_id)
INNER JOIN gfi_status ts ON (gf.status = ts.gfi_status_id)
INNER JOIN transaction_type tt ON (gf.transaction_type_id = tt.transaction_type_id)
INNER JOIN brand brnd ON(gf.brand_id=brnd.brand_id)
-- cc details
INNER JOIN cost_center cc ON (brnd.parent_brand = cc.brand_id OR gf.brand_id = cc.brand_id)
INNER JOIN site site ON(cc.site_id = site.site_id)
INNER JOIN country cntry ON (site.country_id = cntry.country_id)
INNER JOIN currency crncy ON (cntry.currency_id=crncy.currency_id)
LEFT OUTER JOIN alshaya_location_details loc ON
(gf.brand_id = loc.brand_id AND loc.cost_center_id = gf.cost_centre_id)
LEFT OUTER JOIN alshaya_location_details locto ON
(locto.cost_center_id = gf.from_cost_center_id)
WHERE
gf.transaction_id='{?TransID}'
AND rsn.transaction_type_id IN (10,11,14)
wow, that's a big query. I ran across a similar problem in a query i was building and found the if syntax to be a solution to my problem. This was also answered in this question: MYSQL SELECT WITHIN IF Statement
$psdb->query = "SELECT count, s.classid,
if (k.sic != k.siccode, k.siccode, s.siccode) as siccode,
if (k.sic != k.siccode, k.sicdesc, s.sicdesc) as sicdesc,
if (k.sic != k.siccode, k.sicslug, s.sicslug) as sicslug
FROM ...
It looks like scan_mode column comes from "gfi_transaction" table which seems to be primary table in your query. If you get null for this column then it means your table itself have NULL value for this column. Taking that separately in a query wont solve your problem. Try replacing null with a default value and handle it in code. You can add default value instead of NULL by using ifnull(scan_mode, 'default')

MySQL - How can I use left join to get exactly one row from a joined table in the on clause?

I hope the question is clear enough.
Here's my query:
SELECT
'Total Complete' AS name, count(c.id) AS amnt
FROM
lanes AS c
LEFT JOIN leads AS l
ON ((c.carrier_mcn = l.authority_number
AND l.authority_type = 'mc')
OR c.carrier_mcn = l.mcn
OR c.carrier_mcn = CONCAT('MC', l.authority_number))
AND c.carrier_mcn <> 0
LEFT JOIN lanes_emails AS cem1
ON cem1.cid = c.id AND cem1.deleted = 0
LEFT JOIN lanes_emails AS cem2
ON cem1.cid = cem2.cid AND cem2.deleted = 0 AND cem2.id < cem1.id
LEFT JOIN lanes_equipment AS ceq1
ON ceq1.cid = c.id AND ceq1.deleted = 0
LEFT JOIN lanes_equipment AS ceq2
ON ceq1.cid = ceq2.cid AND ceq1.deleted = 0 AND ceq2.id < ceq1.id
LEFT JOIN lanes_service_area AS csa1
ON csa1.cid = c.id AND AND flag = 2 csa1.deleted = 0
LEFT JOIN lanes_service_area AS csa2
ON csa1.cid = csa2.cid AND AND flag = 2 csa1.deleted = 0 AND csa2.id < csa1.id
WHERE
c.carrier_mcn IS NOT NULL and c.carrier_mcn <> ''
AND c.broker_mcn IS NOT NULL and c.broker_mcn <> ''
AND c.fax IS NOT NULL and c.fax <> ''
AND cem2.id IS NOT NULL
AND ceq2.id IS NOT NULL
AND csa2.id IS NOT NULL
Now what I'm trying to do here is get the total count of rows from "lanes" where it has at least one email, equipment, service area, mc number, or fax number. There are two MC Numbers, but they're both on the same table; the main issue is that there can be unlimited emails, equipment, and service areas.
In this case, I starting writing out an article about this issue and discovered another similar problem:
MySQL: Can I do a left join and pull only one row from the join table?
So I tried it out and implemented it into my query. Unfortunately, it didn't work the way I had planned.
It would work fine if I had more than one row; it would get the correct count, ignoring the fact that I had multiple emails or whatnot, but it would NOT work if there was only one. Instead, it returned no results.
No, I cannot use GROUP BY to get the results I need; I've tried GROUP BY, I love using it when returning full result lists, but if I'm trying to return counts GROUP BY repeatedly messes me up.
Is there any way to force a join in mysql to return just one row without using a subquery? I only need to know if there is one item from any of these other tables, I don't need to return everything, and I don't know of a way to program it to work through the WHERE clause.
I need this query for a bar chart. I've actually got several of these queries; this one is the one that requires all results, but I have others for no requirements, at least one email, at least one mc number, etc., but if I can get this working I can get the rest working.
You could use an exists clause:
where exists
(
select *
from lanes_emails AS cem1
where cem1.cid = c.id
and cem1.deleted = 0
)
and exists
...

MySQL view joining table with multiple rows

I have a few tables which I have joined together and would like to join a table that has multiple columns. My current query is as follows:
select
usrs.firstname, usrs.middleNames, usrs.surname,
if (usrs.sex=0,'Male','Female') as sex,
usrs.DOB,
(YEAR(CURDATE())-YEAR(usrs.DOB)) - (RIGHT(CURDATE(),5)<RIGHT(usrs.DOB,5)) AS age,
birth.townName AS 'birthTown', birth.regionName AS 'birthRegion', birth.countryName AS 'birthCountry',
location.townName AS 'curTown', location.regionName AS 'curRegion', location.countryName AS 'curCountry',
usrs.email, emails.email AS 'alternateEmail',
numbers.number,
usrs.website,
usrs.aboutMe,
family.mother, family.father, family.partner, marital.status, family.aboutFamily,
children.name AS 'childsName'
from ch09.tbl_users usrs
LEFT JOIN vw_town_region_country birth ON birth.townID = usrs.birthPlace
LEFT JOIN vw_town_region_country location ON location.townID = usrs.currentLocation
LEFT JOIN tbl_alternate_emails emails ON emails.userID = usrs.id
LEFT JOIN tbl_contact_numbers numbers ON numbers.userID = usrs.id
LEFT JOIN tbl_family family ON family.userID = usrs.id
LEFT JOIN tbl_marital_status marital ON family.maritalStatusID = marital.id
LEFT JOIN tbl_children children ON family.id = children.familyID
I put my whole query it might be a bit wrong or cleaner way to do it. The issue is with the tbl_children, as it is "one to many" it results in multiple rows for a single user for every child that user has in the tbl_children table.
So my results are:
userID:1 firstName middleNames surname ....... childsName
userID:1 firstName middleNames surname ....... childsName
userID:1 firstName middleNames surname ....... childsName
I would prefer:
userID:1 firstName middleNames surname ....... childsName childsName2 childsName3
Is it possible to do this through a Join somehow? Obviously it isn't acceptable for me to have multiple entries per user on the view.
You could use the function GROUP_CONCAT in combination with GROUP BY for this. GROUP_CONCAT let's you aggregate values from a column by concatenating them. Note that this will not give you a column for every child, but one column with a string containing all the names.
EDIT; your query would become something like:
select
usrs.firstname, usrs.middleNames, usrs.surname,
if (usrs.sex=0,'Male','Female') as sex,
usrs.DOB, (YEAR(CURDATE())-YEAR(usrs.DOB)) - (RIGHT(CURDATE(),5)<RIGHT(usrs.DOB,5)) AS age,
birth.townName AS 'birthTown', birth.regionName AS 'birthRegion', birth.countryName AS 'birthCountry',
location.townName AS 'curTown', location.regionName AS 'curRegion', location.countryName AS 'curCountry',
usrs.email, emails.email AS 'alternateEmail',
numbers.number,
usrs.website,
usrs.aboutMe,
family.mother, family.father, family.partner, marital.status, family.aboutFamily,
GROUP_CONCAT(children.name SEPERATOR ",") AS 'childsName'
FROM ch09.tbl_users usrs
LEFT JOIN vw_town_region_country birth ON birth.townID = usrs.birthPlace
LEFT JOIN vw_town_region_country location ON location.townID = usrs.currentLocation
LEFT JOIN tbl_alternate_emails emails ON emails.userID = usrs.id
LEFT JOIN tbl_contact_numbers numbers ON numbers.userID = usrs.id
LEFT JOIN tbl_family family ON family.userID = usrs.id
LEFT JOIN tbl_marital_status marital ON family.maritalStatusID = marital.id
LEFT JOIN tbl_children children ON family.id = children.familyID
GROUP BY userID
Assuming that the number of children is unknown at the time of writing the query (i.e., a user could have 0 or 1 or 5 children), making a pivot like this probably isn't the best route for getting data into a front end application.
Depending on how you're accessing the data, you're better off either returning multiple rows per user as you have or retrieving the children (and emails, etc.) for each user as you need them. The key here is to only retrieve them if you need them. I believe that this is known as Lazy Loading in the Object Oriented world.
If you're doing this to fill a list box of some kind, and therefore you need them for each user, then you might consider setting some limit on the number of children that you'll retrieve based on how your list will appear and then use LEFT JOINs to get exactly that number for the rows that you retrieve, rather than doing the round trip to the server for every user.
In other words, it all depends. :)