I clearly know that there are plenty of questions already asked on this topic but i couldn't optimize mine.
So below is my query:
select tc.id, tc.name tc, s.name state, d.name district,
count(distinct case when curdate() between b.starttime and b.endtime then b.id end) as active,
(case when count(distinct case when curdate() between b.starttime and b.endtime then b.id end) > 0 then 'active' when tc.status = 'Archived-I' then 'transitioned' when count(distinct case when curdate() between b.starttime and b.endtime then b.id end) = 0 and tc.status != 'Archived-I' then 'Idle' end ) as _status,
count(distinct(b.id)) as batches, sum(case when sb.status = 'active' then 1 else 0 end) as in_training, count(distinct case when sb.status = 'complete' then sb.student_id end) as trained,
count(distinct(sa.student_id)) as assessed, count(distinct(sp.student_id)) as placed
from training_centers tc left join batches b on b.training_center_id = tc.id
left join student_batches sb on b.id = sb.batch_id
left join student_assessments sa on sa.batch_id = b.id
left join student_placements sp on sp.batch_id = b.id
left join states s on s.id = tc.state_id
left join districts d on d.id = tc.district_id
where tc.implementing_agency_id = 28
group by tc.id
order by tc.name
Output of EXPLAIN is below:
id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra
1|SIMPLE|tc|ref|implementing agency|implementing agency|4|const|201|Using temporary; Using filesort
1|SIMPLE|b|ALL|NULL|NULL|NULL|NULL|11018|
1|SIMPLE|sb|ref|batch id|batch id|4|ministry_mis.b.id|10|
1|SIMPLE|sa|ref|batch|batch|4|ministry_mis.b.id|7|
1|SIMPLE|sp|ALL|NULL|NULL|NULL|NULL|78799|
1|SIMPLE|s|eq_ref|PRIMARY|PRIMARY|4|ministry_mis.tc.state_id|1|
1|SIMPLE|d|eq_ref|PRIMARY|PRIMARY|4|ministry_mis.tc.district_id|1|1|
I have already tried query caching and since i am using this query in a php application, i have tried memcache as well.
Please help me understand, how can i optimize my query or if something is wrong here.
Add an appropriate index on the students_placements table (aliased as sp in the query).
CREATE INDEX student_placements_IX1
ON student_placements (batch_id, student_id)
The EXPLAIN output is showing a full scan of that table, ("ALL"). With that index, we'd expect the EXPLAIN output to show a ref operation.
Also, on the batches table (aliased as b in the query) add an index like:
CREATE INDEX batches_IX1
ON batches (training_center_id, id, starttime, endtime)
If all of the columns referenced in the query for a table come from the index, the index is called a "covering index" for the query, and the the EXPLAIN output will show "Using index" in the Extra column.
If id is a UNIQUE or PRIMARY KEY on the training_centers table (aliased as tc in the questy), you might also consider changing the query to do this:
GROUP BY tc.name, tc.id
ORDER BY tc.name
A covering index on training_centers, may also be of benefit:
CREATE INDEX training_centers_IX1
ON training_centers (implementing_agency_id, name, id, state_id, district_id, status)
We'd need to look at the EXPLAIN after the indexes are added, and go from there. (Sometimes, MySQL can make use of an index to avoid a "Using filesort" operation, if an appropriate index is available.)
Related
I'm trying to query my items table and get items based on a search query but I want to exclude items that have a status of 2 or 0 from those results, 1 represents items that are open for bidding.
My query thus far is
SELECT a.item_id,a.auction_id as item_auction_id,a.item_code,a.name as item_name,a.description as item_desc,a.full_description as item_full_desc,a.image as item_image,a.donor as item_donor,a.cost as item_cost,a.value as item_value,a.reserve as item_reserve,a.bid_increment as item_bid_increment,a.buy_price as item_buy_price,a.status as item_status,a.published as item_published,a.paid as item_paid,a.pay_type as item_pay_type, a.transaction_id as item_transaction_id,a.is_raffle as item_is_raffle,a.raffle_price as item_raffle_price, a.raffle_qty as item_raffle_qty,a.live_auction as item_live_auction,a.active as item_active,a.refunded as item_refunded, MAX(b.bid_amt) as item_high_bid,b.user_id as item_high_bid_user_id,b.bid_time as item_high_bid_time, b.bid_id as item_high_bid_id,SUM(c.raffle_ticket_qty) as item_tickets_purchased, '0000,0000' as item_bid_history, 0 as watching
FROM text2bid_items as a
LEFT JOIN (SELECT user_id, item_id, MAX(bid_amt) as bid_amt, bid_time, bid_id FROM text2bid_bids GROUP BY bid_id) as b
ON a.item_id = b.item_id
LEFT JOIN text2bid_raffle_purchases as c
ON a.item_id = c.item_id
WHERE a.name LIKE '%$params[2]%'
OR a.description LIKE '%$params[2]%'
OR a.full_description LIKE '%$params[2]%'
AND a.status = 1
GROUP BY a.item_id
But items with status = 2 or 0 are still showing in my results. I'm sure it has something to do with the use of both OR and AND here but I'm not sure how to google-fu this to figure out how to do it, I've tried different combinations of () to see if I can get a difference precedence but maybe that's not the solution?
The results should be any items where the name, description, or full_description are like the search query but I want to exclude items that don't have a status equalling 1
The logical operators in your query are mixed. So its better to keep them in parenthesis so as to keep the query logic in tact.
I have updated the query accordingly, please check!
SELECT a.item_id,a.auction_id as item_auction_id,a.item_code,a.name as item_name,a.description as item_desc,a.full_description as item_full_desc,a.image as item_image,a.donor as item_donor,a.cost as item_cost,a.value as item_value,a.reserve as item_reserve,a.bid_increment as item_bid_increment,a.buy_price as item_buy_price,a.status as item_status,a.published as item_published,a.paid as item_paid,a.pay_type as item_pay_type, a.transaction_id as item_transaction_id,a.is_raffle as item_is_raffle,a.raffle_price as item_raffle_price, a.raffle_qty as item_raffle_qty,a.live_auction as item_live_auction,a.active as item_active,a.refunded as item_refunded, MAX(b.bid_amt) as item_high_bid,b.user_id as item_high_bid_user_id,b.bid_time as item_high_bid_time, b.bid_id as item_high_bid_id,SUM(c.raffle_ticket_qty) as item_tickets_purchased, '0000,0000' as item_bid_history, 0 as watching
FROM text2bid_items as a
LEFT JOIN (SELECT user_id, item_id, MAX(bid_amt) as bid_amt, bid_time, bid_id FROM text2bid_bids GROUP BY bid_id) as b
ON a.item_id = b.item_id
LEFT JOIN text2bid_raffle_purchases as c
ON a.item_id = c.item_id
WHERE (a.name LIKE '%$params[2]%'
OR a.description LIKE '%$params[2]%'
OR a.full_description LIKE '%$params[2]%')
AND a.status = 1
GROUP BY a.item_id
In my query I am listing all of the theater ticket sales and movie ticket sales of different customers. The issue I'm running into is that all of the '0' ticket sales, so those users who haven't boughten a theater ticket or movie ticket is not showing up.
Here's a picture for a visual aspect: table
I believe I need to be doing a union to return the users who haven't boughten any tickets. I just can't seem to figure this out.
Thanks in advance.
Here's my code so far:
select customer.hippcode, customer.LastName, customer.Firstname, customer.Email,
count(ticketdetails.eventtype) as 'Theater Tickets',
0 as 'Movie Tickets'
from customer
inner join ticketdetails on ticketdetails.hippcode = customer.hippcode
where ticketdetails.hippcode is not null
and ticketdetails.eventType ='T'
Group by Customer.hippcode
union
select customer.hippcode, customer.LastName, customer.Firstname, customer.Email,
0 as 'Theater Tickets', count(ticketdetails.eventtype) as 'Movie Tickets'
from customer
inner join ticketdetails on ticketdetails.hippcode = customer.hippcode
where ticketdetails.hippcode is not null
and ticketdetails.eventType ='M'
Group by Customer.hippcode
order by `theater tickets` + `movie tickets` desc;
select
customer.hippcode, customer.LastName, customer.Firstname, customer.Email,
sum(case when ticketdetails.eventtype = 'T' then 1 else 0 end) as TheaterTickets,
sum(case when ticketdetails.eventtype = 'M' then 1 else 0 end) as MovieTickets
from customer
inner join ticketdetails on ticketdetails.hippcode = customer.hippcode
where ticketdetails.hippcode is not null
and ticketdetails.eventType in ('T', 'M')
Group by customer.hippcode, customer.LastName, customer.Firstname, customer.Email
Order by 'TheaterTickets' + 'MovieTickets' desc
inner join => bring the line only if you got a record on both table.
I think you should use a LEFT JOIN somewhere to chose the master table
http://dev.mysql.com/doc/refman/5.7/en/join.html and
http://dev.mysql.com/doc/refman/5.7/en/left-join-optimization.html
I think the last query is the only one you want. A left join is appropriate, but you need to be careful about the where clause:
select c.hippcode, c.LastName, c.Firstname, c.Email,
sum(td.eventtype) as TheaterTickets,
sum(td.eventtype) as MovieTickets
from customer c left join
ticketdetails td
on td.hippcode = c.hippcode and
td.eventType in ('T', 'M')
Group by c.hippcode, c.LastName, c.Firstname, c.Email
Order by count(t.hippcode) desc;
Notes:
Table aliases make the query easier to read and write.
Conditions on ticketdetails go in the on clause, not the where clause.
The condition on td.hippcode is not null is unnecessary, because NULL will not match in the join (note: you might want to check for the customer column).
case is the standard way to do the conditional sum (and hence correct). However, MySQL offers a much simpler and intuitive syntax.
Your order by was doing nothing, because it was adding two strings (hence equivalent to order by 0. Don't use single quotes ever for column names, and you won't have a problem like that.
Here is my query which is taking 17.9397 sec time to get response:
SELECT allbar.iBarID AS iBarID,
allbar.vName AS vName,
allbar.tAddress AS tAddress,
allbar.tDescription AS tDescription,
(SELECT COUNT(*)
FROM tbl_post p
WHERE p.vBarIDs = allbar.iBarID) AS `total_post`,
allbar.bar_usbg AS bar_usbg,
allbar.bar_enhance AS bar_enhance,
(SELECT count(*)
FROM tbl_user
WHERE FIND_IN_SET(allbar.iBarID,vBarIDs)
AND (eType = 'Bartender'
OR eType = 'Bar Manager'
OR eType = 'Bar Owner')) AS countAss,
allbar.eStatus AS eStatus
FROM
(SELECT DISTINCT b.iBarID AS iBarID,
b.vName AS vName,
b.tAddress AS tAddress,
(CASE LENGTH(b.tDescription) WHEN 0 THEN '' WHEN LENGTH(b.tDescription) > 0
AND LENGTH(b.tDescription) < 50 THEN CONCAT(LEFT(b.tDescription, 50),'...') ELSE b.tDescription END) AS tDescription,
b.usbg AS bar_usbg,
b.enhance AS bar_enhance,
b.eStatus AS eStatus
FROM tbl_bar b,
tbl_user u
WHERE b.iBarID <> '-10') AS allbar
I have tried EXPLAIN, here is the result of that:
Can anyone explain me this EXPLAIN result?
You should totaly rewrite that query, it's complete nonsense.
In this part
(SELECT DISTINCT b.<whatever>
FROM tbl_bar b,
tbl_user u
WHERE b.iBarID <> '-10') AS allbar
what you're basically doing is connecting every row from table tbl_bar with every row from tbl_user. Then filter tbl_bar, and when everything is selected (maybe MySQL has to write everything in a temporary table before doing this) return the result set without duplicates. You don't ever want to do that. Especially when you don't even select anything from tbl_user. When there's a connection, specify it. If there's none, don't join those tables or create a connection. I don't know if or how your tables are connected, but it should look something like this:
(SELECT DISTINCT b.<whatever>
FROM tbl_bar b
JOIN tbl_user u ON b.user_id = u.id /*or whatever the connection is*/
WHERE b.iBarID <> '-10') AS allbar
Then you have this ugly subquery.
(SELECT COUNT(*)
FROM tbl_post p
WHERE p.vBarIDs = allbar.iBarID) AS `total_post`,
allbar.bar_usbg AS bar_usbg,
allbar.bar_enhance AS bar_enhance,
which is by the way dependent (see your explain output). Which means, that this subquery is executed for every row of your outer query (yes, the one with the cross join as discussed above). Instead of this subquery, join the table in the outer query and work with GROUP BY.
So far the query should look something like this:
SELECT
b.iBarID AS iBarID,
b.vName AS vName,
b.tAddress AS tAddress,
b.tDescription AS tDescription,
COUNT(*) AS `total_post`,
allbar.bar_usbg AS bar_usbg,
allbar.bar_enhance AS bar_enhance
FROM
tbl_bar b
JOIN tbl_user u ON b.user_id = u.id
JOIN tbl_post p ON p.vBarIDs = b.iBarID
WHERE b.iBarID <> '-10'
GROUP BY b.iBarID
(In fact, this is not really right. Rule is, every column in the SELECT clause should either be in the GROUP BY clause as well or have an aggregate function (like count() or max() applied to it. Otherwise a random row of each group is displayed. But this is just an example. You will have to work out the details.)
Now comes the worst part.
(SELECT count(*)
FROM tbl_user
WHERE FIND_IN_SET(allbar.iBarID,vBarIDs)
AND (eType = 'Bartender'
OR eType = 'Bar Manager'
OR eType = 'Bar Owner')) AS countAss,
allbar.eStatus AS eStatus
The use of FIND_IN_SET() suggests, that you're storing multiple values in one column. Again, you never ever want to do that. Please read this answer to Is storing a delimited list in a database column really that bad? and then redesign your database. I won't help you with this one, as this clearly is stuff for a separate question.
All this didn't really explain the EXPLAIN result. For this question, I would have to write a whole tutorial, which I won't do, since everything is in the manual, as always.
I have two tables: return, and return_details
Return is setup like so -
+ Return
- id
- orderNum
- startDate
- endDate
+ Return_Details
- id
- rid (Return.id)
- stage [this is essentially location]
- lastSeen [timeDate last seen)
I am attempting to find all returns that are "open" (where Return.endDate == null) and then the stage and lastSeen for each open Return.id.
The problem is I can't figure out how to find the last occurrence of Return.id in Return_Details. Currently I am able to find the correct lastSeen time using MAX but how do I grab the corresponding stage.
Here's the query I am using now -
SELECT r.so, rd.lastSeen, rd.stage, r.sotype, MAX(rd.lastSeen) as last
FROM repairs r
JOIN repair_details rd ON r.id = rd.rid
WHERE `enddate` IS NULL
GROUP BY r.so
ORDER BY lastSeen asc
Any assistance as to how this could be done with one query would be much appreciated. Thanks in advance!
This should work.
SELECT a.id, b.stage, b.lastSeen
FROM Return a
LEFT JOIN (SELECT rid, stage, MAX(lastSeen) AS lastSeen FROM Return_Details GROUP BY rid, stage) b ON b.rid = a.id
WHERE a.endDate IS NULL
Edit 1:
This method will find the MAX(id) for each rid, then get the details based off that MAX(id).
SELECT a.orderNum, c.stage, c.lastSeen
FROM repairs a
LEFT JOIN
(SELECT rid, MAX(id) AS id FROM repair_details
GROUP BY rid) b ON b.rid = a.id
LEFT JOIN
(SELECT id, stage, lastSeen FROM repair_details) c ON c.id = b.id
WHERE a.endDate IS NULL
SQL Fiddle
This is a bit beyond my skills and I had a lot of help from the good people at SO to get this far. What I need now is to put in a MATCH() ... AGAINST() but I don't know where to insert it?
The query I have is (and this is the short version):
SELECT
SQL_CALC_FOUND_ROWS i.idItems RowCount,
i.* Items,
# Create a JSON formatted field
CONCAT('{',GROUP_CONCAT('"',Attributes.key, '":"', CONVERT(Attributes.value,CHAR),'"'),'}') as Attributes,
IF (te.Key IS NULL,tp.Key,te.Key) as Type,
tc.Value Color,
l.* Location,
c.Name,
c.Mobile,
c.Mail
FROM
(SELECT ItemID, ats.Key, ats.Value FROM attributeStrings as ats
UNION ALL
SELECT ItemID, ati.Key, ati.Value FROM attributeIntegers as ati
) Attributes
JOIN Items i ON
i.idItems = Attributes.ItemID
AND CheckIn >= DATE_SUB('2011-02-16 00:00:00',INTERVAL 90 DAY)
AND CheckIn <= DATE_ADD('2011-02-16 23:59:59',INTERVAL 90 DAY)
AND Checkout IS NULL
LEFT JOIN Customers c ON c.idCustomers = i.CustomerID
LEFT JOIN attributeintegers atli ON atli.itemid = i.idItems AND atli.key = 'Location'
LEFT JOIN locations l ON l.id = atli.value
LEFT JOIN attributestrings atts ON atts.itemid = i.idItems AND atts.key = 'Type' LEFT
JOIN Lists tp ON tp.value = atts.value
LEFT JOIN attributestrings attes ON attes.itemid = i.idItems AND attes.key = 'Tech' LEFT
JOIN Lists te ON te.value = attes.value
LEFT JOIN attributeintegers atci ON atci.itemid = i.idItems AND atci.key = 'Color' LEFT
JOIN Strings tc ON tc.StringID = atci.value
GROUP BY Attributes.ItemID
ORDER BY CheckIn DESC
Now I need to get this statement in here somewhere
MATCH(attributestrings.Value) AGAINST("Nokia" IN BOOLEAN MODE)
As you can see there is a table called attributestrings and it has 3 columns: ItemID,*Key* and Value. I need to search the column Value for the words in the AGAINST() and only show results matching this and the other criterias such as the Date and Checkout above.
I tried to add the statement after the AND Checkout IS NULL like this:
AND Checkout IS NULL
AND MATCH(Attributes.Value) AGAINST("Nokia" IN BOOLEAN MODE)
I had to use the Attributes.Value instead of attributestrings because it didn't found the table. This only resulted in the CONCATENATED column Attributes only contained the value "Nokia", even if there where more to CONCATENATE.
I hope someone are willing to take on this challenge...
// Tank you.
[EDIT]
I tried to put in the WHERE before the GROUP as Tim Fultz sugested, but I get the error
Unknown column 'attributestrings.Value' in 'Where clause'
LEFT JOIN attributeintegers atci ON atci.itemid = i.idItems AND atci.key = 'Color' LEFT JOIN Strings tc ON tc.StringID = atci.value
WHERE MATCH(attributestrings.Value) AGAINST("Nokia Sony" IN BOOLEAN MODE)
GROUP BY Attributes.ItemID
Typically this is put in the Where clause:
WHERE MATCH(attributestrings.Value) AGAINST("Nokia" IN BOOLEAN MODE)
I think I came up with a solution...
I added another:
LEFT JOIN attributestrings fts ON fts.itemid = i.idItems AND MATCH(fts.Value) AGAINST("Nokia Sony" IN BOOLEAN MODE)
Just before the GROUP BY... and then included fts.Value ftsv in the main select statement. Now I could insert a HAVING ftsv IS NOT NULL between GROUP BY... and ORDER BY...
This gave me the result I wanted but the query starts to get a bit slow now...
There is a problem in your query when you are assigning table names with as use every where in you query the name you assigned. Like you gave attributestrings as ats now use ats everywhere and this will work.