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.
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
Vous pouvez utiliser un tableau de nombres
SELECT value, count, number
FROM table
JOIN Numbers
ON table.count >= Numbers.number
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
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
.
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
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 )
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'
.
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;
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)