How to minimize time in mySQL select query? - mysql

I have this query wherein I want to find out the sales for the current year and the sales for last year. I cannot make it into 2 separate queries since it has to be of the same item code. Meaning the item codes used in the sales for the current year must also be the item codes used for the sales last year.
The code below is working but it takes almost 8 to 9 minutes to fetch
select p.itemcode,
p.itemdescription,
( select round((SUM(SA.QUANTITY*P.SellingPrice)),2)
from sales s
join product p on s.itemcode=p.itemcode
where YEAR(s.date) = 2013
),
( select round((SUM(SA.QUANTITY * P.SellingPrice)),2)
from sales s
join product p on s.itemcode=p.itemcode
where YEAR(s.date) = 2012
)
from product p
join supplier s on p.suppliercode = s.suppliercode
join currency c on c.countrycode=s.countrycode
join country co on co.countrycode=c.countrycode
JOIN SALES SA ON SA.ITEMCODE=P.ITEMCODE
where c.countrycode = 'NZ'
group by p.itemcode
limit 10
Ideally the output should be
Itemcode Itemdescription SalesforCurrentYear SalesforLastYear
GS771516 BUBBLE PARTY MACHINE 1035300.00 2079300.00
GSNBC-025 X'MAS HOUSE 600612.25 1397163.25
GSNBC-031 BRANDENBURGER TOR 741010.75 1572207.25
Thanks!!

The query can be simplified by eliminating two joins:
select .......
.......
from product p
join supplier s on p.suppliercode = s.suppliercode
JOIN SALES SA ON SA.ITEMCODE=P.ITEMCODE
where s.countrycode = 'NZ'
group by p.itemcode
limit 10
Afterwards, two dependent subqueries in the select clause can be reduced to one outer join:
select p.itemcode,
p.itemdescription,
round((SUM( CASE WHEN YEAR(s.date) = 2013
THEN SA.QUANTITY*P.SellingPrice
ELSE 0 END
)),2) As Sum2013,
round((SUM( CASE WHEN YEAR(s.date) = 2012
THEN SA.QUANTITY * P.SellingPrice
ELSE 0 END
)),2) As Sum2012
from product p
join supplier s on p.suppliercode = s.suppliercode
LEFT JOIN SALES SA ON SA.ITEMCODE=P.ITEMCODE
where s.countrycode = 'NZ'
group by p.itemcode
limit 10
Please try this query and let us know how it will perform.

Follow any of these steps
1.You can parse your query.
2.Remove redundant statements.
3.Use inner join or outer join.

You've got sales three times in the same scope. I'd get rid of two of those, and that should help. Also, in terms of business logic, all of these tables seem mandatory for a sale. If that's true, you should use "inner join", for compatibility with standard SQL - even though it's the same in MySQL.

Related

Group Concat having giving weird results

I have this SQL query :
SELECT v.*, group_concat(distinct(vi.interest_id)) as interests, group_concat(distinct(vs.skill_id)) as skills, vc.date_from
FROM `vacancies` as v
LEFT JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
LEFT JOIN `vacancy_skills` as vs on v.vacancy_id = vs.vacancy_id
LEFT JOIN `vacancy_calendar` as vc on v.vacancy_id = vc.vacancy_id
WHERE v.vacancy_visibility_end_date >= CURDATE()
GROUP BY v.vacancy_id
Consider this query returning the following results (the 3 last columns are the ones discussed in this question):
vacancy_id,org_id,name,description,number_required,occupancy_kind,website,offer,logo,banner,address_country,address_city,address_postal_code,address_line_1,address_line_2,vacancy_visibility_start_date,vacancy_visibility_end_date,engagement,interests,skills,date_from
"2","1","test123","aze<sdgqswdfg","1","1","","blabla",NULL,"12049394_10208129537615226_4853636504350654671_n.jpg","Belgie","Brussel","1000","Brusselsestraat 15",NULL,"2016-09-02 00:00:00","2016-09-19 00:00:00","3","13,6,1","4,3","2016-09-13 00:00:00"
"3","1","blablabla","lkpjoip","1","2","","blabla",NULL,NULL,"Belgie","Antwerpen","2000","Antwerpsestraat 16",NULL,"2016-09-02 00:00:00","2016-09-29 00:00:00","3","28","7,8,5","2016-09-01 00:00:00"
"4","1","hahaha","14556dsf","1","3","","blabla",NULL,NULL,"Belgie","Mechelen","2800","Mechelsesteenweg 17",NULL,"2016-09-02 00:00:00","2016-09-28 00:00:00","3",NULL,NULL,"2016-09-26 00:00:00"
"5","1","omggg","45sdfdj5","1","1","","blabla",NULL,NULL,"Belgie","Gent","3000","Gentsesteenweg 18",NULL,"2016-09-02 00:00:00","2016-09-30 00:00:00","3","17,11","4,1","2016-09-19 00:00:00"
"6","1","this is a test","wauhiufdsq","1","2","","blabla",NULL,NULL,"Belgie","Luik","4000","Luikseweg 19",NULL,"2016-09-02 00:00:00","2016-09-30 00:00:00","3","19,17,22","6","2016-08-10 00:00:00"
Note that the vacancy interests and vacancy skills table can contain multiple records for a single vacancy. E.g. It could be that vacancy 3 has 3 rows with all different interest_id's. The group_concat solves my problem here.
So this query works fine as it should.
However, 2 problems I encountered are the following:
1) When I add a filter in HAVING on the interests by an ID this only returns me one row instead of the expected two rows.
SELECT v.*, group_concat(distinct(vi.interest_id)) as interests, group_concat(distinct(vs.skill_id)) as skills, vc.date_from
FROM `vacancies` as v
LEFT JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
LEFT JOIN `vacancy_skills` as vs on v.vacancy_id = vs.vacancy_id
LEFT JOIN `vacancy_calendar` as vc on v.vacancy_id = vc.vacancy_id
WHERE v.vacancy_visibility_end_date >= CURDATE()
GROUP BY v.vacancy_id
HAVING interests IN (17)
This returns me only one row. Namely record with vacanacy_id 5, while it should also obviously also return vacancy_id = 6.
The thing that is the weirdest to me is that if I do the exact same thing but for skills (HAVING skills IN (4)), this does return me multiple rows with the correct result.
2) When I want to filter on the date_from (together with the interests and skills in the HAVING, I do the following:
SELECT v.*, group_concat(distinct(vi.interest_id)) as interests, group_concat(distinct(vs.skill_id)) as skills, vc.date_from
FROM `vacancies` as v
LEFT JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
LEFT JOIN `vacancy_skills` as vs on v.vacancy_id = vs.vacancy_id
LEFT JOIN `vacancy_calendar` as vc on v.vacancy_id = vc.vacancy_id
WHERE v.vacancy_visibility_end_date >= CURDATE() AND date(vc.date_from) > '2016-09-10'
GROUP BY v.vacancy_id
HAVING skills IN (4)
This will only return me vacancy number 5, while obviously also vacancy number 2 has a date greater than 2016-09-10 (2016-09-13 00:00:00)....
What am i doing wrong here?
Your HAVING clauses are the wrong way to check for presence of a condition. Instead of using the concatenated value, just use:
HAVING MAX(vi.interest_id IN (17)) > 0
When you do:
HAVING interests IN (17)
Then you are comparing a string to a number. The string gets silently converted to a number. In this case, only the first element is converted. So, if the interests starts with "17," then it matches, otherwise it does not.
Also, note that your method of using distinct in group_concat() is fine, as long as there are not too many interests and skills. If there were 100 of each for a vacancy, then the intermediate result would have 10,000 rows -- and take longer to process. However, with just a handful of each, the method is fine.

Inner Join in ON statement

I am working on an assignment:
Compare the average daily revenue (as I define it in Teradata Week 5
Exercise Guide) of the store with the highest msa_income and the store
with the lowest median msa_income (according to the msa_income field).
In what city and state were these two stores, and which store had a
higher average daily revenue?
... and the answer key has a inner join in ON statement, which confused me a lot. I have only learnt Join in FROM. So I searched online about an inner join in ON statement, there was not much about it.
I am a new learner, so this question might be really basic. Thanks in advance for your patience!
The line I where I had a problem is: ON m.store=t.store JOIN strinfo s
SELECT SUM(store_rev. tot_sales)
SUM(store_rev.numdays) AS daily_average,
store_rev.msa_income as med_income,
store_rev.city, store_rev.state
FROM (SELECT COUNT (DISTINCT t.saledate) as numdays,
EXTRACT(YEAR from t.saledate) as s_year,
EXTRACT(MONTH from t.saledate) as s_month, t.store,
sum(t.amt) as tot_sales,
CASE
when extract(year from t.saledate) = 2005 AND extract(month from t.saledate) = 8 then 'exclude'
END as exclude_flag, m.msa_income, s.city, s.state
FROM trnsact t JOIN store_msa m
ON m.store=t.store JOIN strinfo s
ON t.store=s.store
WHERE t.stype = 'P' AND exclude_flag IS NULL
GROUP BY s_year, s_month, t.store, m.msa_income, s.city, s.state
HAVING numdays >= 20) as store_rev
WHERE store_rev.msa_income IN ((SELECT MAX(msa_income)
FROM store_msa),(SELECT MIN(msa_income) FROM store_msa))
GROUP BY med_income, store_rev.city, store_rev.state;
Perhaps it would be easier to follow if written like this:
FROM trnsact t JOIN
store_msa m
ON m.store = t.store JOIN
strinfo s
ON t.store = s.store
The JOIN isn't in the ON statement. The FROM clause consists of multiple joins, chained together.
You can think of the JOIN keyword as ending the ON clause and starting a new join condition.

Return the minimum of single field grouping in wider query

Here is a query:
select
e.eid,
e.event_time as contact_created,
cs.name as initial_class,
cn.create_time as lead_date
from bm_sets.event378 e
inner join bm_config.classes cs on cs.id = e.class_id and cs.cid=378 # and cs.name = 'Candidate'
left join bm_sets.conversion378 cn on cn.eid = e.eid and cn.create_time > e.event_time
where e.eid = 283818
group by eid, contact_created, initial_class, lead_date
The results of this query look like this:
eid, contact_created, initial_class, lead_date
283818 2015-03-07 09:43:42 Hot
283818 2015-03-10 22:19:47 Candidate
283818 2015-03-10 22:22:11 Candidate
I need to adjust this query so that only the first record is returned, the one with the min contact_created date. But since I'm using an aggregate function with other fields, I'm grouping by initial_class too so min is the min based on the combined groupings.
Our server seems to struggle whenever I use a subquery. So I tried using another join as a filter, something like:
inner join bm_sets.event378 e1 on e1.eid = e.eid and e1.event_time < e.event_time
But I know before running it that this won't work since the eid (user id) 283818 will still be returned and thus all associated data.
How can I restrict the results to only those records that correspond to the minimum of event_time?
I am using the where condition 283818 (my own user id for debugging) only as a sanity check as I construct this query. The query, when ready, will not have this condition and the results will thus be for many users.
If you need only top 1 then you can call just
select top 1
or you need make a function that returns you minimum created_contact then you just compare it with current created_contact.
I hope that will help you
Thanks
OK I got it. I used a null left join like so (on the back of other SO posts on the topic of groupwise min/max):
added this to selector:
e1.event_time
added this to joins:
left join bm_sets.event378 e1 on e1.eid = e.eid and e1.event_time < e.event_time
added this to where clause:
and e1.event_time is null

Using multiple SUMs in a Single SELECT like a foreach loop or CASE

I have this MySQL query that returns a break down of sales of a certain Product_ID.
I'd like to expand this query to include multiple Product_IDs.
Heres what I have that gives me 1 Product_ID:
SELECT
o.Customer_ID, o.CompanyName, cg.Category, o.Order_ID,
SUM(old.LineTotal) as TOTAL,
SUM(old.qty) as PID,
COUNT(o.Order_ID) as OIDs
FROM Orders o
LEFT JOIN Order_LineDetails old ON old.Order_ID = o.Order_ID
LEFT JOIN CustomerDetails cd ON o.Customer_ID = cd.Customer_ID
LEFT JOIN _CustomerCategory cg ON cg.Category_ID = cd.Category_ID
WHERE o.IsPENDING = 0
AND o.OrderPlaceServerTime >= '2012-1-1'
AND o.OrderPlaceServerTime <= '2012-9-1'
AND o.IsVOID = 0
AND old.Product_ID = '56789'
GROUP BY o.Customer_ID
ORDER BY TOTAL DESC
LIMIT 10
I'm trying to add somthing to do more than 1 Product_ID and get the SUMs of them as well.
This is the idea I was looking at, but obviosley this does not work.
(CASE WHEN old.Product_ID = '56789'
SUM(old.LineTotal) as TOTAL_56789,
SUM(old.qty) as PID_56789)
This would repeat based on how many products I was after.
Hopfully this makes sense.
Any suggestions?
EDIT::
Here's an example of what I'd like to get back.
Take a look at this
Basically you want pivot tables, but mysql does not support it. You can force it to some degree writing your queries dinamically in your client application. If you have more experience with stored procedures, you can try the automatic version .

sql server how to display data which is in the month of august?

hello guys i have a small problem with my sql code
i have to only display data in the database which is in the month of august 2008.
my code so far is this
i am supposed to show the sales of each sales person in the month of august 2008
SELECT p.BusinessEntityID,p.FirstName,p.LastName, ROUND(SUM(soh.TotalDue),2) AS 'Total Attributed Sales'
FROM Person.Person p , Sales.SalesPerson s,Sales.SalesOrderHeader soh
WHERE p.BusinessEntityID = s.BusinessEntityID
AND p.BusinessEntityID = soh.SalesPersonID
AND soh.OrderDate >= '2008' AND soh.OrderDate < '2009'
GROUP BY p.BusinessEntityID,P.FirstName,P.LastName
ORDER BY p.LastName
i have the image of my database
http://www.2shared.com/photo/h2-jMQyP/AdventureWorks2008.html
You can try this:
SELECT DISTINCT p.BusinessEntityID,p.FirstName,p.LastName
FROM Person.Person p INNER JOIN Sales.SalesPerson s
ON p.BusinessEntityID = s.BusinessEntityID
WHERE MONTH(p.your_date) = 8
AND YEAR(p.your_date) = 2008
Just a note: when you have to join tables use JOIN statement in place of WHERE; sql engine here understands you're joining tables and convert execution plan to use a JOIN, but in other situations you risk doing tables cartesian product, making your query really huge!
Looking at your database structure, if you want to get the sales for a particular person you will probably have to add a link into SalesOrderHeader as well
EDIT 2: A slight modification to the script will get the total ammount due for all the sales for all sales people in august 2008
SELECT p.BusinessEntityID,p.FirstName,p.LastName, SUM(soh.TotalDue)
FROM Person.Person p , Sales.SalesPerson s,Sales.SalesOrderHeader soh
WHERE p.BusinessEntityID = s.BusinessEntityID
AND s.BusinessEntityID = soh.BusinessEntityID
AND MONTH(soh.OrderDate) = 8
AND YEAR(soh.OrderDate) = 2008
GROUP BY p.BusinessEntityID