トップ回答者
DBのテーブルからレコード数によって文字列として作成し利用する方法

質問
-
たとえばテーブルにレコードが3レコードあるとき, 1 TIME, 2 TIME, 3 TIMEレコードが5レコードあるとき, 1 TIME, 2 TIME, 3 TIME, 4 TIME, 5 TIMEというように文字列を作成するにはどのような方法があるでしょうか?
for (int x = 1; x <= Cntds.Tables["table"].Rows.Count; ++x){labelValFields.Text = x.ToString();}のようにしてラベルにレコード数を表示することはぐらいならわかるのですがそれを上記のように文字列として作成し利用する方法がわかりません。最終的に、動的にフィールドを作成したいです。
回答
-
以下のように書いた方が速いです。
StringBuilder s = new StringBuilder(); int c = Cntds.Tables["table"].Rows.Count; for (int i = 1; i <= c; i++) { s.AppendFormat(", {0} TIME", i); } labelValFields.Text = s.ToString();
検証の過程をブログにエントリしたので、よかったら見てください。
Blog:プログラマーな日々 http://d.hatena.ne.jp/JHashimoto/- 回答としてマーク oira3ryu 2010年12月25日 5:03
-
oira3ryuさんが理由をご理解されているかどうかわからなかったので、おせっかいかもしれませんが書いておきますね。
string型というのは参照型なのですが、値型のような動きをするように設計されています。文字列の操作としてはこちらの方が直感的にわかりやすいからでしょう。よって、string型に値を代入するというのは、実はstring型のインスタンスを生成して放りこんでいるのです。ですから、何度も代入すれば何度もインスタンスを生成することになり、速度的には不利になります。インスタンスを生成するコストは高いからです。
string型は不変であるため新しい値を代入する時には上述したように新しいインスタンスを生成して代入し直すイメージになりますが、StringBuilderは内部でバッファ管理しており、その長さは可変です。したがって文字列連結を繰り返す場合でもバッファの変更になりますから、StringBuilderの方がパフォーマンスが良くなります。今回のようにループ内での処理はStringBuilderが適していると思いますが、一般的には、コードの読みやすさや、文字列を置き換える頻度などを考えて決めるべきだと思います。もう一つの変数に格納する件ですが、Cntds.Tables["table"]を実行すると"table"という文字列を元にデーターテーブルを検索しに行きます。ですから、何度も検索すれば遅くなってしまいます。検索した結果が既に手元にあるのに、何度も同じ検索をして同じ値を得るのは無駄ですよね。もう少し工夫をすれば、データーテーブルが一つしか無いのであれば、Cntds.Tables[0]の方が文字列で検索を行なわない分、速くなるはずです(テーブル名で検索するよりインデックスを使った方が速いということを意味します)。ただ、前述した通り、人間がほとんど体感できないほどの速度の差であれば(コンパイラが最適化してくれることもあります)、通常はコードの読みやすさを個人的には優先したいと思います。
(追記)
参考までにString.Joinというのもあって、文字列配列を連結する場合はこちらを使うと良いようですよ。文字列の連結
http://blogs.bitlan.net/ito/?p=1013
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 編集済み trapemiyaModerator 2010年12月25日 8:22 String.Joinを追記
- 回答としてマーク oira3ryu 2010年12月25日 10:20
すべての返信
-
最終的な目的がはっきりわかっていないのですが、以下のようなことでしょうか?(コードは未検証です)
for (int x = 1; x <= Cntds.Tables["table"].Rows.Count; ++x){labelValFields.Text += x.ToString() + "TIME, ";}labelValFields.Text = labelValFields.Text.Substring(0, labelValFields.Text.Length - 2);
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 編集済み trapemiyaModerator 2010年12月24日 16:25 コード修正
-
以下のように書いた方が速いです。
StringBuilder s = new StringBuilder(); int c = Cntds.Tables["table"].Rows.Count; for (int i = 1; i <= c; i++) { s.AppendFormat(", {0} TIME", i); } labelValFields.Text = s.ToString();
検証の過程をブログにエントリしたので、よかったら見てください。
Blog:プログラマーな日々 http://d.hatena.ne.jp/JHashimoto/- 回答としてマーク oira3ryu 2010年12月25日 5:03
-
tarpemiyaさん、いつもありがとうございます。
質問がわかりにくくすいません。
最終的な目的は、
テーブルA
ID | POINT
---+-----------
1 | 第4P
---+-----------
2 | 第9P
---+-----------
3 | ゴールP
---+-----------
という3レコードがある場合
, 1 TIME, 2 TIME, 3 TIMEというような文字列を生成して
CREATE TEMP TABLE tmp_table ( p_id integer, 1 TIME, 2 TIME, 3 TIME , PRIMARY KEY (p_id)) ON COMMIT DROP;
というようなSQLを動的に生成したくその過程でした。
それを確認するためにラベルに出力したかったのです。
-
oira3ryuさんが理由をご理解されているかどうかわからなかったので、おせっかいかもしれませんが書いておきますね。
string型というのは参照型なのですが、値型のような動きをするように設計されています。文字列の操作としてはこちらの方が直感的にわかりやすいからでしょう。よって、string型に値を代入するというのは、実はstring型のインスタンスを生成して放りこんでいるのです。ですから、何度も代入すれば何度もインスタンスを生成することになり、速度的には不利になります。インスタンスを生成するコストは高いからです。
string型は不変であるため新しい値を代入する時には上述したように新しいインスタンスを生成して代入し直すイメージになりますが、StringBuilderは内部でバッファ管理しており、その長さは可変です。したがって文字列連結を繰り返す場合でもバッファの変更になりますから、StringBuilderの方がパフォーマンスが良くなります。今回のようにループ内での処理はStringBuilderが適していると思いますが、一般的には、コードの読みやすさや、文字列を置き換える頻度などを考えて決めるべきだと思います。もう一つの変数に格納する件ですが、Cntds.Tables["table"]を実行すると"table"という文字列を元にデーターテーブルを検索しに行きます。ですから、何度も検索すれば遅くなってしまいます。検索した結果が既に手元にあるのに、何度も同じ検索をして同じ値を得るのは無駄ですよね。もう少し工夫をすれば、データーテーブルが一つしか無いのであれば、Cntds.Tables[0]の方が文字列で検索を行なわない分、速くなるはずです(テーブル名で検索するよりインデックスを使った方が速いということを意味します)。ただ、前述した通り、人間がほとんど体感できないほどの速度の差であれば(コンパイラが最適化してくれることもあります)、通常はコードの読みやすさを個人的には優先したいと思います。
(追記)
参考までにString.Joinというのもあって、文字列配列を連結する場合はこちらを使うと良いようですよ。文字列の連結
http://blogs.bitlan.net/ito/?p=1013
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 編集済み trapemiyaModerator 2010年12月25日 8:22 String.Joinを追記
- 回答としてマーク oira3ryu 2010年12月25日 10:20
-
trapemiyaさん、くわしい説明していただきありがとうございます。
(おせっかいではないと思いますけど、おせっかいありがとうございます!)
String.Joinに関する参考リンク拝見しました。ちょっと私の理解力が足らず
現状を置き換えられませんでした。あとでまた試してみたいと思います。
今回、フォームのレコードソースとなる一時テーブルを作成するSQLの一部を変数として
その元になるテーブルのレコード数を変数に格納してそれを配列のように展開すれば
動的にフィールドを生成できるのではと思い質問させていただきましたが、
このような方法が正しいかも含めて参考などありましたらお願いします。
-
String.Joinを試しているのですが
int c = Cntds.Tables["sector"].Rows.Count;string result;int[] _Array = new int[c];for (int i = 0; i < _Array.Length; i++){_Array[i] = i;result = String.Join(",", _Array[i]);}labelValFields.Text = result;
最後のlabelValFields.Text = result;でエラーになってしまいます。
私にはちょっと難しいです。 -
複数のレコードを1レコードの結果として受け取りたいということでしょうか? であれば、少し長いですが、以下のやり取りの中でいろいろな方法が紹介されていますので参考になると思います。
横持ちと縦持ち
http://blogs.wankuma.com/ognac/archive/2009/06/09/174556.aspx「その5」まであります。以下で参照できます。
6月 2009 Entries
http://blogs.wankuma.com/ognac/archive/2009/06.aspxまた、「sql 行 列 入れ替え」などのキーワードで検索されても参考になるページが見つかると思います。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/ -
最後のlabelValFields.Text = result;でエラーになってしまいます。
resultを初期化していないからでしょう。
string result = "";
で良いはずです。でも、コードにあまり意味がないような・・・。以下のような感じをされたいのでしょうか?int c = Cntds.Tables["sector"].Rows.Count;string result = "";int[] _Array = new int[c];for (int i = 0; i < _Array.Length; i++){_Array[i] = i;}result = String.Join(",", _Array);labelValFields.Text = result;
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/ -
trapemiyaさん、言葉足らずな説明下手な質問で
混乱・遠回りさせてしまっているようで申し訳ありません。
そのうえでお答えいただきありがとうございます。
私も「縦持ち」のデータの持ち方がよいのではないかという考えで
今回のDBのテーブルは「縦持ち」なのですが
今回、質問しているテーブルは表示・印刷のみに使用する「一時テーブル」で
そのテーブルの特定のテーブルに紐付く部分のフィールドのみを
複数のレコードを1レコードの結果として「横持ち」したいということなのです
具体的に言いますと
このテーブルB(縦持ち)には
競技の記録が
開催ID | 選手ID | 区間ID | time
------+------+------+-----
2 | 129 | 2 | 09:23:51
------+------+------+-----
2 | 101 | 1 | 09:23:56
--+-----+---+---------
2 | 006 | 1 | 09:51456
--+-----+---+---------
2 | 101 | 2 | 10:45:01
--+-----+---+---------
2 | 129 | 2 | 11:42:08
--+-----+---+---------
2 | 101 | 3 | 12:01:41
--+-----+---+---------
2 | 129 | 3 | 12:02:01
というような形で格納されています。
この記録計測ポイントが開催回によって変動するので
テーブルA (縦持ち)
開催ID | 区間ID | POINT
-------+--------+-----------
2 | 1 | 第4P
-------+--------+-----------
2 | 2 | 第9P
-------+--------+-----------
2 | 3 | ゴールP
-------+--------+-----------
3 | 1 | 第1P
-------+--------+-----------
3 | 2 | 第3P
-------+--------+-----------
3 | 3 | 第5P
-------+--------+-----------
3 | 4 | ゴールP
上記の例ならテーブルAは開催IDが2の時、3レコードなので
表示・印刷のみに使用する「一時テーブル」を作成するために(横持ち)で
CREATE TEMP TABLE tmp_table ( p_id integer, 1 TIME, 2 TIME, 3 TIME , PRIMARY KEY (p_id)) ON COMMIT DROP;
の, 1 TIME, 2 TIME, 3 TIMEを動的に生成したかったということなのです。
(開催IDが3の時、4レコードなので
CREATE TEMP TABLE tmp_table ( p_id integer, 1 TIME, 2 TIME, 3 TIME, 4 TIME, PRIMARY KEY (p_id)) ON COMMIT DROP;
となるということです)
一応、説明を補足させていただきました。
-
列数が動的に変化するのでどうしてもSQL文を組み立てる必要がありますね。参考までにさくっとストアドプロシージャを作ってみました。
CREATE PROCEDURE dbo.ランニングタイム @開催ID int, @選手ID int AS BEGIN declare @sqlstr nvarchar(max), @sqldatas nvarchar(max), @target区間ID nvarchar(10), @targetポイント nvarchar(20), @count int = 0 declare cur cursor for SELECT DISTINCT 区間ID, POINT FROM テーブルA where 開催ID = @開催ID set @sqldatas = '' set @sqlstr = 'SELECT 開催ID' open cur ; fetch next from cur into @target区間ID, @targetポイント while (@@fetch_status <> -1 ) begin if len(@sqldatas) > 0 begin set @sqldatas = @sqldatas + ',' end set @sqldatas = @sqldatas + '[' + @target区間ID + ']' set @count = @count + 1 set @sqlstr = @sqlstr + ',[' + @target区間ID + '] as [TIME' + convert(nvarchar, @count) + '(' + @targetポイント + ')]' fetch next from cur into @target区間ID, @targetポイント end close cur deallocate cur set @sqlstr = @sqlstr + ' from (select T1.開催ID, T1.区間ID, T2.time from dbo.テーブルA as T1 inner join dbo.テーブルB as T2 on T1.開催ID = T2.開催ID and T1.区間ID = T2.区間ID where T1.開催ID = @開催ID and 選手ID = @選手ID) as T pivot (min(time) for 区間ID in ' set @sqlstr = @sqlstr + '(' + @sqldatas + ')) AS PIVOT_TABLE' -- select @sqlstr exec sp_executesql @sqlstr, N'@開催ID int, @選手ID int', @開催ID, @選手ID END
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/ -
-
佐祐理さん、ありがとうございます。
labelValFields.Text = Cntds.Tables["table"].Rows.Count.ToString(); が書けない時点でかなり問題です。レコード数を数えるためにループを回してるようじゃ、ね。
なんて書けばよいかうまく表現できず。説明の仕方が悪かったようです。
labelValFields.Text = Cntds.Tables["table"].Rows.Count.ToString();
で、レコード数を表示できるのはわかっていますが
最初の質問でのループはそれ単にを表示させるために書いたわけではないです。
どうやったら、1,2,3と表示することができるのだろうかと....。
でも、私のレベルはどっちにしてもそんなものですので、
こちらで質問するなんて不適切だということなのですよね。
すいませんでした。みなさん、ありがとうございました。
-
そういう意味で書いたわけではありません。
最初の質問でのループはそれ単にを表示させるために書いたわけではないです。
どうやったら、1,2,3と表示することができるのだろうかと....。
少なくとも私にはテレパシーのスキルがないので、そのような意図を質問文からは読み取ることができませんでした。逆に「のようにしてラベルにレコード数を表示することはぐらいならわかる」と書かれていますから、oria3ryuさんが類似例を持ち出して自身のスキルレベルを説明しているのだと素直に受け取りました。(そして、ループを回さないとレコード数が取得できないレベルと受け取りました。)
そして先の私のコメントは、ループを回さないとレコード数が取得できないレベルの人に対してストアドプロシージャ云々は難しすぎるのでは?と思い、他の回答者の方々に向けて書いたつもりでした。
-
そして先の私のコメントは、ループを回さないとレコード数が取得できないレベルの人に対してストアドプロシージャ云々は難しすぎるのでは?と思い、他の回答者の方々に向けて書いたつもりでした。
oira3ryuさんの以下の発言が元になっています。
>このような方法が正しいかも含めて参考などありましたらお願いします。
しかし、oira3ryuさんの上の発言がなかったとしてもストアドプロシージャの例は書き込んでいたでしょう。もちろん、oira3ryuさんにこのストアドプロシージャで今すぐにやって欲しいわけではありませんし、強要することもできません。いつか役に立つようにと思って参考として載せています。また、掲示板は多くの方が見る場ですし、pivotを使った例は少なく、微力ながら少しでも多くの方に役に立つことも期待しています。(SQL Serverをご使用じゃないかもしれませんが(^^;)
>oira3ryuさんへ
oira3ryuさんの立てたご質問のスレッドですから、このスレッドをリードするのはあくまでoira3ryuさんです。難しい内容があれば何度も質問されてチャレンジされても良いですし、とりあえず今回は時間が無いのであきらめますでも全然かまいません。ただ私は初心者の方でも頭の中に目次を作って欲しいと思っています。こういう時にはこの辺りを調べればいいんだという目次です。キーワードだけの目次でもかまいません。今はそれだけでもよいと思います。String.Joinをご紹介したのもそのような気持ちがあってのことです。
正直、oira3ryuさんが私が掲載したストアドプロシージャを理解できるレベルに達していないことはわかっています。ですから、あくまで参考という形で載せましたが、確かに面喰ってしまい途方に暮れてしまうかもしれませんね。その点はもう少しフォローして書けばよかったかなと反省しています。佐祐理さんのご指摘で気づきました。ありがとうございます。
私はあくまでoira3ryuさんが実現されたいことを実現されたい手段でお手伝いできたらと思っています。ただ、こんな方法もあるし、関連して理解をしてほしいというお節介もあります。特に初心者レベルの人にはいろいろと余計にお節介をしたくなる性格なもので・・・(^^; 誰でも最初は初心者ですし、私だって今でこそMVPに推薦されてなっていますが、いつでもみなさんと一緒に勉強していくスタンスですし、教わることもたくさんあります。えっ、MVPなのにこんなことも知らないの!なんて、こともありますよ。永遠の初心者ですよ。
C#などを勉強し始めのころは私も掲示板でお世話になりました。ちょっと知っている人もそうじゃない人も一緒になって、よりよい問題の解決方法が見つかり、勉強になればいいんじゃないですかね。#(追記)
>正直、oira3ryuさんが私が掲載したストアドプロシージャを理解できるレベルに達していないことはわかっています。失礼な言い方だったかもしれません。決して悪気があったわけではありません。以下のように訂正させていただきます。m(_ _)m
「正直、oira3ryuさんが私が掲載したストアドプロシージャを理解できるレベルに達していないだろうと想像しています。そもそそも私の感覚ではPIVOTを使用されている方はまだまだ少ないと思いますし、PIVOTでも2つのテーブルをjoinする例もほとんど無いのではないかと思います。でもこういうやり方をすると縦横変換をして表示できるという例を見ていただきたかったのです。いずれPIVOTを習得されると必ず役に立つと思うのです。」
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 編集済み trapemiyaModerator 2010年12月26日 12:57 追記
-
trapemiyaさん、ありがとうございます。
発言を訂正までさせてしまったこと、大変ご迷惑をお掛けしてしまっているような気がしてなりません。
お手伝いどころか、ご助言いただけてありがたく思っております。
正直、使用しているRDBMSがpostgresqlで、その簡単なストアドファンクションが書ける程度なので
想像に反せずsqlserverのストアドプロシージャ、ましてPIVOTとなると理解できていないのが現状です。
でも、このようなサイトの皆さんのおかげで日々解ってくるのが楽しいです。
これからも質問させていただきますのでよろしくお願いいたします。
-
発言を訂正までさせてしまったこと、大変ご迷惑をお掛けしてしまっているような気がしてなりません。
いいえとんでもないです。私こそお使いのデーターベースを確認しないままにSQL Serverのストアドプロシージャを紹介してしまうという、いたらないところが多々あって申し訳ないです。そういういたらない面もありますが、いろんな問題を一緒に考えながら、その過程でいろんなことを教わりながら、自分が進歩していくのが楽しいんです。結局、プログラミングが好きなんですよね。自分でそう思います(^^; ちなみにpostgresqlはほとんど触ったことがありません。全くわからないと言ってもいいです。もし、postgresqlを弄る必要に迫れらた時は、oira3ryuさんにお世話になるかもしれませんよ。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/ -
うーん…ほとんど繰り返しになりますが、
「ラベルにレコード数を表示することはぐらいならわかるのですが」~「文字列を作成するにはどのような方法があるでしょうか?」
と書かれていて、他の回答者の方々は文字列の作成方法の説明、そこから脱線までしていました。しかし、「わかる」とまで書いているコードがレコード数を数えるためにわざわざループを回さないと実現できないレベルで、わかっているとは言い難いものでした。実際そういうレベルの人も多数おられるのは知っていますし、それが悪いとは思っていません。
しかし誰もその点には触れられていなかったので、適切なコードを示しました。そして、(質問者自身と回答者の方々に)質問者は「わかる」と表現しながらも実際にはほとんどわかっていない、その点に気づいていますか? という意味で書きました。
罵るつもりは全くありませんでした。そのように感じたのは、ループを回さなくてもいいことを頭の中では知っている、そう自負しているものの、実際にコードを書くとやはりループを回してしまうからではないでしょうか?
trapemiyaさんへ、
私も質問者の方への返信に終始せず、他の閲覧者へのメッセージを込めてコメントすることもありますが、さすがにちょっと質問者の方を置いてきぼりにし過ぎかなーと感じたので今回コメントしました。その辺りは感覚で差が出るところですし。ちなみに私なら
labelValFields.Text = string.Join(",", Enumerable.Range(0, Cntds.Tables["sector"].Rows.Count));
こう書きます。StringBuilderを使う必要も変数にいったん代入する必要もなくなります。
-
私も質問者の方への返信に終始せず、他の閲覧者へのメッセージを込めてコメントすることもありますが、さすがにちょっと質問者の方を置いてきぼりにし過ぎかなーと感じたので今回コメントしました。その辺りは感覚で差が出るところですし。
繰り返しになりますが、ストアドプロシージャを紹介したこと自体は出過ぎたという感覚はあまりありません。ただ、もっと詳しくフォローをすればよかったと思います。実際、質問者の方を置いてきぼりにしてしまったとも感じて反省しています。しかも対象のRDBMSが違っていましたし(^^;
回答者の方にもいろんなスタイルの人がいます。ヒントだけ出して自分で考えて下さいという方や(いじわるじゃなく、その人が自立していけるように)、最初からスバっと手っ取り早くコードを見せる方などです。私はどちらが良いというのではなく、臨機応変で行きたいと思っています。その辺りは感覚でもありますし、既にやり取りが進行中のスレに割り込む場合でも、そのスレの流れに沿うような形で参加するように心がけています(みなさんそうだと思いますが)。いずれにしても質問者の方がどのような形で回答を望まれているかが重要だと思うのですが、掲示板という限られスペースの中でのやり取りですから、思いがうまく伝わらないことも多々発生するでしょう。でも、そこはフォローし合って補うしかないと思います。
ちなみに私なら
labelValFields.Text = string.Join(",", Enumerable.Range(0, Cntds.Tables["sector"].Rows.Count));
こう書きます。StringBuilderを使う必要も変数にいったん代入する必要もなくなります。
私は質問者のレベルに限らず、そのような回答をどんどんしても良いと思います。それを使うか使わないかは質問者の自由だと思いますし、先にも述べましたが後から他の誰かがこのスレを見たときに参考になる場合もあると思いますので。MSDNフォーラムはうまく機能していると思いますし、もっと有益な情報がたくさん蓄えられたらいいと思います。佐祐理さんはレベルが高いですし(お世辞じゃなく)、私にとっても勉強になる回答がたくさんあります。ちなみに、このLinqを使った回答は私は気づいていなく、なるほどなーと感心した読者が事実ここにいますし(^^;
少し話は逸れますが、私はデーターベースでできることはできるだけデーターベースにさせてしまおうと考えている人です。実際、私が今回の問題にぶつかった場合は、ストアドプロシージャなどデーターベース側で解決することを最初に考えていたでしょう。それでストアドプロシージャの方へ走りました。この辺りも本当は先に書くべきでした。理由はいろいろありますが、端的に言えばデーターベースをオブジェクトと見て、必要なデータを依頼して受け取るということです。欠点はRDBMSが変わるとつらいということでしょうか。でも今は大抵のRDBMSのSQLは多機能ですから、ほとんど同じようにストアドプロシージャなどが組めると思いますので、大きな欠点にはならないと思います。
データーベース側で処理するというのは、このスレのタイトルや趣旨から大きく外れることは知っていますが、スレのタイトルの解決方法ではなく、背景にある問題そのものの解決の一つの例としてストアドプロシージャを示しました。ご質問される方は、問題を解決されようと思って質問されています。しかし、ご質問内容が、背景にある問題そのものの解決にとって必ずしも適切ではなかったり、他に良い解決方法がある場合もあります。私は、ご質問に対する回答だけではなく、その元となっている問題を解決するにはどうすれば良いのかまでを含めて回答できればと思っています。ただ、そこをうまく行わいと質問者の方を置いてけぼりにしちゃうんですけどね。(再度反省)
まぁ、お節介と言われればそれまでですが、、回答者にはある程度は必要だと思うんです。お節介にならないようにアドバイス、やはりそこは生身の人間同士ですから、やり取のなかで埋めていくしかないと思っています。いずれにしても佐祐理さんからご指摘があったように、質問者の方が置いてけぼりにならないようにもっと気を付けていこうと思います。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/ -
佐祐理さん、いろいろとありがとうございます。
>SELECT MAX(区間ID) FROM テーブルA >はおおよそ決まっていて、それに基づいて設計すればいいのでは?と思いました。 >そうなるとtrapemiyaさんの書かれたストアドプロシージャは不要で、固定的なPIVOTクエリをすればよくなります。
この固定的なPIVOTクエリとは、具体的にどのようなクエリになるのでしょうか?
(固定的なPIVOTクエリでフィールドが動的に生成できる?)
それと
>さらにその場合はクエリひとつで完結するためtmp_tableを作成する必要もなくなりそうです。
については、ここを一時テーブルで行っている理由は、競技時刻の昇順・さらに全体・男・女等毎にでPostgreSQLのWindow関数(RANK)で順位をふり表示・印刷のみに使うためで、それをさらにイントラ内の複数のPCから随時閲覧するためです。 (ロックとか回避することを考えると自分ではこの方法しか思いつかなかったもので...)
-
たぶん、佐祐理さんが言われているのは、開催毎に区間数は違っても、区間の最大数は例えば10もあれば済むので、区間を10としてpivotを作ってしまえばいいのではないかと言われているのだと思います。つまり、区間数が10で固定なので、固定的なpivotなわけです。確かにそうすればpivotのクエリは簡単になりますし、これで十分仕様を満たすかもしれません。ただ、区間名が開催毎に異なりますので、それをpivotの列名に埋め込もうとすればやはりpivotのSQLを動的に生成するしかないように思います。もっとも、元々はTIME1, TIME2, ・・・ で表示するように書かれていましたので、それであれば全く問題ありませんが・・・
一時テーブルが不要なのは私が書いたストアドプロシージャでも固定ストアドプロシージャでも同じように思います。私が書いたストアドプロシージャは、動的に固定ストアドプロシージャを生成しているとも考えられるからです。また、私が書いたストアドプロシージャから共通テーブル式を作成すれば、結果的に一時テーブルを作成することができます。そのテーブルにRANK()を適用することも可能です。
#以上の話は全てSQL Serverに関してです。あくまで参考として、こんな考え方もあるよ的に捉えて下さい。この考え方がPostgreSQLでも参考になるかもしれないと思い書きました。PostgreSQLはわかりません。ごめんなさい。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/ -
trapemiyaさん、いろいろありがとうございます。
いろいろな解決策、この先を見据えた根本的な解決策まで検討していただきました。
スレも長くなりましたので、ここで今一度本題に戻りたいと思います。
> もっとも、元々はTIME1, TIME2, ・・・ で表示するように書かれていましたので、それであれば全く問題ありませんが・・・
実は、私が最初の質問で, 1 TIME, 2 TIME, 3 TIMEとフィールドを書いていたのは
変数を使う上でフィールド名を連番の数字で作ったほうが適用しやすいと思ったからでしたが
再度、質問を変えて
テーブルA
開催ID | 区間ID | POINT
-------+--------+-----------
2 | 1 | 第4P
-------+--------+-----------
2 | 2 | 第9P
-------+--------+-----------
2 | 3 | ゴールP
-------+--------+-----------
3 | 1 | 第1P
-------+--------+-----------
3 | 2 | 第3P
-------+--------+-----------
3 | 3 | 第5P
-------+--------+-----------
3 | 4 | ゴールP
のようなテーブルから
WHERE 開催ID = 2 の場合なら → ", s4 TIME, s9 TIME, gp TIME"
WHERE 開催ID = 3 の場合なら → ", s1 TIME, s3 TIME, s5 TIME, gp TIME"
というように文字列を生成して変数に代入する場合どのようにすればよいでしょうか?
今後の参考にしたいと思いますので例がありましたらお願いできますか?
- 編集済み oira3ryu 2010年12月27日 22:50 一部訂正
-
まず、s4, s9, gpなどの情報がテーブルAにはありませんので、どうにかしなければなりません。一定の法則がありますので、以下のように変換できなくもありません。
例
第4Pなら、2文字目の4を取得して頭にsを付ける。
ゴールPならgpにする。もしくは、テーブルAにPOINT略号みたいな列を追加して、s4, s4, gpなどの情報をテーブルAにあらかじめ持たせてしまいます。たぶん、こちらの方が汎用性もあり、柔軟に対応できると思います。
さて、今まで大きく2つの回答が出ています。(私が書いたストアドプロシージャはデータまで取得するので今回のご質問に沿いませんが、今回のご質問に沿うように改造することは可能です。)
1.J.Hashimotoさんが書かれた、データーテーブルの各行をループして読みながら文字列を組み立てる方法
StringBuilder s = new StringBuilder();
int c = Cntds.Tables["table"].Rows.Count;
for (int i = 1; i <= c; i++) {
s.AppendFormat(", {0} TIME", i);
}
labelValFields.Text = s.ToString();2.佐祐理さんが書かれたLinqを使う方法
labelValFields.Text = string.Join(",", Enumerable.Range(0, Cntds.Tables["sector"].Rows.Count));
テーブルAにPOINT略号を持たせる場合ですと、上記二つの方法のどちらでもそのまま適用することができます。ただし、以下のようにSQLで抜く際に加工して下さい。
(PostgreSQLの文法を調べれて以下のように書いています。要するに文字列連結でTIMEを付加したいのです。間違っていたらごめんなさい)select POINT略号 || ' TIME' as POINT列名 from テーブルA where 開催ID = 2 order by 区間ID
テーブルAにPOINT略号を持たない場合ですと、1、2に関しては一度
select POINT from テーブルA where 開催ID = 2 order by 区間ID
で、データーテーブルに落とした後、POINT列に入っている値を加工してs4, s9, gpなどに置き換えてから1または2の方法を適用すれば良いと思います。s4, s9, gpなどへの置き換えは、データーテーブルの各行をループして読みながら1行ずつ置き換えていくしかないでしょう。
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/- 編集済み trapemiyaModerator 2010年12月28日 1:50 追記
-
trapemiyaさん、お付き合いいただきありがとうございます。
しつこいようでごめんなさい。
>テーブルAにPOINT略号を持たない場合ですと、1、2に関しては一度
>select POINT from テーブルA where 開催ID = 2 order by 区間ID
>で、データーテーブルに落とした後、POINT列に入っている値を加工してs4, s9, gpなどに置き換えてから1または2の方法を適用すれば良いと思います。
>s4, s9, gpなどへの置き換えは、データーテーブルの各行をループして読みながら1行ずつ置き換えていくしかないでしょう。
ループでもLinqでもいいのですが、
テーブルA
開催ID | 区間ID | POINT
-------+--------+-----------
2 | 1 | 第4P
-------+--------+-----------
2 | 2 | 第9P
-------+--------+-----------
2 | 3 | ゴールP
-------+--------+-----------
3 | 1 | 第1P
-------+--------+-----------
3 | 2 | 第3P
-------+--------+-----------
3 | 3 | 第5P
-------+--------+-----------
3 | 4 | ゴールP
から、POINT列のデータをC#で
select POINT from テーブルA where 開催ID = 2 order by 区間ID
の結果を
,第4P, 第9P, ゴールPという形で取り出せるならできそうです。
(II 文字列連結とかreplaceとか使えばできそうな...)
取り出す方法が知りたいです!
-
2通りの方法を書きます。
1つ目はforで回して取得します。
StringBuilder s = new StringBuilder(); int c = ds.Tables["Table"].Rows.Count; for (int i = 0; i < c; i++) { s.AppendFormat(", {0}", ds.Tables["Table"].Rows[i]["POINT"].ToString()); } label1.Text = s.ToString();
2つ目はLinqを使った方法です。
label2.Text = ", " + string.Join(",", Enumerable.Range(0, ds.Tables["Table"].Rows.Count).Select(i => ds.Tables["Table"].Rows[i]["POINT"]).ToList());
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/ -
tarpemiyaさん、ありがとうございました。
おかげさまで希望通りにできました。
> s.AppendFormat(", {0}", ds.Tables["Table"].Rows[i]["Column"].ToString());
.Rows[i]["Column"]のように書きLoopで回すことでとそのカラムの値をとれるんですね。
これがわかりませんでした(Linqは、さらにわかりません。難しそうです...ね)
string StrVal = ""; StringBuilder s = new StringBuilder(); int c = ds.Tables["Table"].Rows.Count; for (int i = 0; i < c; i++) { StrVal = ds.Tables["Table"].Rows[i]["Column"].ToString(); StrVal = StrVal.Substring(0, 1); s.AppendFormat(", s{0}", StrVal.ToLower() + " TIME"); } labelValFields.Text = s.ToString();
とすることで、
s4, s9, gp と出力させることができるようになりました。
あとは、これを利用して動的SQLを作ってみようと思います。
とりあえず、このスレを閉めさせていただきます。
ありがとうございました。
- 編集済み oira3ryu 2010年12月29日 14:43 コードを挿入