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