ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 도서관 관리 시스템
    문제풀이/데이터베이스 설계 2024. 1. 26. 18:17

     

    테이블 설계 요구사항: 도서관 관리 시스템

     

     

    도서관 관리 시스템을 위한 데이터베이스를 설계해야 합니다. 다음 요구사항을 충족해야 합니다:

    1. 도서(Books): 각 도서는 제목, 저자, 출판사, 출판일, ISBN(국제 표준 도서 번호)을 가지고 있어야 합니다.
    2. 회원(Members): 회원은 이름, 이메일, 가입일, 그리고 회원 ID를 가지고 있어야 합니다.
    3. 대출 정보(Loans): 어떤 회원이 어떤 책을 대출했는지에 대한 정보가 필요합니다. 대출일, 반납 예정일, 실제 반납일을 포함해야 합니다.
    4. 벌금(Fines): 반납이 지연될 경우 회원은 벌금을 지불해야 합니다. 회원별 누적 벌금 정보가 필요합니다.

    이제 이 요구사항을 바탕으로 테이블 구조(스키마)를 설계해보세요. 각 테이블의 필드, 필드의 타입, 그리고 테이블 간의 관계를 정의하는 것을 목표로 합니다. 

     

     


     

    Step1) 최초 작성

     

    접근1 ) 벌금과 회원을 1:1 관계로 생성

    유저의 전체 숫자가 벌금을 받은 유저의 숫자보다는 훨씬 많을 것이기 때문에 벌금 받은 유저를 조회해야하는 경우가 많을 경우에는 별도의 테이블을 만드는 편이 효율적일 것 같다.

     

    CREATE TABLE IF NOT EXISTS members (
    	id BIGINT AUTO_INCREMENT,
    	name VARCHAR(100) NOT NULL,
    	email VARCHAR(255) NOT NULL,
    	created_at DATETIME DEFAULT CURRENT_TIMESTAMP        
    );
    
    CREATE TABLE IF NOT EXISTS books (
    	id BIGINT AUTO_INCREMENT,
        title VARCHAR(255) NOT NULL,
        author VARCHAR(50) NOT NULL,
        publisher VARCHAR(100) NOT NULL,
        published_at DATETIME,
    	isbn BIGINT NOT NULL
    );
    
    CREATE TABLE IF NOT EXISTS borrows (
    	id BIGINT AUTO_INCREMENT,
        user_id BIGINT NOT NULL,
        book_id BIGINT NOT NULL,
        expected_returned_at DATETIME NOT NULL,
        returned_at DATETIME,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_id) REFERENCES members(id),
        FOREIGN KEY (book_id) REFERENCES books(id)
    );
    
    CREATE TABLE IF NOT EXISTS fines (
    	user_id BIGINT NOT NULL,
        fine INT NOT NULL,
        FOREIGN KEY (user_id) REFERENCES members(id)
    );

     

     

    접근2) 벌금을 회원 테이블의 한 컬럼으로 사용 

    벌금 생성에 대한 히스토리를 쌓는 것이 아닌 누적 금액만 알면 되는 것이라면 굳이 별도의 테이블을 만들기보다는 컬럼으로 관리한다면 보다 단순한 구조를 유지할 수 있는 장점이 있을 것 같다.

     

    CREATE TABLE IF NOT EXISTS members (
    	id BIGINT AUTO_INCREMENT,
    	name VARCHAR(100) NOT NULL,
    	email VARCHAR(255) NOT NULL,
    	created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        fine INT 
    );
    
    CREATE TABLE IF NOT EXISTS books (
    	id BIGINT AUTO_INCREMENT,
        title VARCHAR(255) NOT NULL,
        author VARCHAR(50) NOT NULL,
        publisher VARCHAR(100) NOT NULL,
        published_at DATETIME,
    	isbn BIGINT NOT NULL
    );
    
    CREATE TABLE IF NOT EXISTS borrows (
    	id BIGINT AUTO_INCREMENT,
        user_id BIGINT NOT NULL,
        book_id BIGINT NOT NULL,
        expected_returned_at DATETIME NOT NULL,
        returned_at DATETIME,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_id) REFERENCES members(id),
        FOREIGN KEY (book_id) REFERENCES books(id)
    );

     

     

    Step2) 피드백

     

    2번 방법을 선택하여 답안을 제출하였습니다.

    •  fine이 단순한 경우 2번으로 처리하는 것도 적합하지만, 요구 사항이 더 복잡할 경우에는 1번으로 처리하는 것이 좋다.
    • ISBN의 경우에는 고유값이므로 UNIQUE를 설정해주는 것이 좋겠다.
    ALTER TABLE books ADD UNIQUE (isbn);

     

     

     

    Step3) 더미 데이터 추가

    쿼리 작동을 확인할 수 있는 더미 데이터들을 추가해주겠습니다.

    -- members 테이블에 더미 데이터 추가
    INSERT INTO members (name, email, fine) VALUES 
    ('John Doe', 'john.doe@example.com', 0),
    ('Jane Smith', 'jane.smith@example.com', 500),
    ('Emily Johnson', 'emily.johnson@example.com', 0);
    
    -- books 테이블에 더미 데이터 추가
    INSERT INTO books (title, author, publisher, published_at, isbn) VALUES 
    ('The Great Gatsby', 'F. Scott Fitzgerald', 'Charles Scribner\'s Sons', '1925-04-10', 9780743273565),
    ('To Kill a Mockingbird', 'Harper Lee', 'J. B. Lippincott & Co.', '1960-07-11', 9780061120084),
    ('1984', 'George Orwell', 'Secker & Warburg', '1949-06-08', 9780451524935),
    ('Pride and Prejudice', 'Jane Austen', 'T. Egerton, Whitehall', '1813-01-28', 9780198321668),
    ('The Catcher in the Rye', 'J.D. Salinger', 'Little, Brown and Company', '1951-07-16', 9780316769488);
    
    -- borrows 테이블에 더미 데이터 추가
    -- 가정: 현재 날짜는 2024-01-26
    INSERT INTO borrows (user_id, book_id, expected_returned_at, returned_at, created_at) VALUES 
    (1, 1, '2024-01-30', NULL, '2023-12-26'),  -- John Doe가 'The Great Gatsby'를 대출, 아직 반납 안함
    (1, 2, '2024-01-10', '2024-01-20', '2023-12-26'),  -- John Doe가 'To Kill a Mockingbird'를 대출, 이미 반납함
    (2, 3, '2024-01-15', NULL, '2023-12-26'),  -- Jane Smith가 '1984'를 대출, 아직 반납 안함 (연체)
    (3, 4, '2024-01-25', NULL, '2023-12-26'),  -- Emily Johnson가 'Pride and Prejudice'를 대출, 아직 반납 안함
    (3, 5, '2024-01-22', '2024-01-25', '2023-12-26');  -- Emily Johnson가 'The Catcher in the Rye'를 대출, 이미 반납함
    
    -- 현재 날짜를 기준으로 연체된 대출 건에 대한 벌금을 계산하고 members 테이블의 fine 컬럼을 업데이트
    SET SQL_SAFE_UPDATES = 0;
    UPDATE members
    SET fine = fine + COALESCE((
        SELECT SUM(DATEDIFF(CASE 
                                 WHEN returned_at IS NULL THEN CURDATE() 
                                 ELSE returned_at 
                             END, expected_returned_at) * 100)  -- 하루 연체당 100의 벌금
        FROM borrows
        WHERE members.id = borrows.user_id
          AND expected_returned_at < CASE 
                                       WHEN returned_at IS NULL THEN CURDATE() 
                                       ELSE returned_at 
                                   END
    ), 0)
    WHERE id IN (
        SELECT user_id
        FROM borrows
        WHERE expected_returned_at < CASE 
                                       WHEN returned_at IS NULL THEN CURDATE() 
                                       ELSE returned_at 
                                   END
    );
    SET SQL_SAFE_UPDATES = 1;

     

     

    Step4) SQL 문제

     

    시나리오에 알맞은 sql 문을 작성해보겠습니다.

     

    문제 1: 특정 회원의 현재 대출 정보 조회

    • 요구사항: 회원 ID가 2인 회원이 현재 대출 중인 모든 책의 제목, 대출일, 반납 예정일을 조회합니다.
    -- step1) 대출 중인 대출 기록을 추려낸다.
    SELECT * FROM borrows br WHERE br.returned_at IS NULL;
    
    
    -- step2) 대출 기록과 관련된 책의 정보도 추가한다.
    SELECT * FROM borrows br 
        INNER JOIN books b 
        ON br.book_id = b.id
        WHERE br.returned_at IS NULL;
        
        
    -- step3) 필요한 컬럼만 추려낸다.
    SELECT b.title, br.created_at as borrowed_at, br.expected_returned_at
        FROM borrows br
        INNER JOIN books b
        ON br.book_id = b.id
        WHERE br.returned_at IS NULL;
        
    
    -- step4) 이 중에서 회원ID 2인 경우만 추려낸다.
    SELECT b.title, br.created_at as borrowed_at, br.expected_returned_at
        FROM borrows br
        INNER JOIN books b
        ON br.book_id = b.id
        WHERE br.returned_at IS NULL
        AND br.user_id = 2;

     

    • 정답
    SELECT b.title, br.created_at as borrowed_at, br.expected_returned_at
        FROM borrows br
        INNER JOIN books b
        ON br.book_id = b.id
        WHERE br.returned_at IS NULL
        AND br.user_id = 2;

     

     

     

    문제 2: 연체된 대출 건과 연체료 조회

    • 요구사항: 2024-01-26 기준으로 아직 반납되지 않고 반납 예정일이 지난 모든 대출 정보(회원 이름, 책 제목, 대출일, 반납 예정일, 연체료)를 조회합니다. 연체료는 연체된 일수당 100으로 계산합니다.
    -- step1) 아직 반납이 되지 않은 기록을 추려낸다.
    SELECT * FROM borrows br WHERE br.returned_at IS NULL;
    
    
    -- step2) 그 중에서 반납 예정일이 2024-01-26 이전인 데이터를 추려낸다.
    SELECT * FROM borrows br
    	WHERE br.returned_at IS NULL
            AND br.expected_returned_at < '2024-01-26';
             
    
    -- step3) 반납예정일로부터 2024-01-26까지 몇일이 지났는지 확인한다.
    SELECT (DATEDIFF('2024-01-26', br.expected_returned_at)) as delayed_day
    	FROM borrows br
            WHERE br.returned_at IS NULL
            AND br.expected_returned_at < '2024-01-26';
            
    
    -- step4) 하루에 100원씩을 계산에서 벌금을 산출한다.
    SELECT (DATEDIFF('2024-01-26', br.expected_returned_at) * 100) as fine
    	FROM borrows br
            WHERE br.returned_at IS NULL
            AND br.expected_returned_at < '2024-01-26';
    
    
    -- step5) 필요한 모든 필드들을 추가한다.
    SELECT 
        m.id AS user_id,
        b.title AS title,
        br.created_at AS borrowed_at,
        br.expected_returned_at,
        (DATEDIFF('2024-01-26', br.expected_returned_at) * 100) AS fine
    	FROM borrows br
    		INNER JOIN books b 
    		ON br.book_id = b.id
    		INNER JOIN members m
    		ON br.user_id = m.id
    	WHERE br.returned_at IS NULL
    	AND br.expected_returned_at < '2024-01-26';

     

    • 정답
    SELECT 
        m.id AS user_id,
        b.title AS title,
        br.created_at AS borrowed_at,
        br.expected_returned_at,
        (DATEDIFF('2024-01-26', br.expected_returned_at) * 100) AS fine
    	FROM borrows br
    		INNER JOIN books b 
    		ON br.book_id = b.id
    		INNER JOIN members m
    		ON br.user_id = m.id
    	WHERE br.returned_at IS NULL
    	AND br.expected_returned_at < '2024-01-26';

     

     

    문제 3: 가장 많이 대출된 책과 해당 책의 대출 횟수 조회

    • 요구사항: 도서관에서 가장 많이 대출된 상위 3권의 책과 해당 책들의 대출 횟수를 조회합니다.
    -- step1) 대출 기록에서 도서 id별 대출 횟수를 확인한다.
    SELECT br.book_id, count(br.book_id) AS borrow_count FROM borrows br GROUP BY br.book_id;
    
    
    -- step2) 내림 차순 정렬을 사용해서 대출 횟수가 높은 순으로 정렬한다.
    SELECT br.book_id, count(br.book_id) AS borrow_count 
    	FROM borrows br
    	GROUP BY br.book_id
    	ORDER BY borrow_count DESC;
        
    
    -- step3) 그 중 3개만 데이터를 추려낸다.
    SELECT br.book_id, count(br.book_id) AS borrow_count 
    	FROM borrows br
    	GROUP BY br.book_id
    	ORDER BY borrow_count DESC
    	LIMIT 3;
        
        
    -- step4) join을 통해서 필요한 책 정보를 가져온다.
    SELECT b.title, count(br.book_id) AS borrow_count 
    	FROM borrows br 
    		INNER JOIN books b
            ON br.book_id = b.id
    	GROUP BY br.book_id 
    	ORDER BY borrow_count DESC 
    	LIMIT 3;

     

    • 정답
    SELECT b.title, count(br.book_id) AS borrow_count 
    	FROM borrows br 
    		INNER JOIN books b
            ON br.book_id = b.id
    	GROUP BY br.book_id 
    	ORDER BY borrow_count DESC 
    	LIMIT 3;

     

     

     

    STEP5) 정리

    다음 번에는 index를 설정할 때와 안할 때의 차이도 함께 확인하는 문제를 풀어보면 좋겠다.

    DATEDIFF() 함수를 사용해서 날짜의 차이르 구할 수 있다.

     

     

     

     

Designed by Tistory.