Skip to content

比較演算子

NULLとは

  • そこに何も値が格納されていない状態を意味する、特別なもの。
  • 数値のゼロや空白文字、長さゼロの文字列とも異なる存在である。
  • 格納データが「不明」や「無意味」である状況を示す意図で用いられる。

NULLの判定

  • NULLであることを判定する
sql
IS NULL
  • NULLでないことを判定する
sql
IS NOT NULL

比較演算子の=でNULLかどうかを判定してはいけない理由-3値論理

  • =や<>などの通常の比較演算子は、もともと値と値を比較するためのもの。「NULLは値ですらない」ため、通常の値とNULLと比較すると、不明な結果であるUNKNOWNになる。

  • WHERE句による絞り込みは、条件式が真(TRUE)となる行だけが選ばれる。条件式が偽(FALSE)やUNKNOWNとなる行は処理対象にならない。

LIKE演算子

 文字列があるパターンに合致しているかのチェックをパターンマッチングという。SQLではLIKE演算子を使う。

LIKE演算子によるパターンマッチング

sql
LIKE パターン文字列

ts
// LIKE演算子 & エスケープの最小例
async function createLikeDemoTable() {
  return sql.unsafe(`
    CREATE TABLE IF NOT EXISTS LIKE_DEMO (
      name VARCHAR(100)
    );
  `)
}

async function dropLikeDemoTable() {
  return sql.unsafe(`
    DROP TABLE IF EXISTS LIKE_DEMO;
  `)
}

async function seedLikeDemo() {
  await sql`INSERT INTO LIKE_DEMO (name) VALUES ('apple')`
  await sql`INSERT INTO LIKE_DEMO (name) VALUES ('apply')`
  await sql`INSERT INTO LIKE_DEMO (name) VALUES ('application')`
  await sql`INSERT INTO LIKE_DEMO (name) VALUES ('banana')`
  await sql`INSERT INTO LIKE_DEMO (name) VALUES ('50%OFF')`
  await sql`INSERT INTO LIKE_DEMO (name) VALUES ('A_B')`
}

async function likeMain() {
  try {
    await dropLikeDemoTable()
    await createLikeDemoTable()
    await seedLikeDemo()

    // 1) % : 0文字以上の任意の文字列にマッチ
    //    → 'apple', 'apply', 'application'
    const startsWithApp = await sql`SELECT name FROM LIKE_DEMO WHERE name LIKE 'app%'`
    console.log("LIKE 'app%'         :", startsWithApp)

    // 2) _ : 任意の1文字にマッチ
    //    'app' + 任意2文字 → 'apple', 'apply'('application'は長すぎるため不一致)
    const appPlusTwo = await sql`SELECT name FROM LIKE_DEMO WHERE name LIKE 'app__'`
    console.log("LIKE 'app__'        :", appPlusTwo)

    // 3) リテラルの % を検索するにはESCAPEでエスケープ文字を指定する
    //    SQL側では LIKE '%\%%' ESCAPE '\' となるよう、JS文字列では \\ とエスケープする
    //    '\%' は「%という文字そのもの」を表す
    //    → '50%OFF'
    const literalPercent = await sql`SELECT name FROM LIKE_DEMO WHERE name LIKE '%\\%%' ESCAPE '\\'`
    console.log("LIKE '%\\%%' ESCAPE '\\':", literalPercent)

    // 4) リテラルの _ を検索するにもESCAPEを使う
    //    '\_' は「_という文字そのもの」を表す
    //    → 'A_B'
    const literalUnderscore =
      await sql`SELECT name FROM LIKE_DEMO WHERE name LIKE '%\\_%' ESCAPE '\\'`
    console.log("LIKE '%\\_%' ESCAPE '\\':", literalUnderscore)

    // 5) ESCAPE文字は'\'以外も指定できる。'$'を使うとJS側で\\しなくて済むので読みやすい
    //    '$%' は「%という文字そのもの」を表す
    //    → '50%OFF'
    const literalPercent$ = await sql`SELECT name FROM LIKE_DEMO WHERE name LIKE '%$%%' ESCAPE '$'`
    console.log("LIKE '%$%%' ESCAPE '$'  :", literalPercent$)

    // 6) 同じく$でアンダースコアをエスケープ
    //    '$_' は「_という文字そのもの」を表す
    //    → 'A_B'
    const literalUnderscore$ =
      await sql`SELECT name FROM LIKE_DEMO WHERE name LIKE '%$_%' ESCAPE '$'`
    console.log("LIKE '%$_%' ESCAPE '$'  :", literalUnderscore$)
  } catch (e) {
    console.error('❌ エラーが発生しました:', e)
  } finally {
    await sql.end()
  }
}

likeMain()

実行結果

sh
LIKE 'app%'         : Result(3) [
  { name: 'apple' },
  { name: 'apply' },
  { name: 'application' }
]
LIKE 'app__'        : Result(2) [ { name: 'apple' }, { name: 'apply' } ]
LIKE '%\%' ESCAPE '\': Result(1) [ { name: '50%OFF' } ]
LIKE '%\_%' ESCAPE '\': Result(1) [ { name: 'A_B' } ]
LIKE '%$%' ESCAPE '$'  : Result(1) [ { name: '50%OFF' } ]
LIKE '%$_%' ESCAPE '$'  : Result(1) [ { name: 'A_B' } ]

BETWEEN演算子

BETWEEN演算子は、ある範囲内に値が収まっているかを判定する。

BETWEEN演算子による範囲判定

sql
BETWEEN 値1 AND 値2

ts
// BETWEEN演算子の最小例
async function createBetweenDemoTable() {
  return sql.unsafe(`
    CREATE TABLE IF NOT EXISTS BETWEEN_DEMO (
      name VARCHAR(100),
      score INTEGER,
      event_date DATE
    );
  `)
}

async function dropBetweenDemoTable() {
  return sql.unsafe(`
    DROP TABLE IF EXISTS BETWEEN_DEMO;
  `)
}

async function seedBetweenDemo() {
  await sql`INSERT INTO BETWEEN_DEMO (name, score, event_date) VALUES ('Alice',  50, '2026-01-10')`
  await sql`INSERT INTO BETWEEN_DEMO (name, score, event_date) VALUES ('Bob',    70, '2026-02-15')`
  await sql`INSERT INTO BETWEEN_DEMO (name, score, event_date) VALUES ('Carol',  85, '2026-03-20')`
  await sql`INSERT INTO BETWEEN_DEMO (name, score, event_date) VALUES ('Dave',   95, '2026-04-25')`
  await sql`INSERT INTO BETWEEN_DEMO (name, score, event_date) VALUES ('Eve',   100, '2026-05-30')`
}

async function betweenMain() {
  try {
    await dropBetweenDemoTable()
    await createBetweenDemoTable()
    await seedBetweenDemo()

    // 1) BETWEEN a AND b : a以上かつb以下(両端を含む)
    //    score >= 70 AND score <= 90 と同じ意味
    //    → Bob(70), Carol(85)
    const inRange = await sql`SELECT name, score FROM BETWEEN_DEMO WHERE score BETWEEN 70 AND 90`
    console.log('score BETWEEN 70 AND 90        :', inRange)

    // 2) NOT BETWEEN : 範囲の外(境界値はマッチしない)
    //    → Alice(50), Dave(95), Eve(100)
    const outOfRange =
      await sql`SELECT name, score FROM BETWEEN_DEMO WHERE score NOT BETWEEN 70 AND 90`
    console.log('score NOT BETWEEN 70 AND 90    :', outOfRange)

    // 3) BETWEENは日付にも使える(こちらも両端を含む)
    //    → Bob(02-15), Carol(03-20)
    const inDateRange = await sql`
      SELECT name, event_date FROM BETWEEN_DEMO
      WHERE event_date BETWEEN '2026-02-01' AND '2026-03-31'
    `
    console.log("event_date BETWEEN '02-01' AND '03-31':", inDateRange)
  } catch (e) {
    console.error('❌ エラーが発生しました:', e)
  } finally {
    await sql.end()
  }
}

betweenMain()

実行結果

sh
score BETWEEN 70 AND 90        : Result(2) [ { name: 'Bob', score: 70 }, { name: 'Carol', score: 85 } ]
score NOT BETWEEN 70 AND 90    : Result(3) [
  { name: 'Alice', score: 50 },
  { name: 'Dave', score: 95 },
  { name: 'Eve', score: 100 }
]
event_date BETWEEN '02-01' AND '03-31': Result(2) [
  { name: 'Bob', event_date: 2026-02-15T00:00:00.000Z },
  { name: 'Carol', event_date: 2026-03-20T00:00:00.000Z }
]

IN/NOT IN演算子

IN演算子は、カッコ内に列挙した複数の値(値リスト)のいずれかにデータが合致するかを判定する演算子。=演算子では、1つの値との比較しかできないが、IN演算子を使えば、一度にたくさんの値との比較が可能。

IN演算子による複数値との比較

sql
IN (値1, 値2, 値3...)

ts
// IN / NOT IN演算子の最小例
async function createInDemoTable() {
  return sql.unsafe(`
    CREATE TABLE IF NOT EXISTS IN_DEMO (
      name VARCHAR(100),
      category VARCHAR(20)
    );
  `)
}

async function dropInDemoTable() {
  return sql.unsafe(`
    DROP TABLE IF EXISTS IN_DEMO;
  `)
}

async function seedInDemo() {
  await sql`INSERT INTO IN_DEMO (name, category) VALUES ('apple',  'fruit')`
  await sql`INSERT INTO IN_DEMO (name, category) VALUES ('banana', 'fruit')`
  await sql`INSERT INTO IN_DEMO (name, category) VALUES ('carrot', 'vegetable')`
  await sql`INSERT INTO IN_DEMO (name, category) VALUES ('daikon', 'vegetable')`
  await sql`INSERT INTO IN_DEMO (name, category) VALUES ('eel',    'fish')`
  await sql`INSERT INTO IN_DEMO (name, category) VALUES ('flour',  NULL)`
}

async function inMain() {
  try {
    await dropInDemoTable()
    await createInDemoTable()
    await seedInDemo()

    // 1) IN : 値が複数の候補のいずれかと一致すればマッチ
    //    category = 'fruit' OR category = 'vegetable' と同じ意味
    //    → apple, banana, carrot, daikon
    const included = await sql`
      SELECT name, category FROM IN_DEMO
      WHERE category IN ('fruit', 'vegetable')
    `
    console.log("category IN ('fruit', 'vegetable')        :", included)

    // 2) NOT IN : 候補のどれとも一致しないものだけマッチ
    //    NULLは比較結果がUNKNOWNになるためヒットしない点に注意
    //    → eel (flourはcategoryがNULLなのでマッチしない)
    const excluded = await sql`
      SELECT name, category FROM IN_DEMO
      WHERE category NOT IN ('fruit', 'vegetable')
    `
    console.log("category NOT IN ('fruit', 'vegetable')    :", excluded)

    // 3) IN に NULL を混ぜても NULL はマッチしない (= NULL は常にUNKNOWN)
    //    → 結果は1) と同じ: apple, banana, carrot, daikon
    const withNullInList = await sql`
      SELECT name, category FROM IN_DEMO
      WHERE category IN ('fruit', 'vegetable', NULL)
    `
    console.log("category IN ('fruit', 'vegetable', NULL)  :", withNullInList)
  } catch (e) {
    console.error('❌ エラーが発生しました:', e)
  } finally {
    await sql.end()
  }
}

inMain()

実行結果

sh
category IN ('fruit', 'vegetable')        : Result(4) [
  { name: 'apple', category: 'fruit' },
  { name: 'banana', category: 'fruit' },
  { name: 'carrot', category: 'vegetable' },
  { name: 'daikon', category: 'vegetable' }
]
category NOT IN ('fruit', 'vegetable')    : Result(1) [ { name: 'eel', category: 'fish' } ]
category IN ('fruit', 'vegetable', NULL)  : Result(4) [
  { name: 'apple', category: 'fruit' },
  { name: 'banana', category: 'fruit' },
  { name: 'carrot', category: 'vegetable' },
  { name: 'daikon', category: 'vegetable' }
]

ANY/ALL演算子

 複数の値と「大小」を比較したい場合には、ANY演算子ALL演算子を利用する。ANYやALLは、**必ずその直前に基本の比較演算子をつくて、どのような比較を行うのかを指定する。

ANY/ALL演算子による複数値との比較

  • 値リストのそれぞれと比較して、いずれか真なら真
sql
式 基本比較演算子 ANY (値1, 値2, 値3...)
  • 値リストのそれぞれと比較して、すべて真なら真
sql
式 基本比較演算子 ALL (値1, 値2, 値3...)

同じ意味になる演算子

  • NOT INと<>ALLはどの値とも一致しないことを判定する演算子
  • INと=ANYはいずれかの値と一致することを判定する演算子

ts
// ANY / ALL演算子の最小例
async function createAnyAllDemoTables() {
  await sql.unsafe(`
    CREATE TABLE IF NOT EXISTS STUDENTS (
      name VARCHAR(100),
      score INTEGER
    );
  `)
  await sql.unsafe(`
    CREATE TABLE IF NOT EXISTS TEAM_A (
      score INTEGER
    );
  `)
}

async function dropAnyAllDemoTables() {
  await sql.unsafe(`DROP TABLE IF EXISTS STUDENTS;`)
  await sql.unsafe(`DROP TABLE IF EXISTS TEAM_A;`)
}

async function seedAnyAllDemo() {
  // STUDENTS: 判定したい側
  await sql`INSERT INTO STUDENTS (name, score) VALUES ('Alice',  50)`
  await sql`INSERT INTO STUDENTS (name, score) VALUES ('Bob',    80)`
  await sql`INSERT INTO STUDENTS (name, score) VALUES ('Carol',  85)`
  await sql`INSERT INTO STUDENTS (name, score) VALUES ('Dave',   90)`
  await sql`INSERT INTO STUDENTS (name, score) VALUES ('Eve',   100)`

  // TEAM_A: 比較対象のスコア集合 (最小60, 最大90)
  await sql`INSERT INTO TEAM_A (score) VALUES (60)`
  await sql`INSERT INTO TEAM_A (score) VALUES (80)`
  await sql`INSERT INTO TEAM_A (score) VALUES (90)`
}

async function anyAllMain() {
  try {
    await dropAnyAllDemoTables()
    await createAnyAllDemoTables()
    await seedAnyAllDemo()

    // 1) > ANY : 右辺の「少なくとも1つ」を上回ればマッチ = 最小値(60)より大きい
    //    → Bob(80), Carol(85), Dave(90), Eve(100)
    const greaterThanAny = await sql`
      SELECT name, score FROM STUDENTS
      WHERE score > ANY (SELECT score FROM TEAM_A)
    `
    console.log('score > ANY (TEAM_A) :', greaterThanAny)

    // 2) > ALL : 右辺の「すべて」を上回ればマッチ = 最大値(90)より大きい
    //    → Eve(100)
    const greaterThanAll = await sql`
      SELECT name, score FROM STUDENTS
      WHERE score > ALL (SELECT score FROM TEAM_A)
    `
    console.log('score > ALL (TEAM_A) :', greaterThanAll)

    // 3) = ANY : INと等価。いずれかと一致
    //    → Bob(80), Dave(90)
    const equalsAny = await sql`
      SELECT name, score FROM STUDENTS
      WHERE score = ANY (SELECT score FROM TEAM_A)
    `
    console.log('score = ANY (TEAM_A) :', equalsAny)

    // 4) <> ALL : NOT INと等価。どれとも一致しない
    //    → Alice(50), Carol(85), Eve(100)
    const notEqualsAll = await sql`
      SELECT name, score FROM STUDENTS
      WHERE score <> ALL (SELECT score FROM TEAM_A)
    `
    console.log('score <> ALL (TEAM_A):', notEqualsAll)
  } catch (e) {
    console.error('❌ エラーが発生しました:', e)
  } finally {
    await sql.end()
  }
}

anyAllMain()

実行結果

sh
score > ANY (TEAM_A) : Result(4) [
  { name: 'Bob', score: 80 },
  { name: 'Carol', score: 85 },
  { name: 'Dave', score: 90 },
  { name: 'Eve', score: 100 }
]
score > ALL (TEAM_A) : Result(1) [ { name: 'Eve', score: 100 } ]
score = ANY (TEAM_A) : Result(2) [ { name: 'Bob', score: 80 }, { name: 'Dave', score: 90 } ]
score <> ALL (TEAM_A): Result(3) [
  { name: 'Alice', score: 50 },
  { name: 'Carol', score: 85 },
  { name: 'Eve', score: 100 }
]

論理演算子

AND演算子とOR演算子

  • 2つの条件式の両方が真の場合だけ、真となる(AかつB)。
sql
条件式1 AND 条件式2
  • 2つの条件式のどちらかが真ならば、真となる(AまたはB)。
sql
条件式1 OR 条件式2

ts
// AND / OR演算子とUPDATEの最小例
async function createTasksTable() {
  return sql.unsafe(`
    CREATE TABLE IF NOT EXISTS TASKS (
      id INTEGER,
      title VARCHAR(100),
      status VARCHAR(10),
      priority VARCHAR(10)
    );
  `)
}

async function dropTasksTable() {
  return sql.unsafe(`DROP TABLE IF EXISTS TASKS;`)
}

async function seedTasks() {
  await sql`INSERT INTO TASKS (id, title, status, priority) VALUES (1, 'A', 'todo',  'high')`
  await sql`INSERT INTO TASKS (id, title, status, priority) VALUES (2, 'B', 'todo',  'low')`
  await sql`INSERT INTO TASKS (id, title, status, priority) VALUES (3, 'C', 'doing', 'high')`
  await sql`INSERT INTO TASKS (id, title, status, priority) VALUES (4, 'D', 'doing', 'mid')`
  await sql`INSERT INTO TASKS (id, title, status, priority) VALUES (5, 'E', 'done',  'high')`
  await sql`INSERT INTO TASKS (id, title, status, priority) VALUES (6, 'F', 'done',  'low')`
}

async function andOrMain() {
  try {
    await dropTasksTable()
    await createTasksTable()
    await seedTasks()

    // 1) AND : 両方の条件がTRUEのときだけマッチ
    //    → A (status='todo' かつ priority='high')
    const andResult = await sql`
      SELECT id, title FROM TASKS
      WHERE status = 'todo' AND priority = 'high'
    `
    console.log("status='todo' AND priority='high'                :", andResult)

    // 2) OR : どちらか一方でもTRUEならマッチ
    //    → A, B, E, F (todoまたはdone)
    const orResult = await sql`
      SELECT id, title FROM TASKS
      WHERE status = 'todo' OR status = 'done'
    `
    console.log("status='todo' OR status='done'                   :", orResult)

    // 3) ANDはORより優先順位が高い。括弧がないと意図がずれる
    //    評価順: status='todo' OR (status='doing' AND priority='high')
    //    → A, B (todo全部) + C (doing かつ high) = A, B, C
    const noParens = await sql`
      SELECT id, title FROM TASKS
      WHERE status = 'todo' OR status = 'doing' AND priority = 'high'
    `
    console.log('(括弧なし) todo OR doing AND high                :', noParens)

    // 4) ORを先に評価したい場合は明示的に括弧をつける
    //    (todo または doing) かつ priority='high'
    //    → A, C
    const withParens = await sql`
      SELECT id, title FROM TASKS
      WHERE (status = 'todo' OR status = 'doing') AND priority = 'high'
    `
    console.log('(括弧あり) (todo OR doing) AND high              :', withParens)

    // 5) UPDATE + AND : 条件に合う行だけ更新
    //    D (status='doing' かつ priority='mid') を done に変更

    // 5-a) UPDATE前の全件
    const beforeUpdate = await sql`SELECT id, title, status, priority FROM TASKS ORDER BY id`
    console.log('--- UPDATE前の全件 ---')
    console.table(beforeUpdate)

    // 5-b) 更新対象を事前にSELECTで確認(WHERE句のミス防止に有効)
    const target = await sql`
      SELECT id, title, status, priority FROM TASKS
      WHERE status = 'doing' AND priority = 'mid'
    `
    console.log('--- UPDATE対象(WHERE status=doing AND priority=mid) ---')
    console.table(target)

    // 5-c) 実行
    await sql`
      UPDATE TASKS SET status = 'done'
      WHERE status = 'doing' AND priority = 'mid'
    `

    // 5-d) UPDATE後の全件
    const afterUpdate = await sql`SELECT id, title, status, priority FROM TASKS ORDER BY id`
    console.log('--- UPDATE後の全件 ---')
    console.table(afterUpdate)
  } catch (e) {
    console.error('❌ エラーが発生しました:', e)
  } finally {
    await sql.end()
  }
}

andOrMain()

実行結果

sh
status='todo' AND priority='high'                : Result(1) [ { id: 1, title: 'A' } ]
status='todo' OR status='done'                   : Result(4) [
  { id: 1, title: 'A' },
  { id: 2, title: 'B' },
  { id: 5, title: 'E' },
  { id: 6, title: 'F' }
]
(括弧なし) todo OR doing AND high                : Result(3) [
  { id: 1, title: 'A' },
  { id: 2, title: 'B' },
  { id: 3, title: 'C' }
]
(括弧あり) (todo OR doing) AND high              : Result(2) [ { id: 1, title: 'A' }, { id: 3, title: 'C' } ]
--- UPDATE前の全件 ---
┌─────────┬────┬───────┬─────────┬──────────┐
 (index) │ id │ title │ status  │ priority │
├─────────┼────┼───────┼─────────┼──────────┤
 0 1 'A' 'todo' 'high'
 1 2 'B' 'todo' 'low'
 2 3 'C' 'doing' 'high'
 3 4 'D' 'doing' 'mid'
 4 5 'E' 'done' 'high'
 5 6 'F' 'done' 'low'
└─────────┴────┴───────┴─────────┴──────────┘
--- UPDATE対象(WHERE status=doing AND priority=mid) ---
┌─────────┬────┬───────┬─────────┬──────────┐
 (index) │ id │ title │ status  │ priority │
├─────────┼────┼───────┼─────────┼──────────┤
 0 4 'D' 'doing' 'mid'
└─────────┴────┴───────┴─────────┴──────────┘
--- UPDATE後の全件 ---
┌─────────┬────┬───────┬─────────┬──────────┐
 (index) │ id │ title │ status  │ priority │
├─────────┼────┼───────┼─────────┼──────────┤
 0 1 'A' 'todo' 'high'
 1 2 'B' 'todo' 'low'
 2 3 'C' 'doing' 'high'
 3 4 'D' 'done' 'mid'
 4 5 'E' 'done' 'high'
 5 6 'F' 'done' 'low'
└─────────┴────┴───────┴─────────┴──────────┘

NOT演算子による真偽値の逆転

sql
NOT 条件式

ts
// NOT演算子の最小例
async function createMembersTable() {
  return sql.unsafe(`
    CREATE TABLE IF NOT EXISTS MEMBERS (
      id INTEGER,
      name VARCHAR(100),
      active BOOLEAN,
      age INTEGER
    );
  `)
}

async function dropMembersTable() {
  return sql.unsafe(`DROP TABLE IF EXISTS MEMBERS;`)
}

async function seedMembers() {
  await sql`INSERT INTO MEMBERS (id, name, active, age) VALUES (1, 'Alice', TRUE,  25)`
  await sql`INSERT INTO MEMBERS (id, name, active, age) VALUES (2, 'Bob',   FALSE, 30)`
  await sql`INSERT INTO MEMBERS (id, name, active, age) VALUES (3, 'Carol', TRUE,  NULL)`
  await sql`INSERT INTO MEMBERS (id, name, active, age) VALUES (4, 'Dave',  FALSE, NULL)`
  await sql`INSERT INTO MEMBERS (id, name, active, age) VALUES (5, 'Eve',   TRUE,  40)`
}

async function notMain() {
  try {
    await dropMembersTable()
    await createMembersTable()
    await seedMembers()

    // 1) NOT : 条件を反転
    //    active=FALSE の人だけ取得
    //    → Bob, Dave
    const notActive = await sql`SELECT id, name, active FROM MEMBERS WHERE NOT active`
    console.log('NOT active                         :', notActive)

    // 2) NOT (...) : 複合条件を反転。括弧で囲むのがポイント
    //    「30歳以上 かつ active」の反対 = 29歳以下 または active=FALSE
    //    ※ageがNULLの人は年齢比較がUNKNOWNになり結果的に除外される点に注意
    //    → Alice(25,T), Bob(30,F)
    const notCompound = await sql`
      SELECT id, name, active, age FROM MEMBERS
      WHERE NOT (age >= 30 AND active)
    `
    console.log('NOT (age >= 30 AND active)         :', notCompound)

    // 3) IS NOT NULL : NULLでない行だけ取得(NOT列 IS NULLの略記法)
    //    → Alice(25), Bob(30), Eve(40)
    const notNull = await sql`SELECT id, name, age FROM MEMBERS WHERE age IS NOT NULL`
    console.log('age IS NOT NULL                    :', notNull)

    // 4) NOTと3値論理: NOT UNKNOWN = UNKNOWN → WHEREでは弾かれる
    //    「ageが30以上でない」つもりで書くと、NULLの人(Carol/Dave)が出てこない
    //    → Alice(25)のみ(Bob=30は不一致, Eve=40も不一致, Carol/Dave=UNKNOWN)
    const notGte = await sql`SELECT id, name, age FROM MEMBERS WHERE NOT (age >= 30)`
    console.log('NOT (age >= 30) ※NULLは除外される :', notGte)
  } catch (e) {
    console.error('❌ エラーが発生しました:', e)
  } finally {
    await sql.end()
  }
}

notMain()

実行結果

sh
NOT active                         : Result(2) [
  { id: 2, name: 'Bob', active: false },
  { id: 4, name: 'Dave', active: false }
]
NOT (age >= 30 AND active)         : Result(3) [
  { id: 1, name: 'Alice', active: true, age: 25 },
  { id: 2, name: 'Bob', active: false, age: 30 },
  { id: 4, name: 'Dave', active: false, age: null }
]
age IS NOT NULL                    : Result(3) [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 },
  { id: 5, name: 'Eve', age: 40 }
]
NOT (age >= 30) ※NULLは除外される : Result(1) [ { id: 1, name: 'Alice', age: 25 } ]

論理演算子の優先度

 論理演算子で条件式を組み合わせるときは、演算子が評価される順にに注意を払う必要がある。複数の論理演算子が使われている場合、NOT→AND→ORの優先順位に従って処理される。

ts
async function createShoppingTable() {
  return sql.unsafe(`
    CREATE TABLE IF NOT EXISTS MINATO_SHOPPING_LIST (
      category VARCHAR(20),
      name VARCHAR(100),
      store CHAR(1),
      price INTEGER
    );
  `)
}

async function dropShoppingTable() {
  return sql.unsafe(`DROP TABLE IF EXISTS MINATO_SHOPPING_LIST;`)
}

async function seedShopping() {
  await sql`INSERT INTO MINATO_SHOPPING_LIST (category, name, store, price) VALUES ('ゲーム', 'スッキリ勇者クエスト',         'B', 7140)`
  await sql`INSERT INTO MINATO_SHOPPING_LIST (category, name, store, price) VALUES ('ゲーム', 'スッキリ勇者クエスト',         'Y', 6850)`
  await sql`INSERT INTO MINATO_SHOPPING_LIST (category, name, store, price) VALUES ('書籍',   '魔王征伐日記',                 'A', 1200)`
  await sql`INSERT INTO MINATO_SHOPPING_LIST (category, name, store, price) VALUES ('DVD',    'スッキリわかるマンモスの倒し方', 'A', 5250)`
  await sql`INSERT INTO MINATO_SHOPPING_LIST (category, name, store, price) VALUES ('DVD',    'スッキリわかるマンモスの倒し方', 'B', 7140)`
}

async function precedenceMain() {
  try {
    await dropShoppingTable()
    await createShoppingTable()
    await seedShopping()
    const all = await sql`SELECT category, name, store, price FROM MINATO_SHOPPING_LIST`
    console.table(all)
    // ANDはORより優先順位が高いので、実際には次のように評価される:
    //   store='A'  OR  (store='B' AND category='ゲーム')  OR  category='DVD'
    //
    // 各行の評価:
    //   行1 ゲーム/B   : F OR (T AND T)=T OR F → TRUE  (条件式2 AND 3で拾われる)
    //   行2 ゲーム/Y   : F OR (F AND T)=F OR F → FALSE
    //   行3 書籍/A     : T OR ...                 → TRUE  (条件式1で拾われる)
    //   行4 DVD/A      : T OR ...                 → TRUE  (条件式1で拾われる)
    //   行5 DVD/B      : F OR (T AND F)=F OR T   → TRUE  (条件式4で拾われる)
    //
    // → 行1, 行3, 行4, 行5 の4件が返る
    //   「Aで売っているゲームかDVDが欲しい」つもりが、書籍(魔王征伐日記)まで
    //   ヒットしてしまうので意図と違う結果になる
    const noParens = await sql`
      SELECT category, name, store, price FROM MINATO_SHOPPING_LIST
      WHERE store = 'A'
         OR store = 'B'
        AND category = 'ゲーム'
         OR category = 'DVD'
    `
    console.log('--- (括弧なし: AND優先のため意図とずれる) ---')
    console.table(noParens)
    // 括弧で OR を先に評価するよう明示:
    //   (store='A' OR store='B')  AND  (category='ゲーム' OR category='DVD')
    //
    // 各行の評価:
    //   行1 ゲーム/B   : (F or T)=T AND (T or F)=T → TRUE
    //   行2 ゲーム/Y   : (F or F)=F                → FALSE  (店がA/B以外)
    //   行3 書籍/A     : (T or F)=T AND (F or F)=F → FALSE  (カテゴリが対象外)
    //   行4 DVD/A      : (T or F)=T AND (F or T)=T → TRUE
    //   行5 DVD/B      : (F or T)=T AND (F or T)=T → TRUE
    //
    // → 行1, 行4, 行5 の3件が返る
    const withParens = await sql`
      SELECT category, name, store, price FROM MINATO_SHOPPING_LIST
      WHERE (store = 'A' OR store = 'B')
        AND (category = 'ゲーム' OR category = 'DVD')
    `
    console.log('--- (括弧あり: 意図どおりの結果) ---')
    console.table(withParens)
  } catch (e) {
    console.error('❌ エラーが発生しました:', e)
  } finally {
    await sql.end()
  }
}

precedenceMain()

実行結果

sh
┌─────────┬──────────┬──────────────────────────────────┬───────┬───────┐
 (index) │ category │ name                             │ store │ price │
├─────────┼──────────┼──────────────────────────────────┼───────┼───────┤
 0 'ゲーム' 'スッキリ勇者クエスト' 'B' 7140
 1 'ゲーム' 'スッキリ勇者クエスト' 'Y' 6850
 2 '書籍' '魔王征伐日記' 'A' 1200
 3 'DVD' 'スッキリわかるマンモスの倒し方' 'A' 5250
 4 'DVD' 'スッキリわかるマンモスの倒し方' 'B' 7140
└─────────┴──────────┴──────────────────────────────────┴───────┴───────┘
--- (括弧なし: AND優先のため意図とずれる) ---
┌─────────┬──────────┬──────────────────────────────────┬───────┬───────┐
 (index) │ category │ name                             │ store │ price │
├─────────┼──────────┼──────────────────────────────────┼───────┼───────┤
 0 'ゲーム' 'スッキリ勇者クエスト' 'B' 7140
 1 '書籍' '魔王征伐日記' 'A' 1200
 2 'DVD' 'スッキリわかるマンモスの倒し方' 'A' 5250
 3 'DVD' 'スッキリわかるマンモスの倒し方' 'B' 7140
└─────────┴──────────┴──────────────────────────────────┴───────┴───────┘
--- (括弧あり: 意図どおりの結果) ---
┌─────────┬──────────┬──────────────────────────────────┬───────┬───────┐
 (index) │ category │ name                             │ store │ price │
├─────────┼──────────┼──────────────────────────────────┼───────┼───────┤
 0 'ゲーム' 'スッキリ勇者クエスト' 'B' 7140
 1 'DVD' 'スッキリわかるマンモスの倒し方' 'A' 5250
 2 'DVD' 'スッキリわかるマンモスの倒し方' 'B' 7140
└─────────┴──────────┴──────────────────────────────────┴───────┴───────┘

カッコによる優先順位の引き上げ

  • 条件式をカッコでくくると、評価の優先順位が上がる。

問題例1

気象観測テーブルの定義

列名データ型備考
INTEGER1~12のいずれの値
降水量INTEGER観測データがない場合はNULL
最高気温INTEGER観測データがない場合はNULL
最低気温INTEGER観測データがない場合はNULL
湿度INTEGER観測データがない場合はNULL
sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 1 50 10 -5 40
 1 2 80 12 -3 45
 2 3 120 18 2 55
 3 4 150 22 8 60
 4 5 180 26 14 65
 5 6 250 30 18 75
 6 7 300 35 22 80
 7 8 null 38 25 85
 8 9 200 32 20 70
 9 10 100 24 12 null
 10 11 70 16 5 55
 11 12 40 11 -2 50
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 6月のデータ。
ts
await sql`SELECT * FROM WEATHER WHERE month = 6`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 6 250 30 18 75
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 6月以外のデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE month = 6`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 1 50 10 -5 40
 1 2 80 12 -3 45
 2 3 120 18 2 55
 3 4 150 22 8 60
 4 5 180 26 14 65
 5 7 300 35 22 80
 6 8 null 38 25 85
 7 9 200 32 20 70
 8 10 100 24 12 null
 9 11 70 16 5 55
 10 12 40 11 -2 50
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 降水量が100未満のデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE precipitation < 100`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 1 50 10 -5 40
 1 2 80 12 -3 45
 2 11 70 16 5 55
 3 12 40 11 -2 50
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 降水量が200より多いデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE precipitation > 200`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 6 250 30 18 75
 1 7 300 35 22 80
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 最高気温が30以上のデータ
ts
const result = await sql`SELECT * FROM WEATHER WHERE max_temp >= 30`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 6 250 30 18 75
 1 7 300 35 22 80
 2 8 null 38 25 85
 3 9 200 32 20 70
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 最低気温が0以下のデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE min_temp <= 0`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 1 50 10 -5 40
 1 2 80 12 -3 45
 2 12 40 11 -2 50
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 3月、5月、7月のデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE month IN (3, 5, 7)`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 3 120 18 2 55
 1 5 180 26 14 65
 2 7 300 35 22 80
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 3月、5月、7月以外のデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE month NOT IN (3, 5, 7)`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 1 50 10 -5 40
 1 2 80 12 -3 45
 2 4 150 22 8 60
 3 6 250 30 18 75
 4 8 null 38 25 85
 5 9 200 32 20 70
 6 10 100 24 12 null
 7 11 70 16 5 55
 8 12 40 11 -2 50
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 降水量が100以下で、湿度が50より低いデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE precipitation <= 100 AND humidity < 50`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 1 50 10 -5 40
 1 2 80 12 -3 45
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 最低気温が5未満か、最高気温が35より高いデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE min_temp < 5 OR max_temp > 35`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 1 50 10 -5 40
 1 2 80 12 -3 45
 2 3 120 18 2 55
 3 8 null 38 25 85
 4 12 40 11 -2 50
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 湿度が60〜79の範囲にあるデータ。
ts
const result = await sql`SELECT * FROM WEATHER WHERE humidity BETWEEN 60 AND 79`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 4 150 22 8 60
 1 5 180 26 14 65
 2 6 250 30 18 75
 3 9 200 32 20 70
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘
  1. 観測データのない列のある月のデータ。
ts
const result =
  await sql`SELECT * FROM WEATHER WHERE precipitation IS NULL OR max_temp IS NULL OR min_temp IS NULL OR humidity IS NULL`

実行結果

sh
┌─────────┬───────┬───────────────┬──────────┬──────────┬──────────┐
 (index) │ month │ precipitation │ max_temp │ min_temp │ humidity │
├─────────┼───────┼───────────────┼──────────┼──────────┼──────────┤
 0 8 null 38 25 85
 1 10 100 24 12 null
└─────────┴───────┴───────────────┴──────────┴──────────┴──────────┘

問題例2

列名データ型内容
コードCHAR(2)'01'~'47'の都道府県コード
地域VARCHAR(10)'関東'や'九州'など
都道府県名VARCHAR(10)'千葉'や'兵庫'など
県庁所在地VARCHAR(20)'千葉'や'神戸'など
面積INTEGER都道府県の面積(km²)
ts
async function createPrefecturesTable() {
  return sql.unsafe(`
    CREATE TABLE IF NOT EXISTS PREFECTURES (
      code CHAR(2),
      region VARCHAR(10),
      prefecture_name VARCHAR(10),
      prefectural_capital VARCHAR(20),
      area INTEGER
    );
  `)
}

async function seedPrefectures() {
  await sql`INSERT INTO PREFECTURES VALUES ('01', '北海道', '北海道',   '札幌',     83424)`
  await sql`INSERT INTO PREFECTURES VALUES ('02', '東北',   '青森',     '青森',      9646)`
  await sql`INSERT INTO PREFECTURES VALUES ('03', '東北',   '岩手',     '盛岡',     15275)`
  await sql`INSERT INTO PREFECTURES VALUES ('04', '東北',   '宮城',     '仙台',      7282)`
  await sql`INSERT INTO PREFECTURES VALUES ('05', '東北',   '秋田',     '秋田',     11638)`
  await sql`INSERT INTO PREFECTURES VALUES ('06', '東北',   '山形',     '山形',      9323)`
  await sql`INSERT INTO PREFECTURES VALUES ('07', '東北',   '福島',     '福島',     13784)`
  await sql`INSERT INTO PREFECTURES VALUES ('08', '関東',   '茨城',     '水戸',      6097)`
  await sql`INSERT INTO PREFECTURES VALUES ('09', '関東',   '栃木',     '宇都宮',    6408)`
  await sql`INSERT INTO PREFECTURES VALUES ('10', '関東',   '群馬',     '前橋',      6362)`
  await sql`INSERT INTO PREFECTURES VALUES ('11', '関東',   '埼玉',     'さいたま',  3798)`
  await sql`INSERT INTO PREFECTURES VALUES ('12', '関東',   '千葉',     '千葉',      5158)`
  await sql`INSERT INTO PREFECTURES VALUES ('13', '関東',   '東京',     '東京',      2194)`
  await sql`INSERT INTO PREFECTURES VALUES ('14', '関東',   '神奈川',   '横浜',      2416)`
  await sql`INSERT INTO PREFECTURES VALUES ('15', '中部',   '新潟',     '新潟',     12584)`
  await sql`INSERT INTO PREFECTURES VALUES ('16', '中部',   '富山',     '富山',      4248)`
  await sql`INSERT INTO PREFECTURES VALUES ('17', '中部',   '石川',     '金沢',      4186)`
  await sql`INSERT INTO PREFECTURES VALUES ('18', '中部',   '福井',     '福井',      4191)`
  await sql`INSERT INTO PREFECTURES VALUES ('19', '中部',   '山梨',     '甲府',      4465)`
  await sql`INSERT INTO PREFECTURES VALUES ('20', '中部',   '長野',     '長野',     13562)`
  await sql`INSERT INTO PREFECTURES VALUES ('21', '中部',   '岐阜',     '岐阜',     10621)`
  await sql`INSERT INTO PREFECTURES VALUES ('22', '中部',   '静岡',     '静岡',      7777)`
  await sql`INSERT INTO PREFECTURES VALUES ('23', '中部',   '愛知',     '名古屋',    5173)`
  await sql`INSERT INTO PREFECTURES VALUES ('24', '近畿',   '三重',     '津',        5774)`
  await sql`INSERT INTO PREFECTURES VALUES ('25', '近畿',   '滋賀',     '大津',      4017)`
  await sql`INSERT INTO PREFECTURES VALUES ('26', '近畿',   '京都',     '京都',      4612)`
  await sql`INSERT INTO PREFECTURES VALUES ('27', '近畿',   '大阪',     '大阪',      1905)`
  await sql`INSERT INTO PREFECTURES VALUES ('28', '近畿',   '兵庫',     '神戸',      8401)`
  await sql`INSERT INTO PREFECTURES VALUES ('29', '近畿',   '奈良',     '奈良',      3691)`
  await sql`INSERT INTO PREFECTURES VALUES ('30', '近畿',   '和歌山',   '和歌山',    4725)`
  await sql`INSERT INTO PREFECTURES VALUES ('31', '中国',   '鳥取',     '鳥取',      3507)`
  await sql`INSERT INTO PREFECTURES VALUES ('32', '中国',   '島根',     '松江',      6708)`
  await sql`INSERT INTO PREFECTURES VALUES ('33', '中国',   '岡山',     '岡山',      7114)`
  await sql`INSERT INTO PREFECTURES VALUES ('34', '中国',   '広島',     '広島',      8479)`
  await sql`INSERT INTO PREFECTURES VALUES ('35', '中国',   '山口',     '山口',      6113)`
  await sql`INSERT INTO PREFECTURES VALUES ('36', '四国',   '徳島',     '徳島',      4147)`
  await sql`INSERT INTO PREFECTURES VALUES ('37', '四国',   '香川',     '高松',      1876)`
  await sql`INSERT INTO PREFECTURES VALUES ('38', '四国',   '愛媛',     '松山',      5676)`
  await sql`INSERT INTO PREFECTURES VALUES ('39', '四国',   '高知',     '高知',      7103)`
  await sql`INSERT INTO PREFECTURES VALUES ('40', '九州',   '福岡',     '福岡',      4987)`
  await sql`INSERT INTO PREFECTURES VALUES ('41', '九州',   '佐賀',     '佐賀',      2440)`
  await sql`INSERT INTO PREFECTURES VALUES ('42', '九州',   '長崎',     '長崎',      4131)`
  await sql`INSERT INTO PREFECTURES VALUES ('43', '九州',   '熊本',     '熊本',      7409)`
  await sql`INSERT INTO PREFECTURES VALUES ('44', '九州',   '大分',     '大分',      6341)`
  await sql`INSERT INTO PREFECTURES VALUES ('45', '九州',   '宮崎',     '宮崎',      7735)`
  await sql`INSERT INTO PREFECTURES VALUES ('46', '九州',   '鹿児島',   '鹿児島',    9186)`
  await sql`INSERT INTO PREFECTURES VALUES ('47', '九州',   '沖縄',     '那覇',      2281)`
}
sh
┌─────────┬──────┬──────────┬─────────────────┬─────────────────────┬───────┐
 (index) │ code │ region   │ prefecture_name │ prefectural_capital │ area  │
├─────────┼──────┼──────────┼─────────────────┼─────────────────────┼───────┤
 0 '01' '北海道' '北海道' '札幌' 83424
 1 '02' '東北' '青森' '青森' 9646
 2 '03' '東北' '岩手' '盛岡' 15275
 3 '04' '東北' '宮城' '仙台' 7282
 4 '05' '東北' '秋田' '秋田' 11638
 5 '06' '東北' '山形' '山形' 9323
 6 '07' '東北' '福島' '福島' 13784
 7 '08' '関東' '茨城' '水戸' 6097
 8 '09' '関東' '栃木' '宇都宮' 6408
 9 '10' '関東' '群馬' '前橋' 6362
 10 '11' '関東' '埼玉' 'さいたま' 3798
 11 '12' '関東' '千葉' '千葉' 5158
 12 '13' '関東' '東京' '東京' 2194
 13 '14' '関東' '神奈川' '横浜' 2416
 14 '15' '中部' '新潟' '新潟' 12584
 15 '16' '中部' '富山' '富山' 4248
 16 '17' '中部' '石川' '金沢' 4186
 17 '18' '中部' '福井' '福井' 4191
 18 '19' '中部' '山梨' '甲府' 4465
 19 '20' '中部' '長野' '長野' 13562
 20 '21' '中部' '岐阜' '岐阜' 10621
 21 '22' '中部' '静岡' '静岡' 7777
 22 '23' '中部' '愛知' '名古屋' 5173
 23 '24' '近畿' '三重' '津' 5774
 24 '25' '近畿' '滋賀' '大津' 4017
 25 '26' '近畿' '京都' '京都' 4612
 26 '27' '近畿' '大阪' '大阪' 1905
 27 '28' '近畿' '兵庫' '神戸' 8401
 28 '29' '近畿' '奈良' '奈良' 3691
 29 '30' '近畿' '和歌山' '和歌山' 4725
 30 '31' '中国' '鳥取' '鳥取' 3507
 31 '32' '中国' '島根' '松江' 6708
 32 '33' '中国' '岡山' '岡山' 7114
 33 '34' '中国' '広島' '広島' 8479
 34 '35' '中国' '山口' '山口' 6113
 35 '36' '四国' '徳島' '徳島' 4147
 36 '37' '四国' '香川' '高松' 1876
 37 '38' '四国' '愛媛' '松山' 5676
 38 '39' '四国' '高知' '高知' 7103
 39 '40' '九州' '福岡' '福岡' 4987
 40 '41' '九州' '佐賀' '佐賀' 2440
 41 '42' '九州' '長崎' '長崎' 4131
 42 '43' '九州' '熊本' '熊本' 7409
 43 '44' '九州' '大分' '大分' 6341
 44 '45' '九州' '宮崎' '宮崎' 7735
 45 '46' '九州' '鹿児島' '鹿児島' 9186
 46 '47' '九州' '沖縄' '那覇' 2281
└─────────┴──────┴──────────┴─────────────────┴─────────────────────┴───────┘
  1. 都道府県名が「川」で終わる都道府県名。
ts
// LIKE '%川' : 末尾が「川」(前は0文字以上の任意の文字列)
// → 神奈川, 石川, 香川 の3件
const result =
  await sql`SELECT code, prefecture_name FROM PREFECTURES WHERE prefecture_name LIKE '%川'`

実行結果

sh
┌─────────┬──────┬─────────────────┐
 (index) │ code │ prefecture_name │
├─────────┼──────┼─────────────────┤
 0 '14' '神奈川'
 1 '17' '石川'
 2 '37' '香川'
└─────────┴──────┴─────────────────┘
  1. 都道府県名に「島」が含まれる都道府県名。
ts
// LIKE '%島%' : 「島」の前後に0文字以上の任意の文字列。位置は問わない
// → 福島(末尾), 島根(先頭), 広島(末尾), 徳島(末尾), 鹿児島(末尾) の5件
const result = await sql`await sql`SELECT code, prefecture_name FROM PREFECTURES WHERE prefecture_name LIKE '%島%'``

実行結果

sh
┌─────────┬──────┬─────────────────┐
 (index) │ code │ prefecture_name │
├─────────┼──────┼─────────────────┤
 0 '07' '福島'
 1 '32' '島根'
 2 '34' '広島'
 3 '36' '徳島'
 4 '46' '鹿児島'
└─────────┴──────┴─────────────────┘
  1. 都道府県名が「愛」で始まる都道府県名。
ts
// LIKE '愛%' : 先頭が「愛」(後ろは0文字以上の任意の文字列)
// → 愛知, 愛媛 の2件
const result =
  await sql`SELECT code, prefecture_name FROM PREFECTURES WHERE prefecture_name LIKE '愛%'`

実行結果

sh
┌─────────┬──────┬─────────────────┐
 (index) │ code │ prefecture_name │
├─────────┼──────┼─────────────────┤
 0 '23' '愛知'
 1 '38' '愛媛'
└─────────┴──────┴─────────────────┘
  1. 都道府県名と県庁所在地が一致するデータ。
ts
const result =
  await sql`SELECT code, prefecture_name, prefectural_capital FROM PREFECTURES WHERE prefecture_name = prefectural_capital`

実行結果

sh
┌─────────┬──────┬─────────────────┬─────────────────────┐
 (index) │ code │ prefecture_name │ prefectural_capital │
├─────────┼──────┼─────────────────┼─────────────────────┤
 0 '02' '青森' '青森'
 1 '05' '秋田' '秋田'
 2 '06' '山形' '山形'
 3 '07' '福島' '福島'
 4 '12' '千葉' '千葉'
 5 '13' '東京' '東京'
 6 '15' '新潟' '新潟'
 7 '16' '富山' '富山'
 8 '18' '福井' '福井'
 9 '20' '長野' '長野'
 10 '21' '岐阜' '岐阜'
 11 '22' '静岡' '静岡'
 12 '26' '京都' '京都'
 13 '27' '大阪' '大阪'
 14 '29' '奈良' '奈良'
 15 '30' '和歌山' '和歌山'
 16 '31' '鳥取' '鳥取'
 17 '33' '岡山' '岡山'
 18 '34' '広島' '広島'
 19 '35' '山口' '山口'
 20 '36' '徳島' '徳島'
 21 '39' '高知' '高知'
 22 '40' '福岡' '福岡'
 23 '41' '佐賀' '佐賀'
 24 '42' '長崎' '長崎'
 25 '43' '熊本' '熊本'
 26 '44' '大分' '大分'
 27 '45' '宮崎' '宮崎'
 28 '46' '鹿児島' '鹿児島'
└─────────┴──────┴─────────────────┴─────────────────────┘
  1. 都道府県名と県庁所在地が一致しないデータ。
ts
const result =
  await sql`SELECT code, prefecture_name, prefectural_capital FROM PREFECTURES WHERE prefecture_name <> prefectural_capital`

実行結果

sh
┌─────────┬──────┬─────────────────┬─────────────────────┐
 (index) │ code │ prefecture_name │ prefectural_capital │
├─────────┼──────┼─────────────────┼─────────────────────┤
 0 '01' '北海道' '札幌'
 1 '03' '岩手' '盛岡'
 2 '04' '宮城' '仙台'
 3 '08' '茨城' '水戸'
 4 '09' '栃木' '宇都宮'
 5 '10' '群馬' '前橋'
 6 '11' '埼玉' 'さいたま'
 7 '14' '神奈川' '横浜'
 8 '17' '石川' '金沢'
 9 '19' '山梨' '甲府'
 10 '23' '愛知' '名古屋'
 11 '24' '三重' '津'
 12 '25' '滋賀' '大津'
 13 '28' '兵庫' '神戸'
 14 '32' '島根' '松江'
 15 '37' '香川' '高松'
 16 '38' '愛媛' '松山'
 17 '47' '沖縄' '那覇'
└─────────┴──────┴─────────────────┴─────────────────────┘

問題例3

列名データ型備考
学籍番号CHAR(4)学生の学籍番号
学生名VARCHAR(20)学生の名前
法学INTEGER法学の点数
経済学INTEGER経済学の点数
哲学INTEGER哲学の点数
情報理論INTEGER情報論理の点数
外国語INTEGER外国語の点数
総合成績CHAR(1)総合成績
ts
// 列名(原文)   -> SQL列名
//   学籍番号   -> student_no          CHAR(4)
//   学生名     -> student_name        VARCHAR(20)
//   法学       -> law                 INTEGER
//   経済学     -> economics           INTEGER
//   哲学       -> philosophy          INTEGER
//   情報理論   -> information_theory  INTEGER
//   外国語     -> foreign_language    INTEGER
//   総合成績   -> total_grade         CHAR(1)
async function createGradesTable() {
  return sql.unsafe(`
    CREATE TABLE IF NOT EXISTS GRADES (
      student_no CHAR(4),
      student_name VARCHAR(20),
      law INTEGER,
      economics INTEGER,
      philosophy INTEGER,
      information_theory INTEGER,
      foreign_language INTEGER,
      total_grade CHAR(1)
    );
  `)
}
sh
┌─────────┬────────────┬──────────────┬─────┬───────────┬────────────┬────────────────────┬──────────────────┬─────────────┐
 (index) │ student_no │ student_name │ law │ economics │ philosophy │ information_theory │ foreign_language │ total_grade │
├─────────┼────────────┼──────────────┼─────┼───────────┼────────────┼────────────────────┼──────────────────┼─────────────┤
 0 'A002' '豊臣 秀吉' 64 69 70 0 59 null
 1 'E003' '徳川 家康' 80 83 85 90 79 null
 2 'S001' '織田 信長' 77 55 80 75 93 null
└─────────┴────────────┴──────────────┴─────┴───────────┴────────────┴────────────────────┴──────────────────┴─────────────┘
  1. 登録されている全データを取得し、テーブルの内容を確認する。
ts
const result = await sql`SELECT * FROM GRADES`
  1. 次の表にある学生のデータを追加する。
学籍番号学生名法学経済学哲学情報理論外国語総合成績
S001織田 信長7755807593(NULL)
A002豊臣 秀吉646970059(NULL)
E003徳川 家康8083859079(NULL)
ts
await sql`INSERT INTO GRADES (student_no, student_name, law, economics, philosophy, information_theory, foreign_language, total_grade) VALUES ('S001', '織田 信長', 77, 55, 80, 75, 93, NULL)`
await sql`INSERT INTO GRADES (student_no, student_name, law, economics, philosophy, information_theory, foreign_language, total_grade) VALUES ('A002', '豊臣 秀吉', 64, 69, 70, 0, 59, NULL)`
await sql`INSERT INTO GRADES (student_no, student_name, law, economics, philosophy, information_theory, foreign_language, total_grade) VALUES ('E003', '徳川 家康', 80, 83, 85, 90, 79, NULL)`
  1. 学籍番号S001の学生の法学を85、哲学を67に更新する。
ts
await sql`UPDATE GRADES SET law = 85, philosophy = 67 WHERE student_no = 'S001'`
  1. 学籍番号A002の学生と学籍番号E003の学生の外国語を81に修正する。
ts
await sql`UPDATE GRADES SET foreign_language = 81 WHERE student_no IN ('A002', 'E003')`
  1. 次のルールで総合成績を更新する(4つのルールごとにSQL文を作成する)。なお、ルールは(1)から順に適用されるものとする。

(1)全科目が80以上の学生は「A」とする。

ts
await sql`
  UPDATE GRADES
     SET total_grade = 'A'
   WHERE total_grade IS NULL
     AND law                >= 80
     AND economics          >= 80
     AND philosophy         >= 80
     AND information_theory >= 80
     AND foreign_language   >= 80
`

(2)法学と外国語のどちらかが80以上、かつ経済学と哲学のどちらかが80以上の学生は「B」とする。

ts
await sql`
  UPDATE GRADES
  SET total_grade = 'B'
  WHERE total_grade IS NULL
  AND (law       >= 80 OR foreign_language >= 80)
  AND (economics >= 80 OR philosophy       >= 80)
  AND total_grade IS NULL
`

(3)全科目が50未満の学生は「D」とする。

ts
await sql`
  UPDATE GRADES
  SET total_grade = 'D'
  WHERE total_grade IS NULL
  AND law                < 50
  AND economics          < 50
  AND philosophy         < 50
  AND information_theory < 50
  AND foreign_language   < 50
  AND total_grade IS NULL
`

(4)それ以外の学生を「C」とする。

ts
await sql`
  UPDATE GRADES
  SET total_grade = 'C'
  WHERE total_grade IS NULL
`

6.いずれかの科目に0がある学生を、成績表テーブルから削除する。

ts
await sql`
  DELETE FROM GRADES
  WHERE law             = 0
  OR economics          = 0
  OR philosophy         = 0
  OR information_theory = 0
  OR foreign_language   = 0