SQL: повторите строку результата несколько раз и введите число строк
У меня есть SQL-запрос с таким результатом:
value | count
------+------
foo | 1
bar | 3
baz | 2
Теперь я хочу развернуть это, чтобы каждая строка с count
больше 1 выполнялась несколько раз. Мне также нужны эти строки для нумерации. Поэтому я бы получил:
value | count | index
------+-------+------
foo | 1 | 1
bar | 3 | 1
bar | 3 | 2
bar | 3 | 3
baz | 2 | 1
baz | 2 | 2
Мне нужно сделать эту работу над всеми основными базами данных (Oracle, SQL Server, MySQL, PostgreSQL и, возможно, больше). Таким образом, решение, которое работает в разных базах данных, было бы идеальным, но рекомендуется использовать умные способы заставить его работать с любой базой данных.
Ответы
Ответ 1
Для MySQL используйте бедного человека generate_series, который выполняется через представления. MySQL - единственная СУБД среди больших четырех, у которой нет какой-либо функции CTE.
На самом деле вы можете использовать эту технику в базе данных, которая поддерживает представление. Так что практически вся база данных
Используемая здесь техника генератора: http://use-the-index-luke.com/blog/2011-07-30/mysql-row-generator#mysql_generator_code
Единственная незначительная модификация, которую мы сделали, заключается в замене побитовой (shift left и побитовой или) техники по оригинальной методике с простым умножением и добавлением соответственно; поскольку Sql Server и Oracle не имеют оператора сдвига влево.
Эта абстракция гарантирована на 99% для работы во всей базе данных, кроме Oracle; Oracle SELECT
не может функционировать без какой-либо таблицы, для этого нужно выбрать из фиктивной таблицы, Oracle предоставил уже одну, она называется таблицей DUAL
. Переносимость базы данных - это мечта: -)
Здесь абстрагированные представления, которые работают на всех РСУБД, лишены побитовых операций (что в действительности не является необходимостью в любом случае в этом сценарии) и характерных нюансов (мы удаляем OR REPLACE
на CREATE VIEW
, только Postgresql и MySQL поддерживают их) среди всех основных баз данных.
Опасность для Oracle: просто поместите FROM DUAL
после каждого выражения SELECT
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;
Затем используйте этот запрос:
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: http://www.sqlfiddle.com/#!6/84bee/1
MySQL: http://www.sqlfiddle.com/#!2/78f5b/1
Ответ 2
Вы можете использовать таблицу чисел
SELECT value, count, number
FROM table
JOIN Numbers
ON table.count >= Numbers.number
Вот SQLFiddle с использованием MSSQL
Ответ 3
MySQL действительно является IE мира базы данных, это такое удержание, когда дело доходит до стандартов и функций.
Работает на всех основных СУБД, кроме 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
Ответ 4
Вы попросили решение db-agnostic, и @Justin дал вам хороший.
Вы также попросили
умные способы заставить его работать с любой базой данных
Есть один для PostgreSQL: generate_series()
делает то, что вы просили из коробки:
SELECT val, ct, generate_series(1, ct) AS index
FROM tbl;
Кстати, я бы предпочел не использовать value
и count
в качестве имен столбцов. Плохая практика использовать зарезервированные слова в качестве идентификаторов. Вместо этого используйте val
и ct
.
Ответ 5
Создать таблицу чисел - ее определение может немного отличаться в зависимости от платформы (это для SQL Server):
CREATE TABLE Numbers(Number INT PRIMARY KEY);
INSERT Numbers
SELECT TOP 1000 ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_columns;
Теперь этот temp также является SQL Server, но демонстрирует синтаксис соединения, который должен быть действительным для всех заданных вами RDBMS (хотя я буду признаться, что не использую их, поэтому я не могу проверить):
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];
Результаты (опять же, SQL Server):
value | count | index
------+-------+------
foo | 1 | 1
bar | 3 | 1
bar | 3 | 2
bar | 3 | 3
baz | 2 | 1
baz | 2 | 2
Ответ 6
Только для оценки SQL Server 2005 и более поздние версии могут обрабатывать это рекурсивно:
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 )
Ответ 7
Простым JOIN
вы можете достичь цели повторения записей n раз.
Следующий запрос повторяет каждую запись 20 раз.
SELECT TableName.*
FROM TableName
JOIN master.dbo.spt_values on type = 'P' and number < 20
Примечание для master.dbo.spt_values on type = 'P'
:
Эта таблица используется для получения серии чисел, которая жестко закодирована в ней по условию type='P'
.