Getting count from joined table - mysql

Here's my problem: I need to get the amount of test cases and issues associated to a project that meet certain conditions (test cases that are successful, and issues that are flaws of the application), but for some reason the amount doesn't add up. I have 10 test cases in a project, of which 6 are successful; and 8 issues, of which only 4 are flaws. However, the respective results for COUNT each show 24, which makes no sense. I did notice, though, that 24 happens to be 6 times 4, but I don't see how the query would multiply them.
Anyway... Can someone help me find which part of my query is wrong? How can I get the correct result? Thanks in advance.
Here's the query:
SELECT
p.codigo_proyecto,
p.nombre,
IFNULL(COUNT(iep.id_incidencia_etapa_proyecto), 0) AS cantidad_defectos,
IFNULL(COUNT(tc.id_test_case), 0) AS test_cases_exitosos,
CASE IFNULL(COUNT(tc.id_test_case), 0) WHEN 0 THEN 'No aplica'
ELSE CONCAT((IFNULL(COUNT(tc.id_test_case), 0) / IFNULL(COUNT(tc.id_test_case), 0)) * 100, '%') END AS tasa_defectos
FROM proyecto p
INNER JOIN etapa_proyecto ep ON p.codigo_proyecto = ep.codigo_proyecto
INNER JOIN incidencia_etapa_proyecto iep ON ep.id_etapa_proyecto = iep.id_etapa_proyecto
INNER JOIN incidencia i ON iep.id_incidencia = i.id_incidencia
INNER JOIN test_case tc ON ep.id_etapa_proyecto = tc.id_etapa_proyecto
INNER JOIN etapa_proyecto ep_ultima ON ep_ultima.id_etapa_proyecto =
(SELECT ep_ultima2.id_etapa_proyecto FROM etapa_proyecto ep_ultima2
WHERE p.codigo_proyecto = ep_ultima2.codigo_proyecto ORDER BY ep_ultima2.fecha_termino_real DESC LIMIT 1)
WHERE p.esta_cerrado = 1
AND i.es_defecto = 1
AND tc.resultado = 'Exitoso'
AND ep_ultima.fecha_termino_real BETWEEN '2015-01-01' AND '2016-12-31';

I would have thought it obvious that you're not going to get the expected output from an aggregate query without a GROUP BY (which suggests you're not really in a position to evaluate any advice given here effectively).
You've not said how the states of your data are represented in the database - so I'm having to make a lot of guesses based on SQL which is clearly very wrong. And I don't speak spanish/portugese or whatever your native language is.
It looks like you are inferring that a defect exists if the primary key of the defects table is null. Primary keys cannot be null. The only way this would make any sort of sense (BTW it still won't give you the answer you're looking for) is to do a LEFT JOIN rather than an INNER JOIN.
But even then a simple COUNT() will consider null cases (no record in source table) as 1 record in the output set.
Then you've got the problem that you will have the product of defects and test cases in your output - consider the case where you have no defects, but 2 tests cases (1,2) - the result of an outer joiun will be:
defect test
------ ----
null 1
null 2
If you just count the rows, you'll get 2 defects in your output.
Taking a simpler schema, this demonstrates the 2 methods for getting the values - note that they have very different performance characteristics.
SELECT project.id
, dilv.defects
, (SELECT COUNT(*)
FROM test_cases) AS tests
FROM project
LEFT JOIN ( SELECT project_id, COUNT(*) AS defects
FROM defect_table
GROUP BY project_id) AS dilv
ON project.id=dilv.project_id

Related

Cross-Apply bad for a larger database or alternatives perform better?

so 2 (more so 3) questions, is my query just badly coded or thought out ? (be kind, I only just discovered cross apply and relatively new) and is corss-apply even the best sort of join to be using or why is it slow?
So I have a database table (test_tble) of around 66 million records. I then have a ##Temp_tble created which has one column called Ordr_nbr (nchar (13)). This is basically ones I wish to find.
The test_tble has 4 columns (Ordr_nbr, destination, shelf_no, dte_bought).
This is my current query which works the exact way I want it to but it seems to be quite slow performance.
select ##Temp_tble.Ordr_nbr, test_table1.destination, test_table1.shelf_no,test_table1.dte_bought
from ##MyTempTable
cross apply(
select top 1 test_table.destination,Test_Table.shelf_no,Test_Table.dte_bought
from Test_Table
where ##MyTempTable.Order_nbr = Test_Table.order_nbr
order by dte_bought desc)test_table1
If the ##Temp_tble only has 17 orders to search for it take around 2 mins. As you can see I'm trying to get just the most recent dte_bought or to some max(dte_bought) of each order.
In term of index I ran database engine tuner and it says its optimized for the query and I have all relative indexes created such as clustered index on test_tble for dte_bought desc including order_nbr etc.
The execution plan is using a index scan(on non_clustered) and a key lookup(on clustered).
My end result is it to return all the order_nbrs in ##MyTempTble along with columns of destination, shelf_no, dte_bought in relation to that order_nbr, but only the most recent bought ones.
Sorry if I explained this awfully, any info needed that I can provide just ask. I'm not asking for just downright "give me code", more of guidance,advice and learning. Thank you in advance.
UPDATE
I have now tried a sort of left join, it works reasonably quicker but still not instant or very fast (about 30 seconds) and it also doesn't return just the most recent dte_bought, any ideas? see below for left join code.
select a.Order_Nbr,b.Destination,b.LnePos,b.Dte_bought
from ##MyTempTble a
left join Test_Table b
on a.Order_Nbr = b.Order_Nbr
where b.Destination is not null
UPDATE 2
Attempted another let join with a max dte_bought, works very but only returns the order_nbr, the other columns are NULL. Any suggestion?
select a.Order_nbr,b.Destination,b.Shelf_no,b.Dte_Bought
from ##MyTempTable a
left join
(select * from Test_Table where Dte_bought = (
select max(dte_bought) from Test_Table)
)b on b.Order_nbr = a.Order_nbr
order by Dte_bought asc
K.M
Instead of CROSS APPLY() you can use INNER JOIN with subquery. Check the following query :
SELECT
TempT.Ordr_nbr
,TestT.destination
,TestT.shelf_no
,TestT.dte_bought
FROM ##MyTempTable TempT
INNER JOIN (
SELECT T.destination
,T.shelf_no
,T.dte_bought
,ROW_NUMBER() OVER(PARTITION BY T.Order_nbr ORDER BY T.dte_bought DESC) ID
FROM Test_Table T
) TestT
ON TestT.Id=1 AND TempT.Order_nbr = TestT.order_nbr

mysql query finding results using joined status table that needs EXIST and NOT EXIST results

I have been looking around for ages for a solution to my problem.
I have something that works but i am not sure it is the most efficient way of doing things and can't find anyone trying to do this when googling around.
I have a table with customers and a table with statuses that that customer has had.
If I want to find results where a customer has had a status happen I have managed to get the required results using a join, but sometimes I want to be able to find clients where not only has a status been reached but also where a few other statuses haven't been.
Currently I am doing this with a NOT EXISTS Sub query but it seem a bit slow and thinking about it if I have to check after finding a result that matches the first status through all the results again to see if it doesn't match another it could explain the slowness.
for instance a client could have a status of invoiced and a status of paid.
If I wanted to see which clients have been invoiced thats fine, If I want to see which clients have been invoiced and paid thats fine, but if I wanted to see which clients have been invoiced but NOT paid thats where I start having to use a NOT EXIST subquery
Is there another more efficient way around this? or is this the best way to proceed but I need to sort out how mysql uses indxes with these tables to be more efficient?
I can provide more detail of the actual sql if that helps?
Thanks
Matt
If this is over multiple clients then the usual solution would be to have a subselect for the status per client and then use LEFT OUTER JOIN to connect this.
Something like
SELECT *
FROM Clients a
LEFT OUTER JOIN (SELECT ClientId, COUNT(*) FROM ClientsStatus WHERE Status IN (1,2) GROUP BY ClientId) b
ON a.ClientId = b.ClientId
WHERE b.ClientId IS NULL
This (very rough) example is to give you a list of clients who do not have a status of 1 or 2.
You should be able to expand this basic idea to cover the scenarios / data you are dealing with
Edited for below
I have had a play with your SQL. I think you can use a JOIN onto the subselect fairly easily, but this doesn't seem to be checking anything other than whether a claim has had a status of 3 or 95.
SELECT claims.ID, claims.vat_rate, claims.com_rate,
claims.offer_val, claims.claim_value, claims.claim_ppi_amount, claims.claim_amount, claims.approx_loan_val, claims.salutationsa, claims.first_namesa, claims.last_namesa,
clients.salutation, clients.first_name,clients.last_name, clients.phone, clients.phone2, clients.mobile, clients.dob,clients.postcode, clients.address1, clients.address2, clients.town, client_claim_status.person,clients.ID
AS client_id,claims.ID AS claim_id, claims.date_added AS status_date_added,client_claim_status.date_added AS last_client_claim_status_date_added,work_suppliers.name AS refname, financial_institution.name AS lendname, clients.date_added AS client_date_added,ppi_claim_type_2.claim_type AS ppi_claim_type_name
FROM claims
RIGHT JOIN clients ON claims.client_id = clients.ID
RIGHT JOIN client_claim_status
ON claims.ID = client_claim_status.claim_id
AND client_claim_status.deleted != 'yes'
AND ((client_claim_status.status_id IN (1, 170))
AND client_claim_status.date_added < '2012-12-02 00:00:00' )
LEFT OUTER JOIN (SELECT claim_id FROM client_claim_status WHERE status_id IN (3, 95 )) Sub1
ON claims.ID = Sub1.claim_id
LEFT JOIN financial_institution ON claims.claim_against = financial_institution.ID
LEFT JOIN work_suppliers ON clients.work_supplier_id = work_suppliers.ID
LEFT JOIN ppi_claim_type_2 ON claims.ppi_claim_type_id = ppi_claim_type_2.ID
WHERE claims.deleted != 'yes'
AND Sub1.claim_id IS NULL
ORDER BY last_client_claim_status_date_added DESC
I would suggest that you rearrange the code to remove the RIGHT OUTER JOINs though to be honest. Mixing left and right joins up tend to be very confusing.

Advanced mysql grouping issue - brainteaser

I have been using stackoverflow vastly during the last year - an excellent source w/ great contributors. Now it's my time to request for help.
The setup is normal:
Orders, OrderArticles and Articles
I want to get the total amount of articles sold during the last year, but only during the best 5 weeks.
Never mind the WEEK-function and UNIXTIME-blah blah - I've got that covered. My question is wether it's possible or not to do without resorting to stored procedures or functions.
I have created a subquery for the summary for each week and article and order the result by the sum descendingly. Now - I only have to LIMIT the query to 5. Easy, but I also have to filter the result on the ArticleID BUT since I'm inside a subquery I don't have access to the outer ArticleID and it doesn't help to JOIN the result - it's too late ;-)
The syntax (hard to understand w/o the actual sql, right...?)
SELECT a.ID, [more fields], omg.total
FROM Articles AS a
LEFT JOIN
(
SELECT weeklytotals.articleID, weeklytotals.total
FROM
(
SELECT SUM(ra.quantity) AS total, ra.articleID AS articleID
FROM OrderArticles ra
INNER JOIN Orders r
ON ra.orderID = r.ID
WHERE r.timeCreated >= UNIX_TIMESTAMP('2011-06-30')
GROUP BY ra.articleID, WEEK(FROM_UNIXTIME(r.timeCreated))
ORDER BY SUM(ra.quantity) DESC
) AS weeklytotals
WHERE omg.articleID = a.ID --<-- THIS IS NOT WORKING BUT NECESSARY!
LIMIT 0, 5
) AS omg
ON omg.articleID = a.ID
WHERE a.isEnabled = 1 --more WHERE-thingys
This here returns the top 5 articles and ties the them to the correct Article. yay.
I have left out the SUM-function (which could go into the omg-SELECT).
Do you understand? Do I understand what I want? Yes, of course we do!
Thanx in advance.
Edit: The conditions have been changed - which makes my life easier, but I still would like to know if there is a solution to the problem.
If you require the omg subquery to use data from the a table, place it into the SELECT part not the FROM part. Using terms from the mysql documentation, you want the result of a correlated subquery to appear as a scalar operand in the outer result set.
You wrote about being interested in the sum, i.e. only a single number per article, although you left out the SUM from your example query. My approach relies on that sum, and would probably break in a bad way if you really needed distinct values for each of the best five weeks.
SELECT a.ID, [more fields], IFNULL(SUM(
(
SELECT SUM(ra.quantity) AS total
FROM OrderArticles ra
INNER JOIN Orders r
ON ra.orderID = r.ID
WHERE ra.articleID = a.ID -- <-- reference a.ID here
AND r.timeCreated >= UNIX_TIMESTAMP('2011-06-30')
GROUP BY WEEK(FROM_UNIXTIME(r.timeCreated))
ORDER BY SUM(ra.quantity) DESC
LIMIT 0, 5
)), 0) AS total
FROM Articles AS a
WHERE a.isEnabled = 1 --more WHERE-thingys
GROUP BY a.ID
I'm not saying anything about performance here. Placing the subquery this way, it will be executed for every row of the result set. So it might be too slow for practical use if you have a large number of articles. But if that should happen, I doubt that stored procedures or similar tricks would fare any better.
Edit: I found out that my original suggestion, which used subquerys nested two levels deep, doesn't allow access the innermost subquery to use a column of the outermost. But toying with this on sqlfiddle I also found out that one may safely pass the result of a subquery to sum, thus avoiding one level of nesting. So the above code nos has actually been checked and executed by a MySQL server, and should therefore work as intended.

Mysql - Summing counts of multiple joined tables

I will do my best to explain this clearly. I am trying to accomplish two things, but am having trouble even getting the first to work correctly.
I have a schema that has a member table which has foreign keys to multiple tables. In the end I am going to be drawing from about 10 tables that may or may not have records for a particular member. I am trying to get the sum of all the counts. My query looks like this:
SELECT (COUNT(tb1.member_id) + COUNT(tb2.member_id)) as total
FROM members m
LEFT JOIN table_1 tb1 ON tb1.member_id = m.member_id
LEFT JOIN table_2 tb2 ON tb2.member_id = m.member_id
WHERE m.member_id = 27
Where 27 is the member_id of the test account I am working with. This doesn't produce accurate results and I believe it is because of the left join, it seems to be throwing things off and I am getting a total of 8 even though there are only two of each kind of record. If I eliminate one of the left joins then I get the expected result.
Could anyone tell me how I should go about doing this?
That is part one of my problem. The second issue is that in some of these cases I will want each result to count as either 1 or 0, that is even if there are 2 or 3 corresponding records. I was looking for something like casting a result as a bool but have not found anything. Could anyone suggest a way to do this?
Thanks much for reading, any advice would be very much appreciated. It could be that I am approaching this problem in the wrong way, again any advice is appreciated.
Eventhough i am not familiar with state of the art of mysql i am pretty sure something like this will work:
SELECT
(select COUNT(*) from table_1 = where member_id = m.member_id)
+
(select COUNT(*) from table_2 = where member_id = m.member_id)
as total
FROM members m
WHERE m.member_id = 27

Using Joins, Group By and Sub Queries, Oh My!

I have a database with a table for details of ponies, another for details of contacts (owners and breeders), and then several other small tables for parameters (colours, counties, area codes, etc.). To give me a list of existing pony profiles, with their various details given, i use the following query:
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
ORDER BY profiles.ProfileYearOfBirth ASC $limit
In the above sample, the 'profiles' table is my primary table (holding the Ponies info), 'contacts' is second in importance holding as it does the owner and breeder info. The lesser parameter tables can be identified by their prm_ prefix. The above query works fine, but i want to do more.
The first big issue is that I wish to GROUP the results by gender: Stallions, Mares, Geldings... I used << GROUP BY prm_breedgender.BreedGender >> or << GROUP BY ProfileBreedGenderID >> before my ORDER BY line, but than only returns two results from all my available profiles. I have read up on this, and apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause. How to do this however, gets me verrrrrrry confused. Step by step help here would be fantabulous.
As a further note on the above - You may have noticed the $limit var at the end of my query. This is for pagination, a feature I want to keep. I shouldn't think that's an issue however.
My secondary issue is more of an organisational one. You can see where I have pulled my Owner information from the contacts table here:
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
I could add another stipulation:
AND profiles.ProfileBreederID = contacts.ContactID
with the intention of being able to list a pony's Owner and Breeder, where info on either is available. I'm not sure how to echo out this info though, as $row['ContactName'] could apply in either the capacity of owner OR breeder.
Is this a case of simply running two queries rather than one? Assigning a variable $foo to the first run of the query, then just run another separate query altogether and assign $bar to those results? Or is there a smarter way of doing it all in the one query (e.g. $row['ContactName']First-iteration, $row['ContactName']Second-iteration)? Advice here would be much appreciated.
And That's it! I've tried to be as clear as possible, and do really appreciate any help or advice at all you can give. Thanks in advance.
##########################################################################EDIT
My query currently stands as an amalgam of that provided by Cularis and Symcbean:
SELECT *
FROM (
profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
)
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
It works insofar as the results are being arranged as I had hoped: i.e. by age and gender. However, I cannot seem to get the alias' to work in relation to the contacts queries (breeder and owner). No error is displayed, and neither are any Owners or Breeders. Any further clarification on this would be hugely appreciated.
P.s. I dropped the alias given to the final LEFT JOIN by Symcbean's example, as I could not get the resulting ORDER BY statement to work for me - my own fault, I'm certain. Nonetheless, it works now although this may be what is causing the issue with the contacts query.
GROUP in SQL terms means using aggregate functions over a group of entries. I guess what you want is order by gender:
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
This will output all Stallions, etc. next to each other.
To also get the breeders contact, you need to join with the contacts table again, using an alias:
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
To further expand on what #cularis stated, group by is for aggregations down to the lowest level of "grouping" criteria. For example, and I'm not doing per your specific tables, but you'll see the impact. Say you want to show a page grouped by Breed. Then, a user picks a breed and they can see all entries of that breed.
PonyID ProfileGenderID Breeder
1 1 1
2 1 1
3 2 2
4 3 3
5 1 2
6 1 3
7 2 3
Assuming your Gender table is a lookup where ex:
BreedGenderID Description
1 Stallion
2 Mare
3 Geldings
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
select
BG.Description,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
group by
BG.Description
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description CountPerBreed
Geldings 1
Mare 2
Stallion 4
change the "order by" clause to "order by CountsPerBreed Desc" (for descending) and you would get
Description CountPerBreed
Stallion 4
Mare 2
Geldings 1
To expand, if you wanted the aggregations to be broken down per breeder... It is a best practice to group by all things that are NOT AGGREGATES (such as MIN(), MAX(), AVG(), COUNT(), SUM(), etc)
select
BG.Description,
BR.BreaderName,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
join Breeders BR
on p.Breeder = BR.BreaderID
group by
BG.Description,
BR.BreaderName
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description BreaderName CountPerBreed
Geldings Bill 1
Mare John 1
Mare Sally 1
Stallion George 2
Stallion Tom 1
Stallion Wayne 1
As you can see, the more granularity you provide to the group by, the aggregation per that level is smaller.
Your join conditions otherwise are obviously understood from what you've provided. Hopefully this sample clearly provides what the querying process will do. Your group by does not have to be the same as the final order... its just common to see so someone looking at the results is not trying to guess how the data was organized.
In your sample, you had an order by the birth year. When doing an aggregation, you will never have the specific birth year of a single pony to so order by... UNLESS.... You included the YEAR( ProfileYearOfBirth ) as BirthYear as a column, and included that WITH your group by... Such as having 100 ponies 1 yr old and 37 at 2 yrs old of a given breed.
It would have been helpful if you'd provided details of the table structure and approximate numbers of rows. Also using '*' for a SELECT is a messy practice - and will cause you problems later (see below).
What version of MySQL is this?
apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause
Not necessarily since v4 (? IIRC), you could just wrap your query in a consolidating select (but move the limit into the outer select:
SELECT ProfileGenderID, COUNT(*)
FROM (
[your query without the LIMIT]
) ilv
GROUP BY ProfileGenderID
LIMIT $limit;
(note you can't ORDER BY ilv.ProfileYearOfBirth since it is not a selected column / group by expression)
How many records/columns do you have in prm_breedgender? Is it just Stallions, Mares, Geldings...? Do you think this list is likely to change? Do you have ponies with multiple genders? I suspect that this domain would be better represented by an enum in the profiles table.
with the intention of being able to list a pony's Owner and Breeder,
Using the code you suggest, you'll only get returned instances where the owner and breeder are the same! You need to add a second instance of the contacts table with a different alias to get them all, e.g.
SELECT *
FROM (
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts ownerContact
ON profiles.ProfileOwnerID = ownerContact.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
) ilv LEFT JOIN contacts breederContact
ON ilv.ProfileBreederID = breederContact.ContactID
ORDER BY ilv.ProfileYearOfBirth ASC $limit