2012-05-03 5 views
23

ich eine SQL-Abfrage mit einem Ergebnis wie dieses:SQL: eine Ergebniszeile mehrere Male wiederholen, und die Anzahl der Zeilen

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

Jetzt möchte ich dies erweitern, so dass jede Zeile mit einem count größer als 1 tritt mehrmals auf. Ich brauche diese Zeilen auch, um nummeriert zu werden. So würde ich:

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

ich diese Arbeit auf allen wichtigen Datenbanken machen (Oracle, SQL Server, MySQL, PostgreSQL, und vielleicht auch mehr). Daher wäre eine Lösung, die über verschiedene Datenbanken hinweg funktioniert, ideal, aber schlaue Methoden, um es in jeder Datenbank zum Laufen zu bringen, werden geschätzt.

Antwort

20

Für MySQL verwenden die Armen generate_series des Menschen, die über Ansichten erfolgt. MySQL ist die einzige RDBMS unter big four, die jede CTE Funktion nicht hat.

Eigentlich kann man diese Technik auf Datenbank verwenden, die Ansicht unterstützt. Also das ist so gut wie alle Datenbank

Generator-Technik hier gelesen: http://use-the-index-luke.com/blog/2011-07-30/mysql-row-generator#mysql_generator_code

Die einzige kleine Modifikation ich gemacht ist, wir ersetzen die bitweise (Verschiebung nach links und bit- oder) -Technik aus der ursprünglichen Technik mit bloßer Vermehrung und Ergänzung jeweils; wie Sql Server und Oracle hat keine Verschiebung nach links Betreiber.

Diese Abstraktion ist zu 99% garantiert auf alle Datenbank zu arbeiten, außer Oracle; Oracle SELECT kann ohne Tabelle nicht funktionieren kann, um dies zu tun, von der Dummy-Tabelle braucht man, um zu wählen, vorausgesetzt, Oracle eine bereits, es DUAL Tabelle genannt wird. Datenbankportabilität wird ein Rohr Traum :-)

Hier die abstrahierten Ansichten ist, die auf allen RDBMS arbeiten, ohne bitweise Operationen (die ohnehin in diesem Szenario nicht wirklich eine Notwendigkeit ist) und Feature-Nuancen (wir entfernen OR REPLACE auf CREATE VIEW, nur Postgresql und MySQL unterstützt sie) unter allen wichtigen Datenbank.

Oracle Einschränkung: Einfach FROM DUAL nach jedem SELECT Ausdruck

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; 

Dann diese Abfrage verwenden:

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

+0

Ich mag diese Antwort am besten, weil sie tragbar ist. – cygri

+0

Diese Antwort hat mir geholfen, ein großes Problem in unseren Abfragen zu lösen, und mir geholfen, eine ganze Reihe neuer Berichte mit diesem Code zu erstellen. Ich wünschte, ich könnte mehr tun als nur diese Antwort zu wählen. :) – Neels

30

könnten Sie eine Zahlen-Tabelle verwenden

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

Here is a SQLFiddle using MSSQL

+1

+1 dies ist am besten und klarsten Antwort! –

+0

Jeder Einblick, warum das funktioniert? Arbeitete großartig für meine Lösung 4 Jahre später BTW. – nzaleski

+0

Die einfachste Erklärung ist, dass statt einer 1 Zeile alle Zeilen bis zur Zahl nach der Zählung hinzugefügt werden. dh. Count = 3, dann stimmt es mit 1,2,3 überein und erzeugt ein Ergebnis von 3 | 1, 3 | 2 und 3 | 3 –

4

erstellen Zahlen Tabelle - ihre Definition kann je nach Plattform geringfügig variieren (dies ist für SQL Server ist):

CREATE TABLE Numbers(Number INT PRIMARY KEY); 

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

Jetzt ist diese Temp auch SQL Server, aber demonstriert die Joinsyntax, die über die von Ihnen angegebenen RDBMS gültig sein sollte (obwohl ich gestehen werde, dass ich sie nicht verwende, so dass ich c an't Test):

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

Ergebnisse (wieder, SQL Server):

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

MySQL ist wirklich der IE der Datenbank Welt, es ist so ein holdout, wenn es um Standards und Funktionen kommt.

Arbeiten auf allen wichtigen RDBMS außer 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

+0

+1 Schöne Alternative zur klassischen Lösung mit Numbers-Tabelle. –

+0

Schön, es funktioniert, obwohl ich nicht ganz verstehe, was es macht :-) – cygri

+0

Heheh wirklich? Jetzt bin ich inspiriert, einen Blog über Rekursion auf CTE zu machen. Aber im Grunde genommen sind Zeilengeneratoren die einfachste Art der CTE-Rekursion, sie folgt der Logik der Tail-Rekursion; Tail-Rekursion hat fast eine Eins-zu-Eins-Entsprechung mit der Schleife. Hierarchische Rekursionen und Rekursionen von Objektgraphen auf CTE sind ein wenig schwieriger zu verstehen –

2

Für nur Anerkennung, SQL Server 2005 und höher kann dies rekursiv behandeln:

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) 
6

Sie haben nach einer db-agnostic-Lösung gefragt und @Justin hat Ihnen eine nette Lösung gegeben.
Sie auch gefragt, für

clevere Möglichkeiten, um es auf jeder Datenbank funktioniert

ist es eine für PostgreSQL: generate_series() tut, was Sie für aus dem Kasten heraus gestellt:

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

BTW, würde ich lieber nicht value und count als Spaltennamen verwenden. Es ist eine schlechte Übung, reserved words als Bezeichner zu verwenden. Verwenden Sie stattdessen val und ct.

+0

Brilliant! Ich wünschte, das würde überall funktionieren. Und ja, du hast Recht mit den reservierten Worten, danke fürs Hinweisen. – cygri

1

Durch einen einfachen JOIN können Sie zu dem Ziel, sich wiederholenden Datensätze n mal erreichen.
Die folgende Abfrage wiederholt jeden Datensatz 20-mal.

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


Hinweis für master.dbo.spt_values on type = 'P':
Diese Tabelle ist für immer eine Reihe von Nummer verwendet wird, die in ihm durch Bedingung type='P' hartcodiert ist.

0

Sie von CTE verwenden können:

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

In Oracle wir eine Kombination aus LEVEL und CROSS JOIN nutzen könnten.

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

Verwandte Themen