- PR -

SQLServerで複数行を1行にしたい。

1
投稿者投稿内容
お獅子
大ベテラン
会議室デビュー日: 2004/09/14
投稿数: 160
投稿日時: 2006-05-22 15:55
お世話になっております。

SQLserverのSELECT文で複数行を1行にしたのですが出来るのでしょうか?

例:1レコード目:項目1=1,項目2=100
  2レコード目:項目1=1,項目2=200
  3レコード目:項目1=1,項目2=300
  4レコード目:項目1=2,項目2=400
  5レコード目:項目1=2,項目2=500

これを↓
  1レコード目:項目1=1,項目2=100,項目2=200,項目2=300,項目2=0,項目2=0
  2レコード目:項目1=2,項目2=400,項目2=500,項目2=0,項目2=0,項目2=0

補足:項目1がキーとなります。1行にした場合項目2が入る項目を5個用意し対象データが無い場合は0をセットし6レコード以上は抽出しません。

拙い説明で申し訳ないですが、よろしくお願いします。
今川 美保(夏椰)
ぬし
会議室デビュー日: 2004/06/10
投稿数: 363
お住まい・勤務地: 神奈川県茅ヶ崎市
投稿日時: 2006-05-22 17:42
SQLServerのバージョンはいくつでしょうか?

とりあえずSQLServer2005Expressで以下のようなSQLで実現できるのかな?
という感じです。

ROW_NUMBERという関数で、取得するデータの通番をcol1毎に振り、
PIVODによって、通番の1〜5の値を取得するというSQLにしてみました。

「PIVOT と UNPIVOT の使用」
http://msdn2.microsoft.com/ja-jp/library/ms177410.aspx
を参考に組んでみました。

PIVODをはじめて使ったので、うまく満たせていない部分やおかしい部分が
あるかもしれませんが、
参考になれば幸いです。




サンプルデータ
コード:

TestDataテーブル
(両列ともvarchar(10)で定義)

col1 | col2
-------------
1 | 100
1 | 200
1 | 300
1 | 400
1 | 500
1 | 600
2 | 1000
2 | 2000
3 | 3000
3 | 10000
3 | 20000



作成SQL
コード:

select
col1,
COALESCE([1],0) '1行目' ,
COALESCE([2],0) '2行目',
COALESCE([3],0) '3行目',
COALESCE([4],0) '4行目',
COALESCE([5],0) '5行目'
from
(
select
row_number() over(partition by col1 order by col2) num,
col1,
col2
from TestTable
) P
Pivot
(
min(col2) for num In ([1],[2],[3],[4],[5])
) Base
;





実行結果
コード:

col1 | 1行目 | 2行目 | 3行目 | 4行目 | 5行目
----------------------------------------------
1 | 100 | 200 | 300 | 400 | 500
2 | 1000 | 2000 | 0 | 0 | 0
3 | 3000 | 10000 | 20000 | 0 | 0




#order by col1 → order by col2に修正(--;
#「PIVOT と UNPIVOT の使用」のリンクを追加

[ メッセージ編集済み 編集者: 夏椰 編集日時 2006-05-22 18:02 ]
明智重蔵
大ベテラン
会議室デビュー日: 2005/09/05
投稿数: 127
投稿日時: 2006-05-22 17:43
select 項目1,
isnull(max(case when Rank = 1 then 項目2 end),0),
isnull(max(case when Rank = 2 then 項目2 end),0),
isnull(max(case when Rank = 3 then 項目2 end),0),
isnull(max(case when Rank = 4 then 項目2 end),0),
isnull(max(case when Rank = 5 then 項目2 end),0),
isnull(max(case when Rank = 6 then 項目2 end),0)
from (select 項目1,項目2,Row_Number() over(partition by 項目1 order by 項目2) as Rank
from )
group by 項目1;
お獅子
大ベテラン
会議室デビュー日: 2004/09/14
投稿数: 160
投稿日時: 2006-05-22 18:14
ご回答ありがとうございます。

バージョンはSQLServer2000です。

残念ながら「ROW_NUMBER」は使えないようです。
明智重蔵
大ベテラン
会議室デビュー日: 2005/09/05
投稿数: 127
投稿日時: 2006-05-23 00:01
Row_Numberを相関サブクエリで代用すればいいですね
今川 美保(夏椰)
ぬし
会議室デビュー日: 2004/06/10
投稿数: 363
お住まい・勤務地: 神奈川県茅ヶ崎市
投稿日時: 2006-05-23 10:50
引用:

お獅子さんの書き込み (2006-05-22 18:14) より:
バージョンはSQLServer2000です。

残念ながら「ROW_NUMBER」は使えないようです。



そうですね、ROW_NUMBERもPIVOTも2005からの機能ですからね(^^)

とりあえず、2000でやるとしたら
私はスクリプト書くしか思いつかなかったので、
やってみました。
#MSDE2KRelAで動作確認済み

これをストアドファンクションなりプロシージャなり登録して
実行するという方法しか私には思いつきませんでした。

コード:
/* カーソル */
Declare @cur CURSOR ;
set @cur = CURSOR FOR
Select
	col1, col2
From Test1 base 
Where
  base.col2 in ( 
    select top 5 col2 from Test1 p where p.col1 = base.col1 ) 
;

/* 戻りデータ格納テーブル変数 */
Declare @tmp Table (
	col1 char(3),
	col21 char(3),
	col22 char(3),
	col23 char(3),
	col24 char(3),
	col25 char(3)
) ;


/* 前レコードの項目1 */
Declare @prev char(3) ;
/* インクリメント用変数 */
Declare @inc int ;

Set @prev = '' ;
Set @inc = 1 ;

/* 取得データ格納変数 */
Declare @col1 char(3) ;
Declare @col2 char(3) ;


/* 追加データ格納変数 */
Declare @tcol1 char(3) ;
Declare @tcol21 char(3) ;
Declare @tcol22 char(3) ;
Declare @tcol23 char(3) ;
Declare @tcol24 char(3) ;
Declare @tcol25 char(3) ;

set @tcol21 = '0' ;
set @tcol22 = '0' ;
set @tcol23 = '0' ;
set @tcol24 = '0' ;
set @tcol25 = '0' ;

/* カーソルオープン */
Open @cur ;

/* データがなくなるまでループ */
Fetch Next From @cur into @col1,@col2 ;
While @@FETCH_STATUS = 0
Begin

	/* 前回の項目1と今回の項目1が異なる場合 */
	If @prev <> @col1
	begin
		/* 前回項目が入っている場合(初回でない場合) */
		if len(@prev) > 0
		begin
			/* 戻り変数テーブルへデータを追加し、変数を再初期化 */
			insert into @tmp values(@tcol1,@tcol21,@tcol22,@tcol23,@tcol24,@tcol25 ) ;
			set @tcol21 = '0' ;
			set @tcol22 = '0' ;
			set @tcol23 = '0' ;
			set @tcol24 = '0' ;
			set @tcol25 = '0' ;
		end ;
		
		/* 変数初期化 */
		set @prev = @col1 ;
		set @tcol1 = @col1 ;
		Set @inc = 2 ;
		Set @tcol21 = @col2 ;
	End ;
	Else
	Begin
		
		/* インクリメントの数値により、設定する列を変える */
		if @inc = 2 
			Set @tcol22 = @col2 ;
		if @inc = 3 
			Set @tcol23 = @col2 ;
		if @inc = 4 
			Set @tcol24 = @col2 ;
		if @inc = 5 
			Set @tcol25 = @col2 ;
		
		set @inc = @inc+1 ;
	End ;
	Fetch Next From @cur into @col1,@col2 ;
End;

/* ループを抜けたときにデータが存在していたらインサートする */
if len(@prev) > 0
begin
	insert into @tmp values(@tcol1,@tcol21,@tcol22,@tcol23,@tcol24,@tcol25 ) ;
end ;

select * from @tmp ;

Close @cur ;




引用:

明智重蔵さんの書き込み (2006-05-23 00:01) より:
Row_Numberを相関サブクエリで代用すればいいですね


すみませんが、馬鹿な私にはおっしゃっている方法が思い浮かばないので、
SQLを提示してもらえますか?
#どういう感じのSQLを書けば出来るとおっしゃっているのか
#わからなかったので・・・・

ぜひ勉強したいのでお願いします(^^)

_________________
夏椰 @ わんくま同盟
夏椰の庵
Microsoft MVP for Windows Server System - SQL Server ( Jul 2006 - Jun 2008 )
明智重蔵
大ベテラン
会議室デビュー日: 2005/09/05
投稿数: 127
投稿日時: 2006-05-24 19:35
http://d.hatena.ne.jp/CAMUS/20060105
http://oraclesqlpuzzle.hp.infoseek.co.jp/3-22.html
http://fukkey.dyndns.org/cgi-bin/namazu.cgi?idxname=sqls&max=100&sort=score&query=%B9%D4%C8%D6%B9%E6&whence=0&reslt=normal&sort=score


Rankとdense_rankは、相関サブクエリで代用できますが
Row_Numberは、SQLServer2000では、ちょっと無理っぽいですね
oracleならRowIDもしくはRowNumを使えば、対応できますが

まあ、同一レコードが存在するような設計になってない限り
問題なさそうですがね
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2006-05-24 19:46
引用:

お獅子さんの書き込み (2006-05-22 15:55) より:
これを↓
  1レコード目:項目1=1,項目2=100,項目2=200,項目2=300,項目2=0,項目2=0
  2レコード目:項目1=2,項目2=400,項目2=500,項目2=0,項目2=0,項目2=0

補足:項目1がキーとなります。1行にした場合項目2が入る項目を5個用意し対象データが無い場合は0をセットし6レコード以上は抽出しません。


こういう要求は掲示板では良く見かけるのですが、結果を正規化しないで返すことになっているため、RDB 本来の使用方法から逸れています。そのため特別な理由がなければやらないほうが良いのではないでしょうか。レコードセットを受け取る側で、キーブレークの手法を使ったほうが簡単だと思います。
特別な理由としては、既存のプログラムの変更を少なくしたい、や、使うミドルウェアが SQL のレコードセットしか受け付けないとか、SQL の限界までチャレンジしたい、とかいろいろありますけど、新規に開発するのであれば避けられたほうがよいのではないでしょうか。まあ、ケースバイケースの面も大きいですが。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
1

スキルアップ/キャリアアップ(JOB@IT)