Hatena::ブログ(Diary)

stacked tip that high

2018-04-21

ER図の記法3選

ER図(実体関連図:Entity-relationship Diagram)は、主にデータベースを扱う際に、データモデルを表現するのに使用します。
ER図の記法はいくつかあるのですが、ここでは、メジャーな3つを紹介します。

後で述べますが、同じER図を描くのに、それぞれの分野で別々の記法を使用しているため、実はこの2つの記法は同じことを示していた(描き方が違うだけ)ということに気付かないことがあります(私がそうでした)。

バックマン線図

1969年にバックマン(C. W. Bachman)が発表した記法*1
線・矢印を使ったシンプルな記法であり、情報処理技術者試験応用情報技術者試験等)ではこの記法が使われています。
f:id:stacked-tip:20180421162130p:image
シンプルであることは欠点でもあり、他の2つの比較して、ぱっと見た時の意味が分からないのが、バックマン線図の欠点です。原著では下図に対して、
f:id:stacked-tip:20180421162825p:image:w500

The direction of the arrow is read to mean that each employee is a member of a set of employees that belong to a particular department, and further, that each department has such a set of employees.

とあるだけで、ここに矢印を導入した理由や妥当性には触れていません。
基本的には「そういうものだ」と覚えるしかないのです。

Crow's Foot (カラスの足)記法

1980年代に、コンサルティングファームCACIにてバーカー(R. Barker)が考案した記法(バーカーはのちにオラクル入社し、RDBMSソフトを開発)。
見た目が分かりやすいので、ER図の実践的教育の場面で良く使用されますが、表現できる内容が少ないので、複雑な関係性を表記する際には、この記法はあまり使われません。
f:id:stacked-tip:20180421170358p:image

クラス図(UML

UML(統一モデリング言語:Unified Modeling Language)は、オブジェクト指向分析・設計の際に使用される記法群です。UMLの中に色々な記法が含まれており、クラス図はその中のひとつです。
f:id:stacked-tip:20180421172231p:image
多重度が数字で表記されるのでわかりやすく、また、それ以外の様々な情報を表現することができます。しかし、ER図としては機能が過剰であるので、ER図として用いられることはあまりありません。

図の転載元

  1. 平成30年度 春期 応用情報技術者試験 午後 問題
  2. Data structure diagrams
  3. The Details of Conceptual Modelling Notations are Important - A Comparison of Relationship Normative Language
  4. UML Class Diagram - explanation & concepts

2018-04-07

応用情報技術者試験「データベース」に出てきたSQLを眺めてみる

ONの後ろが空欄

ONの後ろの空欄は、「○○.●● = △△.●●」の形の条件文が入ります。
例1) 平成29年春期

SELECT 申請書.申請書ID,
       申請書.タイトル,
       申請書.申請日
       ユーザ.ユーザ名,
       部署マスタ.部署名
FROM 申請書
    INNER JOIN 承認申請 ON 申請書.申請書ID = 承認申請.申請書ID
    INNER JOIN ユーザ ON 申請書.申請者ユーザID = ユーザ.ユーザID
    INNER JOIN 部署マスタ ON ユーザ.部署ID = 部署マスタ.部署ID
WHERE (承認申請.承認申請状態 NOT IN ('可決', '否決'))
      AND
      ((申請書.申請者ユーザID = :ユーザID)
       OR
       (申請書.申請書ID
        IN (SELECT DISTINCT 申請書ID
            FROM 承認者情報
                --この行のON以降が空欄
                INNER JOIN 承認申請 ON 承認者情報.承認申請ID = 承認申請.承認申請ID
            WHERE 承認者情報.承認者ユーザID = :ユーザID )
      ))

SELECT 申請書.申請書ID, 申請書.タイトル, t1.項目値 AS 金額, t2.項目値 AS 支払日
                                         --下の2行のON以降が共に空欄
    FROM 申請書 INNER JOIN 申請書項目 t1 ON 申請書.申請書ID = t1.申請書ID
                INNER JOIN 申請書項目 T2 ON 申請書.申請書ID = t2.申請書ID
    WHERE
    ((申請書.書式ID = '購買' AND t1.項目キー = 'amount' AND t2.項目キー = 'pay_data') OR
     (申請書.書式ID = '契約' AND t1.項目キー = 'pay_initial' AND t2.項目キー = 'start_date'))
    AND (承認申請.申請書ID = 申請書.申請書ID AND 承認申請.承認申請状態 = '可決')

例2) 平成23年秋期

SELECT 明細行番号, 日付, 費用種別.費用種別名, 金額
FROM 申請明細 INNER JOIN 費用種別
    ON 申請明細.費用種別コード = 費用種別.費用種別コード    --この行のON以降が空欄
WHERE 申請明細.申請書番号 = :申請書番号 AND 費用種別.証憑フラグ = 'Y'
ORDER BY 明細行番号

GROUP BYの後ろが空欄

GROUP BYの後ろの空欄には、SELECTの後ろに続く項目名が入ります。
例1)平成29年秋期

SELECT 品名コード, 品目名, 産地コード, 産地名,
SUM(t2.単価 * t1.パレット数) AS 合計返品金額, SUM(t1.パレット数) AS 合計返品数量
    FROM 返品 t1
        INNER JOIN 販売明細 t2 USING (販売番号, 販売明細番号)
        INNER JOIN 仕入明細 USING(仕入番号,仕入明細番号)
        INNER JOIN 品目 USING (品目コード)
        INNER JOIN 産地 USING (産地コード)
    WHERE 返品日 = CURRENT_DATE
    GROUP BY 品目コード,品目名,産地コード,産地名    --この行のGROUP BY以降が空欄
    ORDER BY 品目コード ASC, 産地コード ASC

COALESCEを含むSELECT文

COALESCE()関数が出てきたら、検索結果にNULLが含まれる場合があることを意味します。
この時、多くの場合外挿(OUTER JOIN)が行われていています。
例1) 平成28年春期

--このSELECT文にはCOALESCE()がある
SELECT ST.確認年月日, ST.店舗ID, ST.商品ID, COALESCE(SS.日間販売数量, 0),
    ST.日間在庫数量
FROM
    (SELECT SC.確認年月日, SC.店舗ID, SC.商品ID,
        AVG(SC.在庫数量) AS 日間在庫数量
     FROM 在庫 SC
     GROUP BY SC.確認年月日, SC.店舗ID, SC.商品ID) ST
     LEFT OUTER JOIN    --ここにLEFT OUTER JOINがいる
    (SELECT SL.販売年月日, SL.店舗ID, SD.商品ID,
        SUM(SD.販売数量) AS 日間販売数量
     FROM 販売 SL
        INNER JOIN 販売詳細 SD ON SL.販売ID = SD.販売ID
     GROUP BY SL.販売年月日, SL.店舗ID, SD.商品ID) SS
      ON ST.確認年月日 = SS.販売年月日
         AND ST.店舗ID = SS.店舗ID
         AND ST.商品ID = SS.商品ID

SELECT SF.売上年月, SF.店舗ID, IT.商品分別ID,
    AVG(SF.日間販売数量) AS 平均販売数量, AVG(SF.日間在庫数量) AS 平均在庫数量
FROM
    (SELECT TO_YYYYMM(SA.売上年月日) AS 売上年月, SA.店舗ID, SA.商品ID,
        SA.日間販売数量, SA.日間在庫数量
     FROM 売上ファクト SA) SF
    INNER JOIN 商品 IT ON SF.商品ID = IT.商品ID
GROUP BY SF.売上年月, SF.店舗ID, IT.商品分類ID
ORDER BY SF.売上年月 DESC,SF.店舗ID ASC,平均在庫数量 DESC

例2) 平成25年春期

INSERT INTO 発言キーワード重み (キーワードID, 重み)
--このSELECT文にはCOALESCE()がある
SELECT キーワード.キーワードID, COALESCE(OMOMI.CNT,0)
FROM キーワード
    LEFT OUTER JOIN    --ここにLEFT OUTER JOINがいる(ここは空欄であった)
    (SELECT 発言キーワード.キーワードID, COUNT(*) AS CNT
     FROM 発言キーワード
        INNER JOIN 発言 ON 発言.発言ID = 発言キーワード.発言ID
     WHERE 発言.社員番号 IS NULL
     GROUP BY 発言キーワード.キーワードID) OMOMI
    ON キーワード.キーワードID = OMOMI.キーワードID


SELECT 発言.発言ID, SUM(発言キーワード重み.重み) AS WEIGHT
FROM 発言
    INNER JOIN 発言キーワード ON 発言.発言ID = 発言キーワード.発言ID
    INNER JOIN 発言キーワード重み
        ON 発言キーワード.キーワードID = 発言キーワード重み.キーワードID
WHERE 発言.否定的フラグ = '1'
GROUP BY 発言.発言ID
ORDER BY WEIGHT DESC


平成28年秋期

SELECT t1.会員番号, t1.氏名, t6.商品分類番号,
    t6.商品分類名, SUM(t4.商品単価 * t4.個数) AS 購入金額合計
FROM 会員 t1
    INNER JOIN (SELECT t2.購入番号, t2.会員番号
        FROM 購入 t2 WHERE t2.購入日時 > :一年前) t3 ON t1.会員番号 = t3.会員番号
    INNER JOIN 購入明細 t4 ON t3.購入番号 = t4.購入番号
    INNER JOIN 商品 t5 ON t4.商品番号 = t5.商品番号
    INNER JOIN 商品分類 t6 ON t5.商品分類番号 = t6.商品分類番号
GROUP BY t1.会員番号, t1.氏名, t6.商品分類番号, t6.商品分類名

DECLARE cur CURSOR FOR
    SELECT t2.会員番号, t2.購入番号, t2.購入金額
    FROM 購入 t2
    WHERE t2.購入ステータス = '完了'
    AND t2.購入日時 <= :判定対象期限
    AND t2.判定処理状態 <> '判定処理済み'
    ORDER BY t2.会員番号,t2.購入日時 ;
UPDATE 会員 t1 SET t1.会員種別 = '一般会員';
SET current_kaiin_no = 0;
SET goukei = 0;
OPEN cur;
fetch_loop: LOOP
    FETCH cur INTO kaiin_no, kounyu_no, kounyu_kingaku;
    IF kaiin_no <> current_kaiin_no THEN
        SET current_kaiin_no = kaiin_no;
        SET update_flag = 0;
        SET goukei = 0;
    END IF;
    IF update_flag = 0 THEN
        SET goukei = goukei + kounyu_kingaku;
        UPDATE 購入番号 = kounyu_no;
            WHERE t2.購入番号 = kounyu_no;
        IF goukei >= 50000 THEN
            UPDATE 会員 t1 SET t1.会員種別 = '特別会員' WHERE t1.会員番号 = kaiin_no;
            SET update_flag = 1;
        END IF;
    ELSE
        UPDATE 購入 t2 SET t2.判定処理状態 = '繰越し' WHERE t2.購入番号 = kunyu_no;
    END IF;
END LOOP fetch_loop;
CLOSE cur;

平成27年秋期

WITH RECURSIVE 関連部署(部署ID, 部署名, 上位部署ID) AS (
    SELECT 部署.部署ID, 部署.部署名, 部署.上位部署ID
        FROM 部署 WHERE 部署.上位部署ID IS NULL
            AND :年月日 BETWEEN 部署.適用開始年月日 AND 部署.適用終了年月日
UNION ALL
    SELECT 部署.部署ID, 部署.部署名, 部署.上位部署ID
        FROM 部署, 関連部署 WHERE 部署.上位部署ID = 関連部署.部署ID
            AND :年月日 BETWEEN 部署.適用開始年月日 AND 部署.適用終了年月日
)
SELECT 部署ID, 部署名, 上位部署ID FROM 関連部署

平成27年春期

SELECT AC.*
FROM アクセスログ AC
WHERE EXISTS
    (SELECT * FROM 非営業日 NS
     WHERE AC.操作年月日 = NS.非営業年月日)

SELECT AC.*
FROM アクセスログ AC
    INNER JOIN 利用者 US ON AC.利用者ID = US.利用者ID
    INNER JOIN サーバ SV ON AC.サーバID = SV.サーバID
WHERE AC.操作結果 = 'F'
    AND US.部署ID <> SV.部署ID

平成26年春期

SELECT COUNT(*) AS 判定結果
FROM Webユーザ
WHERE ユーザID = :ユーザID
    AND パスワードハッシュ値 = HASH(:パスワード)

SELECT A.船便番号, A.船便名, C.座席クラス番号, C.座席クラス名,
    COUNT(*) AS 空席数
FROM 船便 A
    INNER JOIN 座席クラス C ON B.座席クラス番号 = C.座席クラス番号
WHERE A.出発日 = :出発日
  AND A.出発地 = :出発地
  AND A.到着地 = :到着地
  AND B.空席状況 = '0'
GROUP BY A.船便番号,A.船便名,C.座席クラス番号,C.座席クラス名

平成25年秋期

SELECT タイトルマスタ.タイトル, 会員マスタ.会員ID, 会員マスタ.氏名,
    会員マスタ.連絡先, 貸出予約.予約日時
FROM (タイトルマスタ
    INNER JOIN 貸出予約 ON タイトルマスタ.タイトルID = 貸出予約.タイトルID)
    INNER JOIN 会員マスタ ON 貸出予約.会員ID = 会員マスタ.会員ID
WHERE タイトルマスタ.タイトルID = :予約タイトルID
    AND 貸出予約.予約日時 = (SELECT MIN(貸出予約.予約日時) FROM 貸出予約
                             WHERE 貸出予約.タイトルID = :予約タイトル
                               AND 貸出予約.予約ステータス = '予約中')

平成24年春期

CREATE VIEW 統合検索
    (書籍名, 著者名, 出版社名, ISBN番号, 図書館名, 貸出可能冊数) AS
SELECT 書籍A.書籍名, 書籍A.著者名, 書籍A.出版社名, 書籍A.ISBN番号,
    'A市図書館', COUNT(書籍A.書籍番号) FROM 蔵書A, 書籍A
WHERE 蔵書A.書籍番号 = 書籍A.書籍番号
    AND 蔵書A.蔵書番号 NOT IN (SELECT 貸出記録A.蔵書番号 FROM 貸出記録A
                               WHERE 貸出記録A.返却日 IS NULL)
GROUP BY 書籍名, 著者名, 出版社名, 書籍A.ISBN番号
UNION
SELECT 書籍B.書籍名, 書籍B.著者名, 書籍B.出版社名, 書籍B.ISBN番号,
    'B市図書館', COUNT(書籍B.ISBN番号) FROM 蔵書B, 書籍B
WHERE 蔵書B.ISBN番号 = 書籍B.ISBN番号
    AND 蔵書B.蔵書番号 NOT IN (SELECT 貸出記録B.蔵書番号 FROM 貸出記録B
                               WHERE 貸出記録B.返却日 IS NULL)
GROUP BY 書籍名, 著者名, 出版社名, 書籍B.ISBN番号


CREATE VIEW 統合貸出予約
    (蔵書番号, 書籍名, 著者名, 出版社名, ISBN番号, 図書館名, 貸出状況) AS
SELECT 蔵書A.蔵書番号, 書籍A.書籍名, 書籍A.著者名, 書籍A.出版社名,
    書籍A.ISBN番号, 'A市図書館', 蔵書A.貸出状況) FROM 蔵書A, 書籍A
WHERE 蔵書A.書籍番号 = 書籍A.書籍番号
    AND 蔵書A.貸出状況 = '貸出可'
UNION
SELECT 蔵書B.蔵書番号, 書籍B.書籍名, 書籍B.著者名, 書籍B.出版社名,
    書籍B.ISBN番号, 'B市図書館', 蔵書B.貸出状況) FROM 蔵書B, 書籍B
WHERE 蔵書B.ISBN番号 = 書籍B.ISBN番号
    AND 蔵書B.貸出状況 = '貸出可'

平成23年秋期

SELECT 明細行番号, 日付, 費用種別.費用種別名, 金額
FROM 申請明細 INNER JOIN 費用種別
    ON 申請明細.費用種別コード = 費用種別.費用種別コード
WHERE 申請明細.申請書番号 = :申請書番号 AND 費用種別.証憑フラグ = 'Y'
ORDER BY 明細行番号


SELECT 申請書.組織コード, 組織.組織名,
    SUM (申請明細.金額) AS 組織交通費合計
FROM 申請書, 申請明細, 組織
WHERE 申請明細.日付 BETWEEN :指定月開始日 AND :指定月終了日
    AND 申請書.申請書番号 = 申請明細.申請書番号
    AND 申請書.組織コード = 組織.組織コード
GROUP BY 申請書.組織コード, 組織.組織名
ORDER BY 申請書.組織コード

平成23年特別

CREATE TABLE 新刊 (書籍番号 INTEGER, ジャンル INTEGER,
    PRIMARY KEY(書籍番号),
    FOREIGN KEY(書籍番号)
    REFERENCES 書籍(書籍番号))

DELETE FROM 新刊;
INSERT INTO 新刊 (書籍番号, ジャンル)
    SELECT 書籍番号, ジャンル FROM 書籍 WHERE 発行年月日 + 60 > :今日;


CREATE TABLE 購入傾向
    (顧客番号 INTEGER, ジャンル INTEGER, 購入冊数 INTEGER,
     PRIMARY KEY(顧客番号, ジャンル),
     FOREIGN KEY(顧客番号)
     REFERENCES 顧客(顧客番号))

DELETE FROM 購入傾向;
INSERT INTO 購入傾向 (顧客番号, ジャンル, 購入冊数)
    SELECT 注文.顧客番号, 書籍.ジャンル, SUM(注文明細.数量)
    FROM 注文, 注文明細, 書籍
    WHERE 注文.注文年月日+180>:今日
    AND 注文.注文番号 = 注文明細.注文番号
    AND 注文明細.書籍番号 = 書籍.書籍番号
    GROUP BY 注文.顧客番号, 書籍.ジャンル;


SELECT 書籍.書籍番号, 書籍.書籍名, 書籍.書籍説明 FROM 書籍, 新刊, 購入傾向
    WHERE 書籍.書籍番号 = 新刊.書籍番号
    AND 購入傾向.顧客番号 = :顧客番号
    AND 書籍.ジャンル = 購入傾向.ジャンル
    AND 購入傾向.購入冊数 * 10 > :購入総冊数


UPDATE 購入傾向 SET 購入冊数 =
    (SELECT 購入傾向.購入冊数 + 注文明細.数量 FROM 注文明細, 書籍
     WHERE 注文明細.注文番号 = :注文番号 AND 注文明細.注文明細番号 = :注文明細番号
     AND 注文明細.書籍番号 = 書籍.書籍番号 AND 書籍.ジャンル = 購入傾向.ジャンル)
WHERE 購入傾向.顧客番号 = :顧客番号
AND 購入傾向.ジャンル IN
    (SELECT 書籍.ジャンル FROM 注文明細, 書籍
     WHERE 注文明細.注文番号 = :注文番号 AND 注文明細.注文明細番号 = :注文明細番号
     AND 注文明細.書籍番号 = 書籍.書籍番号)

平成22年秋期

CREATE TABLE 請求
(請求書番号 CHAR(5),
顧客番号 CHAR(5), 請求日 CHAR(8), 計上年月 CHAR(6), 請求額 NUMERIC(10),
買上額 NUMERIC(10), 消込額 NUMERIC(10),
PRIMARY KEY (請求書番号),
FOREIGN KEY(顧客番号) REFERENCES 顧客(顧客番号))


SELECT 顧客番号, SUM(入金額-消込額)
    FROM 入金
    WHERE 入金日 <= :今月度末日
      AND 入金額 > 消込額
    GROUP BY 顧客番号

平成22年春期

SELECT t.店舗番号, t.店舗名, SUM(m.受注金額) AS 金額
FROM (店舗 t INNER JOIN (SELECT j.受注店舗番号, j.受注番号 FROM 受注 j
           WHERE j.受注日付 BETWEEN :指定月開始日 AND :指定月終了日) p
           ON t.店舗番号 = p.受注店舗番号)
          INNER JOIN 受注明細 m ON p.受注番号 = m.受注番号
GROUP BY t.店舗番号, t.店舗名
ORDER BY t.店舗番号


SELECT m.出荷店舗番号, SUM(m.受注金額) AS Web売上分
FROM 受注明細 m, 受注 j
WHERE (j.受注店舗番号 = 'A09999' ) AND ( j.受注番号 = m.受注番号 ) AND
      (j.受注日付 BETWEEN :指定月開始日 AND :指定月終了日)
GROUP BY m.出荷店舗番号
ORDER BY m.出荷店舗番号


SELECT t.店舗番号, t.店舗名, SUM(m.受注金額) AS 金額
FROM 店舗 t LEFT OUTER JOIN
    (SELECT j.受注店舗番号, j.受注番号 FROM 受注 j
        WHERE j.受注日付 BETWEEN :指定月開始日 AND :指定月終了日) p
            INNER JOIN 受注明細 m ON p.受注番号 = m.受注番号
    ON t.店舗番号 = p.受注店舗番号
GROUP BY t.店舗番号, t.店舗名
ORDER BY t.店舗番号

平成21年春期

SELECT TempTbl.商品番号, SUM(TempTbl.小計)
FROM (SELECT 注文明細.商品番号, 注文明細.数量 AS 小計
      FROM 注文明細
          INNER JOIN 商品 ON 注文明細.商品番号 = 商品.商品番号
      WHERE 注文明細.注文番号 = :注文番号
          AND 商品.セットフラグ = '0'
      UNION ALL
      SELECT セット商品.子商品番号 AS 商品番号,
               セット商品.数量 * 注文明細.数量 AS 小計
      FROM 注文明細
          INNER JOIN 商品 ON 注文明細.商品番号 = 商品.商品番号
          INNER JOIN セット商品 ON 商品.商品番号 = セット商品.セット商品番号
      WHERE 注文明細.注文番号 = :注文番号
          AND 商品.セットフラグ = '1') TempTbl
GROUP BY TempTbl.商品番号

SELECT DISTINCT 注文明細.注文明細番号, 注文明細.商品番号, 商品.商品名,
    注文明細.数量, 注文明細.販売単価, 注文明細.数量 * 注文明細.販売単価,
    CASE WHEN 新商品モデル.新商品番号 IS NOT NULL THEN '新モデルあり'
    ELSE '' END
FROM 注文明細
    LEFT OUTER JOIN 新商品モデル
        ON 注文明細.商品番号 = 新商品モデル.旧商品番号
    INNER JOIN 商品 ON 注文明細.商品番号 = 商品.商品番号
WHERE 注文明細.注文番号 = :注文番号
ORDER BY 注文明細.注文明細番号

2018-03-04

SQLの構文を整理する(その SELECT構文)

応用情報技術者試験の午後の問題でデータベースを選ぼうと計画していますが、
(どの分野が得点しやすいのかざっと調べたところ、
 データベースが取りやすそうだったという理由です)
SQLは実務で使ったこともないのでサッパリです。

なので、まずは構文を覚えようかなと思い、整理してみます。

SELECT [フィールド] FROM [テーブル]

  • [テーブル]の中の[フィールド]だけを抽出する構文
  • [フィールド]はカンマで区切ることで、複数指定することができる

SQLZOO SELECT basicsの1.の回答欄に、
以下のように入力し「Submit SQL」をクリックしてみましょう。

SELECT population
FROM world

すると、すべての国の「population」のフィールドが表示されます。

今度は以下のように入力し「Submit SQL」をクリックしてみましょう。

SELECT population, gdp
FROM world

すると、すべての国の「population」と「gdp」のフィールドが表示されます。

SELECT [フィールド] FROM [テーブル] WHERE [条件]

  • 選択した[フィールド]の中で、[条件]を満たすものだけを表示する

ちなみに、"where"という英単語は「どこ」という意味の他、
接続詞として使うと「〜において」、「ここで、〜とする」という意味になります。
SQLZOO SELECT basicsの1.の回答欄に、
以下のように入力し「Submit SQL」をクリックしてみましょう。

SELECT population
FROM world
WHERE name = 'Germany'

すると、「population」のフィールドのうち、「name」が「Germany」のものだけが表示されます。

先ほどと同様、複数のフィールドを選択することもできます。

SELECT population, gdp
FROM world
WHERE name = 'Germany'

条件式には、AND, OR, NOTなどが使用できます。

SELECT population, gdp
FROM world
WHERE name = 'Germany' OR name = 'France'

と入力すれば、「Germany」と「France」の「population」「gdp」を表示することができます。
しかし、上記の例のように、同じフィールドに対し複数の条件を設定する場合は、
INを利用した方が楽です。
SQLZOO SELECT basicsの2.の回答欄に、
以下のように入力し「Submit SQL」をクリックしてみましょう。

SELECT name, population
FROM world
WHERE name IN ('Sweden', 'Norway', 'Denmark')

すると、「population」のフィールドのうち、
「name」が「Sweden」「Norway」「Denmark」のものが表示されます。
(この例では一緒に「name」も表示しています。

また、数字の範囲指定には、各種等号・不等号(=, <, >, <=, >=, <>*1)の他、
BETWEENも使用できます。
SQLZOO SELECT basicsの3.の回答欄に、
以下のように入力し「Submit SQL」をクリックしてみましょう。

SELECT name, area
FROM world
WHERE area BETWEEN 200000 AND 250000

すると、「area」が20万以上25万以下の「name」と「area」が表示されます。

*1:≠を意味する記号として、<>の他に!=も使用できます。

2018-02-22

不正アクセスの単語整理

セキュリティAPでは午後必須科目ですから、
絶対に覚えたいところですね、17個もありますけど。

ちなみに、その17個は下記参考書に則って選択しています。
「平成29・30年版 ニュースペックテキスト応用情報技術者TAC出版

ソーシャルエンジニアリング

ソーシャル」と聞くとどうしても「社会的な」と翻訳してしまいますが、
元々"social"という英語は「人のあつまりの」という意味がありますから*1
ここから「人を介した(不正アクセス)技術」という意味でこの言葉が使われています。
総務省のHPによれば、

が具体例として挙げられていますが、他にも手口は色々はあります。

キーロガー

キー入力の記録(ログ)を取ることで、パスワードなどの機密情報を盗み出す手法です。

バックドア

"back door"は「裏口」という意味です。
バックドアという不正アクセスは、

  1. まず攻撃対象のパソコン・サーバに裏口を作るソフトを忍ばせ
  2. そのソフトが実行されることで実際に裏口が作られ
  3. その裏口から攻撃を受ける(データの盗み出しなど)

という手口です。

フットプリンティング

直訳すれば「足跡をつけること」であり、そこから、攻撃対象への事前調査を意味します。
whois情報等を調べて、攻撃対象のセキュリティシステムを推測したり、
CGIなどのWebサービスに悪意のある入力を与えた時の反応を見て、
ソフトウェアの弱点を推測したりする手口です。

ブルートフォース攻撃

"brute-force"に「強引な」という意味があります。
このうち"brute"は「獣の」という意味を持っていますが、ややマイナスな印象を持っており、
「野蛮な」「知性の低い」というニュアンスがあります。
総当たり攻撃などが分類されます。

パスワードリスト攻撃

名前の通り、攻撃者が別途入手したパスワードのリストを元に、
パスワードをこじ開けようとする手口です。
パスワードの流用を避ける理由は、パスワードリスト攻撃を回避するためです。

SQLインジェクション

"injection"は「挿入」という意味です。
次の項目のコマンドインジェクションもそうですが、
検索単語が入力されることが期待されているところに、SQLの命令文を混ぜて入力することで、
SQLが想定しない出力をさせて、不正に情報を得る手口のこと。
IPAのHPにも同様の事例が紹介されています。

コマンドインジェクション

上記SQLインジェクションOSコマンド版です。
具体的には、直接OSコマンドを実行できるシェルスクリプトに対して、
キーワード等が入力されることを期待差異れているところに、OSコマンドを混ぜて入力することで、
シェルスクリプトが想定しないOSコマンドを実行し、
不正に情報得たり、データ破壊を行ったりすることです。
IPAのHPでは、コマンドインジェクションで乗っ取ったサーバを経由して、
第三者サーバを攻撃する事例が紹介されています。

クロスサイトスクリプティング

手口としては、上述のSQLインジェクションやコマンドインジェクションと似ています。
「クロスサイト」とは「2つのサイトを跨いで」という意味であり、ここでは、

  1. 不正なスクリプト(命令)を受け付けると、問題のある動作をしてしまうWebサイト
  2. 不正なスクリプトのリンクが貼られているサイト(掲示板サイトなど)

の2つが登場します。そして、ユーザが2.の方のサイトでリンクをクリックしてしまうと、
ユーザが意図せず、問題のあるWebサイトに不正なスクリプトを送ってしまい、
その結果として、ユーザが偽のWebサイトに誘導され、個人情報等をだまし取られてしまう、
というのが代表的な手口です。
IPAのHPで図付きで解説されていますが、私は余りピンときませんでした。

クロスサイトリクエストフォージェリ

"forgery"は「偽造」という意味で、「リクエストフォージェリ」で
サーバへのリクエストを偽造する」ということを指しています。
登場するのは、上述のクロスサイトスクリプティングと似ていて、

  1. 偽造が容易なリクエストを受け付ける、問題のあるWebサイトサーバ
  2. 不正なリクエストを含むURLのリンクが貼られているサイト(掲示板サイトなど)

の2つです。Webサイトサーバへのリクエストは、
本来ユーザが正規の方法で(Webサイト上の操作で)送信しますが、
これを、不正なリクエストを含むURLのリンクをユーザが踏むことで、
ユーザが意図せず、Webサイトサーバにリクエストを送信してしまい、
それを偽造されたリクエストと見抜けないWebサイトサーバが、
ユーザからの意図したリクエストと判断し、それに従った処理をしてしまう、
というのが、クロスサイトリクエストフォージェリの流れです。
これも、IPAのHPに事例が載っています。

ゼロデイ攻撃

ソフトウェアはリリース後に脆弱性が明らかになることがあります。
通常は、脆弱性が発見されるとすぐにその箇所を修正するソフトが配布され、
その配布日をワンデイと呼びます。
ゼロデイ攻撃は、ワンデイより前にその脆弱箇所を突いた攻撃のことです。

ディレクトリトラバーサル攻撃

ディレクトリ」は「フォルダ」とも呼ばれる、
ファイルを(仮想的に)分類・格納するシステムです(と今更説明するまでもないですが)。
"traversal"は「横断する」という意味で、「ディレクトリトラバーサル」とはつまり、
本来は横断されないはずのディレクトリへ不正に横断され、
外部からのアクセスが想定されていないデータへアクセスされてしまう攻撃です。
日経のHPが分かりやすいと思います。

DoS攻撃

"DoS"とは"Denial of Service"の略語で、「サービス妨害」という意味です。
昔からあるサーバ攻撃手法で、大量のリクエストをサーバに送り付ける等の方法で、
サーバ処理負荷を上昇させ、処理を遅くしたり、サーバをダウンさせたりする手口です。
情報を盗み出すというよりも、サービスの妨害やシステムの破壊を目的とした不正アクセスです。

フィッシング

元々は「釣り」を意味する"fishing"から来た言葉ですが、
「偽のWebサイトへ誘導して、個人情報等を盗み取る」という意味から
"phishing"(発音は同じ)と綴られることが多いです。

IPスプーフィング

"spoof"は「だます、ひっかける」という意味ですが、インターネットセキュリティの分野では
「なりすます」という意味を帯びます。
IPスプーフィングとは、IPアドレスをなりすますことで、攻撃をしやすくする手口です。
具体的には、あるサーバに対して、あたかも内部からのアクセスかのようにIPを偽造して
アクセスすることで、相手の防御システムをかいくぐる方法です。

セッションハイジャック

ユーザとサーバとの通信のやり取りを「セッション」と言います。
セッションハイジャックとは、このやり取りに紛れて、サーバに不正な操作を行う攻撃です。

DNSキャッシュポイズニング

DNSURLIPアドレスに変換する(名前解決する)サーバです。
DNSはこの変換を行うために、URLIPアドレスの対応リストを持っていますが、
このリストにないURLを変換する時や、定期的なリスト更新のために、
別のDNSに対してIPアドレスを問い合わせることをします。
この機能を悪用して、

  1. 攻撃者がDNSに対して、DNSが持っていないだろうURLの名前解決をリクエストする
  2. DNSが他のDNSに対して、同様のリクエストをする
  3. その回答が送られてくる前に、攻撃者が偽のIPアドレスの回答を送り付ける
  4. それを正規の回答と勘違いし、自分のリストに追加してしまう

というのが、DNSキャッシュポイズニングです。
"poisoning"は「毒を盛る」という意味です。
この攻撃を受けたDNSを使うと、該当するURLに対して偽のIPアドレスを回答してしまうので、
ユーザは正しいURLを入力したにも関わらず、偽のWebサイトに誘導されてしまうのです。

*1:そして、元々は「村の祭り」という意味でしかなかった「社会」という漢語を、"society"の訳語として日本人が当てたのです。

2018-02-17

原点を通る3次関数の接線の式を代数的に求めてみる

3次関数の接線に関する問題は、接点の座標を使って接線の関数を立て、
その接線が3次関数上にない決められた点を通るので…という流れで解くのがセオリーです。
微分を用いた、いわゆる「逆手流」の解き方ですね。

3次関数y=px^3+qx^2+rx+sの接線が点(a,b)を通るときの、
接線の式を求めよ、という問題は、
接点のx座標をtと置くことで、接線の式を
y=(3pt^2+2qt+r)(x-t)+pt^3+qt^2+rt+sと表せるので、
これが点(a,b)を通るということは、
b=(3pt^2+2qt+r)(a-t)+pt^3+qt^2+rt+sが成り立つので…という流れで、
接点のx座標をtを求め、そこから接線の方程式を求める、
という方法です。

(a,b)がたとえ原点、つまり点(0,0)であっても同様であり、
某有名予備校の某講師曰く、
「ここ(=点(a,b))が原点だとぉ、誘惑に負けて
 y=mxなんて置いちゃう輩がいるけど、駄目だよぉ!」
というやつです。

じゃぁこれを敢えてy=mxと置いて解こうとするとどうなるのか、
ちょっとやってみようと思います。

カルダノ=タリタリアの公式

カルダノ=タリタリアの公式(単にカルダノの公式とも)とは、
3次方程式の解の公式です。
中学・高校で、2次方程式ax^2+bx+c=0の解の公式
x=¥frac{-b¥pm¥sqrt{b^2-4ac}}{2a}
を習いましたが、これの3次方程式バージョンです。

この公式自体については、他の書籍やWebサイトに譲りますが、
導出までの手順としては、

  1. 3次方程式ax^3+bx^2+cx+d=0を立体完成する
  2. x+¥frac{b}{3a}=Xと書き換える
  3. 更にX=u+vで置き換える
  4. 書き換えたuvの式はuv恒等式であることを利用して、u^3+v^3u^3v^3の値を求める
  5. 2次方程式の解と係数の関係を用いて、u^3v^3の値を求める
  6. uvとの関係に注意して、3つのuvの組を求める
  7. ここからXの値を算出し、最後にxの値を算出する

という流れです。

今回は、この流れを途中まで利用して、
先の問題、「接線の式を敢えてy=mxと置いて解いてみる」をやってみます。

方針

接線y=mxが3次関数y=f(x)と接しているということは、
これを連立させることで得られる3次方程式f(x)-mx=0
重解を持っているということになります。

ところで、2次以上の方程式複素数を解に持っている場合、
その共役の複素数も解であるという決まりがありますから、
3次方程式が重解を持っている、という時点で

  • その重解は実数である
  • 残りの解も実数である

ことが判明し、3つの実数解(うち2つは重解)を持っていると言うことができます。

このこと踏まえて、先ほどのカルダノ=タルタリアの公式の流れを進むと、
5.の「u^3v^3を求める」タイミングで、これが重解である必要が出てきます。
これを使って、mの値を求めます。

例題

例題として、次の問題を解いてみます。

3次関数y=x^3-3x^2-1の接線が原点を通るとき、この接線の関数を求めよ

普通に導関数を用いて答えを求めると、
(過程は省略しますが)y=-3xy=¥frac{15}{4}xが正解です。

回答

3次関数の接線として、y軸に平行なものは存在しないので、
原点を通る直線の関数は実数mを用いてy=mxと置ける。

これが3次関数y=x^3-3x^2-1に接しているので、
この2式を連立させyを消去することで得られる
3次方程式x^3-3x^2-mx-1=0が実数の重解を持つことになる。

ここで、この3次方程式を以下のように変形する:
x^3-3x^2-mx-1=0
(x-1)^3-3x-mx=0
(x-1)^3-(m+3)(x-1)-(m+3)=0

ここでx-1=Xと置くと、
X^3-(m+3)X-(m+3)=0となり、
このXの3次方程式が実数の重解を持つこととなる。

更に、X=u+vと置く。
すると、この3次方程式の解の一つの値に対して、自由uを定めることができ、
それに対応してvが定まることになる。
すなわち、X=u+vで置き換えた
(u+v)^3-(m+3)(u+v)-(m+3)=0は、uv恒等式である。

上式を変形すると、
u^3+v^3+¥{3uv-(m+3)¥}(u+v)-(m+3)=0となるが、
式中のu+vについて、

1)u+v=0の場合
u=-vであるので、この式はm=-3となる。
なお、この時X=0x=1であるから、
重解を持つとした3次方程式に代入すると、こちらもm=-3となり矛盾がない。

2)u+v¥neq0の場合
u^3+v^3+¥{3uv-(m+3)¥}(u+v)-(m+3)=0uv恒等式であるので、
u^3+v^3=m+3
uv=¥frac{m+3}{3} i.e. u^3v^3=¥frac{(m+3)^3}{27}
が成り立つ。
従って、u^3v^3は、
t2次方程式t^2-(m+3)t+¥frac{(m+3)^3}{27}=0の解である。

この解を、u^3=¥alphav^3=¥betaとすると、
u={}^{3}¥sqrt{¥alpha}, {}^{3}¥sqrt{¥alpha}¥omega, {}^{3}¥sqrt{¥alpha}¥omega^2
v={}^{3}¥sqrt{¥beta}, {}^{3}¥sqrt{¥beta}¥omega, {}^{3}¥sqrt{¥beta}¥omega^2
(¥omega=¥frac{-1+¥sqrt{3}i}{2}と定義する、これは1以外の1の3乗根2つの内の1つであり¥omega^3=1を満たす。)
となるが、uv=¥frac{m+3}{3}が成立する必要があるので、
1つのuについて、1つのvが対応することになる。
具体的には、uvは実数である必要があるので、3つのuvの組、
(u,v)=({}^{3}¥sqrt{¥alpha}, {}^{3}¥sqrt{¥beta}), ({}^{3}¥sqrt{¥alpha}¥omega, {}^{3}¥sqrt{¥beta}¥omega^2), ({}^{3}¥sqrt{¥alpha}¥omega^2, {}^{3}¥sqrt{¥beta}¥omega)が解となる。

ところで、Xの3次方程式が実数の重解を持つということは、
上記3つの組から算出した3つのu+vの値の内、2つが同一であることを意味する。
つまり、{}^{3}¥sqrt{¥alpha}¥omega={}^{3}¥sqrt{¥beta}¥omega{}^{3}¥sqrt{¥alpha}¥omega^2={}^{3}¥sqrt{¥beta}¥omega^2が成り立つ必要があるので、
u^3v^3を解とするt2次方程式t^2-(m+3)t+¥frac{(m+3)^3}{27}=0の解も
重解であることになる。

この2次方程式の判別式をDとすれば、
D=(m+3)^2-4¥cdot¥frac{(m+3)^3}{27}=0となる。
このmの3次方程式を解くと、
(m+3)^2(1-4¥cdot¥frac{m+3}{27})=0
(m+3)^2(4m-15)=0
m=-3, ¥frac{15}{4}
となる。

1), 2)より、m=-3, ¥frac{15}{4}であるから、
求める接線の関数は、
y=-3xy=¥frac{15}{4}xとなる。

感想

これは、普通に接線の公式を使った方がいいわ。
y=mxなんて置いちゃう輩の末路はこうなるということです。

補足

ちなみに、実は3次方程式にも解の公式というのは存在します。

ax^3+bx^2+cx+d=0a¥neq0)の時、
判別式D
D=-4ac^3-27a^2d^2+b^2c^2+18abcd-4b^3d
となります。
この時、
D>0:異なる3つの実数解を持つ
D=0:重解を持つ(2重解or3重解はわからない)
D<0:1つの実数解と2つの虚数解を持つ
となります。