바

ollama 오픈소스 LLM gemma4 를 이용해서, 자연어로 sql 검색 가능한 text2sql 기능 테스트.

· 2026-05-15 (금) 11:08:32 · 2932
스크린샷 2026-05-15 101222.png
스크린샷 2026-05-15 101027.png
### 작성물 : ollama 로컬 LLM gemma4 를 이용한 Mysql DB 의 text2sql 기능 테스트
### 작성일 : 2026-05-15
### 작성자 : 바다클라우드 [email protected]

LLM 로 놀다가 gemma4 도움으로 초보적인걸 간단하게 만들어봤습니다.
Mysql DB 에 저장된 정보를, sql 전문가가 쿼리문으로 정보를 뽑는게 아니라,
sql을 몰라도 자연어, 일상어로 입력해서 정보를 다룰수 있는 기능입니다.
이 text2sql 기능은,
원래 openAI 에서 만든 API 에 있는 기능인데,  다른 언어모델들도 같이 적용을해서 
ollama  에 공개된 모델들도 같은 API 로 사용가능하게 됐다고 합니다.
내 PC 가 리눅스인데, 여기에  ollama 를 설치하고, 최근에 나온 gemma4 를 설치해서 
테스트 해봤습니다.

자신의 PC 에 설치된 오픈소스 LLM 을 사용하므로, 토큰비용 없이 사용가능합니다.


### 사전 설정 사항.
1. ollama 설치해서 gemma4:31b-cloud 를 설치합니다.
  gemma4:latest 를 직접 설치해도 되는데 , 내 컴이 사양이 낮아서 cloud 로 했습니다.

 >> nonots@nonots-hp:~$ ollama list
 >> NAME                      ID              SIZE      MODIFIED
 >> gemma4:31b-cloud          c382fbfbc73b    -         43 hours ago   ===> 이걸 사용합니다.
 >> kimi-k2.6:cloud           a90cd0d1590c    -         3 weeks ago
 >> nemotron-3-super:cloud    be3943c5a818    -         5 weeks ago
 >> gemma4:latest             c6eb396dbd59    9.6 GB    5 weeks ago
 >> qwen3-coder-next:cloud    aa626c11ae8d    -         8 weeks ago
 >> qwen3.5:cloud             a7bf6f7891c3    -         8 weeks ago



2. Mysql 데몬을 설치해서, db 와 db사용자를 만들어서 아래 간단한 테스트용 
 테이블 2개를 생성합니다. member 는 회원정보, orderinfo 는 주문정보입니다.


CREATE TABLE `member` (
  `idx` int NOT NULL AUTO_INCREMENT,
  `userid` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  `username` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `address` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `tel` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `birth` date DEFAULT NULL,
  `indate` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`idx`),
  UNIQUE KEY `userid` (`userid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `member` VALUES (2,'nonots','나이름','서울시 갈현','[email protected]','010-2222-333','2026-04-16','2026-04-01 07:11:02');
INSERT INTO `member` VALUES (3,'bada3','나바다','서울시 바다면 면','[email protected]','011-222-3333','2026-04-16','2026-04-01 07:30:23');
INSERT INTO `member` VALUES (4,'user001','김철수','서울특별시 강남구 테헤란로 123','cheolsu.[email protected]','010-1234-5678','1990-05-15','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (5,'user002','이영희','부산광역시 해운대구 해운대로 456','younghee.[email protected]','010-2345-6789','1985-08-22','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (6,'user003','김민수','경기도 성남시 분당구 불정로 789','minsu.[email protected]','010-3456-7890','1992-11-03','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (7,'user004','정수진','대광역시 수성구 달구벌대로 1011','sujin.[email protected]','010-4567-8901','1988-03-28','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (8,'user005','최동현','서울특별시 마포구弘대문로 1213','donghyun.[email protected]','010-5678-9012','1995-07-19','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (9,'user006','강지영','인천광역시 남구 인하로 1415','jiyoung.[email protected]','010-6789-0123','1993-12-05','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (10,'user007','윤서준','서울특별시 강동구 천호대로 1617','seojun.[email protected]','010-7890-1234','1991-09-14','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (11,'user008','홍길동','광주광역시东区笔山洞 1819','gildong.[email protected]','010-8901-2345','1987-04-25','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (12,'user009','임미영','대전광역시 유성구 계백로 2021','miyoung.[email protected]','010-9012-3456','1994-06-08','2026-04-10 01:17:26');
INSERT INTO `member` VALUES (13,'user010','신동욱','서울특별시 동작구 사당로 2223','donguk.[email protected]','010-0123-4567','1989-10-30','2026-04-10 01:17:26');

CREATE TABLE `orderinfo` (
  `idx` int NOT NULL AUTO_INCREMENT,
  `userid` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `product` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `price` int DEFAULT NULL,
  `count_num` int DEFAULT NULL,
  `indate` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`idx`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `orderinfo` VALUES (1,'bada3','마법의 다이어트 껌',10000,1,'2024-01-18 15:00:00');
INSERT INTO `orderinfo` VALUES (2,'nonots','밤샘 보장 고카페인 물약',20000,2,'2024-10-22 15:00:00');
INSERT INTO `orderinfo` VALUES (3,'user001','어색함 제거용 대화 가이드',15000,1,'2024-11-27 15:00:00');
INSERT INTO `orderinfo` VALUES (4,'user002','월요병 치료용 상상 휴가권',30000,3,'2024-02-11 15:00:00');
INSERT INTO `orderinfo` VALUES (5,'user003','로또 당첨 기원 부적',12000,1,'2024-11-03 15:00:00');
INSERT INTO `orderinfo` VALUES (6,'user004','전남친/전여친 차단용 특수 안경',25000,2,'2024-11-15 15:00:00');
INSERT INTO `orderinfo` VALUES (7,'user005','과제 자동완성 펜',8000,5,'2024-11-05 15:00:00');
INSERT INTO `orderinfo` VALUES (8,'user006','잠 깨우는 벼락 알람',40000,1,'2024-08-13 15:00:00');
INSERT INTO `orderinfo` VALUES (9,'user007','사회생활 만렙 가면',11000,2,'2024-07-17 15:00:00');
INSERT INTO `orderinfo` VALUES (10,'user008','먼지 먹는 최첨단 청소기',5000,10,'2024-11-15 15:00:00');




### 이제 웹서버를 실행해서 아래 파일을 실행합니다. 

 php 실행용 간단한 웹서버는  php -S localhost:8000  와 같이 하면
 서버에서 일반 사용자 권한으로 웹서버를 8000 번 포트로 접속 가능합니다.
아래 소스에서 핵심은 $schema 와 $prompt 부분인거 같습니다.

아래 소스를 text2sql.php 로 저장후 , 웹서버에서 실행합니다.
http://localhost:8000/text2sql.php 와 같이 접속하면, 첨부한 이미지와 같이 사용가능합니다. 

이제 개떡같이 말해도 gemma4 가 찰떡같이 알아듣고 자동으로 SQL 문을 생성해서 
실행해 줍니다.
예를 들면,

"회원 나이가 40대인 회원을 뽑아줘."
"회원중 서울에 사는 회원을 알려줘."
"회원중 김씨 성을 가진 회원이 주문한 정보와 그 회원의 정보를 같이 보여줘."
"주문한 정보중 주문 금액 순으로 정렬해서 5개만 보여줘."
"회원정보에
이름 : 나그네
아이디 : guest
주소 : 안동시 중구 대신동
이메일 : [email protected]
생일 : 1922-01-22
이 정보로 회원을 추가 해줘."
"회원중에 아이디가 guest 인 회원을 삭제해줘."

이런 입력을 하면, 알아듣고 직접 db 에 쿼리문을 생성해 줍니다.


### 주의할점.

- 보안상 select 검색 기능만 부여한 db 아이디를 사용하는게 좋습니다. 
   delete , update ,insert 권한으로 잘못하면, 낭패를 당할수도.

- select 로 출력시 매우 많은 row 출력은 자원을 고갈시킬수 있으므로,
  아래 소스에서 주석처리한 limit 를  강제 적용해서 제한하는게 좋습니다.




//========================== 소스 시작 ======================

<?php
// DB 접속정보
$host = 'localhost';
$dbname = 'db_name';
$username = 'db_id';
$password = 'xxxxxx';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("Database connection failed: " . $e->getMessage());
}

function textToSqlOllama($userInput, $pdo) {

    // 1. 스키마 정보
    //$stmt = $pdo->query("DESCRIBE member");
    //$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
    //$schema = "Table: member(회원정보) , Columns: " . implode(", ", $columns);

    $schema = "
    Table: member(회원정보) , 
        Columns: idx, userid(아이디) , username, address(주소), email, tel, birth, indate (가입일)
    Table: orderinfo(주문정보), 
        Columns: idx, userid, product(제품명), price(주문액), count_num(갯수), indate(주문일)
    ";

    $prompt = "당신은 SQL 전문가입니다. 자연어로 된 질의를 완전한 MySQL SQL  쿼리 문으로 변환해 주세요.\n";
    $prompt .= "Database Schema: $schema\n";
    $prompt .= "Request: $userInput\n";
    $prompt .= "Return ONLY the SQL query without any markdown, explanation, or backticks.";

    //Ollama API 호출 설정
    $url = 'http://localhost:11434/v1/chat/completions'; // Ollama API 주소
    
    $data = [
        'model' => 'gemma4:31b-cloud', // 설치하신 Ollama 모델명으로 변경
        'messages' => [['role' => 'user', 'content' => $prompt]],
        'temperature' => 0
    ];

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json'
        // Ollama는 기본적으로 API Key가 필요 없으므로 생략 가능합니다.
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    $result = json_decode($response, true);
    return trim($result['choices'][0]['message']['content'] ?? '');
}

//사용자 입력
$userInput = $_REQUEST['userInput'];

if( $userInput){
    $sql = textToSqlOllama($userInput, $pdo);

    /*
    // 출력 목록이 많을 경우 강제로 limit 제한
    if( preg_match("/^select/i", $sql) &&  !preg_match("/limit/i", $sql)) {
        $sql = str_replace(";","",$sql);
        $sql .= " limit 300 "; // 최대 300건 제한
    }
     */

    try {
        // 보안 주의: 생성된 SQL을 그대로 실행하는 것은 매우 위험합니다 (SQL Injection)
        // 실제 서비스에서는 실행 전 SQL 검증 단계가 반드시 필요합니다.
        $stmt = $pdo->query($sql);
        $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

    } catch (PDOException $e) {
        echo "Query 실행 오류: " . $e->getMessage();
    }

}
?>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>SQL</title>
</head>
<body>

<form method=post>
<textarea name=userInput rows=5 cols=60 id="userInput"><?php echo $userInput;?></textarea> <br>
* 생성한 SQL : <?php echo $sql;?><br>
<input type=submit value='실행'>   
<input type=button value='취소' onclick="document.getElementById('userInput').value='';">
</form>
<hr>
<table border=1>
<?php 
if( $results){
    //foreach( $results as $k=>$v){ $karr = array_keys($v); }
    $karr = array_keys($results[0]);
    echo "<tr>";
    foreach($karr as $vv){ echo "<td>".$vv."</td>"; }
    echo "</tr>";

    foreach( $results as $k=>$v){
        echo "<tr>";
            foreach($v as $vvv){ echo "<td>".$vvv."</td>"; }
        echo "</tr>";
    }
}
//echo "<pre>"; print_r($results); echo "</pre>"; ?>
</table>
</body>
</html>

//========================== 소스 끝 ======================
|
댓글을 작성하시려면 로그인이 필요합니다.

개발자팁

5,403건

개발과 관련된 유용한 정보를 공유하세요. 질문은 QA에서 해주시기 바랍니다.

+
분류 제목 글쓴이 날짜 조회
기타 05-15 조회 2,933
MySQL 01-06 조회 4,375
JavaScript 25-12-22 조회 1,038
MySQL 25-11-20 조회 3,691
PHP 25-10-21 조회 5,327
PHP 25-10-21 조회 3,638
PHP 25-10-18 조회 4,777
기타 25-08-02 조회 1,417
PHP 25-06-17 조회 5,494
MySQL 25-06-06 조회 4,775
웹서버 25-04-01 조회 1,759
24-10-26 조회 5,581
25-01-25 조회 3,824
25-02-01 조회 5,131
25-02-22 조회 1,846
25-03-08 조회 2,047
25-03-30 조회 6,226
JavaScript 25-03-21 조회 1,985
웹서버 25-03-21 조회 2,083
JavaScript 25-03-03 조회 4,769
기타 25-02-01 조회 2,578
기타 25-01-29 조회 1,454
JavaScript 25-01-28 조회 4,310
기타 25-01-22 조회 1,693
JavaScript 25-01-19 조회 1,663
25-01-10 조회 2,246
기타 25-01-05 조회 1,729
jQuery 25-01-05 조회 1,397
jQuery 25-01-04 조회 1,840
기타 24-12-30 조회 1,885