У меня есть простой запрос за столом, который возвращает результаты как следующее:
id id_type id_ref
2702 5 31
2702 16 14
2702 17 3
2702 40 1
2703 23 4
2703 23 5
2703 34 6
2704 1 14
И я хотел бы объединить результаты в одну строку, например:
id concatenation
2702 5,16,17,40:31,14,3,1
2703 23,23,34:4,5,6
2704 1:14
Там какой-либо путь состоит в том, чтобы сделать это в триггере?
NB: я знаю, что могу использовать курсор, но я действительно предпочел бы не тому, если нет никакого лучшего пути.
База данных является версией 12.5.4 Sybase.
Поскольку сделать это в Sybase с помощью оператора select довольно сложно, я бы предложил цикл while
, как показано ниже. В то время как циклы предпочтительнее курсоров, поскольку они намного быстрее. Предполагая, что имя таблицы - MYTABLE:
CREATE TABLE #temp
(
aa numeric(5,0) identity,
id int not null,
id_type int not null,
id_ref int not null
)
CREATE TABLE #results
(
id int not null,
concatenation varchar(1000) not null,
)
insert into #temp
select id, id_type, id_ref from MYTABLE order by id
declare @aa int, @maxaa int, @idOld int, @idNew int
declare @str1 varchar(1000), @str2 varchar(1000)
set @aa = 1
set @maxaa = (select max(aa) from #temp)
set @idNew = (select id from #temp where aa = 1)
, @idOld = @idNew
while @aa <= @maxaa
begin
set @idNew = (select id from #temp where aa = @aa)
IF @idNew = @idOld
BEGIN
set @str1 = @str1 + convert(varchar,(select id_type from #temp where aa = @aa)) + ','
, @str2 = @str2 + convert(varchar,(select id_ref from #temp where aa = @aa)) + ','
IF @aa = @maxaa
insert into #results (id, concatenation)
VALUES (@idOld, left(@str1,len(@str1) - 1) + ':' + left(@str2,len(@str2) - 1) )
END
ELSE
BEGIN
insert into #results (id, concatenation)
VALUES (@idOld, left(@str1,len(@str1) - 1) + ':' + left(@str2,len(@str2) - 1) )
set @str1 = NULL, @str2 = NULL
set @str1 = @str1 + convert(varchar,(select id_type from #temp where aa = @aa)) + ','
, @str2 = @str2 + convert(varchar,(select id_ref from #temp where aa = @aa)) + ','
IF @aa = @maxaa
insert into #results (id, concatenation)
VALUES (@idNew, left(@str1,len(@str1) - 1) + ':' + left(@str2,len(@str2) - 1) )
END
set @idOld = @idNew
set @aa = @aa+1
end
select * from #results
РЕДАКТИРОВАТЬ Следующая версия примерно на 45% быстрее
CREATE TABLE #temp
(
aa numeric(5,0) identity,
id int not null,
id_type int not null,
id_ref int not null
)
CREATE TABLE #results
(
id int not null,
concatenation varchar(1000) not null,
)
insert into #temp
select id, id_type, id_ref from MYTABLE order by id
declare @aa int, @maxaa int, @idOld int, @idNew int
declare @str1 varchar(1000), @str2 varchar(1000), @j int
set @aa = 1
set @maxaa = (select max(aa) from #temp)
set @idNew = (select id from #temp where aa = 1)
, @idOld = @idNew
set @str1 = ':'
while @aa <= @maxaa
begin
set @idNew = (select id from #temp where aa = @aa)
IF @idNew = @idOld
BEGIN
set @str2 = (select convert(varchar,id_type) + ':' + convert(varchar,id_ref) from #temp where aa = @aa)
set @j = (select charindex(':',@str2))
set @str1 = str_replace(@str1, ':', substring(@str2,1,@j - 1) + ',:') + right(@str2,len(@str2) - @j) + ','
IF @aa = @maxaa
insert into #results (id, concatenation)
VALUES (@idOld, left(str_replace(@str1, ',:', ':'),len(@str1) - 2) )
END
ELSE
BEGIN
insert into #results (id, concatenation)
VALUES (@idOld, left(str_replace(@str1, ',:', ':'),len(@str1) - 2) )
set @str1 = ':'
set @str2 = (select convert(varchar,id_type) + ':' + convert(varchar,id_ref) from #temp where aa = @aa)
set @j = (select charindex(':',@str2))
set @str1 = str_replace(@str1, ':', substring(@str2,1,@j - 1) + ',:') + right(@str2,len(@str2) - @j) + ','
IF @aa = @maxaa
insert into #results (id, concatenation)
VALUES (@idNew, left(str_replace(@str1, ',:', ':'),len(@str1) - 2) )
END
set @idOld = @idNew
set @aa = @aa+1
end
select * from #results
У меня нет сервера sybase для тестирования, но, читая документацию в Интернете, кажется, что поддерживаются общие табличные выражения. Я не был уверен в том, что ROW_NUMBER используется в других решениях, поэтому вот решение, которое его не использует.
Я считаю, что Sybase использует || для конкатенации строк, хотя в документах, которые я читал, упоминается, что также можно использовать '+', поэтому я использовал это. Пожалуйста, измените при необходимости.
Я прокомментировал запрос, чтобы попытаться объяснить, что происходит.
Запрос объединяет все значения id_type и id_ref с одним и тем же идентификатором в порядке возрастания id_type.
/* a common table expression is used to concatenate the values, one by one */
WITH ConcatYourTable([id], /* the id of rows being concatenated */
concat_id_type, /* concatenated id_type so far */
concat_id_ref, /* concatenated id_ref so far */
last_id_type, /* the last id_type added */
remain) /* how many more values are there to concatenate? */
AS
(
/* start with the lowest id_type value for some id */
SELECT id, id_type, id_ref,
id_type, /* id_type was concatentated (it's presently the only value) */
(SELECT COUNT(*) FROM YourTable f2 WHERE f2.id=f.id)-1
/* how many more values to concatenate -1 because we've added one already */
FROM YourTable f
WHERE NOT EXISTS
/* start with the lowest value - ensure there are no other values lower. */
(SELECT 1 FROM YourTable f2 WHERE f2.id=f.id AND f2.id_type<f.id_type)
UNION ALL
/* concatenate higher values of id_type for the same id */
SELECT f.id,
c.id_type + ',' + f.id_type, /* add the new id_type value to the current list */
c.id_ref + ',' + f.id_ref, /* add the new id_ref value to the current list */
f.id_type, /* the last value added - ensured subsequent added values are greater */
c.remain-1 /* one less value to add */
FROM ConcatYourTable c /* take what we have concatenated so far */
INNER JOIN YourTable f /* add another row with the same id, and > id_type */
ON f.id = c.id AND f.id_type > c.last_id_type
/* we really want the next highest id_type, not just one that is greater */
WHERE NOT EXISTS (SELECT 1 FROM YourTable f2
WHERE f2.id=f.id AND f2.id_type<f.id_type AND
f2.id_type>c.last_id_type)
)
/* Select the rows where all values for and id were concatenated (remain=0) */
/* Concatenate the cumulated id_type and id_ref fields to format id_type values:id_ref values*/
SELECT id, id_type+':'+id_ref FROM ConcatYourTable
WHERE remain=0
Запрос довольно «грубый» в том смысле, что в нем не используются более сложные функции, которые могли бы улучшить читаемость или, возможно, производительность. Я сделал это, так как плохо знаю sybase, и использовал те функции, которые, как я уверен, поддерживаются. Для лучшей производительности убедитесь, что id и (id, id_type) проиндексированы.
Чтобы использовать это в триггере, таком как триггер INSERT или UPDATE для поддержки таблицы на основе этого объединенного запроса, расширьте предложение WHERE базового случая (перед UNION ALL), чтобы включить id = @ changed_id. Это обеспечит вычисление только объединенной строки для измененного идентификатора. Затем вы можете делать все, что хотите, с вычисленной объединенной строкой. Если вы материализуете объединенный запрос в таблицу, тогда УДАЛИТЬ текущую строку объединения для @changed_id в таблице и ВСТАВИТЬ новую строку из результата указанного выше запроса объединения. Вы также можете проверить, содержит ли ваша таблица конкатенации уже значение с changed_id, и вместо этого использовать оператор UPDATE.
Другой подход, который работает на Sybase ASE 12.5.4. Таблица должна иметь кластеризованный индекс на id, чтобы это работало. Предположим, что имя таблицы MYTABLE:
declare @strNew varchar(10), @strOld varchar(10), @str1 varchar(1000), @str2 varchar(1000)
set @str1 = NULL, @str2 = NULL, @strNew = NULL, @strOld = NULL
UPDATE MYTABLE
SET @strNew = convert(varchar,id)
, @str1 = case when @strNew = @strOld then @str1 + convert(varchar,id_type) + "," else @str1 + '$' + @strNew + '$' + convert(varchar,id_type) + "," end
, @str2 = case when @strNew = @strOld then @str2 + convert(varchar,id_ref) + "," else @str2 + '$' + @strNew + '$' + convert(varchar,id_ref) + "," end
, @strOld = convert(varchar,id)
select id, substring(@str1,charindex("$" + convert(varchar,id) + "$",@str1) + len("$" + convert(varchar,id) + "$"),
case when
charindex(",$",substring(@str1,charindex("$" + convert(varchar,id) + "$",@str1) + len("$" + convert(varchar,id) + "$") + 1,len(@str1)))
= 0 then len(@str1) - (charindex("$" + convert(varchar,id) + "$",@str1) + len("$" + convert(varchar,id) + "$"))
else
charindex(",$",substring(@str1,charindex("$" + convert(varchar,id) + "$",@str1) + len("$" + convert(varchar,id) + "$") + 1,len(@str1)))
end
)
+ ':' +
substring(@str2,charindex("$" + convert(varchar,id) + "$",@str2) + len("$" + convert(varchar,id) + "$"),
case when
charindex(",$",substring(@str2,charindex("$" + convert(varchar,id) + "$",@str2) + len("$" + convert(varchar,id) + "$") + 1,len(@str2)))
= 0 then len(@str2) - (charindex("$" + convert(varchar,id) + "$",@str2) + len("$" + convert(varchar,id) + "$"))
else
charindex(",$",substring(@str2,charindex("$" + convert(varchar,id) + "$",@str2) + len("$" + convert(varchar,id) + "$") + 1,len(@str2)))
end
) as concatenation
from MYTABLE
group by id
Вот решение:
SELECT DISTINCT
id,
concatenation = LEFT(id_types, LEN(id_types) - 1) + ':' + LEFT(id_refs, LEN(id_refs) - 1)
FROM (
SELECT id,
id_types = (SELECT CAST(b.id_type AS nvarchar) + ',' FROM Table1 b WHERE b.id = a.id FOR XML PATH('')),
id_refs = (SELECT CAST(c.id_ref AS nvarchar) + ',' FROM Table1 c WHERE c.id = a.id FOR XML PATH(''))
FROM Table1 a
) t
ОБНОВЛЕНИЕ: Другой подход
;WITH r(id, rnk, id_type, id_ref) AS
(
SELECT id,
rnk = ROW_NUMBER() OVER(ORDER BY id),
id_type = CAST(id_type AS nvarchar(MAX)),
id_ref = CAST(id_ref AS nvarchar(MAX))
FROM Table1
), anchor(id, rnk, id_type, id_ref) AS
(
SELECT id,
rnk,
id_type,
id_ref
FROM r
WHERE rnk = 1
), result(id, rnk, id_type, id_ref) AS
(
SELECT id,
rnk,
id_type,
id_ref
FROM anchor
UNION ALL
SELECT r.id,
r.rnk,
result.id_type + ',' + r.id_type,
result.id_ref + ',' + r.id_ref
FROM r
INNER JOIN result ON r.id = result.id AND r.rnk = result.rnk + 1
)
SELECT id, concatenation = MAX(id_type) + ':' + MAX(id_ref)
FROM result
GROUP BY id
Хорошо, простите меня, если я упускаю что-то решающее, потому что я ничего не знаю о Sybase. Но в mysql это абсурдно просто, поэтому я решил, что все не может быть так плохо, как в ответах до сих пор. Итак, извлечение из документации, которая может быть или не быть релевантной:
SELECT id, LIST(id_type) + ":" + LIST(id_ref) AS concatentation
Пожалуйста, сообщите мне, если я что-то неправильно понял, и я удалю это.
Лучшее, что я мог подумать сейчас, это следующий:
select a.id id,
str (a.id_type,4,0)||
','||str (b.id_type,4,0)||
','||str (c.id_type,4,0)||
','||str (d.id_type,4,0)||
','||str (e.id_type,4,0)||':'||
str (a.id_ref,4,0)||
','||str (b.id_ref,4,0)||
','||str (c.id_ref,4,0)||
','||str (d.id_ref,4,0)||
','||str (e.id_ref,4,0) concatenation
from dbo.merge_test a,
dbo.merge_test b,
dbo.merge_test c,
dbo.merge_test d,
dbo.merge_test e
where a.id = b.id
and a.id = b.id
and a.id = c.id
and a.id = d.id
and a.id = e.id
and a.id_type < b.id_type
and b.id_type <c.id_type
and c.id_type < d.id_type
and d.id_type < e.id_type
Но результат немного отличается от того, который вы ввели ... !!!