Counting rows in a Query containing Join - mysql

I have a query that goes like this:
SELECT Product.local_price*Rate.exchange_rate AS 'US_price' FROM Product
INNER JOIN Rate ON Rate.currency = Product.currency
WHERE Product.type='TV'
HAVING US_price BETWEEN 500 AND 600;
How do I do a count on the number of TV sets that satisfy this query?
Table structure
Product Table: ID, type, local_price
Rate Table: currency, exchange_rate

Replace the HAVING US_price with AND Product.local_price * Rate.exchange_rate and just do a COUNT(Product.ID) in the SELECT clause:
SELECT COUNT(Product.ID)
FROM Product
INNER JOIN Rate ON Rate.currency = Product.currency
WHERE Product.type='TV'
AND Product.local_price * Rate.exchange_rate BETWEEN 500 AND 600;
You would want to use a HAVING if you wanted criteria on aggregated data, like this:
SELECT p.type, AVG(p.local_price)
FROM Product p
GROUP BY p.type
HAVING AVG(p.local_price) > 50

There's no need to use a HAVING clause here; its special semantics are only relevant when you have a GROUP BY clause. So, we can simply replace US_price in the HAVING clause with the expression that generates it, and move it into the WHERE clause; and then, use SELECT COUNT(*):
SELECT COUNT(*)
FROM Product
JOIN Rate
ON Rate.currency = Product.currency
WHERE Product.type = 'TV'
AND Product.US_price * Rate.exchange_rate BETWEEN 500 AND 600
;
Also, as a general rule — not needed in this case — you can always (or almost always?) wrap your entire query in SELECT COUNT(*) FROM (...) t to get the total number of rows it returns.

Related

Why does a MySQL query with a Dependent Subquery take so much longer to execute than executing each statement individually?

I'm running two queries.
The first one gets unique IDs. This executes in ~350ms.
select parent_id
from duns_match_sealed_air_072815
group by duns_number
Then I paste those IDs into this second query. With >10k ids pasted in, it also executes in about ~350ms.
select term, count(*) as count
from companies, business_types, business_types_to_companies
where
business_types.id = business_types_to_companies.term_id
and companies.id = business_types_to_companies.company_id
and raw_score > 25
and diversity = 1
and company_id in (paste,ten,thousand,ids,here)
group by term
order by count desc;
When I combine these queries into one it takes a long time to execute. I don't know how long because I stopped it after minutes.
select term, count(*) as count
from companies, business_types, business_types_to_companies
where
business_types.id = business_types_to_companies.term_id
and companies.id = business_types_to_companies.company_id
and raw_score > 25
and diversity = 1
and company_id in (
select parent_id
from duns_match_sealed_air_072815
group by duns_number
)
group by term
order by count desc;
What is going on?
It's down to the way it processes the query - I believe it has to run your embedded query once for each row, whereas using two queries allows you to store the result.
Hope this helps!
The query has been re-written using JOIN, but particularly I've used EXISTS instead of IN. This is a short in the dark. It is possible that there may be many values generated in the sub-query causing the outer query to struggle while it goes through matching each item returned from the sub-query.
select term, count(*) as count
from companies c
inner join business_types_to_companies bc on bc.company_id = c.id
inner join business_types b on b.id = bc.term_id
where
raw_score > 25
and diversity = 1
and exists (
select 1
from duns_match_sealed_air_072815
where parent_id = c.id
)
group by term
order by count desc;
First, with respect, your subquery doesn't use GROUP BY in a sensible way.
select parent_id /* wrong GROUP BY */
from duns_match_sealed_air_072815
group by duns_number
In fact, it misuses the pernicious MySQL extension to GROUP BY. Read this. http://dev.mysql.com/doc/refman/5.6/en/group-by-handling.html . I can't tell what your application logic intends from this query, but I can tell you that it actually returns an unpredictably selected parent_id value associated with each distinct duns_number value.
Do you want
select MIN(parent_id) parent_id
from duns_match_sealed_air_072815
group by duns_number
or something like that? That one selects the lowest parent ID associated with each given number.
Sometimes MySQL has a hard time optimizing the WHERE .... IN () query pattern. Try a join instead. Like this:
select term, count(*) as count
from companies
join (
select MIN(parent_id) parent_id
from duns_match_sealed_air_072815
group by duns_number
) idlist ON companies.id = idlist.parent_id
join business_types_to_companies ON companies.id = business_types_to_companies.company_id
join business_types ON business_types.id = business_types_to_companies.term_id
where raw_score > 25
and diversity = 1
group by term
order by count desc
To optimize this further we'll need to see the table definitions and the output from EXPLAIN.

Mysql query get results if fields aren't equal

I have a query like this:
select a.*, ag.Winstpercentage from Artikels a
inner join Artikelgroep ag on a.`Artgroep`=ag.Groepcode
where a.`Manuf_nr` in (some array)
In this query I have price field. I need to compare Manuf_nr for all these fields, and if it's same I need to check that price fields are not equal for rows who have same Manuf_nr. Anyone knows how to do this?
UPDATE:
field price is in Artikels table, so I select it under a.*
I changed my query so it looks like this
SELECT *
FROM `Artikels`
inner join (select * from Artikels) as totals
on Artikels.`Manuf_nr` = totals.`Manuf_nr` and
Artikels.`Vprijsexcl`!= totals.`Vprijsexcl`
where Artikels.`Manuf_nr` in
(select Manuf_nr from Artikels
group by Manuf_nr having count(*) >1)
but it takes too long. Anyone knows how to speed it up?
UPDATE: field price is field Vprijsexcl
This is what I have:
my table
And I need to get all data where Manuf_nr is equal and Vprijsexcl are not equal.
Try:
SELECT *
FROM `Artikels`
inner join Artikels as totals
on Artikels.`Manuf_nr` = totals.`Manuf_nr` and
Artikels.`Vprijsexcl`!= totals.`Vprijsexcl`
- the where ... in (subquery) clause is redundant, because the main query can only return results from manufacturers that have more than one row in the Artikels table.
UPDATED: To see only the differing prices for the same manufacturer, try:
SELECT `Manuf_nr`, group_concat(distinct `Vprijsexcl`) prices
FROM `Artikels`
GROUP BY `Manuf_nr`
HAVING count(distinct `Vprijsexcl`) > 1
What about
SELECT Manuf_nr, Vprijsexcl, COUNT (*)
FROM Artikels
GROUP BY Manuf_nr, Vprijsexcl
HAVING COUNT (*) > 1
which lists the Manuf_nr, Vprijsexcl combinations that are not unique.
EDIT:
To get all information about the products which have a distinct Vprijsexcl for their Manuf_nr:
SELECT Artikels.*
FROM Artikels
INNER JOIN (SELECT Manuf_nr, Vprijsexcl, COUNT (*)
FROM Artikels
GROUP BY Manuf_nr, Vprijsexcl
HAVING COUNT (*) = 1) AS A
USING (Manuf_nr, Vprijsexcl)

Merge two rows into one

When i run the below written procedure, the returned result set which i get is like
But Actually according to the scenario, what i want is that I want a single record against the #ContractId parameter. So, I want to merge the rows which my result set returns.
PS: This image shows only few columns, there also exists some other columns which have different values.
This is My Procedure:
ALTER PROCEDURE [dbo].[sp_Tbl_Contract_SearchOne]
-- Add the parameters for the stored procedure here
#ContractID int
AS
BEGIN
select
tbl_Contract.ContractID,
KeyWinCountNumber,
ItemName,
BrandName,
CountName,
SellerName,
BuyerName,
ContractNumber,
ContractDate,
CountryFromName,
CountryToName,
TotalQty,
Vans,
UnitPrice,
Amount
from tbl_Contract
inner join tbl_CountDetail
on
tbl_CountDetail.ContractID = Tbl_Contract.ContractID
inner join tbl_Count tcount
on
tcount.CountID = tbl_CountDetail.CountID
INNER JOIN Tbl_Item
on Tbl_Contract.ItemID = Tbl_Item.ItemID
INNER JOIN Tbl_Brand
on Tbl_Contract.BrandID = Tbl_Brand.BrandID
INNER JOIN Tbl_Seller
on Tbl_Contract.SellerID = Tbl_Seller.SellerID
INNER JOIN Tbl_Buyer
on Tbl_Contract.BuyerID = Tbl_Buyer.BuyerID
INNER JOIN Tbl_CountryFrom
ON Tbl_Contract.CountryFromID=Tbl_CountryFrom.CountryFromID
INNER JOIN Tbl_CountryTo
ON Tbl_Contract.CountryToID = Tbl_CountryTo.CountryToID
inner join tbl_CostUnit
on Tbl_Contract.CostUnitID = tbl_CostUnit.CostUnitID
where Tbl_Contract.ContractID = 1
and Tbl_Contract.IsDeleted = 0 and tbl_CountDetail.IsDeleted = 0
END
It depends what you want to do with the CountName field (the only value that differs between the two) but in theory you could just put it through an aggregation using GROUP BY (If you excluded CountName) or if you wanted to include CountName then maybe PIVOT would do the job.
This falls under aggregation, quite often aggregation means doing an operation (sum, average, standard deviation) on th rows you want to compress into a single row. For example, if your data consisted off number of cookie sales per person per day:
day person sales
======================
1 Bob 5
1 Jane 8
2 Bob 2
2 Jane 10
And you wanted to see over all days what the total sales per person is, you would select person and the sum(sales) grouping by the person
select
person
sum(sales)
from salesData
group by person
Your case is somewhat less standard, in that you are trying to aggregate a filed which is character-based, or alphanumeric. This is fine, sort of, in that there are some aggregations which will work with a character-field. MIN will still work, as wil MAX - returning the first and last field respectively.
ie, doing a min over the set a,b,c will return a as it is first (Minimum ordered by string ordering rules). You seem to have some other numeric fields (Amount,UnitPrice,TotalQty) - these you can pick the correct aggregation for - I suspect SUM is most likely
So you could do this:
select
tbl_Contract.ContractID,
KeyWinCountNumber,
ItemName,
BrandName,
MIN(CountName) as FirstCountName,
SellerName,
BuyerName,
ContractNumber,
ContractDate,
CountryFromName,
CountryToName,
SUM(TotalQty) AS SumTotalQuantity,
Vans,
SUM(UnitPrice) as TotalUnitPrice,
SUM(Amount) AS TotalAmount
from tbl_Contract
[...snip...]
group by tbl_Contract.ContractID,
KeyWinCountNumber,
ItemName,
BrandName,
SellerName,
BuyerName,
ContractNumber,
ContractDate,
CountryFromName,
CountryToName,
Vans
This will now return 1 row, where FirstCountName has the value Count1 502 as this is the first (min) value from the aggregated fields.

How to count rows from one table using multiple tables in FROM clause and some search conditions in WHERE clause?

I have a QUERY which is like
SELECT COUNT(*) as cnt
FROM tbl_docatrtypegroupdoctype,
tbl_doctype,
tbl_docatrtypegroup
WHERE 1=1
AND
(tbl_doctype.doctype_name like '%Payment%'
OR tbl_doctype.doctype_name like'% Payment'
OR tbl_doctype.doctype_name like ' Payment%' )
LIMIT 1
Now in the above query I need to count the number of records in table "tbl_docatrtypegroupdoctype" under the conditons given in where clause, whenever i execute the query, I get 77 count, but actual count in DB is 12.
What could be the problem with this query and how can I rectify it?
Ant help will be appriciated
Thanks
You need to specify your join conditions. What happens if you don't is a cross product which is not what you want.
SELECT COUNT(*) as cnt
FROM tbl_docatrtypegroupdoctype JOIN
tbl_doctype on (THE CONDITION) JOIN
tbl_docatrtypegroup on (THE CONDITION)
Alternatively the JOIN conditions can be spefified in the WHERE clause.
In the where clause:
WHERE table1.field1 = table2.field2 AND table2.field3 = table3.field4
The fields that you join on must be semantically related in some way of course.
You need to apply all join-conditions between the three tables.

How do I limit the result of a subquery in MySQL?

Is there a way of limiting the result of a subquery? The sort of thing I'm trying to achieve can be explained by the query below:
SELECT *
FROM product p
JOIN (
SELECT price
FROM supplierPrices sp
ORDER BY price ASC
LIMIT 1
) ON (p.product_id = sp.product_id)
The idea would be to get only the lowest price for a particular product from a table that had all the price data in it. LIMIT 1 is limiting the entire result set, whereas excluding it would result in a row being returned for each price, with duplicated product data. I tried GROUP BY price as well to no avail.
Once the limit is working I need to apply IFNULL as well, so that if there is no price found at all for any supplier it can return a supplied string, such as "n/a" rather than NULL. I assume that would just mean modifying the SELECT as below, and changing the JOIN to a LEFT JOIN?
SELECT *, IFNULL(price,'n/a')
Just to expand on Wolfy's answer slightly, and bearing in mind this is untested:
SELECT *
FROM product p
LEFT JOIN (
SELECT product_id, MIN(price)
FROM supplierPrices sp
GROUP BY product_id
) x ON (p.product_id = x.product_id)
And, as you say, it should just be a matter of doing an IFNULL on that column to replace it with something sensible.