SQL: répétez une ligne de résultat plusieurs fois, et numérotez les lignes

j'ai une requête SQL avec un résultat comme ceci:

value | count
------+------
foo   |     1
bar   |     3
baz   |     2

Maintenant je veux agrandir de sorte que chaque ligne avec un count plus de 1 se produit plusieurs fois. J'ai aussi besoin que ces lignes soient numérotées. Donc, je voudrais obtenir:

value | count | index
------+-------+------
foo   |     1 |     1
bar   |     3 |     1
bar   |     3 |     2
bar   |     3 |     3
baz   |     2 |     1
baz   |     2 |     2

je dois faire ce travail sur toutes les bases de données principales (Oracle, SQL Server, MySQL, PostgreSQL, et peut-être plus). Ainsi, une solution qui fonctionne à travers différentes bases de données serait idéale, mais des moyens astucieux pour le faire fonctionner sur n'importe quelle base de données sont apprécier.

23
demandé sur cygri 2012-05-03 04:57:02

9 réponses

Pour MySQL, utilisez le pauvre homme generate_series, ce qui se fait via des vues. MySQL est le seul RDBMS parmi quatre qui n'a pas de fonction CTE.

en fait, vous pouvez utiliser cette technique sur la base de données qui supporte la vue. Donc, c'est pratiquement toute la base de données

technique du générateur source: http://use-the-index-luke.com/blog/2011-07-30/mysql-row-generator#mysql_generator_code

Le seul mineur la modification que nous avons faite est que nous remplaçons le bitwise (décalage à gauche et or) technique à partir de la technique d'origine avec une simple multiplication et addition respectivement; comme Sql Server et Oracle n'a pas d'opérateur de gauche shift.

cette abstraction est garantie à 99% de fonctionner sur toutes les bases de données, sauf Oracle; Oracle SELECT ne peut pas fonctionner sans n'importe quelle table, pour faire cela, on a besoin de choisir de table factice, Oracle pourvu qu'un déjà, il s'appelle DUAL tableau. La portabilité des bases de données est une chimère: -)

Voici les vues abstraites qui fonctionnent sur tous les RDBM, dépourvues d'opérations bitwise (ce qui n'est pas vraiment une nécessité dans ce scénario) et comportant des nuances(nous supprimons OR REPLACECREATE VIEW, seulement Postgresql et MySQL les supporte) parmi toutes les bases de données majeures.

Oracle avertissement: mettez juste FROM DUAL après chaque SELECT expression

CREATE VIEW generator_16
AS SELECT 0 n UNION ALL SELECT 1  UNION ALL SELECT 2  UNION ALL 
   SELECT 3   UNION ALL SELECT 4  UNION ALL SELECT 5  UNION ALL
   SELECT 6   UNION ALL SELECT 7  UNION ALL SELECT 8  UNION ALL
   SELECT 9   UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL
   SELECT 12  UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL 
   SELECT 15;

CREATE VIEW generator_256
AS SELECT ( ( hi.n * 16 ) + lo.n ) AS n
     FROM generator_16 lo, generator_16 hi;

CREATE VIEW generator_4k
AS SELECT ( ( hi.n * 256 ) + lo.n ) AS n
     FROM generator_256 lo, generator_16 hi;

CREATE VIEW generator_64k
AS SELECT ( ( hi.n * 256 ) + lo.n ) AS n
     FROM generator_256 lo, generator_256 hi;

CREATE VIEW generator_1m
AS SELECT ( ( hi.n * 65536 ) + lo.n ) AS n
     FROM generator_64k lo, generator_16 hi;

alors utilisez ceci requête:

SELECT t.value, t.cnt, i.n
FROM tbl t
JOIN generator_64k i 
ON i.n between 1 and t.cnt
order by t.value, i.n

Postgresql:http://www.sqlfiddle.com/#!1 / 1541d/1

Oracle:http://www.sqlfiddle.com/#!4 / 26c05/1

Sql Server:http://www.sqlfiddle.com/#!6 / 84bee/1

MySQL:http://www.sqlfiddle.com/#!2 / 78f5b/1

20
répondu Michael Buen 2017-05-23 12:26:35

Vous pouvez utiliser une table de nombres

SELECT value, count, number
FROM table
    JOIN Numbers 
        ON table.count >= Numbers.number

voici un SQLFiddle utilisant MSSQL

32
répondu Justin Pihony 2012-05-03 01:01:39

MySQL est vraiment L'IE du monde de la base de données, c'est un tel Hold-Out quand il s'agit de normes et de fonctionnalités.

Fonctionne sur tous les principaux SGBDR à l'exception de MySQL:

with 
-- Please add this on Postgresql:
-- RECURSIVE
tbl_populate(value, cnt, ndx) as
(
  select value, cnt, 1 from tbl

  union all

  select t.value, t.cnt, tp.ndx + 1
  from tbl t
  join tbl_populate tp 
  on tp.value = t.value  
  and tp.ndx + 1 <= t.cnt
)
select * from tbl_populate
order by cnt, ndx

SQL Server:http://www.sqlfiddle.com/#!6 / 911a9/1

Oracle:http://www.sqlfiddle.com/#!4 / 198cd/1

Postgresql:http://www.sqlfiddle.com/#!1 / 0b03d/1

7
répondu Michael Buen 2012-05-04 13:34:09

vous avez demandé une solution db-agnostique et @ Justin vous en a donné une belle.

Vous m'avez également demandé pour

des astuces pour le faire fonctionner sur une base de données

Il y en a un pour PostgreSQL:generate_series() est-ce que vous avez demandé pour le sortir de la boîte:

SELECT val, ct, generate_series(1, ct) AS index
FROM   tbl;

BTW, je préfère ne pas l'utiliser value et count comme noms de colonne. C'est une mauvaise pratique à utiliser mots réservés comme identificateurs. Utiliser val et ct à la place.

6
répondu Erwin Brandstetter 2012-05-03 22:06:01

Créer une table de nombres - sa définition peut varier légèrement en fonction de la plate-forme (c'est pour SQL Server):

CREATE TABLE Numbers(Number INT PRIMARY KEY);

INSERT Numbers 
SELECT TOP 1000 ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_columns;

maintenant ce temp est aussi SQL Server, mais démontre la syntaxe de jointure qui devrait être valide dans les RDBMSes que vous spécifiez (bien que je vais avouer que je ne les utilise pas donc je ne peux pas les tester):

DECLARE @foo TABLE(value VARCHAR(32), [count] INT);

INSERT @foo SELECT 'foo', 1
UNION ALL SELECT 'bar', 3
UNION ALL SELECT 'baz', 2;

SELECT f.value, f.[count], [index] = n.Number
FROM @foo AS f, Numbers AS n
WHERE n.Number <= f.[count];

Résultats (encore une fois, SQL Serveur):

value | count | index
------+-------+------
foo   |     1 |     1
bar   |     3 |     1
bar   |     3 |     2
bar   |     3 |     3
baz   |     2 |     1
baz   |     2 |     2
4
répondu Aaron Bertrand 2012-05-03 01:06:24

Pour l'appréciation seulement, SQL Server 2005 et versions ultérieures peuvent gérer cela de façon récursive:

declare @Stuff as Table ( Name VarChar(10), Number Int )
insert into @Stuff ( Name, Number ) values ( 'foo', 1 ), ( 'bar', 3 ), ( 'baz', 2 )

select * from @Stuff

; with Repeat ( Name, Number, Counter ) as (
  select Name, Number, 1
    from @Stuff
    where Number > 0
  union all
  select Name, Number, Counter + 1
    from Repeat
    where Counter < Number
  )
select *
  from Repeat
  order by Name, Counter -- Group by name.
  option ( maxrecursion 0 )
2
répondu HABO 2012-05-03 02:31:11

par un simple JOIN vous pouvez atteindre le but de répéter les dossiers n fois.

La requête suivante répète chaque enregistrement 20 fois.

SELECT  TableName.*
FROM    TableName
JOIN    master.dbo.spt_values on type = 'P' and number < 20



Remarque:master.dbo.spt_values on type = 'P':

Ce tableau est utilisé pour obtenir une série de nombre qui est codé dur en elle par condition de type='P'.

1
répondu Siyavash Hamdi 2017-01-22 10:42:08

Vous pouvez utiliser CTE:

WITH Numbers(Num) AS
(
    SELECT 1 AS Num
    UNION ALL 
    SELECT Num + 1
    FROM   Numbers c
    WHERE  c.Num < 1000
)

SELECT VALUE,COUNT, number
FROM   TABLE
       JOIN Numbers
            ON  TABLE.count >= Numbers.Num
OPTION(MAXRECURSION 1000)
0
répondu Ebrahim Sabeti 2018-01-13 11:46:47

Oracle, nous pourrions utiliser une combinaison de LEVEL et CROSS JOIN.

  SELECT *
    FROM yourtable
         CROSS JOIN (    SELECT ROWNUM index_t
                           FROM DUAL
                     CONNECT BY LEVEL <= (SELECT MAX (count_t) FROM yourtable))
   WHERE index_t <= count_t
ORDER BY VALUE, index_t;

DEMO

0
répondu Kaushik Nayak 2018-02-06 13:39:51