mysql Rank function grouped for a COUNT

Asked

Viewed 219 times

1

I have 3 tables in MYSQL represented here as an example:

Tabelas

I need a query that returns a RANK of contracts made grouped by commercial in a given period of time, for example:

A query that returns the RANK of the commercials between 2016-01-01 and 2016-01-03, should look like this:

Como deve ficar

I have tried several solutions but none returns the result I need, for example an important point is when the commercial has the same number of contracts must have the same position in the ranking, in this example the commercial Com1 and Com2 have the position 1 in the Ranking because they both have 2 contracts in the chosen period.

My query currently is:

    set @rn:=0,@grp:=0,@prevdate:='';
SELECT COUNT(*) AS NumContratos, NomeCom,NomeEmp
     , ( select count(*)+1
           from (
                select IdComercial
                     , count(*) as c
                  from contratos
                group
                    by IdComercial
                ) as m
          where c >
                (
                select count(*) 
                  from contratos as e

                 where e.IdComercial = T.IdComercial
                )
       ) AS Rank
     , T.IdComercial AS IdCom
  FROM contratos AS T

INNER JOIN comerciais ON T.IdComercial=comerciais.IdComercial
INNER JOIN empresas ON T.IdEmpresa=empresas.IdEmpresa

WHERE (DataContrato BETWEEN '2015-01-01' AND '2016-01-03')

GROUP 
    BY T.IdComercial
ORDER
    BY Rank

And this is the script for database creation, table and data insertion:

-- --------------------------------------------------------
-- Host:                         localhost
-- Server version:               10.1.9-MariaDB - mariadb.org binary distribution
-- Server OS:                    Win32
-- HeidiSQL Version:             9.2.0.4947
-- --------------------------------------------------------

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;

-- Dumping database structure for teste
CREATE DATABASE IF NOT EXISTS `teste` /*!40100 DEFAULT CHARACTER SET latin1 */;
USE `teste`;


-- Dumping structure for table teste.comerciais
CREATE TABLE IF NOT EXISTS `comerciais` (
  `IdComercial` int(11) NOT NULL AUTO_INCREMENT,
  `IdEmpresa` int(11) NOT NULL DEFAULT '0',
  `NomeCom` varchar(100) NOT NULL DEFAULT '0',
  PRIMARY KEY (`IdComercial`),
  KEY `IdEmpresa` (`IdEmpresa`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;

-- Dumping data for table teste.comerciais: ~5 rows (approximately)
/*!40000 ALTER TABLE `comerciais` DISABLE KEYS */;
INSERT INTO `comerciais` (`IdComercial`, `IdEmpresa`, `NomeCom`) VALUES
    (1, 1, 'Com1'),
    (2, 1, 'Com2'),
    (3, 2, 'Com3'),
    (4, 2, 'Com4'),
    (5, 4, 'Com5');
/*!40000 ALTER TABLE `comerciais` ENABLE KEYS */;


-- Dumping structure for table teste.contratos
CREATE TABLE IF NOT EXISTS `contratos` (
  `IdContratos` int(11) NOT NULL AUTO_INCREMENT,
  `IdComercial` int(11) NOT NULL,
  `DataContrato` date NOT NULL,
  PRIMARY KEY (`IdContratos`),
  KEY `IdComercial` (`IdComercial`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;

-- Dumping data for table teste.contratos: ~5 rows (approximately)
/*!40000 ALTER TABLE `contratos` DISABLE KEYS */;
INSERT INTO `contratos` (`IdContratos`, `IdComercial`, `DataContrato`) VALUES
    (1, 1, '2016-01-01'),
    (2, 1, '2016-01-01'),
    (3, 2, '2016-01-02'),
    (4, 3, '2016-01-02'),
    (5, 4, '2016-01-03');
/*!40000 ALTER TABLE `contratos` ENABLE KEYS */;


-- Dumping structure for table teste.empresas
CREATE TABLE IF NOT EXISTS `empresas` (
  `IdEmpresa` int(11) NOT NULL AUTO_INCREMENT,
  `NomeEmp` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`IdEmpresa`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;

-- Dumping data for table teste.empresas: ~5 rows (approximately)
/*!40000 ALTER TABLE `empresas` DISABLE KEYS */;
INSERT INTO `empresas` (`IdEmpresa`, `NomeEmp`) VALUES
    (1, 'Emp1'),
    (2, 'Emp2'),
    (3, 'Emp3'),
    (4, 'Emp4'),
    (5, 'Emp5');
/*!40000 ALTER TABLE `empresas` ENABLE KEYS */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

What returns me is not consistent with what I need, for example, returns me a rank where for example the position is missing 6 (in the case of my database)

  • Looking half over I kick that yours INNER JOIN is making position 6 disappear. Change the 2 INNER JOIN for LEFT JOIN

  • equal result.

  • Okay, you can put the creates and inserts table so we can test the querys?

  • Sorry, I’m still new here. Thank you.

  • Take a look at the answer. Maybe some commercial that has no contract does not appear in the relationship, then we need to make a slight change.

1 answer

3


Your query would look something like this:

SELECT x.IdComercial,
       x.NomeCom,
       emp.NomeEmp,
       CASE x.quantidade
           when @curQtd then @curRank
           else @curRank := @curRank + 1
       END AS Rank,
       CASE @curQtd
            when x.quantidade then @curQtd := quantidade
            else @curQtd := x.quantidade
       END AS NumContratos
  FROM (SELECT com2.IdComercial,
               com2.NomeCom,
               com2.IdEmpresa,
               COUNT(1) AS quantidade
         FROM comerciais com2
              INNER JOIN contratos con2 ON con2.IdComercial = com2.IdComercial
         GROUP BY com2.IdComercial) x
       INNER JOIN empresas emp ON x.IdEmpresa = emp.IdEmpresa
       JOIN (SELECT @curRank := 0, @curQtd := 0) r -- Aqui apenas zeramos os valores iniciais
 ORDER BY x.quantidade DESC

The last JOIN is to zero the variables and the first is to get the quantities. Note that the variables are incremented according to the case rules and are only changed if the quantity is changed according to the previous line.

If you want to show even the commercials that do not have contracts, you must use LEFT JOIN which will still bring the record, even if the other does not exist, and place the column IdContratos in the COUNT thus, the commercial without contract will have the correct sum:

SELECT x.IdComercial,
       x.NomeCom,
       emp.NomeEmp,
       CASE x.quantidade
           when @curQtd then @curRank
           else @curRank := @curRank + 1
       END AS Rank,
       CASE @curQtd
            when x.quantidade then @curQtd := quantidade
            else @curQtd := x.quantidade
       END AS NumContratos
  FROM (SELECT com2.IdComercial,
               com2.NomeCom,
               com2.IdEmpresa,
               COUNT(con2.IdContratos) AS quantidade
         FROM comerciais com2
              LEFT JOIN contratos con2 ON con2.IdComercial = com2.IdComercial
         GROUP BY com2.IdComercial) x
       INNER JOIN empresas emp ON x.IdEmpresa = emp.IdEmpresa
       JOIN (SELECT @curRank := 0, @curQtd := 0) r -- Aqui apenas zeramos os valores iniciais
 ORDER BY x.quantidade DESC
  • It works perfectly. Thank you @Sorack. A Master.

  • 1

    @Paulorosa see only if the situation I told you will not disturb. When you have some commercial without contracts

  • When I have commercials without contracts do not actually appear these commercials, but should appear with 0 contracts.

  • 1

    @Paulorosa I added a second example that will fit this problem

  • Fabulous. Thank you @Sorack

Browser other questions tagged

You are not signed in. Login or sign up in order to post.