Skip to content

feat: 데이터베이스 sql 파일 분리 및 개선 #290

Merged
xunxxoie merged 15 commits intodevfrom
feat/database-sql
Jan 12, 2026
Merged

feat: 데이터베이스 sql 파일 분리 및 개선 #290
xunxxoie merged 15 commits intodevfrom
feat/database-sql

Conversation

@sukangpunch
Copy link
Contributor

@sukangpunch sukangpunch commented Jan 10, 2026

📋 이슈 번호

  • close #이슈 번호

🛠 구현 사항

  1. 데이터베이스 문제 파트(problem4 ~ 5. sql 파일을 unit 별로 분리) 추가

  2. 문제 정답 셔플 및 주관식 답변 추가(유연한 정답 처리)

🤔 추가 고려 사항

Summary by CodeRabbit

  • New Features
    • 데이터베이스 관련 14개 단원(데이터모델링·식별관계·관계형모델·키·외래키·DDL·DML·서브쿼리·조인·페이징·뷰·정규화·트랜잭션·인덱스) 교육 콘텐츠 추가
    • 각 단원별 객관식·주관식 문제, 선택지, 정답 및 해설을 포함한 완전한 문제 은행 제공
    • 특정 문제에 대한 정답 항목(예: EXISTS) 추가로 콘텐츠 보강

✏️ Tip: You can customize this high-level summary in your review settings.

@sukangpunch sukangpunch self-assigned this Jan 10, 2026
@sukangpunch sukangpunch added 🌼 Feat to add new Feature! 🧹Refactor to refactor code or update logic labels Jan 10, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2026

Walkthrough

이 PR은 데이터베이스 기초 과정(Unit 01–14)에 대한 다수의 SQL 시드 파일을 추가·수정하여 lesson, problem, option, answer 테이블에 대량의 교육용 문제·선택지·정답 데이터를 삽입합니다. 변경은 순차적인 INSERT 문 위주입니다. (≤50단어)

Changes

응집군 / 파일(s) 변경 요약
데이터 모델링 (Unit 01)
src/main/resources/sql/problem/database/unit01_데이터모델링.sql
Lesson(IDs 122–124)과 다수의 problem(845–865), option, answer INSERT 추가. 개념/논리/물리 모델링 관련 문항 및 정답/설명 포함.
식별/비식별 관계 (Unit 02)
src/main/resources/sql/problem/database/unit02_식별관계와비식별관계.sql
Lesson(125–127)과 관련 problem/option/answer INSERT 추가. 식별관계 vs 비식별관계 관련 문항 포함.
관계형 모델 (Unit 03)
src/main/resources/sql/problem/database/unit03_관계형모델.sql
Lesson(128–130) 및 다수의 problem/option/answer INSERT. 무결성 제약 등 관계형 개념 포함.
키 (Unit 04)
src/main/resources/sql/problem/database/unit04_키.sql
Lesson(131–133)과 관련 problem/option/answer INSERT. 후보키, 기본키 등 키 관련 문제 추가.
외래키와 제약조건 (Unit 05)
src/main/resources/sql/problem/database/unit05_외래키와제약조건.sql
Lesson(134–136) 및 problem/option/answer INSERT. 외래키·제약조건 및 참조 동작 사례 포함.
DDL (Unit 06)
src/main/resources/sql/problem/database/unit06_DDL.sql
Lesson(…) 3개 및 관련 문제/옵션/정답 INSERT. CREATE/DROP/ALTER/TRUNCATE 관련 설명 포함.
DML (Unit 07)
src/main/resources/sql/problem/database/unit07_DML.sql
Lesson(140–142) 및 problems(971–991), option, answer INSERT. DML 관련 문항 추가.
서브쿼리 (Unit 08)
src/main/resources/sql/problem/database/unit08_서브쿼리.sql
Lesson(143–145) 및 problems(992–1012)과 option/answer INSERT. 서브쿼리 유형·상관서브쿼리 포함.
조인 (Unit 09)
src/main/resources/sql/problem/database/unit09_조인.sql
조인 관련 3개 lesson 및 다수 문제/옵션/정답 INSERT. 조인 유형·다중테이블 조인 포함.
페이징 (Unit 10)
src/main/resources/sql/problem/database/unit10_페이징.sql
Lesson(149–151) 및 problems(1034–1054) INSERT. OFFSET/커서 페이징 등 포함.
뷰 (Unit 11)
src/main/resources/sql/problem/database/unit11_뷰.sql
Lesson(152–154) 및 problems(1055–1075), option/answer INSERT. VIEW 제약사항 등 포함.
정규화 (Unit 12)
src/main/resources/sql/problem/database/unit12_정규화.sql
Lesson(155–157) 및 problems(1076–1096), option/answer INSERT. 정규형·정규화 과정 관련 문항 포함.
트랜잭션 (Unit 13)
src/main/resources/sql/problem/database/unit13_트랜잭션.sql
Lesson(158–160) 및 다수 문제/옵션/정답 INSERT. 트랜잭션 제어·격리 수준 관련 문항 포함.
인덱스 (Unit 14)
src/main/resources/sql/problem/database/unit14_인덱스.sql
Lesson(161–163) 및 problems(1118–1138), option/answer INSERT. 인덱스 종류·최적화 관련 문항 포함.
기존 정답 테이블 소량 수정
src/main/resources/sql/problem5.sql
answer 테이블에 problem_id=1010에 대한 단일 INSERT 추가 (content: 'EXISTS').

Sequence Diagram(s)

(생성 조건에 부합하지 않아 생략)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20분

Possibly related PRs

Suggested reviewers

  • xunxxoie

시 🐰

새 레슨 밭에 씨앗을 뿌렸어요,
문제와 선택지가 춤추는 들판에,
학생들 길잡이 될 답안도 놓았지요 🥕
지식의 당근 더 깊이 뿌리내리길,
토실토실 성장하길 바라요 🌱✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 변경사항의 주요 내용을 명확하게 반영하고 있습니다. 문제 데이터베이스 SQL 파일들을 단위별로 분리하고 개선한 것이 제목에 잘 표현되어 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 10, 2026

Unit Test Results

0 tests   0 ✔️  0s ⏱️
0 suites  0 💤
0 files    0

Results for commit 05b69ef.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🤖 Fix all issues with AI agents
In @src/main/resources/sql/problem/database/unit04_키.sql:
- Around line 63-66: In the INSERT tuples (IDs 918–921) the question/instruction
strings contain unnecessary leading/trailing spaces and extra spaces before
punctuation (e.g., " ? "); update each string literal (for example the texts
starting with " 다음 중 복합키(Composite Key) 에 대한 설명으로 올바른 것은 ? ", " 빈칸에 들어갈 용어를
작성하시오.", etc.) to remove leading/trailing spaces and remove the space before the
question mark so they follow correct Korean punctuation and have no extraneous
surrounding whitespace.

In @src/main/resources/sql/problem/database/unit05_외래키와제약조건.sql:
- Around line 52-54: The INSERT INTO statement for answer entries includes a
leading space in the explanation string for the tuple with id 268; locate the
VALUES tuple referencing id 268 in the INSERT INTO answer statement and remove
the extra leading whitespace before "외래키 제약은..." so the explanation string
begins immediately with the first character (i.e., change "' 외래키 제약은..." to
"'외래키 제약은...'" in the VALUES list).
- Around line 110-118: The INSERT INTO problem VALUES list contains an unescaped
newline inside the content value for id 946 (the 'CREATE TABLE products (id INT
PRIMARY KEY, stock INT DEFAULT 0);' string), which breaks the SQL statement; fix
the row in the VALUES list used by INSERT INTO problem by removing or escaping
the newline so the entire CREATE TABLE statement is a single-line string (e.g.,
replace the line break with a space or an escaped newline) for the entry with id
946.
- Around line 60-68: The INSERT tuple for id 936 contains a newline inside the
content string (the fragment starting "참조 동작 옵션은 부모 테이블의 데이터가 삭제되거나 수정될 때 자식
테이블의 처리 방식을 지정한다" split across lines), which breaks the SQL; fix it by replacing
the embedded newline with a single space so the content value is one continuous
quoted string in the VALUES list for the INSERT INTO problem statement (the
tuple with id 936 in the VALUES (...) list).

In @src/main/resources/sql/problem/database/unit06_DDL.sql:
- Around line 59-66: The INSERT INTO problem VALUES block contains string
literals with an embedded newline in the 'content' for id 957/958 (e.g., 'ALTER
TABLE students ADD phone VARCHAR(20);\n') and a stray comma-only line after the
record for id 959; fix by normalizing each VALUES tuple to keep SQL string
literals on a single line (remove the actual newline characters inside the
quoted 'content' values) and move/remove the standalone comma so that each tuple
is separated by a trailing comma on the same line, referencing the INSERT INTO
problem (...) VALUES block and the tuples for ids 957, 958, 959, 960, 961 to
ensure there are no literal newlines inside quotes and no comma-only lines.

In @src/main/resources/sql/problem/database/unit07_DML.sql:
- Line 15: Add the missing SUBJECTIVE answer row for problem id 975 into the
answer insertion block (the answers for Lesson 1 around the existing 51-102
INSERTs) so that the `answer` table contains the correct answer text and
explanation for question 975; then renumber subsequent answer IDs as suggested
(shift the following Lesson 2 IDs: 280→281, 281→282, 282→283 and Lesson 3 IDs:
283→284, 284→285) to avoid ID collisions and keep sequences consistent with the
existing problem IDs referenced by the `problem` table.

In @src/main/resources/sql/problem/database/unit08_서브쿼리.sql:
- Around line 53-101: Update the instructional text for the affected problems
(e.g., the instruction/content for problem ids like 999, 1001, 1002, 1004, 1005
and any subjective prompts such as 1000/1003) to avoid definitive claims that
"subqueries are always executed first" or that "execution order is defined by
standard SQL"; instead rephrase to separate logical evaluation from physical
execution (e.g., state that logically a subquery may be considered as producing
a value/set for comparison but the DB optimizer can transform or reorder queries
— using semi-joins, decorrelation, etc.), and replace absolutist wording with
qualifiers like "logically" or "in terms of evaluation" and note that actual
runtime execution depends on the optimizer and execution plan.
- Around line 103-150: Problem 1007 (answer id 289) has a contradictory
explanation: it claims 'AS' is the required alias keyword then also says "AS는 생략
가능하다", and it fails to note DBMS dialect differences; update the answer
explanation for id 289 to consistently state that an alias is required for an
inline view and either (A) remove "AS는 생략 가능하다" and instead note "별칭(alias) 지정이
필요하며 AS를 사용할 수 있음; 일부 DBMS(예: Oracle)는 FROM 절에서 AS 사용을 허용하지 않음" or (B) if this
course targets specific DBMS, state which dialects are supported and explicitly
say whether omitting AS is accepted for those dialects; ensure the explanation
references problem 1007 and the keyword 'AS' so graders/learners aren’t
confused.

In @src/main/resources/sql/problem/database/unit12_정규화.sql:
- Around line 1-150: The script lacks transaction boundaries so partial INSERTs
can leave inconsistent data; wrap each lesson block (the INSERT INTO lesson +
its related INSERT INTO problem, INSERT INTO option, INSERT INTO answer groups)
in a transaction by adding a BEGIN (or START TRANSACTION) before the lesson's
INSERTs and a COMMIT after its last related INSERT, and add a ROLLBACK on error
if your DB client supports conditional/error handling; specifically target the
blocks around lesson id 155, 156, and 157 (the INSERTs that include problems
1076–1082, 1083–1089, 1090–1096 and their corresponding option ids 3063–3102 and
answer ids 309–314) so that all lesson/problem/option/answer inserts are atomic.
- Around line 4-5: The INSERT into table "lesson" using a hard-coded primary key
(VALUES with id 155) can cause duplicate-key errors if rerun; change the
statement that inserts into lesson (the INSERT INTO lesson (id, title, unit_id)
VALUES (155, '정규화 기초', 53) line) to be idempotent — for PostgreSQL add an ON
CONFLICT (id) DO NOTHING clause, or use ON CONFLICT ... DO UPDATE to upsert, or
switch to DELETE FROM lesson WHERE id = 155 before inserting for dev-only reset
scenarios; pick the approach appropriate for the environment and apply it to
this INSERT.

In @src/main/resources/sql/problem/database/unit13_트랜잭션.sql:
- Around line 1-150: The script hardcodes specific ID ranges (problem IDs
1097–1117, option IDs 3123–3182, answer IDs 315–320) and assumes unit_id=54
exists and that execution order with unit14 relies on these IDs; make the script
idempotent and atomic by first ensuring unit 54 exists (INSERT ... ON
CONFLICT/IF NOT EXISTS or a SELECT check), avoid fixed IDs by using sequences or
by computing next IDs (or use INSERT ... RETURNING to link generated IDs), or
change INSERTs to upsert semantics (INSERT ... ON CONFLICT DO NOTHING/UPDATE or
MERGE) for lesson/problem/option/answer, and wrap the whole set of inserts in a
transaction so they apply atomically to prevent partial application and ID
collisions with other unit scripts.

In @src/main/resources/sql/problem/database/unit14_인덱스.sql:
- Around line 107-115: Problem entry id 1135 stores a literal "\n" in its VALUES
string instead of a real newline; update the VALUES tuple for problem id 1135 to
use an actual line break inside the 'content' string (or use your DB's
newline-escape syntax, e.g., the DB-specific E'...\n...' form) so the content
renders with a real newline, and apply the same fix to the other affected INSERT
rows that contain literal "\n".
- Around line 16-47: The SQL uses the unquoted reserved word table name "option"
causing MySQL syntax errors; either quote the table name or rename it
consistently across schema and data files. Update the DDL (V1__init_tables.sql)
and all DML (e.g., the INSERT INTO option (...) VALUES ... statements in
unit14_인덱스.sql) plus any code/queries that reference the table to use a safe
identifier (e.g., `problem_option` everywhere) or consistently quote it for
MySQL (`` `option` ``) so MySQL, PostgreSQL, H2 all use the same identifier.
- Around line 3-15: The seed SQL uses hardcoded PKs (e.g., INSERT INTO lesson
with id 161 and INSERT INTO problem with ids 1118–1124) which breaks idempotency
and causes PK conflicts on reapply; modify the inserts to be idempotent by
either deleting existing rows before insert (DELETE FROM lesson WHERE id = 161;
DELETE FROM problem WHERE id IN (1118,...)) or convert to upsert semantics
(INSERT ... ON CONFLICT (id) DO UPDATE ...) for the lesson and problem inserts,
and add a short comment block documenting the per-unit ID ranges policy to avoid
future collisions.
🧹 Nitpick comments (12)
src/main/resources/sql/problem/database/unit13_트랜잭션.sql (2)

1-150: 스크립트 재실행 가능성(Idempotence) 부재

이 SQL 파일을 두 번 실행하면 PRIMARY KEY 제약 조건 위반으로 실패합니다. 데이터 마이그레이션이나 환경 설정 시 스크립트를 안전하게 재실행할 수 있도록 구성해야 합니다.

관례상 배포 스크립트는 멱등성(idempotent) 또는 재실행 가능(re-runnable)하도록 구성하는 것이 좋습니다.

♻️ 제안: INSERT ... ON CONFLICT 또는 조건부 INSERT 추가
-- 방법 1: ON CONFLICT 절 사용 (PostgreSQL, SQLite)
INSERT INTO lesson (id, title, unit_id)
VALUES (158, '트랜잭션 기초', 54)
ON CONFLICT(id) DO NOTHING;

-- 방법 2: 조건부 INSERT (MySQL, MSSQL)
IF NOT EXISTS (SELECT 1 FROM lesson WHERE id = 158)
BEGIN
  INSERT INTO lesson (id, title, unit_id)
  VALUES (158, '트랜잭션 기초', 54);
END

또는 더 간단하게 DELETE 후 INSERT:

DELETE FROM answer WHERE id IN (315, 316, 317, 318, 319, 320);
DELETE FROM option WHERE id BETWEEN 3123 AND 3182;
DELETE FROM problem WHERE id BETWEEN 1097 AND 1117;
DELETE FROM lesson WHERE id IN (158, 159, 160);

-- 그 다음 현재 INSERT 문 실행...

1-10: 선택사항: 트랜잭션 래핑으로 데이터 원자성 보장

여러 INSERT 문을 트랜잭션으로 감싸면 성능이 개선되며, 트랜잭션이 없으면 각 INSERT가 독립적인 트랜잭션으로 처리됩니다. 데이터 일관성을 더욱 강화하기 위해 전체 스크립트를 BEGIN TRANSACTION과 COMMIT으로 감싸는 것을 고려하세요.

♻️ 제안: 트랜잭션 래핑 추가
+ BEGIN TRANSACTION;
+
  -- Chapter: 데이터베이스 (id: 4), Unit: 트랜잭션 (id: 54)
  
  -- Lesson 1: 트랜잭션 기초 (ID: 158)
  ...
  INSERT INTO answer (id, problem_id, content, explanation)
  VALUES (319, 1112, 'Dirty Read, 더티 리드', ...),
         (320, 1115, 'Non-Repeatable Read, 반복 불가능한 읽기...', ...);
+
+ COMMIT;

또는 롤백 시나리오에 대비하려면:

BEGIN TRY
  BEGIN TRANSACTION;
    -- 모든 INSERT 문...
  COMMIT;
END TRY
BEGIN CATCH
  ROLLBACK;
  THROW;
END CATCH;

Also applies to: 48-50, 98-100, 148-150

src/main/resources/sql/problem/database/unit05_외래키와제약조건.sql (1)

60-68: 텍스트 서식의 일관성을 개선하세요.

여러 줄에서 불필요한 공백이 발견됩니다:

  • 문장 시작/끝의 공백
  • 물음표 앞의 공백 (예: ' ? ''?')

일관된 서식을 유지하면 코드 가독성이 향상됩니다.

src/main/resources/sql/problem/database/unit03_관계형모델.sql (1)

52-55: 텍스트 포맷팅 개선 권장

Answer 레코드의 설명 텍스트에 일부 포맷팅 문제가 있습니다:

  • Line 52: "표현한 것이다.물리적으로는" → 마침표 뒤에 공백 필요
  • Line 54: " 도메인(Domain) 은" → 앞쪽에 불필요한 공백
  • Line 54: "집합이다.속성에" → 마침표 뒤에 공백 필요
📝 포맷팅 수정 제안
-VALUES (255, 888, '릴레이션, Relation, 테이블, Table', '릴레이션(Relation) 은 관계형 데이터베이스에서 데이터를 2차원 표 형태로 표현한 것이다.물리적으로는 테이블(Table) 이라고도 한다. 구조를 정의하는 릴레이션 스키마와 실제 데이터인 릴레이션
-        인스턴스로 구성된다.'),
-       (256, 891, '도메인, Domain', ' 도메인(Domain) 은 하나의 속성이 가질 수 있는 원자값들의 집합이다.속성에 허용되는 값의 범위를 정의하며, 예를 들어 성별 속성의 도메인은 {남, 여}, 학년 속성의 도메인은 {1, 2, 3,
-        4}로 정의할 수 있다.');
+VALUES (255, 888, '릴레이션, Relation, 테이블, Table', '릴레이션(Relation)은 관계형 데이터베이스에서 데이터를 2차원 표 형태로 표현한 것이다. 물리적으로는 테이블(Table)이라고도 한다. 구조를 정의하는 릴레이션 스키마와 실제 데이터인 릴레이션 인스턴스로 구성된다.'),
+       (256, 891, '도메인, Domain', '도메인(Domain)은 하나의 속성이 가질 수 있는 원자값들의 집합이다. 속성에 허용되는 값의 범위를 정의하며, 예를 들어 성별 속성의 도메인은 {남, 여}, 학년 속성의 도메인은 {1, 2, 3, 4}로 정의할 수 있다.');
src/main/resources/sql/problem/database/unit02_식별관계와비식별관계.sql (1)

100-102: 텍스트 품질 개선 권장

Answer 251의 content에 마지막에 불필요한 공백이 있습니다: 'FK, 외래키, 외래 키, Foreign Key '

🔧 수정 제안
-VALUES (251, 874, 'FK, 외래키, 외래 키, Foreign Key ', ' 비식별 관계에서는 부모 테이블의 기본 키를 자식 테이블의 외래 키(FK) 로만 사용하고, 자식은 자체적인 기본 키를 가진다. 부모의 PK가 자식의 일반 속성(FK)으로만 사용되는 것이 비식별 관계의 특징이다.'),
+VALUES (251, 874, 'FK, 외래키, 외래 키, Foreign Key', '비식별 관계에서는 부모 테이블의 기본 키를 자식 테이블의 외래 키(FK)로만 사용하고, 자식은 자체적인 기본 키를 가진다. 부모의 PK가 자식의 일반 속성(FK)으로만 사용되는 것이 비식별 관계의 특징이다.'),
src/main/resources/sql/problem/database/unit04_키.sql (4)

71-74: 옵션 텍스트의 공백 일관성을 개선하세요.

옵션 문자열에도 불필요한 공백이 포함되어 있습니다. 특히 시작 부분의 공백이 일관성을 해칩니다.

♻️ 제안하는 수정사항
-(2603, 915, 'NULL 값을 가질 수 있다 ', ' 기본키는 NOT NULL 제약이 적용되어 NULL 값을 가질 수 없다.', false),
+(2603, 915, 'NULL 값을 가질 수 있다', '기본키는 NOT NULL 제약이 적용되어 NULL 값을 가질 수 없다.', false),
-(2604, 915, '중복된 값을 가질 수 있다 ', ' 기본키는 UNIQUE 제약이 적용되어 중복된 값을 가질 수 없다.', false),
+(2604, 915, '중복된 값을 가질 수 있다', '기본키는 UNIQUE 제약이 적용되어 중복된 값을 가질 수 없다.', false),
-(2605, 915, 'NOT NULL과 UNIQUE 제약이 적용된다 ', ' 기본키는 NOT NULL (NULL 값 불가) 과 UNIQUE (중복 값 불가) 제약이 모두 적용된다.', true),
+(2605, 915, 'NOT NULL과 UNIQUE 제약이 적용된다', '기본키는 NOT NULL (NULL 값 불가)과 UNIQUE (중복 값 불가) 제약이 모두 적용된다.', true),
-(2606, 915, '테이블당 여러 개를 지정할 수 있다 ', ' 기본키는 테이블당 하나만 지정할 수 있다.', false),
+(2606, 915, '테이블당 여러 개를 지정할 수 있다', '기본키는 테이블당 하나만 지정할 수 있다.', false),

141-141: 숫자 답안의 공백을 제거하세요.

옵션 2638의 " 0개 " 문자열에 불필요한 공백이 포함되어 있어 다른 옵션들과 일관성이 떨어집니다.

♻️ 제안하는 수정사항
-(2638, 926, ' 0개 ', ' 유일하게 식별 가능한 속성이 있으므로 후보키가 존재한다.', false),
+(2638, 926, '0개', '유일하게 식별 가능한 속성이 있으므로 후보키가 존재한다.', false),

144-147: 마지막 옵션 그룹의 공백을 정리하세요.

옵션 2639-2642에도 불필요한 공백이 포함되어 있습니다. 전체 파일에서 일관된 형식을 유지하기 위해 수정을 권장합니다.

♻️ 제안하는 수정사항
-(2639, 928, ' 값이 자주 변경되는 속성을 선택한다 ', ' 기본키는 값이 자주 변경되지 않는 안정적인 속성을 선택해야 한다.기본키가 변경되면 참조하는 외래키도 모두 변경해야 하므로 비효율적이다.',true),
+(2639, 928, '값이 자주 변경되는 속성을 선택한다', '기본키는 값이 자주 변경되지 않는 안정적인 속성을 선택해야 한다. 기본키가 변경되면 참조하는 외래키도 모두 변경해야 하므로 비효율적이다.', true),
-(2640, 928, ' NULL 값을 허용하지 않는 속성을 선택한다 ', ' 기본키는 NOT NULL 제약이 있으므로 NULL 값을 허용하지 않는 속성을 선택해야 한다.이는 올바른 고려사항이다.', false),
+(2640, 928, 'NULL 값을 허용하지 않는 속성을 선택한다', '기본키는 NOT NULL 제약이 있으므로 NULL 값을 허용하지 않는 속성을 선택해야 한다. 이는 올바른 고려사항이다.', false),
-(2641, 928, ' 유일성을 만족하는 속성을 선택한다 ', ' 기본키는 UNIQUE 제약이 있으므로 유일성을 만족하는 속성을 선택해야 한다.이는 올바른 고려사항이다.', false),
+(2641, 928, '유일성을 만족하는 속성을 선택한다', '기본키는 UNIQUE 제약이 있으므로 유일성을 만족하는 속성을 선택해야 한다. 이는 올바른 고려사항이다.', false),
-(2642, 928, ' 가능한 단순한 속성을 선택한다 ', ' 기본키는 가능한 단순한(적은 수의 속성으로 구성된) 속성을 선택하는 것이 효율적이다.이는 올바른 고려사항이다.', false);
+(2642, 928, '가능한 단순한 속성을 선택한다', '기본키는 가능한 단순한(적은 수의 속성으로 구성된) 속성을 선택하는 것이 효율적이다. 이는 올바른 고려사항이다.', false);

83-86: 옵션 문자열의 공백 패턴을 통일하세요.

옵션 2611-2614에도 동일한 공백 문제가 있습니다. 파일 전체에 걸쳐 이러한 패턴이 반복되므로, 일괄 정리를 고려해보세요.

♻️ 제안하는 수정사항
-(2611, 918, ' 시스템에서 자동으로 생성되는 키이다 ', ' 시스템에서 자동으로 생성되는 키는 대리키(Surrogate Key) 이다.', false),
+(2611, 918, '시스템에서 자동으로 생성되는 키이다', '시스템에서 자동으로 생성되는 키는 대리키(Surrogate Key)이다.', false),
-(2612, 918, ' 두 개 이상의 속성을 결합하여 구성된 키이다 ', ' 복합키(Composite Key) 는 두 개 이상의 속성을 결합하여 구성된 키이다.단일 속성으로는 유일성을 만족하지 못할 때 사용한다.',true),
+(2612, 918, '두 개 이상의 속성을 결합하여 구성된 키이다', '복합키(Composite Key)는 두 개 이상의 속성을 결합하여 구성된 키이다. 단일 속성으로는 유일성을 만족하지 못할 때 사용한다.', true),
-(2613, 918, ' 후보키 중에서 선택되지 않은 키이다 ', ' 후보키 중에서 선택되지 않은 키는 대체키(Alternate Key) 이다.', false),
+(2613, 918, '후보키 중에서 선택되지 않은 키이다', '후보키 중에서 선택되지 않은 키는 대체키(Alternate Key)이다.', false),
-(2614, 918, ' 현실 세계에서 고유한 의미를 가진 키이다 ', ' 현실 세계에서 고유한 의미를 가진 키는 자연키(Natural Key) 이다.', false),
+(2614, 918, '현실 세계에서 고유한 의미를 가진 키이다', '현실 세계에서 고유한 의미를 가진 키는 자연키(Natural Key)이다.', false),
src/main/resources/sql/problem/database/unit08_서브쿼리.sql (1)

3-15: 시드 재실행(멱등)과 ID 충돌/시퀀스 정합성 보장이 필요합니다.

현재는 lesson/problem에 고정 idINSERT만 수행해서(라인 4-14) 같은 시드를 재실행하면 PK 충돌로 실패할 가능성이 큽니다. 또한 DB가 IDENTITY/SEQUENCE 기반이면 이후 자동 증가 값이 충돌할 수 있어, 시드 정책(“1회성” vs “재실행 가능”)을 명확히 하고 그에 맞는 처리가 필요합니다.

가능한 개선안(DB 방언에 맞게 택1)
+-- (예) PostgreSQL: 멱등 시드
 INSERT INTO lesson (id, title, unit_id)
-VALUES (143, '서브쿼리 기초', 49);
+VALUES (143, '서브쿼리 기초', 49)
+ON CONFLICT (id) DO NOTHING;

 INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
 VALUES ...
+ON CONFLICT (id) DO NOTHING;
+-- (예) MySQL/MariaDB: 멱등 시드
+INSERT IGNORE INTO lesson (id, title, unit_id) VALUES (143, '서브쿼리 기초', 49);
+INSERT IGNORE INTO problem (id, lesson_id, instruction, content, problem_type) VALUES ...;
+-- (선택) 시드 후 시퀀스/아이덴티티 정합성(예: PostgreSQL)
+-- SELECT setval(pg_get_serial_sequence('lesson','id'), (SELECT MAX(id) FROM lesson));
+-- SELECT setval(pg_get_serial_sequence('problem','id'), (SELECT MAX(id) FROM problem));
src/main/resources/sql/problem5.sql (1)

1-10: 트랜잭션 제어를 추가하는 것을 고려하세요.

이 파일에는 300개 이상의 INSERT 문이 포함되어 있지만 명시적인 트랜잭션 제어가 없습니다. 스크립트 실행 중 오류가 발생하면 데이터베이스가 부분적으로만 업데이트되어 일관성 없는 상태가 될 수 있습니다.

♻️ 제안: 트랜잭션 래퍼 추가

파일 시작 부분에 다음을 추가:

BEGIN TRANSACTION;
-- 또는 데이터베이스에 따라
-- START TRANSACTION;

파일 끝 부분에 다음을 추가:

COMMIT;
-- 오류 발생 시 롤백되도록 보장

이렇게 하면 전체 시드 데이터 삽입이 원자적으로 처리됩니다.

src/main/resources/sql/problem/database/unit14_인덱스.sql (1)

1-150: (권장) 시드 전체를 트랜잭션으로 감싸고, 재실행 전략을 정해두면 운영/테스트가 편해집니다.
현재는 “한 번만 실행”이 전제처럼 보이는데, CI/로컬 초기화/마이그레이션 재실행 시 실패할 수 있어 BEGIN/COMMIT + (DB에 맞는) upsert/무시 전략을 권장합니다.

제안 diff (트랜잭션 래핑)
+BEGIN;
 -- Chapter: 데이터베이스 (id: 4), Unit: 인덱스 (id: 55)
 
 -- Lesson 1: 인덱스 기초 (ID: 161)
 INSERT INTO lesson (id, title, unit_id)
 VALUES (161, '인덱스 기초', 55);
@@
 INSERT INTO answer (id, problem_id, content, explanation)
 VALUES (325, 1133, 'DROP', 'DROP INDEX 문으로 인덱스를 삭제한다. DROP INDEX 인덱스명 형식으로 작성한다.'),
        (326, 1136, '풀 테이블 스캔, full table scan, 전체 테이블 스캔, full scan, 풀스캔', '인덱스를 사용하지 않고 테이블의 모든 행을 처음부터 끝까지 읽는 것을 풀 테이블 스캔이라고 한다. 데이터가 많으면 성능이 크게 저하된다.');
+
+COMMIT;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d80368 and 14beedb.

📒 Files selected for processing (15)
  • src/main/resources/sql/problem/database/unit01_데이터모델링.sql
  • src/main/resources/sql/problem/database/unit02_식별관계와비식별관계.sql
  • src/main/resources/sql/problem/database/unit03_관계형모델.sql
  • src/main/resources/sql/problem/database/unit04_키.sql
  • src/main/resources/sql/problem/database/unit05_외래키와제약조건.sql
  • src/main/resources/sql/problem/database/unit06_DDL.sql
  • src/main/resources/sql/problem/database/unit07_DML.sql
  • src/main/resources/sql/problem/database/unit08_서브쿼리.sql
  • src/main/resources/sql/problem/database/unit09_조인.sql
  • src/main/resources/sql/problem/database/unit10_페이징.sql
  • src/main/resources/sql/problem/database/unit11_뷰.sql
  • src/main/resources/sql/problem/database/unit12_정규화.sql
  • src/main/resources/sql/problem/database/unit13_트랜잭션.sql
  • src/main/resources/sql/problem/database/unit14_인덱스.sql
  • src/main/resources/sql/problem5.sql
🔇 Additional comments (23)
src/main/resources/sql/problem/database/unit07_DML.sql (1)

1-148: 전반적인 데이터 구조와 내용은 양호합니다.

  • 3개의 강의(DML 1, 2, 3)와 21개의 문제가 적절히 구조화되어 있습니다.
  • 모든 객관식 문제(OBJECTIVE)에는 선택지와 정답 표시가 명확합니다.
  • 교육 내용상 DML 개념에 대한 설명과 예제가 정확하고 이해하기 좋습니다.
  • SQL 문법과 데이터 무결성(외래키 관계)이 올바른 상태입니다.

위의 문제 975 누락 이슈만 해결하면 모두 정상입니다.

src/main/resources/sql/problem/database/unit13_트랜잭션.sql (1)

4-105: 우수한 데이터 일관성 및 교육 콘텐츠 품질

이 파일의 강점:

  • ✓ 모든 OBJECTIVE 문제(21개 중 15개)에 정확히 4개의 선택지와 1개의 정답 플래그 설정
  • ✓ 모든 SUBJECTIVE 문제(21개 중 6개)에 대한 정답 레코드 존재
  • ✓ 트랜잭션 개념을 체계적으로 다루는 잘 구성된 교육 콘텐츠
  • ✓ 각 선택지에 명확한 설명(explanation)으로 학습 가치 극대화
  • ✓ 3개 단원(기초, 제어, 격리 수준)의 논리적 진행

데이터 관계 설정이 정확하고 일관성이 우수합니다.

src/main/resources/sql/problem/database/unit09_조인.sql (6)

16-50: Lesson 1 구조 및 데이터 일관성이 양호합니다.

각 문제의 유형에 맞게 OBJECTIVE 문제는 선택지와 정답이, SUBJECTIVE 문제는 답안이 올바르게 구성되어 있습니다. 조인 기초 개념의 설명과 선택지 설명이 기술적으로 정확합니다.


53-100: Lesson 2 내용이 정확하며, FULL OUTER JOIN 설명에 주의하세요.

Line 100의 FULL OUTER JOIN 설명은 MySQL의 경우 FULL OUTER JOIN을 직접 지원하지 않으며, LEFT OUTER JOIN과 RIGHT OUTER JOIN의 조합으로 구현해야 한다는 점을 반영하고 있습니다. 대상 데이터베이스가 MySQL인 경우, 이 설명이 시스템에 적절한지 확인하세요.


103-150: Lesson 3 구조 및 내용이 양호하며, 실용적 예제가 잘 구성되어 있습니다.

다중 조인, 필터링을 활용한 조인 결과 처리, 성능 최적화 등 실전적인 내용이 잘 정리되어 있습니다. 조인 키에 인덱스를 생성하는 최적화 방법(line 138)도 최신 SQL JOIN 모범 사례를 따릅니다.


1-150: PR 목표의 '답안 셔플링' 구현 방식 확인이 필요합니다.

PR 목표에 명시된 "Introduces answer shuffling for problems" 기능이 이 SQL 파일에서 직접 구현되는지, 아니면 애플리케이션 로직(예: 문제 로딩 시점)에서 구현되는지 명확하지 않습니다. 해당 기능이 어느 부분에 구현되어 있는지 확인해주세요.


1-14: 이 문제는 이미 처리되었습니다.

파일에서 참조하는 unit_id 50src/main/resources/sql/unit.sql에 올바르게 정의되어 있습니다:

(50, 4, '조인', '여러 테이블 결합(INNER/OUTER/CROSS/SELF)과 조인 조건 설계')

chapter_id 4(데이터베이스)에 속하며, 파일명 unit09_조인.sql과도 일치합니다. unit.sql은 마스터 초기화 파일로 모든 단원 정의를 포함하고 있으므로 문제 데이터 파일보다 먼저 실행되어 참조 무결성이 보장됩니다.


16-46: 옵션 ID 범위가 다른 unit 파일들과 겹치지 않습니다.

검증 결과, unit08은 2823-2882, unit09는 2883-2942, unit10은 2943-3002로 ID 범위가 명확하게 분리되어 있고 중복이 없습니다. 각 unit마다 60개의 연속적인 option ID를 할당하는 일관된 패턴을 유지하고 있으므로 추가 조치가 필요하지 않습니다.

src/main/resources/sql/problem/database/unit05_외래키와제약조건.sql (1)

1-17: 기본 구조가 올바르게 작성되었습니다.

레슨과 문제 데이터의 기본 구조가 적절하며, ID 시퀀스와 외래키 참조가 일관성 있게 유지되고 있습니다.

src/main/resources/sql/problem/database/unit01_데이터모델링.sql (1)

1-155: LGTM!

데이터 모델링 기본 unit의 SQL seed 파일이 올바르게 구성되어 있습니다:

  • 3개의 lesson과 21개의 problem이 적절히 정의됨
  • OBJECTIVE 문제들은 모두 정확히 하나의 정답을 가짐
  • SUBJECTIVE 문제들은 모두 답안 레코드를 가짐
  • 교육 콘텐츠가 명확하고 일관성 있게 작성됨
src/main/resources/sql/problem/database/unit10_페이징.sql (1)

1-149: LGTM!

페이징(Paging) unit의 SQL seed 파일이 잘 구성되어 있습니다:

  • OFFSET 기반 페이징과 커서 기반 페이징 개념을 포괄적으로 다룸
  • 모든 문제와 답안이 올바르게 매핑됨
  • 기술적 정확성이 확보됨 (예: LIMIT/OFFSET 순서, 성능 특성)
  • 실용적인 예제와 명확한 설명 제공
src/main/resources/sql/problem/database/unit11_뷰.sql (1)

1-149: LGTM!

뷰(View) unit의 SQL seed 파일이 우수하게 작성되었습니다:

  • 뷰의 기본 개념부터 갱신 가능한 뷰, WITH CHECK OPTION까지 체계적으로 다룸
  • 모든 데이터 무결성 검증 통과
  • 기술적 정확성 확보 (갱신 불가능한 뷰 조건, 성능 고려사항 등)
  • 보안 활용 사례를 포함한 실용적인 예제 제공
src/main/resources/sql/problem/database/unit03_관계형모델.sql (1)

1-4: 파일명과 Unit ID의 불일치는 없습니다. 파일은 순차적 조직 번호(01-07)를 사용하고, 실제 데이터베이스 Unit ID(42-48)는 일관된 매핑을 따릅니다. 이는 의도된 설계입니다.

src/main/resources/sql/problem/database/unit12_정규화.sql (1)

1-150: 전체 데이터 구조와 내용이 우수합니다.

정규화 단원의 데이터 시드 구조가 잘 설계되었습니다:

  • 3개 레슨으로 논리적으로 구성 (기초 → 정규형 → 과정)
  • 각 레슨당 7개 문제로 일관성 있게 구성
  • 객관식/주관식 문제 적절히 혼합 (15:6 비율)
  • 모든 객관식 문제에 정확히 4개의 선택지와 1개의 정답
  • 상세한 해설로 학습 효과 향상
  • 외래 키 참조 관계가 올바르게 설정됨

데이터 무결성도 검증되었습니다:

  • 모든 ID가 고유하고 순차적
  • 모든 주관식 문제에 답안 데이터 존재
  • is_answer 플래그가 정확히 설정됨
src/main/resources/sql/problem/database/unit04_키.sql (2)

1-18: 데이터 삽입 구조가 올바르게 구성되어 있습니다.

레슨과 문제 삽입이 적절한 순서로 작성되어 있으며, 외래키 참조도 정확합니다.


1-151: 데이터 무결성은 잘 유지되고 있습니다.

검증 결과: 총 21개 문제(OBJECTIVE 15개, SUBJECTIVE 6개)가 올바르게 구성되어 있으며, OBJECTIVE 문제별로 4개의 옵션이, SUBJECTIVE 문제별로 답안이 정확히 제공되어 있습니다. 외래키 참조와 ID 시퀀스도 일관성 있게 유지되고 있습니다.

다만 문제 텍스트와 옵션 설명에서 불필요한 공백이 일부 존재합니다:

  • 문제 918-921: 지시문과 콘텐츠 앞뒤에 공백
  • 옵션 설명: 2603-2606, 2610-2611 등에서 텍스트 뒤에 공백

이는 데이터 기능에 영향을 주지 않지만, 사용자 인터페이스 일관성을 위해 정리하면 좋습니다.

src/main/resources/sql/problem/database/unit08_서브쿼리.sql (1)

16-50: 이 리뷰 의견의 우려사항들이 실제 구현에 적용되지 않습니다.

검토 결과:

  1. option 테이블명: PostgreSQL에서 option은 예약어가 아니므로 따옴표 없이 사용 가능하며, 현재 스키마에서도 그대로 정의되어 있습니다.

  2. true/false 리터럴: PostgreSQL의 BOOLEAN 타입은 true, false, TRUE, FALSE, 1, 0 모두를 지원합니다. 스키마의 is_answer BOOLEAN NOT NULL에서 정의된 타입과 완벽하게 호환되므로 문제가 없습니다.

  3. 주관식 답변 정규화: 실제 채점 로직(ProblemSubmissionService)은 클라이언트로부터 isCorrect boolean 값만 받아 처리하며, 백엔드는 답변 콘텐츠(Answer.content)를 파싱하거나 검증하지 않습니다. 따라서 SQL 파일의 콤마 구분 형식은 단순 데이터 저장만 되고, 채점 시스템의 검증 대상이 아닙니다.

INSERT 쿼리는 예상 오류 없이 정상 실행될 것입니다.

src/main/resources/sql/problem5.sql (2)

148-151: LGTM! 정답 데이터가 올바르게 추가되었습니다.

문제 1010번에 대한 정답 'EXISTS'가 정확하며, 설명도 상관 서브쿼리와의 사용법을 잘 설명하고 있습니다. 데이터 구조와 형식이 기존 패턴과 일관성 있게 작성되었습니다.


1-10: ID 충돌 우려는 근거가 없습니다. problem1-5.sql 파일들 간 ID 범위가 겹치지 않으며, 모든 ID가 고유합니다.

  • Lesson ID: 143-163 (고유)
  • Problem ID: 992-1132 (고유)
  • Answer ID: 285-325 (고유)

현재 구조에서는 ID 충돌 가능성이 없습니다.

Likely an incorrect or invalid review comment.

src/main/resources/sql/problem/database/unit06_DDL.sql (3)

91-95: 문제 962의 정답/오답 구분이 정확함
데이터베이스 설정에서 확인된 타깃 DBMS는 PostgreSQL입니다. PostgreSQL에서는 제약 조건을 추가할 때 반드시 CONSTRAINT 키워드가 필요하거나, 제약 조건 이름을 명시하지 않아야 합니다. 따라서 ALTER TABLE students ADD uk_email UNIQUE (email) 문법은 PostgreSQL에서 유효하지 않은 문법이며, 현재 정답/오답 구분이 올바릅니다. 별도 검증이 필요하지 않습니다.


19-49: option 테이블명은 프로젝트의 대상 데이터베이스(PostgreSQL)에서 예약어가 아님
해당 프로젝트는 PostgreSQL을 사용하도록 설정되어 있으며(build.gradle: runtimeOnly 'org.postgresql:postgresql'), PostgreSQL에서는 option이 예약어가 아닙니다. MySQL, SQL Server, Oracle에서는 예약어이지만, 현재 코드베이스는 PostgreSQL 호환성을 기준으로 하므로 이 테이블명은 문제가 없습니다.

Likely an incorrect or invalid review comment.


6-154: ID 충돌/시퀀스 불일치 우려는 근거 부족 — 하지만 PostgreSQL 대상의 TRUNCATE 관련 설명이 부정확함

unit01부터 unit14까지 모든 유닛의 ID 범위를 검증한 결과, lesson/problem/option/answer 테이블의 ID 할당이 순차적이고 겹치지 않습니다. unit06_DDL.sql의 problem 950–970, option 2703–2722 범위는 unit05의 ID 2702 이후, unit07의 971부터 시작하기 전으로 안전하게 격리되어 있습니다. 따라서 ID 충돌이나 auto-increment 불일치 위험은 현재 구조에서 발견되지 않습니다.

다만 교육 콘텐츠 정확성에 이슈가 있습니다. 프로젝트의 대상 DBMS는 PostgreSQL(application-dev.yml, application-prod.yml에서 org.postgresql.DriverPostgreSQLDialect 확인)인데, 문제 969 선택지 2755의 설명이 부정확합니다:

  • 현재 내용: "TRUNCATE는 DDL로 자동 커밋되어 일반적으로 ROLLBACK이 불가능하다"
  • PostgreSQL 실제 동작: TRUNCATE는 명시적 트랜잭션(BEGIN...COMMIT/ROLLBACK) 내에서는 ROLLBACK 가능
  • 문제점: MySQL/MariaDB 동작을 PostgreSQL에 그대로 적용

문제 969의 선택지 2755 설명을 PostgreSQL 동작에 맞게 수정하거나, 더 정확히 "자동 커밋 모드에서 일반적으로 불가능"으로 명시하십시오.

Likely an incorrect or invalid review comment.

src/main/resources/sql/problem/database/unit14_인덱스.sql (1)

48-51: 주관식 정답 content 다중허용 포맷(콤마 구분)이 서버 로직과 일치하는지 확인해주세요.
예: 목차, 색인, ..., 1, 하나, 한, 풀 테이블 스캔, full table scan, ... 처럼 콤마/공백/대소문자 변형을 허용하고 있는데, 채점 로직이 (1) 콤마 split 후 trim 하는지, (2) 대소문자/공백 정규화 하는지, (3) 부분일치가 아닌 완전일치 기준인지에 따라 정답 판정이 달라질 수 있습니다.

Also applies to: 98-101, 148-150

Comment on lines +110 to +118
INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
VALUES (943, 136, '다음 중 NOT NULL 제약에 대한 설명으로 올바른 것은?', 'NOT NULL은 일반 제약 조건 중 하나이다.', 'OBJECTIVE'),
(944, 136, '빈칸에 들어갈 제약 조건을 작성하시오.', '해당 속성의 값이 고유(중복 불가능)해야 함을 보장하는 제약 조건은 ___이다.', 'SUBJECTIVE'),
(945, 136, '다음 중 CHECK 제약에 대한 설명으로 올바른 것은?', 'CHECK는 특정 조건을 검사하는 제약 조건이다.', 'OBJECTIVE'),
(946, 136, '다음 SQL의 DEFAULT 제약의 의미는?', 'CREATE TABLE products (id INT PRIMARY KEY, stock INT DEFAULT 0);
', 'OBJECTIVE'),
(947, 136, '빈칸에 들어갈 내용을 작성하시오.', 'UNIQUE 제약이 적용된 컬럼은 중복된 값을 가질 수 없지만, ___ 값은 가질 수 있다.', 'SUBJECTIVE'),
(948, 136, '다음 중 CHECK 제약의 올바른 사용 예시는?', 'CHECK 제약은 속성 값이 특정 조건을 만족해야 한다.', 'OBJECTIVE'),
(949, 136, '다음 중 UNIQUE 제약과 기본키의 차이점으로 올바른 것은?', '둘 다 중복을 허용하지 않는다.', 'OBJECTIVE');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

SQL 문자열 내 개행 문자로 인한 구문 오류를 수정하세요.

Lines 114-115의 content 필드에 개행 문자가 포함되어 있어 SQL INSERT 문이 실패할 수 있습니다.

🐛 제안하는 수정
 INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
 VALUES (943, 136, '다음 중 NOT NULL 제약에 대한 설명으로 올바른 것은?', 'NOT NULL은 일반 제약 조건 중 하나이다.', 'OBJECTIVE'),
        (944, 136, '빈칸에 들어갈 제약 조건을 작성하시오.', '해당 속성의 값이 고유(중복 불가능)해야 함을 보장하는 제약 조건은 ___이다.', 'SUBJECTIVE'),
        (945, 136, '다음 중 CHECK 제약에 대한 설명으로 올바른 것은?', 'CHECK는 특정 조건을 검사하는 제약 조건이다.', 'OBJECTIVE'),
-       (946, 136, '다음 SQL의 DEFAULT 제약의 의미는?', 'CREATE TABLE products (id INT PRIMARY KEY, stock INT DEFAULT 0);
-', 'OBJECTIVE'),
+       (946, 136, '다음 SQL의 DEFAULT 제약의 의미는?', 'CREATE TABLE products (id INT PRIMARY KEY, stock INT DEFAULT 0);', 'OBJECTIVE'),
        (947, 136, '빈칸에 들어갈 내용을 작성하시오.', 'UNIQUE 제약이 적용된 컬럼은 중복된 값을 가질 수 없지만, ___ 값은 가질 수 있다.', 'SUBJECTIVE'),
        (948, 136, '다음 중 CHECK 제약의 올바른 사용 예시는?', 'CHECK 제약은 속성 값이 특정 조건을 만족해야 한다.', 'OBJECTIVE'),
        (949, 136, '다음 중 UNIQUE 제약과 기본키의 차이점으로 올바른 것은?', '둘 다 중복을 허용하지 않는다.', 'OBJECTIVE');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
VALUES (943, 136, '다음 중 NOT NULL 제약에 대한 설명으로 올바른 것은?', 'NOT NULL은 일반 제약 조건 중 하나이다.', 'OBJECTIVE'),
(944, 136, '빈칸에 들어갈 제약 조건을 작성하시오.', '해당 속성의 값이 고유(중복 불가능)해야 함을 보장하는 제약 조건은 ___이다.', 'SUBJECTIVE'),
(945, 136, '다음 중 CHECK 제약에 대한 설명으로 올바른 것은?', 'CHECK는 특정 조건을 검사하는 제약 조건이다.', 'OBJECTIVE'),
(946, 136, '다음 SQL의 DEFAULT 제약의 의미는?', 'CREATE TABLE products (id INT PRIMARY KEY, stock INT DEFAULT 0);
', 'OBJECTIVE'),
(947, 136, '빈칸에 들어갈 내용을 작성하시오.', 'UNIQUE 제약이 적용된 컬럼은 중복된 값을 가질 수 없지만, ___ 값은 가질 수 있다.', 'SUBJECTIVE'),
(948, 136, '다음 중 CHECK 제약의 올바른 사용 예시는?', 'CHECK 제약은 속성 값이 특정 조건을 만족해야 한다.', 'OBJECTIVE'),
(949, 136, '다음 중 UNIQUE 제약과 기본키의 차이점으로 올바른 것은?', '둘 다 중복을 허용하지 않는다.', 'OBJECTIVE');
INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
VALUES (943, 136, '다음 중 NOT NULL 제약에 대한 설명으로 올바른 것은?', 'NOT NULL은 일반 제약 조건 중 하나이다.', 'OBJECTIVE'),
(944, 136, '빈칸에 들어갈 제약 조건을 작성하시오.', '해당 속성의 값이 고유(중복 불가능)해야 함을 보장하는 제약 조건은 ___이다.', 'SUBJECTIVE'),
(945, 136, '다음 중 CHECK 제약에 대한 설명으로 올바른 것은?', 'CHECK는 특정 조건을 검사하는 제약 조건이다.', 'OBJECTIVE'),
(946, 136, '다음 SQL의 DEFAULT 제약의 의미는?', 'CREATE TABLE products (id INT PRIMARY KEY, stock INT DEFAULT 0);', 'OBJECTIVE'),
(947, 136, '빈칸에 들어갈 내용을 작성하시오.', 'UNIQUE 제약이 적용된 컬럼은 중복된 값을 가질 수 없지만, ___ 값은 가질 수 있다.', 'SUBJECTIVE'),
(948, 136, '다음 중 CHECK 제약의 올바른 사용 예시는?', 'CHECK 제약은 속성 값이 특정 조건을 만족해야 한다.', 'OBJECTIVE'),
(949, 136, '다음 중 UNIQUE 제약과 기본키의 차이점으로 올바른 것은?', '둘 다 중복을 허용하지 않는다.', 'OBJECTIVE');
🤖 Prompt for AI Agents
In @src/main/resources/sql/problem/database/unit05_외래키와제약조건.sql around lines 110
- 118, The INSERT INTO problem VALUES list contains an unescaped newline inside
the content value for id 946 (the 'CREATE TABLE products (id INT PRIMARY KEY,
stock INT DEFAULT 0);' string), which breaks the SQL statement; fix the row in
the VALUES list used by INSERT INTO problem by removing or escaping the newline
so the entire CREATE TABLE statement is a single-line string (e.g., replace the
line break with a space or an escaped newline) for the entry with id 946.

Comment on lines +4 to +5
INSERT INTO lesson (id, title, unit_id)
VALUES (155, '정규화 기초', 53);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's read the SQL file to understand its structure
wc -l src/main/resources/sql/problem/database/unit12_정규화.sql

Repository: Team-Gravit/gravit-server

Length of output: 127


🏁 Script executed:

# Read the file content to verify the claims
cat -n src/main/resources/sql/problem/database/unit12_정규화.sql

Repository: Team-Gravit/gravit-server

Length of output: 9437


🏁 Script executed:

# Check for similar SQL seed files and their patterns
find src/main/resources/sql -name "*.sql" -type f | head -20

Repository: Team-Gravit/gravit-server

Length of output: 1245


🏁 Script executed:

# Search for database configuration and SQL file usage
find src -name "*.properties" -o -name "*.yaml" -o -name "*.yml" | head -20

Repository: Team-Gravit/gravit-server

Length of output: 187


🏁 Script executed:

# Check for any migration framework configuration
rg -i "flyway|liquibase|migration" src/main --type java --type properties --type yaml | head -20

Repository: Team-Gravit/gravit-server

Length of output: 103


🏁 Script executed:

# Look for other similar SQL files in the problem/database directory
ls -la src/main/resources/sql/problem/database/

Repository: Team-Gravit/gravit-server

Length of output: 1107


🏁 Script executed:

# Check table schema definitions to understand PK constraints
find src -name "*schema*" -o -name "*create*" -o -name "*DDL*" | grep -i sql

Repository: Team-Gravit/gravit-server

Length of output: 123


🏁 Script executed:

# Check application configuration for database setup
cat -n src/main/resources/application-dev.yml | head -50

Repository: Team-Gravit/gravit-server

Length of output: 1499


🏁 Script executed:

# Check one of the other database SQL files to see if it has the same pattern
head -30 src/main/resources/sql/problem/database/unit01_데이터모델링.sql

Repository: Team-Gravit/gravit-server

Length of output: 2086


🏁 Script executed:

# Search for any table schema or JPA entity definitions
find src -name "*.java" -type f -path "*/entity/*" | head -10

Repository: Team-Gravit/gravit-server

Length of output: 124


🏁 Script executed:

# Check if there's a schema.sql or init script
find src/main/resources -name "*.sql" | grep -i "schema\|init\|create"

Repository: Team-Gravit/gravit-server

Length of output: 173


🏁 Script executed:

# Check the Flyway migration files to see table schemas
cat src/main/resources/db/migration/V1__init_tables.sql | grep -A 20 "lesson\|problem\|option\|answer" | head -80

Repository: Team-Gravit/gravit-server

Length of output: 1921


🏁 Script executed:

# Search for how these SQL files in problem/database are loaded
rg -i "problem.*database.*sql\|resource.*sql\|sql.*loader" src/main --type java | head -20

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Check if there's a data loader or initializer
find src/main/java -name "*Loader*" -o -name "*Initializer*" -o -name "*Setup*" | head -10

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Look for references to these problem SQL files
rg "unit12_정규화" src --type java --type yaml

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Check if there's a Spring Boot application startup class that loads these SQL files
find src/main/java -name "Application.java" -o -name "*Runner*" -type f | head -10

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Search more broadly for references to the SQL files or data initialization
rg "ExecutionContext\|CommandLineRunner\|ApplicationRunner\|DataLoader\|afterPropertiesSet" src/main/java --type java -B 2 -A 5 | head -40

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Check the directory structure for any README or documentation about these SQL files
find src/main/resources/sql -name "README*" -o -name "*.md" -o -name "*.txt"

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Let me verify the exact table definitions to confirm PK constraints
grep -A 10 "CREATE TABLE lesson\|CREATE TABLE problem\|CREATE TABLE option\|CREATE TABLE answer" src/main/resources/db/migration/V1__init_tables.sql

Repository: Team-Gravit/gravit-server

Length of output: 1680


🏁 Script executed:

# Search for any data population scripts or loaders that reference these SQL files
find src -type f \( -name "*.java" -o -name "*.sh" -o -name "*.sql" \) -exec grep -l "problem/database\|unit12_정規化" {} \; 2>/dev/null

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Check if there's any documentation or comments about SQL file usage
find . -name "README*" -o -name "*.md" | xargs grep -l "sql\|seed" 2>/dev/null | head -5

Repository: Team-Gravit/gravit-server

Length of output: 138


🏁 Script executed:

# Look at the full directory structure to understand how these files are organized
tree -L 3 src/main/resources/sql/ 2>/dev/null || find src/main/resources/sql -type f | head -20

Repository: Team-Gravit/gravit-server

Length of output: 2089


🏁 Script executed:

# Check if there are any gradle or maven build files that might load these SQLs
find . -name "build.gradle" -o -name "pom.xml" | xargs cat 2>/dev/null | grep -A 5 -B 5 "sql\|resource" | head -40

Repository: Team-Gravit/gravit-server

Length of output: 1402


🏁 Script executed:

# Check if there's a custom data loader or if these SQL files are referenced anywhere
rg "ExecutionContext\|SqlScriptUtils\|ScriptUtils\|DataSource.*execute\|JdbcTemplate" src --type java -B 2 -A 5 | head -50

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Check for any configuration that loads SQL files from the problem directory
rg "problem.*sql\|sql.*load\|resource.*load" src --type java --type yaml | head -20

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Look for API endpoints or services that might use this data
rg "lesson\|problem\|option\|answer" src/main/java --type java -l | grep -i "service\|controller\|repository" | head -10

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Check if these SQL files are commented or documented anywhere
head -5 src/main/resources/sql/problem1.sql src/main/resources/sql/unit.sql 2>/dev/null

Repository: Team-Gravit/gravit-server

Length of output: 342


하드코딩된 ID의 Primary Key 충돌 처리 필요

이 스크립트는 hard-coded ID 값을 사용하여 INSERT하므로, 스크립트를 두 번 실행하면 Primary Key 중복 오류가 발생합니다. 개발/테스트 환경에서 데이터를 재설정해야 할 경우 문제가 될 수 있습니다.

다음 방법 중 하나를 고려하세요:

  • INSERT ... ON CONFLICT DO NOTHING (PostgreSQL) 또는 INSERT IGNORE 사용
  • ON DUPLICATE KEY UPDATE 활용
  • 스크립트 시작 시 기존 데이터 삭제 (개발 환경용)
🤖 Prompt for AI Agents
In @src/main/resources/sql/problem/database/unit12_정규화.sql around lines 4 - 5,
The INSERT into table "lesson" using a hard-coded primary key (VALUES with id
155) can cause duplicate-key errors if rerun; change the statement that inserts
into lesson (the INSERT INTO lesson (id, title, unit_id) VALUES (155, '정규화 기초',
53) line) to be idempotent — for PostgreSQL add an ON CONFLICT (id) DO NOTHING
clause, or use ON CONFLICT ... DO UPDATE to upsert, or switch to DELETE FROM
lesson WHERE id = 155 before inserting for dev-only reset scenarios; pick the
approach appropriate for the environment and apply it to this INSERT.

Comment on lines +1 to +150
-- Chapter: 데이터베이스 (id: 4), Unit: 트랜잭션 (id: 54)

-- Lesson 1: 트랜잭션 기초 (ID: 158)
INSERT INTO lesson (id, title, unit_id)
VALUES (158, '트랜잭션 기초', 54);

INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
VALUES (1097, 158, '다음 중 트랜잭션(Transaction)에 대한 설명으로 올바른 것은?', '트랜잭션은 데이터베이스의 상태를 변화시키는 작업의 단위이다.', 'OBJECTIVE'),
(1098, 158, '빈칸에 들어갈 약어를 작성하시오.', '트랜잭션의 네 가지 특성을 ___라고 한다. (각 속성의 앞글자를 딴 단어)', 'SUBJECTIVE'),
(1099, 158, '다음 중 트랜잭션의 ACID 특성이 아닌 것은?', 'ACID는 트랜잭션이 보장해야 하는 네 가지 특성이다.', 'OBJECTIVE'),
(1100, 158, '다음 중 원자성(Atomicity)의 의미로 올바른 것은?', '원자성은 트랜잭션의 기본 특성 중 하나이다.', 'OBJECTIVE'),
(1101, 158, '빈칸에 들어갈 용어를 작성하시오.', '트랜잭션이 성공적으로 완료되었음을 확정하는 명령어는 ___이다.', 'SUBJECTIVE'),
(1102, 158, '다음 중 일관성(Consistency)의 의미로 올바른 것은?', '일관성은 데이터베이스의 무결성을 보장한다.', 'OBJECTIVE'),
(1103, 158, '다음 SQL 명령어의 역할은?', 'BEGIN;\nUPDATE accounts SET balance = balance - 100 WHERE id = 1;\nUPDATE accounts SET balance = balance + 100 WHERE id = 2;\nCOMMIT;', 'OBJECTIVE');

INSERT INTO option (id, problem_id, content, explanation, is_answer)
VALUES
-- 문제 1097
(3123, 1097, '트랜잭션은 데이터를 조회만 하는 단위이다', '트랜잭션은 조회뿐만 아니라 삽입, 수정, 삭제를 포함한다.', false),
(3124, 1097, '트랜잭션은 하나 이상의 SQL 문을 논리적 작업 단위로 묶은 것이다', '트랜잭션은 데이터베이스의 상태를 변화시키는 하나 이상의 작업을 논리적으로 하나의 단위로 처리한다. 모두 성공하거나 모두 실패한다.', true),
(3125, 1097, '트랜잭션은 항상 자동으로 커밋된다', '명시적으로 COMMIT 또는 ROLLBACK 해야 한다.', false),
(3126, 1097, '트랜잭션은 테이블을 생성하는 단위이다', 'CREATE TABLE이 테이블을 생성한다.', false),

-- 문제 1099
(3127, 1099, 'Atomicity(원자성)', '원자성은 ACID 특성 중 하나이다.', false),
(3128, 1099, 'Consistency(일관성)', '일관성은 ACID 특성 중 하나이다.', false),
(3129, 1099, 'Isolation(격리성)', '격리성은 ACID 특성 중 하나이다.', false),
(3130, 1099, 'Availability(가용성)', '가용성은 ACID 특성이 아니다. ACID는 Atomicity, Consistency, Isolation, Durability이다.', true),

-- 문제 1100
(3131, 1100, '여러 트랜잭션이 동시에 실행되어도 독립적으로 동작한다', '격리성의 의미이다.', false),
(3132, 1100, '트랜잭션의 모든 연산이 완전히 수행되거나 전혀 수행되지 않아야 한다', '원자성은 All or Nothing 원칙이다. 트랜잭션의 모든 작업이 성공하면 COMMIT, 하나라도 실패하면 ROLLBACK하여 전체를 취소한다.', true),
(3133, 1100, '트랜잭션 완료 후 결과가 영구적으로 저장된다', '지속성의 의미이다.', false),
(3134, 1100, '트랜잭션 실행 전후 데이터베이스가 일관된 상태를 유지한다', '일관성의 의미이다.', false),

-- 문제 1102
(3135, 1102, '트랜잭션이 모두 성공하거나 모두 실패해야 한다', '원자성의 의미이다.', false),
(3136, 1102, '여러 트랜잭션이 독립적으로 실행되어야 한다', '격리성의 의미이다.', false),
(3137, 1102, '트랜잭션 결과가 영구적으로 저장되어야 한다', '지속성의 의미이다.', false),
(3138, 1102, '트랜잭션 실행 전후로 데이터베이스가 유효한 상태를 유지해야 한다', '일관성은 트랜잭션이 실행되기 전과 후에 데이터베이스의 모든 무결성 제약조건을 만족해야 한다는 특성이다.', true),

-- 문제 1103
(3139, 1103, '계좌 1번에서 100을 차감하고 계좌 2번에 100을 추가하는 이체 작업을 하나의 트랜잭션으로 처리한다', 'BEGIN으로 트랜잭션을 시작하고, 두 개의 UPDATE를 수행한 후 COMMIT으로 확정한다. 하나의 논리적 작업을 원자적으로 처리한다.', true),
(3140, 1103, '계좌 1번의 잔액만 감소시킨다', '두 개의 UPDATE가 모두 실행된다.', false),
(3141, 1103, '두 계좌의 잔액을 모두 삭제한다', 'UPDATE로 잔액을 수정한다.', false),
(3142, 1103, '트랜잭션을 취소한다', 'COMMIT으로 트랜잭션을 확정한다.', false);

INSERT INTO answer (id, problem_id, content, explanation)
VALUES (315, 1098, 'ACID', '트랜잭션의 네 가지 특성인 Atomicity(원자성), Consistency(일관성), Isolation(격리성), Durability(지속성)의 앞글자를 따서 ACID라고 한다.'),
(316, 1101, 'COMMIT, 커밋', 'COMMIT은 트랜잭션의 모든 작업을 확정하여 데이터베이스에 영구적으로 반영하는 명령어이다.');


-- Lesson 2: 트랜잭션 제어 (ID: 159)
INSERT INTO lesson (id, title, unit_id)
VALUES (159, '트랜잭션 제어', 54);

INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
VALUES (1104, 159, '다음 중 ROLLBACK의 역할로 올바른 것은?', 'ROLLBACK은 트랜잭션 제어 명령어이다.', 'OBJECTIVE'),
(1105, 159, '빈칸에 들어갈 키워드를 작성하시오.', 'BEGIN;\nUPDATE students SET grade = 4;\n___; -- 트랜잭션 취소', 'SUBJECTIVE'),
(1106, 159, '다음 중 SAVEPOINT의 역할로 올바른 것은?', 'SAVEPOINT는 트랜잭션 내에서 특정 지점을 표시한다.', 'OBJECTIVE'),
(1107, 159, '다음 SQL 명령어의 실행 결과는?', 'BEGIN;\nINSERT INTO students VALUES (1, ''홍길동'');\nSAVEPOINT sp1;\nINSERT INTO students VALUES (2, ''김철수'');\nROLLBACK TO sp1;\nCOMMIT;', 'OBJECTIVE'),
(1108, 159, '빈칸에 들어갈 명령어를 작성하시오.', 'BEGIN;\nDELETE FROM students WHERE id = 1;\n___ sp1; -- 특정 지점 저장', 'SUBJECTIVE'),
(1109, 159, '다음 중 자동 커밋(Autocommit)에 대한 설명으로 올바른 것은?', '자동 커밋은 데이터베이스의 기본 동작 방식이다.', 'OBJECTIVE'),
(1110, 159, '다음 중 트랜잭션 격리 수준과 관계없는 것은?', '격리 수준은 동시성 제어와 관련이 있다.', 'OBJECTIVE');

INSERT INTO option (id, problem_id, content, explanation, is_answer)
VALUES
-- 문제 1104
(3143, 1104, '트랜잭션을 확정한다', 'COMMIT이 트랜잭션을 확정한다.', false),
(3144, 1104, '트랜잭션을 시작한다', 'BEGIN이 트랜잭션을 시작한다.', false),
(3145, 1104, '특정 지점을 저장한다', 'SAVEPOINT가 특정 지점을 저장한다.', false),
(3146, 1104, '트랜잭션의 모든 변경 사항을 취소하고 이전 상태로 되돌린다', 'ROLLBACK은 트랜잭션의 모든 작업을 취소하고 트랜잭션 시작 이전 상태로 되돌린다.', true),

-- 문제 1106
(3147, 1106, '트랜잭션 내에서 중간 지점을 표시하여 부분 롤백을 가능하게 한다', 'SAVEPOINT는 트랜잭션 내에서 특정 지점을 표시한다. ROLLBACK TO SAVEPOINT로 해당 지점까지만 되돌릴 수 있다.', true),
(3148, 1106, '트랜잭션을 완전히 취소한다', 'ROLLBACK이 트랜잭션을 취소한다.', false),
(3149, 1106, '트랜잭션을 확정한다', 'COMMIT이 트랜잭션을 확정한다.', false),
(3150, 1106, '데이터를 백업한다', 'SAVEPOINT는 백업 기능이 아니다.', false),

-- 문제 1107
(3151, 1107, '두 학생 모두 삽입된다', 'sp1까지 롤백하므로 두 번째 삽입은 취소된다.', false),
(3152, 1107, '홍길동만 삽입된다', 'SAVEPOINT sp1 이후의 작업만 취소되므로 첫 번째 INSERT는 유지되고 두 번째 INSERT는 취소된다. COMMIT으로 확정한다.', true),
(3153, 1107, '김철수만 삽입된다', '첫 번째 삽입이 유지된다.', false),
(3154, 1107, '아무것도 삽입되지 않는다', 'sp1 이전의 작업은 유지된다.', false),

-- 문제 1109
(3155, 1109, '자동 커밋은 항상 비활성화되어 있다', '데이터베이스마다 기본 설정이 다르다.', false),
(3156, 1109, '자동 커밋이 활성화되면 각 SQL 문이 자동으로 커밋된다', '자동 커밋 모드에서는 각 SQL 문이 실행 후 자동으로 COMMIT된다. 명시적 트랜잭션 제어가 필요하면 자동 커밋을 비활성화해야 한다.', true),
(3157, 1109, '자동 커밋은 ROLLBACK을 불가능하게 만든다', '명시적 트랜잭션에서는 ROLLBACK 가능하다.', false),
(3158, 1109, '자동 커밋은 성능을 저하시킨다', '자동 커밋 자체는 성능과 직접적 관련이 없다.', false),

-- 문제 1110
(3159, 1110, 'READ UNCOMMITTED', 'READ UNCOMMITTED는 격리 수준 중 하나이다.', false),
(3160, 1110, 'READ COMMITTED', 'READ COMMITTED는 격리 수준 중 하나이다.', false),
(3161, 1110, 'REPEATABLE READ', 'REPEATABLE READ는 격리 수준 중 하나이다.', false),
(3162, 1110, 'COMMIT DELAY', 'COMMIT DELAY는 격리 수준이 아니다. 격리 수준은 READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE이다.', true);

INSERT INTO answer (id, problem_id, content, explanation)
VALUES (317, 1105, 'ROLLBACK, 롤백', 'ROLLBACK은 트랜잭션의 모든 변경 사항을 취소하고 트랜잭션 시작 이전 상태로 되돌린다.'),
(318, 1108, 'SAVEPOINT, 세이브포인트', 'SAVEPOINT는 트랜잭션 내에서 특정 지점을 표시한다. SAVEPOINT 이름 형식으로 사용한다.');


-- Lesson 3: 격리 수준과 동시성 (ID: 160)
INSERT INTO lesson (id, title, unit_id)
VALUES (160, '격리 수준과 동시성', 54);

INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
VALUES (1111, 160, '다음 중 격리성(Isolation)의 의미로 올바른 것은?', '격리성은 동시성 제어와 관련이 있다.', 'OBJECTIVE'),
(1112, 160, '빈칸에 들어갈 용어를 작성하시오.', '커밋되지 않은 데이터를 다른 트랜잭션이 읽는 현상을 ___라고 한다.', 'SUBJECTIVE'),
(1113, 160, '다음 중 READ UNCOMMITTED 격리 수준의 특징으로 올바른 것은?', 'READ UNCOMMITTED는 가장 낮은 격리 수준이다.', 'OBJECTIVE'),
(1114, 160, '다음 중 Dirty Read의 설명으로 올바른 것은?', 'Dirty Read는 격리 수준이 낮을 때 발생하는 문제이다.', 'OBJECTIVE'),
(1115, 160, '빈칸에 들어갈 용어를 작성하시오.', '같은 쿼리를 두 번 실행했을 때 결과가 달라지는 현상을 ___라고 한다.', 'SUBJECTIVE'),
(1116, 160, '다음 중 SERIALIZABLE 격리 수준의 특징으로 올바른 것은?', 'SERIALIZABLE은 가장 높은 격리 수준이다.', 'OBJECTIVE'),
(1117, 160, '다음 중 Phantom Read의 설명으로 올바른 것은?', 'Phantom Read는 격리 수준과 관련된 현상이다.', 'OBJECTIVE');

INSERT INTO option (id, problem_id, content, explanation, is_answer)
VALUES
-- 문제 1111
(3163, 1111, '트랜잭션이 모두 성공하거나 모두 실패해야 한다', '원자성의 의미이다.', false),
(3164, 1111, '동시에 실행되는 트랜잭션들이 서로 영향을 주지 않아야 한다', '격리성은 여러 트랜잭션이 동시에 실행되어도 각 트랜잭션이 독립적으로 실행되는 것처럼 보장한다. 한 트랜잭션의 중간 결과가 다른 트랜잭션에 영향을 주지 않는다.', true),
(3165, 1111, '트랜잭션 결과가 영구적으로 저장되어야 한다', '지속성의 의미이다.', false),
(3166, 1111, '데이터베이스가 일관된 상태를 유지해야 한다', '일관성의 의미이다.', false),

-- 문제 1113
(3167, 1113, '가장 높은 격리 수준으로 모든 문제를 방지한다', 'SERIALIZABLE이 가장 높은 격리 수준이다.', false),
(3168, 1113, 'Dirty Read를 완전히 방지한다', 'READ UNCOMMITTED는 Dirty Read를 방지하지 못한다.', false),
(3169, 1113, '가장 안전하지만 성능이 가장 느리다', 'READ UNCOMMITTED는 가장 빠르지만 안전하지 않다.', false),
(3170, 1113, '커밋되지 않은 데이터를 읽을 수 있어 Dirty Read가 발생한다', 'READ UNCOMMITTED는 커밋되지 않은 변경 사항도 읽을 수 있다. Dirty Read, Non-Repeatable Read, Phantom Read 모두 발생 가능하다.', true),

-- 문제 1114
(3171, 1114, '커밋되지 않은 데이터를 읽는 현상', 'Dirty Read는 다른 트랜잭션이 수정했지만 아직 커밋하지 않은 데이터를 읽는 현상이다. 해당 트랜잭션이 롤백되면 잘못된 데이터를 읽게 된다.', true),
(3172, 1114, '같은 데이터를 두 번 읽었을 때 결과가 다른 현상', 'Non-Repeatable Read의 설명이다.', false),
(3173, 1114, '없던 데이터가 나타나는 현상', 'Phantom Read의 설명이다.', false),
(3174, 1114, '데드락이 발생하는 현상', 'Dirty Read와 데드락은 다른 개념이다.', false),

-- 문제 1116
(3175, 1116, '커밋되지 않은 데이터를 읽을 수 있다', 'READ UNCOMMITTED의 특징이다.', false),
(3176, 1116, '가장 높은 격리 수준으로 모든 동시성 문제를 방지한다', 'SERIALIZABLE은 트랜잭션을 순차적으로 실행하는 것처럼 동작하여 Dirty Read, Non-Repeatable Read, Phantom Read를 모두 방지한다. 가장 안전하지만 성능이 가장 낮다.', true),
(3177, 1116, 'Phantom Read를 방지하지 못한다', 'SERIALIZABLE은 Phantom Read를 방지한다.', false),
(3178, 1116, '가장 빠른 성능을 제공한다', 'READ UNCOMMITTED가 가장 빠르다.', false),

-- 문제 1117
(3179, 1117, '커밋되지 않은 데이터를 읽는 현상', 'Dirty Read의 설명이다.', false),
(3180, 1117, '같은 데이터를 두 번 읽었을 때 값이 변경된 현상', 'Non-Repeatable Read의 설명이다.', false),
(3181, 1117, '같은 쿼리를 두 번 실행했을 때 없던 행이 나타나거나 있던 행이 사라지는 현상', 'Phantom Read는 한 트랜잭션 내에서 같은 쿼리를 실행했을 때 다른 트랜잭션의 INSERT나 DELETE로 인해 결과 집합이 달라지는 현상이다.', true),
(3182, 1117, '데드락이 발생하는 현상', 'Phantom Read와 데드락은 다른 개념이다.', false);

INSERT INTO answer (id, problem_id, content, explanation)
VALUES (319, 1112, 'Dirty Read, 더티 리드', '커밋되지 않은 데이터를 다른 트랜잭션이 읽는 현상을 Dirty Read라고 한다. 해당 트랜잭션이 롤백되면 잘못된 데이터를 읽게 된다.'),
(320, 1115, 'Non-Repeatable Read, 반복 불가능한 읽기, nonrepatable read', '같은 쿼리를 두 번 실행했을 때 다른 트랜잭션의 수정으로 인해 결과가 달라지는 현상을 Non-Repeatable Read라고 한다.');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# List all SQL files in the problem/database directory
find src/main/resources/sql/problem/database -type f -name "*.sql" | head -20

Repository: Team-Gravit/gravit-server

Length of output: 852


🏁 Script executed:

# Check the structure of other unit files to understand ID patterns
fd -e sql src/main/resources/sql/problem/database | sort

Repository: Team-Gravit/gravit-server

Length of output: 530


🏁 Script executed:

# Search for problem IDs in the 1097-1117 range in all files
rg "INSERT INTO problem.*1097|1098|1099|110[0-9]|111[0-7]" src/main/resources/sql/problem/database/ -c

Repository: Team-Gravit/gravit-server

Length of output: 127


🏁 Script executed:

# Search for option IDs in the 3123-3182 range in all files
rg "INSERT INTO option.*312[3-9]|313[0-9]|314[0-9]|315[0-9]|316[0-9]|317[0-9]|318[0-2]" src/main/resources/sql/problem/database/ -c

Repository: Team-Gravit/gravit-server

Length of output: 127


🏁 Script executed:

# Search for answer IDs in the 315-320 range in all files
rg "INSERT INTO answer.*31[5-9]|320" src/main/resources/sql/problem/database/ -c

Repository: Team-Gravit/gravit-server

Length of output: 184


🏁 Script executed:

# Check what answer IDs are actually used in unit14_인덱스.sql
rg "INSERT INTO answer" src/main/resources/sql/problem/database/unit14_인덱스.sql -A 3

Repository: Team-Gravit/gravit-server

Length of output: 882


🏁 Script executed:

# Check what answer IDs are in unit13_트랜잭션.sql
rg "INSERT INTO answer" src/main/resources/sql/problem/database/unit13_트랜잭션.sql -A 3

Repository: Team-Gravit/gravit-server

Length of output: 934


🏁 Script executed:

# Get all answer IDs across all unit files to understand the pattern
rg "INSERT INTO answer.*id.*VALUES" src/main/resources/sql/problem/database/ | head -30

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Check if unit_id 54 is mentioned in any existing SQL files
rg "unit_id.*54" src/main/resources/sql/problem/database/ | head -10

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

# Get a sample of other unit files to see their ID ranges
head -50 src/main/resources/sql/problem/database/unit14_인덱스.sql

Repository: Team-Gravit/gravit-server

Length of output: 2905


하드코딩된 ID 범위의 연속성 확인 필요

이 파일의 ID들(문제: 1097-1117, 옵션: 3123-3182, 답변: 315-320)은 다른 단원 파일들과의 순차적 연관성이 있습니다. 다음 단원(unit14_인덱스.sql)이 답변 ID 321-326을 사용하므로 이 파일의 데이터가 먼저 적용되어야 합니다. 또한 unit_id=54가 데이터베이스에 미리 존재해야 하며, 이 스크립트가 안전하게 재실행될 수 있도록 멱등성(idempotence)을 고려하거나 트랜잭션으로 감싸서 원자성(atomicity)을 보장하는 것이 좋습니다.

🤖 Prompt for AI Agents
In @src/main/resources/sql/problem/database/unit13_트랜잭션.sql around lines 1 -
150, The script hardcodes specific ID ranges (problem IDs 1097–1117, option IDs
3123–3182, answer IDs 315–320) and assumes unit_id=54 exists and that execution
order with unit14 relies on these IDs; make the script idempotent and atomic by
first ensuring unit 54 exists (INSERT ... ON CONFLICT/IF NOT EXISTS or a SELECT
check), avoid fixed IDs by using sequences or by computing next IDs (or use
INSERT ... RETURNING to link generated IDs), or change INSERTs to upsert
semantics (INSERT ... ON CONFLICT DO NOTHING/UPDATE or MERGE) for
lesson/problem/option/answer, and wrap the whole set of inserts in a transaction
so they apply atomically to prevent partial application and ID collisions with
other unit scripts.

Comment on lines +3 to +15
-- Lesson 1: 인덱스 기초 (ID: 161)
INSERT INTO lesson (id, title, unit_id)
VALUES (161, '인덱스 기초', 55);

INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
VALUES (1118, 161, '다음 중 인덱스(Index)에 대한 설명으로 올바른 것은?', '인덱스는 데이터 검색 속도를 향상시키는 데이터베이스 객체이다.', 'OBJECTIVE'),
(1119, 161, '빈칸에 들어갈 키워드를 작성하시오.', '___ INDEX idx_name ON students(name); -- 인덱스 생성', 'SUBJECTIVE'),
(1120, 161, '다음 중 인덱스의 장점으로 올바르지 않은 것은?', '인덱스는 조회 성능을 향상시키지만 단점도 있다.', 'OBJECTIVE'),
(1121, 161, '다음 SQL 명령어의 역할은?', 'CREATE INDEX idx_student_name ON students(name);', 'OBJECTIVE'),
(1122, 161, '빈칸에 들어갈 용어를 작성하시오.', '인덱스는 책의 ___와 같은 역할을 한다.', 'SUBJECTIVE'),
(1123, 161, '다음 중 인덱스가 필요한 경우로 올바른 것은?', '인덱스는 적절한 상황에서 사용해야 효과적이다.', 'OBJECTIVE'),
(1124, 161, '다음 중 인덱스의 단점으로 올바른 것은?', '인덱스는 장점과 함께 단점도 고려해야 한다.', 'OBJECTIVE');

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all SQL files in the database seed directory
fd -type f -name "*.sql" src/main/resources/sql/problem/database/ 2>/dev/null | head -20

Repository: Team-Gravit/gravit-server

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Run the duplicate detection script provided in the review
set -euo pipefail

ROOT="src/main/resources/sql/problem/database"

# First, verify directory exists and list files
echo "=== SQL files in $ROOT ==="
fd -e sql . "$ROOT" 2>/dev/null | sort

echo ""
echo "=== Checking for duplicate IDs ==="

python3 <<'PY'
import re
import sys
from pathlib import Path

root = Path("src/main/resources/sql/problem/database")
files = sorted(root.glob("*.sql"))

# Pattern to match INSERT INTO <table> (...) VALUES (...);
pat = re.compile(r"INSERT\s+INTO\s+(lesson|problem|option|answer)\b.*?\bVALUES\s*(.+?);", re.I|re.S)
# Pattern to extract first value (id) from each tuple
tuple_pat = re.compile(r"\(\s*([0-9]+)\s*,")

seen = {t: {} for t in ["lesson", "problem", "option", "answer"]}
dups = []

for f in files:
    try:
        txt = f.read_text(encoding="utf-8")
    except:
        continue
    
    for m in pat.finditer(txt):
        table = m.group(1).lower()
        values = m.group(2)
        for tid in tuple_pat.findall(values):
            tid = int(tid)
            if tid in seen[table]:
                dups.append((table, tid, str(seen[table][tid]), str(f.name)))
            else:
                seen[table][tid] = f.name

# Print summary
print(f"Scanned files: {len(files)}")
print()

for table in ["lesson", "problem", "option", "answer"]:
    if seen[table]:
        min_id = min(seen[table].keys())
        max_id = max(seen[table].keys())
        count = len(seen[table])
        print(f"{table}: {count} IDs (range {min_id}-{max_id})")
        if table == "lesson":
            print(f"  - lesson IDs: {sorted(seen[table].keys())}")

print()
if dups:
    print("DUPLICATES FOUND:")
    for table, tid, f1, f2 in sorted(dups):
        print(f"- {table}.id={tid}: {f1} <-> {f2}")
else:
    print("No duplicate IDs found across all files")

PY

Repository: Team-Gravit/gravit-server

Length of output: 155


🏁 Script executed:

#!/bin/bash
# List SQL files in the database directory
ls -la src/main/resources/sql/problem/database/ 2>/dev/null | head -30

Repository: Team-Gravit/gravit-server

Length of output: 1107


🏁 Script executed:

#!/bin/bash
# Read the specific file under review to see its full content
wc -l src/main/resources/sql/problem/database/unit14_인덱스.sql
head -100 src/main/resources/sql/problem/database/unit14_인덱스.sql

Repository: Team-Gravit/gravit-server

Length of output: 5907


🏁 Script executed:

#!/bin/bash
# Search for all problem IDs in the database directory to check for conflicts
echo "=== All problem IDs in database seed files ==="
grep -h "INSERT INTO problem" src/main/resources/sql/problem/database/*.sql 2>/dev/null | grep -oP '\(([0-9]+),' | head -50

Repository: Team-Gravit/gravit-server

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Check for lesson ID ranges across all files
echo "=== Lesson IDs across all files ==="
grep -h "INSERT INTO lesson" src/main/resources/sql/problem/database/*.sql 2>/dev/null

Repository: Team-Gravit/gravit-server

Length of output: 1784


🏁 Script executed:

#!/bin/bash
# Extract actual lesson and problem IDs from all unit files
echo "=== Lesson IDs by file ==="
for f in src/main/resources/sql/problem/database/unit*.sql; do
  echo "File: $(basename "$f")"
  grep -oP "INSERT INTO lesson.*?VALUES\s*\(\K[0-9]+" "$f" 2>/dev/null | tr '\n' ',' | sed 's/,$//'
  echo
done

Repository: Team-Gravit/gravit-server

Length of output: 417


🏁 Script executed:

#!/bin/bash
# Extract problem ID ranges from each unit file
echo "=== Problem ID ranges by file ==="
for f in src/main/resources/sql/problem/database/unit*.sql; do
  echo "File: $(basename "$f")"
  problem_ids=$(grep -oP "INSERT INTO problem.*?VALUES\s*\([0-9]+\)" "$f" 2>/dev/null | grep -oP '\(\K[0-9]+' | tr '\n' ',' | sed 's/,$//')
  if [ -n "$problem_ids" ]; then
    min=$(echo "$problem_ids" | tr ',' '\n' | sort -n | head -1)
    max=$(echo "$problem_ids" | tr ',' '\n' | sort -n | tail -1)
    count=$(echo "$problem_ids" | tr ',' '\n' | wc -l)
    echo "  Range: $min - $max (Count: $count)"
  fi
done

Repository: Team-Gravit/gravit-server

Length of output: 410


🏁 Script executed:

#!/bin/bash
# Check for any ID overlaps across all files
echo "=== Checking for lesson ID duplicates ==="
python3 <<'PY'
from pathlib import Path
import re

root = Path("src/main/resources/sql/problem/database")
lesson_ids = {}
problem_ids = {}

for f in sorted(root.glob("unit*.sql")):
    txt = f.read_text(encoding="utf-8")
    
    # Extract lesson IDs
    lesson_matches = re.findall(r"INSERT INTO lesson \(id.*?\bVALUES\s*\((\d+),", txt)
    for lid in lesson_matches:
        lid = int(lid)
        if lid in lesson_ids:
            print(f"DUPLICATE lesson.id={lid}: {lesson_ids[lid]} and {f.name}")
        else:
            lesson_ids[lid] = f.name
    
    # Extract problem IDs
    problem_matches = re.findall(r"INSERT INTO problem \(id.*?\bVALUES\s*(.*?);\n", txt, re.S)
    for vals in problem_matches:
        pids = re.findall(r"\((\d+),", vals)
        for pid in pids:
            pid = int(pid)
            if pid in problem_ids:
                print(f"DUPLICATE problem.id={pid}: {problem_ids[pid]} and {f.name}")
            else:
                problem_ids[pid] = f.name

print("\nNo duplicates found!" if len(lesson_ids) + len(problem_ids) > 0 else "")
print(f"Total unique lesson IDs: {len(lesson_ids)}")
print(f"Total unique problem IDs: {len(problem_ids)}")
PY

Repository: Team-Gravit/gravit-server

Length of output: 248


🏁 Script executed:

#!/bin/bash
# Check if there's any idempotency mechanism (DELETE/TRUNCATE before INSERT) or ID collision handling
echo "=== Checking for idempotency patterns ==="
grep -l "DELETE\|TRUNCATE\|DROP\|ON CONFLICT\|EXISTS" src/main/resources/sql/problem/database/*.sql
echo "Exit code: $?"

Repository: Team-Gravit/gravit-server

Length of output: 622


🏁 Script executed:

#!/bin/bash
# Verify the duplicate problem.id=1 found
echo "=== Duplicate problem.id=1 ==="
echo "From unit07_DML.sql:"
grep -A2 "INSERT INTO problem" src/main/resources/sql/problem/database/unit07_DML.sql | head -5

echo ""
echo "From unit13_트랜잭션.sql:"
grep -A2 "INSERT INTO problem" src/main/resources/sql/problem/database/unit13_트랜잭션.sql | head -5

Repository: Team-Gravit/gravit-server

Length of output: 854


🏁 Script executed:

#!/bin/bash
# Check the specific file under review (unit14_인덱스.sql) for its problem IDs
echo "=== Problem IDs in unit14_인덱스.sql ==="
grep -oP "INSERT INTO problem.*?\(\K[0-9]+" src/main/resources/sql/problem/database/unit14_인덱스.sql | sort -n | tr '\n' ',' | sed 's/,$//'
echo ""

echo "=== Answer IDs in unit14_인덱스.sql ==="
grep -oP "INSERT INTO answer.*?\(\K[0-9]+" src/main/resources/sql/problem/database/unit14_인덱스.sql | sort -n | tr '\n' ',' | sed 's/,$//'
echo ""

echo "=== Option IDs in unit14_인덱스.sql ==="
grep -oP "INSERT INTO option.*?\(\K[0-9]+" src/main/resources/sql/problem/database/unit14_인덱스.sql | sort -n | tr '\n' ',' | sed 's/,$//'
echo ""

Repository: Team-Gravit/gravit-server

Length of output: 183


🏁 Script executed:

#!/bin/bash
# Check all answer and option IDs across files for duplicates
echo "=== Checking for answer ID duplicates ==="
python3 <<'PY'
from pathlib import Path
import re

root = Path("src/main/resources/sql/problem/database")
answer_ids = {}
option_ids = {}

for f in sorted(root.glob("unit*.sql")):
    txt = f.read_text(encoding="utf-8")
    
    # Extract answer IDs
    answer_matches = re.findall(r"INSERT INTO answer.*?\((\d+),", txt)
    for aid in answer_matches:
        aid = int(aid)
        if aid in answer_ids:
            print(f"DUPLICATE answer.id={aid}: {answer_ids[aid]} and {f.name}")
        else:
            answer_ids[aid] = f.name
    
    # Extract option IDs
    option_matches = re.findall(r"INSERT INTO option.*?\((\d+),", txt)
    for oid in option_matches:
        oid = int(oid)
        if oid in option_ids:
            print(f"DUPLICATE option.id={oid}: {option_ids[oid]} and {f.name}")
        else:
            option_ids[oid] = f.name

print(f"\nTotal unique answer IDs: {len(answer_ids)}")
print(f"Total unique option IDs: {len(option_ids)}")
PY

Repository: Team-Gravit/gravit-server

Length of output: 165


🏁 Script executed:

#!/bin/bash
# Read unit14_인덱스.sql directly and extract all IDs
echo "=== Extracting IDs from unit14_인덱스.sql ==="
python3 <<'PY'
from pathlib import Path

f = Path("src/main/resources/sql/problem/database/unit14_인덱스.sql")
content = f.read_text(encoding="utf-8")

# Extract problem IDs
import re
problem_ids = re.findall(r"VALUES \((\d+), 16[0-9],", content)
print(f"Problem IDs: {sorted(set(int(x) for x in problem_ids))}")

# Extract option IDs
option_ids = re.findall(r"^\(\s*(\d+),\s*1[0-9]{3},", content, re.MULTILINE)
print(f"Option IDs: {sorted(set(int(x) for x in option_ids))}")

# Extract answer IDs
answer_ids = re.findall(r"^\s*\((\d+),\s*1[0-9]{3},", content, re.MULTILINE)
print(f"Answer IDs: {sorted(set(int(x) for x in answer_ids))}")

# Extract lesson IDs
lesson_ids = re.findall(r"VALUES \((\d+),", content)
print(f"Lesson IDs: {sorted(set(int(x) for x in lesson_ids[:10]))}")  # First 10
PY

Repository: Team-Gravit/gravit-server

Length of output: 965


🏁 Script executed:

#!/bin/bash
# Check if the file has any DELETE, ON CONFLICT, or idempotency patterns
echo "=== Checking unit14_인덱스.sql for idempotency ==="
grep -i "DELETE\|TRUNCATE\|DROP\|ON CONFLICT\|IF EXISTS" src/main/resources/sql/problem/database/unit14_인덱스.sql
echo "Result: $? (0=found, 1=not found)"

Repository: Team-Gravit/gravit-server

Length of output: 463


🏁 Script executed:

#!/bin/bash
# Get complete view of lesson IDs from all files
echo "=== All Lesson IDs across database files ==="
python3 <<'PY'
from pathlib import Path
import re

root = Path("src/main/resources/sql/problem/database")
all_lessons = {}

for f in sorted(root.glob("unit*.sql")):
    txt = f.read_text(encoding="utf-8")
    # Match VALUES (123, 'title', 55);
    matches = re.findall(r"INSERT INTO lesson.*?VALUES\s*\((\d+),\s*'([^']+)',", txt)
    for lid, title in matches:
        lid = int(lid)
        if lid in all_lessons:
            print(f"DUPLICATE lesson.id={lid}: {all_lessons[lid][1]} and {f.name}")
        else:
            all_lessons[lid] = (f.name, title)

if all_lessons:
    print("\nLessons by unit:")
    for f in sorted(root.glob("unit*.sql")):
        fname = f.name
        lessons = [(lid, title) for lid, (fname2, title) in all_lessons.items() if fname2 == fname]
        if lessons:
            print(f"{fname}: {sorted(lessons)}")
PY

Repository: Team-Gravit/gravit-server

Length of output: 113


🏁 Script executed:

#!/bin/bash
# More precise check for all ID duplicates across all files
echo "=== Complete ID duplicate check ==="
python3 <<'PY'
from pathlib import Path
import re

root = Path("src/main/resources/sql/problem/database")

lesson_ids = {}
problem_ids = {}
option_ids = {}
answer_ids = {}

for f in sorted(root.glob("unit*.sql")):
    txt = f.read_text(encoding="utf-8")
    
    # Extract lesson IDs: INSERT INTO lesson (id, ...) VALUES (123, ...)
    lesson_matches = re.findall(r"INSERT INTO lesson \(id,.*?\)\s*\n\s*VALUES\s*\((\d+),", txt)
    for lid in lesson_matches:
        lid = int(lid)
        if lid in lesson_ids:
            print(f"DUPLICATE lesson.id={lid}: {lesson_ids[lid]} <-> {f.name}")
        else:
            lesson_ids[lid] = f.name
    
    # Extract problem IDs from all VALUES tuples
    problem_section = re.search(r"INSERT INTO problem.*?VALUES\s*(.+?);\n", txt, re.S)
    if problem_section:
        pids = re.findall(r"\(\s*(\d+)\s*,\s*\d+\s*,", problem_section.group(1))
        for pid in pids:
            pid = int(pid)
            if pid in problem_ids:
                print(f"DUPLICATE problem.id={pid}: {problem_ids[pid]} <-> {f.name}")
            else:
                problem_ids[pid] = f.name
    
    # Extract option IDs
    option_section = re.search(r"INSERT INTO option.*?VALUES\s*(.+?);\n", txt, re.S)
    if option_section:
        oids = re.findall(r"\(\s*(\d+)\s*,\s*\d+\s*,", option_section.group(1))
        for oid in oids:
            oid = int(oid)
            if oid in option_ids:
                print(f"DUPLICATE option.id={oid}: {option_ids[oid]} <-> {f.name}")
            else:
                option_ids[oid] = f.name
    
    # Extract answer IDs
    answer_section = re.search(r"INSERT INTO answer.*?VALUES\s*(.+?);", txt, re.S)
    if answer_section:
        aids = re.findall(r"\(\s*(\d+)\s*,\s*\d+\s*,", answer_section.group(1))
        for aid in aids:
            aid = int(aid)
            if aid in answer_ids:
                print(f"DUPLICATE answer.id={aid}: {answer_ids[aid]} <-> {f.name}")
            else:
                answer_ids[aid] = f.name

print(f"\n=== Summary ===")
print(f"Total unique lesson IDs: {len(lesson_ids)}")
print(f"Total unique problem IDs: {len(problem_ids)}")
print(f"Total unique option IDs: {len(option_ids)}")
print(f"Total unique answer IDs: {len(answer_ids)}")
print(f"\nunit14_인덱스.sql uses:")
print(f"  Lesson: {sorted([k for k,v in lesson_ids.items() if v == 'unit14_인덱스.sql'])}")
print(f"  Problem: {sorted([k for k,v in problem_ids.items() if v == 'unit14_인덱스.sql'])}")
print(f"  Option: {len([k for k,v in option_ids.items() if v == 'unit14_인덱스.sql'])} options")
print(f"  Answer: {len([k for k,v in answer_ids.items() if v == 'unit14_인덱스.sql'])} answers")
PY

Repository: Team-Gravit/gravit-server

Length of output: 378


🏁 Script executed:

#!/bin/bash
# Check if DB uses special handling for "option" table name (reserved keyword)
echo "=== Checking table schema and quotation ==="
find . -name "*.sql" -path "*/schema*" -o -name "*schema*" -o -name "*ddl*" 2>/dev/null | grep -i schema | head -5

# Also check if option table is quoted anywhere
grep -r '`option\|"option\|\[option' src/main/resources/sql/ 2>/dev/null | head -3

Repository: Team-Gravit/gravit-server

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Check how other unit files handle re-runs (do they have DELETE before INSERT?)
echo "=== Checking for DELETE/truncate patterns in other files ==="
for f in src/main/resources/sql/problem/database/unit0[1-5]*.sql; do
  echo "File: $(basename "$f")"
  if grep -q "DELETE FROM\|TRUNCATE" "$f"; then
    grep "DELETE FROM\|TRUNCATE" "$f" | head -2
  else
    echo "  (no DELETE/TRUNCATE found)"
  fi
done

Repository: Team-Gravit/gravit-server

Length of output: 394


🏁 Script executed:

#!/bin/bash
# Check if there's a setup/cleanup pattern used elsewhere
echo "=== Checking project structure for migration/seed management ==="
find . -name "*.sql" -path "*/migration*" -o -path "*/seed*" 2>/dev/null | head -10
ls -la src/main/resources/sql/ 2>/dev/null

Repository: Team-Gravit/gravit-server

Length of output: 861


🏁 Script executed:

#!/bin/bash
# Check the migration files to see table structure and any constraints
echo "=== Checking migration V1 for table definitions ==="
head -150 src/main/resources/db/migration/V1__init_tables.sql

Repository: Team-Gravit/gravit-server

Length of output: 4688


🏁 Script executed:

#!/bin/bash
# Check if "option" is quoted in the migration
echo "=== Checking if 'option' table is quoted in migrations ==="
grep -n "option\|CREATE TABLE" src/main/resources/db/migration/V1__init_tables.sql | head -20

Repository: Team-Gravit/gravit-server

Length of output: 650


🏁 Script executed:

#!/bin/bash
# Get the complete option table definition
echo "=== Option table definition ==="
grep -A 12 "^CREATE TABLE option" src/main/resources/db/migration/V1__init_tables.sql

Repository: Team-Gravit/gravit-server

Length of output: 400


🏁 Script executed:

#!/bin/bash
# Check if the project has any documentation about ID allocation
echo "=== Searching for ID allocation documentation ==="
find . -name "README*" -o -name "*.md" 2>/dev/null | xargs grep -l "id\|seed" 2>/dev/null | head -5

Repository: Team-Gravit/gravit-server

Length of output: 188


🏁 Script executed:

#!/bin/bash
# Final verification: check if the problem/lesson/option/answer IDs in unit14 overlap with any other files
echo "=== Checking for actual ID conflicts in unit14_인덱스.sql ==="
python3 <<'PY'
from pathlib import Path
import re

root = Path("src/main/resources/sql/problem/database")
unit14_file = root / "unit14_인덱스.sql"
txt14 = unit14_file.read_text(encoding="utf-8")

# Extract IDs from unit14
lesson_ids_14 = set(int(x) for x in re.findall(r"INSERT INTO lesson \(id,.*?\)\s*\n\s*VALUES\s*\((\d+),", txt14))
problem_ids_14 = set(int(x) for x in re.findall(r"VALUES\s*\((\d+),\s*16[0-3],", txt14))
option_ids_14 = set(int(x) for x in re.findall(r"\((\d+),\s*11[0-9]{2},", txt14))
answer_ids_14 = set(int(x) for x in re.findall(r"VALUES\s*\((\d+),\s*11[0-9]{2},", txt14))

print("unit14_인덱스.sql IDs:")
print(f"  Lesson: {sorted(lesson_ids_14)}")
print(f"  Problem: {sorted(problem_ids_14)}")
print(f"  Option: count={len(option_ids_14)}, range {min(option_ids_14)}-{max(option_ids_14) if option_ids_14 else 'none'}")
print(f"  Answer: {sorted(answer_ids_14)}")

# Check against other files
other_files = [f for f in root.glob("unit*.sql") if f.name != "unit14_인덱스.sql"]
overlaps = {"lesson": [], "problem": [], "option": [], "answer": []}

for f in other_files:
    txt = f.read_text(encoding="utf-8")
    
    lesson_matches = set(int(x) for x in re.findall(r"INSERT INTO lesson \(id,.*?\)\s*\n\s*VALUES\s*\((\d+),", txt))
    problem_matches = set(int(x) for x in re.findall(r"INSERT INTO problem.*?\)\s*\n\s*VALUES\s*(.+?);\n", txt, re.S))
    problem_ids_other = set()
    for vals in problem_matches:
        problem_ids_other.update(int(x) for x in re.findall(r"\(\s*(\d+)\s*,", vals))
    
    if lesson_ids_14 & lesson_matches:
        overlaps["lesson"].append((f.name, lesson_ids_14 & lesson_matches))
    if problem_ids_14 & problem_ids_other:
        overlaps["problem"].append((f.name, problem_ids_14 & problem_ids_other))

if any(overlaps.values()):
    print("\nOVERLAPS FOUND:")
    for table, conflicts in overlaps.items():
        if conflicts:
            print(f"  {table}: {conflicts}")
else:
    print("\nNo ID overlaps found with other unit files")

PY

Repository: Team-Gravit/gravit-server

Length of output: 648


🏁 Script executed:

#!/bin/bash
# Simpler final check - just verify the ranges without complex parsing
echo "=== Final ID range verification ==="
cat src/main/resources/sql/problem/database/unit14_인덱스.sql | head -50 | tail -20
echo "---"
cat src/main/resources/sql/problem/database/unit14_인덱스.sql | grep -A 2 "INSERT INTO answer"

Repository: Team-Gravit/gravit-server

Length of output: 2102


하드코딩된 ID 사용으로 인한 멱등성 부재 문제를 개선하세요.

현재 lesson.id(161~163) / problem.id(1118~1132) / answer.id(321~326) 등을 고정 값으로 삽입하고 있으며, 데이터 재적용이나 브랜치 병합 시 PK 충돌로 실패합니다. 현재 데이터 범위 내 중복은 없으나, 다음 개선이 필요합니다:

  • 멱등성 확보: 재실행 안전성을 위해 DELETE ... WHERE ... 또는 INSERT ... ON CONFLICT DO UPDATE 패턴 도입
  • ID 범위 정책 문서화: 유닛별 ID 범위 할당 규칙을 명시하여 향후 데이터 추가 시 충돌 방지
  • 현재 구조로는 시드 재적용 시 반드시 실패하므로 운영/테스트 환경에서 문제 발생
🤖 Prompt for AI Agents
In @src/main/resources/sql/problem/database/unit14_인덱스.sql around lines 3 - 15,
The seed SQL uses hardcoded PKs (e.g., INSERT INTO lesson with id 161 and INSERT
INTO problem with ids 1118–1124) which breaks idempotency and causes PK
conflicts on reapply; modify the inserts to be idempotent by either deleting
existing rows before insert (DELETE FROM lesson WHERE id = 161; DELETE FROM
problem WHERE id IN (1118,...)) or convert to upsert semantics (INSERT ... ON
CONFLICT (id) DO UPDATE ...) for the lesson and problem inserts, and add a short
comment block documenting the per-unit ID ranges policy to avoid future
collisions.

Comment on lines +16 to +47
INSERT INTO option (id, problem_id, content, explanation, is_answer)
VALUES
-- 문제 1118
(3183, 1118, '인덱스는 데이터를 물리적으로 저장한다', '인덱스는 데이터의 위치 정보를 저장한다.', false),
(3184, 1118, '인덱스는 검색 속도를 향상시키기 위한 별도의 자료구조이다', '인덱스는 테이블의 검색 속도를 높이기 위해 컬럼 값과 해당 레코드의 주소를 키-값 쌍으로 저장하는 자료구조이다.', true),
(3185, 1118, '인덱스는 모든 쿼리의 성능을 향상시킨다', '인덱스는 조회 성능은 향상시키지만 삽입, 수정, 삭제 성능은 저하시킬 수 있다.', false),
(3186, 1118, '인덱스는 테이블과 동일한 개념이다', '인덱스와 테이블은 서로 다른 객체이다.', false),

-- 문제 1120
(3187, 1120, '검색 속도가 향상된다', '인덱스의 주요 장점이다.', false),
(3188, 1120, 'WHERE 절의 조회 성능이 개선된다', '인덱스는 조건 검색 성능을 향상시킨다.', false),
(3189, 1120, 'ORDER BY 성능이 향상된다', '인덱스는 정렬된 상태로 저장되어 ORDER BY 성능이 향상된다.', false),
(3190, 1120, '데이터 삽입 속도가 향상된다', '인덱스는 삽입 시 인덱스도 업데이트해야 하므로 삽입 속도가 저하된다. 이것은 단점이다.', true),

-- 문제 1121
(3191, 1121, 'students 테이블의 name 컬럼에 인덱스를 생성한다', 'CREATE INDEX로 name 컬럼에 idx_student_name이라는 인덱스를 생성한다. 이름으로 검색하는 쿼리의 성능이 향상된다.', true),
(3192, 1121, 'students 테이블을 삭제한다', 'DROP TABLE이 테이블을 삭제한다.', false),
(3193, 1121, 'name 컬럼을 삭제한다', 'ALTER TABLE ... DROP COLUMN이 컬럼을 삭제한다.', false),
(3194, 1121, 'students 테이블의 데이터를 조회한다', 'SELECT가 데이터를 조회한다.', false),

-- 문제 1123
(3195, 1123, '테이블의 데이터가 매우 적을 때', '데이터가 적으면 인덱스 효과가 미미하다.', false),
(3196, 1123, 'WHERE 절에서 자주 사용되는 컬럼일 때', 'WHERE 절에서 자주 조회되는 컬럼에 인덱스를 생성하면 검색 성능이 크게 향상된다.', true),
(3197, 1123, '데이터 수정이 매우 빈번한 컬럼일 때', '수정이 빈번하면 인덱스 유지 비용이 크다.', false),
(3198, 1123, '모든 컬럼에 인덱스를 생성할 때', '불필요한 인덱스는 성능을 저하시킨다.', false),

-- 문제 1124
(3199, 1124, '조회 성능이 향상된다', '이것은 장점이다.', false),
(3200, 1124, '추가 저장 공간이 필요하다', '인덱스는 별도의 저장 공간을 차지한다. 테이블 크기의 약 10% 정도의 추가 공간이 필요하다.', true),
(3201, 1124, '정렬 성능이 향상된다', '이것은 장점이다.', false),
(3202, 1124, '검색 속도가 빨라진다', '이것은 장점이다.', false);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# DDL 파일 찾기
find . -type f \( -name "*.sql" -o -name "*.ddl" \) | head -20

Repository: Team-Gravit/gravit-server

Length of output: 1253


🏁 Script executed:

#!/bin/bash
# option 테이블 생성 DDL 검색
rg -n "CREATE\s+TABLE\s+" --type sql | grep -i option
rg -n "CREATE\s+TABLE" --type sql -A 5 | grep -B 5 -i "option"

Repository: Team-Gravit/gravit-server

Length of output: 616


🏁 Script executed:

#!/bin/bash
# option 테이블이 quoted/unquoted로 어떻게 사용되는지 확인
rg -n "option" . --type sql -C 2 | head -100

Repository: Team-Gravit/gravit-server

Length of output: 6989


🏁 Script executed:

#!/bin/bash
# V1__init_tables.sql에서 CREATE TABLE option 주변 내용 확인
sed -n '150,175p' ./src/main/resources/db/migration/V1__init_tables.sql

Repository: Team-Gravit/gravit-server

Length of output: 873


🏁 Script executed:

#!/bin/bash
# option 테이블이 quoted되어 있는지 확인
cat ./src/main/resources/db/migration/V1__init_tables.sql | grep -i "option"

Repository: Team-Gravit/gravit-server

Length of output: 104


🌐 Web query:

Is "option" a SQL reserved keyword in MySQL, PostgreSQL, H2 database

💡 Result:

Short answer:

  • MySQL: Yes — OPTION is a reserved keyword. [1]
  • PostgreSQL: No — OPTION is listed as non-reserved. [2]
  • H2: No — OPTION is not in H2's keyword list (not treated as a reserved keyword). [3][4]

Sources:
[1] MySQL Keywords and Reserved Words (shows OPTION (R)).
[2] PostgreSQL SQL Key Words appendix (shows OPTION as non-reserved).
[3] H2 documentation (keywords/commands, SET NON_KEYWORDS explanation).
[4] H2 discussion / docs listing H2 keywords (example keyword list that does not include OPTION).


MySQL 호환성을 위해 option 테이블명 쿼팅 또는 변경이 필요합니다.

option은 MySQL의 예약 키워드이므로, 현재 코드의 unquoted 테이블명 사용 시 MySQL에서 SQL 구문 오류가 발생합니다. 다음 중 하나로 해결하세요:

  • 테이블명을 쿼팅: `option` (MySQL) 또는 "option" (표준 SQL)
  • 테이블명 변경: problem_option 등 예약어가 아닌 이름으로 변경
  • DDL(V1__init_tables.sql)과 모든 DML(INSERT INTO, SELECT 등)에서 일관되게 적용

참고: PostgreSQL, H2는 option을 예약어로 취급하지 않아 현재 상태에서도 동작합니다.

🤖 Prompt for AI Agents
In @src/main/resources/sql/problem/database/unit14_인덱스.sql around lines 16 - 47,
The SQL uses the unquoted reserved word table name "option" causing MySQL syntax
errors; either quote the table name or rename it consistently across schema and
data files. Update the DDL (V1__init_tables.sql) and all DML (e.g., the INSERT
INTO option (...) VALUES ... statements in unit14_인덱스.sql) plus any code/queries
that reference the table to use a safe identifier (e.g., `problem_option`
everywhere) or consistently quote it for MySQL (`` `option` ``) so MySQL,
PostgreSQL, H2 all use the same identifier.

Comment on lines +107 to +115
INSERT INTO problem (id, lesson_id, instruction, content, problem_type)
VALUES (1132, 163, '다음 중 인덱스가 사용되지 않는 경우는?', '특정 상황에서는 인덱스가 무시될 수 있다.', 'OBJECTIVE'),
(1133, 163, '빈칸에 들어갈 키워드를 작성하시오.', '___ INDEX idx_name; -- 인덱스 삭제', 'SUBJECTIVE'),
(1134, 163, '다음 중 복합 인덱스 사용 시 주의사항으로 올바른 것은?', '복합 인덱스는 컬럼 순서가 중요하다.', 'OBJECTIVE'),
(1135, 163, '다음 SQL 쿼리에서 인덱스가 사용되지 않는 이유는?', 'CREATE INDEX idx_name ON students(name);\nSELECT * FROM students WHERE UPPER(name) = ''홍길동'';', 'OBJECTIVE'),
(1136, 163, '빈칸에 들어갈 용어를 작성하시오.', '인덱스를 사용하지 않고 테이블 전체를 읽는 것을 ___라고 한다.', 'SUBJECTIVE'),
(1137, 163, '다음 중 인덱스 성능을 저하시키는 요인으로 올바른 것은?', '인덱스도 관리가 필요하다.', 'OBJECTIVE'),
(1138, 163, '다음 중 인덱스 설계 시 고려사항으로 올바르지 않은 것은?', '인덱스는 신중하게 설계해야 한다.', 'OBJECTIVE');

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

문제 1135의 \n은 실제 개행이 아니라 문자열로 저장될 가능성이 높습니다.
problem.content\n을 넣으면 DB에 따라 백슬래시+n 그대로 저장되어 화면에 \n이 노출될 수 있어요. “실제 개행”을 원하면 문자열 안에 실제 줄바꿈을 넣거나(DB가 허용하는 경우), DB별 방식으로 개행을 표현하는 편이 안전합니다.

제안 diff (실제 줄바꿈으로 저장)
-       (1135, 163, '다음 SQL 쿼리에서 인덱스가 사용되지 않는 이유는?', 'CREATE INDEX idx_name ON students(name);\nSELECT * FROM students WHERE UPPER(name) = ''홍길동'';', 'OBJECTIVE'),
+       (1135, 163, '다음 SQL 쿼리에서 인덱스가 사용되지 않는 이유는?', 'CREATE INDEX idx_name ON students(name);
+SELECT * FROM students WHERE UPPER(name) = ''홍길동'';', 'OBJECTIVE'),

Also applies to: 130-135

🤖 Prompt for AI Agents
In @src/main/resources/sql/problem/database/unit14_인덱스.sql around lines 107 -
115, Problem entry id 1135 stores a literal "\n" in its VALUES string instead of
a real newline; update the VALUES tuple for problem id 1135 to use an actual
line break inside the 'content' string (or use your DB's newline-escape syntax,
e.g., the DB-specific E'...\n...' form) so the content renders with a real
newline, and apply the same fix to the other affected INSERT rows that contain
literal "\n".

- 문자열 앞뒤 공백 제거
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/resources/sql/problem/database/unit02_식별관계와비식별관계.sql (1)

1-152: 트랜잭션 처리 추가 권장

전체 파일에 트랜잭션 경계가 정의되어 있지 않습니다. lesson, problem, option, answer 간에는 외래 키 관계가 있으므로, 일부 INSERT가 실패할 경우 데이터 일관성이 깨질 수 있습니다.

예를 들어:

  • lesson INSERT는 성공했으나 problem INSERT가 실패하면 빈 레슨이 남음
  • problem INSERT는 성공했으나 option INSERT가 실패하면 선택지 없는 문제가 남음
♻️ 제안 수정

파일 시작과 끝에 트랜잭션 경계를 추가하세요:

+-- Transaction start
+BEGIN;
+
 -- =====================================================
 -- Chapter 4: 데이터베이스 (ID: 4)
 -- Unit 43: 식별 관계와 비식별 관계
 -- =====================================================
 
 ...
 
 INSERT INTO answer (id, problem_id, content, explanation)
 VALUES (253, 881, '실선', '식별 관계는 ERD에서 실선으로 표현된다. 실선은 부모와 자식 간의 강한 결합을 나타내며, 부모 데이터가 존재해야만 자식 데이터를 생성할 수 있음을 의미한다. 반면 비식별 관계는 점선으로 표현된다.'),
        (254, 885, '비식별, Non-Identifying', '비식별 관계(Non-Identifying Relationship)에서는 부모 테이블의 기본 키를 자식 테이블의 외래 키로만 사용하고, 자식은 자체적인 기본 키를 가진다. ERD에서 점선으로 표현되며, 부모와 자식의 생명주기가 분리된다.');
+
+-- Transaction commit
+COMMIT;

PostgreSQL을 사용하는 경우 추가적으로 에러 처리를 고려할 수 있습니다:

BEGIN;
-- All INSERT statements here
COMMIT;
-- Or in case of error:
-- ROLLBACK;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14beedb and 05b69ef.

📒 Files selected for processing (4)
  • src/main/resources/sql/problem/database/unit02_식별관계와비식별관계.sql
  • src/main/resources/sql/problem/database/unit04_키.sql
  • src/main/resources/sql/problem/database/unit05_외래키와제약조건.sql
  • src/main/resources/sql/problem/database/unit06_DDL.sql
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/resources/sql/problem/database/unit06_DDL.sql
  • src/main/resources/sql/problem/database/unit04_키.sql
  • src/main/resources/sql/problem/database/unit05_외래키와제약조건.sql
🔇 Additional comments (5)
src/main/resources/sql/problem/database/unit02_식별관계와비식별관계.sql (5)

10-17: 문제 데이터 구조 확인 완료

OBJECTIVE 타입 문제들은 option 테이블에 선택지를 가지고 있으며, SUBJECTIVE 타입 문제들은 answer 테이블에 정답을 가지고 있습니다. 데이터 구조가 일관되게 설계되어 있습니다.

Also applies to: 59-66, 108-115


19-50: 교육 콘텐츠 품질 양호

식별 관계와 비식별 관계에 대한 설명이 정확하고 일관성 있게 작성되었습니다:

  • 핵심 개념(PK/FK 역할, 생명주기, ERD 표현)이 명확히 구분됨
  • 각 선택지의 explanation이 오답인 이유를 명확히 설명
  • 실무 예시(주문-주문상세, 회원-게시글, 부서-사원)가 적절함

Also applies to: 68-99, 117-149


140-141: SQL 구문 오류: 문자열 리터럴 줄바꿈

option ID 2517의 content 필드가 두 줄에 걸쳐 있으나 적절한 SQL 줄 연속 구문이 없습니다. 대부분의 SQL 파서는 이를 구문 오류로 처리합니다.

🐛 수정 제안
-(2517, 884, '비식별 관계', '사원이 부서에 소속되지 않을 수도 있으므로, 부모 - 자식 관계가 선택적이다. 이런 경우 비식별 관계가 적절하며, 사원 테이블의 부서ID FK는 NULL을 허용할 수 있다.',
- true),
+(2517, 884, '비식별 관계', '사원이 부서에 소속되지 않을 수도 있으므로, 부모 - 자식 관계가 선택적이다. 이런 경우 비식별 관계가 적절하며, 사원 테이블의 부서ID FK는 NULL을 허용할 수 있다.', true),

Likely an incorrect or invalid review comment.


7-8: Unit ID 43이 존재하지 않는다는 우려는 해결됨

검증 결과, unit_id 43은 unit.sql에 실제로 정의되어 있으며, 파일의 단원 제목과 정확히 일치합니다. 따라서 외래 키 제약 조건 위반 위험은 없습니다.

또한 다른 SQL 파일들에서 lesson ID 125-127, problem ID 866-886, option ID 2463-2522, answer ID 249-254와 충돌하는 데이터를 찾을 수 없었습니다. 이는 ID 범위가 적절히 관리되고 있음을 나타냅니다.

하드코딩된 ID는 이 파일이 초기화 스크립트(seed data)이기 때문에 표준 관행이며, 현재 코드베이스에서 실제 문제를 발생시키지 않습니다.

Likely an incorrect or invalid review comment.


51-53: 프론트엔드에서 답안 검증이 처리되는 아키텍처 확인

답안 검증은 이 백엔드 서버가 아닌 프론트엔드에서 처리됩니다. ProblemSubmissionRequestproblemIdisCorrect 불린값만 포함하며, 사용자의 실제 답안 내용은 전송되지 않습니다. 따라서 백엔드는 정/오답 판정 로직이 없으며, 프론트엔드에서 결정된 isCorrect 값을 그대로 저장합니다.

answer 테이블의 쉼표 구분 형식(예: '식별, Identifying', 'FK, 외래키, 외래 키, Foreign Key')은 프론트엔드의 답안 매칭 로직에서 처리되어야 합니다. 백엔드 코드에는 답안 내용 검증 로직이 없으므로, 해당 우려사항은 프론트엔드 구현 검토 대상입니다.

Likely an incorrect or invalid review comment.

@xunxxoie xunxxoie merged commit a417736 into dev Jan 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🌼 Feat to add new Feature! 🧹Refactor to refactor code or update logic

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants