2010-05-31 21 views
8

我有超過一表的簡單查詢,返回類似如下的結果:如何從將查詢返回多個行的查詢「合併」,「變平」或「旋轉」結果?

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:我知道我可以使用遊標,但我真的不喜歡,除非沒有更好的方法。

數據庫是Sybase版本12.5.4。

+1

編輯標籤有點「軸」是這裏最常用的術語,可能會得到適當的人在關注這個問題,如果你更新使用它,特別是在標題中。 – 2010-05-31 11:30:24

回答

6

由於使用select語句在Sybase中完成這項工作相當困難,所以我建議使用如下所示的while循環。 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 
+0

這種方法比我目前使用真實數據 – dsm 2010-06-04 13:20:23

+0

+1來產生正確結果所用的光標要長得多。我將讓問題保持未解決狀態,直到完成所有其他答案的評估 – dsm 2010-06-04 15:32:39

+0

@dsm性能問題的一個明顯原因是重新初始化表(如#temp)。如果您的真實表具有標識列或其他唯一標識,則可以省略表格重新創建步驟,因此您可以將該列用作計數器。 – 2010-06-04 15:55:56

1

這裏是一個解決方案:

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:另一種方法

;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 
+0

您使用的是哪個版本的Sybase?我似乎無法得到「FOR XML ...」的工作。我的版本是ASE 12.5.4 – dsm 2010-05-31 12:21:17

+0

至於第二種解決方案,是不是Oracle? – dsm 2010-05-31 12:30:21

+0

我用MSSQL做了這個解決方案,但第二個應該與Sybase一起工作,據我發現它支持常見的表達式 – 2010-05-31 12:36:01

1

我現在能想到的最好的是下一個:

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 

但結果與你輸入的有點不同...... !!!

+0

它太簡單了,但它不需要額外和困難的SQL編碼 – sgian76 2010-06-03 11:01:21

+0

請使用「代碼」塊格式化您的答案,以方便閱讀。 – Timothy 2010-06-03 11:41:59

+0

我試圖重新格式化您的代碼沒有運氣。你的解決方案確實適用於這個特定的例子,但現在假設我們想要多個ID相同,或重複ID_TYPE – dsm 2010-06-03 12:02:10

2

適用於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 
+0

你能解釋這個查詢是如何工作的嗎? – dsm 2010-06-03 14:23:13

+0

@dsm在update語句後面添加以下內容以查看它創建的字符串:'print「%1!:%2!」,@ str1,@ str2'。接下來的select語句根據'id'的值提取這些字符串的合適的子字符串。計算正確的長度時必須小心。 'case'語句是需要的,因爲在最後一個子字符串之後沒有',$'了,這表示子字符串的結尾。希望有所幫助。 – 2010-06-03 16:18:31

+0

我已經測試過這個,它對小MYTABLE工作正常,但是任意大的表都會打破它。結果如下開始顯示:62126 \t $ 16,17,6,6,22:$ 11,5,11,28,1 – dsm 2010-06-04 13:11:51

0

我沒有Sybase服務器進行測試,但閱讀的文檔在線,看來公用表表達式的支持。我對其他解決方案中使用的ROW_NUMBER不確定,因此這裏有一個解決方案不使用它。

我相信sybase使用||對於字符串連接,儘管我讀的文檔中提到'+'也可以使用,所以我使用了它。請適當更改。

我評論了這個查詢,試圖解釋發生了什麼。

該查詢使用相同的id連接所有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觸發器)中使用這個觸發器來基於此concatenate查詢維護一個表,請將基本情況(在UNION ALL之前)的WHERE子句擴展爲包含id = @ changed_id。這將確保只計算已更改ID的連接行。然後,您可以使用計算的連接行執行所需操作。如果您將連接查詢實現爲表,則刪除表中的@changed_id的當前連接行,並從上面的連接查詢的結果中插入新行。您還可以檢查連接表是否已經包含changed_id的值,並改爲使用UPDATE語句。

+0

這是無效的Sybase語法。無論如何謝謝:-) – dsm 2010-06-07 16:46:17

+0

我必須諮詢錯誤的手冊。你知道我在哪裏可以找到在線語法嗎?如果是這樣,我會在適當的地方重寫查詢。 – mdma 2010-06-07 17:00:00

+0

http://manuals.sybase。com/onlinebooks/group-as/asg1250e – dsm 2010-06-08 09:35:12

1

好吧,原諒我,如果我想的東西這裏至關重要,因爲我不瞭解Sybase的第一件事。但在mysql中,這是荒謬的簡單,所以我認爲它不可能像迄今爲止的答案一樣糟糕。因此,從可能相關或可能不相關的文檔中提取:

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

請通知我,如果我誤讀了某些內容,我會刪除它。

+0

yup ... mysql!= sybase。我真的希望這是這個簡單的壽...... – dsm 2010-06-05 04:30:33

+0

爲了我自己的薰陶,心靈的解釋是什麼不起作用? – 2010-06-10 21:32:29

+1

沒有列表功能 – dsm 2010-06-13 12:23:40

2

使用行級功能。

查詢:

select distinct id ,fn(id) from table1 

功能:

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