0

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"

2
  • IN is a very slow operation in SQL. I would suggest trying to work with an EXISTS and try to rewrite your query to encapsulate the correct logic. Commented Sep 26, 2014 at 11:38
  • First, I would suggest always qualifying column names with corresponding table/alias references. Where is EnquiryDate associated and same on InvoiceProductTotal. Clarify your question regardless of SQL syntax... It LOOKS like you want total of products, but only for the first invoice for any client of Sep 2014, then summing them together of invoice products? Very bizarre as might be seen from my interpretation of reading your post. Commented Sep 26, 2014 at 11:42

2 Answers 2

1

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

  1. using a date range scan.
  2. eliminating the NOT ... IS NULL criterion.
  3. 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.

Sign up to request clarification or add additional context in comments.

2 Comments

Great job and explanation btw. :)
Thanks for noticing the mistake... fixed.
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.