Pathros
Pathros

Reputation: 10720

How to make a nested query for a specific condition in MySQL

I have made this query where I get a list of items by their statuses and get the quantities for each status. Here is the query:

SELECT DISTINCT (status) AS Status, COUNT(status) AS Quantity
FROM projects
GROUP BY status
ORDER BY Quantity DESC

And here are the results:

Status   Quantity
'PRO', 238
'TER', 75
'SUS', 14
'REI', 3

Now for those that have a 'TER' status, I want to get those 'TER' statuses that were updated or modified this year and add them to the results (excluding those 'TER' from previous years). To do this, I have this column updated_at (so to speak).

How can I filter those values?

For example, I have tried:

SELECT DISTINCT (status) AS Status, COUNT(status) AS Quantity,
IF(status='TER',SELECT * FROM projects WHERE year(updated_at)=YEAR(CURDATE()))
    FROM projects
    GROUP BY status
    ORDER BY Quantity DESC

But it didn't work.

Any ideas?

Thanks for the ideas.

Posible solution:

I have figured out another solution by using UNION:

(SELECT DISTINCT (status) AS Status, COUNT(status) AS Quantity,
    IF(status='TER',SELECT * FROM projects WHERE year(updated_at)=YEAR(CURDATE()))
        FROM projects
        GROUP BY status
        ORDER BY Quantity DESC)
UNION 
(
SELECT DISTINCT (status) AS Status, COUNT(status) AS Quantity,
    IF(status='TER',SELECT * FROM projects WHERE year(updated_at)=YEAR(CURDATE()))
        FROM projects
        WHERE year(updated_at) = YEAR(curdate())
        AND status IN ('TER')
        GROUP BY status
        ORDER BY Quantity DESC
)

So now I get those projects in 'TER' status and only those within the current year.

So the filter results for 'TER' are now (and should be less results):

Status   Quantity
    'PRO', 238
    'SUS', 14
    'REI', 3
    'TER', 45

The problem now is that I need to reorder the results, I mean, 'TER' should be in second place... well, I have just found out here.

So my final query is:

SELECT * FROM (
(SELECT DISTINCT (status) AS Status, COUNT(status) AS Quantity,
        IF(status='TER',SELECT * FROM projects WHERE year(updated_at)=YEAR(CURDATE()))
            FROM projects
            GROUP BY status
            ORDER BY Quantity DESC)
    UNION 
    (
    SELECT DISTINCT (status) AS Status, COUNT(status) AS Quantity,
        IF(status='TER',SELECT * FROM projects WHERE year(updated_at)=YEAR(CURDATE()))
            FROM projects
            WHERE year(updated_at) = YEAR(curdate())
            AND status IN ('TER')
            GROUP BY status
            ORDER BY Quantity DESC
    )
) a 

GROUP BY status
ORDER BY Quantity DESC

And now I get the results I want and ordered desc:

Status   Quantity
    'PRO', 238
    'TER', 45
    'SUS', 14
    'REI', 3

Or is there a better way out there?

Upvotes: 0

Views: 230

Answers (1)

spencer7593
spencer7593

Reputation: 108370

Based on the edit to the question, which now better explains the expected result, I've updated this answer with a different query. I still recommend conditional aggregation.

  SELECT p.status AS Status
       , COUNT( IF( p.status = 'TER'
                  , IF( YEAR(p.updated_at) = YEAR(CURDATE())
                      , 1
                      , NULL
                    ) 
                  , 1
                )
         ) AS Quantity
    FROM projects p
   GROUP BY p.status
   ORDER BY Quantity DESC

The line breaks and spacing aren't required... I just do that to make it easier to decipher.

For a row that has status = 'TER', it is included in the "count" only if the condition on updated_at is satisfied. For all other values of status, the row is included in the "count" for that status.

The MySQL-specific expression:

                IF( p.status = 'TER'
                  , IF( YEAR(p.updated_at) = YEAR(CURDATE())
                      , 1
                      , NULL
                    ) 
                  , 1
                )

Could be replaced with a more ANSI standards compliant expression, for example:

                CASE WHEN p.status = 'TER'
                       CASE WHEN YEAR(p.updated_at) = YEAR(CURDATE()
                            THEN 1
                       END
                     ELSE 1
                END

ORIGINAL ANSWER

What's not clear is what result you want returned.

It looks like you want an additional column returned, and it looks like you might want that to be a "count" of a subset of the rows included in the "Quantity" column. We can do that.

But first, let's fix some issues with the original query.

The keyword DISTINCT is not necessary, it's redundant with the GROUP BY. And there's no need for parens around the reference to status. That's not illegal to do that, but it doesn't do anything, so it's just confusing. It makes it look as if someone believes that DISTINCT is a function. It's not. It's a keyword that applies to the entire SELECT list.

  SELECT p.status          AS Status
       , COUNT(p.status)   AS Quantity
    FROM projects p
   GROUP BY p.status
   ORDER BY Quantity DESC

It looks like you want conditional aggregation... if the updated_at is a date or datetime in the current year, then include it in the count, otherwise don't.

  SELECT p.status          AS Status
       , COUNT(p.status)   AS Quantity
       , SUM(IF(p.status = 'TER' AND YEAR(p.updated_at)=YEAR(CURDATE()),1,NULL)) AS curcnt
    FROM projects p
   GROUP BY p.status
   ORDER BY Quantity DESC

The expression:

IF(p.status = 'TER' AND YEAR(p.updated_at)=YEAR(CURDATE()),1,NULL)

is MySQL shorthand. An equivalent result could be obtained with a more ANSI standards compliant expression:

CASE WHEN p.status = 'TER' AND YEAR(p.updated_at)=YEAR(CURDATE()) THEN 1 END

Depending on how you want a count of "zero" to be displayed on that row, and other rows, you might want to use 0 in place of NULL, and/or use an expression that includes additional IF, IFNULL or NULLIF functions.


FOLLOWUP

The desired resultset is not clear from the question.

The answer above is based on the assumption that the desired result is an additional column that contains a "count" of the rows that have status='TER' and updated_at within the current year.

The answer above demonstrates conditional aggregation, and does not make use of a "nested query".

A "nested query" could be used to obtain an equivalent result.

As an example of using a "nested query" as an inline view:

  SELECT p.status                AS Status
       , COUNT(p.status)         AS Quantity
       , NULLIF(MAX(q.curcnt),0) AS curcnt
    FROM projects p
    LEFT
    JOIN ( SELECT r.status
                , COUNT(1) AS curcnt
             FROM projects r
            WHERE r.status = 'TER'
              AND YEAR(r.updated_at) = YEAR(NOW())
            GROUP BY r.status
         ) q
      ON q.status = p.status
   GROUP BY p.status
   ORDER BY Quantity DESC

And as an example of a "nested query" as a subquery in the SELECT list:

  SELECT p.status                AS Status
       , COUNT(p.status)         AS Quantity
       , NULLIF( SELECT COUNT(1)
                   FROM projects r
                  WHERE r.status = p.status
                    AND r.status = 'TER'
                    AND YEAR(r.updated_at) = YEAR(NOW())
         ,0) AS curcnt
    FROM projects p
   GROUP BY p.status
   ORDER BY Quantity DESC

Upvotes: 2

Related Questions