Search code examples
mysqlif-statementcomposite

mysql composite if column from composite values?


I need to add an extra column to my query results,

The column needs to be called percent, If col1 == 0 then percent should contain NIS else percent should contain ceil(col2 / col1 * 100)

So I believe the following should work:

IF(col1 = 0, 'NIS', ceil(col2 / col1 * 100)) as percent

But I run into an issue as col1 and col2 are also composite's.

COUNT(distinct i.id) as col1
COUNT(distinct q.id) as col2

So I get hit with

Unknown column 'col1' in 'field list'

I could get around that issue with
IF(COUNT(distinct i.id) = 0, 'NIS', ceil(COUNT(distinct q.id) / COUNT(distinct i.id) * 100)) as percent

But that just seems like a bunch of extra processing to me, surely there is a better way around that?

Full Query

SELECT 
  `t`.*,
  `r`.`name` AS region_name,
  GROUP_CONCAT(DISTINCT p.ptype_id SEPARATOR "|") AS ptype_ids,
  COUNT(DISTINCT q.id) AS quoted,
  COUNT(DISTINCT i.id) AS intended,
  COUNT(DISTINCT qa.id) AS awarded,
  IF(
    intended = 0,
    `"NIS"`,
    CEIL(quoted / intended * 100)
  ) AS percent 
FROM
  (`tradesmen` t) 
  LEFT JOIN `regions` r 
    ON `r`.`id` = `t`.`region_id` 
  LEFT JOIN `quotes` q 
    ON `t`.`id` = `q`.`tradesman_id` 
  LEFT JOIN `quote_intentions` i 
    ON `t`.`id` = `i`.`tradesman_id` 
  LEFT JOIN `quotes` qa 
    ON `q`.`tradesman_id` = `qa`.`tradesman_id` 
    AND qa.accepted = 1 
  LEFT JOIN `ptypes_tradesmen` p 
    ON `p`.`tradesman_id` = `t`.`id` 
GROUP BY `t`.`id` 
LIMIT 20 

Solution

  • As mentioned in SELECT Syntax:

    It is not permissible to refer to a column alias in a WHERE clause, because the column value might not yet be determined when the WHERE clause is executed. See Section C.5.5.4, “Problems with Column Aliases”.

    Whilst the manual doesn't explicitly say so, the same reasoning applies to referring to a column alias within a select_expr.

    You could place your query in a subquery to an outer one that calculates percent using the column aliases:

    SELECT *, IF(intended, CEIL(quoted / intended * 100), NULL) AS percent FROM (
      SELECT 
        `t`.*,
        `r`.`name` AS region_name,
        GROUP_CONCAT(DISTINCT p.ptype_id SEPARATOR "|") AS ptype_ids,
        COUNT(DISTINCT q.id)  AS quoted,
        COUNT(DISTINCT i.id)  AS intended,
        COUNT(DISTINCT qa.id) AS awarded
      FROM
        (`tradesmen` t) 
        LEFT JOIN `regions` r 
          ON `r`.`id` = `t`.`region_id` 
        LEFT JOIN `quotes` q 
          ON `t`.`id` = `q`.`tradesman_id` 
        LEFT JOIN `quote_intentions` i 
          ON `t`.`id` = `i`.`tradesman_id` 
        LEFT JOIN `quotes` qa 
          ON `q`.`tradesman_id` = `qa`.`tradesman_id` 
          AND qa.accepted = 1 
        LEFT JOIN `ptypes_tradesmen` p 
          ON `p`.`tradesman_id` = `t`.`id` 
      GROUP BY `t`.`id` 
      LIMIT 20
    ) t
    

    However, I don't think this is really worthwhile as I believe (am looking for a reference that I can cite, but nothing forthcoming yet) MySQL will only calculate each COUNT() once and use the cached result in each reference.