技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

情報処理技術者試験対策「SQL(重複排除と集計)」

今回は重複排除(distinct句)と集計(group by)の紹介です。
どちらも挙動が似ているのでまとめて紹介します。
実務で使うだけでなく、どちらも試験頻出です。
 
【機能説明】
・重複排除
 select文の結果について、重複を排除することができる。
 
・集計
 指定したカラムで集計を行うことができる。
 (結果として重複排除もされる)
 select文で集計関数を使用することができるようになる。
 (該当行数を返すCOUNT関数、最大値を返すMAX関数、最小値を返すMIN関数等)
 なお、having句でgroup byで集計した結果に対して条件で抽出することができる。
 (where句も同じように抽出を行うが、
  where句は集計前に、having句は集計後に抽出する違いがある)
 
【文法】
・重複排除
 select distinct…
 
・集計
 select…
 from…
 (where…)
 group by 集計を行うカラム名(,集計を行うカラム名…)
 (having 条件)
 (order by…)
 
【対象テーブル例】
・ご意見
  +--------+--------+----------+
  | 店舗ID | 商品ID | ご意見   |
  +--------+--------+----------+
  | 1      | 4      | ほげ14 |
  | 1      | 4      | NULL     |
  | 1      | 5      | ほげ15 |
  | 3      | 2      | ほげ32 |
  +--------+--------+----------+
 
【使用例】
asは省略可能である。

・重複排除(distinct句)
select distinct 店舗ID,
                商品ID
from            ご意見
order by        店舗ID,商品ID;

  +--------+--------+
  | 店舗ID | 商品ID |
  +--------+--------+
  | 1      | 4      |
  | 1      | 5      |
  | 3      | 2      |
  +--------+--------+
 ※店舗ID=1、商品ID=4の行は2行存在するが、重複排除され1行のみ出力された。
 
・重複排除(group by句)
select   店舗ID,
         商品ID
from     ご意見
group by 店舗ID,商品ID
order by 店舗ID,商品ID;

  +--------+--------+
  | 店舗ID | 商品ID |
  +--------+--------+
  | 1      | 4      |
  | 1      | 5      |
  | 3      | 2      |
  +--------+--------+
 ※distinct句とorder by句のどちらで重複排除すべきか下記ページで考察されている。
  重複行のまとめ方はGROUP BY?DISTINCT? - Qiita
  https://qiita.com/tori076/items/ef7ac4301f9c20491bae


 
・集計(count関数)
select   店舗ID,
         商品ID,
         count(*) as ご意見件数
from     ご意見
group by 店舗ID,商品ID
order by 店舗ID,商品ID;

  +--------+--------+------------+
  | 店舗ID | 商品ID | ご意見件数 |
  +--------+--------+------------+
  | 1      | 4      | 2          |
  | 1      | 5      | 1          |
  | 3      | 2      | 1          |
  +--------+--------+------------+
 ※count関数の引数にカラム名を指定すると、そのカラムがNULLではない件数を返す。
  例えば、count(ご意見件数)なら、店舗ID=1、商品ID=4の行のご意見件数は1になる。
 
・集計(having句での抽出)
select   店舗ID,
         商品ID
from     ご意見
group by 店舗ID,商品ID
order by 店舗ID,商品ID
having   店舗ID = 1;

  +--------+--------+
  | 店舗ID | 商品ID |
  +--------+--------+
  | 1      | 4      |
  | 1      | 5      |
  +--------+--------+

 
---------------------
目次

https://1drv.ms/b/s!AivF3bzWXOzuhG1Xk5hscKYqkLkM

eclipseのショートカット自分用まとめ

Eclipseをまだ使いこなせておらず生産性が上がらないので、身近な所からということでショートカットキーの練習をしていました。

 

他の人の操作を見たり実際に試したりして、最優先で使いこなせるようになりたいと思ったショートカットキーは以下です。
Windowsで一般的に使用できる当たり前のショートカットもあえて載せています)
覚えていないものもあるので、これらのショートカットキーは使いこなせるようにしたいです。

 

【ウインドウ操作】
・上書き保存
 ctrl + s
・すべてを上書き保存
 ctrl + shift + s
・閉じる
 ctrl + w
・すべてを閉じる
 ctrl + shift + w
・ウインドウの最大化/最大化解除
 ctrl + m
・タブを検索
 ctrl + e
 
【範囲選択】
・範囲選択
 shift + 矢印キー
・区切りの良い場所へカーソル移動
 ctrl + right or ctrl + left
  ※shiftと併用可
・選択範囲の拡大縮小(変数→行→for文→メソッド)
 alt + shift + up or alt + shift + down
 
【検索】
・呼び出し階層を開く
 ctrl + alt + h
・検索と置換ダイアログの表示
 ctrl + f
javaに限ったファイル検索(grep)
 alt + a -> j
・対応する大括弧へジャンプ
 ctrl + shift + p
・宣言を開く(定義している箇所を探す)
 F3
 
【コーディング】
・元に戻す
 ctrl + z
・やり直し
 ctrl + y
・変数名やメソッド名の変更
 alt + shift + r
コメントアウト
 ctrl + /
・インデント整形
 ctrl + shift + f
・import文の編成(import文ソート、不要なimport文削除)
 ctrl + shift + o
・次の警告・エラーへジャンプ
 ctrl + .
・警告・エラーのクイックフィックス呼び出し
 ctrl + 1
 
デバッグ
・1行ずつ実行(そのクラス内において1行ずつ)
 F6
・次のブレークポイントまで実行
 F8
・変数の値を表示
 ctrl + shift + i
ブレークポイントの設定/解除
 ctrl + shift + b

java:CLASSPATHの基礎を調べてみた

そういえばCLASSPASSについて知識があやふやだな…自分が読んだ入門書にも書いてなかったしな…と思ったので、入門的な内容ですが簡単に調べてみました。
 

CLASSPATHって(ytp.ne.jp)
 http://www.ytp.ne.jp/tech/java/sineruka/classpath.html

 CLASSPATHとはどのようなものか書かれています。
 CLASSPATHコンパイルする時にも実行する時にも使われます。
 importとpackageとの関係についても書かれています。
 

・『Eclipse SWTに特化した情報 』 - クラスパスの設定
 http://www.hot-surprise.org/IntroEclipse/Operation/N01/3_3.html
 Eclipseで用いるクラスパスについて設定方法が書かれています。
 (OSの環境変数CLASSPATH」とは異なります。

  Eclipse上でビルド・実行する時に使用されます。)
 

GWT Center - Javaの実行時にクラスパスを追加する
 https://www.gwtcenter.com/dynamic-classpath

 jarファイルの実行時にクラスパスを指定する方法について書かれています。
 一般的にはjarファイル内のマニフェストファイルで指定するようです。

deleteの前にselectをしてミスを防ぐ

作業ミスを防ぐためのちょっとしたテクニックです。
参考書にはあまり載っていない暗黙知的なテクニックで、運が良ければ先輩から教えてもらえるのですが、逆に言うと運が悪いと教えてもらえないかもしれないので、文章に起こします。
 
特にオートコミット(commitを発行しなくても更新が反映される設定)の場合に効果を発揮します。
delete文で誤ったレコードを消すと取り返しがつかなくなることがあるのですが、事前にdelete文と同じ条件でselect文を打つことで、delete文のミスに事前に気付くことができます。
 
例えば、このようなテーブルがあるとします。
商品
+--------+--------+------------+
| 商品ID | 商品名 | 引き渡し先 |
+--------+--------+------------+
| 1 | 商品1 | A社 |
| 2 | 商品2 | B社 |
| 3 | 商品3 | NULL |
| 4 | 商品4 | D社 |
+--------+--------+------------+
 
このテーブルについて、引き渡し先が登録されているレコード(NULLではないレコード)を削除しようとした場合、以下のようなSQL文を作成します。
delete from 商品 where 引き渡し先 is not null;
しかし、仮にここで条件文のnotを忘れると、逆に引き渡し先が登録されていないレコード(NULLのレコード)を削除してしまい、取り返しがつかなくなります。
 
そこで、同じ条件で事前にSELECT文を発行します。
select * from 商品 where 引き渡し先 is not null;
このselect文を発行することで、事前に消えるレコードを見ることができます。
今回の例では以下のレコードが消えることを確認できます。
+--------+--------+------------+
| 商品ID | 商品名 | 引き渡し先 |
+--------+--------+------------+
| 1 | 商品1 | A社 |
| 2 | 商品2 | B社 |
| 4 | 商品4 | D社 |
+--------+--------+------------+
 
-----------------------
 
作業ミスを防ぐための暗黙知的なテクニックは他にもあるので、思い出したら随時書きます。

JUnit:テストケース毎の初期化

JUnitで複数のテストを行う際、あるテストが以降のテストに影響を与えてしまうことがあります。
あるテストが以降のテストに影響を与えると、テストの実行順を変えたりテストケースを変えたりするだけで今まで通っていたテストが通らなくなるので、望ましい状況ではありません。
今回は簡単な例を挙げて、対処法を紹介します。
 
【テスト対象のプログラム】
テスト対象は以下のプログラムである。
"HelloWorld"を返すのが正しい仕様だが、以前の実行で内部変数が変化すると"HelloKitty"を返すことがある、というのを表現している。
(なお、以前のテストが影響を与える例としては、内部変数の変化の他に、インプットのファイル・テーブルの変更が考えられる)
 
package jp.co.hello;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class HelloWorld
 * WebServletとしたことに特に意味は無い。
 */
@WebServlet("/HelloWorld")
public class HelloWorld extends HttpServlet {
    static int flg = 0;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloWorld() {
        super();
    }

    public String Hello() {
        if(flg==0) {
            flg=1;
            return "HelloWorld";
        } else {
            return "HelloKitty";
        }
    }
}
 
【テストクラス】
HelloTest.class→HelloTest2.classの順番に実行する。
HelloTest.classのテストメソッドは2つ、HelloTest2.classのテストメソッドは1つであり、何れのテストメソッドも"HelloWorld"が返却されることを期待する。
 
・HelloTestAll.java
package jp.co.hello;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({
    HelloTest.class,
    HelloTest2.class
})

public class HelloTestAll {

}
 
・HelloTest.java
package jp.co.hello;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Test;

public class HelloTest {

    @Test
    public void testHello() {
        HelloWorld example = new HelloWorld();
        assertEquals("HelloWorld", example.Hello());
    }
    @Test
    public void testHello2() {
        HelloWorld example = new HelloWorld();
        assertEquals("HelloWorld", example.Hello());
    }
}
 
・HelloTest2.java
package jp.co.hello;

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Test;

public class HelloTest2 {

    @Test
    public void testHello() {
        HelloWorld example = new HelloWorld();
        assertEquals("HelloWorld", example.Hello());
    }
}
 
【対応前のテスト結果】
HelloTest.classの1つ目のテストメソッドで内部変数が変化することで、以降のテストメソッドでは"HelloKitty"を返却するようになる。
その結果、HelloTest.classの2つ目のテストメソッド、及びHelloTest2.classのテストメソッドが失敗する。
 
【対応】
一つのテストメソッドが完了する度に、@Afterアノテーションを付与したメソッドで初期化処理を行う。
(なお、一つのテストメソッドが開始する前に初期化処理を行う場合は@Beforeアノテーション、一つのテストクラスが開始/終了する度に初期化処理を行う場合は@BeforeClassアノテーション/@AfterClassアノテーションを使用する)
 
以下のコードをHelloTest.class、HelloTest2.classに追加する。
 
    @After
    public void doAfter() {
     HelloWorld.flg=0;
    }
 
【対応後のテスト結果】
テストメソッドが完了する度に内部変数が初期化されるため、何れのテストも成功するようになる。

情報処理技術者試験対策「SQL(取得結果に対する条件判定)」

SELECT文で取得した結果に対して、CASE式を使用することで条件判定が可能です。
条件判定を行うことで、SQL文の出力結果を変えることができます。
プログラムのIF文の判定をSQL文の中で行っているようなイメージであり、プログラムを作るまでもない作業で使用する他、プログラムに組み込む場合も使い方次第でプログラムの可読性・保守性を高めることができます。
 
なお、NULLの時に出力結果を変えるだけであればCOALESCE関数の方がスマートなので、これも併せて紹介します。
 
【文法】

  • CASE式(単純CASE式)
    SELECT~FROMの間のカラム指定の箇所で使用する。
    カラムの値に応じて返す値を指定する。
     
    CASE カラム
        WHEN 値 THEN 値
        :
        :
        :
        ELSE 値
    END
     
  • CASE式(検索CASE式)
    SELECT~FROMの間のカラム指定の箇所で使用する。
    式に応じて返す値を指定する。
     
    CASE
        WHEN 式 THEN 値
        :
        :
        :
        ELSE 値
    END
     
  • COALESCE関数
    SELECT~FROMの間のカラム指定の箇所で使用する。
    可変長の引数を持ち、NULLではない最初の値を返す。
     
    COALESCE(値,値…)
     

【対象テーブル例】
商品
+--------+--------+--------+----------+----------+
| 商品ID | 商品名 | 在庫数 | 現在売値 | 標準売値 |
+--------+--------+--------+----------+----------+
| 1      | 商品1 | 60     | 100      | 100      |
| 2      | 商品2 | 60     | 200      | 250      |
| 3      | 商品3 | 15     | 300      | NULL     |
| 4      | 商品4 | 0      | 400      | 350      |
+--------+--------+--------+----------+----------+
 
【取得例】
※ASは省略可能
 
SELECT   商品名,
         CASE 在庫数
             WHEN 0 THEN '品切れ'
             ELSE '在庫有'
         END AS 在庫状況,
         CASE
             WHEN 標準売値 IS NULL THEN '一時品  '
             WHEN 現在売値 = 標準売値 THEN '適正価格 '
             WHEN 現在売値 < 標準売値 THEN '値下げ品 '
             ELSE '便乗値上げ'
         END AS 価格戦略,
         COALESCE(標準売値,現在売値) AS 適正価格
FROM     商品
ORDER BY 商品ID;

+--------+----------+------------+----------+
| 商品名 | 在庫状況 | 価格戦略   | 適正価格 |
+--------+----------+------------+----------+
| 商品1 | 在庫有   | 適正価格  | 100      |
| 商品2 | 在庫有   | 値下げ品  | 250      |
| 商品3 | 在庫有   | 一時品   | 300      |
| 商品4 | 品切れ   | 便乗値上げ | 350      |
+--------+----------+------------+----------+
 
---------------------
目次

https://1drv.ms/b/s!AivF3bzWXOzuhG1Xk5hscKYqkLkM

java:2通りの例外の受け取り方

入門書にも書いてある内容ですが、COBOLC言語にはない挙動なので備忘のためにまとめます。
 
【パターン1:catch文で例外を受け取る】
①で例外が発生すると、catchに遷移する。
 
public static void main(String args) {
    try{
        ①
    }
    catch(Exception e){
        …例外処理…
    }
}
 
【パターン2:throwsで呼び出し元に例外を投げる】
computeメソッドではthrowsで呼び出し元に例外を戻す宣言がされている。
computeメソッド内で例外が発生した場合、throwで呼び出し元のmainメソッドに例外を返す。
mainメソッドではその例外をcatchし、例外処理へ遷移する。
 
public static void main(String
args) {
    try{
        …
        compute(x,y);
        …
    }
    catch(Exception e){
        …例外処理…
    }
}
 
public int compute(int x,int y) throws IllegalArgumentException {
    …
    throw new IllegalArgumentException("不正な計算です");
    …
}
 
【例外を受け取らない場合】
computeメソッドでmainメソッドに例外を返しても、mainメソッドには例外を処理する記述がされていない。
この場合、エラーメッセージ「Exception in thread "main" java.lang.IllegalArgumentException: 不正な計算です」とスタックトレースを出力して処理が途中で止まる(異常終了する)。
 
public static void main(String[] args) {
    …
    compute(x,y);
    …
}
 
public int compute(int x,int y) throws IllegalArgumentException {
    …
    throw new IllegalArgumentException("不正な計算です");
    …
}