简体   繁体   English

如何从一个将多行返回到单个结果的查询中“合并”,“展平”或“旋转”结果?

[英]How can I “merge”, “flatten” or “pivot” results from a query which returns multiple rows into a single result?

I have a simple query over a table, which returns results like the following: 我对表有一个简单的查询,它返回如下结果:

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

And I would like to merge the results into a single row, for instance: 我想将结果合并为一行,例如:

id    concatenation 
2702  5,16,17,40:31,14,3,1
2703  23,23,34:4,5,6
2704  1:14

Is there any way to do this within a trigger? 有没有办法在触发器内执行此操作?

NB: I know I can use a cursor, but I would really prefer not to unless there is no better way. 注意:我知道我可以使用光标,但我真的不愿意,除非没有更好的方法。

The database is Sybase version 12.5.4. 该数据库是Sybase 12.5.4版。

Since it's rather difficult to get this done in Sybase using a select statement I would suggest a while loop like the following. 由于使用select语句在Sybase中完成此操作相当困难,我建议使用while循环,如下所示。 While loops are preferred over cursors for being much faster. 虽然循环优于游标,但速度要快得多。 Assuming that table name is MYTABLE: 假设表名是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

EDIT The following version is about 45% faster 编辑以下版本的速度提高了约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

Another approach that works on Sybase ASE 12.5.4. 另一种适用于Sybase ASE 12.5.4的方法。 The table must have a clustered index on id, in order for this to work. 该表必须在id上具有聚簇索引,以使其起作用。 Assuming that table name is MYTABLE: 假设表名是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

Use the row level function. 使用行级功能。

your query: 您的查询:

select distinct id ,fn(id) from table1

function: 功能:

fn(@id int)
(
declare @res varchar
select @res = @res+id_ref+"," from table1 where id=@id
return @res
)

Here is a solution: 这是一个解决方案:

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

UPDATE: Another approach 更新:另一种方法

;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

The best I could think now is the next one: 我现在能想到的最好的是下一个:

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

But the result is a bit different than the one you typed...!!! 但结果与你输入的结果有点不同...... !!!

Ok, forgive me if I'm missing something crucial here because I don't know the first thing about Sybase. 好的,请原谅我,如果我错过了一些关键的东西,因为我不知道关于Sybase的第一件事。 But in mysql, this is absurdly simple so I figured it couldn't be as bad as the answers so far. 但是在mysql中,这非常简单,所以我认为它到目前为止还不如答案那么糟糕。 So pulling from documentation that may or may not be relevant: 因此,从可能相关或不相关的文档中提取:

SELECT id, LIST(id_type) + ":" + LIST(id_ref) AS concatentation

Please inform me if I've misread something and I'll delete this. 如果我误读了某些内容,请通知我,我会删除它。

I don't have a sybase server to test, but reading the docs online, it appears that common table expressions are supported. 我没有要测试的sybase服务器,但是在线阅读文档,似乎支持公共表表达式。 I was unsure about ROW_NUMBER, as used in other solutions, so here is a solution that does not use that. 我不确定ROW_NUMBER,正如其他解决方案中所使用的那样,所以这里有一个不使用它的解决方案。

I believe sybase uses || 我相信sybase使用|| for string concatenation, although the docs I read mentions that '+' can also be used, so I've used that. 对于字符串连接,虽然我读过的文档提到“+”也可以使用,所以我用过它。 Please change as appropriate. 请根据需要进行更改。

I've commented the query to try to explain what is going on. 我已经对查询进行了评论,试图解释发生了什么。

The query concatenates all id_type and id_ref values with the same id, in increasing 'id_type' order. 查询以增加的“id_type”顺序将所有id_type和id_ref值连接到相同的id。

/* 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

The query is quite "brutish" in that it doesn't use more sophisticated features that might improve readability or possibly performance. 该查询非常“野蛮”,因为它不使用可能提高可读性或可能性能的更复杂功能。 I've done this since I don't know sybase well, and used those features that I'm reasonably confident are supported. 我已经这样做了,因为我不熟悉sybase,并且使用了那些我有理由相信的功能。 For best performance ensure id and (id,id_type) are indexed. 为获得最佳性能,请确保将id和(id,id_type)编入索引。

To use this in a trigger, such as an INSERT or UPDATE trigger to maintain a table based on this concatentate query, extend the WHERE clause of the base case (before UNION ALL) to include id=@changed_id. 要在触发器中使用它,例如INSERT或UPDATE触发器来维护基于此连接查询的表,请扩展基本案例的WHERE子句(在UNION ALL之前)以包括id = @ changed_id。 This will ensure only the concatenated row for the changed id is computed. 这将确保仅计算更改的id的连接行。 You can then do what you want with the computed concatenated row. 然后,您可以使用计算的连接行执行所需操作。 If you are materializing the concatenated query to a table, then DELETE the current concatenate row for @changed_id in the table, and INSERT a new row from the result of the concatenate query above. 如果要将连接查询具体化为表,则在表中删除@changed_id的当前连接行,并从上面的连接查询的结果中插入新行。 You could also check if your concatenate table already contains a value with the changed_id, and use an UPDATE statement instead. 您还可以检查您的连接表是否已包含changed_id的值,并使用UPDATE语句。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM