SELECT *
FROM product
ORDER BY created_at DESC;
SELECT *
FROM product
WHERE product_name LIKE CONCAT('%', **{검색어}**, '%')
ORDER BY product_name;
SELECT *
FROM product
WHERE category_id = **{카테고리 ID}**
ORDER BY product_name;
SELECT *
FROM product
ORDER BY price ASC;
SELECT *
FROM product
ORDER BY price DESC;
SELECT pd.product_detail_id, od.option_type_id, od.option_detail_id
FROM product AS p JOIN
product_detail pd ON p.product_id = pd.product_id JOIN
product_option op ON pd.product_detail_id = op.product_detail_id JOIN
option_detail od ON op.option_detail_id = od.option_detail_id
WHERE p.product_id = **{클릭한 상품ID}**;
SELECT pd.product_detail_id
FROM product_detail pd
JOIN product_option op ON pd.product_detail_id = op.product_detail_id
WHERE pd.product_id = **{상품ID}**
AND op.option_detail_id IN (**{선택한 옵션상세ID(1)}**, **{선택한 옵션상세ID(2)}**)
GROUP BY pd.product_detail_id
HAVING COUNT(DISTINCT op.option_detail_id) = **{선택한 옵션 개수}**;
INSERT INTO cart_item("member_id", "product_detail_id", "quantity")
VALUES (?, ?, ?);
UPDATE cart_item
SET quantity = quantity + 1
WHERE member_id = ? AND product_detail_id = ?;
UPDATE cart_item
SET quantity = quantity - 1
WHERE member_id = ? AND product_detail_id = ?;
DELETE FROM cart_item
WHERE member_id = ? AND product_detail_id = ?;
SELECT p.product_id, SUM(pd.sales) AS total_sales
FROM product p JOIN
product_detail pd ON p.product_id = pd.product_id
GROUP BY p.product_id;

SELECT pd.product_detail_id,
MAX(CASE WHEN od.option_type_id=1 THEN od.option_value END) AS size_option,
MAX(CASE WHEN od.option_type_id=2 THEN od.option_value END) AS color_option,
SUM(pd.sales) AS total_sales
FROM product_detail pd JOIN
product_option op ON pd.product_detail_id = op.product_detail_id JOIN
option_detail od ON op.option_detail_id = od.option_detail_id
GROUP BY pd.product_detail_id;

SELECT p.product_id, pd.product_detail_id,
MAX(CASE WHEN od.option_type_id=1 THEN od.option_value END) AS size_option,
MAX(CASE WHEN od.option_type_id=2 THEN od.option_value END) AS color_option,
SUM(pd.sales) AS total_sales
FROM product p JOIN
product_detail pd ON p.product_id = pd.product_id JOIN
product_option op ON pd.product_detail_id = op.product_detail_id JOIN
option_detail od ON op.option_detail_id = od.option_detail_id
GROUP BY pd.product_detail_id;

SELECT MONTH(order_date) AS month, SUM(total_price) AS total_income
FROM orders
WHERE YEAR(order_date) = 2026
GROUP BY MONTH(order_date)
ORDER BY month;
WITH month_income AS (
SELECT MONTH(order_date) AS month, SUM(total_price) AS total_income
FROM orders
WHERE YEAR(order_date) = 2026
GROUP BY MONTH(order_date)
ORDER BY month
)
SELECT month, total_income AS "월 매출",
LAG(total_income, 1, 0) OVER (ORDER BY month) AS "전월매출",
total_income - LAG(total_income, 1, 0) OVER (ORDER BY month) AS "전월 대비 증감"
FROM month_income;
-- [제조업체]
INSERT INTO manufacturer (manufacturer_id, company_name, owner)
VALUES (1, 'ZARA', '자라 소유주');
INSERT INTO manufacturer (manufacturer_id, company_name, owner)
VALUES (2, 'H&M', 'H&M 소유주');
INSERT INTO manufacturer (manufacturer_id, company_name, owner)
VALUES (3, 'UNIQLO', 'UNIQLO 소유주');
INSERT INTO manufacturer (manufacturer_id, company_name, owner)
VALUES (4, 'Nike', 'Nike 소유주');
INSERT INTO manufacturer (manufacturer_id, company_name, owner)
VALUES (5, 'Adidas', 'Adidas 소유주');
-- [카테고리]
INSERT INTO category (category_id, category_name)
VALUES (1, '자켓');
INSERT INTO category (category_id, category_name)
VALUES (2, '티셔츠');
INSERT INTO category (category_id, category_name)
VALUES (3, '바지');
INSERT INTO category (category_id, category_name)
VALUES (4, '스커트');
INSERT INTO category (category_id, category_name)
VALUES (5, '아우터');
INSERT INTO category (category_id, category_name)
VALUES (6, '니트');
-- [상품]
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (1, 1, '트위드 자켓', 150000, 1, 'http://img/zara-tweed-jacket');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (2, 1, '플리츠 미디 스커트', 89000, 4, 'http://img/zara-pleats-skirt');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (3, 1, '리넨 블라우스', 65000, 2, 'http://img/zara-linen-blouse');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (4, 2, '오버사이즈 티셔츠', 29900, 2, 'http://img/hm-oversized-tee');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (5, 2, '슬림핏 청바지', 49900, 3, 'http://img/hm-slim-jeans');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (6, 3, '히트텍 롱슬리브', 19900, 2, 'http://img/uniqlo-heattech');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (7, 3, '후리스 집업', 59900, 5, 'http://img/uniqlo-fleece-zip');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (8, 4, '드라이핏 반팔', 45000, 2, 'http://img/nike-dri-fit');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (9, 5, '트랙 재킷', 89000, 1, 'http://img/adidas-track-jacket');
INSERT INTO product (product_id, manufacturer_id, product_name, price, category_id, image_url)
VALUES (10, 5, '조거 팬츠', 69000, 3, 'http://img/adidas-jogger-pants');
-- [상품상세]
-- 트위드 자켓 (product_id=1)
INSERT INTO product_detail (product_detail_id, product_id, stock_quantity, surcharge, sales, image_url)
VALUES (1, 1, 30, 0, 10, 'http://img/zara-tweed-jacket-white-s');
INSERT INTO product_detail (product_detail_id, product_id, stock_quantity, surcharge, sales, image_url)
VALUES (2, 1, 25, 0, 20, 'http://img/zara-tweed-jacket-white-m');
INSERT INTO product_detail (product_detail_id, product_id, stock_quantity, surcharge, sales, image_url)
VALUES (3, 1, 20, 0, 15, 'http://img/zara-tweed-jacket-black-m');
INSERT INTO product_detail (product_detail_id, product_id, stock_quantity, surcharge, sales, image_url)
VALUES (4, 1, 15, 0, 5, 'http://img/zara-tweed-jacket-black-l');
-- 플리츠 미디 스커트 (product_id=2)
INSERT INTO product_detail (product_detail_id, product_id, stock_quantity, surcharge, sales, image_url)
VALUES (5, 2, 40, 0, 30, 'http://img/zara-pleats-skirt-beige-s');
INSERT INTO product_detail (product_detail_id, product_id, stock_quantity, surcharge, sales, image_url)
VALUES (6, 2, 35, 0, 25, 'http://img/zara-pleats-skirt-beige-m');
INSERT INTO product_detail (product_detail_id, product_id, stock_quantity, surcharge, sales, image_url)
VALUES (7, 2, 30, 0, 20, 'http://img/zara-pleats-skirt-black-m');
INSERT INTO product_detail (product_detail_id, product_id, stock_quantity, surcharge, sales, image_url)
VALUES (8, 2, 20, 0, 10, 'http://img/zara-pleats-skirt-navy-l');
-- 등...
각 상품별로 어떤 옵션들이 있는지 조회
SELECT *
FROM product_detail as pd JOIN
product_option as pop ON pd.product_detail_id = pop.product_detail_id JOIN
option_detail as od ON pop.option_detail_id = od.option_detail_id
WHERE pd.product_id = **{선택한 상품ID}**
AND **pd.stock > 0**;


상품 안에서 색상/사이즈 내역을 조회해서 출력하기 위한 쿼리문
SELECT *
FROM product_detail as pd JOIN
product_option as pop ON pd.product_detail_id = pop.product_detail_id JOIN
option_detail as od ON pop.option_detail_id = od.option_detail_id
WHERE pd.product_id = **{선택한 상품ID}**
AND option_type_id = **{선택한 옵션종류ID}**;


SELECT *
FROM product as p JOIN
category as c ON p.category_id = c.category_id
WHERE c.category_name = '자켓';


UPDATE product
SET price = **{새로운 가격}**
WHERE product_id = **{변경하려는 상품}**;


UPDATE product_detail
SET sales = {업데이트되는 판매량}
WHERE product_detail_id = {업데이트되는 상품상세ID};

UPDATE product
SET category_id = **{새로운 옵션ID}**
WHERE product_id = **{바꾸려는 상품ID}**;


상품-옵션 테이블의 행을 삭제하는 것은 “해당 상품의 옵션 종류 중 하나를 삭제하는 것을 의미”
DELETE FROM product_option
WHERE product_detail_id = **{상품 상세ID}**
AND option_detail_id = **{삭제하려는 옵션상세ID}**;

상품을 삭제했을 때, 상품을 외래키로 갖는 상품 상세 등이 어떻게 동작하는지를 확인.













-- [옵션 종류]
INSERT INTO option_type (option_type_id, option_type_name)
VALUES (1, '사이즈');
INSERT INTO option_type (option_type_id, option_type_name)
VALUES (2, '색상');
-- [옵션 상세]
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (1, 1, 'XS');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (2, 1, 'S');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (3, 1, 'M');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (4, 1, 'L');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (5, 1, 'XL');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (6, 2, '화이트');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (7, 2, '블랙');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (8, 2, '네이비');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (9, 2, '베이지');
INSERT INTO option_detail (option_detail_id, option_type_id, option_value)
VALUES (10, 2, '그레이');

⇒ option_type_name을 UNIQUE가 되도록 (option_type_id, option_type_name)을 복합키로 구성하도록 변경
CREATE TABLE option_type
(
option_type_id BIGINT NOT NULL AUTO_INCREMENT,
option_type_name VARCHAR(30) NOT NULL,
PRIMARY KEY (option_type_name, option_type_id)
);






⇒ ❓ 의문: PK(대리키)를 제외하고 두 속성을 합쳐서 UNIQUE인 제약이 있어도 괜찮을지?








DELIMITER $$
CREATE PROCEDURE report_review (
IN r_user_id BIGINT,
IN r_review_id BIGINT,
IN r_reason VARCHAR(255)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO review_report (review_id, reporter_member_id, report_reason, report_status)
VALUES(r_review_id, r_user_id, r_reason, 'WAITING');
UPDATE review
SET report_count = report_count+1
WHERE review_id = r_review_id;
COMMIT;
END $$
DELIMITER ;
DELIMITER $$
CREATE PROCEDURE add_cart (
IN p_member_id BIGINT,
IN p_product_id BIGINT,
IN p_quantity INT
)
BEGIN
DECLARE exist INT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
START TRANSACTION;
SELECT COUNT(*) INTO exist
FROM cart_item
WHERE member_id = p_member_id AND product_detail_id = p_product_id;
IF exist > 0 THEN
UPDATE cart_item
SET quantity = quantity + p_quantity
WHERE member_id = p_member_id AND product_detail_id = p_product_id;
ELSE
INSERT INTO cart_item (member_id, product_detail_id, quantity)
VALUES (p_member_id, p_product_id, p_quantity);
END IF;
COMMIT;
END $$
DELIMITER ;
DELIMITER $$
CREATE TRIGGER check_stock_insert
BEFORE INSERT ON cart_item
FOR EACH ROW
BEGIN
DECLARE stock INT;
SELECT stock_quantity INTO stock
FROM product_detail
WHERE product_detail_id = NEW.product_detail_id;
IF NEW.quantity > stock
THEN SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '상품 재고가 부족합니다.';
END IF;
END $$
CREATE TRIGGER check_stock_update
BEFORE UPDATE ON cart_item
FOR EACH ROW
BEGIN
DECLARE stock INT;
SELECT stock_quantity INTO stock
FROM product_detail
WHERE product_detail_id = NEW.product_detail_id;
IF NEW.quantity > stock
THEN SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '상품 재고가 부족합니다.';
END IF;
END $$
DELIMITER ;
DELIMITER $$
CREATE FUNCTION get_cart_price (
p_member_id BIGINT
)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE total_price INT;
SELECT IFNULL(SUM(price*quantity),0) INTO total_price
FROM cart_item
WHERE member_id = p_member_id;
RETURN total_price;
END $$
DELIMITER ;
SELECT get_cart_price(5) AS total_price;


@Controller
@RequiredArgsConstructor
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
//TODO: 상품 생성
@PostMapping
public ResponseEntity<ProductResponse> createProduct(@RequestBody CreateProductRequest request) {
ProductResponse response = productService.createProduct(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
//TODO: 상품 목록 조회 (가격순 정렬)
@GetMapping
public ResponseEntity<ProductListResponse> getProductList(@RequestParam(defaultValue = "productName,desc") String sortBy) {
ProductListResponse response = productService.getProductList(sortBy);
return ResponseEntity.ok(response);
}
//TODO: 카테고리별 상품 목록 조회 (가격순 정렬)
@GetMapping("/category/{categoryId}")
public ResponseEntity<ProductListResponse> getProductListByCategory(@PathVariable("categoryId") Long categoryId,
@RequestParam(defaultValue = "productName,desc") String sortBy) {
ProductListResponse response = productService.getProductListByCategory(categoryId, sortBy);
return ResponseEntity.ok(response);
}
//TODO: 상품 개별 조회
@GetMapping("/{productId}")
public ResponseEntity<ProductResponse> getProductDetail(@PathVariable("productId") Long productId) {
ProductResponse response = productService.getProductDetail(productId);
return ResponseEntity.ok(response);
}
//TODO: 상품 검색
@GetMapping("/search")
public ResponseEntity<ProductListResponse> searchProduct(@RequestBody SearchProduct request,
@RequestParam(defaultValue = "productName,desc") String sortBy) {
ProductListResponse response = productService.searchProduct(request, sortBy);
return ResponseEntity.ok(response);
}
//TODO: 상품 가격 업데이트
@PatchMapping("/{productId}")
public ResponseEntity<Void> updateProductPrice(@PathVariable("productId") Long productId,
@RequestBody UpdateProductPrice request) {
productService.updateProductPrice(productId, request);
return ResponseEntity.ok().build();
}
//TODO: 상품 삭제
@DeleteMapping("/{productId}")
public ResponseEntity<Void> deleteProduct(@PathVariable("productId") Long productId) {
productService.deleteProduct(productId);
return ResponseEntity.ok().build();
}
}
@Entity
@Getter
@NoArgsConstructor
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "category_id")
private Long id;
@Column(name = "category_name")
private String category;
@Builder
public Category(String category) {
this.category = category;
}
}
@Entity
@Getter
@NoArgsConstructor
public class Manufacturer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "manufacturer_id")
private Long id;
@Column (name = "company_name")
private String company;
@Column (name = "owner")
private String owner;
@Builder
public Manufacturer(String company, String owner) {
this.company = company;
this.owner = owner;
}
}
@Entity
@Getter
@NoArgsConstructor
public class Product {
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
@Column(name = "product_id")
private Long id;
@ManyToOne (fetch = FetchType.LAZY)
@JoinColumn(name = "manufacturer_id")
private Manufacturer manufacturer;
@Column(name = "product_name")
private String productName;
@Column(name = "price")
private int price;
@OneToOne
@JoinColumn(name = "category_id")
private Category category;
@Column (name = "image_url")
private String imageUrl;
@Builder
public Product(Manufacturer manufacturer, String name, int price, Category category, String imageUrl) {
this.manufacturer = manufacturer;
this.productName = name;
this.category = category;
this.price = price;
this.imageUrl = imageUrl;
}
public void updatePrice(int price) {
this.price = price;
}
}
@Getter
@AllArgsConstructor
public class CreateProductRequest {
private Long manufacturerId;
private String productName;
private int price;
private Long categoryId;
private String imageUrl;
}
@Getter
@AllArgsConstructor
public class SearchProduct {
private String keyword;
}
@Getter
@AllArgsConstructor
public class UpdateProductPrice {
private int price;
}
@Getter
@Builder
@AllArgsConstructor
public class ProductListResponse {
private List<ProductResponse> productResponseList;
private Long productCount;
public static ProductListResponse of (List<Product> products) {
return ProductListResponse.builder()
.productResponseList(products.stream().map(ProductResponse::of).toList())
.productCount((long) products.size())
.build();
}
}
@Getter
@Builder
@AllArgsConstructor
public class ProductResponse {
private Long productId;
private String productName;
private int price;
private String manufacturer;
private String category;
private String imageUrl;
public static ProductResponse of (Product product) {
return ProductResponse.builder()
.productId(product.getId())
.productName(product.getProductName())
.price(product.getPrice())
.category(product.getCategory().getCategory())
.manufacturer(product.getManufacturer().getCompany())
.imageUrl(product.getImageUrl())
.build();
}
}
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findAllByCategory(Category category, Sort sort);
@Query("select p from Product p where p.productName LIKE %:keyword%" )
List<Product> searchByName(@Param("keyword")String keyword, Sort sort);
}
@Service
@RequiredArgsConstructor
public class ProductService {
private final ManufacturerRepository manufacturerRepository;
private final CategoryRepository categoryRepository;
private final ProductRepository productRepository;
//TODO: 상품 생성
@Transactional
public ProductResponse createProduct(CreateProductRequest request) {
Manufacturer manufacturer = getManufacturer(request.getManufacturerId());
Category category = getCategory(request.getCategoryId());
Product newProduct = Product.builder()
.manufacturer(manufacturer)
.name(request.getProductName())
.price(request.getPrice())
.category(category)
.imageUrl(request.getImageUrl())
.build();
productRepository.save(newProduct);
return ProductResponse.of(newProduct);
}
//TODO: 상품 목록조회 (일반)
@Transactional(readOnly = true)
public ProductListResponse getProductList(String sortBy) {
Sort sort = createSort(sortBy);
List<Product> productList = productRepository.findAll(sort);
return ProductListResponse.of(productList);
}
//TODO: 상품 목록조회 (카테고리)
@Transactional(readOnly = true)
public ProductListResponse getProductListByCategory(Long categoryId, String sortBy) {
Category category = getCategory(categoryId);
Sort sort = createSort(sortBy);
List<Product> productList = productRepository.findAllByCategory(category, sort);
return ProductListResponse.of(productList);
}
//TODO: 상품 개별조회
@Transactional(readOnly = true)
public ProductResponse getProductDetail(Long productId) {
Product product = getProduct(productId);
return ProductResponse.of(product);
}
//TODO: 상품 가격 수정
@Transactional
public void updateProductPrice(Long productId, UpdateProductPrice request) {
Product product = getProduct(productId);
product.updatePrice(request.getPrice());
}
//TODO: 상품 삭제
@Transactional
public void deleteProduct(Long productId) {
Product product = getProduct(productId);
productRepository.delete(product);
}
//TODO: 상품 검색
@Transactional(readOnly = true)
public ProductListResponse searchProduct(SearchProduct request, String sortBy) {
Sort sort = createSort(sortBy);
List<Product> productList = productRepository.searchByName(request.getKeyword(),sort);
return ProductListResponse.of(productList);
}
private Product getProduct(Long productId) {
return productRepository.findById(productId)
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));
}
private Manufacturer getManufacturer(Long manufacturerId) {
return manufacturerRepository.findById(manufacturerId)
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));
}
private Category getCategory (Long categoryId) {
return categoryRepository.findById(categoryId)
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));
}
//AI 도움.
private Sort createSort (String sortBy) {
try {
String[] sortParams = sortBy.split(",");
String property = sortParams[0];
String direction = sortParams[1];
if (direction.equalsIgnoreCase("asc")) {
return Sort.by(Sort.Direction.ASC, property);
} else {
return Sort.by(Sort.Direction.DESC, property);
}
} catch (Exception e) {
return Sort.by(Sort.Direction.DESC, "productName");
}
}
}








