I have this MySQL query to get the total amount of only the first invoice for each client on a given month:
SELECT SUM(InvoiceProductTotal)
FROM tblinvoiceproduct
WHERE InvoiceID IN (
SELECT MIN(tblinvoice.InvoiceID) AS InvoiceID
FROM tblinvoice
WHERE tblinvoice.ClientID IN (
SELECT tblclient.ClientID
FROM tblclient
LEFT JOIN tblenquiry ON tblclient.EnquiryID = tblenquiry.EnquiryID
WHERE NOT tblclient.EnquiryID IS NULL
AND YEAR(EnquiryDate) = 2014
AND MONTH(EnquiryDate) = 9
)
GROUP BY tblinvoice.ClientID
);
When I run it, it seems to loop forever. If I remove the first part it gives me the list of invoices instantly. Am sure it is a small syntax detail but haven't been able to figure out what the problem is after nearly one hour trying to fix it.
Your assistance is appreciated.
This query can probably be done in a better way without all the sub queries as well, just I'm not so experienced with sub queries. :)
Solution was given but I should have included the full query rather than just the part I was having trouble with. The full query is:
SELECT AdvertisingID, AdvertisingTitle, AdvertisingYear,
AdvertisingMonth, AdvertisingTotal, AdvertisingVisitors,
IFNULL(
(SELECT SUM(InvoiceProductTotal)
FROM tblinvoiceproduct
JOIN
(SELECT MIN(tblinvoice.InvoiceID) AS InvoiceID
FROM tblinvoice
JOIN
(SELECT DISTINCT tblclient.ClientID
FROM tblclient
JOIN tblenquiry ON tblclient.EnquiryID = tblenquiry.EnquiryID
WHERE YEAR(tblenquiry.EnquiryDate)=tbladvertising.AdvertisingYear
AND MONTH(tblenquiry.EnquiryDate)=tbladvertising.AdvertisingMonth)
AS inq
ON tblinvoice.ClientID = inq.ClientID
GROUP BY tblinvoice.ClientID) AS inq2
ON tblinvoiceproduct.InvoiceID = inq2.InvoiceID)
, 0)
FROM tbladvertising
ORDER BY AdvertisingYear DESC, AdvertisingMonth DESC, AdvertisingTitle;
Now the problem is that the column with the sub query has no access to "tbladvertising.AdvertisingYear" or "tbladvertising.AdvertisingMonth"
A commenter mentioned that it's hard to understand what you're trying to do here. I agree. But I will take the risk of trying to puzzle it out.
As usual with this sort of query, it's helpful to take advantage of the structured part of structured query language, and try to build this up piece by piece. That's the secret to creating complex queries that actually do what you want them to do.
Your innermost query is this:
SELECT tblclient.ClientID
FROM tblclient
LEFT JOIN tblenquiry ON tblclient.EnquiryID = tblenquiry.EnquiryID
WHERE NOT tblclient.EnquiryID IS NULL
AND YEAR(EnquiryDate) = 2014
AND MONTH(EnquiryDate) = 9
It is saying, "give me the list of ClientID values which have enquiries in September 2014. There's a more efficient way to do this:
SELECT DISTINCT tblclient.ClientID
FROM tblclient
JOIN tblenquiry ON tblclient.EnquiryID = tblenquiry.EnquiryID
WHERE tblenquiry.EnquiryDate >= '2014-09-01'
AND tblenquiry.EnquiryDate < '2014-09-01' + INTERVAL 1 MONTH
Two changes here: First, the NOT ... IS NULL search is unnecessary because if the item you're searching on is null, there's no way for your EnquiryDate to be valid. So we just change the LEFT JOIN to an ordinary inner JOIN and get rid of the otherwise expensive NULL scan.
Second, we recast the date matching as a range scan, so it can use an index on tbl.EnquiryDate.
Cool.
Next, we have this query level.
SELECT MIN(tblinvoice.InvoiceID) AS InvoiceID
FROM tblinvoice
WHERE tblinvoice.ClientID IN (
/* that list of Client IDs from the innermost query */
)
GROUP BY tblinvoice.ClientID
That is pretty straightforward. But MySQL isn't too swift with IN () clauses, so let's recast it in the form of a JOIN as follows:
SELECT MIN(tblinvoice.InvoiceID) AS InvoiceID
FROM tblinvoice
JOIN (
/* that list of Client IDs from the innermost query */
) AS inq ON tblinvoice.ClientID = inq.ClientID
GROUP BY tblinvoice.ClientID
This gets us the list of invoice IDs which were the subject of the first enquiry of the month on behalf of each distinct ClientID. (It's hard for me to figure out the business meaning of this, but I don't understand your business.)
Finally, we come to your outermost query. We can also recast that as a JOIN, like so.
SELECT SUM(InvoiceProductTotal)
FROM tblinvoiceproduct
JOIN (
/* that list of first-in-month invoices */
) AS inq2 ON tblinvoiceproduct.InvoiceID = inq2.InvoiceID
So, this all expands to:
SELECT SUM(InvoiceProductTotal)
FROM tblinvoiceproduct
JOIN (
SELECT MIN(tblinvoice.InvoiceID) AS InvoiceID
FROM tblinvoice
JOIN (
SELECT DISTINCT tblclient.ClientID
FROM tblclient
JOIN tblenquiry ON tblclient.EnquiryID = tblenquiry.EnquiryID
WHERE tblenquiry.EnquiryDate >= '2014-09-01'
AND tblenquiry.EnquiryDate < '2014-09-01' + INTERVAL 1 MONTH
) AS inq ON tblinvoice.ClientID = inq.ClientID
GROUP BY tblinvoice.ClientID
) AS inq2 ON tblinvoiceproduct.InvoiceID = inq2.InvoiceID
That should do the trick for you. In summary, the big optimizing changes are
using a date range scan.
eliminating the NOT ... IS NULL criterion.
recasting your IN clauses as JOIN clauses.
The next step will be to create useful indexes. A compound index (EnquiryDate, EnquiryID) on your tblenquiry is very likely to help a lot. But to be sure you'll need to do some EXPLAIN analysis.
What if you modify your above posted query, to replace the subquery with JOIN (INNER JOIN) like below. Give it a try.
SELECT SUM(InvoiceProductTotal)
FROM tblinvoiceproduct
JOIN
(
SELECT MIN(ti.InvoiceID) as MinInvoice
FROM tblinvoice ti
JOIN
(
SELECT tblclient.ClientID
FROM tblclient
LEFT JOIN tblenquiry
ON tblclient.EnquiryID = tblenquiry.EnquiryID
WHERE NOT tblclient.EnquiryID IS NULL
AND YEAR(EnquiryDate) = 2014
AND MONTH(EnquiryDate) = 9
) tab
on ti.ClientID = tab.ClientID
GROUP BY ti.ClientID
) tab1
on tblinvoiceproduct.InvoiceID = tab1.MinInvoice
Related
have read all similar qs but cant apply to my sql
Would like to select all customers AND the last action record inserted.
The sql below first selects the max actionid then uses that on another sub query - this takes 5+secs to run ;(
Please advise TQ
SELECT cus.cusid,cus.FirstName,cus.Surname,
lastact.actionid, lastact.actiondate, lastact.siteid
FROM cus
LEFT JOIN(
SELECT MAX(actionid) AS maxactionid, cusid
FROM `action`
INNER JOIN `event` ON event.eventid = action.`eventid`
GROUP BY cusid
) AS maxactionid ON maxactionid.cusid = cus.cusid
LEFT JOIN (
SELECT
action.actionid,
action.actiondate,
event.cusid,
event.siteid
FROM
`action`
INNER JOIN `event`
ON event.eventid = action.eventid
ORDER BY actionid DESC
) AS lastact ON lastact.actionid = maxactionid
WHERE UCASE(CONCAT(firstname, surname)) LIKE '%JIM%HEMM%'
TQ for ideas - please see following:
1) the limit idea, provides null results for lastact.actionid, lastact.actiondate, lastact.siteid - but does run in 0.075 secs!
Such a shame this idea fails
SELECT cus.cusid,cus.FirstName,cus.Surname, lastact.actionid, lastact.actiondate, lastact.siteid
FROM cus
LEFT JOIN (SELECT action.actionid, action.actiondate, event.cusid, event.siteid
FROM action
INNER JOIN event ON event.eventid = action.eventid
ORDER BY actionid DESC LIMIT 1
) AS lastact ON lastact.cusid = cus.cusid
WHERE UCASE(CONCAT(firstname, surname)) LIKE '%JIM%HEMM%'
2) EXPLAIN results of original query are:
3) Adding LIKE 'JIM%' AND cus.surname LIKE 'HEMM%' doesn't affect query time much but will include as per suggestion
Hi - have got great result by using ideas from everyone - Thank you
1) Changed WHERE to cus.FirstName LIKE 'JIM%' AND cus.surname LIKE 'H%'
2) Added index on firstname, surname
3) Added cusid in action table (don't need event table anymore)
4) Moved lookup tables (not in orig question) outside of action sub query
Finished sql looks like (runs in 0.063 secs - tested with a surname of only one letter!)
SELECT cus.cusid,cus.FirstName,cus.Surname, lastact.actionid, lastact.actiondate, lastact.siteid, actiontype.action,
FROM cus
LEFT JOIN (
SELECT action.actionid, action.actiondate, event.cusid, event.siteid
FROM action
ORDER BY actionid DESC
) AS lastact ON lastact.cusid = cus.cusid
LEFT JOIN actiontype ON actiontype.actiontypeid = lastact.typeid
WHERE cus.FirstName LIKE 'JIM%' AND cus.surname LIKE 'H%'
GROUP BY lastact.cusid
As JC Sama said "change select MAX(actionid) by select actionid and adding a limit 1 and order by desc", helps indexed
searchs.
As David K-J said "run an EXPLAIN first to see what the planner is trying to do. I would suspect it's the (non-indexable) search on concatenation strings".
You shouldn't put jokers '%' at the begining of a string when comparing, that disables indexed search.
You shouldn't use functions when comparing (at least, avoid them if you can), also for the indexed search.
Now that you can use indexes, add them if you haven't done it yet.
I may be wrong here, but I don't see the point of the last LEFT JOIN, as far as I'm concerned. You could withdraw that data from the first LEFT JOIN. Neither why are you grouping by cusid.
With all, the sql I made is (obviously not tested, you may have to fix some thing):
SELECT cus.cusid,cus.FirstName,cus.Surname,
maxaction.actionid, maxaction.actiondate, maxaction.siteid
FROM cus
LEFT JOIN(
SELECT actionid AS maxaction, action.actiondate, event.cusid, event.siteid
FROM `action`
INNER JOIN `event` ON event.eventid = action.eventid
order by actionid desc limit 1
) AS maxaction ON maxaction.cusid = cus.cusid
WHERE cus.FirstName like 'JIM%' and cus.surname like 'HEMM%'
I am having trouble getting a MySQL query to work for me. Here is the setup.
A customer has asked me to compile a report from some accounting data. He wants to select a date (and possibly other criteria) and have it return all of the following (an OR statement):
1.) All invoices that were inserted on or after that date
2.) All invoices regardless of their insert date that have corresponding payments in a separate table whose insert dates are on or after the selected date.
The first clause is basic, but I am having trouble pairing it with the second.
I have assembled a comparable set of test data in an SQL Fiddle. The query that I currently have is provided.
http://www.sqlfiddle.com/#!2/d8d9c/3/2
As noted in the comments of the fiddle, I am working with July 1, 2013 as my selected date. For the test to work, I need invoices 1 through 5 to appear, but not invoice #6.
Try this: http://www.sqlfiddle.com/#!2/d8d9c/9
Here are the summarized changes
I got rid of your GROUP BY. You did not have any aggregate functions. I used DISTINCT instead to eliminate duplicate records
I removed your implicit joins and put explicit joins in their place for readability. Then I changed them to LEFT JOINs. I am not sure what your data looks like but at a minimum, I would assume you need the payments LEFT JOINed if you want to select an invoice that has no payments.
This will probably get you the records you want, but those subselects in the SELECT clause may perform better as LEFT JOINs then using the SUM function
Here is the query
SELECT DISTINCT
a.abbr landowner,
CONCAT(f.ForestLabel, '-', l.serial, '-', l.revision) leasenumber,
i.iid,
FROM_UNIXTIME(i.dateadded,'%M %d, %Y') InvoiceDate,
(SELECT IFNULL(SUM(ch.amount), 0.00) n FROM test_charges ch WHERE ch.invoiceid = i.iid) totalBilled,
(SELECT SUM(p1.amount) n FROM test_payments p1 WHERE p1.invoiceid = i.iid AND p1.transtype = 'check' AND p1.status = 2) checks,
(SELECT SUM(p1.amount) n FROM test_payments p1 WHERE p1.invoiceid = i.iid AND p1.transtype = 'ach' AND p1.status = 2) ach,
CASE WHEN i.totalbilled < 0 THEN i.totalbilled * -1 ELSE 0.00 END credits,
CASE WHEN i.balance >= 0 THEN i.balance ELSE 0.00 END balance,
t.typelabel, g.groupname
FROM test_invoices i
LEFT JOIN test_contracts c
ON i.contractid = c.cid
LEFT JOIN test_leases l
ON c.leaseid = l.bid
LEFT JOIN test_forest f
ON l.forest = f.ForestID
LEFT JOIN test_leasetypes t
ON l.leasetype = t.tid
LEFT JOIN test_accounts a
ON l.account = a.aid
LEFT JOIN test_groups g
ON c.groupid = g.gid
LEFT JOIN test_payments p
ON p.invoiceid = i.iid
WHERE (i.dateadded >= #startdate) OR (p.dateadded >= #startdate)
Try this.
http://www.sqlfiddle.com/#!2/d8d9c/11/2
TL;DR:
… AND (i.dateadded > #startdate
OR EXISTS (
SELECT * FROM test_payments
WHERE test_payments.invoiceid = i.iid
AND test_payments.dateadded >= #startdate))
Everything in the following query results in one line for each invBlueprintTypes row with the correct information. But I'm trying to add something to it. See below the codeblock.
Select
blueprintType.typeID,
blueprintType.typeName Blueprint,
productType.typeID,
productType.typeName Item,
productType.portionSize,
blueprintType.basePrice * 0.9 As bpoPrice,
productGroup.groupName ItemGroup,
productCategory.categoryName ItemCategory,
blueprints.productionTime,
blueprints.techLevel,
blueprints.researchProductivityTime,
blueprints.researchMaterialTime,
blueprints.researchCopyTime,
blueprints.researchTechTime,
blueprints.productivityModifier,
blueprints.materialModifier,
blueprints.wasteFactor,
blueprints.maxProductionLimit,
blueprints.blueprintTypeID
From
invBlueprintTypes As blueprints
Inner Join invTypes As blueprintType On blueprints.blueprintTypeID = blueprintType.typeID
Inner Join invTypes As productType On blueprints.productTypeID = productType.typeID
Inner Join invGroups As productGroup On productType.groupID = productGroup.groupID
Inner Join invCategories As productCategory On productGroup.categoryID = productCategory.categoryID
Where
blueprints.techLevel = 1 And
blueprintType.published = 1 And
productType.marketGroupID Is Not Null And
blueprintType.basePrice > 0
So what I need to get in here is the following table with the columns below it so I can use the values timestamp and sort the entire result by profitHour
tablename: invBlueprintTypesPrices
columns: blueprintTypeID, timestamp, profitHour
I need this information with the following select in mind. Using a select to show my intention of the JOIN/in-query select or whatever that can do this.
SELECT * FROM invBlueprintTypesPrices
WHERE blueprintTypeID = blueprintType.typeID
ORDER BY timestamp DESC LIMIT 1
And I need the main row from table invBlueprintTypes to still show even if there is no result from the invBlueprintTypesPrices. The LIMIT 1 is because I want the newest row possible, but deleting the older data is not a option since history is needed.
If I've understood correctly I think I need a subquery select, but how to do that? I've tired adding the exact query that is above with a AS blueprintPrices after the query's closing ), but did not work with a error with the
WHERE blueprintTypeID = blueprintType.typeID
part being the focus of the error. I have no idea why. Anyone who can solve this?
You'll need to use a LEFT JOIN to check for NULL values in invBlueprintTypesPrices. To mimic the LIMIT 1 per TypeId, you can use the MAX() or to truly make sure you only return a single record, use a row number -- this depends on whether you can have multiple max time stamps for each type id. Assuming not, then this should be close:
Select
...
From
invBlueprintTypes As blueprints
Inner Join invTypes As blueprintType On blueprints.blueprintTypeID = blueprintType.typeID
Inner Join invTypes As productType On blueprints.productTypeID = productType.typeID
Inner Join invGroups As productGroup On productType.groupID = productGroup.groupID
Inner Join invCategories As productCategory On productGroup.categoryID = productCategory.categoryID
Left Join (
SELECT MAX(TimeStamp) MaxTime, TypeId
FROM invBlueprintTypesPrices
GROUP BY TypeId
) blueprintTypePrice On blueprints.blueprintTypeID = blueprintTypePrice.typeID
Left Join invBlueprintTypesPrices blueprintTypePrices On
blueprintTypePrice.TypeId = blueprintTypePrices.TypeId AND
blueprintTypePrice.MaxTime = blueprintTypePrices.TimeStamp
Where
blueprints.techLevel = 1 And
blueprintType.published = 1 And
productType.marketGroupID Is Not Null And
blueprintType.basePrice > 0
Order By
blueprintTypePrices.profitHour
Assuming you might have the same max time stamp with 2 different records, replace the 2 left joins above with something similar to this getting the row number:
Left Join (
SELECT #rn:=IF(#prevTypeId=TypeId,#rn+1,1) rn,
TimeStamp,
TypeId,
profitHour,
#prevTypeId:=TypeId
FROM (SELECT *
FROM invBlueprintTypesPrices
ORDER BY TypeId, TimeStamp DESC) t
JOIN (SELECT #rn:=0) t2
) blueprintTypePrices On blueprints.blueprintTypeID = blueprintTypePrices.typeID AND blueprintTypePrices.rn=1
You don't say where you are putting the subquery. If in the select clause, then you have a problem because you are returning more than one value.
You can't put this into the from clause directly, because you have a correlated subquery (not allowed).
Instead, you can put it in like this:
from . . .
(select *
from invBLueprintTypesPrices ibptp
where ibtp.timestamp = (select ibptp2.timestamp
from invBLueprintTypesPrices ibptp2
where ibptp.blueprintTypeId = ibptp2.blueprintTypeId
order by timestamp desc
limit 1
)
) ibptp
on ibptp.blueprintTypeId = blueprintType.TypeID
This identifies the most recent records for all the blueprintTypeids in the subquery. It then joins in the one that matches.
In the following query, I show the latest status of the sale (by stage, in this case the number 3). The query is based on a subquery in the status history of the sale:
SELECT v.id_sale,
IFNULL((
SELECT (CASE WHEN IFNULL( vec.description, '' ) = ''
THEN ve.name
ELSE vec.description
END)
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
WHERE veh.id_sale = v.id_sale
AND vec.id_stage = 3
ORDER BY veh.id_record DESC
LIMIT 1
), 'x') sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
WHERE 1 =1
AND v.flag =1
AND v.id_quarters =4
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
the query delay 0.0057seg and show 1011 records.
Because I have to filter the sales by the name of the state as it would have to repeat the subquery in a where clause, I have decided to change the same query using joins. In this case, I'm using the MAX function to obtain the latest status:
SELECT
v.id_sale,
IFNULL(veh3.State3,'x') AS sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
LEFT JOIN (
SELECT veh.id_sale,
(CASE WHEN IFNULL(vec.description,'') = ''
THEN ve.name
ELSE vec.description END) AS State3
FROM t_record veh
INNER JOIN (
SELECT id_sale, MAX(id_record) AS max_rating
FROM(
SELECT veh.id_sale, id_record
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign AND vec.id_stage = 3
) m
GROUP BY id_sale
) x ON x.max_rating = veh.id_record
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
) veh3 ON veh3.id_sale = v.id_sale
WHERE v.flag = 1
AND v.id_quarters = 4
This query shows the same results (1011). But the problem is it takes 0.0753 sec
Reviewing the possibilities I have found the factor that makes the difference in the speed of the query:
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
If I remove this clause, both queries the same time delay... Why it works better? Is there any way to use this clause in the joins? I hope your help.
EDIT
I will show the results of EXPLAIN for each query respectively:
q1:
q2:
Interesting, so that little statement basically determines if there is a match between t_record.id_sale and t_sale.id_sale.
Why is this making your query run faster? Because Where statements applied prior to subSelects in the select statement, so if there is no record to go with the sale, then it doesn't bother processing the subSelect. Which is netting you some time. So that's why it works better.
Is it going to work in your join syntax? I don't really know without having your tables to test against but you can always just apply it to the end and find out. Add the keyword EXPLAIN to the beginning of your query and you will get a plan of execution which will help you optimize things. Probably the best way to get better results in your join syntax is to add some indexes to your tables.
But I ask you, is this even necessary? You have a query returning in <8 hundredths of a second. Unless this query is getting ran thousands of times an hour, this is not really taxing your DB at all and your time is probably better spent making improvements elsewhere in your application.
When I open this query in Access (which is just a collection of 2 linked tables) I'm able to edit the data with no problems whatsoever.
SELECT O.*, PP.skuapexid
FROM tblSkuBestellingen AS O
INNER JOIN tblSkuApex AS PP
ON (PP.begindatum <= O.besteldatum) AND (PP.sku = O.sku)
WHERE NOT EXISTS
(
SELECT * FROM tblSkuApex PP2
WHERE PP2.sku = PP.sku AND
PP2.begindatum <= O.besteldatum AND
PP2.begindatum > PP.begindatum
);
However, when I add a little field to show me the total of delivered products it suddenly won't allow me to edit this any more. Is there a way around this?
SELECT O.*, PP.skuapexid,
(
SELECT Sum(Nz(tblLeveringProduct.geleverd,0)) AS TotaalGeleverd
FROM tblSkuBestellingen LEFT JOIN tblLeveringProduct ON tblSkuBestellingen.SkuBestelId=tblLeveringProduct.SkuBestelId
WHERE tblSkuBestellingen.sku = PP.sku
) AS TotaalGeleverd
FROM tblSkuBestellingen AS O
INNER JOIN tblSkuApex AS PP
ON (PP.begindatum <= O.besteldatum) AND (PP.sku = O.sku)
WHERE NOT EXISTS
(
SELECT * FROM tblSkuApex PP2
WHERE PP2.sku = PP.sku AND
PP2.begindatum <= O.besteldatum AND
PP2.begindatum > PP.begindatum
);
If any Aggregation (Grouy by, Sum, Count, ...) in the Query or any joined query you can't edit the data.
You can write an function the sum for every row in the table.