‘SQL Server’ カテゴリーのアーカイブ
SQL Server – 「基準値」テーブルでコードを一括管理する
SQL Serverによるシステムがある程度になると、いろいろな処理をするためのコードを効率よく処理することが必要になる。学校の場合は、たとえば「教科科目コード」や「時限コード」のような時間割に関するコード、「在籍コード」や「異動コード」など生徒の在籍状態に関するコード、などだ。
これらのコードをシステム上どのようにして処理するかだが、単純に考えると「教科科目コード」や「時限コード」といった名前のテーブルを作りコードを記録することだが、それぞれのコードに対してテーブルを作ると、テーブルの数が増えてシステム全体の見通しが悪くなる。ちなみに、このようにテーブルを作る場合でも、テーブル名は「コード教科科目」や「コード時限」のように「コード」といった共通の名前を前にするとテーブル名を一覧表示したときに見通しがよくなる。もちろん「C_教科科目コード」や「C_時限コード」のようにシンボリックな文字を前につけてもよい。
システムが大きくなると、このようなコードを記録する必要も大きくなる。そこでコードを一括して「基準値」テーブルにストアして管理する方法がよい。そのためにコードはすべて二桁の文字char(2)か、または日付smalldatetimeと統一し、次のようなテーブルを作る。
——————————————————————————-
テーブル名:基準値
フィールド:
基準値管理番号 int IDENTITY(1,1) NOT NULL
基準分類 varchar(50)
基準内容 varchar(50)
基準値 char(2)
基準日 smalldatetime
表示順 int
——————————————————————————-
このテーブルで、「基準分類」によってコードの分類をし、コードの内容は「基準内容」に記述する。たとえば次のようにデータをストアする。
——————————————————————————-
基準分類 基準内容 基準値 基準日 表示順
性別 男 01 NULL 1
性別 女 02 NULL 2
異動 入学 01 NULL 1
異動 休学 02 NULL 2
異動 復学 03 NULL 3
異動 留学 04 NULL 4
異動 転学 05 NULL 5
異動 退学 06 NULL 6
異動 卒業 07 NULL 7
教科 国語 01 NULL 1
教科 地歴 02 NULL 2
教科 数学 03 NULL 3
教科 理科 04 NULL 4
教科 保健体育 05 NULL 5
教科 芸術 06 NULL 6
教科 外国語 07 NULL 7
教科 家庭 08 NULL 8
教科 情報 09 NULL 9
教科 商業 10 NULL 10
日程 前期開始日 NULL 2011/04/01 1
日程 前期終了日 NULL 2011/09/30 2
日程 後期開始日 NULL 2011/10/01 3
日程 後期終了日 NULL 2012/03/31 4
——————————————————————————-
この「基準値」テーブルを使って必要なコードを取得する。たとえば「性別」のコードを使うときは、
select 基準内容,基準値 from 基準値 where 基準分類=’性別’ order by 表示順
といったクエリを使う。これを「v_性別コード」のようなビューにしておくのもよいだろう。
「日程」データを取得するには、
select 基準内容,基準日 from 基準値 where 基準分類 = ‘日程’ order by 表示順
とする。
このように、コードを一元管理することで「あのコードを格納したのはなんというテーブルだったっけ」というように探し出す必要がなくなるのだ。
SQL Serverの文字列処理 – 職員コードから先頭のアルファベット3文字分を判定して取り除く
職員コードが6桁のコードでできているが、使われる記号が数字とアルファベットの混合であり、次の4つのパターンがあるとする。
(1)すべて数字でできてきている。 <例>123456
(2)アルファベット1文字と数字5桁でできている。 <例>A12345
(3)アルファベット2文字と数字4桁でできている。 <例>AB1234
(4)アルファベット3文字と数字3桁でできている。 <例>ABC123
このとき、「職員」テーブルにある「職員番号」フィールドについて、数字部分だけを取り出して昇順に並べたいとする。たとえばSQL Serverのビューでは「LIKE」演算子とパターン指定によって次のようにする。
———————————————————————-
SELECT 職員番号, CASE
WHEN 職員番号 LIKE ‘[a-z][a-z][a-z]%’ THEN substring(職員番号, 4, 3)
WHEN 職員番号 LIKE ‘[a-z][a-z]%’ THEN substring(職員番号, 3, 4)
WHEN 職員番号 LIKE ‘[a-z]%’ THEN substring(職員番号, 2, 5)
ELSE 職員番号
END AS 職員番号の数字部分, 職員姓名, 職員姓名ふりがな
FROM dbo.職員
———————————————————————-
結果は次のとおり。
<Fig.1 職員コードから先頭のアルファベット3文字分を判定して取り除いた例>
この例は、あくまでも文字列の先頭部分に連続してアルファベットが続く場合にコードを処理する例であるが、複数のデータを統合する際などにこのような文字列処理によるデータの加工が必要になる。コード体系が異なる場合、新しくコード体系を作り直す場合、いわゆる「名寄せ」が必要な場合、などだ。
システムの更新、統合には、実際にシステムを作る作業に匹敵するほど、データの整理に手間がかかる場合が少なくない。それは逆にいえば、いかに合理的で永続的なコード体系を作ることが重要であるか、ということも示している。
SQL Server – ストアドプロシージャの雛形を作成した
SQL Serverでストアドプロシージャを作成する時間が多くなった。管理のための作業をストアドプロシージャにする作業が多くなったからだ。そこで新しいストアドプロシージャを作るときに、雛形となるものを作ってみた。
このストアドプロシージャでは、引数として「対象」と「年度」を設定している。もちろん引数の意味を変更することや増やすことも可能だ。また処理の履歴を「処理記録」テーブルにストアすることにしている。そのとき、システムから「年度コード」、「日付」、「処理者」を取得し、パラメーターとして一緒にストアしている。これにより処理の履歴を追跡しやすくしている。参考になれば幸いである。
———————————————————————————————————————————-
create procedure ストアドプロシージャ雛形
@P_対象 as varchar(50),
@P_年度 as char(2)
as
/*
使い方
<実行例> execute ストアドプロシージャ雛形 ‘男性’,’10′
*/
–処理定数の取得
declare @今年度 as char(2)
declare @日付時間 as smalldatetime
declare @処理者 as varchar(50)
set @今年度 = (select nendo_cd from const_nendo where now_cd = 1)
set @日付時間 = getdate()
set @処理者 = substring(suser_sname(),6,6)
–実際の処理
if @P_対象 = ‘男性’
begin
insert into 処理記録(日付,職員番号,備考)
values (@日付時間,@処理者,’「ストアドプロシージャ雛形」を実行した。引数は、’ + ‘引数1(対象):’ + @P_対象
+ ‘ 引数2(年度):’ + @P_年度 + ‘ 今年度 :’ + @今年度)
end
if @P_対象 = ‘女性’
begin
insert into 処理記録(日付,職員番号,備考)
values (@日付時間,@処理者,’「ストアドプロシージャ雛形」を実行した。引数は、’ + ‘引数1(対象):’ + @P_対象
+ ‘ 引数2(年度):’ + @P_年度 + ‘ 今年度 :’ + @今年度)
end
———————————————————————————————————————————-
正しく作られたシステムでも、運用を誤り破綻する恐れをゼロにできない
いささか大上段に構えたタイトルにしたが、実際におこったことは些細なことである。しかし、今日はあらためてシステムを作ることの難しさを考えさせられた。
今日のテーマはシステム構築における純粋な技術の話ではなく、人の思考や行動といったヒューマンな側面についてである。しかしシステム開発は技術的に完全であればよいものではなく、ヒューマンな要素を十分に考慮しなければならない。また狭い意味でのコンピュータシステムだけを考えるのではなく、データ入力の帳票のあり方、作業の方法なども見直さなければならないケースもある。
私は勤務校でSQL Serverをデータベースにし、InfoPathとAccessを組み合わせた、いわゆる「OBA開発」の手法でクライアントサーバー型の「校務システム」を構築し、運用している。このシステムの基本は、単位制高校である本校の講座編成、時間割、履修登録、出欠、考査点、成績、修得単位など、教務処理を行うものである。それに加えて、通知表などを家庭に発送するための住所管理、職員の勤務時間を集計する従事時間集計、学校評価のアンケート集計など校内の情報管理を一元的に行うものへ発展させている。「OBA開発」の利点は、運用しながらシステムを改良することがやりやすいところだ。
このシステムに今年度から生徒の保健情報も扱うことにした。身長、体重などの健康記録に加えて、内科検診など検査結果も処理できなければならない。これらのデータをどのようにデータベース化するかについては、養護教諭つまり保健の先生と相談しながら設計し、実際のデータに対応できるものにした。このあたりの詳細は、また別にblogにまとめるつもりだ。
さて前置きが長くなったが、このシステムに「結核検診結果」を入力することになった。結核検診について、SQL Serverのテーブル構造は次のようになっている。
<SQL Serverのテーブル構造>
学籍番号 char(7)
年度 char(2)
結核検診 char(2)
結核検診詳細 varchar(50)
「結核検診」フィールドはコード管理し、00が「未受診」、01が「異常なし」、02が「異常あり」とし、所見があったときは「結核検診詳細」フィールドに自由記述することとした。
これにデータを入力するためのInfoPathフォームは次のようなものである。
<Fig.1 結核検診結果を入力するInfoPathフォーム>
SQL Serverで「結核検診」フィールドのデフォルト値を00にしておき、ボタンで01または02に変更できるようにする。「結核検診詳細」テキストボックスは、「結核検診」フィールドが02でなければグレーアウトし、読み取り専用になるようにしておく。これはInfoPathのテキストボックスのプロパティで「条件付き書式」で設定する。結核検診の結果が「異常あり」でなければ、詳細は入力できないようにしておくのだ。入力間違いを少なくする仕掛けだ。
さて、入力作業をしているところに、ふと、立ち寄って後ろから見ていると、なにかおかしいことに気づいた。次のような入力画面が見えたのだ。
結核検診の結果を入力しているのに、詳細が「脊柱側湾」となっている。入力担当は若い男性教員だ。どうやら養護教諭に頼まれてかわりに入力しているらしい。
「脊柱側湾」って、結核と関係ないんじゃない」「はあ。」「それは内科検診の項目だから、入力フォームを間違っていると思うよ」「はあ。でも結核検診の結果に書いてあるんです。養護教諭の先生がとりあえずそこに入力しておいて、って言ったので」
そこで入力のために使っている検診結果の表を見ると、確かに次のように書かれている。氏名はもちろん仮名である。
「結核検診」の記録のはずなのに、結核と関係ない「脊柱側湾」の所見が書かれている。これはおかしいのではないか。そこで養護教諭に事情を問いただした。すると、こういうことである。
結核検診はレントゲンなので、結核の疑いのあるなしだけでなく、脊柱側湾つまり脊柱が曲がっている症状もわかることが多いのだ。そこで慣習として、いわばサービスみたいなものとして、脊柱側湾の症状がレントゲンからわかれば、検査機関が所見に書いてくれるということなのだ。
養護教諭の立場からすると、少しでも多くの症状が早く発見できればよいのだろうが、データ入力上は間違いの原因になる。この生徒は、脊柱側湾であるが、結核の異常はないのである。しかし、上のような入力では、結核検診で異常が発見されたことに集計されてしまう。
何が問題なのか。まず入力に使う結果用紙の様式が問題である。脊柱側湾を結核検診で所見に書くなら、所見の欄を2つに分け、まず結核検診の結果を書き、それとは別にその他の所見を書くべきである。検査結果の用紙を見直したい。
もし検査用紙の見直しができないならば、データ入力において、やはりそのデータに関する、ある程度の知識を持っていること、データがどのように集計されるべきなのかという意味を理解していることが必要である。書式がデータ入力に即していなかったり、記入の仕方があいまいであっても、きちんと判断できる人間が入力するなら問題ない。
今回のケースは些細なこと、また結核検診という、まず全員が異常なしとなるだろう記録であったので、このまま間違い入力をしてしまっても、後で間違いが発見されただろう。しかしこのようなケースが他の例でも起こりうることであり、いかに正しく設計されたシステムであっても、間違ったデータ入力が見過ごされて信頼性のないデータで汚染され、全体として機能しないシステムに陥る危険を垣間見た気がする。
SQL Serverのユーザー定義関数を使おう~(11)任意の日における生徒の年齢を知るユーザー定義関数
生徒の年齢を知りたい場合がある。それも任意の日における年齢を知る必要がある。というのも、学校における各種統計において、「その日の年齢」であったり「ある基準日における年齢」であったり、年齢を算出する基準日が異なるためだ。これに対応するユーザー定義関数を作ってみた。
生徒は学籍番号で管理されており、「生徒」テーブルに生年月日が保存されている。この生年月日から年齢を算出するユーザー定義関数だ。
使い方はこうだ。
select dbo.fx生徒年齢(学籍番号,基準日)
たとえば2010年4月1日における学籍番号0101001の生徒の年齢を知るには、実際にはこんなかんじ。
select dbo.fx生徒年齢(’0101001′,’2010/04/01′)
——————(ここからユーザー定義関数)——————
create function [dbo].[fx生徒年齢]
(
@p_学籍番号 char(7),
@p_基準日 smalldatetime
)
returns int
AS
begin
declare @判定 int
declare @生年月日 smalldatetime
set @生年月日 = (select 生年月日 from 生徒 where 学籍番号 = @p_学籍番号)
set @判定 = datediff(dd
,cast(’2000/’+cast(month(@生年月日) as varchar(2))+’/'+cast(day(@生年月日) as varchar(2)) as smalldatetime)
,cast(’2000/’+cast(month(@p_基準日) as varchar(2))+’/'+cast(day(@p_基準日) as varchar(2))as smalldatetime))
return (select case when @判定 < 0 then datediff(yy,@生年月日,@p_基準日)-1
when @判定 >= 0 then datediff(yy,@生年月日,@p_基準日) end)
return null
end
——————(ここまでユーザー定義関数)——————
InfoPathとSQL Serverで「学校評価」の集計をする
年度末に近づき、多くの学校では一年間のまとめをする時期になったと思う。本校でもこの一年の取り組みのまとめとして、学校評価を行うことになった。
本校の学校評価では、いくつかの大項目の下に、小項目として50弱の項目を目標として設定した。これらの小項目に対して、それぞれ教員が1から4までの評価を与えることにしている。このようなアンケート式のデータ集計は、InfoPathとSQL Serverが最も得意とするところである。しかしテーブル構造や集計については、少し工夫を要するところがある。
InfoPathフォームはSQL Serverのテーブルに対して1対1の対応が得意である。そこでやや強引ではあるが、テーブル構造として次のようなものを作成した。
———————-(ここからテーブル作成SQL)———————-
CREATE TABLE gakkohyoka_t_hyoka (
[shokuinbango] [char] (6) ,
[inputtime] [datetime] ,
[01] [char] (2) ,
[02] [char] (2) ,
[03] [char] (2) ,
[04] [char] (2) ,
[05] [char] (2) ,
[06] [char] (2) ,
[07] [char] (2) ,
[08] [char] (2) ,
[09] [char] (2) ,
[10] [char] (2) ,
[11] [char] (2) ,
[12] [char] (2) ,
[13] [char] (2) ,
[14] [char] (2) ,
[15] [char] (2) ,
[16] [char] (2) ,
[17] [char] (2) ,
[18] [char] (2) ,
[19] [char] (2) ,
[20] [char] (2) ,
[21] [char] (2) ,
[22] [char] (2) ,
[23] [char] (2) ,
[24] [char] (2) ,
[25] [char] (2) ,
[26] [char] (2) ,
[27] [char] (2) ,
[28] [char] (2) ,
[29] [char] (2) ,
[30] [char] (2) ,
[31] [char] (2) ,
[32] [char] (2) ,
[33] [char] (2) ,
[34] [char] (2) ,
[35] [char] (2) ,
[36] [char] (2) ,
[37] [char] (2) ,
[38] [char] (2) ,
[39] [char] (2) ,
[40] [char] (2) ,
[41] [char] (2) ,
[42] [char] (2) ,
[43] [char] (2) ,
[44] [char] (2) ,
[45] [char] (2) ,
[46] [char] (2) ,
[47] [char] (2) ,
[48] [char] (2) ,
[49] [char] (2) ,
[50] [char] (2) ,
[hyokaiken] [varchar] (2000) ,
[unneiiken] [varchar] (2000)
)
———————-(ここまでテーブル作成SQL)———————-
フィールド[shokuinbango]に職員番号を生成しておき、[inputtime]にInfoPathフォームで入力したときの日付を入れる、フィールド[01]から[50]までにそれぞれの項目の1から4評価をストアし、最後の[hyokaiken]と[unneiiken]には自由記述で文を入力できるようにする。このようなテーブルにすると、InfoPathフォームは作りやすい。
しかし問題は集計の方法だ。これがExcelの表ならば、各列の最後にcountif()文を使って各評価の個数を集計するところだが、SQL Serverではそうはいかない。
そこで、まず、このテーブルの値を正規化するビューを作る。次のようなビューだ。
———————-(ここからビューのSQL)———————-
SELECT ’01′ AS 項目, [01] AS 評価, COUNT(01) AS 回答数
FROM gakkohyoka_t_hyoka
GROUP BY [01]
UNION
SELECT ’02′ AS 項目, [02] AS 評価, COUNT(02) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_02
GROUP BY [02]
UNION
SELECT ’03′ AS 項目, [03] AS 評価, COUNT(03) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_03
GROUP BY [03]
UNION
SELECT ’04′ AS 項目, [04] AS 評価, COUNT(04) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_04
GROUP BY [04]
UNION
SELECT ’05′ AS 項目, [05] AS 評価, COUNT(05) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_05
GROUP BY [05]
UNION
SELECT ’06′ AS 項目, [06] AS 評価, COUNT(06) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_06
GROUP BY [06]
UNION
SELECT ’07′ AS 項目, [07] AS 評価, COUNT(07) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_07
GROUP BY [07]
UNION
SELECT ’08′ AS 項目, [08] AS 評価, COUNT(08) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_08
GROUP BY [08]
UNION
SELECT ’09′ AS 項目, [09] AS 評価, COUNT(09) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_09
GROUP BY [09]
UNION
SELECT ’10′ AS 項目, [10] AS 評価, COUNT(10) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_10
GROUP BY [10]
UNION
SELECT ’11′ AS 項目, [11] AS 評価, COUNT(11) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_11
GROUP BY [11]
UNION
SELECT ’12′ AS 項目, [12] AS 評価, COUNT(12) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_12
GROUP BY [12]
UNION
SELECT ’13′ AS 項目, [13] AS 評価, COUNT(13) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_13
GROUP BY [13]
UNION
SELECT ’14′ AS 項目, [14] AS 評価, COUNT(14) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_14
GROUP BY [14]
UNION
SELECT ’15′ AS 項目, [15] AS 評価, COUNT(15) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_15
GROUP BY [15]
UNION
SELECT ’16′ AS 項目, [16] AS 評価, COUNT(16) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_16
GROUP BY [16]
UNION
SELECT ’17′ AS 項目, [17] AS 評価, COUNT(17) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_17
GROUP BY [17]
UNION
SELECT ’18′ AS 項目, [18] AS 評価, COUNT(18) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_18
GROUP BY [18]
UNION
SELECT ’19′ AS 項目, [19] AS 評価, COUNT(19) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_19
GROUP BY [19]
UNION
SELECT ’20′ AS 項目, [20] AS 評価, COUNT(20) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_20
GROUP BY [20]
UNION
SELECT ’21′ AS 項目, [21] AS 評価, COUNT(21) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_21
GROUP BY [21]
UNION
SELECT ’22′ AS 項目, [22] AS 評価, COUNT(22) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_22
GROUP BY [22]
UNION
SELECT ’23′ AS 項目, [23] AS 評価, COUNT(23) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_23
GROUP BY [23]
UNION
SELECT ’24′ AS 項目, [24] AS 評価, COUNT(24) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_24
GROUP BY [24]
UNION
SELECT ’25′ AS 項目, [25] AS 評価, COUNT(25) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_25
GROUP BY [25]
UNION
SELECT ’26′ AS 項目, [26] AS 評価, COUNT(26) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_26
GROUP BY [26]
UNION
SELECT ’27′ AS 項目, [27] AS 評価, COUNT(27) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_27
GROUP BY [27]
UNION
SELECT ’28′ AS 項目, [28] AS 評価, COUNT(28) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_28
GROUP BY [28]
UNION
SELECT ’29′ AS 項目, [29] AS 評価, COUNT(29) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_29
GROUP BY [29]
UNION
SELECT ’30′ AS 項目, [30] AS 評価, COUNT(30) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_30
GROUP BY [30]
UNION
SELECT ’31′ AS 項目, [31] AS 評価, COUNT(31) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_31
GROUP BY [31]
UNION
SELECT ’32′ AS 項目, [32] AS 評価, COUNT(32) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_32
GROUP BY [32]
UNION
SELECT ’33′ AS 項目, [33] AS 評価, COUNT(33) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_33
GROUP BY [33]
UNION
SELECT ’34′ AS 項目, [34] AS 評価, COUNT(34) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_34
GROUP BY [34]
UNION
SELECT ’35′ AS 項目, [35] AS 評価, COUNT(35) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_35
GROUP BY [35]
UNION
SELECT ’36′ AS 項目, [36] AS 評価, COUNT(36) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_36
GROUP BY [36]
UNION
SELECT ’37′ AS 項目, [37] AS 評価, COUNT(37) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_37
GROUP BY [37]
UNION
SELECT ’38′ AS 項目, [38] AS 評価, COUNT(38) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_38
GROUP BY [38]
UNION
SELECT ’39′ AS 項目, [39] AS 評価, COUNT(39) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_39
GROUP BY [39]
UNION
SELECT ’40′ AS 項目, [40] AS 評価, COUNT(40) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_40
GROUP BY [40]
UNION
SELECT ’41′ AS 項目, [41] AS 評価, COUNT(41) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_41
GROUP BY [41]
UNION
SELECT ’42′ AS 項目, [42] AS 評価, COUNT(42) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_42
GROUP BY [42]
UNION
SELECT ’43′ AS 項目, [43] AS 評価, COUNT(43) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_43
GROUP BY [43]
UNION
SELECT ’44′ AS 項目, [44] AS 評価, COUNT(44) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_44
GROUP BY [44]
UNION
SELECT ’45′ AS 項目, [45] AS 評価, COUNT(45) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_45
GROUP BY [45]
UNION
SELECT ’46′ AS 項目, [46] AS 評価, COUNT(46) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_46
GROUP BY [46]
UNION
SELECT ’47′ AS 項目, [47] AS 評価, COUNT(47) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_47
GROUP BY [47]
UNION
SELECT ’48′ AS 項目, [48] AS 評価, COUNT(48) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_48
GROUP BY [48]
UNION
SELECT ’49′ AS 項目, [49] AS 評価, COUNT(49) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_49
GROUP BY [49]
UNION
SELECT ’50′ AS 項目, [50] AS 評価, COUNT(50) AS 回答数
FROM gakkohyoka_t_hyoka AS gakkohyoka_t_hyoka_50
GROUP BY [50]
———————-(ここまでビューのSQL)———————-
ひたすら長いSQL文だが、構造は簡単なUNIONクエリの繰り返しなので、機械的に作ることができるはずだ。このSQL文によって、「項目」、「評価」、「回答数」というビューが得られる。結果はSQL Server Management Studioの編集画面を見てもらうと一目瞭然だろう。
この後はPIVOTによる集計を行う。クエリは以下のとおり。
———————-(ここからPIVOTのSQL)———————-
SELECT 項目
,SUM(CASE WHEN 評価 = ‘A’ THEN 回答数 ELSE 0 END) AS 評価A,
,SUM(CASE WHEN 評価 = ‘B’ THEN 回答数 ELSE 0 END) AS 評価B,
,SUM(CASE WHEN 評価 = ‘C’ THEN 回答数 ELSE 0 END) AS 評価C,
,SUM(CASE WHEN 評価 = ‘D’ THEN 回答数 ELSE 0 END) AS 評価D
FROM v_gakkohyoka_shu
GROUP BY 項目
———————-(ここまでPIVOTのSQL)———————-
これもSQL Server Management Studioの編集画面を見てもらうと直感的にわかるだろう。
あとはこのビューに対して、画面に表示する単純なInfoPathフォームを作るだけだ。こうしておけば、アンケートの入力が終わった時点で、即集計結果を出すことができる。
SQL ServerのようなデータベースとExcelを単純に比べることは意味がないが、このような集計をするにはExcelは得意であるが、SQL Serverでやろうとすると手間がかかることは間違いない。しかし一度作っておけば、毎年同じ処理をこのテーブルとビューでできるので、省力化できる。項目がいくら増えても、選択肢が増えても、職員数が変わっても、ほとんど変更することなく集計ができる。
SQL Server 2000のデータをSQL Server 2005にインポートするにはAccess Projectを使おう
つい先日のことだが、古いサーバーでSQL Server 2000で管理されていたデータを、新しいサーバーのSQL Server 2005へ移行する作業を行った。このとき、データ移行でエラーとなり、試行錯誤した。
データ移行には、DTSを使った。DTS(Data Transformation Services)である。旧サーバーのSQL Server 2000からDTSを使ってデータをExcel形式で書き出し、SQL Server 2005のDTSでインポートしようとした。このとき「エラー0xc02020f6:データフロータスク : 列****では、Unicod形式の文字列データ型とUnicode以外の形式の文字列データ型を変換できません」といったエラーが出てインポートができない現象がおきた。
<Fig.1 : DTSのインポートウィザードが停止した状態>
もとのSQL Server 2000のテーブルは、単純なvarchar型のテーブルであり、なんら文字コードに特殊な仕掛けをしていない。さて困ったところで、DTSでデータを書き出すためにExcel形式はやめてCSVにしたり文字コードを決め打ちしたりいろいろとやっても解決できない。かなり行き詰まった状況になり、焦燥感が充満した。
Excel形式でもCSVでもだめだったので、データをAccessファイルに書き出すことを考えた。しかしSQL Server 2000のDTSで書き出したAccessファイルをSQL Server 2005のDTSで読み込んでもエラーで止まってしまう。
しばらく頭を冷やし、何か方法がないか、と考え、Access Projectを使うことを思いついた。つまり、こうするのだ。
1.移転先のSQL Server 2005に対してAccess Projectを作成する。
2.そのAccess Projectでデータのインポートを実行する。
3.インポートするファイルは、SQL Server 2000のDTSで書き出したExcelファイルでもCSVファイルでもよい。
4.念のため新しいテーブルにインポートし、その後SQL文でインポートしたいテーブルにINSERTする。
上記のような手順で、うまくデータをインポートすることができた。つまり、データを書き出すにはSQL ServerのDTSでよいが、インポートするにはDTSよりもAccess Projectのインポートウィザードを使った方がよかった、ということだ。これがなぜなのか、といった追求までしていないが、DTSでうまくいかない場合はAccessを間に介することで解決できることもある、ということはTipsとして知っておいていいだろう。
SQL Serverのユーザー定義関数を使おう~(10)月曜から始まる年度週番号ユーザー定義関数
以前、「年度週番号」を返すT-SQLのユーザー定義関数を作ったことを書いた。ある日付が、その年度の何週目にあたるかを求める必要があったからだ。以前に書いたユーザー定義関数はこのようなものだった。
———————-(ここから)———————-
create function fx年度週番号
(
@p_日付 datetime
)
returns int
as
begin
declare @thisyear41 datetime
declare @overyear41 datetime
set @thisyear41 = cast(cast(datepart(year,@p_日付) as varchar) + ‘/’ +’4/1′ as datetime)
set @overyear41 = cast(cast(datepart(year,@p_日付)-1 as varchar) + ‘/’ +’4/1′ as datetime)
if @p_日付 > @thisyear41
begin
return datediff(week,@thisyear41,@p_日付)+1
end
if @p_日付 < @thisyear41
begin
return datediff(week,@overyear41,@p_日付)+1
end
if @p_日付 = @thisyear41
begin
return 1
end
return null
end
———————-(ここまで)———————-
引数にする日付の年の4月1日を求め、日付が4月1日よりも後だったらその日付は4月1日から12月31日までであり、4月1日よりも前だったら年が変わっているので1月1日から3月31日までであると判定し、週の番号を求める仕組みだった。
このユーザー定義関数では、日曜日が週の最初に来る値を返す。しかし欲しかったのは日曜日が週の最後に来る値を返すユーザー定義関数だった。そこでこのユーザー定義関数を修正し、月曜で始まる週として年度週番号を返すユーザー定義関数に変更した。変更は力技的であり、日付が日曜ならば年度週番号を1マイナスする、という仕組みにした。
———————-(ここから)———————-
create function fx年度週番号
(
@p_日付 datetime
)
returns int
as
/*
日曜はじまりの年度週番号を返すように変更
*/
begin
declare @thisyear41 datetime
declare @overyear41 datetime
set @thisyear41 = cast(cast(datepart(year,@p_日付) as varchar) + ‘/’ +’4/1′ as datetime)
set @overyear41 = cast(cast(datepart(year,@p_日付)-1 as varchar) + ‘/’ +’4/1′ as datetime)
declare @sunday_minus int
set @sunday_minus = 0
if datepart(weekday,@p_日付) = 1
set @sunday_minus = 1
if @p_日付 > @thisyear41
begin
return datediff(week,@thisyear41,@p_日付)+1-@sunday_minus
end
if @p_日付 < @thisyear41
begin
return datediff(week,@overyear41,@p_日付)+1-@sunday_minus
end
if @p_日付 = @thisyear41
begin
return 1
end
return null
end
———————-(ここまで)———————-
システムを作りこむと運用を誤ったときの問題解決に手間取る
システムを作り込むと、本来必要なデータに加えて様々な属性情報を付け加えることになる。例えば成績データに対して、生徒の在籍状況や成績が確定したことを示すロックフラグなどが付いている。これは例えば年度途中で退学した生徒の単位を学期末に認定してしまったり、卒業してしまった生徒のデータを書き換えないため、単位認定会議が終わった後で成績を変更されないため、など、データ処理の安全性を高める役割をしている。だが運用を誤ると、これが問題解決を複雑にし、手間取らせる原因になる。今回おこったことは、こういうものだった。
私の勤務校は前期後期制をとっており、ほとんどの科目は半期で単位認定を行う。また入学、卒業も半期ごとに行い、毎年9月に「前期卒業式」を行い、同月に「後期入試」を行って10月に新入生を迎える。今年の前期末処理を行う際、ある理由があって単位認定のシステム上の処理が遅れてしまった。単位認定処理を行ってから通知表などの成績資料を出すのだが、その一連の処理が終わる前に、教務の担当者が前期卒業生に対して「卒業処理」を行ってしまったのだ。
生徒を「卒業」としてしまうと、その生徒はもはや本校に「在籍していない」ことになってしまう。この生徒の在籍状況は、たくさんのテーブルに影響を与える。システムは「在籍していない」生徒に対して、単位の認定をしないようになっている。冒頭に書いた「安全装置」が働いているのだ。そこで前期卒業生の単位認定処理ができなくなってしまった。
「卒業処理をした」ことが私に伝えられなかったため、初動調査が遅れた。このとき私は、単位認定処理が正しく行われない理由を、ストアドプロシージャにバグがあったのだろうか、と思っていた。しかしそうではなく、卒業処理が問題だった。その後、卒業処理によって生徒の在籍状況がどのテーブルに影響するかを調べる作業が必要となった。在籍状況は数多くのテーブルに影響を与える。これにかなり時間を必要とした。その結果、前期末処理が完了するのも、ますます後にずれることとなった。
卒業処理がどのテーブルのどのデータに影響を与えるか、ドキュメントを残しておけばよい、というのは模範的な解答だと思うが、例えば一年間で行われる処理は様々にあって、それらのすべてについてどのテーブルにどんな影響を与えるかをドキュメントに残すことは、言うは易し行なうは難しである。仮にドキュメント化されていたとしても、手作業でデータを修復しなければならないことは変わらない。
システムを使いやすく構築すると、データ構造は複雑になることが避けられない。運用が簡単になるように一連の作業をストアドプロシージャ化すると、今回のように処理の前後が間違ったとき、問題解決に時間がかかる。
運用を誤る可能性がゼロにならない限り、システム運用にはシステムを熟知した管理者が必要なはずである。
SQL Serverのユーザー定義関数を使おう~(9)日付に関するユーザー定義関数を作る
日付に関するユーザー定義関数を作った。この関数を作った目的は、今年度の範囲で処理をする場面が多かったので、「今年度の最初の日」と「今年度の最後の日」を知るユーザー定義関数を作ろうと思ったためだ。例えば日付が「2010年6月15日」だと、この日に対して「今年度最初の日」は「2010年4月1日」であり、「今年度最後の日」は「2011年3月31日」である。
2010年であっても、4月1日以前は前年度である。日付が「2010年3月15日」だと、この日に対して「今年度最初の日」は「2009年4月1日」であり、「今年度最後の日」は「2010年3月31日」なのだ。
そこでこの関数の中では、まず「日付」に対して「年度」を求めている。「年度」さえ判定できれば、その年の4月1日が「今年度最初の日」であり、次の年の3月31日が「今年度最後の日」である。
使い方は、select dbo.fx日付(’2010/06/15′,’年度最初日’)、のようにする。最初の引数が日付で、次の引数がパラメーターだ。パラメーターは「年度最初日」と「年度最後日」を実装しているが、これも拡張可能である。日付に関するいろいろな処理を一括してこの関数に盛り込もうと思ったので、パラメーターによって拡張可能にしている。
また、今日の日付で「今年度の最初の日」を求めたいなら、select dbo.fx日付(getdate(),’年度最初日’)、にすることもできる。
——————————————————————————————————-
create function [dbo].[fx日付]
(
@P_日付 smalldatetime,
@p_パラメータ varchar(10)
)
returns smalldatetime
as
begin
declare @thisnendo int
declare @thisyear41 smalldatetime
/*年度の取得*/
set @thisyear41 = cast(cast(datepart(year,@p_日付) as varchar) + ‘/’ +’4/1′ as smalldatetime)
if @p_日付 >= @thisyear41
begin
set @thisnendo = datepart(year,@p_日付)
end
if @p_日付 < @thisyear41
begin
set @thisnendo = datepart(year,@p_日付)-1
end
/*今年度最初の日を求める*/
if @P_パラメータ = ‘年度最初日’
begin
return cast(cast(@thisnendo as varchar) + ‘/’ +’4/1′ as smalldatetime)
end
/*今年度最後の日を求める*/
if @P_パラメータ = ‘年度最後日’
begin
return cast(cast((@thisnendo + 1) as varchar) + ‘/’ +’3/31′ as smalldatetime)
end
return null
end


