web-dev-qa-db-fra.com

SQL: répéter plusieurs fois une ligne de résultat et numéroter les lignes

J'ai une requête SQL avec un résultat comme celui-ci:

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

Maintenant, je veux développer cela afin que chaque ligne avec un count supérieur à 1 se répète plusieurs fois. J'ai également besoin que ces lignes soient numérotées. Donc j'obtiendrais:

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 principales bases de données (Oracle, SQL Server, MySQL, PostgreSQL, et peut-être plus). Une solution qui fonctionne sur différentes bases de données serait donc idéale, mais des moyens intelligents de la faire fonctionner sur n'importe quelle base de données sont appréciés.

24
cygri

Pour MySQL, utilisez le pauvre generate_series , qui se fait via les vues. MySQL est le seul SGBDR parmi les big four qui n'a pas de fonctionnalité CTE.

En fait, vous pouvez utiliser cette technique sur une base de données qui prend en charge la vue. Voilà donc pratiquement toute la base de données

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

La seule modification mineure que nous avons apportée est que nous remplaçons le décalage au niveau du bit ( à gauche et au niveau du bit ou ) technique de la technique originale avec simple multiplication et addition respectivement; comme Sql Server et Oracle n'a pas d'opérateur de décalage gauche.

Cette abstraction est garantie à 99% pour fonctionner sur toutes les bases de données, sauf Oracle; SELECT d'Oracle ne peut pas fonctionner sans aucune table, pour ce faire, il faut sélectionner dans une table fictive, Oracle en a déjà fourni une, elle s'appelle DUAL table. La portabilité de la base de données est un rêve de pipe :-)

Voici les vues abstraites qui fonctionnent sur tous les SGBDR, dépourvues d'opérations au niveau du bit (ce qui n'est pas vraiment une nécessité de toute façon dans ce scénario) et présentent des nuances (nous supprimons OR REPLACE sur CREATE VIEW, seuls Postgresql et MySQL les prennent en charge) parmi toutes les principales bases de données.

Avertissement d'Oracle: Mettez simplement 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;

Utilisez ensuite cette 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

Serveur SQL: http: //www.sqlfiddle.com/#! 6/84bee/1

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

21
Michael Buen

Vous pouvez utiliser un tableau de nombres

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

Voici un SQLFiddle utilisant MSSQL

32
Justin Pihony

MySQL est vraiment le IE du monde de la base de données, c'est une telle résistance en ce qui concerne les normes et les 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
Michael Buen

Vous avez demandé une solution db-agnostique et @Justin vous en a donné une de Nice.
Vous avez également demandé

façons intelligentes de le faire fonctionner sur n'importe quelle base de données

Il y en a un pour PostgreSQL: generate_series() fait ce que vous avez demandé hors de la boîte:

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

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

6

Créez une table de nombres - sa définition peut varier légèrement selon 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 également SQL Server, mais montre la syntaxe de jointure qui devrait être valide dans tous les SGBDR que vous spécifiez (bien que j'avoue que je ne les utilise pas, donc je ne peux pas 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 Server):

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

Pour appréciation uniquement, SQL Server 2005 et versions ultérieures peuvent gérer cela récursivement:

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
HABO

Par un simple JOIN vous pouvez atteindre l'objectif de répéter les enregistrements 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 pour master.dbo.spt_values on type = 'P':
Ce tableau est utilisé pour obtenir une série de nombres codés en dur par condition de type='P'.

1
Siyavash Hamdi

Dans 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;

[~ # ~] démo [~ # ~]

0
Kaushik Nayak

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
Ebrahim Sabeti