2012-07-04 59 views
0

假设你有MySQL-选择每组至少n行

id / value 
1  2 
1  3 
1  6 
2  3 
3  1 
3  3 
3  6 

我要检索每个ID组至少n行,假设N = 4。此外,这将有助于如果计数器作为列添加。所以结果应该是这样的:

counter/id /value 
1   1  2 
2   1  3 
3   1  6 
4   null null 
1   2  3 
2   null null 
3   null null 
4   null null 
1   3  1 
2   3  3 
3   3  6 
4   null null 

问候

+0

你有什么已经尝试过?什么类型的数据被定义为列;如果集合中的小于* n *,您希望发生什么? –

+0

@RussC示例数据显示了他想要的内容:在数据不足的情况下,他想要空值。而且,列数据类型是不相关的。如果您有解决方案,请发布。 – Bohemian

+0

我怀疑这可能更适合自己的应用程序代码:它需要在数据库中完成的任何特定原因? – eggyal

回答

0

我假设的idvalue组合是唯一的。这里是你如何能做到这一点,而不使用的MySQL变量:

SELECT 
    a.n AS counter, 
    b.id, 
    b.value 
FROM 
    (
     SELECT 
      aa.n, 
      bb.id 
     FROM 
      (
       SELECT 1 AS n UNION ALL 
       SELECT 2 AS n UNION ALL 
       SELECT 3 AS n UNION ALL 
       SELECT 4 AS n 
      ) aa 
     CROSS JOIN 
      (
       SELECT DISTINCT id 
       FROM tbl 
      ) bb 
    ) a 
LEFT JOIN 
    (
     SELECT aa.id, aa.value, COUNT(*) AS rank 
     FROM tbl aa 
     LEFT JOIN tbl bb ON aa.id = bb.id AND aa.value >= bb.value 
     GROUP BY aa.id, aa.value 
    ) b ON a.id = b.id AND a.n = b.rank 
ORDER BY 
    a.id, 
    a.n 
0

下一个博客帖子描述了解决您的查询: SQL: selecting top N records per group

它需要一个额外的数字小表,用于通过字符串散步技术“迭代”每个组的前N个值。 它使用GROUP_CONCAT作为克服MySQL不支持窗口函数的一种方式。这也意味着它不是一个美丽的景象!

该技术的一个优点是它不需要子查询,并且可以最佳地利用表上的索引。

要完成问题的答案,我们必须添加一个额外的列:您已经为每个组的每个项目请求一个计数器。

下面是一个使用世界示例数据库为例,选择每大陆前5大县:

CREATE TABLE `tinyint_asc` (
`value` tinyint(3) unsigned NOT NULL default '0', 
PRIMARY KEY (value) 
) ; 

INSERT INTO `tinyint_asc` VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29),(30),(31),(32),(33),(34),(35),(36),(37),(38),(39),(40),(41),(42),(43),(44),(45),(46),(47),(48),(49),(50),(51),(52),(53),(54),(55),(56),(57),(58),(59),(60),(61),(62),(63),(64),(65),(66),(67),(68),(69),(70),(71),(72),(73),(74),(75),(76),(77),(78),(79),(80),(81),(82),(83),(84),(85),(86),(87),(88),(89),(90),(91),(92),(93),(94),(95),(96),(97),(98),(99),(100),(101),(102),(103),(104),(105),(106),(107),(108),(109),(110),(111),(112),(113),(114),(115),(116),(117),(118),(119),(120),(121),(122),(123),(124),(125),(126),(127),(128),(129),(130),(131),(132),(133),(134),(135),(136),(137),(138),(139),(140),(141),(142),(143),(144),(145),(146),(147),(148),(149),(150),(151),(152),(153),(154),(155),(156),(157),(158),(159),(160),(161),(162),(163),(164),(165),(166),(167),(168),(169),(170),(171),(172),(173),(174),(175),(176),(177),(178),(179),(180),(181),(182),(183),(184),(185),(186),(187),(188),(189),(190),(191),(192),(193),(194),(195),(196),(197),(198),(199),(200),(201),(202),(203),(204),(205),(206),(207),(208),(209),(210),(211),(212),(213),(214),(215),(216),(217),(218),(219),(220),(221),(222),(223),(224),(225),(226),(227),(228),(229),(230),(231),(232),(233),(234),(235),(236),(237),(238),(239),(240),(241),(242),(243),(244),(245),(246),(247),(248),(249),(250),(251),(252),(253),(254),(255); 

SELECT 
    Continent, 
    SUBSTRING_INDEX(
    SUBSTRING_INDEX(
     GROUP_CONCAT(Name ORDER BY SurfaceArea DESC), 
     ',', value), 
    ',', -1) 
    AS Name, 
    CAST(
    SUBSTRING_INDEX(
     SUBSTRING_INDEX(
     GROUP_CONCAT(SurfaceArea ORDER BY SurfaceArea DESC), 
     ',', value), 
     ',', -1) 
    AS DECIMAL(20,2) 
    ) AS SurfaceArea, 
    CAST(
    SUBSTRING_INDEX(
     SUBSTRING_INDEX(
     GROUP_CONCAT(Population ORDER BY SurfaceArea DESC), 
     ',', value), 
     ',', -1) 
    AS UNSIGNED 
    ) AS Population, 
    tinyint_asc.value AS counter 
FROM 
    Country, tinyint_asc 
WHERE 
    tinyint_asc.value >= 1 AND tinyint_asc.value <= 5 
GROUP BY 
    Continent, value 
;