# Machbase DBMS 매뉴얼 전체 본문 > 현재 published DBMS 8.7.0 한국어 페이지를 navigation 순서로 연결한 Markdown corpus입니다. - language: `kr` - document_count: `241` - document_index: https://docs.machbase.com/kr/llms-chunks.json --- title: "Machbase DBMS 매뉴얼" url: https://docs.machbase.com/kr/dbms/ language: kr kind: section --- # Machbase DBMS 매뉴얼 Machbase DBMS 매뉴얼에 오신 것을 환영합니다. 이 문서는 Machbase 8.7.0을 기준으로 설치, 테이블 타입별 활용, 애플리케이션 개발, 운영, 보안과 레퍼런스를 제공합니다. 처음 사용하는 독자는 1장에서 SQL 실습을 진행하고 2장에서 데이터 모델과 저장·운영 원리를 익힙니다. 시스템을 설계할 때는 4장의 테이블 선택 기준을 확인한 뒤 필요한 테이블 장으로 이동합니다. 이미 사용 중인 기능의 정확한 문법이나 지원 범위는 16장에서 찾을 수 있습니다. ## 매뉴얼 구성 | 장 | 제목 | 내용 | |----|------|------| | 1 | [처음 시작하기](./getting-started/) | 개요, 접속 확인, 빠른 시작, 기본 명령 | | 2 | [핵심 개념](./core-concepts/) | 테이블 유형, 시간 모델, ROLLUP, Retention Policy | | 3 | [설치, 배포, 업그레이드](./installation-deployment-upgrade/) | 설치 준비, Standard Edition, Cluster Edition, 업그레이드 | | 4 | [테이블 타입 선택과 스키마 설계](./data-modeling-table-design/) | 타입 결정, 스키마, 변경 정책과 모델링 패턴 | | 5 | [TAG 테이블 활용](./tag-table-usage/) | TAG 구조, 메타데이터, 입력, 조회, 보정, 운영 | | 6 | [TAG 테이블을 위한 ROLLUP 활용](./tag-rollup-usage/) | ROLLUP 설계, 생성, 조회, 재구성, 운영, 성능 튜닝 | | 7 | [LOG 테이블 활용](./log-table-usage/) | LOG 구조, 입력과 텍스트 검색 | | 8 | [TRANSACTION 테이블 활용](./rdb-table-usage/) | TRANSACTION 스키마, DML, 트랜잭션, JOIN, 백업·복원 | | 9 | [LOOKUP 테이블 활용](./lookup-table-usage/) | 기준 정보, PRIMARY KEY, JSON, 일반 predicate DML, JOIN | | 10 | [VOLATILE 테이블 활용](./volatile-table-usage/) | 메모리 테이블, UPSERT, 상태 캐시, 재시작과 데이터 소실 | | 11 | [개발 및 애플리케이션 연동](./development-tools-integration/) | 연동 방식, 공통 개념과 언어별 SDK/API | | 12 | [성능 튜닝](./performance-tuning/) | 쿼리 성능, 수집 성능, 캐시 튜닝 | | 13 | [운영, 설정, 복구](./operations-configuration-recovery/) | 서버 관리, 백업, Cluster 운영 | | 14 | [계정, 권한, 접속 제어](./security-access-control/) | 계정, 권한, AUTH KEY와 접속 제어 | | 15 | [문제 해결](./troubleshooting/) | 오류 진단 및 해결 방법 | | 16 | [레퍼런스](./reference/) | SQL 문법, 함수, 설정, 시스템 카탈로그 | --- title: "1. 처음 시작하기" url: https://docs.machbase.com/kr/dbms/getting-started/ language: kr kind: section --- # 1. 처음 시작하기 이 장은 데이터베이스를 처음 다루는 독자와 다른 DBMS를 사용하다 Machbase를 접하는 독자를 위한 출발점입니다. Machbase DBMS 8.7.0에서 어떤 데이터를 어떻게 저장하는지 살펴보고, 이벤트 한 건을 입력하고 조회하는 실습으로 기본 SQL 흐름을 익힙니다. 데이터베이스를 설치하지 않아도 개요부터 읽을 수 있습니다. 실습을 시작할 때는 실행 중인 DBMS 서버와 접속 도구인 `machsql`이 필요합니다. 설치와 접속 준비는 [10분 빠른 시작](./quick-start/)의 실행 전제에서 안내합니다. ## 이 장에서 배울 것 1. 테이블, 행, 컬럼과 SQL의 역할을 이해합니다. 2. 시간에 따라 쌓이는 이력과 현재 상태를 구분합니다. 3. 계측값, 이벤트와 기준 정보에 맞는 Machbase 테이블 유형을 살펴봅니다. 4. 서버에 접속해 LOG 테이블을 만들고 데이터를 입력·조회합니다. 5. 발생 시각과 서버 수신 시각의 차이를 확인하고 다음 학습 경로를 선택합니다. ## 읽는 순서 | 절 | 읽고 나면 할 수 있는 일 | |---|---| | [Machbase DBMS 개요](./overview/) | 시계열 데이터베이스의 목적과 테이블 유형별 역할을 설명합니다. | | [10분 빠른 시작](./quick-start/) | 서버 접속부터 데이터 입력·조회·실습 정리까지 실행합니다. | | [기본 명령 치트시트](./command-cheatsheet/) | 셸 명령과 SQL을 구분하고 자주 쓰는 명령을 찾습니다. | | [다음에 읽을 문서 선택하기](./choose-next-doc/) | 자신의 데이터와 개발·운영 업무에 맞는 문서로 이동합니다. | 실습에서는 동작을 쉽게 확인할 수 있는 소량의 SQL `INSERT`를 사용합니다. 지속적으로 유입되는 대량 데이터의 입력 방식과 오류 처리는 [데이터 입력과 반출](/dbms/development-tools-integration/data-input-load-export/)에서 이어 학습합니다. --- title: "1.1 Machbase DBMS 개요" url: https://docs.machbase.com/kr/dbms/getting-started/overview/ language: kr kind: page --- # 1.1 Machbase DBMS 개요 Machbase DBMS는 센서 계측값, 설비 이벤트, 애플리케이션 로그처럼 시간에 따라 쌓이는 데이터를 저장하고 SQL로 조회·분석하는 시계열 데이터베이스입니다. 사용자는 데이터의 형태와 변경·조회 방식에 맞는 테이블을 선택하고, 원본 이력과 기준 정보를 함께 관리합니다. ## Machbase DBMS 소개 ### 데이터베이스와 SQL의 역할 데이터베이스는 애플리케이션이 수집한 데이터를 일정한 구조로 저장하고 여러 작업에서 다시 사용할 수 있게 합니다. DBMS는 그 데이터를 읽고 쓰는 요청, 사용자 권한과 저장 공간을 관리하는 소프트웨어입니다. 테이블은 같은 구조의 기록을 모은 것입니다. 행은 이벤트 한 건이나 측정값 한 건을 나타내고, 컬럼은 시각·장비 이름·측정값 같은 항목을 나타냅니다. 컬럼에는 숫자, 문자열, 날짜와 시간 같은 데이터 타입을 지정합니다. 이렇게 정한 구조를 스키마라고 합니다. 예를 들어 온도 이력은 다음과 같이 표현할 수 있습니다. 아래 시각과 값은 개념 설명용입니다. | 측정 대상 | 측정 시각 | 온도 | |---|---|---:| | 설비 A의 온도 센서 | 09:00:00 | 23.1 | | 설비 A의 온도 센서 | 09:00:01 | 23.5 | | 설비 B의 온도 센서 | 09:00:01 | 18.0 | SQL은 이 데이터를 다루는 언어입니다. `CREATE`로 테이블을 만들고, `INSERT`로 행을 추가하고, `SELECT`로 필요한 행과 컬럼을 읽습니다. `WHERE`는 조회 조건을, `GROUP BY`는 집계 기준을, `ORDER BY`는 결과의 정렬 순서를 지정합니다. 같은 SQL을 사용하더라도 지원하는 변경 연산과 저장 특성은 테이블 유형에 따라 다릅니다. ### 현재 상태와 시간 이력 “설비 A의 현재 온도는 얼마인가?”와 “지난 1시간 동안 온도가 어떻게 변했는가?”는 다른 질문입니다. 현재 값 하나를 계속 덮어쓰면 과거 변화는 알 수 없습니다. 각 측정값에 시각을 붙여 새 행으로 저장하면 변화 추이, 최대값과 이상 발생 시점을 분석할 수 있습니다. 시계열 시스템에서는 이런 이력이 계속 증가합니다. 따라서 입력 속도뿐 아니라 어떤 대상을 어느 시간 범위로 조회할지, 원본을 얼마나 보관할지도 설계해야 합니다. 계측값이 항상 일정한 간격으로 도착하는 것은 아닙니다. 네트워크 지연, 장비 중단과 재전송으로 늦게 도착하거나 빠진 값이 생길 수 있습니다. ### 계측값, 이벤트와 기준 정보 계측값은 “어느 대상이 어느 시각에 어떤 값을 가졌는가”를 나타냅니다. 이벤트는 알람, 장비 정지, 서비스 시작처럼 어떤 일이 발생한 사실을 기록합니다. 장비 이름, 설치 위치와 측정 단위는 그 기록을 해석하기 위한 기준 정보입니다. 이 세 가지는 하나의 시스템에서 함께 사용됩니다. 온도 이상을 분석하려면 온도 이력뿐 아니라 같은 시각의 알람과 해당 센서의 설치 위치도 필요합니다. SQL의 조인(`JOIN`)은 장비 코드처럼 공통된 값을 기준으로 이력과 기준 정보를 연결하는 데 사용합니다. ## Machbase가 해결하는 문제 Machbase의 시계열 기능은 다음 작업을 함께 수행할 때 활용할 수 있습니다. | 작업 | 예 | 관련 기능 | |---|---|---| | 원본 이력 수집 | 센서 측정값과 설비 이벤트를 계속 저장 | TAG·LOG, SQL 입력과 SDK Append | | 필요한 구간 조회 | 설비 A의 최근 1시간 온도 변화 확인 | 태그·시간 조건, 인덱스와 실행 계획 | | 반복 통계 조회 | 긴 기간의 분·시간 단위 추이 비교 | TAG ROLLUP | | 데이터 해석 | 센서 코드에 장비명과 위치 연결 | 기준 정보와 JOIN | | 저장 기간 관리 | 보관 기간이 지난 원본 정리 | 지원 테이블의 Retention Policy | | 장애에 대비한 데이터 보호 | 백업과 복구 절차 검증 | Edition별 백업·복구 기능 | ROLLUP은 원본에서 구간 통계를 계산합니다. 압축은 저장에 필요한 공간을 줄이는 기술이고, Retention Policy는 오래된 데이터를 삭제하는 정책입니다. 세 기능의 목적은 서로 다르며, 집계가 있다는 이유만으로 원본을 삭제해도 되는 것은 아닙니다. 필요한 성능은 데이터 타입, 입력량, 동시 조회, 보관 기간과 서버 자원에 따라 달라집니다. 작은 실습으로 사용법을 익힌 뒤, 실제 데이터와 조회 조건으로 처리량과 지연 시간을 측정하십시오. 기능의 역할은 [핵심 개념](/dbms/core-concepts/)에서 더 자세히 설명합니다. ## 데이터에 맞는 테이블 선택 | 테이블 | 주 용도 | 선택할 때 확인할 점 | |---|---|---| | TAG | 태그 이름과 시간·거리 축을 가진 계측 이력 | 특정 태그의 구간 조회와 집계가 중심인지 확인합니다. | | LOG | 여러 항목을 가진 이벤트·로그 이력 | 새 기록을 계속 추가하고 시간 조건이나 검색 조건으로 읽는지 확인합니다. | | LOOKUP | 장비 코드, 매핑 같은 영속 기준 정보 | 메모리에 적재해 참조할 데이터의 크기와 변경 방식을 확인합니다. | | TRANSACTION | 행 단위 변경과 트랜잭션이 필요한 업무 데이터 | Standard Edition에서 관계형 DML과 트랜잭션이 필요한지 확인합니다. | | VOLATILE | 재시작 후 다시 만들 수 있는 공유 상태 | 서버 종료로 데이터가 사라져도 복원할 수 있는지 확인합니다. | Machbase DBMS 8.7.0에서 LOG 테이블은 `CREATE LOG TABLE`로 명시합니다. 유형을 생략한 `CREATE TABLE`은 TRANSACTION 테이블을 생성하며, TRANSACTION 테이블은 Standard Edition에서 지원합니다. 데이터의 산업 분야만으로 테이블을 결정하지는 않습니다. 변경·조인·보관 요구사항을 포함한 최종 선택은 [테이블 타입 선택](/dbms/data-modeling-table-design/table-types-selection-type/)에서 확인합니다. 이제 [10분 빠른 시작](../quick-start/)에서 이벤트 한 건을 저장하고 다시 읽어 봅니다. --- title: "1.2 10분 빠른 시작" url: https://docs.machbase.com/kr/dbms/getting-started/quick-start/ language: kr kind: page --- # 1.2 10분 빠른 시작 실행 중인 서버에 접속해 서비스 시작 이벤트 한 건을 저장하고 다시 읽어 봅니다. 추가 중심 이벤트에 맞는 LOG 테이블을 사용하며, SQL 실행 결과와 두 시간 컬럼의 의미를 확인합니다. 설치에 필요한 시간은 이 실습의 예상 시간에 포함하지 않습니다. ## 실행 전제 - Machbase DBMS 서버가 `127.0.0.1:5656`에서 실행 중입니다. - `machsql` 명령을 사용할 수 있습니다. - 테이블 생성·입력·조회·삭제 권한이 있는 실습 계정으로 접속할 수 있습니다. - 아래 명령은 초기 실습 계정 `SYS`와 비밀번호 `MANAGER`를 사용합니다. 비밀번호를 변경했다면 실제 값으로 바꿉니다. - `/tmp`에 SQL 파일을 저장할 수 있고, 실습용 이름 `DBMS_GS_QUICK`을 사용할 수 있습니다. 기존 업무 테이블과 이름이 겹치지 않는 실습 환경을 사용합니다. 예제 마지막의 `DROP TABLE`은 실습 테이블과 입력한 데이터를 삭제합니다. 서버가 아직 준비되지 않았다면 [설치, 배포, 업그레이드](/dbms/installation-deployment-upgrade/)와 [Linux Standard Edition 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux)를 먼저 참고하십시오. ## 대표 실행 예제 서비스 시작 이벤트 한 건을 LOG 테이블에 기록합니다. LOG 테이블은 `CREATE LOG TABLE`로 명시해서 생성하며, `_arrival_time` 컬럼이 자동으로 추가됩니다. 다음 명령으로 SQL 파일을 저장하고 실행합니다. ```bash cat > /tmp/dbms_gs_quick.sql <<'SQL' CREATE LOG TABLE DBMS_GS_QUICK ( EVENT_ID INTEGER, EVENT_TIME DATETIME, LEVEL VARCHAR(10), MESSAGE VARCHAR(40) ); INSERT INTO DBMS_GS_QUICK (EVENT_ID, EVENT_TIME, LEVEL, MESSAGE) VALUES ( 1, TO_DATE('2026-07-02 09:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'INFO', 'service started' ); SELECT _arrival_time, EVENT_ID, EVENT_TIME, LEVEL, MESSAGE FROM DBMS_GS_QUICK ORDER BY EVENT_ID; DROP TABLE DBMS_GS_QUICK; SQL machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER -f /tmp/dbms_gs_quick.sql ``` ### 결과 확인 각 SQL 단계에 오류가 없는지 확인합니다. SELECT 결과 한 행에 `EVENT_ID`가 `1`, `LEVEL`이 `INFO`, `MESSAGE`가 `service started`로 표시되어야 합니다. `EVENT_TIME`은 애플리케이션이 저장한 실제 이벤트 시각이고, `_arrival_time`은 DBMS가 이 예제에서 자동 기록한 서버 입력 시각입니다. 따라서 예제를 실행할 때마다 `_arrival_time` 값은 달라지며 `EVENT_TIME`과 같을 필요가 없습니다. `CREATE LOG TABLE`은 구조를 만들고 `INSERT`는 한 행을 추가합니다. `SELECT`는 읽을 컬럼을 지정하며, `ORDER BY EVENT_ID`는 결과 순서를 지정합니다. 마지막 `DROP TABLE`까지 성공하면 실습 테이블이 제거됩니다. 데이터를 더 살펴보려면 실행 전에 마지막 DROP 문을 제외하고, 조회를 마친 뒤 해당 실습 테이블만 정리합니다. 재실행 중 `DBMS_GS_QUICK`이 이미 존재한다는 오류가 나면 이전 실행이 `DROP TABLE` 단계에 도달하지 못했을 수 있습니다. `DESC DBMS_GS_QUICK;`으로 구조를 확인하고, 이전 실습에서 만든 테이블인 경우에만 `DROP TABLE DBMS_GS_QUICK;`을 실행한 뒤 재시도합니다. 접속 오류라면 먼저 서버 주소·포트·기동 상태와 계정 정보를 확인합니다. ## 이 예제에서 확인한 것 | 항목 | 확인 내용 | | --- | --- | | 서버 접속 | `machsql`로 `127.0.0.1:5656`에 접속 | | 테이블 생성 | `CREATE LOG TABLE`로 LOG 테이블을 명시적으로 생성 | | 데이터 입력 | `INSERT`와 `TO_DATE`로 이벤트 데이터 저장 | | 데이터 조회 | `SELECT`와 `ORDER BY`로 입력 결과 확인 | | 자동 컬럼 | LOG 테이블의 `_arrival_time`은 서버가 자동 기록 | | 정리 | `DROP TABLE`로 실습 테이블 삭제 | --- title: "1.3 기본 명령 치트시트" url: https://docs.machbase.com/kr/dbms/getting-started/command-cheatsheet/ language: kr kind: page --- # 1.3 기본 명령 치트시트 접속 명령은 운영체제의 터미널에서 실행하고, SQL은 접속한 `machsql`에서 실행합니다. 서버 주소, 포트와 계정은 실제 환경에 맞춥니다. 아래 접속 예제의 `MANAGER`는 빠른 시작에서 사용하는 초기 실습 비밀번호이며, 변경한 환경에서는 해당 계정의 비밀번호를 사용합니다. ## 터미널에서 실행할 명령 | 작업 | 명령 | |---|---| | 대화형 접속 | `machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER` | | SQL 파일 실행 | `machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER -f file.sql` | ## machsql에서 실행할 SQL | 작업 | 명령 | |---|---| | 테이블 목록 | `SHOW TABLES;` | | 컬럼과 타입 확인 | `DESC table_name;` | | TRANSACTION 테이블 생성 | `CREATE TRANSACTION TABLE table_name (...);` | | LOG 테이블 생성 | `CREATE LOG TABLE table_name (...);` | | 데이터 입력 | `INSERT INTO table_name VALUES (...);` | | 조건에 맞는 데이터 조회 | `SELECT ... FROM table_name WHERE ...;` | | 조회 결과 정렬 | `SELECT ... FROM table_name ORDER BY ...;` | | 테이블과 데이터 삭제 | `DROP TABLE table_name;` | `table_name`과 `...`은 실제 이름과 정의로 바꿔야 하는 자리 표시자입니다. 그대로 실행할 수 있는 SQL은 [빠른 시작](../quick-start/)에 있습니다. `DROP TABLE`은 테이블의 데이터도 삭제하므로 정리할 실습 테이블인지 먼저 확인합니다. Machbase DBMS 8.7.0의 `CREATE TABLE`은 유형을 생략하면 TRANSACTION 테이블을 생성합니다. TRANSACTION은 Standard Edition에서 지원합니다. 사용하려는 유형을 명시하면 예제의 의도가 분명해집니다. 전체 명령은 [machsql 레퍼런스](../../reference/command-line-tools/machsql/)를 참고하십시오. --- title: "1.4 다음에 읽을 문서 선택하기" url: https://docs.machbase.com/kr/dbms/getting-started/choose-next-doc/ language: kr kind: page --- # 1.4 다음에 읽을 문서 선택하기 빠른 시작에서는 한 행을 저장하고 조회했습니다. 실제 시스템을 만들 때는 데이터의 형태뿐 아니라 변경 방식, 시간 기준, 조회 패턴과 보관 기간을 함께 결정합니다. ## 설계 전에 답할 질문 - 같은 대상의 값을 시간에 따라 누적합니까, 현재 상태를 덮어씁니까? - 측정·발생 시각과 서버 수신 시각 중 어떤 시각이 조회의 기준입니까? - 특정 태그의 구간 조회, 여러 컬럼 검색, 키 조회와 조인 중 무엇을 자주 수행합니까? - 늦게 도착하거나 중복된 데이터, 잘못된 값을 어떻게 처리합니까? - 원본과 집계는 각각 얼마나 오래 필요하며, 재시작 후 반드시 남아야 하는 데이터는 무엇입니까? - 필요한 기능을 어느 Edition에서 사용할 수 있습니까? ## 다음 경로 | 해야 할 일 | 다음 문서 | |---|---| | 시간 이력·저장·조회 원리 이해 | [핵심 개념](../../core-concepts/) | | 테이블 유형과 변경 정책 결정 | [테이블 타입 선택과 스키마 설계](../../data-modeling-table-design/) | | 센서·계측 이력 저장 | [TAG 테이블 활용](../../tag-table-usage/) | | 이벤트와 로그 수집·검색 | [LOG 테이블 활용](../../log-table-usage/) | | 장기간의 태그 통계 조회 | [TAG 테이블을 위한 ROLLUP 활용](../../tag-rollup-usage/) | | 기준 정보와 업무 데이터 관리 | [LOOKUP](../../lookup-table-usage/), [TRANSACTION](../../rdb-table-usage/) | | 다시 생성할 수 있는 상태 관리 | [VOLATILE 테이블 활용](../../volatile-table-usage/) | | 애플리케이션에서 입력·조회 | [개발 및 애플리케이션 연동](../../development-tools-integration/) | | SQL 문법과 타입 확인 | [SQL 레퍼런스](../../reference/sql/) | | 운영·백업·권한 설정 | [운영, 설정, 복구](../../operations-configuration-recovery/), [계정과 권한](../../security-access-control/) | 처음에는 대표 데이터와 자주 쓰는 조회를 정해 작은 규모로 실행합니다. 입력 속도, 조회 지연과 저장 공간을 측정한 뒤 수집 방식, 인덱스, 집계와 보관 정책을 조정합니다. --- title: "2. 핵심 개념" url: https://docs.machbase.com/kr/dbms/core-concepts/ language: kr kind: section --- # 2. 핵심 개념 1장에서 SQL로 데이터를 저장하고 읽는 흐름을 확인했다면, 이 장에서는 그 동작을 설계와 운영의 관점에서 이해합니다. 같은 데이터라도 시간의 의미, 변경 방식과 조회 범위에 따라 적절한 테이블과 저장 전략이 달라집니다. 예를 들어 설비의 현재 온도와 지난달 온도 이력은 보관할 행의 수가 다릅니다. 월평균 온도와 순간적인 이상값 탐지는 필요한 데이터의 해상도가 다릅니다. 이 차이를 알면 테이블, 인덱스, ROLLUP과 보관 정책을 목적에 맞게 선택할 수 있습니다. ## 이 장의 구성 | 절 | 다루는 질문 | |---|---| | [데이터 모델 개념](concepts/) | 무엇을 한 행으로 저장하며, 시간·NULL·중복·변경을 어떻게 해석합니까? | | [저장 및 실행 구조](storage-execution-architecture/) | 필요한 데이터를 어떻게 찾으며, 저장·인덱스·캐시는 어떤 비용을 줄입니까? | | [주요 기능과 용어 구분](features-concepts/) | 원본·집계·보관·백업은 어떤 목적을 각각 담당합니까? | | [Edition 개념](concepts-edition/) | 단일 서버와 분산 구성 중 무엇을 선택하고 어떤 기능 차이를 확인합니까? | 예제는 개념 설명을 위한 작은 데이터 집합을 사용합니다. 기능별 SQL, 지원 범위와 운영 절차는 본문에서 연결한 상세 문서에서 확인합니다. 개념을 익힌 뒤 [테이블 타입 선택과 스키마 설계](../data-modeling-table-design/)에서 자신의 데이터에 적용하십시오. --- title: "2.1 데이터 모델 개념" url: https://docs.machbase.com/kr/dbms/core-concepts/concepts/ language: kr kind: page --- # 2.1 데이터 모델 개념 데이터 모델은 무엇을 한 건으로 저장하고, 어떤 값으로 구분하며, 어떻게 변경하고 조회할지를 정한 규칙입니다. 시계열 모델에서는 측정 대상과 시각, 값의 의미를 함께 정해야 합니다. 컬럼 이름을 정하기 전에 이 규칙부터 확인하면 입력과 분석 결과의 해석이 일관됩니다. ## 시계열 데이터 이해하기 시계열 데이터는 시간에 따른 관측이나 사건의 기록입니다. 온도 측정, 체결 이력, 서비스 오류 기록이 대표적입니다. 일정 간격으로 수집할 수도 있고 사건이 발생할 때만 기록할 수도 있습니다. 저장 순서가 발생 순서와 같다는 보장은 없습니다. ### 한 행의 의미와 식별자 온도 이력의 한 행을 “센서 한 개가 특정 시각에 측정한 값”으로 정했다면 센서 식별자, 측정 시각과 값이 필요합니다. 식별자는 데이터가 어느 대상에서 나온 것인지 구분하고, 시각은 대상의 변화 순서를 해석하는 기준입니다. 여러 센서가 같은 시각에 측정할 수 있으므로 시각만으로 행을 유일하게 구분할 수는 없습니다. 같은 센서의 같은 시각 데이터도 재전송으로 반복될 수 있습니다. 중복을 허용할지, 제거할지, 별도 이벤트 번호가 필요한지를 결정해야 합니다. TAG의 `PRIMARY KEY`는 태그를 식별하는 역할이며 일반 관계형 테이블처럼 각 측정 행의 이름이 유일하다는 뜻은 아닙니다. TAG 데이터 중복 처리는 [TAG 테이블 운영](../../tag-table-usage/operations-lifecycle/)에서 확인합니다. ### 값, 단위와 품질 숫자만 저장하면 단위와 의미를 알 수 없습니다. 같은 `23.5`라도 온도, 압력과 전압은 서로 다른 데이터입니다. 태그 정의나 기준 정보에 단위와 측정 위치를 관리하고, 하나의 시계열에서 값의 의미가 바뀌지 않게 합니다. `NULL`과 `0`도 구분합니다. 장비가 0을 측정한 것과 측정에 실패해 값을 알 수 없는 것은 다릅니다. 필요하면 품질이나 상태를 별도 컬럼으로 기록합니다. 수집되지 않은 구간에는 행 자체가 없을 수 있으므로, NULL 행이 있는 경우와도 구분해야 합니다. 일반적인 `AVG` 같은 집계는 NULL을 제외한 값에 적용됩니다. 누락된 값을 무조건 0으로 채우거나 서로 다른 단위를 함께 집계하면 분석 결과가 달라집니다. 지원 집계 함수의 정확한 NULL 처리는 [함수 레퍼런스](../../reference/sql/functions/functions-full/)를 참고하십시오. ### 시계열 워크로드의 특성 많은 시계열 시스템은 새 행을 지속적으로 입력하고, 특정 대상의 시간 범위를 조회합니다. 최신 값을 확인하는 모니터링과 장기간 통계를 계산하는 분석을 함께 수행하기도 합니다. 이런 패턴이 추가 중심 입력, 구간 조회와 ROLLUP을 사용하는 이유입니다. 그러나 과거 데이터가 항상 불변인 것은 아닙니다. 장비 시각의 오류, 센서 보정이나 중복 수집으로 정정이 필요할 수 있습니다. 최근 데이터보다 오래된 데이터를 자주 읽는 업무도 있습니다. 실제 입력·조회·정정 패턴을 측정해 설계하십시오. ### 테이블 타입의 역할 | 테이블 타입 | 개념적 역할 | |---|---| | TAG | 이름과 시간·거리 축을 가진 계측 이력 | | LOG | 새 기록을 계속 추가하는 이벤트와 로그 | | TRANSACTION | 트랜잭션과 행 단위 변경이 필요한 업무 데이터 | | LOOKUP | 메모리에 적재해 참조하는 영속 기준 정보 | | VOLATILE | 재시작 후 다시 만들 수 있는 공유 메모리 상태 | 이력과 기준 정보를 분리하면 장비 이름이나 위치를 모든 측정 행에 반복 저장할 필요가 줄어듭니다. 다만 최신 기준 정보와 과거 이력을 조인하면 결과에도 최신 이름이나 위치가 표시됩니다. 발생 당시의 정보가 필요하면 해당 값을 이력에 남기거나 기준 정보의 변경 이력을 별도로 설계해야 합니다. ### 관계형 업무 모델과 시계열 모델 관계형 모델과 시계열 모델은 서로 배타적인 개념이 아닙니다. 관계형 DBMS에서도 시계열을 저장하고 인덱스·파티션·집계를 사용할 수 있으며, Machbase도 테이블과 SQL, 조인과 관계형 변경 기능을 제공합니다. 비교할 때는 제품 이름보다 주된 작업을 기준으로 판단합니다. | 관점 | 관계형 업무 데이터의 예 | 시계열 이력의 예 | |---|---|---| | 행의 의미 | 주문 한 건의 현재 상태 | 센서 측정 한 건 또는 이벤트 한 건 | | 변경 패턴 | 키로 찾은 행의 수정·삭제 | 새 기록 추가, 필요한 범위의 정정·정리 | | 조회 패턴 | 키 조회, 조건 검색, 업무 테이블 조인 | 대상·시간 범위 조회, 추이와 구간 통계 | | 일관성 요구 | 여러 변경을 함께 확정하거나 취소 | 수집 누락·중복·지연과 조회 가능 시점 관리 | | 보관 설계 | 업무 수명과 변경 이력 | 원본 해상도, 집계 주기와 보관 기간 | Machbase의 LOG·TAG 저장과 입력 특성을 TRANSACTION·LOOKUP·VOLATILE에 그대로 적용하지 마십시오. 최종 선택은 [테이블 타입 선택](../../data-modeling-table-design/table-types-selection-type/)과 [데이터 변경 정책](../../data-modeling-table-design/alter-data-mutation-policy/)에서 확인합니다. ## 쓰기 중심 워크로드와 append-only 모델 append는 기존 값의 덮어쓰기 대신 새 행을 추가하는 방식입니다. 예를 들어 설비 상태가 `RUNNING`에서 `STOPPED`로 바뀔 때 새 이벤트를 기록하면 이전 상태와 전환 시점을 보존할 수 있습니다. 현재 상태를 바로 찾는 테이블과 상태 변화 이력을 저장하는 테이블을 별도로 운영할 수도 있습니다. ### 테이블 타입과 변경 모델 LOG는 추가 중심 이력 테이블이며 일반적인 행 `UPDATE`를 지원하지 않습니다. TAG는 계측 이력을 추가하고, 지원되는 조건과 Edition 범위에서 DATA 값을 보정할 수 있습니다. 태그 이름이나 시간축까지 임의로 수정하는 일반 행 변경과는 다릅니다. TRANSACTION은 관계형 DML과 명시적 트랜잭션을 제공하고, LOOKUP·VOLATILE은 기준 정보나 상태 변경에 사용합니다. 한 테이블의 변경 기능이 다른 유형에서도 같다고 가정하지 말고 [데이터 변경 정책](../../data-modeling-table-design/alter-data-mutation-policy/)을 확인합니다. 추가 중심 설계는 반복 입력과 과거 행의 임의 갱신을 함께 처리할 때의 경합을 줄이는 데 도움이 됩니다. 그렇다고 DBMS 내부의 동기화나 장애 복구 처리가 모두 없어지는 것은 아닙니다. 저장 구조만으로 잠금 없음이나 특정 처리량을 보장할 수는 없습니다. ### SQL 입력과 SDK Append SQL `INSERT`는 SQL 문장으로 값을 입력하고 실행 결과를 확인하는 경로입니다. SDK Append는 연속 수집을 위해 여러 행을 전송하는 입력 API입니다. 버퍼링, 전송, 오류 확인과 종료 절차를 해당 SDK의 계약에 맞게 처리해야 합니다. 데이터를 애플리케이션 버퍼에 넣은 시점, 서버가 처리한 시점과 장애 뒤에도 복구할 수 있는 시점을 같은 것으로 취급하지 마십시오. LOG·TAG의 Append는 TRANSACTION 테이블의 명시적 트랜잭션에서 `ROLLBACK`할 대상이 아닙니다. TRANSACTION 테이블에 대한 Append의 트랜잭션 참여와 오류 처리는 사용하는 SDK의 계약을 확인합니다. 입력 확인과 재시도 방법은 [데이터 입력과 반출](../../development-tools-integration/data-input-load-export/)을 참고하십시오. ### 정정, 스키마 변경과 보관 잘못된 LOG 이벤트는 보정 이벤트를 추가하는 방식으로 이력을 남길 수 있습니다. 삭제 후 다시 입력할 때는 LOG에서 허용하는 시간 기반 삭제 범위와 입력 순서를 먼저 확인해야 합니다. 특정 행 하나만 임의로 지울 수 있다고 가정하지 마십시오. TAG 값 보정은 [TAG 데이터 보정 설계](../../tag-table-usage/tag-data-update-correction/#design-correction-tag)를 따릅니다. 컬럼 추가·삭제와 타입 변경도 별개의 기능입니다. LOG나 TAG의 모든 스키마 변경이 금지된 것은 아니며, 유형과 DATA·METADATA 영역에 따라 지원 범위가 다릅니다. [DDL 문법](../../reference/sql/syntax/ddl-syntax/)을 확인한 뒤 적용합니다. 원본을 계속 추가하면 저장 공간이 증가합니다. 원본 보관 기간과 ROLLUP 통계의 사용 목적을 별도로 정하십시오. 집계 생성만으로 원본이 삭제되지는 않습니다. ## 시간 모델과 _arrival_time ### 발생 시각과 수신 시각 발생 시각은 장비에서 측정하거나 사건이 발생한 시각이고, 수신 시각은 DBMS가 기록을 받은 시각입니다. 09:00에 측정한 값을 네트워크 복구 후 09:05에 받았다면 두 시각의 차이는 5분입니다. 측정 추이를 보려면 발생 시각이, 수집 지연을 보려면 두 시각의 비교가 필요합니다. 시간 데이터에는 정밀도, 시간대와 시계 정확도도 구분해야 합니다. 나노초 단위로 표현할 수 있다는 사실이 센서 시계도 나노초 단위로 정확하다는 뜻은 아닙니다. 문자열을 DATETIME으로 해석하거나 표시할 때의 시간대는 [시간대 설정](../../reference/configuration/configuration-timezone/)을 참고하십시오. ### LOG 테이블의 `_arrival_time` LOG에는 `_arrival_time` DATETIME 컬럼이 자동으로 추가됩니다. 값을 생략하는 일반적인 입력에서는 서버 입력 시각을 기록합니다. 별도의 발생 시각이 필요하면 일반 DATETIME 컬럼을 정의합니다. ```sql CREATE LOG TABLE device_events ( device_id VARCHAR(20), event_time DATETIME, status VARCHAR(20) ); ``` `_arrival_time`을 명시하는 입력 경로도 있으므로 이 컬럼이 언제나 실제 수신 시각이라고 단정할 수는 없습니다. 명시 입력의 시간 순서와 제약은 [LOG 시간 모델](../../log-table-usage/arrival-time-model/)을 확인합니다. ```sql SELECT device_id, event_time, status FROM device_events WHERE _arrival_time >= TO_DATE('2026-07-03 09:00:00', 'YYYY-MM-DD HH24:MI:SS') AND _arrival_time < TO_DATE('2026-07-03 10:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY _arrival_time; ``` 시작 시각은 포함하고 끝 시각은 제외하는 `[시작, 끝)` 범위를 사용하면 이어지는 구간의 경계값을 두 번 집계하는 실수를 줄일 수 있습니다. `BETWEEN`은 양쪽 경계를 모두 포함하므로 목적에 맞게 선택합니다. LOG의 `DURATION`은 `_arrival_time`을 기준으로 구간을 제한합니다. ### TAG 테이블의 BASETIME 컬럼 시간축 TAG 테이블은 `BASETIME` 속성의 DATETIME 컬럼에 애플리케이션이 시각을 입력합니다. LOG의 자동 `_arrival_time` 컬럼을 사용하지 않습니다. ```sql CREATE TAG TABLE sensor_values ( name VARCHAR(128) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); INSERT INTO sensor_values VALUES ('temp_sensor_01', TO_DATE('2026-07-03 08:55:00', 'YYYY-MM-DD HH24:MI:SS'), 23.1); ``` 이 예에서 `name`은 센서를, `time`은 측정 시각을, `value`는 측정값을 나타냅니다. `SUMMARIZED`는 ROLLUP 등 관련 통계 기능이 사용할 대표 숫자 컬럼을 지정합니다. 테이블을 생성한 것만으로 원하는 모든 주기의 집계가 생기는 것은 아닙니다. TAG에는 시간 대신 수치 축을 사용하는 BASE DISTANCE 모델도 있습니다. 거리·위치에 따른 계측에는 이 모델을 검토하되 시간축 전용 함수와 정책을 그대로 적용하지 마십시오. [TAG 스키마](../../tag-table-usage/table-structure-schema/)에서 축별 규칙을 설명합니다. ### 두 시간 모델의 비교 | 항목 | LOG | 시간축 TAG | |---|---|---| | 특수 시간 컬럼 | 자동 생성되는 `_arrival_time` | 선언한 `BASETIME` 컬럼 | | 입력 시각의 의미 | 기본 입력에서는 서버 시각, 명시 입력은 해당 값 | 애플리케이션이 지정한 시각 | | 별도 발생 시각 | 일반 DATETIME 컬럼에 저장 가능 | BASETIME을 발생 시각으로 사용 가능 | | 주요 설계 질문 | 어떤 이벤트와 필드를 검색할 것인가 | 어떤 태그의 어느 구간을 분석할 것인가 | 발생 시각이 필요하다는 이유만으로 LOG를 배제할 필요는 없습니다. 시간 의미와 함께 태그 구조, 검색·집계, 수정·삭제 조건으로 테이블을 선택하십시오. LOOKUP·VOLATILE·TRANSACTION의 DATETIME은 일반 컬럼이며 LOG·TAG의 특수 시간축 기능이 자동으로 부여되지 않습니다. 행이 저장된 순서와 결과를 보여 줄 순서도 구분합니다. 결과 순서가 중요하면 `ORDER BY`를 명시하고, 동일 시각의 행까지 구분해야 하면 추가 정렬 기준을 정합니다. --- title: "2.2 저장 및 실행 구조" url: https://docs.machbase.com/kr/dbms/core-concepts/storage-execution-architecture/ language: kr kind: page --- # 2.2 저장 및 실행 구조 조회 시간은 SQL 문장의 길이만으로 결정되지 않습니다. 얼마나 많은 데이터를 읽는지, 조건으로 읽을 범위를 얼마나 줄일 수 있는지, 정렬·조인·집계에 어떤 처리가 필요한지가 중요합니다. 이 절에서는 저장, 인덱스와 캐시를 각각 어떤 비용을 줄이는 기술인지 설명합니다. ## Machbase 아키텍처 개요 사용자는 `machsql`이나 SDK를 통해 서버에 입력·조회 요청을 보냅니다. 서버는 SQL의 문법, 객체와 권한을 확인하고 실행 방법을 결정한 뒤 저장 데이터에 접근해 결과를 반환합니다. 저장·실행 방식은 테이블 유형과 Edition에 따라 다릅니다. ### Standard Edition 구조 Standard Edition은 단일 데이터베이스 서버에서 SQL 처리와 데이터 저장을 수행합니다. LOG·TAG의 시계열 입력뿐 아니라 TRANSACTION의 관계형 변경, LOOKUP·VOLATILE의 기준 정보와 상태 관리도 각각의 테이블 특성에 맞는 경로로 처리합니다. ```text 클라이언트: machsql 또는 SDK │ 입력·조회 요청 ▼ Machbase DBMS 서버 SQL 분석과 실행 계획 테이블별 저장·인덱스 접근 메모리 버퍼와 백그라운드 처리 │ ▼ 테이블 유형에 따른 저장 데이터 ``` 이 그림은 역할을 나타낸 개념도입니다. 모든 요청이 같은 저장 경로를 거치거나 API 호출 즉시 디스크 파일에 기록된다는 의미는 아닙니다. ### Cluster Edition 구조 Cluster Edition은 여러 노드가 역할을 나누어 수행합니다. 일반적인 애플리케이션 SQL 접속은 Broker를 통해 이루어지며, Warehouse는 시계열 데이터를 저장하고 쿼리를 실행합니다. Coordinator는 클러스터 메타데이터와 노드 상태를 관리하고, Lookup은 기준 정보 처리를, Deployer는 배포와 노드 관리를 담당합니다. 데이터를 여러 그룹에 분산하는 것은 처리량과 용량을 나누는 일이고, 같은 그룹에서 복제하는 것은 장애에 대비하는 일입니다. 두 목적은 다릅니다. 노드를 추가한다고 모든 쿼리가 같은 비율로 빨라지거나 모든 장애를 자동으로 극복하는 것은 아닙니다. 구성별 기능 차이는 [Edition 개념](../concepts-edition/)에서, 실제 배포와 장애 대응은 [Cluster 설치](../../installation-deployment-upgrade/cluster-edition/)와 [Cluster 운영](../../operations-configuration-recovery/cluster/)에서 확인합니다. ### 입력과 조회의 흐름 입력은 대상 테이블·컬럼 확인, 값 변환과 검증, 데이터 전달·저장 단계로 생각할 수 있습니다. SQL과 Append API는 처리 결과를 확인하는 방식이 다르므로 애플리케이션은 선택한 경로의 오류 처리와 완료 조건을 따라야 합니다. 조회는 SQL 분석과 실행 계획 수립, 대상 데이터 접근, 조건 평가와 집계·조인·정렬, 결과 반환으로 이어집니다. 모든 데이터가 반드시 같은 순서로 처리되는 것은 아니며 실제 실행 계획에 따라 접근 순서가 달라집니다. ## 컬럼형 저장과 압축 ### 행 지향과 컬럼 지향 행 지향 저장은 한 행에 속한 값을 함께 다루는 방식이고, 컬럼 지향 저장은 같은 컬럼의 값을 묶어 다루는 방식입니다. 아래 그림은 두 방식의 차이를 나타낸 예시이며 실제 파일 배치를 그대로 표현한 것은 아닙니다. ```text 논리적 행: (시각1, 센서A, 23.1) (시각2, 센서A, 23.5) (시각3, 센서B, 18.0) 행 지향: [시각1, 센서A, 23.1] [시각2, 센서A, 23.5] ... 컬럼 지향: [시각1, 시각2, 시각3] [센서A, 센서A, 센서B] [23.1, 23.5, 18.0] ``` Machbase의 LOG·TAG 시계열 저장은 컬럼 단위 접근과 압축을 활용합니다. 많은 행에서 일부 컬럼만 읽는 분석에서는 읽을 데이터의 양을 줄이는 데 도움이 됩니다. 다만 온도 평균을 조회하더라도 센서·시각 조건이 있으면 해당 조건을 평가할 데이터도 필요합니다. 행 지향 시스템도 인덱스나 파티션을 이용해 필요한 범위만 읽을 수 있습니다. “행 지향은 항상 전체 행을 읽고 컬럼 지향은 언제나 빠르다”는 식으로 비교하지 말고, 읽을 행·컬럼 수와 접근 경로를 함께 살펴보십시오. TRANSACTION의 관계형 저장이나 LOOKUP·VOLATILE의 메모리 특성을 LOG·TAG와 같은 구조로 해석해서도 안 됩니다. ### 압축이 효과적인 조건 같은 컬럼에는 같은 타입의 값이 모이고, 센서 값이나 시각에는 반복되거나 비슷한 패턴이 나타날 수 있습니다. 이런 특성은 압축에 유리합니다. 반면 잡음이 큰 값, 불규칙한 문자열과 입력 순서가 섞인 데이터는 다른 결과를 보일 수 있습니다. 시계열 시각이 반드시 단조 증가하는 것은 아닙니다. 늦게 도착한 측정값과 여러 수집원의 입력이 섞일 수 있습니다. 실제 압축률과 처리 성능은 타입, 값의 분포, 입력 순서와 설정에 따라 측정해야 하며 특정 압축 알고리즘이나 비율을 전제하지 않습니다. ### 파티션과 읽기 범위 파티션은 데이터를 관리 가능한 부분으로 나누는 단위입니다. 조회 조건과 저장된 범위 정보를 활용해 관련 없는 부분을 건너뛰면 읽기 작업을 줄일 수 있습니다. 이를 파티션 가지치기(partition pruning)라고 합니다. LOG·TAG의 저장 단위가 사용자가 지정한 하루·한 달과 반드시 일치하는 것은 아닙니다. 태그와 축의 조건, 데이터 분포와 테이블별 저장 구조에 따라 실제 접근 범위가 달라집니다. 시간 조건을 작성했다는 이유만으로 필요한 데이터만 읽는다고 단정하지 말고 실행 계획과 측정 결과를 확인합니다. ## 인덱싱 기본 원리 인덱스는 조건에 맞는 데이터를 찾는 접근 경로입니다. 찾을 대상이 전체 데이터의 작은 부분이면 도움이 될 수 있지만, 대부분의 행을 읽는 집계에서는 다른 경로가 유리할 수 있습니다. 인덱스는 저장 공간과 입력·변경 시 관리 비용도 필요합니다. | 테이블 유형 | 접근 경로를 생각할 때의 기준 | |---|---| | TAG | 태그 이름과 시간·거리 축의 범위 | | LOG | `_arrival_time` 조건과 지원하는 검색 인덱스 | | TRANSACTION | PRIMARY KEY, UNIQUE와 일반 인덱스 | | LOOKUP·VOLATILE | 메모리에 적재된 키와 지원하는 보조 인덱스 | 예를 들어 “전체 센서의 한 달 평균”과 “센서 A의 최근 1분 값”은 읽을 데이터의 비율이 다릅니다. 같은 테이블이라도 두 쿼리에 같은 성능을 기대하기 어렵습니다. 조인에서는 각 입력의 행 수와 조인 조건도 중요합니다. 지원 인덱스와 제한은 [스키마 객체 정의](../../data-modeling-table-design/schema-objects-definition/)에서, 측정과 조정 방법은 [인덱스 튜닝](../../performance-tuning/index-tuning/)에서 확인합니다. ## Cache와 실행 계획 개념 ### SQL 실행 과정 서버는 SQL의 문법과 타입·객체를 확인하고, 조건과 인덱스 등으로 실행 가능한 접근 경로를 정한 뒤 실제 데이터를 처리합니다. 실행 계획은 이 처리 방법을 나타냅니다. 계획을 만드는 비용과 계획에 따라 데이터를 읽는 비용은 서로 다릅니다. ### 실행 계획 재사용과 PVO Cache PVO Statement Cache는 재사용 가능한 SQL의 파싱·검증·최적화 결과와 실행 계획을 재사용해 반복 준비 비용을 줄입니다. 조회 결과 행을 저장하는 캐시가 아니므로 계획을 재사용해도 데이터를 읽고 조건을 평가하는 작업은 필요합니다. Min-Max Cache처럼 저장 데이터의 범위 정보를 활용하는 캐시는 읽을 대상을 줄이는 목적을 가집니다. 실행 계획 캐시와 데이터 접근용 캐시를 하나로 생각하지 마십시오. 어떤 캐시를 크게 할지는 적중률과 메모리 사용량을 확인한 뒤 판단합니다. ### EXPLAIN으로 실행 계획 확인 다음 예제는 [데이터 모델 개념](../concepts/#time-model-arrival-time)에서 만든 `sensor_values` 테이블을 사용합니다. ```sql EXPLAIN SELECT AVG(value) FROM sensor_values WHERE name = 'temp_sensor_01' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD') AND time < TO_DATE('2026-07-03', 'YYYY-MM-DD'); ``` 실행 계획으로 데이터 접근과 조건 적용 방법을 확인합니다. 실행 계획이 존재한다는 사실만으로 실제 응답 시간이나 디스크 읽기량까지 알 수 있는 것은 아니므로 대표 데이터에서 실행 시간도 함께 측정합니다. 캐시가 비어 있는 첫 실행과 재사용한 실행의 조건을 구분하면 결과를 해석하기 쉽습니다. PVO Cache의 적중·제거 통계는 `V$PVO_CACHE_STAT`, 캐시된 SQL은 `V$PVO_CACHE_LIST`에서 확인합니다. 설정과 진단은 [캐시와 메모리 튜닝(영문)](/dbms/performance-tuning/cache-tuning-memory/#pvo-cache)을 참고하십시오. --- title: "2.3 주요 기능과 용어 구분" url: https://docs.machbase.com/kr/dbms/core-concepts/features-concepts/ language: kr kind: page --- # 2.3 주요 기능과 용어 구분 Machbase DBMS의 장기 데이터 운영에 필요한 ROLLUP, Retention Policy, Backup·Restore·Mount의 역할과 선택 기준을 설명합니다. 생성 구문과 운영 절차는 각 기능의 상세 문서에서 다룹니다. 원본은 개별 사건이나 측정값을 다시 분석하는 데 사용하고, 집계는 많은 원본을 요약해 반복 조회에 사용합니다. 보관 정책은 무엇을 언제 삭제할지 정하고, 백업은 장애가 발생했을 때 복구할 자료를 준비합니다. 이 목적들을 구분해야 집계가 남아 있다는 이유로 필요한 원본을 삭제하거나, 복제가 있다는 이유로 백업을 생략하는 일을 피할 수 있습니다. - **[ROLLUP 통계의 역할](#role-statistics-rollup)** -- 반복 집계의 조회 비용을 줄이는 방법 - **[Retention Policy의 역할](#role-retention-policy)** -- 기간에 따라 데이터를 자동 정리하는 방법 - **[Backup·Restore·Mount의 관계](#concepts-backup-restore-mount)** -- 보호, 복구, 조회 목적의 구분 ## ROLLUP 통계의 역할 TAG 테이블의 장기간 데이터를 매번 원시 행에서 집계하면 조회 범위가 커질수록 처리 비용도 증가합니다. ROLLUP은 시간축 TAG의 지정한 숫자 컬럼 등을 구간별로 집계해 반복 조회에 사용하는 기능입니다. 집계할 컬럼과 집계 방식은 ROLLUP 정의에 따라 결정됩니다. 기본 ROLLUP은 초(SEC), 분(MIN), 시간(HOUR) 계층을 구성합니다. 테이블을 만들 때 `WITH ROLLUP`을 지정하거나, 일반 `CREATE ROLLUP`으로 필요한 주기와 조건을 지정할 수 있습니다. 생성되는 객체의 이름이나 저장 구조에 의존하지 말고 공개된 ROLLUP SQL로 관리하십시오. ### 선택 기준 | 조회 패턴 | 선택 | | --- | --- | | 장기간의 분·시간 단위 통계를 반복 조회 | 기본 ROLLUP 검토 | | 집계 주기나 필터 조건을 직접 지정 | 주기·조건을 지정한 일반 ROLLUP 검토 | | 직접 작성한 집계 SELECT의 결과를 별도 TAG에 저장 | Standard Edition의 Custom ROLLUP 검토 | | 첫 값·마지막 값이 필요 | 확장 ROLLUP 검토 | | 원시 값 조회가 대부분이거나 집계 빈도가 낮음 | ROLLUP 없이 시작하고 실행 시간을 측정 | ROLLUP은 원시 데이터를 대신하는 보관 정책이 아닙니다. 원시 데이터의 보관 기간은 Retention Policy로 별도 설계하고, TAG 데이터를 수정했다면 해당 범위의 ROLLUP 재구성 여부도 확인합니다. ### 집계 결과를 해석할 때 집계는 정보의 해상도를 낮춥니다. 1분 평균만 남기면 그 1분 안에 있었던 순간적인 이상값이나 개별 측정 순서를 복원할 수 없습니다. 최대·최소값을 함께 보관하면 범위를 알 수 있지만, 이 역시 원본의 모든 정보를 보존하는 것은 아닙니다. 여러 구간의 평균을 다시 평균 내면 전체 평균과 달라질 수 있습니다. 예를 들어 2건의 평균이 10이고 8건의 평균이 20이면 전체 평균은 `(2 × 10 + 8 × 20) / 10 = 18`이며, 두 평균을 단순히 평균 낸 15가 아닙니다. 재집계에는 합계와 유효 건수 등 필요한 통계를 함께 사용하고, ROLLUP의 지원 조회 함수를 따릅니다. ROLLUP 처리는 원본 입력과 별도로 진행되므로 최신 원본과 집계의 반영 시점이 다를 수 있습니다. 지연 도착이나 값 보정이 있다면 원본 범위, 집계 진행 상태와 재구성 필요성을 함께 확인합니다. 자세한 생성 구문, 조회 함수, 재구성 절차는 [TAG·ROLLUP 활용](/dbms/tag-rollup-usage/)을 참고하십시오. ## Retention Policy의 역할 Retention Policy는 LOG 또는 TAG 테이블에서 보관 기간을 넘긴 데이터를 정해진 주기로 정리합니다. 데이터가 계속 유입되는 테이블의 저장 공간을 운영자가 반복적인 삭제 작업 없이 관리할 때 사용합니다. | 요구사항 | 선택 | | --- | --- | | 일정 기간이 지난 데이터를 계속 자동 정리 | Retention Policy | | 잘못 입력한 특정 범위를 즉시 제거 | 테이블 유형이 지원하는 `DELETE` | | 지원 테이블의 전체 데이터를 비움 | `TRUNCATE TABLE` | Retention은 적용 즉시 모든 오래된 데이터가 사라진다는 의미가 아닙니다. 정책의 실행 주기, 대상 테이블 지원 범위와 실제 삭제 상태를 함께 확인해야 합니다. LOOKUP, VOLATILE, TRANSACTION 테이블의 수명 관리는 해당 테이블이 지원하는 명시적 DML로 처리합니다. 보관 기간은 입력량과 함께 저장 용량을 결정합니다. 초당 입력 건수에 보관 초 수를 곱하면 원본 행 수의 대략적인 규모를 예상할 수 있지만, 실제 디스크 용량에는 타입·압축·인덱스· 복제·백업이 영향을 줍니다. 원본을 지우기 전에 집계의 범위와 보관 기간, 감사·재분석에 필요한 해상도를 확인합니다. ROLLUP을 생성했다고 원본의 보관 기간이 자동 변경되지는 않습니다. 정책 생성·적용·해제 구문과 운영 점검은 [데이터 보존 정책](/dbms/operations-configuration-recovery/policy-data-retention/)을 참고하십시오. ## Backup·Restore·Mount의 관계 세 기능은 모두 백업 데이터와 관련되지만 결과가 다릅니다. | 기능 | 목적 | 운영 서버 | 결과 | | --- | --- | --- | --- | | Backup | 복구에 사용할 복사본 생성 | 실행 중 수행 가능 | 별도 경로에 백업 생성 | | 인스턴스 Restore | 백업으로 인스턴스 복구 | 오프라인 절차 필요 | 운영 데이터베이스를 복구 | | Mount | 백업 내용을 읽기 전용으로 확인 | 실행 중 수행 가능 | 백업을 별도 이름으로 조회 | 인스턴스 복원과 별개로, 논리 데이터베이스를 복원하는 `RESTORE DATABASE` SQL도 있습니다. 이 명령은 실행 중인 서버에서 수행하므로 오프라인 인스턴스 복원과 대상·절차를 구분합니다. Backup이 성공했다는 사실만으로 복구 절차까지 검증된 것은 아닙니다. 지원하는 Edition에서 Mount로 내용을 조회하거나 격리 환경에서 Restore를 수행해 백업을 검증합니다. 백업 경로의 권한과 보존 주기도 함께 관리하십시오. Mount는 백업을 운영 데이터로 되돌리지 않으며, 마운트한 데이터에는 쓸 수 없습니다. Restore와 Mount는 Standard Edition 기능이므로 Cluster 환경에서는 해당 Edition의 백업·장애 복구 절차를 확인합니다. 운영 계획에서는 허용 가능한 데이터 손실 구간(RPO)과 서비스 복구에 허용되는 시간(RTO)을 정합니다. 전자는 백업·복제 간격을, 후자는 복구할 데이터 규모와 실제 복원 시간을 검토하는 기준입니다. 특정 Edition이나 백업 주기만으로 두 목표가 자동 보장되지는 않습니다. 복제본에도 잘못된 삭제가 반영될 수 있으므로 복제와 백업의 목적을 구분합니다. 명령, 권한, Edition별 지원 범위와 복구 순서는 [백업·복원·마운트](/dbms/operations-configuration-recovery/backup-restore-mount/)를 참고하십시오. 여러 logical database를 운용한다면 [다중 데이터베이스 운영](/dbms/operations-configuration-recovery/multi-database/)도 함께 확인하십시오. ## 입력 경로 비교 작은 SQL 실습에는 `INSERT`, 애플리케이션의 연속 수집에는 지원 SDK의 Append, 파일 적재에는 `machloader` 같은 도구를 검토합니다. 도구마다 지원 테이블, 입력 형식과 실패 확인 방법이 다르므로 이름이 비슷하다는 이유로 서로 바꿔 사용할 수는 없습니다. SQL 입력, SDK, 파일 적재 도구의 선택 기준은 [데이터 입력과 반출](/dbms/development-tools-integration/data-input-load-export/#machloader-vs-csvimport-csvexport-tagmetaimport)을 참고하십시오. --- title: "2.4 Edition 개념" url: https://docs.machbase.com/kr/dbms/core-concepts/concepts-edition/ language: kr kind: page --- # 2.4 Edition 개념 Edition은 배포 구조와 기능 지원 범위를 결정합니다. Standard는 단일 서버에서 시작하는 구성이고, Cluster는 여러 노드가 저장과 처리를 나누는 구성입니다. 현재 데이터 크기뿐 아니라 필요한 SQL 기능, 증가율, 장애 대응과 운영 역량을 함께 고려해 선택합니다. ## Standard Edition과 Cluster Edition 차이 ### Standard Edition 하나의 DBMS 서버에서 SQL 처리와 데이터 저장을 수행합니다. 분산 노드 간의 배포·통신을 관리할 필요가 없어 개발과 단일 서버 운영을 시작하기에 적합합니다. TRANSACTION 테이블과 Restore·Mount 등 Standard 전용 기능이 필요한지도 확인합니다. 단일 서버에서 시작한다고 데이터량이 반드시 작아야 하는 것은 아닙니다. 입력량, 조회 부하와 보관 기간이 해당 서버의 CPU·메모리·스토리지 범위에 맞는지를 측정해 판단합니다. ### Cluster Edition Coordinator, Deployer, Broker, Warehouse와 Lookup 노드가 역할을 나눕니다. | 노드 유형 | 역할 | |---|---| | Coordinator | 클러스터 메타데이터와 노드 상태 관리 | | Deployer | 패키지 배포와 노드 관리 | | Broker | 애플리케이션 SQL 접속과 쿼리 분배 | | Warehouse | 시계열 데이터 저장과 쿼리 실행 | | Lookup | 클러스터 기준 정보 처리 | 일반적인 SQL 애플리케이션은 Broker에 접속합니다. 관리 도구의 접속 대상과 포트는 역할별로 다르므로 SQL 접속 주소와 관리 주소를 구분합니다. 여러 Warehouse 그룹으로 데이터를 나누는 분산과 같은 그룹 안의 데이터 복제는 서로 다른 목적입니다. 분산은 용량·처리량 확장에, 복제는 장애 대응에 사용합니다. 노드 수, 복제 상태, 클라이언트 재접속과 장애 시 운영 절차를 함께 설계해야 합니다. ### 기능 지원 차이 | 확인할 항목 | Standard Edition | Cluster Edition | |---|---|---| | 주요 배포 구조 | 단일 DBMS 서버 | 역할을 나눈 여러 노드 | | TRANSACTION 테이블 | 지원 | 미지원 | | Restore·Mount | 지원 | 미지원 | | 용량·처리 확장 | 서버 자원과 저장 구성 확장 | 분산 그룹과 노드 구성 확장 | | 장애 대응 설계 | 백업과 복구 절차, 서버 운영 계획 | 노드·그룹 복제와 상태, 접속·복구 절차 | LOG·TAG·LOOKUP·VOLATILE, ROLLUP과 Retention 등 공통 기능도 세부 DML·DDL·운영 제약이 모두 같지는 않습니다. 전체 기능 지원 여부는 [Edition 지원 범위](../../reference/support-scope-constraints/edition/)와 [테이블 타입별 지원 범위](../../reference/support-scope-constraints/table-types-type/)를 확인합니다. ### 선택 기준 1. 필수 기능을 확인합니다. TRANSACTION이나 Restore·Mount가 필요하면 Standard 지원 범위를 먼저 검토합니다. 2. 대표 입력과 조회를 실행합니다. 데이터 타입, 동시 접속, 조회 구간과 보관 기간을 실제 업무에 가깝게 맞춰 단일 서버의 여유 자원을 확인합니다. 3. 데이터 증가율과 장애 요구사항을 반영합니다. 단일 서버를 넘어 분산이 필요하다면 Cluster의 네트워크, 복제와 노드 운영 비용까지 검토합니다. 4. 장애 시나리오를 시험합니다. 노드 감시가 된다는 사실과 서비스가 중단 없이 복구되는 것은 다르므로, 복구 시간과 애플리케이션의 재접속·재시도 동작을 확인합니다. 운영 중 Edition을 바꿀 때도 단순히 서버 수만 늘리는 작업으로 생각해서는 안 됩니다. 사용 중인 SQL·SDK와 데이터 이전·백업·복구 방법의 호환성을 먼저 확인합니다. ## 관련 문서 - [저장 및 실행 구조](../storage-execution-architecture/)에서 처리 흐름을 설명합니다. - [설치, 배포, 업그레이드](../../installation-deployment-upgrade/)에서 배포 절차를 안내합니다. - [버전 및 호환성](../../reference/support-scope-constraints/compatibility-version/)에서 버전별 지원 범위를 확인합니다. - [관계형 업무 모델과 시계열 모델](../concepts/#differences-rdbms)에서 데이터 모델의 선택 기준을 설명합니다. --- title: "3. 설치, 배포, 업그레이드" url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/ language: kr kind: section --- # 3. 설치, 배포, 업그레이드 이 장에서는 Machbase DBMS 8.7.0을 설치하고, 데이터 입력과 조회가 가능한 상태인지 확인합니다. 새 서버를 준비하는 작업과 기존 데이터를 유지하면서 업그레이드하는 작업은 출발점이 다릅니다. 먼저 필요한 기능과 배포 환경을 정하고 자신의 상황에 맞는 절차를 선택합니다. 2장에서 살펴본 데이터 모델과 Edition의 차이는 설치에도 영향을 줍니다. TRANSACTION 테이블이나 Restore·Mount가 필요하다면 Standard Edition의 지원 범위를 확인합니다. 분산 저장과 복제가 필요하다면 Cluster의 노드 역할, 네트워크와 장애 대응을 함께 설계합니다. ## 설치 경로 선택 | 에디션 | 배포 구조 | 선택할 때 확인할 점 | |---|---|---| | Standard Edition | 하나의 DBMS 서버가 SQL 처리와 저장 수행 | 필요한 기능과 입력·조회·보관 부하를 서버 자원으로 처리할 수 있는지 확인 | | Cluster Edition | Coordinator·Deployer·Lookup·Broker·Warehouse 역할 분리 | 분산 그룹, 복제, 통신 경로와 노드 운영 절차를 함께 준비할 수 있는지 확인 | 단일 서버가 소규모 데이터만 처리할 수 있다는 뜻은 아닙니다. 필요한 저장량과 성능을 대표 데이터로 측정한 뒤 판단합니다. 반대로 Cluster도 노드 수만 늘리면 모든 쿼리가 비례해서 빨라지는 것은 아닙니다. 자세한 선택 기준은 [에디션 차이점](/dbms/core-concepts/concepts-edition/#differences-standard-edition-cluster)을 참고하십시오. ### 설치 전에 구분할 대상 | 대상 | 의미 | 확인할 예 | |---|---|---| | 배포 패키지 | 실행 파일, 라이브러리와 샘플 설정 | 버전·Edition·OS·CPU 아키텍처 | | 설치 홈 | 한 서버나 노드가 사용하는 실행·설정 경로 | `MACHBASE_HOME`, `conf/machbase.conf` | | 데이터 저장 경로 | DBMS가 실제 데이터를 읽고 쓰는 위치 | `DBS_PATH`, 여유 공간과 접근 권한 | | 서버 인스턴스·노드 | 해당 설정으로 실행되는 DBMS 프로세스 | 기동 상태, 로그와 접속 포트 | | 논리 데이터베이스 | 접속 후 SQL 객체를 생성하고 사용하는 공간 | 현재 데이터베이스, 사용자·권한·테이블 | 패키지 압축 해제, 새 인스턴스의 데이터베이스 생성, 서버 시작과 테이블 생성은 서로 다른 단계입니다. 기존 데이터가 있는 홈에 신규 설치용 초기화 명령을 실행하지 마십시오. 설치 홈과 실제 데이터 경로도 다를 수 있으므로 백업·업그레이드 전에 둘 다 확인합니다. SQL에서 사용하는 논리 데이터베이스는 설치 홈과 별개의 개념입니다. 여러 데이터베이스를 운용할 때의 선택·생성·권한은 [다중 데이터베이스 운영](/dbms/operations-configuration-recovery/multi-database/)에서 설명합니다. ## 설치 순서 ### Standard Edition 1. [설치 전 준비](./pre-install-preparation/)에서 패키지, 서버 계정, 자원과 포트를 확인합니다. 2. 운영체제에 맞는 설치 절차에서 전용 홈과 환경 변수를 준비합니다. - [Linux — Tarball 설치](./standard-edition/#linux-tarball) - [Linux — Docker 설치](./standard-edition/#linux-docker) - [Windows — 패키지 설치](./standard-edition/#windows-package) 3. 설정과 데이터 경로를 확인하고, 별도 라이선스를 사용할 경우 최초 기동 전에 [라이선스 설치](./pre-install-preparation/#license)를 수행합니다. 4. 선택한 설치 절차에 따라 새 데이터베이스를 생성하고 서버를 시작합니다. 5. [설치 검증 체크리스트](./validation-checklist/)에서 프로세스·포트·접속·버전·라이선스와 소량 데이터의 입력·조회를 확인합니다. 컨테이너를 사용하는 경우에도 패키지의 버전, 데이터 볼륨과 실행 계정의 쓰기 권한을 확인해야 합니다. 컨테이너 시작과 DBMS 초기화·시작의 관계는 사용하는 이미지에 따라 다르므로 해당 설치 절차를 따릅니다. ### Cluster Edition 1. [설치 전 준비](./pre-install-preparation/)와 [클러스터 환경 준비](./cluster-edition/#preparation-environment-cluster-edition)를 확인합니다. 2. 노드별 호스트, 홈, SQL·관리·노드 간 통신 포트와 Warehouse 복제 그룹을 정합니다. 3. 사용할 패키지와 라이선스를 준비하고 배포 방식을 선택합니다. - [machclusterctl 배포](./cluster-edition/#machclusterctl): 구성 파일로 배포하고 실행 계획 확인 - [수동 설치](./cluster-edition/#manual-machcoordinatoradmin): 관리 도구로 단계별 등록·기동 4. 노드 역할에 맞는 상태와 복제 구성을 확인하고 Broker에 SQL로 접속합니다. 5. [설치 검증 체크리스트](./validation-checklist/)에서 데이터 입력·조회와 그룹 내 복제 상태를 구분해 점검합니다. SQL 접속이 성공하는 것과 모든 복제본이 정상인 것은 별개의 확인입니다. 서로 다른 Warehouse 그룹이 반드시 같은 데이터를 가지는 것도 아닙니다. 장애 대응은 [Cluster 운영](/dbms/operations-configuration-recovery/cluster/)으로 이어서 확인합니다. ## 업그레이드 기존 시스템은 [업그레이드](./upgrade/)의 절차를 사용합니다. 새 패키지의 실행 가능 여부뿐 아니라 데이터 파일, SQL·SDK, 설정·라이선스와 백업·복구 경로의 호환성을 점검합니다. 운영 설정을 새 패키지의 샘플로 덮어쓰거나, 실행 파일을 바꾼 것만으로 업그레이드가 완료됐다고 판단하지 마십시오. 업그레이드 전의 기준 측정값과 백업을 확보하고, 격리 환경에서 복구에 필요한 시간도 확인합니다. “이전 버전으로 돌아갈 수 있다”는 판단에는 바이너리뿐 아니라 이전 버전이 읽을 수 있는 데이터와 설정이 필요합니다. ## 다음 단계 설치 검증은 운영 준비의 시작입니다. 먼저 [빠른 시작](/dbms/getting-started/quick-start/)으로 SQL 흐름을 익히고, [테이블 타입 선택과 스키마 설계](../data-modeling-table-design/)에서 실제 데이터를 모델링합니다. 운영 투입 전에는 전용 계정, 수집 오류 처리, 보관·백업, 대표 부하 시험과 관측 지표를 준비합니다. [관측과 진단](/dbms/operations-configuration-recovery/diagnosis-observability/)과 [성능 튜닝 접근법](/dbms/performance-tuning/performance-approach/)에서 그 과정을 안내합니다. --- title: "3.1 설치 전 준비" url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/pre-install-preparation/ language: kr kind: page --- # 3.1 설치 전 준비 설치 전 준비의 목적은 실행 파일을 복사할 위치뿐 아니라 데이터가 남을 위치, 서버를 실행할 계정과 클라이언트의 접속 경로를 확정하는 것입니다. 패키지와 운영체제의 호환성을 확인한 뒤 저장 공간, 네트워크와 라이선스를 준비합니다. 지원 범위는 제공받은 패키지의 릴리스 정보와 기술 지원 정책을 기준으로 판단합니다. ## 먼저 정할 배포 정보 | 항목 | 정할 내용 | 필요한 이유 | |---|---|---| | Edition과 버전 | Standard 또는 Cluster, 서버·SDK 버전 | 사용할 SQL 기능과 배포 방법 결정 | | OS 계정 | 서버 실행 계정과 파일 소유자 | 설정·데이터·로그 경로의 접근 권한 일치 | | 설치 홈 | 실행 파일과 설정이 있는 절대 경로 | 관리 명령이 조작할 인스턴스 식별 | | 데이터 경로 | 실제 `DBS_PATH`, 파일 시스템과 여유 공간 | 재시작·업그레이드 때 보존할 데이터 식별 | | 접속 정보 | 서버 주소, SQL 포트, 관리 포트와 허용 클라이언트 | 포트 충돌과 잘못된 인스턴스 접속 방지 | | 복구와 라이선스 | 백업 저장소, 복원 절차, 사용할 라이선스 | 장애 대응과 운영 범위 확인 | OS 계정 `machbase`와 DB 사용자 `SYS`는 다릅니다. 전자는 프로세스와 파일의 권한을, 후자는 SQL 접속과 데이터베이스 작업 권한을 결정합니다. 서버 시작에 성공해도 파일 권한이나 SQL 권한이 맞지 않으면 적재·백업 같은 작업은 실패할 수 있습니다. | 항목 | 설명 | |------|------| | [설치 전 요구사항](/dbms/installation-deployment-upgrade/pre-install-preparation/#pre-install-requirements) | OS 버전, 하드웨어 최소 사양, 네트워크 포트 | | [패키지 구성 이해](/dbms/installation-deployment-upgrade/pre-install-preparation/#package) | 패키지 파일 명명 규칙, 디렉터리 구조, 주요 실행 파일 | | [라이선스 설치](/dbms/installation-deployment-upgrade/pre-install-preparation/#license) | license.dat 파일 배치 방법, 라이선스 상태 확인 방법 | --- ## 설치 전 요구사항 ### 운영체제와 패키지 운영체제 종류와 CPU 아키텍처가 설치 패키지의 표기와 일치해야 합니다. 지원 운영체제와 최소 버전은 릴리스마다 바뀔 수 있으므로, 고정된 버전 표 대신 패키지와 함께 제공되는 릴리스 정보로 확인하십시오. ### 시스템 자원 CPU, 메모리, 디스크와 네트워크 요구량은 입력률, 보관 기간, 인덱스와 ROLLUP 구성에 따라 달라집니다. 설치 공간뿐 아니라 예상 원시 데이터, 백업과 운영 여유 공간을 포함해 산정하고, 실제 워크로드로 용량과 처리량을 검증하십시오. 초당 행 수와 보관 기간으로 원본 행 수를 예상하고, 대표 데이터를 적재해 행 크기·압축·인덱스의 실제 저장 비용을 측정합니다. Cluster에서는 복제본 공간도 포함합니다. 같은 디스크의 다른 디렉터리는 별도의 장애 영역이나 독립적인 I/O 장치가 아닙니다. ### 기본 포트 | 포트 | 용도 | |------|------| | **5656** | SQL 클라이언트 접속 (Native TCP) | SQL 클라이언트 포트를 변경하려면 `$MACHBASE_HOME/conf/machbase.conf`의 `PORT_NO`를 설정합니다. `MACHBASE_PORT_NO` 환경 변수도 사용하므로 서버를 시작하는 셸이나 서비스의 환경 변수를 함께 확인합니다. 변경한 포트는 클라이언트에도 명시하고, 이미 실행 중인 서버에 새 환경 변수가 소급 적용된다고 가정하지 마십시오. 방화벽이 있는 환경에서는 위 포트를 인바운드 허용으로 열어야 합니다. Cluster Edition은 Coordinator link/admin 포트와 Broker, Warehouse, Deployer 포트도 추가로 열어야 합니다. ### 시스템 커널 파라미터 (Linux) Linux에 설치할 경우 아래 항목을 설치 전에 점검합니다. #### 파일 디스크립터 한도 다수의 파일을 동시에 여는 워크로드에서는 낮은 파일 디스크립터 한도가 병목이 될 수 있습니다. 기본 한도는 운영체제와 계정 설정에 따라 다르므로 서버를 실행할 계정에서 확인합니다. ```bash # 현재 값 확인 ulimit -Sn ``` 이 설치 예제에서는 65535를 사용합니다. 한도가 이보다 작으면 `/etc/security/limits.conf`를 수정한 뒤 새 로그인 세션에서 적용 여부를 확인합니다. ``` * hard nofile 65535 * soft nofile 65535 ``` 서버를 실행할 계정으로 다시 로그인한 뒤 값을 확인합니다. 서비스 관리자를 통해 서버를 시작한다면 해당 서비스의 파일 디스크립터 한도도 별도로 확인합니다. ```bash ulimit -Sn # 출력: 65535 ``` #### 포트 예약 Machbase 서비스 포트가 운영체제의 임시 포트 자동 할당에 사용되지 않도록 예약합니다. 이 설정은 다른 프로세스가 같은 포트를 명시적으로 사용하는 것까지 차단하지는 않습니다. ```bash current=$(cat /proc/sys/net/ipv4/ip_local_reserved_ports) ports=5656 sudo sysctl -w net.ipv4.ip_local_reserved_ports="${current:+$current,}$ports" ``` 기존 예약 포트가 있으면 덮어쓰지 말고 쉼표로 구분해 병합합니다. 영구 적용은 `/etc/sysctl.conf`의 `net.ipv4.ip_local_reserved_ports` 항목을 다음 값과 병합합니다. ``` net.ipv4.ip_local_reserved_ports = 5656 ``` ### 시간 동기화 시계열 데이터를 처리하므로 서버의 시간이 정확해야 합니다. NTP 또는 `chrony`로 시스템 시간을 동기화하십시오. Cluster Edition에서는 모든 노드의 시간을 일치시켜야 합니다. ```bash # 타임존 확인 ls -l /etc/localtime date ``` --- ## 패키지 구성 이해 ### 패키지 파일 명명 규칙 패키지 파일 이름은 에디션에 따라 다음 형식을 따릅니다. ``` machbase-EDITION-VERSION-OS-CPU-BIT-MODE.EXT ``` | 항목 | 설명 | 예시 | |------|------|------| | EDITION | 에디션 구분 | `SDK`, `cluster` | | VERSION | 버전 (Major.Minor.Fix.AUX) | `8.7.0.official` | | OS | 운영체제 | `LINUX`, `WINDOWS` | | CPU | CPU 아키텍처 | `X86` | | BIT | 아키텍처 비트 수 | `64` | | MODE | 빌드 모드 | `release` | | EXT | 확장자 | `tgz` (Linux), `zip` 또는 설치 실행 파일 (Windows) | Standard Edition Linux tarball은 `machbase-SDK-...tgz` 이름으로 생성됩니다. 예시: - Standard Edition: `machbase-SDK-8.7.0.official-LINUX-X86-64-release.tgz` - Cluster Edition: `machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz` Minor 버전이 다르면 DB 파일과 프로토콜의 호환성이 달라질 수 있습니다. Fix 버전 변경을 포함한 실제 업그레이드 가능 경로는 대상 릴리스의 호환성 안내와 [업그레이드 절차](../upgrade/)에서 확인합니다. ### 설치 디렉터리 구조 tarball 압축을 해제하면 `$MACHBASE_HOME` 아래에 다음 구조가 생성됩니다. ``` $MACHBASE_HOME/ ├── bin/ 실행 파일 ├── conf/ 설정 파일 (machbase.conf 등) ├── dbs/ 데이터 저장 공간 ├── doc/ 라이선스 문서 ├── include/ C/C++ 헤더 파일 ├── install/ Makefile용 mk 파일 ├── lib/ 공유 라이브러리 ├── package/ Cluster Edition 추가 패키지 경로 ├── sample/ 예제 파일 ├── trc/ 서버 로그 및 트레이스 파일 ├── tutorials/ 튜토리얼 ├── utility/ 유틸리티 파일 └── 3rd-party/ Grafana 플러그인 등 ``` ### 주요 실행 파일 | 실행 파일 | 설명 | |-----------|------| | `machbased` | 서버 데몬 | | `machadmin` | 서버 관리 (시작·종료·DB 생성) | | `machsql` | CLI 쿼리 도구 | | `machloader` | 대용량 파일 적재·추출 도구 | | `csvimport` | CSV 파일 가져오기 | | `csvexport` | CSV 파일 내보내기 | | `tagmetaimport` | TAG 메타 데이터 일괄 등록 | Cluster Edition 패키지에는 `machcoordinatoradmin`, `machdeployeradmin` 등의 관리 도구가 추가됩니다. `machclusterctl`은 해당 도구를 포함하도록 빌드된 패키지에서 사용할 수 있습니다. ### 설정 파일 `$MACHBASE_HOME/conf/` 아래에 에디션별 샘플 설정 파일이 있습니다. ```bash ls $MACHBASE_HOME/conf/ # machbase.conf # machbase.conf.sample.standard # machbase.conf.sample.edge # machloader.conf.sample ``` 실제 사용 파일은 `machbase.conf`입니다. Standard full 패키지는 빌드 과정에서 `machbase.conf.sample.standard`를 복사해 `machbase.conf`를 포함합니다. 실제 파일이 없는 패키지에서는 에디션에 맞는 샘플 파일을 복사하여 수정합니다. Standard/Edge 샘플에는 TRANSACTION 쓰기 충돌과 내구성 정책을 제어하는 `TRANSACTION_BUSY_TIMEOUT_MS`, `TRANSACTION_SYNCHRONOUS`, `TRANSACTION_JOURNAL_MODE` 설정이 포함됩니다. TRANSACTION 테이블을 사용하는 환경에서는 기본값을 먼저 사용하고, 동시 쓰기와 내구성 요구를 검토한 뒤 값을 조정합니다. --- ## 라이선스 설치 라이선스 파일이 없으면 서버는 기본 `COMMUNITY` 라이선스 정보를 사용합니다. 설치 전에 이 범위가 예정한 기능·용량에 맞는지 확인하고, 별도 라이선스가 필요한 환경에서는 첫 서버 시작 전에 해당 파일을 준비합니다. 서버가 기동된다는 사실만으로 운영에 필요한 라이선스 조건을 충족했다고 판단하지 마십시오. ### 라이선스 상태 확인 설치된 라이선스의 상태와 제한 위반 여부는 `V$LICENSE_INFO`의 `VIOLATE_STATUS`와 `VIOLATE_MSG`로 확인합니다. 라이선스 파일의 본문은 수정하지 마십시오. ### 설치 방법 #### 방법 1: 파일 복사 (서버 시작 전) `license.dat` 파일을 `$MACHBASE_HOME/conf/`에 복사합니다. 서버가 시작될 때 자동으로 인식합니다. ```bash cp license.dat $MACHBASE_HOME/conf/license.dat ``` #### 방법 2: machadmin 명령 `machadmin`으로 라이선스 파일을 검증하고 설치합니다. 서버가 실행 중이면 라이선스 reload 요청을 함께 보냅니다. ```bash machadmin -t /path/to/license.dat ``` #### 방법 3: SQL 쿼리 (서버 실행 중) 서버가 이미 실행 중인 경우 machsql에서 쿼리로 설치합니다. ```sql ALTER SYSTEM INSTALL LICENSE = '/path/to/license.dat'; ``` ### 설치 확인 #### machadmin 확인 ```bash machadmin -f ``` #### V$LICENSE_INFO 뷰 조회 ```sql SELECT ID, ISSUE_DATE, TYPE, CUSTOMER, VIOLATE_STATUS, VIOLATE_MSG FROM V$LICENSE_INFO; ``` `VIOLATE_STATUS`가 0이면 정상입니다. machsql에서는 다음 명령으로도 라이선스 정보를 확인합니다. ```sql SHOW LICENSE; ``` --- title: "3.2 Standard Edition 설치" url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/standard-edition/ language: kr kind: page --- # 3.2 Standard Edition 설치 Standard Edition은 한 서버에서 SQL 처리와 데이터 저장을 수행합니다. 설치는 패키지 준비, 서버 실행 환경 설정, 데이터베이스 생성, 라이선스 확인, 시작과 SQL 검증 순서로 진행합니다. 단일 서버라고 해서 소량 데이터만 다루는 것은 아니며, 처리량과 보관 기간에 필요한 자원을 실제 워크로드로 확인해야 합니다. ## 설치 경로 운영체제에 따라 아래 경로 중 하나를 선택합니다. | OS | 설치 방식 | 링크 | |----|-----------|------| | Linux | Tarball (.tgz) | [Tarball 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux-tarball) | | Linux | Docker 컨테이너 | [Docker 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux-docker) | | Windows | ZIP 또는 설치 실행 파일 | [Windows 패키지 설치](/dbms/installation-deployment-upgrade/standard-edition/#windows-package) | 설치 전에 [Linux 환경 준비](/dbms/installation-deployment-upgrade/standard-edition/#linux-preparation-environment-linux) 또는 [Windows 환경 준비](/dbms/installation-deployment-upgrade/standard-edition/#windows-preparation-environment-windows)를 먼저 확인하십시오. --- ## Linux 설치 Linux에서 Standard Edition을 설치하는 방법은 두 가지입니다. | 방식 | 적합한 상황 | |------|------------| | [Tarball 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux-tarball) | 실제 서버 환경, 데이터 디렉터리를 직접 관리해야 하는 경우 | | [Docker 설치](/dbms/installation-deployment-upgrade/standard-edition/#linux-docker) | 개발·테스트 환경, 빠른 구동이 필요한 경우 | Tarball 설치는 [설치 전 준비](../pre-install-preparation/)를 먼저 완료해야 합니다. Docker 설치는 Docker Engine과 볼륨·포트 권한을 준비하십시오. --- ### Linux 환경 준비 파일 디스크립터 한도, 시간 동기화, 포트 예약과 방화벽 설정은 Edition 공통 작업입니다. [설치 전 준비](../pre-install-preparation/)에서 서버를 실행할 계정과 운영 환경에 맞게 설정한 뒤 다음 설치 절차를 진행합니다. --- ### Tarball 설치 Linux 환경에 tarball(.tgz)을 압축 해제하여 Standard Edition을 설치하는 절차입니다. #### 1. 사용자 생성 Machbase 전용 OS 사용자를 생성합니다. ```bash sudo useradd -m -d /home/machbase machbase sudo passwd machbase ``` 이후 `machbase` 계정으로 로그인하여 작업합니다. #### 2. 패키지 다운로드 및 압축 해제 예제에서는 패키지를 `/home/machbase/packages/`에 다운로드한 것으로 가정합니다. 아래 패키지 이름을 실제 배포본으로 바꾸고, 아직 인스턴스가 없는 새 설치 디렉터리에 압축을 해제합니다. 기존 설치의 갱신은 [업그레이드](../upgrade/) 절차를 사용합니다. ```bash machbase_package=/home/machbase/packages/machbase-SDK-8.7.0.official-LINUX-X86-64-release.tgz test -r "$machbase_package" && mkdir /home/machbase/machbase_home && tar zxf "$machbase_package" -C /home/machbase/machbase_home && cd /home/machbase/machbase_home ``` 압축 해제 후 디렉터리 구조를 확인합니다. ```bash ls -l # bin/ conf/ dbs/ doc/ include/ lib/ trc/ ... ``` #### 3. 환경 변수 설정 `~/.bashrc`에 환경 변수를 추가합니다. ```bash export MACHBASE_HOME=/home/machbase/machbase_home export PATH="$MACHBASE_HOME/bin:$PATH" export LD_LIBRARY_PATH="$MACHBASE_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" ``` 적용합니다. ```bash source ~/.bashrc ``` #### 4. 데이터베이스 생성 `machadmin -c`는 물리 인스턴스의 데이터베이스 파일을 생성합니다. SQL의 `CREATE DATABASE`로 논리 데이터베이스를 추가하는 작업과 구분하십시오. 실행 전에 `MACHBASE_HOME`, `conf/machbase.conf`와 실제 `DBS_PATH`가 새 설치 대상인지 확인합니다. 기존 데이터베이스가 있다는 오류가 나면 삭제·재생성하지 말고 대상 경로부터 확인합니다. ```bash machadmin -c # Database created successfully. ``` #### 서버 시작 전 설정과 라이선스 확인 `conf/machbase.conf`에서 `PORT_NO`와 `DBS_PATH`를 확인합니다. 별도 라이선스를 사용하는 경우 [라이선스 설치](../pre-install-preparation/#license)의 파일 복사 또는 `machadmin -t` 방법으로 설치하고 `machadmin -f`로 확인한 뒤 시작합니다. #### 5. 서버 시작 ```bash machadmin -u # Machbase server started successfully. ``` 프로세스 확인: ```bash machadmin -e ``` #### 6. 접속 테스트 `machsql`로 서버에 접속합니다. 기본 관리자 계정은 `SYS / MANAGER`입니다. ```bash machsql # Machbase server address (Default:127.0.0.1) : # Machbase user ID (Default:SYS) # Machbase User Password : # MACHBASE_CONNECT_MODE=INET, PORT=5656 EDITION=STANDARD # Mach> ``` 간단한 테스트를 수행합니다. ```sql CREATE LOG TABLE install_check (id INTEGER, val DOUBLE); INSERT INTO install_check (id, val) VALUES (1, 3.14); SELECT id, val FROM install_check; DROP TABLE install_check; ``` `id=1`, `val=3.14` 한 행이 반환되고 마지막 DROP이 성공하는지 확인합니다. `install_check`가 이미 있으면 기존 객체를 삭제하지 말고 사용하지 않는 실습 이름으로 바꿉니다. #### 서버 종료 설치 확인이 끝난 뒤 서버를 중지해야 할 때만 실행합니다. ```bash machadmin -s # Machbase server shut down successfully. ``` #### 포트 변경 기본 포트(5656)를 변경하려면 `$MACHBASE_HOME/conf/machbase.conf`의 `PORT_NO`를 수정하거나 환경 변수를 설정합니다. ```bash export MACHBASE_PORT_NO=7878 ``` 서버를 종료한 상태에서 설정을 적용하고 다시 시작합니다. 이 환경 변수는 현재 셸에만 적용되므로 서비스로 시작한다면 서비스의 환경에도 반영합니다. 접속에는 `machsql -s 127.0.0.1 -P 7878 -u SYS`처럼 바뀐 포트를 지정합니다. --- ### Docker 설치 Docker 이미지를 사용하면 서버와 실행 환경을 컨테이너로 배포할 수 있습니다. 개발·테스트 환경에서도 호스트의 Docker Engine, 저장 볼륨, 포트와 파일 디스크립터 한도를 준비해야 합니다. Docker가 사전에 설치되어 있어야 합니다. 배포 튜토리얼은 `machbase/machbase` 이미지를 사용합니다. 소스에서 Docker 이미지를 직접 빌드한 경우에는 로컬 이미지 이름(`machbase:latest` 등)으로 바꾸십시오. #### 이미지 확인 ```bash docker pull machbase/machbase docker image ls machbase/machbase ``` #### 컨테이너 실행 ```bash docker create \ --name machbase \ --ulimit nofile=65535 \ -p 5656:5656 \ -v /data/machbase:/home/machbase/machbase/dbs \ machbase/machbase ``` | 옵션 | 설명 | |------|------| | `-p 5656:5656` | 호스트 SQL 포트:컨테이너 SQL 포트 매핑 | | `--ulimit nofile=65535` | 컨테이너 안에서 서버가 사용할 파일 디스크립터 한도 | | `-v /data/machbase:...` | 데이터 디렉터리를 호스트에 보존하는 볼륨 마운트 | 데이터가 컨테이너의 쓰기 계층에만 있으면 컨테이너 삭제 시 함께 제거됩니다. 실제 데이터 경로가 볼륨에 연결되었는지 확인하고, 볼륨 자체의 삭제나 디스크 장애에 대비한 백업도 준비합니다. `docker create`는 아직 서버를 시작하지 않습니다. `/data/machbase`는 컨테이너의 서버 실행 계정이 쓸 수 있어야 하며, 기존 DB 파일이 있으면 버전과 인스턴스가 맞는지 확인합니다. 이미지의 실제 버전과 `MACHBASE_HOME` 경로를 확인한 뒤 운영에서는 검증한 이미지 태그나 digest를 고정합니다. 태그 없는 공개 이미지가 항상 8.7.0이라고 가정하지 마십시오. 별도 라이선스를 사용하는 경우 시작 전에 다음처럼 준비한 파일을 복사합니다. ```bash docker cp /path/to/license.dat machbase:/home/machbase/machbase/conf/license.dat ``` 준비가 끝나면 컨테이너를 시작합니다. ```bash docker start machbase ``` #### 컨테이너 상태 확인 ```bash docker ps docker logs machbase ``` #### 접속 테스트 ##### machsql (컨테이너 내부) ```bash docker exec -it machbase machsql # Mach> ``` ##### 호스트에서 접속 호스트에 machsql이 설치된 경우: ```bash machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER ``` #### 컨테이너 종료 및 재시작 ```bash docker stop machbase docker start machbase ``` #### 라이선스 설치 실행 중 라이선스를 갱신할 때는 컨테이너에 파일을 복사한 뒤 라이선스 설치 명령을 실행합니다. 컨테이너의 설정 파일과 라이선스는 데이터 볼륨과 별개이므로 컨테이너를 재생성할 때도 다시 적용할 수 있도록 원본을 보관합니다. ```bash docker cp /path/to/license.dat machbase:/tmp/license.dat docker exec machbase machadmin -t /tmp/license.dat docker exec machbase machadmin -f ``` --- ## Windows 설치 ### 설치 전 확인 - **지원 OS**: 제공받은 Windows 패키지의 릴리스 정보에서 지원 버전을 확인합니다. - **아키텍처**: 배포 패키지의 비트 수와 운영체제 아키텍처가 일치해야 합니다. 설치 전에 [Windows 환경 준비](/dbms/installation-deployment-upgrade/standard-edition/#windows-preparation-environment-windows)를 먼저 완료하십시오. ### 설치 방식 | 방식 | 설명 | |------|------| | [Windows 패키지 설치](/dbms/installation-deployment-upgrade/standard-edition/#windows-package) | 설치 마법사를 통한 설치. 환경 변수와 바로가기 생성 | --- ### Windows 환경 준비 Windows에 설치하기 전에 방화벽 설정을 확인합니다. #### 방화벽 포트 열기 Machbase가 사용하는 포트를 Windows 방화벽 인바운드 규칙에 추가합니다. | 포트 | 프로토콜 | 용도 | |------|---------|------| | 5656 | TCP | SQL 클라이언트 접속 | ##### 설정 방법 1. **제어판 → Windows Defender 방화벽 → 고급 설정**을 엽니다. 2. 왼쪽 패널에서 **인바운드 규칙**을 선택하고, 오른쪽 패널에서 **새 규칙**을 클릭합니다. 3. 규칙 유형으로 **포트**를 선택하고 **다음**을 클릭합니다. 4. **TCP**를 선택하고, **특정 로컬 포트** 필드에 `5656`을 입력한 후 **다음**을 클릭합니다. 5. **연결 허용**을 선택하고 **다음**을 클릭합니다. 6. 접속을 허용할 네트워크의 프로필(**도메인**, **개인**, **공용**)만 선택하고 **다음**을 클릭합니다. 7. 규칙 이름(예: `Machbase`)을 입력하고 **마침**을 클릭합니다. 규칙을 만든 뒤 속성의 원격 주소 범위도 실제 클라이언트 주소로 제한합니다. 원격 접속을 사용하지 않는 환경에서는 인바운드 허용 규칙을 만들 필요가 없습니다. ##### PowerShell로 설정 (관리자 권한) GUI 대신 PowerShell로 빠르게 설정합니다. ```powershell New-NetFirewallRule -DisplayName "Machbase SQL" -Direction Inbound -Protocol TCP -LocalPort 5656 -Action Allow ``` #### Visual C++ 재배포 패키지 실행에 Visual C++ Redistributable이 필요합니다. 설치 실행 파일을 사용하는 경우 자동으로 처리될 수 있지만, 문제가 발생하면 Microsoft 공식 사이트에서 최신 버전을 수동으로 설치하십시오. --- ### Windows 패키지 설치 Windows 버전은 ZIP 패키지 또는 설치 실행 파일로 제공됩니다. ZIP 패키지는 압축 파일 루트에 `bin\`, `conf\`, `dbs\`, `trc\` 등을 포함하며, 설치 실행 파일은 설치 경로 아래에 `machbase_home\`을 만들고 환경 변수와 실행 바로가기를 생성합니다. #### 설치 절차 1. Windows 배포 패키지를 다운로드합니다. 2. ZIP 패키지를 사용하는 경우 원하는 설치 디렉터리에 압축을 해제합니다. ```cmd mkdir C:\machbase tar -xf machbase-SDK-8.7.0.official-WINDOWS-X86-64-release.zip -C C:\machbase ``` 3. 설치 실행 파일이 제공된 경우 파일을 실행합니다. 설치 시작 화면이 표시되면 **Next**를 클릭합니다. 4. 설치 경로를 선택합니다. 기본값은 `C:\machbase-\` 형식입니다. 변경이 필요하면 경로를 수정한 후 **Next**를 클릭합니다. 5. 설치가 진행됩니다. 완료되면 **Next** → **Close**를 클릭합니다. #### ZIP 패키지 초기화 ZIP을 해제했다면 명령 프롬프트에서 해당 디렉터리를 설치 홈으로 지정합니다. 다음은 `C:\machbase`에 새로 설치한 예제입니다. 실제 설정 파일과 라이선스를 먼저 확인하고, 기존 DB가 없는 새 인스턴스에서만 `-c`를 실행합니다. ```cmd set "MACHBASE_HOME=C:\machbase" set "PATH=%MACHBASE_HOME%\bin;%PATH%" machadmin.exe -c machadmin.exe -f machadmin.exe -u machadmin.exe -e ``` 위 `set` 명령은 현재 창에 적용됩니다. 이후 다른 창이나 서비스에서 실행할 때도 같은 설치 홈과 실행 파일 경로를 사용해야 합니다. 설치 실행 파일이 이미 데이터베이스를 만들었다면 ZIP용 생성 명령을 반복하지 마십시오. #### 서버 시작과 종료 설치 실행 파일을 사용한 경우 바탕화면과 시작 메뉴에 바로가기가 생성됩니다. - **start Machbase**: `machadmin.exe -u`를 실행해 서버를 시작합니다. - **stop Machbase**: `machadmin.exe -s`를 실행해 서버를 종료합니다. - **machsql**: SQL 콘솔을 실행합니다. #### 명령줄 접속 설치 후 명령 프롬프트에서 `machsql`을 실행하여 서버에 접속합니다. 설치 실행 파일을 사용한 경우 `<설치 경로>\machbase_home\bin`이 시스템 `PATH`에 추가됩니다. ZIP 패키지를 사용한 경우 압축을 해제한 디렉터리의 `bin\`을 `PATH`에 추가하거나 전체 경로로 실행합니다. ```cmd machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER ``` 기본 관리자 계정: `SYS` / `MANAGER` #### 설치 경로 구조 ZIP 패키지는 압축 해제 경로 바로 아래에 `bin\`, `conf\`, `dbs\`, `trc\` 등을 배치합니다. 설치 실행 파일은 설치 경로 아래의 `machbase_home\`에 같은 구성을 생성합니다. [패키지 구성](/dbms/installation-deployment-upgrade/pre-install-preparation/#package) 참고. --- title: "3.3 Cluster Edition 설치와 배포" url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/cluster-edition/ language: kr kind: page --- # 3.3 Cluster Edition 설치와 배포 Cluster Edition은 SQL 접속, 데이터 저장, 복제와 노드 관리를 여러 역할로 나눕니다. 설치 전에 각 역할을 어느 호스트에 배치할지, 어떤 포트와 저장 경로를 사용할지 정합니다. 일반 SQL 접속은 Broker로, 운영 명령은 해당 관리 노드로 보내야 합니다. ## 노드 역할 | 노드 | 역할 | |------|------| | **Coordinator** | 클러스터 메타 정보 관리, 노드 상태 감시 | | **Deployer** | 패키지 배포 및 노드 초기화 중계 | | **Lookup** | 참조 데이터와 조회 처리 | | **Broker** | SQL 파싱 및 쿼리 분배, 클라이언트 접점 | | **Warehouse** | 실제 데이터 저장 및 쿼리 실행 | 아래 YAML 예시는 세 호스트에 Coordinator 2개, Deployer 3개, Lookup 2개(master 1개, monitor 1개), Broker 2개와 Warehouse 2개(하나의 복제 그룹)를 배치합니다. 실제 노드 수와 배치는 가용성, 처리량과 장애 시 남아 있어야 할 용량을 기준으로 정합니다. ## 배포 방식 | 방식 | 설명 | 적합한 경우 | |------|------|------------| | [machclusterctl](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl) | cluster.yaml 기반 자동 배포 | 권장. 신규 구축 | | [수동 (machcoordinatoradmin)](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin) | Coordinator 명령 기반 노드 등록·배포 | 세밀한 제어가 필요한 경우 | ## 설치 순서 1. [Cluster Edition 구성 개요](/dbms/installation-deployment-upgrade/cluster-edition/#overview) 숙지 2. [환경 준비](/dbms/installation-deployment-upgrade/cluster-edition/#preparation-environment-cluster-edition) (SSH 키, 커널 파라미터, NTP) 3. 패키지·경로와 [라이선스](../pre-install-preparation/#license) 적용 방식 준비 4. 배포 방식 선택 후 설치·기동하고 라이선스 확인 5. [설치 검증](/dbms/installation-deployment-upgrade/validation-checklist/) --- ## Cluster Edition 구성 개요 역할이 분리된 Coordinator, Deployer, Lookup, Broker, Warehouse 노드로 구성됩니다. 각 노드의 역할과 상호 관계를 이해한 후 배포 계획을 세우십시오. ### 노드 역할 상세 #### Coordinator 클러스터 메타데이터, 노드 등록과 상태 감시를 담당합니다. Primary/Secondary 이중화를 검토하십시오. 실행 중인 데이터 처리 경로와 관리 경로는 구분되지만, Coordinator 장애만으로 모든 SQL의 지속 동작을 보장할 수는 없습니다. 다른 노드 상태와 클라이언트 영향을 함께 확인하고 검증한 장애 대응 절차를 사용합니다. - 설정 파일: `$MACHBASE_COORDINATOR_HOME/conf/machbase.conf` - 관리 도구: `machcoordinatoradmin` - 주요 포트: `CLUSTER_LINK_PORT_NO`, `HTTP_ADMIN_PORT` 기본값은 `CLUSTER_LINK_PORT_NO=3868`, `HTTP_ADMIN_PORT=5779`입니다. 이 장의 예제에서는 운영 중 포트 충돌을 피하기 위해 Coordinator link/admin 포트로 `5101`/`5102`를 명시합니다. #### Deployer Coordinator의 지시에 따라 각 노드에 패키지를 배포하고 초기화를 중계합니다. 각 노드 호스트에 하나씩 배치하거나, 별도 배포 서버로 운영합니다. - 관리 도구: `machdeployeradmin` #### Lookup 참조 데이터와 조회 처리를 위한 노드입니다. 구성에 따라 `master`, `monitor`, `slave` 역할을 지정합니다. #### Broker 클라이언트의 SQL 요청을 받아 파싱하고 적절한 Warehouse로 분배합니다. 일반 애플리케이션은 Broker 주소로 연결합니다. Warehouse 직접 연결은 복제 상태 비교 같은 관리 진단 절차에서만 사용하며, 애플리케이션의 접속 경로와 구분합니다. Broker 이중화를 권장합니다. - 클라이언트 접속 포트: 기본 5656 #### Warehouse 실제 데이터를 저장하고 쿼리를 실행합니다. 같은 그룹의 Warehouse끼리 데이터를 복제하여 고가용성을 제공합니다. 그룹당 2개 이상을 권장합니다. ### 구성 예시 ``` [클라이언트] │ (SQL, 5656) ▼ [Broker ×2] ────────────────────────────────────────── │ (쿼리 분배) ├─► [Warehouse group1-node1] ◄──복제──► [Warehouse group1-node2] └─► [Warehouse group2-node1] ◄──복제──► [Warehouse group2-node2] [Coordinator Primary] ◄──HA──► [Coordinator Secondary] │ (메타 관리, 노드 감시) [Deployer] │ [Lookup master / monitor] ``` ### 에디션 비교 Standard Edition과의 상세 비교는 [에디션 차이점](/dbms/core-concepts/concepts-edition/#differences-standard-edition-cluster)을 참고하십시오. --- ## Cluster Edition 설치 환경 준비 배포 전에 모든 노드에 다음 환경을 준비합니다. ### 파일 디스크립터 한도 모든 노드에서 아래 설정을 적용합니다. ```bash sudo vi /etc/security/limits.conf ``` ``` * hard nofile 65535 * soft nofile 65535 ``` 서버 실행 계정으로 새 로그인 세션을 연 뒤 확인합니다. 서비스 관리자로 시작한다면 서비스 자체의 파일 디스크립터 한도도 확인합니다. ```bash ulimit -Sn # 65535 ``` ### OS 사용자 생성 모든 노드에 `machbase` 계정을 생성합니다. ```bash sudo useradd -m machbase --home-dir /home/machbase sudo passwd machbase ``` ### SSH 키 기반 인증 `machclusterctl`을 사용하는 경우, 배포 서버에서 모든 노드로 비밀번호 없이 SSH 접속이 가능해야 합니다. ```bash # 배포 서버에서 SSH 키 생성 (이미 있으면 생략) ssh-keygen -t rsa -b 4096 # 각 노드에 공개 키 등록 ssh-copy-id machbase@192.168.1.11 ``` 등록 후 비밀번호 없이 접속이 되는지 확인합니다. ```bash ssh machbase@192.168.1.11 'hostname' ``` ### 네트워크 커널 파라미터 다음은 네트워크 버퍼 조정 예시이며 모든 서버에 적용할 필수값이 아닙니다. 현재 커널 설정, 메모리 사용량과 네트워크 병목을 먼저 측정합니다. 값을 늘린 뒤 처리량뿐 아니라 메모리와 지연도 비교하고 효과가 확인된 항목만 영구 적용합니다. ```bash sudo sysctl -w net.core.rmem_default=33554432 sudo sysctl -w net.core.wmem_default=33554432 sudo sysctl -w net.core.rmem_max=268435456 sudo sysctl -w net.core.wmem_max=268435456 sudo sysctl -w 'net.ipv4.tcp_rmem=262144 33554432 268435456' sudo sysctl -w 'net.ipv4.tcp_wmem=262144 33554432 268435456' sudo sysctl -w 'net.ipv4.tcp_mem=8388608 8388608 8388608' ``` 영구 적용은 `/etc/sysctl.conf`에 추가합니다. ### 시간 동기화 (NTP) 모든 노드의 시스템 시간이 일치해야 합니다. NTP 또는 `chrony`로 동기화하십시오. ```bash # chrony 사용 예 sudo systemctl enable chronyd sudo systemctl start chronyd chronyc tracking ``` 타임 서버를 사용할 수 없는 격리된 설치 환경에서는 초기 시각을 직접 설정할 수 있습니다. 다음 값은 형식 예시이므로 실제 현재 시각으로 바꾸십시오. 수동 설정만으로 노드 간 시간 차이가 지속적으로 보정되지는 않으므로 운영 전에는 시간 동기화 경로를 마련합니다. ```bash sudo date -s "2025-01-02 12:34:56" ``` ### 포트 예약 각 노드에서 Machbase가 사용할 포트를 예약합니다. ```bash current=$(cat /proc/sys/net/ipv4/ip_local_reserved_ports) ports=5101-5110,5201-5202,5301-5302,5401,5500-5503,5656 sudo sysctl -w net.ipv4.ip_local_reserved_ports="${current:+$current,}$ports" ``` 기존 예약 포트가 있으면 덮어쓰지 말고 쉼표로 구분해 병합합니다. 클러스터 구성에 따라 포트 범위를 조정하십시오. Cluster link, Coordinator/Deployer 관리, 서비스, Warehouse 복제 관리자 포트 등을 모두 포함해야 합니다. --- ## machclusterctl 기반 배포 `machclusterctl`은 `cluster.yaml` 파일 하나로 전체 클러스터를 자동으로 배포하고 관리하는 도구입니다. SSH를 통해 각 노드에 원격 접속하여 패키지 배포, 초기화, 시작·종료를 일괄 처리합니다. ### 사전 조건 - Primary Coordinator를 설치할 호스트에서 명령을 실행하며, 패키지와 SSH 개인키를 해당 호스트에서 읽을 수 있어야 합니다. - Primary 호스트에서 모든 대상 호스트로 SSH 키 기반 인증이 설정되어 있어야 합니다. - 각 노드의 `home_path`와 `dbs_path` 상위 경로를 생성·기록할 권한이 있어야 합니다. - 별도 라이선스가 필요하면 자동 기동 전에 배포 패키지와 라이선스 적용 절차를 준비합니다. YAML에 임의의 라이선스 속성을 추가하지 않습니다. - [Cluster Edition 설치 환경 준비](/dbms/installation-deployment-upgrade/cluster-edition/#preparation-environment-cluster-edition) 완료 ### 작업 순서 | 단계 | 문서 | |------|------| | 1. cluster.yaml 작성 | [cluster.yaml 작성](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl-cluster-yaml) | | 2. YAML 유효성 검사 | [YAML 검증](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl-validation-yaml) | | 3. 최초 설치 및 시작 | [최초 설치](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl-initial) | | 4. 상태 확인 | [최초 설치와 상태 확인](/dbms/installation-deployment-upgrade/cluster-edition/#machclusterctl-initial) | | 5. (이후) 구성 변경 | [Cluster 운영](/dbms/operations-configuration-recovery/cluster/) | | 6. (장애 시) 복구 | [Cluster 문제 해결](/dbms/troubleshooting/cluster/) | --- ### cluster.yaml 작성 `cluster.yaml`은 설치할 노드 구성과 경로를 선언합니다. 아래 주소는 예시이므로 실제 호스트로 바꿉니다. `origin_path`는 명령을 실행하는 Primary Coordinator 호스트에서 읽는 압축 파일, `home_path`와 `dbs_path`는 해당 노드 호스트의 경로입니다. `deployer`는 그 노드를 배치·제어할 Deployer를 참조합니다. #### 파일 구조 예시 ```yaml version: "1" cluster: name: mc-prod hosts: node1: address: machbase@192.168.1.10 node2: address: machbase@192.168.1.11 node3: address: machbase@192.168.1.12 package: name: machbase origin_path: /home/machbase/packages/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz ssh: key_file: /home/machbase/.ssh/id_rsa defaults: coordinator: home_path: /home/machbase/coordinator cluster_link_port: 5101 http_admin_port: 5102 deployer: home_path: /home/machbase/deployer cluster_link_port: 5201 http_admin_port: 5202 lookup: home_path: /home/machbase/lookup cluster_link_port: 5301 broker: home_path: /home/machbase/broker cluster_link_port: 5401 service_port: 5656 warehouse: home_path: /home/machbase/warehouse cluster_link_port: 5501 service_port: 5500 coordinators: - alias: coord-primary-1 host: node1 role: primary - alias: coord-secondary-1 host: node2 role: secondary deployers: - alias: deployer-1 host: node1 - alias: deployer-2 host: node2 - alias: deployer-3 host: node3 lookup: - alias: lookup-master-1 host: node1 deployer: deployer-1 type: master - alias: lookup-monitor-1 host: node2 deployer: deployer-2 type: monitor brokers: - alias: broker-1 host: node1 deployer: deployer-1 dbs_path: /data/machbase/broker-1/dbs - alias: broker-2 host: node2 deployer: deployer-2 warehouse_groups: - name: group1 nodes: - alias: warehouse-group1-1 host: node2 deployer: deployer-2 dbs_path: /data/machbase/warehouse-group1-1/dbs - alias: warehouse-group1-2 host: node3 deployer: deployer-3 dbs_path: /data/machbase/warehouse-group1-2/dbs ``` #### 주요 항목 설명 | 항목 | 설명 | |------|------| | `version` | YAML 스키마 버전입니다. 현재 `"1"`을 사용합니다. | | `cluster.name` | 클러스터 이름입니다. `destroy` 확인 등에서 사용됩니다. | | `cluster.hosts` | 노드에서 참조할 호스트 별칭과 SSH 접속 주소입니다. `address`는 `user@host` 형식을 사용합니다. | | `cluster.package.name` | Coordinator에 등록할 패키지 이름입니다. | | `cluster.package.origin_path` | `install`, `apply`, `upgrade` 실행 때 입력으로 사용할 패키지 보관 파일 경로입니다. | | `cluster.package.registered_path` | `export`가 기록하는 Coordinator 패키지 저장소의 관찰 경로입니다. 실행 입력으로 사용하지 않습니다. | | `cluster.ssh.key_file` | 대상 서버 접속에 사용할 개인키 경로입니다. 비밀번호 필드는 사용하지 않습니다. | | `cluster.defaults` | 노드 타입별 `home_path`, `cluster_link_port`, `service_port` 기본값입니다. | | `cluster.coordinators` | Coordinator 노드 목록입니다. `role`은 `primary` 또는 `secondary`를 사용합니다. | | `cluster.deployers` | Deployer 노드 목록입니다. | | `cluster.lookup` | Lookup 노드 목록입니다. `type`은 `master`, `monitor`, `slave`를 사용합니다. | | `cluster.brokers` | Broker 노드 목록입니다. 클라이언트 SQL 접속 포트는 `service_port`입니다. | | `cluster.warehouse_groups` | Warehouse 그룹과 그룹별 노드 목록입니다. | #### 권장 구성 - Coordinator: 2개 (Primary + Secondary HA) - Deployer: 1개 이상 - Lookup: `master` 1개, `monitor` 1개 이상 - Broker: 2개 이상 (부하 분산) - Warehouse 그룹: 그룹당 2개 (복제를 통한 고가용성) 같은 서버에 같은 타입의 노드를 2개 이상 배치할 때는 두 번째 노드부터 `home_path`와 포트를 명시적으로 지정하여 충돌을 피합니다. Coordinator와 Deployer의 관리 포트는 `http_admin_port`를 사용합니다. Broker와 Warehouse 노드에는 선택적으로 `dbs_path`를 지정합니다. `dbs_path`는 노드가 설치되는 서버 기준의 데이터 파일 경로이며, 생략하면 `machcoordinatoradmin --add-node`의 기본 `DBS_PATH` 동작을 따릅니다. 기존 `cluster.package.path`는 하위 호환 입력으로 사용할 수 있지만, 새로 작성하는 YAML에서는 `origin_path`를 사용합니다. `http_admin_port`는 Coordinator와 Deployer에만 사용합니다. Broker, Lookup, Warehouse에는 HTTP 포트 필드를 지정하지 않습니다. 작성이 완료되면 유효성을 검사합니다. --- ### YAML 검증 `cluster.yaml`을 실제 설치에 사용하기 전에 유효성 검사를 수행합니다. `validate`는 YAML 문법과 필수 값, 별칭, 포트 충돌, 토폴로지 관계를 정적으로 검사합니다. #### 검증 명령 ```bash machclusterctl validate -f cluster.yaml ``` #### 검증 항목 | 항목 | 설명 | |------|------| | YAML 문법 | 파일 파싱 오류 여부 | | 환경변수 치환 | `${VAR}` 또는 `${VAR:-default}` 표현식 해석 가능 여부 | | 필수 필드 | 클러스터 이름, 호스트, 패키지, 노드별 필수 값 누락 여부 | | 별칭 | 노드 별칭 중복 여부 | | 포트 충돌 | 같은 호스트 안에서 선언된 포트 충돌 여부 | | 토폴로지 | Primary Coordinator, Lookup master/monitor, Deployer 참조 관계 | #### 출력 예시 ```text Validation passed. ``` 오류가 있으면 메시지에 표시된 항목을 수정한 뒤 재검증합니다. #### 설치 전 실행 계획 확인 신규 설치 전에 SSH 접속, 패키지 파일 존재 여부, 원격 디렉터리 권한까지 확인하려면 `install --dry-run --verbose`를 사용합니다. ```bash machclusterctl install -f cluster.yaml --dry-run --verbose ``` 설치 후 구성 변경을 검증할 때는 `apply --dry-run`을 사용합니다. 현재 클러스터 상태와 YAML의 차이를 계산해 실행 계획을 보여주며, 실제 원격 변경 작업은 수행하지 않습니다. ```bash machclusterctl apply -f cluster.yaml --dry-run --verbose ``` #### 일반적인 오류와 해결 | 오류 | 원인 | 해결 | |------|------|------| | `field ... not found` | 지원하지 않는 YAML 키 사용 | 현행 스키마의 `cluster.*` 항목으로 수정 | | `required field ...` | 필수 값 누락 | 메시지에 표시된 필드를 추가 | | `duplicate alias` | 노드 별칭 중복 | 모든 노드 별칭을 고유하게 변경 | | `port conflict` | 같은 호스트에서 동일 포트 사용 | 충돌한 포트를 다르게 지정. 홈 경로만 바꾸어도 포트 충돌은 해결되지 않음 | | `deployer ... not found` | Lookup/Broker/Warehouse가 존재하지 않는 Deployer를 참조 | `deployer` 값을 Deployer 별칭 또는 호스트:포트로 수정 | --- ### 최초 설치 `cluster.yaml` 작성과 유효성 검사가 완료되면 설치 계획을 확인한 뒤 클러스터를 설치합니다. #### 1. 클러스터 설치 패키지를 각 노드에 배포하고 초기화하기 전에 실행 계획과 사전 점검 결과를 확인합니다. ```bash machclusterctl install -f cluster.yaml --dry-run --verbose ``` 문제가 없으면 실제 설치를 실행합니다. ```bash machclusterctl install -f cluster.yaml --yes --verbose ``` 이 명령은 다음을 자동으로 처리합니다. 1. 패키지를 각 노드에 복사하고 압축 해제 2. 각 노드의 `machbase.conf` 생성 및 포트 설정 3. Coordinator 데이터베이스 초기화 4. 노드 등록 (Coordinator에 각 노드 추가) #### 2. 클러스터 시작 `install`은 Coordinator, Deployer, Lookup, Broker, Warehouse를 준비하고 기동합니다. 설치 후 전체 클러스터를 다시 시작해야 할 때만 다음 명령을 사용합니다. ```bash machclusterctl start ``` #### 3. 상태 확인 ```bash export MACHBASE_COORDINATOR_HOME=/home/machbase/coordinator machclusterctl status ``` 한 서버에서 여러 Coordinator 홈을 번갈아 확인할 때는 직접 지정합니다. ```bash machclusterctl status --coordinator /home/machbase/coordinator ``` 출력은 `machcoordinatoradmin --cluster-status-full --verbose` 형식입니다. 아래는 출력 열을 보여 주는 일부 행의 예시이며 YAML의 전체 노드 목록이 아닙니다. Coordinator와 Broker는 `primary`, `leader` 같은 역할 상태를 가질 수 있으므로 모든 행이 `normal`인지가 아니라 등록 노드 수와 역할별 목표 상태·실제 상태가 맞는지 확인합니다. ``` +-------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------+-----------------+----------+ | Node Type | Node Name | Group Name | Group State | Desired & Actual State | RP State | Disk(%) (00/00) | Ping(μs) | +-------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------+-----------------+----------+ | coordinator | coord-1(192.168.1.10:5101) | Coordinator | normal | primary | primary | ----------- | --------------- | 214 | | deployer | deployer-1(192.168.1.10:5201) | Deployer | normal | running | running | ----------- | --------------- | 100 | | broker | broker-1(192.168.1.11:5401) | Broker | normal | leader | leader | ----------- | --------------- | 100 | | warehouse | wh-g1-1(192.168.1.13:5501) | group1 | normal | normal | normal | running | 26.9 | 100 | +-------------+--------------------------------+--------------------------------+--------------------------------+-------------------------------+-------------+-----------------+----------+ ``` #### 4. 클라이언트 접속 테스트 Broker 노드의 IP와 포트로 접속합니다. ```bash machsql -s 192.168.1.11 -P 5656 -u SYS -p MANAGER # Mach> ``` #### 클러스터 종료 ```bash machclusterctl stop ``` --- ### 설치 후 운영 최초 설치와 접속 검증이 끝난 뒤의 노드 구성 변경, 노드 추가·제거와 상태 복구는 [Cluster 운영](../../operations-configuration-recovery/cluster/)을 따릅니다. 장애 원인 분류와 복구 판단은 [Cluster 문제 해결](../../troubleshooting/cluster/)을 참고하십시오. ## machcoordinatoradmin 기반 수동 배포 `machclusterctl`을 사용할 수 없을 때는 Coordinator와 Deployer를 직접 준비하고 패키지와 노드를 등록합니다. 자동 배포가 끝난 환경에 아래 생성 명령을 다시 실행하지 마십시오. 수동 예제는 YAML과 별개의 구성입니다. 역할별 실행 위치를 바꾸어 명령을 수행합니다. | 호스트 | 역할 | |---|---| | `192.168.1.10` | Primary Coordinator, Deployer, Lookup master | | `192.168.1.11` | Deployer, Lookup monitor, Broker | | `192.168.1.13` | Deployer, Warehouse group1 첫 노드 | | `192.168.1.14` | Deployer, Warehouse group1 복제 노드 | | `192.168.1.20` | 선택적인 Secondary Coordinator | 같은 호스트의 역할마다 홈과 포트를 분리합니다. Deployer는 데이터 처리 노드가 설치될 각 호스트에서 준비하며, 아래 `--deployer` 주소도 대상 호스트와 맞춥니다. ### 수동 배포 순서 1. [Package 준비와 등록](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-package) — 전체 패키지 설치와 경량 패키지 등록 준비 2. [Coordinator / Deployer 설치](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-coordinator-deployer) — 핵심 관리 노드 구동 3. [Package 등록](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-package) — 실행 중인 Coordinator에 경량 패키지 등록 4. [Lookup / Broker / Warehouse 설치](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-lookup-broker-warehouse) — 데이터 처리 노드 등록 및 구동 5. [전체 상태 확인](/dbms/installation-deployment-upgrade/cluster-edition/#manual-machcoordinatoradmin-lookup-broker-warehouse) — 등록·구동 후 최초 상태 검증 ### machclusterctl 대비 차이점 | 항목 | machclusterctl | 수동 배포 | |------|---------------|----------| | 설정 방식 | cluster.yaml 단일 파일 | 각 노드 machbase.conf 직접 편집 | | 패키지 배포 | 자동 원격 복사 | Coordinator/Deployer는 직접 설치, Broker/Warehouse는 Deployer가 배포 | | 노드 등록 | 자동 | `machcoordinatoradmin --add-node` 수동 실행 | | 일괄 시작·종료 | `machclusterctl start/stop` | 노드별 개별 실행 | 수동 배포는 유연성이 높지만 실수 가능성도 높습니다. 신규 구축에는 `machclusterctl`을 권장합니다. --- ### Package 준비와 등록 Cluster Edition 수동 배포의 패키지 준비와 등록 절차입니다. Coordinator와 Deployer에는 전체 패키지를 설치하고 환경 변수를 설정합니다. Broker와 Warehouse 배포에 사용할 경량 패키지는 Coordinator가 기동된 후에 등록합니다. #### 패키지 종류 Cluster Edition에는 두 가지 패키지가 있습니다. | 패키지 | 대상 노드 | 특징 | |--------|-----------|------| | 전체 패키지 | Coordinator, Deployer | 모든 실행 파일 포함 | | 경량 패키지 (lightweight) | Broker, Warehouse | 데이터 처리에 필요한 파일만 포함, 크기 작음 | 파일명 예시: - 전체: `machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz` - 경량: `machbase-cluster-8.7.0.official-LINUX-X86-64-release-lightweight.tgz` #### Coordinator와 Deployer에 패키지 배포 전체 패키지 파일을 Coordinator와 Deployer 노드에 복사하고 압축 해제합니다. ##### Coordinator 노드 ```bash # Coordinator 노드에서 실행 mkdir -p /home/machbase/coordinator scp machbase@package-host:/path/to/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz /home/machbase/ tar zxf /home/machbase/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz -C /home/machbase/coordinator ``` ##### Deployer 노드 ```bash mkdir -p /home/machbase/deployer scp machbase@package-host:/path/to/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz /home/machbase/ tar zxf /home/machbase/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz -C /home/machbase/deployer ``` Broker와 Warehouse 노드에는 이 단계에서 경량 패키지를 직접 압축 해제하지 않습니다. 경량 패키지를 Coordinator에 등록하면, 이후 `--add-node`로 지정한 Deployer가 대상 노드의 `--home-path`에 패키지를 배포합니다. #### Coordinator에 패키지 등록 Broker와 Warehouse를 Coordinator에서 기동하려면 경량 패키지를 Coordinator에 등록해야 합니다. Coordinator와 Deployer를 설치하고 Coordinator가 실행 중인 상태에서 다음 명령을 실행합니다. ```bash $MACHBASE_COORDINATOR_HOME/bin/machcoordinatoradmin --add-package=machbase \ --file-name="/home/machbase/machbase-cluster-8.7.0.official-LINUX-X86-64-release-lightweight.tgz" ``` 등록된 패키지는 이후 Broker와 Warehouse를 `--add-node`로 등록할 때 `--package-name=machbase`로 참조합니다. #### 환경 변수 설정 `package-host`와 `/path/to/`는 실제 패키지 보관 위치로 바꿉니다. Coordinator와 Deployer를 같은 호스트에서 관리하더라도 별도의 셸에서 역할에 맞는 환경을 사용합니다. 다음은 각 셸에 적용할 값이며, 서비스나 로그인 초기화 파일에도 같은 값을 반영합니다. ```bash # Coordinator 노드 export MACHBASE_COORDINATOR_HOME=/home/machbase/coordinator export MACHBASE_HOME=$MACHBASE_COORDINATOR_HOME export PATH=$MACHBASE_HOME/bin:$PATH export LD_LIBRARY_PATH=$MACHBASE_HOME/lib:$LD_LIBRARY_PATH ``` ```bash # 별도 Deployer 관리 셸 export MACHBASE_DEPLOYER_HOME=/home/machbase/deployer export MACHBASE_HOME=$MACHBASE_DEPLOYER_HOME export PATH="$MACHBASE_HOME/bin:$PATH" export LD_LIBRARY_PATH="$MACHBASE_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" ``` --- ### Coordinator / Deployer 설치 패키지 배포가 완료되면 Coordinator를 먼저 설치·시작한 후 Deployer를 등록합니다. #### Coordinator 설치 ##### 1. machbase.conf 설정 `$MACHBASE_COORDINATOR_HOME/conf/machbase.conf`를 편집합니다. ```bash vi $MACHBASE_COORDINATOR_HOME/conf/machbase.conf ``` 주요 설정: ``` CLUSTER_LINK_HOST = 192.168.1.10 # 이 노드의 IP CLUSTER_LINK_PORT_NO = 5101 HTTP_ADMIN_PORT = 5102 ``` ##### 2. 메타 데이터베이스 생성 및 서비스 시작 ```bash machcoordinatoradmin -c machcoordinatoradmin -u ``` ##### 3. 자기 자신을 Coordinator 노드로 등록 ```bash machcoordinatoradmin --add-node="192.168.1.10:5101" \ --node-type=coordinator \ --http-admin-port=5102 ``` ##### 4. 등록 확인 ```bash machcoordinatoradmin --cluster-status ``` #### Secondary Coordinator 설치 (선택) 고가용성을 위해 Secondary Coordinator를 추가합니다. Secondary 노드에서 패키지를 배포하고 `machbase.conf`를 설정한 후, **Primary Coordinator에서** 먼저 노드를 등록합니다. ```bash # Primary Coordinator에서 먼저 등록 machcoordinatoradmin --add-node="192.168.1.20:5101" \ --node-type=coordinator \ --http-admin-port=5102 # 그 다음 Secondary 노드에서 시작 (--primary 옵션으로 Primary 지정) machcoordinatoradmin -u --primary=192.168.1.10:5101 ``` Secondary를 시작하기 전에 반드시 Primary에서 노드 등록을 완료해야 합니다. #### Deployer 설치 수동 구성표의 각 Deployer 호스트에서 이 절차를 수행합니다. 아래 설정의 `CLUSTER_LINK_HOST`는 그 호스트 자신의 IP로 바꿉니다. 다른 호스트의 주소를 그대로 복사하면 노드의 통신 주소와 실제 실행 위치가 달라집니다. ##### 1. machbase.conf 설정 ``` CLUSTER_LINK_HOST = 192.168.1.10 # Deployer 노드 IP CLUSTER_LINK_PORT_NO = 5201 HTTP_ADMIN_PORT = 5202 ``` ##### 2. 시작 ```bash machdeployeradmin -c machdeployeradmin -u ``` ##### 3. Coordinator에 Deployer 노드 등록 모든 Deployer가 시작되면 Primary Coordinator 관리 셸에서 등록합니다. ```bash machcoordinatoradmin --add-node="192.168.1.10:5201" \ --node-type=deployer \ --http-admin-port=5202 machcoordinatoradmin --add-node="192.168.1.11:5201" \ --node-type=deployer --http-admin-port=5202 machcoordinatoradmin --add-node="192.168.1.13:5201" \ --node-type=deployer --http-admin-port=5202 machcoordinatoradmin --add-node="192.168.1.14:5201" \ --node-type=deployer --http-admin-port=5202 ``` --- ### Lookup / Broker / Warehouse 설치 Coordinator와 Deployer가 준비된 후 Lookup, Broker, Warehouse 노드를 Coordinator에 등록하고 시작합니다. Broker와 Warehouse를 등록하기 전에 경량 패키지를 Coordinator에 `--add-package`로 등록해야 합니다. #### Lookup 노드 Lookup master와 monitor를 각각 등록하고 시작합니다. 다음 예제는 Primary 호스트와 Broker 호스트에 배치하며, 각 호스트의 Deployer를 사용합니다. 같은 홈이 기존 Lookup에 사용 중이 아닌지 확인합니다. ```bash machcoordinatoradmin --add-node="192.168.1.10:5301" \ --node-type=lookup \ --lookup-type=master \ --deployer="192.168.1.10:5201" \ --home-path="/home/machbase/lookup" machcoordinatoradmin --add-node="192.168.1.11:5301" \ --node-type=lookup \ --lookup-type=monitor \ --deployer="192.168.1.11:5201" \ --home-path="/home/machbase/lookup" machcoordinatoradmin --startup-node="192.168.1.10:5301" machcoordinatoradmin --startup-node="192.168.1.11:5301" ``` #### Broker 설치 ##### 1. 등록 파라미터 확인 Broker 설정 파일은 `--add-node` 실행 때 생성되어 Deployer를 통해 대상 노드에 배포됩니다. 등록 전에 클러스터 통신 포트와 서비스 포트를 확정합니다. Broker에는 HTTP 관리 포트를 지정하지 않습니다. ``` CLUSTER_LINK_HOST = 192.168.1.11 # Broker 노드 IP CLUSTER_LINK_PORT_NO = 5401 PORT_NO = 5656 # 클라이언트 접속 포트 ``` ##### 2. Coordinator에 노드 등록 Coordinator 노드에서 실행합니다. ```bash machcoordinatoradmin --add-node="192.168.1.11:5401" \ --node-type=broker \ --deployer="192.168.1.11:5201" \ --package-name=machbase \ --home-path="/home/machbase/broker" \ --dbs-path="/data/machbase/broker_dbs" \ --port-no=5656 ``` | 파라미터 | 설명 | |---------|------| | `--add-node` | 등록할 노드의 IP:CLUSTER_LINK_PORT_NO | | `--node-type` | `broker` / `warehouse` / `lookup` | | `--deployer` | 해당 노드를 설치하고 제어할 Deployer의 IP:CLUSTER_LINK_PORT_NO | | `--package-name` | Coordinator에 등록한 패키지 이름 | | `--home-path` | 노드 홈 디렉터리 | | `--dbs-path` | Broker/Warehouse의 데이터 파일 경로. 생략하면 기본 `DBS_PATH`를 사용 | | `--port-no` | 클라이언트 또는 노드 서비스 포트 | | `--replication` | Warehouse 복제 관리자 주소입니다. `host:port` 형식을 사용합니다. | ##### 3. 노드 시작 Coordinator에서 해당 노드를 시작합니다. ```bash machcoordinatoradmin --startup-node="192.168.1.11:5401" ``` #### Warehouse 설치 Warehouse 노드는 그룹 단위로 구성합니다. 같은 그룹의 노드끼리 데이터를 복제합니다. ##### 1. 등록 파라미터 확인 Warehouse 설정 파일은 `--add-node` 실행 때 생성되어 Deployer를 통해 대상 노드에 배포됩니다. 등록 전에 클러스터 통신 포트, 서비스 포트, 복제 관리자 주소를 확정합니다. ``` CLUSTER_LINK_HOST = 192.168.1.13 CLUSTER_LINK_PORT_NO = 5501 PORT_NO = 5500 ``` ##### 2. 노드 등록 ```bash machcoordinatoradmin --add-node="192.168.1.13:5501" \ --node-type=warehouse \ --deployer="192.168.1.13:5201" \ --package-name=machbase \ --home-path="/home/machbase/warehouse_g1_1" \ --dbs-path="/data/machbase/warehouse_g1_1_dbs" \ --port-no=5500 \ --replication=192.168.1.13:5502 \ --group=group1 \ --no-replicate machcoordinatoradmin --add-node="192.168.1.14:5501" \ --node-type=warehouse \ --deployer="192.168.1.14:5201" \ --package-name=machbase \ --home-path="/home/machbase/warehouse_g1_2" \ --dbs-path="/data/machbase/warehouse_g1_2_dbs" \ --port-no=5500 \ --replication=192.168.1.14:5502 \ --group=group1 ``` 별도 `--add-group` 명령은 사용하지 않습니다. Warehouse 그룹 이름은 각 Warehouse 노드를 등록할 때 `--group`으로 지정합니다. ##### 3. 노드 시작 ```bash machcoordinatoradmin --startup-node="192.168.1.13:5501" machcoordinatoradmin --startup-node="192.168.1.14:5501" ``` #### 전체 상태 확인 모든 노드 등록 후 상태를 확인합니다. ```bash machcoordinatoradmin --cluster-status ``` Coordinator, Lookup, Broker, Warehouse가 각 역할에 맞는 정상 상태로 표시되면 클러스터가 정상 구동 중입니다. Coordinator는 `primary`, Broker는 `leader`, Warehouse는 `normal`, `sync-active`, `sync-standby` 등으로 표시됩니다. #### 최초 접속 확인 Broker 네이티브 포트로 접속해 `SELECT CURRENT_DATABASE();`와 표본 조회를 실행합니다. 최초 설치 검증이 끝난 뒤의 개별 노드 시작·종료와 상태 복구는 [Cluster 운영](../../operations-configuration-recovery/cluster/)과 [Cluster 문제 해결](../../troubleshooting/cluster/)을 사용하십시오. --- title: "3.4 업그레이드" url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/upgrade/ language: kr kind: page --- # 3.4 업그레이드 업그레이드는 실행 파일 교체뿐 아니라 기존 데이터, 설정과 애플리케이션이 새 버전에서 같은 의미로 동작하는지 확인하는 작업입니다. 먼저 지원되는 버전 간 경로를 확인하고, 복원 가능한 백업과 서비스 재개 기준을 준비합니다. 다음 예제의 8.7.0 패키지명과 경로는 제공받은 실제 배포본에 맞춥니다. ## 업그레이드 전 확인사항 - 현재 버전과 대상 버전의 호환성을 확인합니다. Minor 버전이 다르면 DB 파일 형식이 변경될 수 있습니다. - 업그레이드 전 백업을 수행합니다. [백업 방법](/dbms/operations-configuration-recovery/backup-restore-mount/#backup) 참고. - 진행 중인 INSERT·APPEND 클라이언트를 확인합니다. ### 8.7.0 업그레이드 사전 점검 8.5에서 8.7.0으로 업그레이드할 때는 바이너리를 교체하기 전에 다음 의존성을 조사하고 지원되는 방식으로 전환합니다. 1. 제거된 `HTTP_AUTH`, `HTTP_ENABLE`, `HTTP_MAX_MEM`, `HTTP_PORT_NO`, `RS_CACHE_*`, `STREAM_THREAD_COUNT`, `STREAM_WAIT_MS` 설정을 현재 파일에서 찾아 지원 목록과 대조합니다. 제거된 설정은 삭제하고 유지 설정은 보존합니다. 특히 Cluster의 `HTTP_ADMIN_PORT`는 현행 관리 포트이므로 `HTTP_*`라는 이유로 함께 제거하지 마십시오. 2. `/machbase`, `/machiot`를 호출하는 애플리케이션은 지원되는 SDK를 사용하는 백엔드로 전환합니다. 3. `STREAM_*` 프로시저와 `FLUSH RESULT_CACHE`를 실행하는 SQL 또는 운영 스크립트를 변경합니다. 4. `machcli.h`와 `MachCLI*()`를 사용하는 C/C++ 애플리케이션을 Machbase SQLCLI 또는 ODBC로 이전합니다. SQLCLI와 ODBC는 서로 다른 API 집합입니다. 5. WebAdmin/MWA에 의존하는 운영 절차와 대시보드는 명령행 도구 또는 별도 애플리케이션으로 전환합니다. 제거 항목 전체와 유지 기능은 [버전 및 호환성](/dbms/reference/support-scope-constraints/compatibility-version/#removed-features-870)을 참고하십시오. ## 업그레이드 경로 | 에디션 | 방식 | 링크 | |--------|------|------| | Standard Edition | 서버 종료 후 패키지 교체 | [Standard Edition 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#standard-edition) | | Cluster Edition | Broker/Warehouse 순차 업그레이드 | [온라인 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-online) | | Cluster Edition | 전체 중지 | [전체 중지 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-full-stop) | Cluster Edition의 경우 데이터 가용성 요구사항에 따라 온라인 또는 전체 중지 방식을 선택합니다. --- ## Standard Edition 업그레이드 서버를 종료하고 패키지를 교체한 후 재시작합니다. 물리 DB 파일을 그대로 여는 것이 지원되는 버전 간 경로에만 이 절차를 적용합니다. 데이터 변환이나 export/import가 필요한 경로는 해당 릴리스의 마이그레이션 절차를 먼저 수행합니다. ### 업그레이드 전 준비 1. **백업 수행**: 지원되는 BACKUP 명령으로 백업을 만들고 별도 환경에서 복원 여부를 확인합니다. 실행 중인 데이터 디렉터리의 단순 복사만으로 복구 가능한 백업을 확보했다고 판단하지 마십시오. 2. **클라이언트 연결 종료**: 진행 중인 Append 또는 INSERT 작업을 모두 완료합니다. 3. **현재 버전 확인**: ```bash machbased -v ``` ### 업그레이드 절차 #### 1. 서버 종료 ```bash machadmin -s # Machbase server shut down successfully. ``` #### 2. 기존 패키지와 설정 보관 실행 파일과 라이브러리뿐 아니라 현재 설정과 라이선스도 별도 위치에 보관합니다. 아래 경로에 이전 백업이 없는지 확인한 뒤 실행합니다. ```bash cp -a "$MACHBASE_HOME/bin" "$MACHBASE_HOME/bin.bak" cp -a "$MACHBASE_HOME/lib" "$MACHBASE_HOME/lib.bak" cp -a "$MACHBASE_HOME/conf" "$MACHBASE_HOME/conf.bak" ``` **데이터 디렉터리(`dbs/`)와 별도로 지정한 `DBS_PATH`는 보존합니다.** 위 복사는 실행 파일과 설정의 보관이며 DB 백업을 대체하지 않습니다. 새 버전이 데이터를 변경한 뒤에는 이전 실행 파일만 되돌려 복구할 수 있다고 가정하지 말고, 검증한 백업 복원 경로를 사용합니다. #### 3. 새 패키지 압축 해제 새 패키지는 별도 작업 디렉터리에 해제하여 구성과 설정 변경 사항을 먼저 확인합니다. ```bash upgrade_stage=$(mktemp -d) tar zxf machbase-SDK-8.7.0.official-LINUX-X86-64-release.tgz -C "$upgrade_stage" ``` 압축 해제만으로 기존 설치의 실행 파일이 바뀌지는 않습니다. 다음은 `bin/`, `lib/`, `include/`를 포함하는 Standard tarball에서 실행 파일·라이브러리·헤더를 반영하는 예입니다. 먼저 서버가 종료되었는지 확인하고, 명령이 실패하면 다음 시작 단계로 넘어가지 않습니다. ```bash ( set -e test -n "$MACHBASE_HOME" test -x "$upgrade_stage/bin/machbased" test -d "$upgrade_stage/lib" test -d "$upgrade_stage/include" test -d "$MACHBASE_HOME/bin" test -d "$MACHBASE_HOME/lib" test -d "$MACHBASE_HOME/include" cp -a "$upgrade_stage/bin/." "$MACHBASE_HOME/bin/" cp -a "$upgrade_stage/lib/." "$MACHBASE_HOME/lib/" cp -a "$upgrade_stage/include/." "$MACHBASE_HOME/include/" "$MACHBASE_HOME/bin/machbased" -v ) ``` 기존 `conf/machbase.conf`, 라이선스와 실제 `DBS_PATH`의 데이터는 보존하고 새 설정 항목은 기존 설정에 병합합니다. 복사는 같은 이름의 배포 파일을 교체하며 이전 버전에서만 있던 파일을 자동 삭제하지 않습니다. SDK나 플러그인은 새 버전에 맞는 파일을 명시적으로 선택하고, 추가 교체 대상과 제거 항목은 릴리스 안내를 확인합니다. 출력된 바이너리 버전과 설정 검토가 완료된 뒤에만 서버를 시작합니다. #### 4. 서버 시작 ```bash machadmin -u # Machbase server started successfully. ``` #### 5. 버전 확인 ```bash machbased -v # 서버 접속 machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER ``` ```sql SELECT EDITION, BINARY_DB_MAJOR_VERSION, BINARY_DB_MINOR_VERSION FROM V$VERSION; ``` ### 주의사항 - `dbs/` 디렉터리를 절대 삭제하거나 초기화(`machadmin -d`)하지 마십시오. - Minor 버전 간 업그레이드는 DB 파일 마이그레이션이 필요할 수 있습니다. 릴리스 노트를 반드시 확인하십시오. - Windows 환경에서는 새 패키지 또는 설치 실행 파일을 적용하기 전에 Machbase 서비스를 중지합니다. --- ## Cluster Edition 업그레이드 서비스 중단 여부에 따라 두 가지 방식을 선택합니다. | 방식 | 서비스 중단 | 적합한 상황 | |------|-----------|------------| | [온라인 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-online) | Broker/Warehouse 순차 재기동 | Broker와 Warehouse만 교체하는 운영 환경 | | [전체 중지 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-full-stop) | 있음 | 유지보수 창이 허용되는 경우, Major 버전 변경 | ### 업그레이드 전 공통 주의사항 - 업그레이드 중에는 DDL 또는 DELETE를 실행하지 마십시오. - 업그레이드 중 노드 추가·시작·종료·삭제 작업을 병행하지 마십시오. - 온라인 업그레이드는 Broker와 Warehouse를 대상으로 합니다. Coordinator, Deployer, Lookup까지 교체하려면 전체 중지 업그레이드를 사용합니다. - 업그레이드 전 백업을 권장합니다. --- ### 온라인 업그레이드 실행 중인 클러스터에서 Broker와 Warehouse를 순차적으로 업그레이드합니다. Coordinator, Deployer, Lookup까지 포함한 전체 바이너리 교체가 필요하면 [전체 중지 업그레이드](/dbms/installation-deployment-upgrade/upgrade/#cluster-edition-full-stop)를 사용합니다. #### 업그레이드 절차 ##### 1. cluster.yaml의 패키지 변경 `cluster.package.name`과 `cluster.package.origin_path`를 새 패키지로 변경합니다. 패키지 내용이 바뀌면 패키지 이름과 압축 파일 이름도 함께 고유하게 바꿉니다. ```yaml cluster: package: name: machbase-v8.7.0 origin_path: /home/machbase/packages/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz ``` `registered_path`는 `machclusterctl export`가 기록하는 Coordinator 패키지 저장소 경로입니다. 업그레이드할 압축 파일을 지정할 때는 `origin_path`를 사용합니다. ##### 2. 실행 계획 확인 ```bash machclusterctl upgrade -f cluster.yaml --online --dry-run --verbose ``` ##### 3. 온라인 업그레이드 실행 ```bash machclusterctl upgrade -f cluster.yaml --online --yes --verbose ``` `--online`을 생략해도 온라인 모드로 처리되지만, 운영 절차를 명확히 하기 위해 옵션을 명시하는 것을 권장합니다. ##### 4. 전체 상태 확인 ```bash machclusterctl status ``` #### 수동 업그레이드 참고 `machcoordinatoradmin --upgrade-node`를 직접 사용하는 경우에는 대상 노드와 패키지 이름을 함께 지정합니다. ```bash machcoordinatoradmin --upgrade-node=192.168.1.11:5401 --package-name=machbase-v8.7.0 ``` 온라인 대상은 Broker와 Warehouse로 제한합니다. Broker가 하나만 남아 있을 때 해당 Broker를 업그레이드하면 그 시간 동안 클라이언트 접속이 끊길 수 있습니다. 온라인 모드는 전체 클러스터 중지를 생략하는 방식이며 무중단을 보장하는 HA-aware rolling upgrade는 아닙니다. Warehouse 그룹이 일시적으로 읽기 전용이 될 수 있으므로 애플리케이션의 재접속·재시도와 쓰기 지연을 검증해야 합니다. 프로토콜 호환성이 달라지거나 모든 역할의 바이너리를 맞춰야 한다면 전체 중지 방식을 사용합니다. 모든 대상 노드의 역할 상태와 버전을 확인한 뒤 대표 조회·입력과 복제 상태까지 검증합니다. --- ### 전체 중지 업그레이드 클러스터를 완전히 종료한 후 모든 노드를 일괄 업그레이드합니다. Coordinator, Deployer, Lookup까지 포함해 전체 바이너리를 교체해야 하거나 DB 파일 형식 변경이 수반되는 경우에 사용합니다. #### 업그레이드 절차 ##### 1. 클라이언트 연결 종료 모든 INSERT·APPEND·SELECT 작업이 완료되었는지 확인합니다. ##### 2. cluster.yaml의 패키지 변경 `cluster.package.name`과 `cluster.package.origin_path`를 새 패키지로 변경합니다. 업그레이드 전에는 노드 추가, 삭제, 포트 변경 같은 토폴로지 변경이 없어야 합니다. 토폴로지 변경이 있으면 먼저 `apply`로 반영한 뒤 업그레이드를 수행합니다. `machclusterctl upgrade --full-stop`은 Coordinator와 Deployer를 포함한 모든 노드 홈에 같은 패키지를 교체 반영합니다. 따라서 `origin_path`에는 `machcoordinatoradmin`과 `machdeployeradmin`이 포함된 전체 Cluster 패키지를 지정합니다. ##### 3. 실행 계획 확인 ```bash machclusterctl upgrade -f cluster.yaml --full-stop --dry-run --verbose ``` ##### 4. 전체 중지 업그레이드 실행 ```bash machclusterctl upgrade -f cluster.yaml --full-stop --yes --verbose ``` `machclusterctl`은 전체 클러스터 중단을 전제로 패키지를 임시 준비 경로에 해제한 뒤 노드 홈에 교체 반영합니다. #### 수동 배포 참고 수동 배포 환경에서 직접 교체해야 하는 경우, Coordinator에 새 패키지를 등록합니다. ```bash machcoordinatoradmin --add-package=machbase-v8.7.0 \ --file-name=/home/machbase/packages/machbase-cluster-8.7.0.official-LINUX-X86-64-release.tgz ``` Warehouse → Broker → Lookup → Deployer → Coordinator 순으로 종료합니다. ```bash machcoordinatoradmin --shutdown-node=192.168.1.13:5501 machcoordinatoradmin --shutdown-node=192.168.1.14:5501 machcoordinatoradmin --shutdown-node=192.168.1.11:5401 machcoordinatoradmin --shutdown-node=192.168.1.10:5301 machdeployeradmin --shutdown machcoordinatoradmin --shutdown ``` 각 노드 홈을 새 패키지로 교체할 때는 기존 `conf/machbase.conf`, `dbs/`, `meta/`, `package/` 디렉터리를 보존합니다. 별도 작업 경로에 새 패키지를 해제한 뒤 보존 대상 경로를 제외하고 교체합니다. Coordinator → Deployer → Lookup → Broker → Warehouse 순으로 시작합니다. ```bash machcoordinatoradmin --startup machdeployeradmin --startup machcoordinatoradmin --startup-node=192.168.1.10:5301 machcoordinatoradmin --startup-node=192.168.1.11:5401 machcoordinatoradmin --startup-node=192.168.1.13:5501 machcoordinatoradmin --startup-node=192.168.1.14:5501 ``` 재시작 후 Broker와 Warehouse의 패키지 메타데이터를 새 패키지 이름으로 동기화합니다. ```bash machcoordinatoradmin --upgrade-node=192.168.1.11:5401 --package-name=machbase-v8.7.0 machcoordinatoradmin --upgrade-node=192.168.1.13:5501 --package-name=machbase-v8.7.0 machcoordinatoradmin --upgrade-node=192.168.1.14:5501 --package-name=machbase-v8.7.0 ``` ##### 5. 상태 확인 ```bash machclusterctl status ``` 노드 상태뿐 아니라 Broker 접속, 대표 데이터 조회·입력, 복제, 라이선스와 설정값을 확인한 뒤 서비스를 재개합니다. [설치 검증](../validation-checklist/) 결과를 변경 전 기록과 비교하고, 검증이 끝날 때까지 이전 패키지·설정과 백업을 보존합니다. #### 주의사항 - Major 버전 업그레이드는 DB 파일 형식이 변경될 수 있습니다. 릴리스 노트를 반드시 확인하고, 업그레이드 전 백업을 수행하십시오. - `conf/machbase.conf`, `dbs/`, `meta/`, `package/` 경로를 절대 삭제하거나 초기화하지 마십시오. --- title: "3.5 설치 검증 체크리스트" url: https://docs.machbase.com/kr/dbms/installation-deployment-upgrade/validation-checklist/ language: kr kind: page --- # 3.5 설치 검증 체크리스트 설치 검증은 프로세스가 살아 있는지 확인하는 것에서 끝나지 않습니다. 올바른 버전과 설정의 서버에 실제 클라이언트가 접속하고, 권한 범위에서 데이터를 기록·조회할 수 있어야 합니다. 아래 항목을 서버 상태 → 접속 → 버전·라이선스 → SQL → Cluster 복제 순서로 확인합니다. 각 결과에 실행 호스트, 설치 홈, 접속 주소·포트, 시각과 오류를 기록합니다. 업그레이드라면 변경 전 기록과 비교하십시오. `SYS`/`MANAGER`는 초기 실습 접속 예시이며 실제 계정으로 바꾸어야 합니다. 실습 테이블 이름이 기존 업무 객체와 겹치지 않는지도 먼저 확인합니다. ## Standard Edition 체크리스트 ### 1. 서버 프로세스 확인 ```bash machadmin -e # Machbase server is running with PID(). ``` 프로세스를 직접 확인할 수도 있지만 다른 설치 홈에서 실행된 서버인지 구분해야 합니다. `MACHBASE_HOME`이 검사할 인스턴스를 가리키는지 확인합니다. ```bash ps -ef | grep machbased | grep -v grep ``` ### 2. 포트 리스닝 확인 ```bash ss -tlnp | grep 5656 # LISTEN 0 128 0.0.0.0:5656 ... ``` 현재 운영체제에 맞는 포트 확인 도구를 사용합니다. 예시의 `5656`은 실제 SQL 포트로 바꾸고, 수신 주소가 의도한 인터페이스인지 확인합니다. 리스너 확인과 원격 클라이언트의 접속 성공은 별개의 검사이므로 애플리케이션이 실행될 호스트에서도 접속을 시험합니다. ### 3. machsql 접속 테스트 ```bash machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER # MACHBASE_CONNECT_MODE=INET, PORT=5656 EDITION=STANDARD # Mach> ``` 로컬 접속은 성공하고 원격 접속만 실패하면 주소·포트, 리스너 설정과 방화벽을 확인합니다. 인증 오류는 사용자명·인증 방식·만료 상태를 확인하며, 접속 성공 뒤 객체 접근 오류는 현재 데이터베이스와 SQL 권한을 별도로 점검합니다. ### 4. 버전 확인 ```sql SELECT * FROM V$VERSION; SELECT CURRENT_DATABASE(); ``` ### 5. 라이선스 확인 ```sql SELECT ID, ISSUE_DATE, TYPE, VIOLATE_STATUS FROM V$LICENSE_INFO; ``` `VIOLATE_STATUS`가 0인지 확인하고 설치된 라이선스 유형·기간이 운영 계획과 맞는지 봅니다. 위반 상태라면 `VIOLATE_MSG`와 서버 로그에서 원인을 확인합니다. ### 6. 기본 쿼리 테스트 ```sql CREATE LOG TABLE check_test (id INTEGER, ts DATETIME); INSERT INTO check_test (id, ts) VALUES (1, NOW); SELECT id, ts FROM check_test; DROP TABLE check_test; ``` 입력한 `id=1` 한 행과 시각이 반환되고 DROP이 성공하는지 확인합니다. 이 검사는 기본 LOG 입력 경로만 확인합니다. 실제 서비스에서 TAG·TRANSACTION·Append 등을 사용한다면 해당 테이블과 SDK로 대표 입력·조회도 수행합니다. ## Cluster Edition 추가 체크리스트 ### 7. 클러스터 노드 상태 확인 ```bash machcoordinatoradmin --cluster-status # 배포 기록의 노드 수와 역할별 상태를 비교 ``` ### 8. Broker 접속 테스트 ```bash machsql -s 192.168.1.11 -P 5656 -u SYS -p MANAGER ``` ```sql SELECT * FROM V$NODE_STATUS; ``` Coordinator의 `primary`, Broker의 `leader`, Warehouse의 복제 상태처럼 역할마다 정상 상태 표기가 다릅니다. 모든 노드를 단일 문자열과 비교하지 말고 목표 상태와 실제 상태, 그룹 구성과 주소가 배포 기록과 맞는지 확인합니다. ### 9. 데이터 복제 확인 먼저 Broker를 통한 입력·조회를 확인한 뒤 Warehouse 그룹의 복제 상태를 확인합니다. Broker에서 한 행이 조회된다는 사실만으로 모든 복제본의 동기화를 증명할 수는 없습니다. ```sql -- Broker를 통해 데이터 입력 CREATE LOG TABLE cluster_check_test (id INTEGER, ts DATETIME); INSERT INTO cluster_check_test (id, ts) VALUES (1, NOW); -- Broker에서 입력 결과 확인 SELECT COUNT(*) FROM cluster_check_test; ``` Warehouse 직접 SQL 접속은 일반 애플리케이션 경로가 아니라 복제 진단용 관리 절차입니다. `machcoordinatoradmin --cluster-status`에서 같은 복제 그룹의 active·standby peer를 확인한 뒤, 필요한 경우에만 각 peer의 네이티브 포트에 관리 계정으로 접속해 동일한 조회 결과를 비교합니다. 서로 다른 Warehouse 그룹의 모든 노드가 같은 행을 가진다고 가정하지 마십시오. 검증이 끝나면 Broker 연결에서 테이블을 정리합니다. ```sql DROP TABLE cluster_check_test; ``` --- ## 완료 기준과 문제 발생 시 서버·접속·권한·대표 SQL과 필요한 복제 검사가 모두 통과하면 서비스 인수를 진행합니다. 영속성을 검증해야 한다면 별도 검증 환경에서 데이터를 남긴 뒤 정상 재시작 전후 결과를 비교합니다. 업무 데이터가 있는 서버를 단순 설치 점검 목적으로 재초기화하지 마십시오. 실패하면 최초 오류와 관련 로그를 보존하고 실패한 단계부터 원인을 좁힙니다. - 서버 로그: `$MACHBASE_HOME/trc/machbase.trc` - [운영·장애 진단](/dbms/operations-configuration-recovery/diagnosis-observability/) 참고 --- title: "4. 테이블 타입과 스키마 설계" url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/ language: kr kind: section --- # 4. 테이블 타입과 스키마 설계 이 장에서는 저장할 데이터의 의미와 변경·조회 요구사항을 Machbase DBMS의 테이블과 컬럼으로 구체화합니다. 데이터베이스가 실행되는 것을 확인했다면, 이제 무엇을 한 행으로 저장할지부터 정합니다. 익숙한 테이블 유형을 먼저 고른 뒤 모든 데이터를 거기에 맞추면 시간 이력, 수정 범위와 보관 정책이 서로 충돌할 수 있습니다. 각 테이블 타입의 역할을 처음 접한다면 [데이터 모델 개념](/dbms/core-concepts/concepts/)을 먼저 읽으십시오. 이 장에서는 그 개념을 실제 스키마와 검증 가능한 설계로 연결합니다. ## 설계의 출발점 테이블 이름보다 먼저 한 행의 의미를 문장으로 적습니다. 예를 들어 “센서 하나의 측정값 한 건”, “설비 상태가 바뀐 사건 한 건”, “장비 하나의 현재 설치 정보”는 서로 다른 행의 단위입니다. 같은 설비에서 발생한 데이터라도 저장 역할이 다릅니다. 다음 질문에 답하면 테이블을 선택할 근거가 생깁니다. | 설계 질문 | 결정할 내용 | |---|---| | 한 행은 무엇인가? | 측정·이벤트·현재 상태·기준 정보 중 무엇을 저장하는지 | | 대상을 어떻게 구분하는가? | 태그 이름, 업무 키와 중복 수집 식별 기준 | | 시간이나 축은 무엇을 뜻하는가? | 측정 시각, 수신 시각 또는 거리·위치 | | 값을 어떻게 해석하는가? | 단위, 타입 범위, 정밀도와 NULL·결측 처리 | | 어떤 변경이 필요한가? | 새 행 추가, 과거 값 보정, 키 변경과 삭제 범위 | | 어떤 조회를 자주 하는가? | 태그·시간 구간, 조건 검색, 키 조회, 집계와 조인 | | 얼마나 남겨 두어야 하는가? | 원본·집계·기준 정보 이력의 보관 기간과 재시작 후 복구 | | 작업이 실패하면 무엇을 되돌리는가? | 테이블·API별 트랜잭션 범위와 재시도·재구성 절차 | 데이터 크기나 입력 빈도 하나만으로 타입을 결정하지 않습니다. 한 행에 모은 값들이 같은 관측이나 사건을 설명하는지 확인하고, 연결이 필요한 데이터는 공통 식별자로 조인할 수 있게 설계합니다. 조인 가능한 키가 있다는 사실과 DBMS가 모든 업무 관계를 자동으로 강제한다는 뜻도 구분합니다. 지원 제약 조건은 테이블별로 확인합니다. ## 이 장의 구성 | 순서 | 섹션 | 설계 결과 | |-----:|---|---| | 4.1 | [테이블 타입 선택](./table-types-selection-type/) | 변경·조회·영속성·Edition 요구에 맞는 타입 | | 4.2 | [스키마 객체 정의](./schema-objects-definition/) | 컬럼과 타입, 식별자, 기본값·제약, 인덱스와 VIEW | | 4.3 | [데이터 변경 정책](./alter-data-mutation-policy/) | 허용할 보정·삭제와 실패 처리의 범위 | | 4.4 | [안티패턴](./table-types-patterns-type-anti/) | 요구와 맞지 않는 선택을 찾아 수정할 근거 | | 4.5 | [모델링 패턴](./patterns-modeling/) | 이력·상태·기준 정보를 함께 사용하는 구체적인 모델 | 4.1에서 타입을 선택하고 4.2와 4.3에서 구조와 변경 정책을 정합니다. 이어서 4.4의 안티패턴을 점검하고 4.5의 패턴을 자신의 데이터에 맞게 적용합니다. 표의 비교 결과는 출발점이며 실제 SQL과 지원 조건은 연결된 테이블별 문서와 레퍼런스에서 확인합니다. ## 설비 모니터링을 예로 생각하기 온도, 알람, 장비 정보와 화면용 상태를 모두 같은 방식으로 저장할 필요는 없습니다. | 데이터 | 한 행의 의미 | 검토할 저장 역할 | |---|---|---| | 온도 이력 | 한 센서가 특정 시각에 측정한 값 | 태그·시간 범위 조회와 집계를 위한 TAG | | 알람 이력 | 특정 시각에 발생한 알람 사건 | 추가 중심 이벤트를 위한 LOG | | 장비 기준 정보 | 장비 하나의 이름·위치·허용 기준 | LOOKUP 또는 여러 변경을 트랜잭션으로 묶어야 하는 경우 TRANSACTION | | 화면용 현재 상태 | 다시 계산할 수 있는 최근 상태 | 원본과 재구성 경로가 있는 경우 VOLATILE | 이 표는 예시이지 고정된 정답은 아닙니다. 이벤트에 여러 측정 항목이 함께 들어오거나 수정·보관 요구가 다르면 다른 설계가 적합할 수 있습니다. TRANSACTION은 Standard Edition 지원 여부도 함께 확인합니다. 온도 이력에 최신 장비 정보를 조인하면 결과는 현재 위치와 이름으로 해석됩니다. 측정 당시 위치나 허용 기준이 필요하면 그 정보를 원본에 남기거나 기준 정보의 변경 이력을 설계해야 합니다. 현재 상태 캐시를 함께 갱신하는 경우에도 원본 입력과 캐시 갱신이 한 트랜잭션이라고 가정하지 말고, 실패 후 캐시를 다시 만들 방법을 준비합니다. ## 작은 데이터로 설계 검증하기 운영 규모로 늘리기 전에 대표 데이터와 자주 쓰는 쿼리로 다음을 확인합니다. 1. 정상값뿐 아니라 NULL, 누락, 경계값과 중복·지연 도착 데이터를 입력합니다. 2. 원본 조회와 집계 결과가 같은 시간 기준·단위·NULL 정책을 사용하는지 확인합니다. 3. 보정·삭제·재입력 후 결과를 비교하고 다른 행이나 관련 집계에 미치는 영향을 확인합니다. 4. 재시작 시 보존돼야 할 데이터와 다시 생성해야 할 상태를 구분해 복구 순서를 점검합니다. 5. 입력량·조회 범위·동시성을 늘려 처리량, 지연과 저장 공간을 측정합니다. 입력 오류, 오래된 스키마를 사용하는 애플리케이션과 재시작 복구는 정상 경로와 함께 설계할 대상입니다. 스키마를 변경할 때는 기존 데이터와 뷰·롤업·클라이언트의 의존성을 확인하고, 필요하면 새 테이블로 옮겨 검증한 뒤 전환합니다. 구현은 [테이블별 활용 장](/dbms/)과 [개발 및 애플리케이션 연동](/dbms/development-tools-integration/)으로, 측정은 [성능 튜닝](/dbms/performance-tuning/)으로 이어집니다. --- title: "4.1 테이블 타입 선택" url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/table-types-selection-type/ language: kr kind: page --- # 4.1 테이블 타입 선택 잘못된 타입 선택은 성능 저하와 기능 제한으로 이어지므로, 설계 초기에 데이터 성격에 맞는 타입을 결정해야 합니다. - **[타입 선택 결정 가이드](/dbms/data-modeling-table-design/table-types-selection-type/#selection-decision)** - **[타입 비교표](/dbms/data-modeling-table-design/table-types-selection-type/#comparison-tag-log-rdb-volatile-lookup)** - **[TRANSACTION vs LOOKUP 비교](/dbms/data-modeling-table-design/table-types-selection-type/#comparison-rdb-vs-lookup)** 테이블 타입의 역할과 저장 개념은 [데이터 모델 개념](../../core-concepts/concepts/#time-series)을 참고하십시오. 같은 데이터라도 이력을 누적할지, 현재 상태를 갱신할지에 따라 적합한 타입이 달라집니다. 변경·조회·영속성 요구사항을 함께 검토합니다. ### 선택 전에 작성할 데이터 설명 테이블 이름보다 먼저 한 행이 나타내는 사실을 한 문장으로 적습니다. 같은 설비에서 나온 데이터라도 “온도 측정 한 건”, “현재 운전 상태 한 건”, “정비 작업 한 건”은 서로 다른 행의 단위이며 키와 변경 방식도 달라집니다. | 설계 질문 | 설비 모니터링에서 정할 내용 | |---|---| | 한 행은 무엇인가? | 센서 한 개의 측정 한 건인지, 설비의 현재 상태인지 구분 | | 무엇으로 찾는가? | 센서 이름과 발생 시각, 설비 ID, 정비 작업 번호 | | 값은 어떻게 변하는가? | 새 이력 추가, 잘못된 값 보정, 현재 행 덮어쓰기 | | 함께 확정할 변경이 있는가? | 정비 작업 등록과 부품 수량 변경을 한 트랜잭션으로 묶을지 결정 | | 얼마나 보관하는가? | 원본 기간, 집계 기간, 재시작 후 재생성 가능 여부 | | 어느 정도의 크기인가? | 태그 수, 초당 행 수, 행 크기, 기준 정보와 인덱스의 메모리 사용량 | 예를 들어 온도 이력은 TAG, 알람 사건은 LOG, 설비 코드표는 LOOKUP이 후보입니다. 부품 재고 변경과 작업 등록을 함께 확정해야 하면 Standard Edition의 TRANSACTION을 검토합니다. 현재 상태 캐시는 원본에서 재구성할 수 있을 때 VOLATILE로 분리할 수 있습니다. 이들을 한 테이블로 합치기보다 각 행의 의미와 실패 시 복구 방법을 먼저 맞춥니다. ## 타입 선택 결정 가이드 아래 흐름으로 후보를 좁힌 뒤, 필요한 DML과 트랜잭션, 메모리 사용량, Edition 지원 여부를 비교표에서 확인합니다. 예를 들어 기준 정보라도 여러 변경을 하나의 트랜잭션으로 묶어야 한다면 LOOKUP 대신 TRANSACTION을 검토합니다. ### 결정 흐름 ``` 데이터가 센서/기기 계측값인가? ├── YES → 시간축인가? YES → TAG TABLE (BASETIME) │ 거리축인가? YES → TAG TABLE (BASEDISTANCE) └── NO ↓ 데이터가 이벤트/로그/패킷인가? (추가 전용) ├── YES → LOG TABLE └── NO ↓ 데이터가 코드 테이블/기준 정보인가? (반복 조회·갱신) ├── YES → LOOKUP TABLE └── NO ↓ 데이터가 서버 재시작 시 폐기 가능한 인메모리 상태/캐시인가? ├── YES → VOLATILE TABLE └── NO ↓ 일반 관계형 업무 데이터 (UPDATE/DELETE/SELECT/INSERT 모두 필요) └── TRANSACTION TABLE ``` ### 주요 판단 기준 | 질문 | 타입 | |------|------| | 시간 또는 거리 기반 계측값인가? | TAG | | 원본 이벤트를 추가하고 오래된 구간만 정리하는가? | LOG | | PRIMARY KEY가 필요한 기준 정보이며 반복 조회·갱신하는가? | LOOKUP | | 서버 재시작 시 데이터가 사라져도 되는가? | VOLATILE | | 일반 관계형 업무 (INSERT/UPDATE/DELETE/SELECT)? | TRANSACTION | ### 주의사항 - 이벤트마다 고유한 이름을 태그 식별자로 사용하면 태그 수와 메타데이터가 계속 늘어납니다. 반복 계측 대상이 없는 이벤트는 LOG 테이블을 검토합니다. - LOG 테이블은 UPDATE와 일반 조건 DELETE가 불가하므로 수정 가능성이 있는 데이터에는 부적합합니다. 보존/정리 목적의 `BEFORE`, `OLDEST`, `EXCEPT` DELETE만 사용합니다. - TRANSACTION 테이블은 Standard Edition 전용입니다. Cluster Edition 환경에서는 LOOKUP(소규모) 또는 외부 RDBMS를 활용합니다. - VOLATILE 테이블은 서버 재시작 시 데이터가 소멸됩니다. ## 타입 비교표 ### 기능 비교 | 항목 | TAG | LOG | TRANSACTION | VOLATILE | LOOKUP | |------|-----|-----|-----|----------|--------| | DDL | `CREATE TAG TABLE` | `CREATE LOG TABLE` | `CREATE TABLE` / `CREATE TRANSACTION TABLE` / `CREATE TXN TABLE` | `CREATE VOLATILE TABLE` | `CREATE LOOKUP TABLE` | | 주 용도 | 센서·계측값 | 이벤트·로그 | 관계형 업무 | 임시 집계 | 코드·기준 | | INSERT | O | O | O | O | O | | UPDATE | O (Standard, 태그/BASETIME 조건) | X | O | O | O | | DELETE | O (BEFORE/조건/전체) | O (BEFORE/OLDEST/EXCEPT/전체) | O | O (PK 일치/전체) | O (일반 조건식/전체 삭제) | | PRIMARY KEY | 필수 | X | 선택 | 선택 | 필수 | | BASETIME | 필수 (시간축) | X | X | X | X | | _arrival_time | X | 자동 추가 | X | X | X | | 인덱스 | 태그·축 접근, 지원되는 보조 인덱스 | BITMAP/KEYWORD/LSM | BTREE PK + 보조 인덱스 | 키·보조 인덱스 | 키·보조 인덱스 | | 영속성 | O | O | O | X (메모리) | O | | Cluster Edition | O | O | X | O | O | Append API는 테이블뿐 아니라 SDK와 입력 경로에 따라 지원 범위가 달라집니다. [SDK Append 지원표](../../development-tools-integration/sdk-support-scope/#append-table-type-matrix)에서 사용하는 드라이버와 테이블 조합을 확인합니다. ### 스토리지 특성 | 항목 | TAG | LOG | TRANSACTION | VOLATILE | LOOKUP | |------|-----|-----|-----|----------|--------| | 스토리지 | 컬럼형 | 컬럼형 | 행 기반 (관계형) | 메모리 | 영속 저장 + 전체 행 메모리 상주 | | 용량 검토 기준 | 태그 수·원본·ROLLUP·보관 기간 | 원본·검색 인덱스·보관 기간 | 행·인덱스·트랜잭션 부하 | 전체 행·인덱스의 메모리 크기 | 전체 행·인덱스의 메모리 크기와 재시작 적재 시간 | 메모리 테이블의 “작다”는 고정 행 수를 의미하지 않습니다. 행 폭, 가변 길이 값과 보조 인덱스를 포함해 실제 메모리 사용량을 측정합니다. 디스크 기반 테이블도 압축률이나 서버 사양만으로 처리량을 보장할 수 없으므로 대표 입력과 조회를 함께 실행합니다. ### TRANSACTION 테이블 제약 TRANSACTION 테이블은 다음 제약이 있습니다. - **Cluster Edition 미지원**: Standard Edition 전용 - **최소 컬럼 수**: 1개 이상 ## TRANSACTION vs LOOKUP 비교 TRANSACTION 테이블과 LOOKUP 테이블은 모두 관계형 데이터를 저장하지만, 대상 규모와 기능에 차이가 있습니다. ### 비교표 | 항목 | TRANSACTION 테이블 | LOOKUP 테이블 | |------|-----------|--------------| | DDL | `CREATE TRANSACTION TABLE` | `CREATE LOOKUP TABLE` | | PRIMARY KEY | 선택 | 필수 | | INSERT | O | O | | UPDATE (WHERE 포함) | O | O | | UPDATE (WHERE 없음) | O (전체 행) | X | | DELETE | O | O | | 명시적 트랜잭션 | 여러 문장을 COMMIT/ROLLBACK으로 제어 | 참여하지 않음, 문장별 변경 | | 인덱스 | BTREE PK + 보조 인덱스 | 메모리 키·보조 인덱스 | | 데이터 규모 | 디스크 용량과 트랜잭션 부하로 검증 | 참조 데이터 조회·갱신 부하로 검증 | | JOIN 대상 | O | O | | Cluster Edition | X | O | ### 선택 가이드 **TRANSACTION 테이블을 선택하는 경우** - 명시적 트랜잭션과 관계형 DML이 필요한 데이터 - UPDATE·DELETE·INSERT·SELECT가 모두 필요한 일반 관계형 워크로드 - PRIMARY KEY 없이 다양한 컬럼 조합으로 조회하는 경우 - Standard Edition 환경 **LOOKUP 테이블을 선택하는 경우** - 코드 테이블과 기준 정보 - PRIMARY KEY 조회와 단건 UPDATE/DELETE가 필요한 경우 - Cluster Edition 환경에서도 사용해야 하는 경우 - 실시간 기준 정보 갱신이 필요한 경우 ### 예시 ```sql -- LOOKUP: 국가 코드 테이블 (PK 조회와 조건 기반 UPDATE) CREATE LOOKUP TABLE country_code ( code VARCHAR(4) PRIMARY KEY, name VARCHAR(64) ); INSERT INTO country_code VALUES ('KR', 'Republic of Korea'); UPDATE country_code SET name = 'Korea' WHERE code = 'KR'; SELECT code, name FROM country_code WHERE code = 'KR'; -- TRANSACTION: 주문 이력 (대규모, 일반 UPDATE/DELETE 지원) CREATE TRANSACTION TABLE order_history ( order_id LONG PRIMARY KEY, item_id INTEGER, qty INTEGER, amount DECIMAL(18,2) ); INSERT INTO order_history VALUES (12345, 501, 1, 12000.00); UPDATE order_history SET qty = 10 WHERE order_id = 12345; SELECT order_id, qty, amount FROM order_history WHERE order_id = 12345; DELETE FROM order_history WHERE order_id = 12345; SELECT COUNT(*) FROM order_history WHERE order_id = 12345; ``` 첫 SELECT는 변경된 국가명을, 주문 SELECT는 수량 `10`을 반환합니다. 마지막 COUNT는 `0`입니다. 이 예제의 `amount`는 수량 변경과 별도로 유지하는 예시 금액이며 자동 재계산되지 않습니다. 실제 주문 모델에서는 단가·수량·합계의 관계와 함께 변경할 컬럼을 명시합니다. 실습을 끝내면 이 예제에서 만든 테이블만 `DROP TABLE`로 정리합니다. --- title: "4.2 스키마 객체 정의" url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/schema-objects-definition/ language: kr kind: page --- # 4.2 스키마 객체 정의 이 페이지는 테이블, 컬럼, 인덱스와 VIEW를 설계할 때 결정할 항목을 정리합니다. SQL 문법과 옵션을 중복해서 나열하지 않고 [SQL 문법 사전](/dbms/reference/sql/syntax/)을 정본으로 사용합니다. - **[테이블 생성과 삭제](#create-delete)** - **[테이블 변경](#alter)** - **[컬럼과 데이터 타입 선택](#selection-type-column-data-types)** - **[제약 조건과 기본값](#constraints-defaults-condition)** - **[인덱스 설계](#index-create-delete)** - **[VIEW 설계](#create-view)** ## 테이블 생성과 삭제 먼저 [테이블 타입 선택](../table-types-selection-type/)을 완료합니다. 이 페이지는 선택한 타입에 맞게 테이블 이름, 컬럼, 키, 제약 조건, 인덱스와 VIEW를 정의합니다. ### 행의 단위와 컬럼의 역할 테이블 하나에서 행의 단위를 일정하게 유지합니다. 설비별 하루 요약과 초 단위 원본을 같은 의미의 행처럼 섞으면 `COUNT`나 `AVG`를 해석하기 어렵습니다. 원본과 집계에는 각각 명확한 행의 단위와 조회 이름을 부여합니다. | 컬럼 역할 | 예 | 설계 원칙 | |---|---|---| | 대상 식별 | `sensor_id`, `equipment_id` | 표시 이름과 분리한 안정적인 값 사용 | | 발생 기준 | `measured_at`, `event_time` | 실제 발생 시각인지 수신 시각인지 명시 | | 측정값 | `temperature_c`, `pressure_kpa` | 단위, 유효 범위와 보정 방법 정의 | | 품질 | `quality_code` | 값 누락, 측정 실패와 정상 0 구분 | | 기준 속성 | 위치, 설비 종류 | 태그 메타데이터 또는 별도 기준표에서 관리할지 결정 | 외부 장비 코드처럼 업무에서 이미 사용하는 키는 자연키이고, 별도로 발급한 번호는 대리키입니다. 코드가 바뀔 수 있거나 여러 수집원이 같은 코드를 사용하면 식별 범위를 명확히 하거나 대리키를 검토합니다. 자동 증가 번호는 생성 순서를 위한 값이지 발생 시각이나 무중복 수집을 자동 보장하는 값이 아닙니다. TAG의 이름은 측정 행이 아닌 태그를 식별합니다. 테이블 이름은 영문자, 숫자와 밑줄을 사용하고 영문자로 시작하도록 정합니다. 예약어 또는 시스템 객체와 혼동할 수 있는 이름은 피하십시오. 삭제 전에는 의존하는 VIEW, 인덱스, ROLLUP과 보존 정책을 확인합니다. 타입별 생성 예제는 다음 문서를 참고하십시오. - [TAG 테이블 생성](/dbms/tag-table-usage/create-alter-drop/) - [LOG 테이블 생성](/dbms/log-table-usage/create-alter-drop/) - [TRANSACTION 테이블](/dbms/rdb-table-usage/) - [LOOKUP 테이블](/dbms/lookup-table-usage/) - [VOLATILE 테이블](/dbms/volatile-table-usage/) ## 테이블 변경 테이블 타입과 데이터 유무에 따라 컬럼 추가·삭제·이름 변경·타입 변경의 지원 범위가 다릅니다. 운영 테이블을 변경하기 전에 다음 순서로 판단하십시오. 1. 대상 Edition과 테이블 타입이 해당 `ALTER TABLE` 동작을 지원하는지 확인합니다. 2. 기존 데이터, 인덱스, VIEW와 애플리케이션의 컬럼 순서 의존성을 확인합니다. 3. 운영과 같은 스키마·데이터량의 검증 환경에서 실행 시간과 잠금 영향을 측정합니다. 4. 되돌리기 어렵다면 새 테이블을 만들고 검증 후 전환하는 방식을 사용합니다. 새 컬럼을 추가한 뒤에는 기존 행과 이후 입력한 행을 각각 조회해 NULL·DEFAULT 결과를 확인합니다. 모든 테이블에서 기존 행이 DEFAULT로 채워지는 것은 아닙니다. 예를 들어 VOLATILE의 기존 행과 TAG 메타데이터의 자동 등록 행에는 별도 규칙이 있습니다. [ARRAY의 DEFAULT 규칙](/dbms/reference/sql/types/array/#default와-기존-row)을 포함해 실제 타입의 DDL 계약을 확인하십시오. 애플리케이션 배포와 스키마 변경의 순서도 정합니다. 가능한 입력 경로에서는 컬럼 목록을 명시하고, 위치 순서에 의존하는 Append와 바인딩 코드는 새 스키마와 대조합니다. 새 테이블로 이전하면 행 수뿐 아니라 키별 건수, 시간 범위, NULL 비율과 대표 집계도 비교하고, 전환 중 새로 들어오는 행의 누락·중복을 어떻게 처리할지 정합니다. 정확한 지원 범위와 구문은 [ALTER TABLE 사전](/dbms/reference/sql/syntax/)과 [테이블 타입별 지원 범위](/dbms/reference/support-scope-constraints/table-types-type/)를 참고하십시오. ## 컬럼과 데이터 타입 선택 값의 실제 범위와 연산을 기준으로 가장 작은 적합 타입을 선택합니다. 표시 형식을 저장 타입과 혼동하지 마십시오. | 데이터 | 권장 검토 타입 | 주의점 | | --- | --- | --- | | 정수 계측값·코드 | `SHORT`·`INTEGER`·`LONG` 계열 | NULL 예약값과 범위 확인 | | 실수 계측값 | `FLOAT`·`DOUBLE` | 정밀도, 집계 오차와 NULL 예약값 확인 | | 정확한 소수 계산이 필요한 값 | `DECIMAL` | precision·scale과 반올림 규칙을 먼저 결정 | | 시각 | `DATETIME` | 표현 범위와 시간대를 클라이언트·세션 정책과 함께 설계 | | 짧은 문자열 | `VARCHAR` | 최대 길이와 인코딩 확인 | | 긴 본문 | `TEXT` | 지원 테이블 타입, 정렬·집계 제약과 인덱스 비용 확인 | | 네트워크 주소 | `IPV4`·`IPV6` | 문자열 대신 주소 타입 사용 검토 | | 구조화 문서 | `JSON` | 크기 제한, 키 승격 기준과 테이블 타입별 지원 확인 | | 이진 데이터 | `BINARY` | 테이블 타입에 따라 가변·고정 길이가 다름 | | 고정 개수의 숫자 묶음 | 숫자 `ARRAY` | 요소 타입·길이, 요소별 NULL과 전체 NULL 구분 | `VARCHAR(n)`의 길이는 바이트 수입니다. 한글이나 이모지를 포함하면 문자 수와 다를 수 있습니다. 정수 타입은 일부 경계값을 NULL 표현으로 예약하므로 일반 프로그래밍 언어의 정수 범위를 그대로 적용하지 않습니다. FLOAT과 DOUBLE도 양수 최대값을 NULL로 인식하므로 실수 타입이라고 예외는 아닙니다. “작은 타입”보다 유효 범위와 연산 의미가 우선입니다. ### 정확한 소수가 필요한 값: DECIMAL 금액, 세율, 정산 값처럼 10진 정확성이 필요한 값에는 `DECIMAL`을 사용합니다. 오차가 허용되고 지수 범위가 넓은 계측값에는 `FLOAT`·`DOUBLE`을 사용합니다. 두 계열의 차이는 저장 크기가 아니라 값의 의미이므로, 반올림 결과를 업무에서 그대로 사용하는 값이라면 DECIMAL을 선택합니다. 설계 시 다음을 먼저 정합니다. | 결정 항목 | 내용 | |---|---| | precision | 전체 유효 숫자 수, 1~65 | | scale | 소수 자릿수, 0~30이며 precision보다 클 수 없음 | | 생략 규칙 | `DECIMAL`은 `DECIMAL(10,0)`, `DECIMAL(M)`은 `DECIMAL(M,0)` | scale을 넘는 소수 자릿수는 입력 시점에 0에서 멀어지는 방향의 절반 올림으로 저장됩니다. 반올림이 저장 시점에 확정되므로 업무 규칙과 같은 방향인지 확인합니다. precision을 넘는 값은 잘라내거나 부동소수점으로 바꾸지 않고 오류로 처리하므로, 자릿수는 여유를 두고 정합니다. `SUM`, `AVG`, `MIN`, `MAX`와 `GROUP BY`, `ORDER BY`, `DISTINCT`는 exact 경로로 동작합니다. 반면 percentile이나 고급 통계 함수처럼 exact DECIMAL 경로가 없는 연산은 DOUBLE로 변환해 계산하므로 결과가 근삿값입니다. 정확성이 필요한 집계와 참고용 통계를 구분해 설계합니다. 애플리케이션 경로에서 값을 부동소수점으로 경유시키면 저장 타입과 무관하게 정확성이 사라집니다. JDBC는 `BigDecimal`, Python은 `decimal.Decimal`, ODBC는 `SQL_NUMERIC`처럼 각 언어의 decimal 표현이나 문자열로 전달합니다. 선언 규칙, 인덱스, 클라이언트 매핑은 [DECIMAL과 NUMERIC 고정소수점 타입](/dbms/reference/sql/types/decimal-numeric-fixed-point/)을 참고하십시오. ### 구조화 문서: JSON 수집원마다 키가 다르거나 항목이 계속 늘어나는 부가 속성에는 `JSON` 컬럼이 적합합니다. 스키마 변경 없이 항목을 추가할 수 있기 때문입니다. 반대로 자주 `WHERE`나 `GROUP BY`에 사용하는 값은 JSON 안에 두지 말고 별도 컬럼으로 승격합니다. JSON 컬럼은 primary key로 선언할 수 없고, LOOKUP 테이블에서는 JSON path 인덱스를 지원하지 않습니다. 크기 제한도 설계에 반영합니다. 문서 하나는 최대 32,768 바이트, JSON path는 최대 512 바이트입니다. 원본 payload 전체를 보관하는 용도가 아니라 조회에 필요한 속성을 담는 용도로 사용합니다. | 테이블 타입 | JSON 컬럼 | 확인할 점 | |---|:---:|---| | TAG·LOG·TRANSACTION | O | JSON 함수와 path 조회 지원 | | LOOKUP | O | 일반 컬럼으로 지원, JSON path 인덱스는 미지원 | | VOLATILE | X | JSON 컬럼을 만들 수 없음 | 조회는 `->` 연산자와 `JSON_EXTRACT_*` 계열을, 갱신은 `JSON_SET` 계열을 사용합니다. 갱신 함수는 해당 테이블 타입이 `UPDATE`를 지원할 때만 의미가 있으므로, LOG나 TAG처럼 행 수정이 없는 테이블에서는 입력 시점에 문서를 완성합니다. 함수별 지원 범위는 [JSON 타입의 테이블 타입별 지원 범위](/dbms/reference/sql/types/table-types-type-support-scope-json/)를 참고하십시오. ### 타입 선택 전에 확인할 제약 | 타입 | 설계 단계에서 확인할 내용 | |---|---| | `TEXT` | LOG와 Standard Edition의 TRANSACTION에서만 지원합니다. LOG의 TEXT 컬럼에는 `ORDER BY`와 `GROUP BY`를 사용할 수 없고, `MODIFY COLUMN`으로 VARCHAR로 바꿀 수도 없습니다. 정렬·집계에 쓸 값은 VARCHAR나 숫자 컬럼으로 따로 둡니다. | | `BINARY` | LOG는 가변 길이 최대 64MB이지만 TAG는 `BINARY(n)` 고정 길이 1~32,767 바이트로 성격이 다릅니다. LOOKUP과 VOLATILE에서는 지원하지 않습니다. | | `DATETIME` | 표현 범위는 1970-01-01부터 2262-04-11까지이며 나노초까지 저장합니다. 만료일이나 무기한을 뜻하는 먼 미래 시각을 임의로 넣지 않습니다. | | `ARRAY` | 고정 길이 1차원 숫자 배열이며 cardinality는 1~1024입니다. 전체 NULL과 요소 NULL을 구분해 설계합니다. | 전체 범위와 테이블 타입별 지원 여부는 [데이터 타입 사전](/dbms/reference/sql/types/)을 기준으로 확인하십시오. ## 제약 조건과 기본값 `PRIMARY KEY`, `NOT NULL`, `DEFAULT` 지원은 테이블 타입마다 다릅니다. 특히 TAG의 `PRIMARY KEY`는 태그 식별자, LOOKUP·VOLATILE·TRANSACTION의 `PRIMARY KEY`는 행 식별과 갱신 경로를 결정합니다. NULL은 알 수 없거나 없는 값이고 0이나 빈 구간과 다릅니다. 측정 실패를 DEFAULT 0으로 대체하면 평균과 정상값 판정이 왜곡될 수 있습니다. `COUNT(*)`는 행 수, `COUNT(value)`는 그 컬럼이 NULL이 아닌 행 수이므로 표본 수를 보고할 때 구분합니다. 기본값은 생략한 입력에 대한 정책이며 값의 유효성 검사를 대신하지 않습니다. 테이블이 제공하지 않는 제약은 수집·업무 애플리케이션에서 검증합니다. 예를 들어 외래 키처럼 다른 DBMS의 제약을 이름만 보고 지원한다고 가정하지 말고 [TRANSACTION 지원 범위](/dbms/reference/support-scope-constraints/rdb/)를 확인합니다. 시스템이 제공하는 `_ARRIVAL_TIME`이나 `_RID` 같은 컬럼을 애플리케이션의 업무 키로 사용하지 마십시오. 공개된 조회 의미가 필요한 경우에만 참조하고, 시스템 컬럼의 저장 구조나 생성 방식에는 의존하지 않습니다. ## 인덱스 설계 인덱스는 조회 비용을 낮추지만 입력과 저장 비용을 추가합니다. 대표 조회를 먼저 적고 조건에 맞는 행의 비율(선택도)을 확인합니다. 전체 설비의 한 달 평균과 설비 한 대의 특정 주문 조회는 접근 방식이 다릅니다. 필터·조인 컬럼마다 무조건 인덱스를 만들기보다 `EXPLAIN`과 실제 실행 시간을 비교해 도움이 되는 인덱스를 남깁니다. - 시간 범위와 태그 식별자로 충분한 TAG 조회에는 추가 인덱스를 먼저 만들지 않습니다. - LOG에서 자주 필터링하는 컬럼은 실제 실행 계획과 선택도를 측정한 뒤 인덱스를 검토합니다. - LOOKUP·VOLATILE·TRANSACTION은 키 조회와 조인 조건을 기준으로 설계합니다. - 긴 텍스트의 단어 검색은 해당 타입이 지원하는 `KEYWORD` 인덱스를 검토합니다. 생성·삭제 구문과 지원 타입은 [인덱스 SQL 사전](/dbms/reference/sql/syntax/)을 참고하십시오. ## VIEW 설계 VIEW는 반복해서 사용하는 조회에 이름을 부여하지만 결과 자체를 저장하지 않습니다. VIEW 정의에서 시간 범위가 고정되거나 불필요한 전체 컬럼을 읽지 않도록 하고, 기반 테이블이나 컬럼을 변경하기 전에 의존 VIEW를 확인하십시오. 자주 쓰는 컬럼 목록과 단위 변환을 VIEW로 일관되게 제공할 수 있습니다. 그러나 VIEW를 만든 것만으로 데이터가 복사되거나 조회 비용이 줄지는 않습니다. 과거 이벤트를 최신 기준표와 조인한 VIEW는 기준표가 바뀌면 과거 결과도 바뀔 수 있으므로, 발생 당시의 속성이 필요하면 버전별 기준 정보나 원본에 기록한 속성을 사용합니다. VIEW 생성·조회·삭제와 제한은 [VIEW SQL 사전](/dbms/reference/sql/syntax/)을 참고하십시오. --- title: "4.3 데이터 변경 정책" url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/alter-data-mutation-policy/ language: kr kind: page --- # 4.3 데이터 변경 정책 테이블 타입에 따라 `UPDATE`, `DELETE`, `TRUNCATE` 지원 범위가 다릅니다. 이 페이지는 데이터 모델을 선택할 때 필요한 정책만 요약합니다. 정확한 문법과 제약은 연결된 SQL 레퍼런스를 기준으로 확인하십시오. 변경 정책은 “이 값을 수정할 수 있는가”뿐 아니라 누가 어떤 범위를 바꾸고, 실패하면 어디까지 되돌릴 수 있는지를 정하는 것입니다. 현재 상태의 수정, 원본 계측값 보정, 스키마 변경과 보관 기간 만료에 따른 삭제를 별도 작업으로 구분합니다. ## 테이블 타입별 데이터 변경 지원 범위 | 테이블 타입 | UPDATE | DELETE | TRUNCATE | |------------|--------|--------|----------| | TAG | Standard의 DATA 보정: 태그 선택 조건과 BASETIME 조건 필요 | `BEFORE`, 태그/축 조건 또는 전체 삭제 | X | | LOG | X | `BEFORE`, `OLDEST`, `EXCEPT`, 전체 삭제 | O | | TRANSACTION | O | O | O | | VOLATILE | 기본 키 조건 | 기본 키 조건 또는 전체 삭제 | X | | LOOKUP | 일반 조건식, PK 변경 불가 | 일반 조건식 또는 전체 삭제 | X | LOG 테이블은 적재한 이벤트를 수정하지 않는 구조입니다. 수정이 잦은 상태·설정 정보는 VOLATILE, LOOKUP 또는 TRANSACTION 테이블에 저장하십시오. ## 변경 단위와 실패 처리 | 작업 | 모델에서 정할 사항 | |---|---| | 현재 설정값 갱신 | 키, 허용 값과 동시 변경자 처리 | | 잘못된 계측값 보정 | 태그·시간 범위, 보정 사유와 ROLLUP 재계산 | | LOG 이벤트 정정 | 원본을 남기고 보정 이벤트로 연결할지 결정 | | 기준 키 교체 | 참조하는 데이터의 전환 순서와 중간 실패 처리 | | 기간 삭제 | 보관 기준 시각, 삭제 범위와 필요한 백업 | TRANSACTION의 여러 DML은 명시적 트랜잭션으로 묶을 수 있습니다. LOOKUP·VOLATILE의 변경과 LOG·TAG 입력이 그 트랜잭션에 함께 참여한다고 가정하지 마십시오. 예를 들어 TAG 이력을 저장한 뒤 VOLATILE 캐시 갱신이 실패하면 이력은 남아 있을 수 있습니다. 캐시 재구성이나 재시도 절차가 필요합니다. Append의 트랜잭션 참여는 API와 대상 유형에 따라 다르므로 [SDK 지원 범위](/dbms/development-tools-integration/sdk-support-scope/)에서 확인합니다. 여러 행을 변경하기 전에는 같은 조건으로 건수와 대표 행을 조회합니다. 이 사전 조회가 행을 잠그거나 이후 변경 범위를 고정하는 것은 아니므로, 동시 입력·갱신이 있다면 작업 시간대와 대상 범위를 함께 통제합니다. 실행 결과의 영향 행 수와 변경 후 값도 확인합니다. ## UPDATE 정책 ### TRANSACTION, VOLATILE, LOOKUP - TRANSACTION은 일반 관계형 `UPDATE`와 트랜잭션을 지원합니다. - VOLATILE은 기본 키 일치 조건으로 대상을 지정합니다. - LOOKUP은 일반 조건식을 사용할 수 있지만 기본 키 컬럼 자체는 바꿀 수 없습니다. LOOKUP UPDATE에는 WHERE 조건이 필요합니다. LOOKUP·VOLATILE의 기본 키를 바꾸려면 삭제와 새 키 입력을 별도 문장으로 처리해야 하므로, 원본 보관과 참조 전환 순서를 먼저 정합니다. 이 두 문장을 묶어 롤백할 필요가 있다면 TRANSACTION 모델을 검토합니다. LOOKUP의 지원 predicate와 표현식은 [LOOKUP predicate UPDATE](/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-update-syntax/)를 참고하십시오. ### TAG data UPDATE TAG의 실제 시계열 데이터와 메타데이터는 서로 다른 구문으로 수정합니다. | 대상 | 구문 | 핵심 제약 | |------|------|-----------| | 시계열 데이터 | `UPDATE tag_table SET ... WHERE ...` | 태그 선택 조건과 BASETIME 조건이 모두 필요 | | 메타데이터 | `UPDATE tag_table METADATA SET ...` | TAG 메타데이터 전용 구문 사용 | TAG data UPDATE는 Standard Edition의 논리 TAG 테이블에서 지원합니다. 태그 이름, BASETIME, 메타데이터 컬럼은 `SET` 대상이 될 수 없습니다. 수정한 구간에 구체화된 롤업이 있다면 `ROLLUP_REBUILD`로 다시 생성하십시오. TAG DATA의 SET 우변은 기존 행 컬럼을 참조할 수 없습니다. 따라서 `SET value = value + 1`처럼 일괄 가산하는 방식으로 보정하지 않습니다. 계산한 보정값을 상수나 매개변수로 전달하고, 필요한 범위를 한정합니다. 여러 번 보정한 이력을 남겨야 하면 현재 값만 덮어쓰지 말고 변경 전후 값과 사유를 별도 이력으로 저장합니다. 문법과 허용 표현식은 다음 레퍼런스가 정본입니다. - [TAG data UPDATE](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/) - [TAG data UPDATE WHERE/SET 제약](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-where-set-constraints/) - [TAG 메타데이터](/dbms/tag-table-usage/tag-metadata/) LOG 테이블은 `UPDATE`를 지원하지 않습니다. ## DELETE 정책 ### TRANSACTION, VOLATILE, LOOKUP - TRANSACTION은 일반 `WHERE` 조건으로 삭제할 수 있습니다. - VOLATILE은 기본 키 조건으로 삭제하거나 조건 없이 전체 행을 삭제할 수 있습니다. - LOOKUP은 일반 조건식으로 삭제하거나 `WHERE` 없이 전체 행을 삭제할 수 있습니다. LOOKUP의 지원 predicate는 [LOOKUP predicate DELETE](/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-delete-syntax/)를 참고하십시오. ### LOG LOG는 임의의 일반 `WHERE` 조건 대신 로그 보존형 삭제 구문을 사용합니다. `OLDEST`, `EXCEPT`, `BEFORE` 또는 전체 삭제 중 목적에 맞는 형식을 선택합니다. 정확한 구문과 실행 예제는 [LOG 데이터 생명주기](/dbms/log-table-usage/operations-lifecycle/)를 참고하십시오. ### TAG/KV TAG/KV는 `BEFORE`로 오래된 데이터를 정리하거나, 태그 이름과 축 조건을 사용해 대상을 지정합니다. `BEFORE` 시각은 현재보다 과거여야 합니다. 실행 구문은 [TAG 데이터 변경](/dbms/tag-table-usage/data-input-mutation/)을 참고하십시오. 수동 보존 삭제를 반복해야 한다면 [Retention Policy](/dbms/operations-configuration-recovery/policy-data-retention/)를 사용하십시오. ### TAG 메타데이터 TAG 메타데이터는 `DELETE FROM table_name METADATA` 형식으로 삭제합니다. 대상 중 실제 데이터가 있는 태그가 하나라도 있으면 문장 전체가 실패합니다. 상세 조건은 [TAG 메타데이터](/dbms/tag-table-usage/tag-metadata/)를 참고하십시오. ## TRUNCATE 정책 `TRUNCATE TABLE`은 LOG와 TRANSACTION에서만 지원합니다. 스키마와 인덱스 정의는 유지하고 모든 행을 제거합니다. | 항목 | TRUNCATE | DELETE | |------|----------|--------| | 대상 | 테이블 전체 | 타입에 따라 전체 또는 조건 지정 | | `WHERE` | 불가 | 지원 범위 안에서 가능 | | TRANSACTION 롤백 | 명시적 트랜잭션에서 가능 | 명시적 트랜잭션에서 가능 | 삭제 전 백업과 재입력 경로를 확인하십시오. TAG는 지원되는 `BEFORE` 또는 태그/축 조건을, VOLATILE과 LOOKUP의 전체 삭제는 조건 없는 `DELETE`를 사용합니다. TRANSACTION의 롤백 가능 여부를 LOG의 삭제에도 적용하지 마십시오. 이미 확정한 변경은 이후 ROLLBACK으로 되돌릴 수 없습니다. 또한 삭제와 물리 디스크 공간 반환은 같은 시점의 작업이라고 가정하지 말고 저장 사용량과 해당 테이블의 정리 상태를 확인합니다. --- title: "4.4 안티패턴" url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/table-types-patterns-type-anti/ language: kr kind: page --- # 4.4 안티패턴 안티패턴은 특정 테이블을 사용했다는 사실보다 데이터의 의미와 요구사항에 맞지 않는 방식으로 사용한 경우를 뜻합니다. 아래 예제의 전제가 자신의 업무에도 적용되는지 확인한 뒤 대안을 선택하십시오. 같은 스키마라도 현재 상태 관리에는 맞고 이력 누적에는 맞지 않을 수 있습니다. - **[LOOKUP에 무제한 이력 누적](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#high-frequency-lookup)** - **[센서별 테이블 생성](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#per-sensor-create)** - **[잘못된 타입 선택](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#table-types-selection-type-wrong)** - **[VOLATILE 영속 저장 오용](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#storage-persistent-volatile)** - **[시계열 데이터 TRANSACTION 오용](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#time-series-storage-misuse-rdb)** ## LOOKUP에 무제한 이력 누적 ### 문제 문제는 조회 빈도 자체가 아니라, 계속 늘어나는 계측 이력을 LOOKUP에 저장하는 설계입니다. LOOKUP은 전체 행과 인덱스를 메모리에 유지하므로 장기 시계열 이력이 쌓일수록 메모리 부담이 커집니다. 작은 기준 정보를 키로 반복 조회하는 용도에는 적합합니다. ### 안티패턴 예시 ```sql -- 잘못된 설계: 센서 계측값을 LOOKUP 테이블에 저장 CREATE LOOKUP TABLE sensor_data_wrong ( sensor_id VARCHAR(64) PRIMARY KEY, value DOUBLE, ts DATETIME ); -- 이 스키마는 sensor_id가 PK이므로 센서마다 현재 한 행만 저장 -- 측정 이력을 그대로 추가하면 같은 PK와 충돌 INSERT INTO sensor_data_wrong VALUES ('TEMP-01', 25.3, NOW); INSERT INTO sensor_data_wrong VALUES ('TEMP-01', 25.5, NOW); -- PK 중복 오류 ``` ### 올바른 패턴 태그별 계측 이력 수집과 조회가 중심이면 TAG를 검토합니다. LOOKUP에서도 측정마다 다른 키를 부여할 수 있지만 전체 이력이 메모리에 상주하는 비용은 남습니다. 최신값 캐시는 별도의 성능상 필요가 있고 원본에서 복구할 수 있을 때 VOLATILE로 추가합니다. TAG의 최신값 조회로 요구사항을 만족한다면 캐시를 별도로 유지할 필요가 없습니다. ```sql -- 올바른 설계: 이력은 TAG 테이블 CREATE TAG TABLE sensor_data ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); -- 최신값 캐시는 VOLATILE 테이블 CREATE VOLATILE TABLE sensor_latest ( sensor_id VARCHAR(64) PRIMARY KEY, value DOUBLE, updated_at DATETIME ); ``` ### 결과 | | 안티패턴 (LOOKUP) | 올바른 설계 (TAG) | |-|-----------------|-----------------| | 같은 센서 키로 이력 누적 | X (예제의 센서 키가 행을 식별) | O (태그 이름 아래 여러 계측 행 저장) | | 지속 입력 경로 | 행 식별자 중심 | 시계열 Append API 사용 가능 | | 시간 범위 조회 | 일반 조건 조회 | 태그·시간 축 조회 | ## 센서별 테이블 생성 ### 문제 센서(태그)마다 별도 테이블을 생성하는 패턴입니다. 센서 수가 늘어날수록 DDL, 권한과 조회 대상도 함께 늘어나 운영 비용이 커집니다. ### 안티패턴 예시 ```sql -- 잘못된 설계: 센서마다 테이블 생성 CREATE TAG TABLE sensor_temp_01 ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); CREATE TAG TABLE sensor_temp_02 ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); CREATE TAG TABLE sensor_temp_03 ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); -- ... 센서가 10,000개면 테이블도 10,000개 ``` ### 문제점 | 문제 | 설명 | |------|------| | 관리 복잡도 | 테이블 수만큼 DDL 관리 필요 | | 쿼리 불편 | 여러 테이블을 결합해야 센서 간 집계 가능 | | 메타데이터 증가 | 시스템 카탈로그 부하 | | 신규 센서 추가 | 매번 DDL 실행 필요 | ### 올바른 패턴 센서 이름을 PRIMARY KEY로 하는 하나의 TAG 테이블에 모든 센서 데이터를 저장합니다. 이 원칙은 같은 컬럼 구조·권한·보관 정책을 공유하는 센서 집합에 적용합니다. 측정 단위, 스키마, 접근 권한이나 보관 기간을 독립적으로 관리해야 한다면 테이블을 나누는 것이 적절할 수 있습니다. 센서 수 자체를 테이블 분리 기준으로 삼지 않는 것이 핵심입니다. ```sql -- 올바른 설계: 모든 온도 센서를 하나의 테이블로 CREATE TAG TABLE temperature_sensor ( name VARCHAR(128) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); -- 모든 센서 데이터를 하나의 테이블에 삽입 INSERT INTO temperature_sensor VALUES ('TEMP-01', NOW, 23.5); INSERT INTO temperature_sensor VALUES ('TEMP-02', NOW, 24.1); INSERT INTO temperature_sensor VALUES ('TEMP-10000', NOW, 22.9); ``` ### 장점 - 신규 센서 추가 시 DDL 불필요 (새 태그 이름으로 INSERT만 하면 됨) - 크로스-센서 집계 용이 - 운영 관리 포인트 최소화 ## 잘못된 타입 선택 데이터 특성에 맞지 않는 테이블 타입을 선택하는 대표적인 안티패턴입니다. ### 안티패턴 1: 이벤트 로그를 TAG 테이블에 저장 ```sql -- 잘못됨: 이벤트 로그를 TAG로 저장 CREATE TAG TABLE error_log_wrong ( name VARCHAR(256) PRIMARY KEY, -- 이벤트 내용이 태그 이름이 됨 time DATETIME BASETIME, level SHORT ); -- 문제: 이벤트마다 고유 이름 → 태그 수 폭발 ``` **올바른 설계**: LOG 테이블 사용 ```sql CREATE LOG TABLE error_log ( level SHORT, msg VARCHAR(512), src VARCHAR(128) ); ``` ### 안티패턴 2: 센서 값을 LOG 테이블에 저장 LOG에 센서 값이 있다는 사실이 오류는 아닙니다. 문제는 아래처럼 실제 측정 시각을 생략하고, 요구사항은 태그별 측정 시간 집계인데 수신 시각만 남기는 경우입니다. 여러 필드로 된 장비 이벤트를 검색하는 것이 주목적이면 LOG가 적합할 수 있습니다. ```sql -- 측정 시각이 필요한 요구사항에 부족한 스키마 CREATE LOG TABLE sensor_wrong ( sensor_id VARCHAR(64), value DOUBLE -- 실제 측정 시각이 없고 서버 수신 시각만 자동 저장됨 ); ``` **올바른 설계**: TAG 테이블 사용 ```sql CREATE TAG TABLE sensor_measurements ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); ``` ### 안티패턴 3: 대용량 이력을 LOOKUP에 저장 ```sql -- 잘못됨: 관계형 트랜잭션이 필요한 주문 이력을 LOOKUP에 저장 CREATE LOOKUP TABLE order_history_wrong ( order_id LONG PRIMARY KEY, customer VARCHAR(64) -- 전체 행과 인덱스의 메모리 비용, 명시적 트랜잭션 요구를 확인 ); ``` **올바른 설계**: TRANSACTION 테이블 사용 ```sql CREATE TRANSACTION TABLE order_history ( order_id LONG, customer VARCHAR(64), item_id INTEGER, amount DOUBLE, status VARCHAR(16) ); -- UPDATE/DELETE/SELECT 모두 지원 UPDATE order_history SET status = 'SHIPPED' WHERE order_id = 1001; ``` ### 안티패턴 4: 시계열 데이터를 TRANSACTION 테이블에 저장 TRANSACTION에도 시간 컬럼과 Append API를 사용할 수 있지만 TAG 전용 시간축 저장 구조와 ROLLUP은 제공되지 않습니다. 관계형 변경보다 계측 이력 수집과 집계가 중심이면 TAG를 검토합니다. 자세한 내용은 [시계열 데이터 TRANSACTION 오용](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#time-series-storage-misuse-rdb)을 참고하십시오. ## VOLATILE 영속 저장 오용 ### 문제 VOLATILE 테이블에 영구 보존이 필요한 데이터를 저장하는 패턴입니다. ### 안티패턴 예시 ```sql -- 잘못됨: 중요 설정을 VOLATILE에 저장 CREATE VOLATILE TABLE critical_config ( key_name VARCHAR(64) PRIMARY KEY, value VARCHAR(256) ); INSERT INTO critical_config VALUES ('license_key', 'XXXX-XXXX-XXXX'); INSERT INTO critical_config VALUES ('max_connections', '1000'); -- 서버 재시작 시 모든 설정 소멸! ``` ### 문제점 - 서버가 종료되거나 재시작되면 테이블과 데이터가 소멸합니다. - 서버 프로세스가 종료되는 장애에서도 메모리의 데이터를 복구할 수 없으므로, 유일한 원본을 VOLATILE에 저장하면 데이터 손실로 이어집니다. ### 올바른 패턴 영구 보존이 필요한 데이터는 LOOKUP 또는 TRANSACTION 테이블에 저장합니다. ```sql -- 올바름: 설정은 LOOKUP 테이블 CREATE LOOKUP TABLE app_config ( key_name VARCHAR(64) PRIMARY KEY, value VARCHAR(256) ); INSERT INTO app_config VALUES ('max_connections', '1000'); -- 서버 재시작 후에도 데이터 유지 ``` ### VOLATILE의 올바른 용도 VOLATILE에는 재시작 후 재생성하거나 폐기할 수 있는 데이터를 저장합니다. 조회용 캐시뿐 아니라 수명이 명확한 작업 상태도 포함할 수 있습니다. 업무상 반드시 보존해야 하는 결과를 메모리에만 두지 않습니다. | 적합 | 부적합 | |------|--------| | 센서 최신값 캐시 | 원본 트랜잭션 데이터 | | 실시간 집계 결과 | 중요 설정 값 | | 세션 임시 상태 | 감사 로그 | | 대시보드 캐시 | 사용자 정보 | ## 시계열 데이터 TRANSACTION 오용 ### 문제 센서·IoT 계측값 같은 지속적인 시계열 데이터를 TRANSACTION 테이블에 저장하는 패턴입니다. 관계형 갱신이 필요하지 않다면 TAG 테이블의 태그·시간 축과 ROLLUP을 활용할 수 없으므로 해당 기능이 필요한 조회와 운영 요구에 맞지 않을 수 있습니다. 반대로 측정값 등록을 다른 업무 변경과 하나의 트랜잭션으로 묶어야 하면 TRANSACTION을 선택할 이유가 있습니다. ### 안티패턴 예시 ```sql -- 잘못됨: 센서 시계열 데이터를 TRANSACTION 테이블에 저장 CREATE TRANSACTION TABLE sensor_timeseries ( sensor_id VARCHAR(64), ts DATETIME, value DOUBLE, unit VARCHAR(16) ); ``` ### 문제점 | 문제 | 설명 | |------|------| | 입력 의미 불일치 | 관계형 트랜잭션이 필요하지 않은 값에도 관계형 쓰기 경로 사용 | | 시간 축 부재 | TAG의 BASETIME 기반 조회 구조를 사용할 수 없음 | | 집계 기능 차이 | TAG 전용 ROLLUP을 사용할 수 없음 | ### 올바른 패턴 센서 계측값은 TAG 테이블에 저장합니다. ```sql -- 올바름: TAG 테이블 사용 CREATE TAG TABLE sensor_history ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, unit VARCHAR(16) ); -- Append API 고속 버퍼로 대량 입력 가능 -- 시간 단위 집계와 TAG 전용 최적화 활용 가능 SELECT name, DATE_TRUNC('hour', time, 1) AS hour, AVG(value), MAX(value) FROM sensor_history WHERE time >= NOW - 86400000000000 GROUP BY name, hour; ``` ### TRANSACTION 테이블이 적합한 경우 TRANSACTION 테이블은 관계형 구조의 업무 데이터(주문, 재고, 설비 이력 등)에 씁니다. 시간 컬럼이 있더라도 UPDATE/DELETE가 필요한 업무 이력이라면 TRANSACTION 테이블을, 수정 없이 계속 쌓이는 고빈도 계측값이라면 TAG를 선택합니다. ## 의미가 다른 값을 같은 통계로 계산 스키마가 같아도 단위나 행의 의미가 다르면 단순 집계할 수 없습니다. 누적 전력량(kWh)의 평균은 소비 전력(kW)이 아니며, 여러 구간 평균의 단순 평균은 전체 표본 평균과 다를 수 있습니다. NULL을 0으로 채우면 측정 실패가 정상적인 0으로 바뀝니다. 타입을 정할 때 단위, 표본 수와 품질 규칙도 함께 기록합니다. 구간별 통계를 재집계할 때는 합계와 유효 건수 같은 필요한 통계를 유지하고, 원본을 지우기 전에 향후 분석에 필요한 해상도를 확인합니다. --- title: "4.5 모델링 패턴" url: https://docs.machbase.com/kr/dbms/data-modeling-table-design/patterns-modeling/ language: kr kind: page --- # 4.5 모델링 패턴 실제 운영 환경에서 자주 쓰이는 데이터 모델링 패턴을 다룹니다. 각 절의 SQL은 서로 다른 모델을 보여 주는 예제입니다. 필요한 절을 선택해 별도 실습 환경에서 실행하며, 같은 이름의 테이블이 있는지 먼저 확인합니다. 스키마 생성만으로 수집·집계·캐시 갱신 작업이 자동 실행되지는 않습니다. 입력 애플리케이션이나 작업 스케줄러가 수행할 일과 실패 처리도 함께 설계합니다. - **[시간축 모델링](/dbms/data-modeling-table-design/patterns-modeling/#time-axis-modeling)** - **[거리축 모델링](/dbms/data-modeling-table-design/patterns-modeling/#distance-axis-modeling)** - **[상태·캐시 모델링](/dbms/data-modeling-table-design/patterns-modeling/#state-cache-status-modeling)** - **[이벤트·로그 모델링](/dbms/data-modeling-table-design/patterns-modeling/#event-log-modeling-logs)** - **[참조·마스터 모델링](/dbms/data-modeling-table-design/patterns-modeling/#reference-master-modeling)** - **[영속·임시 혼합 패턴](/dbms/data-modeling-table-design/patterns-modeling/#persistent-temporary)** - **[INSERT·UPDATE 패턴](/dbms/data-modeling-table-design/patterns-modeling/#insert-update)** - **[JOIN·메타데이터 설계](/dbms/data-modeling-table-design/patterns-modeling/#join-metadata-design)** - **[복합 타입 조합 패턴](/dbms/data-modeling-table-design/patterns-modeling/#table-types-patterns-combined-type)** ## 시간축 모델링 시간을 기준 축으로 삼는 패턴으로, 센서 계측값·에너지 모니터링·환경 데이터 등에 적합합니다. 한 행은 계량기 한 대의 한 번의 관측입니다. `time`은 수신 시각이 아닌 측정 시각으로 정하고, `kwh`는 누적 계량값인지 구간 사용량인지 수집 계약에 기록합니다. 다음 스키마는 전압과 전류도 같은 관측에 포함된다는 전제입니다. 측정 주기나 시각이 서로 다르면 같은 행에 억지로 맞추기보다 별도 시계열로 저장하거나 결측 처리 규칙을 정합니다. ### 기본 패턴: TAG 테이블 ```sql CREATE TAG TABLE power_meter ( meter_id VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, kwh DOUBLE, voltage DOUBLE, current DOUBLE ) METADATA ( location VARCHAR(64), phase SHORT, rating_kw DOUBLE ); ``` ### 시간 범위 집계 ```sql -- 1시간 단위 계량값 평균과 최댓값 (최근 24시간) SELECT meter_id, DATE_TRUNC('hour', time, 1) AS hour, AVG(kwh) AS avg_kwh, MAX(kwh) AS peak_kwh FROM power_meter WHERE time >= NOW - 86400000000000 GROUP BY meter_id, hour ORDER BY meter_id, hour; ``` `kwh`가 누적 전력량이면 위 평균은 계량기 지시값의 평균이며 시간당 소비량이 아닙니다. 구간 소비량은 시작·종료 계량값의 차이와 계량기 초기화·교체·최댓값 초과 처리 규칙을 함께 적용해 계산합니다. 전력(kW)과 전력량(kWh)도 구분해 컬럼 이름과 단위를 정합니다. ### 다중 해상도 저장 패턴 원시 데이터(고해상도)와 집계 데이터(저해상도)를 별도 테이블에 나누어 저장합니다. ```sql -- 원시 데이터 (초 단위) CREATE TAG TABLE power_raw ( meter_id VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, kwh DOUBLE ); -- 1분 집계 (VOLATILE 또는 별도 TAG로 캐싱) CREATE VOLATILE TABLE power_1min ( key_id VARCHAR(80) PRIMARY KEY, meter_id VARCHAR(32), ts DATETIME, avg_kwh DOUBLE, max_kwh DOUBLE ); ``` 위 DDL은 저장 공간만 만듭니다. `power_1min`의 `key_id`에는 계량기와 구간을 유일하게 식별할 값을 애플리케이션이 지정하고 집계값을 채워야 합니다. VOLATILE의 결과는 재시작하면 사라지므로 장기 집계 보관소로 사용하지 않습니다. 원본 삭제 후에도 필요한 통계는 영속 TAG 테이블이나 지원되는 ROLLUP으로 보관하고 각각의 보존 정책을 확인합니다. ### 시간대 처리 DATETIME은 시점을 나타내며 문자열 입력과 출력은 접속 환경의 시간대 영향을 받습니다. 한국 시각으로 표시하려면 클라이언트의 시간대 설정을 맞춥니다. 저장된 시각에 9시간을 더하면 같은 시점을 다른 시간대로 표시하는 것이 아니라 값 자체를 9시간 뒤로 바꾸므로 표시용 변환과 구분해야 합니다. ```sql -- 클라이언트 시간대를 확인한 뒤 원래 시각을 조회 SELECT meter_id, time, kwh FROM power_meter WHERE meter_id = 'MTR-001' AND time >= '2024-01-01 00:00:00'; ``` 입력 시각의 시간대와 접속 시간대가 다르면 입력 전에 기준을 통일합니다. JDBC의 `TIMEZONE` 설정 예는 [JDBC 연결](/dbms/development-tools-integration/jdbc/)을 참고하십시오. ## 거리축 모델링 거리(위치)를 기준 축으로 삼는 패턴으로, 파이프라인 검사·도로 센서·레이저 스캔 등에 적합합니다. 거리축은 시간축의 다른 표시 형식이 아닙니다. 시간축 전용 ROLLUP과 Retention을 그대로 적용할 수 없습니다. 같은 파이프를 반복 검사한다면 `pipe_id`만으로 서로 다른 검사 회차를 섞지 않도록 회차 식별자나 테이블 분리 기준을 정합니다. 아래 예제는 한 파이프의 한 회차를 가정하며 거리 단위는 m, 두께 단위는 mm입니다. ### 기본 패턴 ```sql CREATE TAG TABLE pipeline_thickness ( pipe_id VARCHAR(32) PRIMARY KEY, distance DOUBLE BASEDISTANCE, -- 단위: 미터 thickness DOUBLE, temp DOUBLE ); ``` ### 구간 데이터 조회 ```sql -- 파이프 ID별 0~50m 구간 데이터 조회 SELECT pipe_id, distance, thickness FROM pipeline_thickness WHERE pipe_id = 'PIPE-A' AND distance BETWEEN 0.0 AND 50.0 ORDER BY distance; -- 임계치 이하 구간 조회 SELECT pipe_id, distance, thickness FROM pipeline_thickness WHERE pipe_id = 'PIPE-A' AND thickness < 8.0 -- 두께가 8mm 미만인 구간 ORDER BY distance; ``` ### 거리 기반 집계 ```sql -- 10m 구간별 평균 두께 SELECT pipe_id, FLOOR(distance / 10.0) * 10 AS segment_start, AVG(thickness) AS avg_thickness, MIN(thickness) AS min_thickness FROM pipeline_thickness WHERE pipe_id = 'PIPE-A' GROUP BY pipe_id, FLOOR(distance / 10.0) * 10 ORDER BY segment_start; ``` ### 시간 + 거리 복합 모델링 검사 시각과 위치를 함께 관리해야 하면 시간축 TAG 테이블에 거리 컬럼을 추가합니다. ```sql -- 시간축 + 위치 정보 함께 저장 CREATE TAG TABLE inspection_data ( inspector VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, distance DOUBLE, -- 위치 컬럼 (축은 아님) thickness DOUBLE, defect SHORT ); -- 특정 날짜·구간 조회 SELECT inspector, time, distance, thickness FROM inspection_data WHERE inspector = 'INSPECTOR-01' AND time BETWEEN '2024-01-01' AND '2024-01-02' AND distance BETWEEN 100.0 AND 200.0 ORDER BY time; ``` ## 상태·캐시 모델링 디바이스나 센서의 현재 상태를 실시간 조회하기 위한 캐시 모델링 패턴입니다. ### 최신 상태 캐시 패턴 VOLATILE 테이블로 각 디바이스의 현재 상태를 캐싱합니다. ```sql -- 상태 캐시 (VOLATILE) CREATE VOLATILE TABLE device_status ( device_id VARCHAR(64) PRIMARY KEY, status VARCHAR(16), value DOUBLE, updated_at DATETIME ); -- 상태 이력 (TAG 또는 LOG) CREATE TAG TABLE device_status_history ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, status VARCHAR(16) ); ``` ### 상태 업데이트 흐름 다음 SQL은 이력과 캐시를 각각 갱신하는 동작을 보여 줍니다. 두 입력은 하나의 트랜잭션이 아니며, 각각 평가하는 `NOW`도 같은 시각이라고 보장하지 않습니다. 실제 수집에서는 한 번 정한 측정 시각을 두 경로에 전달합니다. 늦게 도착한 과거 값이 최신 캐시를 덮어쓰지 않도록 이벤트 순서 판정과 동시 갱신 처리를 애플리케이션에서 정합니다. ```sql -- 새 계측값 수신 시: -- 1. TAG 테이블에 이력 저장 INSERT INTO device_status_history VALUES ('DEV-01', NOW, 78.5, 'WARNING'); -- 2. VOLATILE 캐시 업데이트 (ON DUPLICATE KEY UPDATE) INSERT INTO device_status VALUES ('DEV-01', 'WARNING', 78.5, NOW) ON DUPLICATE KEY UPDATE SET status = 'WARNING', value = 78.5, updated_at = NOW; ``` ### 대시보드 조회 패턴 ```sql -- 현재 ALARM 상태인 모든 디바이스 SELECT device_id, status, value, updated_at FROM device_status WHERE status IN ('ALARM', 'WARNING') ORDER BY updated_at DESC; -- 특정 디바이스의 최신 상태 SELECT device_id, status, value, updated_at FROM device_status WHERE device_id = 'DEV-01'; ``` ### 상태 정의 참조 패턴 상태 코드의 의미는 LOOKUP 테이블에서 관리합니다. ```sql CREATE LOOKUP TABLE status_definition ( code VARCHAR(16) PRIMARY KEY, label VARCHAR(64), color VARCHAR(16), severity SHORT ); INSERT INTO status_definition VALUES ('NORMAL', '정상', 'green', 0); INSERT INTO status_definition VALUES ('WARNING', '경고', 'yellow', 1); INSERT INTO status_definition VALUES ('ALARM', '알람', 'red', 2); -- JOIN 조회 SELECT d.device_id, s.label, s.color, d.value FROM device_status d JOIN status_definition s ON d.status = s.code ORDER BY s.severity DESC; ``` ## 이벤트·로그 모델링 시스템 이벤트, 알람, 감사 로그를 LOG 테이블로 모델링하는 패턴입니다. ### 계층적 이벤트 모델 ```sql -- 알람 이벤트 LOG 테이블 CREATE LOG TABLE alarm_event ( severity SHORT, -- 1=INFO, 2=WARN, 3=ERROR, 4=CRITICAL category VARCHAR(32), -- 카테고리 source VARCHAR(64), -- 발생 소스 message VARCHAR(512), src_ip IPV4 -- 발생 IP (있는 경우) ); -- 시스템 감사 LOG 테이블 CREATE LOG TABLE audit_log ( user_id VARCHAR(64), action VARCHAR(32), -- INSERT, UPDATE, DELETE, LOGIN 등 target VARCHAR(128), -- 대상 테이블/리소스 detail TEXT, -- 상세 내용 (전문 검색 대상) result VARCHAR(8) -- SUCCESS, FAILURE ); CREATE INDEX idx_audit_detail ON audit_log(detail) INDEX_TYPE KEYWORD; ``` ### 알람 집계 패턴 ```sql -- 최근 1시간 심각도별 알람 수 SELECT severity, COUNT(*) AS cnt FROM alarm_event WHERE _arrival_time >= NOW - 3600000000000 GROUP BY severity ORDER BY severity DESC; -- 소스별 알람 현황 (최근 24시간) SELECT source, COUNT(*) AS total, SUM(CASE WHEN severity = 4 THEN 1 ELSE 0 END) AS critical_cnt FROM alarm_event WHERE _arrival_time >= NOW - 86400000000000 GROUP BY source ORDER BY total DESC; ``` ### 로그 레벨 필터 패턴 ```sql -- ERROR 이상 로그 조회 (최근 10분) SELECT _arrival_time, source, message FROM alarm_event WHERE severity >= 3 AND _arrival_time >= NOW - 600000000000 ORDER BY _arrival_time DESC LIMIT 100; ``` ### 전문 검색 패턴 ```sql -- 특정 키워드를 포함하는 감사 로그 조회 SELECT _arrival_time, user_id, action, target FROM audit_log WHERE detail SEARCH 'password' AND _arrival_time >= NOW - 86400000000000; ``` ## 참조·마스터 모델링 코드 테이블, 설비 마스터, 사용자 정보 등 참조 데이터를 LOOKUP 테이블로 모델링하는 패턴입니다. 식별자만 이력에 저장하면 이름과 위치를 반복 저장하는 비용을 줄일 수 있습니다. 하지만 현재 마스터의 위치를 바꾸면 과거 이력을 조인한 결과도 새 위치로 표시됩니다. “발생 당시 어느 라인에 있었는가”가 중요하면 유효 기간이 있는 별도 변경 이력을 설계하거나 원본 행에 당시 속성을 기록합니다. 아래 계층 스키마의 참조 관계가 외래 키로 자동 검증되는 것은 아니므로 존재하지 않는 공장·라인 코드를 입력하지 않도록 애플리케이션에서 확인합니다. ### 계층적 코드 체계 ```sql -- 대분류 코드 CREATE LOOKUP TABLE category_main ( code VARCHAR(8) PRIMARY KEY, label VARCHAR(64) ); -- 중분류 코드 (대분류 참조) CREATE LOOKUP TABLE category_sub ( code VARCHAR(16) PRIMARY KEY, main_code VARCHAR(8), label VARCHAR(64) ); CREATE INDEX idx_sub_main ON category_sub(main_code); ``` ### 설비 계층 마스터 ```sql -- 공장 마스터 CREATE LOOKUP TABLE factory ( factory_id VARCHAR(16) PRIMARY KEY, name VARCHAR(64), location VARCHAR(128) ); -- 라인 마스터 (공장 참조) CREATE LOOKUP TABLE production_line ( line_id VARCHAR(16) PRIMARY KEY, factory_id VARCHAR(16), name VARCHAR(64) ); -- 설비 마스터 (라인 참조) CREATE LOOKUP TABLE equipment ( equip_id VARCHAR(32) PRIMARY KEY, line_id VARCHAR(16), equip_name VARCHAR(128), equip_type VARCHAR(32), install_dt DATETIME ); CREATE INDEX idx_equip_line ON equipment(line_id); CREATE INDEX idx_equip_type ON equipment(equip_type); ``` ### 마스터 조인 조회 패턴 계측 테이블에는 설비 식별자를 저장하고, 공장·라인·설비 이름 같은 속성은 LOOKUP 테이블에 한 번만 저장합니다. 조회할 때 계측 테이블의 시간 범위를 먼저 제한한 뒤 설비 식별자로 LOOKUP 테이블을 조인합니다. 실제 조인 구문과 실행 계획 확인 방법은 [JOIN·서브쿼리](/dbms/tag-table-usage/query-analysis/)을 참고하십시오. ## 영속·임시 혼합 패턴 영속 테이블(TAG, LOG, TRANSACTION, LOOKUP)에 원본을 보관하고 VOLATILE에 조회용 캐시를 유지하는 패턴입니다. 두 저장 경로가 하나의 트랜잭션으로 처리된다고 가정하지 말고, 캐시 지연과 실패 후 재구성 절차를 함께 설계합니다. ### 원본 + 집계 캐시 패턴 원본 데이터는 영속 테이블에, 집계 결과는 VOLATILE 테이블에 저장합니다. ```sql -- 원본 데이터 (TAG, 영속) CREATE TAG TABLE sensor_data ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); -- 집계 캐시 (VOLATILE, 임시) CREATE VOLATILE TABLE sensor_recent_avg ( key_id VARCHAR(64) PRIMARY KEY, sensor_id VARCHAR(64), base_ts DATETIME, avg_val DOUBLE, max_val DOUBLE, cnt LONG ); ``` ### 캐시 갱신 패턴 캐시 한 행은 센서 하나의 최근 2시간 통계입니다. `base_ts`는 집계 창의 경계가 아니라 집계에 포함된 가장 최근 측정 시각입니다. 매번 같은 범위 정의로 계산하고, 정확히 같은 시각의 결과를 비교해야 하면 실행마다 기준 시각을 한 번 정해 사용합니다. 아래 예제는 미래 시각의 측정값을 제외합니다. 결과 비교 시에는 각 SQL의 `NOW` 대신 동일한 고정 기준 시각을 전달해 하한과 상한을 함께 계산합니다. ```sql -- 주기적 집계 갱신 (1시간마다 실행) DELETE FROM sensor_recent_avg; INSERT INTO sensor_recent_avg SELECT name AS key_id, name, MAX(time) AS base_ts, AVG(value), MAX(value), COUNT(*) FROM sensor_data WHERE time >= NOW - 3600000000000 * 2 -- 최근 2시간 재계산 AND time <= NOW GROUP BY name; ``` `INSERT ... SELECT`에는 `ON DUPLICATE KEY UPDATE`를 붙이지 않습니다. 캐시를 재구성할 때는 기존 캐시를 삭제한 뒤 다시 적재합니다. 삭제와 재적재 사이에는 캐시가 비거나 일부만 채워진 상태를 다른 조회가 볼 수 있습니다. 일괄 갱신 중 표시할 결과, 실패 시 재시도와 원본 조회 여부를 애플리케이션에서 정합니다. 항상 일관된 전체 결과가 필요하면 그 요구를 충족하는 별도의 전환·트랜잭션 모델을 검토합니다. ### 대시보드 조회 최적화 ```sql -- 저장된 캐시 조회 (원본 조회로의 전환은 애플리케이션에서 처리) SELECT sensor_id, base_ts, avg_val, max_val FROM sensor_recent_avg WHERE base_ts >= NOW - 3600000000000 * 2 AND base_ts <= NOW ORDER BY sensor_id, base_ts; ``` 이 조건은 최신 측정 시각이 최근 2시간에 속한 캐시 행을 표시합니다. 기존 평균을 조회 시각 기준으로 다시 계산하는 것은 아니므로, 집계 자체의 최신성은 갱신 주기로 관리합니다. ### 장애 복구 서버 재시작으로 VOLATILE 테이블과 캐시가 소멸되면 테이블을 먼저 다시 만든 뒤 원본 TAG 테이블에서 같은 최근 2시간 통계를 재계산합니다. 아래 CREATE는 테이블이 사라진 재시작 이후에만 실행합니다. 원본 보존 기간 안에 재계산할 데이터가 남아 있어야 합니다. ```sql -- 캐시 재구성 (서버 재시작 후) CREATE VOLATILE TABLE sensor_recent_avg ( key_id VARCHAR(64) PRIMARY KEY, sensor_id VARCHAR(64), base_ts DATETIME, avg_val DOUBLE, max_val DOUBLE, cnt LONG ); INSERT INTO sensor_recent_avg SELECT name, name, MAX(time), AVG(value), MAX(value), COUNT(*) FROM sensor_data WHERE time >= NOW - 3600000000000 * 2 -- 정상 갱신과 같은 최근 2시간 AND time <= NOW GROUP BY name; ``` 복구 후 캐시의 센서별 건수·평균·최신 시각을 같은 기준 시각의 원본 집계와 비교합니다. `cnt`는 NULL 측정값을 포함한 행 수입니다. 평균에 사용한 표본 수가 필요하면 `COUNT(value)`를 별도 컬럼으로 저장합니다. ## INSERT·UPDATE 패턴 모델은 어떤 데이터를 변경 가능하게 둘지 결정해야 하지만, 이 페이지에서 DML 지원표를 다시 정의하지 않습니다. 테이블별 변경 조건은 [데이터 변경 정책](../alter-data-mutation-policy/)을, INSERT·Append·파일 입력 선택은 [데이터 입력과 반출](/dbms/development-tools-integration/data-input-load-export/)을 사용하십시오. ## JOIN·메타데이터 설계 여러 테이블 타입을 조합할 때는 다음 원칙을 적용합니다. 먼저 조인의 관계를 정합니다. 센서 코드마다 마스터가 정확히 한 행인지, 코드가 공장마다 반복되는지 확인하십시오. 한 원본 행이 여러 기준 행과 일치하면 결과 행이 늘고 SUM 같은 집계도 중복될 수 있습니다. 코드의 이름뿐 아니라 식별 범위와 타입을 맞춰야 합니다. 1. 원본 행 수뿐 아니라 필터 적용 후 행 수와 실행 계획을 보고 조인 순서를 검토합니다. 2. JOIN 조건 컬럼의 타입을 맞추고, 지원되는 인덱스의 사용 여부를 확인합니다. 3. WHERE 절에 시간 범위와 업무 조건을 명시해 조인할 행 수를 줄입니다. 4. TAG 속성을 함께 조회하는 목적이라면 별도 LOOKUP 대신 METADATA가 적합한지 검토합니다. 재현 가능한 조인 예제는 [TAG 조회와 분석](/dbms/tag-table-usage/query-analysis/)과 [LOOKUP 조회와 분석](/dbms/lookup-table-usage/query-analysis/)을 참고하십시오. ## 복합 타입 조합 패턴 실제 운영 시스템에서 여러 테이블 타입을 조합하는 대표적인 설계 패턴입니다. ### 제조 설비 모니터링 시스템 ``` ┌─────────────────────────────────────────────────┐ │ 설비 모니터링 시스템 │ ├──────────────┬──────────────┬───────────────────┤ │ TAG 테이블 │ LOG 테이블 │ LOOKUP 테이블 │ │ sensor_data │ alarm_event │ equipment_master │ │ (계측값 이력) │ (알람 이벤트) │ (설비 기준 정보) │ ├──────────────┴──────────────┴───────────────────┤ │ VOLATILE 테이블 │ │ sensor_latest (최신값 캐시) │ └─────────────────────────────────────────────────┘ ``` ### 물류·주문 관리 시스템 (Standard Edition) ``` ┌─────────────────────────────────────────────────┐ │ 물류 관리 시스템 │ ├──────────────┬──────────────┬───────────────────┤ │ TRANSACTION 테이블 │ LOG 테이블 │ LOOKUP 테이블 │ │ orders │ delivery_log │ product_master │ │ (주문 관리) │ (배송 이벤트) │ (제품 기준 정보) │ │ UPDATE/DELETE│ │ │ ├──────────────┴──────────────┴───────────────────┤ │ VOLATILE 테이블 │ │ order_status_cache (현재 상태 캐시) │ └─────────────────────────────────────────────────┘ ``` ### 패턴 요약 | 역할 | 권장 타입 | 이유 | |------|---------|------| | 고빈도 계측값 이력 | TAG | Append API 고속 버퍼, 시계열 최적화 | | 이벤트·알람 로그 | LOG | 추가 전용, 도착 시각 자동 | | 관계형 업무 (UPDATE/DELETE) | TRANSACTION | SELECT/INSERT/UPDATE/DELETE 모두 지원 | | 기준·코드 정보 | LOOKUP | PK 식별과 일반 조건식 UPDATE/DELETE, 영속 | | 실시간 상태 캐시 | VOLATILE | 메모리 속도, UPSERT | --- 다음으로 읽을 내용: - [SELECT의 GROUP BY와 집계](/dbms/reference/sql/syntax/select-syntax/) - [운영 및 구성](/dbms/operations-configuration-recovery/) --- title: "5. TAG 테이블 활용" url: https://docs.machbase.com/kr/dbms/tag-table-usage/ language: kr kind: section --- # 5. TAG 테이블 활용 TAG 테이블은 반복 관측하는 대상의 이름과 시간 또는 거리 축으로 계측 이력을 저장합니다. 이 장은 Machbase DBMS 8.7.0의 TAG 구조, 입력·조회, 메타데이터와 보정·운영을 설명합니다. 3·4장에서 정한 배포 환경과 데이터 모델을 실제 SQL로 확인하는 단계입니다. 태그는 측정 대상이고 DATA 행은 관측 한 건입니다. METADATA는 태그마다 한 행인 속성이며, 과거 관측마다 별도로 저장되는 속성이 아닙니다. 원본 측정, 현재 속성, 구간 집계와 수집 시각을 구분하면 조회 결과와 정정 범위를 일관되게 해석할 수 있습니다. ## 이 장의 구성 | 절 | 내용 | |---|---| | [개요와 사용 기준](./overview-use-criteria/) | 태그 식별자와 관측 행, 시간·거리 축 선택 | | [테이블 구조와 스키마](./table-structure-schema/) | 컬럼 순서·타입, LSL/USL, BINARY와 저장 설계 | | [생성, 변경, 삭제](./create-alter-drop/) | 기본 DDL, METADATA 확장과 객체 정리 | | [데이터 입력과 변경](./data-input-mutation/) | 자동 태그 등록, SQL·Append·파일 입력 구분 | | [조회와 분석](./query-analysis/) | 구간·최신값·STAT 조회와 결과 해석 | | [인덱스와 성능](./index-performance/) | 실제 데이터를 이용한 접근 경로 확인 | | [운영과 데이터 생명주기](./operations-lifecycle/) | 삭제, 보존 정책과 중복 제거 완료 확인 | | [제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 정상 조건과 의도적 실패 예제 | | [활용 패턴과 시나리오](./patterns-scenarios/) | 관측 단위·단위계·결측에 맞는 모델 | | [TAG 메타데이터](./tag-metadata/) | 등록·조회·수정·삭제, JSON과 ARRAY | | [TAG data UPDATE와 데이터 보정](./tag-data-update-correction/) | 직접 정정, NULL 보정과 감사 이력 | | [tagmetaimport와 메타데이터 일괄 등록](./tagmetaimport/) | CSV 준비, 입력 대상, 재입력 오류 확인 | ## 실습과 지원 범위 각 페이지는 독립 실습입니다. 준비 SQL의 테이블이 이미 있으면 다른 업무 객체인지 확인하고, 임의로 삭제하지 않습니다. 성공 예제와 실패를 확인하는 예제를 분리해 실행하며 정리 SQL은 해당 실습에서 만든 객체에만 적용합니다. TAG DATA UPDATE는 Standard Edition 전용입니다. 자동 중복 검사 기간, METADATA ALTER와 LSL/USL의 세부 작업도 Edition별 범위를 확인합니다. SQL INSERT, Append의 처리 응답, 저장 버퍼 flush와 인덱스·통계 처리 완료는 같은 의미가 아닙니다. ROLLUP의 생성·조회·재구성 전체 절차는 [6장](../tag-rollup-usage/)에서 설명합니다. 이 장에서는 TAG 정정과 삭제가 집계에 미치는 관계만 다룹니다. --- title: "5.1 개요와 사용 기준" url: https://docs.machbase.com/kr/dbms/tag-table-usage/overview-use-criteria/ language: kr kind: page --- # 5.1 개요와 사용 기준 TAG는 센서·설비처럼 같은 대상을 반복 관측한 이력을 저장할 때 검토합니다. “태그 하나”와 “행 하나”를 구분하는 것이 출발점입니다. 같은 이름으로 여러 시각의 행을 입력할 수 있으며, 태그 이름이 같다는 이유만으로 중복 행이 제거되지는 않습니다. ## TAG 테이블의 특성 `PRIMARY KEY`는 태그 이름을 지정합니다. 관계형 테이블의 행별 고유 키와 역할이 다릅니다. 한 테이블에는 시간축 또는 거리축 하나를 지정합니다. ```sql -- 시간에 따라 발생한 관측값을 저장하는 시간축 TAG입니다. CREATE TAG TABLE ch5_overview_time ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); -- 거리나 위치 구간을 기준으로 관측값을 저장하는 거리축 TAG입니다. -- 거리축 TAG에는 ROLLUP을 사용할 수 없습니다. CREATE TAG TABLE ch5_overview_distance ( name VARCHAR(64) PRIMARY KEY, distance DOUBLE BASEDISTANCE, value DOUBLE ); ``` 두 예제 모두 이름은 첫 번째 컬럼, 축은 두 번째 컬럼입니다. 시간축은 `DATETIME BASETIME`, 거리축은 `DOUBLE`, `LONG`, `ULONG` 중 하나와 `BASEDISTANCE`를 사용합니다. `SUMMARIZED`를 사용하면 세 번째 컬럼에 지정합니다. 컬럼 순서와 지정 가능한 타입은 [생성, 변경, 삭제](../create-alter-drop/), 모델별 설계 판단은 [테이블 구조와 스키마](../table-structure-schema/)를 참고합니다. `SUMMARIZED`는 대표 통계·집계 관련 컬럼을 지정하는 속성이며, ROLLUP 객체를 자동으로 만들지는 않습니다. 다음 표는 예제 테이블의 컬럼 목록이 아니라, TAG 테이블을 사용할 때 구분해야 하는 데이터 범위입니다. DATA는 사용자가 입력하는 관측 행이고, METADATA와 STAT는 태그별 속성·통계를 확인할 때 사용하는 별도 범위입니다. | 데이터 범위 | 의미 | |---|---| | 태그 이름 | 센서나 반복 관측 대상을 식별하는 첫 번째 컬럼 | | DATA | 사용자가 입력하는 관측 행: 축 값과 여러 데이터 컬럼 | | METADATA | 태그별 위치·단위·설정 등 현재 속성. 일반 DATA 컬럼과 구분 | | STAT | `V$_STAT`에서 확인하는 태그별 입력 통계. 예제 테이블의 직접 컬럼이 아님 | ## TAG가 적합한 경우 - 같은 스키마로 여러 대상의 이력을 계속 추가합니다. - 특정 태그의 시간·거리 범위를 자주 조회합니다. - 현재 속성을 조건으로 태그를 고르고 해당 이력을 분석합니다. - 시간축 TAG에서 반복 구간 통계를 저장하고 조회해야 합니다. 이 경우 ROLLUP 설계는 [TAG ROLLUP](../../tag-rollup-usage/)에서 별도로 확인합니다. 단일 값 모델은 측정 항목별로 태그를 나누고, 다중 값 모델은 같은 관측에서 함께 나온 온도·압력 등을 한 행에 모읍니다. 측정 시각이 다른 값을 억지로 같은 행에 넣지 말고 결측·품질 정책을 정합니다. 시계열의 같은 시각 데이터도 중복 수집일 수 있으므로 이름·시각·값의 중복 처리 정책을 별도로 확인합니다. ## 다른 테이블을 검토할 경우 | 요구사항 | 검토할 대안 | |---|---| | 관측 대상보다 사건 자체가 중요한 이벤트 검색 | LOG | | 영속 기준 정보의 일반 조건 조회·변경 | LOOKUP | | 여러 DML의 명시적 트랜잭션과 관계형 변경 | TRANSACTION(Standard Edition 전용) | | 원본에서 재구성할 수 있는 공유 상태 캐시 | VOLATILE | TAG에서도 JOIN과 제한된 값 보정을 사용할 수 있습니다. “JOIN이 필요하면 TAG를 쓸 수 없다” 또는 “계측값은 반드시 TAG여야 한다”는 식으로 판단하지 않습니다. 다만 DATA의 UPDATE는 Standard Edition 전용입니다. Cluster Edition에서는 METADATA UPDATE만 사용할 수 있으므로, 값 보정이 필요하면 재입력과 재집계 절차를 함께 설계합니다. ## 설계 순서 1. 태그가 센서·장비·검사 회차 중 무엇을 식별하는지 정합니다. 2. 측정 시각 또는 거리·위치의 의미와 단위를 정합니다. 3. 값의 타입, NULL과 품질 표시, 관측 주기를 정합니다. 4. 관측마다 보존할 속성과 현재 METADATA를 구분합니다. 5. 구간 조회·집계·보정·보관 요구를 Edition의 지원 범위와 대조합니다. 예제 테이블의 확인과 정리는 다음과 같습니다. ```sql -- 예제 테이블의 스키마를 확인합니다. DESC ch5_overview_time; DESC ch5_overview_distance; -- 뒤 예제와 이름이 겹치지 않도록 정리합니다. DROP TABLE ch5_overview_distance; DROP TABLE ch5_overview_time; ``` 다음은 [스키마 설계](../table-structure-schema/)와 [입력·조회 실습](../data-input-mutation/)입니다. --- title: "5.2 테이블 구조와 스키마" url: https://docs.machbase.com/kr/dbms/tag-table-usage/table-structure-schema/ language: kr kind: page --- # 5.2 테이블 구조와 스키마 ## TAG 테이블 설계 TAG 테이블 설계는 컬럼을 몇 개 둘지 정하는 일이 아니라, 무엇을 하나의 태그로 볼지와 어떤 값을 어느 범위에 둘지 정하는 일입니다. 컬럼 위치에 따라 정해지는 역할, 축 컬럼의 타입, 지정할 수 없는 타입은 [생성, 변경, 삭제](../create-alter-drop/#original-85-creating-tag-tables)를 참고합니다. 이 페이지는 그 규칙 안에서 결정할 항목을 다룹니다. 이 페이지의 DDL은 모델별 독립 예제입니다. 생성 순서가 필요한 실습은 해당 절 안에서 설명하며, 같은 이름의 기존 객체가 있으면 다른 이름으로 실행합니다. 스키마를 정할 때는 다음 항목을 함께 검토합니다. - [활용 사례](#tag-schema-use-case-summary) - [태그 이름 컬럼](#tag-name-column-design) - [시간축과 거리축 선택](#time-axis-design-tag) - [값 컬럼 설계](#tag-table-design-design-column) - [METADATA 컬럼 설계](#metadata-column-design-summary) - [JSON METADATA 컬럼 설계](#json-metadata-column-design-summary) - [이진 데이터 컬럼 설계](#tag-table-design-design-column-binary) - [VARCHAR 스토리지 최적화](#tag-table-design-storage-varchar) - [스토리지 전략](#tag-table-design-strategy) - [LSL·USL 설계](#tag-table-design-lsl-usl) - [보정과 중복 정책](#correction-duplication-policy-summary) - [제약 및 지원 범위](#tag-schema-limitations-summary) ### 활용 사례 TAG는 같은 구조의 관측값이 여러 대상에서 계속 쌓이는 업무에 적합합니다. 센서, 설비, 이동체, 검사 회차처럼 반복 관측 대상을 먼저 정하고, 그 대상별 이력을 시간 또는 거리 축으로 조회할 수 있는지 확인합니다. 이 항목에서는 대상을 TAG로 모델링할지, 다른 테이블 타입으로 나눌지를 확정합니다. 업무 모델 예시는 [활용 사례](../patterns-scenarios/#use-cases-tag)를 참고합니다. ### 태그 이름 컬럼 센서별, 설비별, 검사 회차별 중 어떤 단위를 하나의 태그로 볼지 먼저 정합니다. 잘게 나누면 태그 수가 늘어 메타데이터와 인덱스 부담이 커지고, 크게 묶으면 한 태그 안에 서로 다른 대상의 이력이 섞입니다. 이름 규칙은 [VARCHAR 스토리지 최적화](#tag-table-design-storage-varchar)에서 함께 다룹니다. ### 시간축과 거리축 선택 축은 조회 조건에서 무엇을 범위로 지정할지를 기준으로 고릅니다. 측정 시각으로 구간을 지정하면 시간축, 특정 경로의 누적 위치처럼 거리 구간으로 지정하면 거리축입니다. 한 TAG 테이블은 두 축을 동시에 가질 수 없으므로 나중에 바꾸려면 테이블을 다시 만들어야 합니다. ```sql -- 측정 시각으로 구간을 지정하는 시간축 TAG입니다. CREATE TAG TABLE time_sensor ( name VARCHAR(40) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); ``` ```sql -- 거리·위치로 구간을 지정하는 거리축 TAG입니다. CREATE TAG TABLE rail_sensor ( name VARCHAR(40) PRIMARY KEY, distance DOUBLE BASEDISTANCE, value DOUBLE ); ``` `DURATION`, ROLLUP과 시간 함수는 BASETIME에만 적용됩니다. 거리 범위 조회는 일반 비교와 `BETWEEN`을 사용하며, 실행 예제는 [조회와 분석](../query-analysis/)을 참고합니다. ### 값 컬럼 설계 TAG 테이블의 값 컬럼은 태그 이름과 축 컬럼을 제외한 일반 데이터 컬럼입니다. 계측값, 상태, 품질 코드처럼 측정 행마다 달라지는 값을 저장합니다. #### 지원 타입 아래는 자주 사용하는 타입의 예입니다. JSON, BINARY, DECIMAL과 숫자 ARRAY를 포함한 전체 지원 범위는 [데이터 타입 사전](/dbms/reference/sql/types/)을 참고하십시오. | 타입 | 설명 | 저장 크기 | |------|------|---------| | `DOUBLE` | 64비트 부동소수점 | 8바이트 | | `FLOAT` | 32비트 부동소수점 | 4바이트 | | `LONG` | 64비트 정수 | 8바이트 | | `INTEGER` (`INT`) | 32비트 정수 | 4바이트 | | `SHORT` | 16비트 정수 | 2바이트 | | `VARCHAR(n)` | 가변 문자열 | 최대 n바이트 | #### 권장 타입 선택 | 데이터 | 권장 타입 | |--------|---------| | 온도, 습도, 압력 등 아날로그 값 | `DOUBLE` | | 카운터, 상태 코드 | `INTEGER` | | 플래그, 이진 상태 | `SHORT` | | 에너지, 유량 누적값 | `DOUBLE` 또는 `LONG` | | 태그 문자열 값 | `VARCHAR(n)` | #### 다중 값 컬럼 설계 한 테이블에 여러 계측항목을 함께 저장할 때는 NULL 값이 발생할 수 있습니다. 계측항목이 서로 동시에 수집되는 경우에 적합합니다. ```sql CREATE TAG TABLE weather_station ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, temperature DOUBLE, -- 항상 수집 humidity DOUBLE, -- 항상 수집 wind_speed DOUBLE, -- 옵션 rainfall DOUBLE -- 옵션 ); ``` #### NULL 허용 설계 TAG 테이블 값 컬럼은 기본적으로 NULL을 허용합니다. 특정 태그가 일부 항목만 수집하는 경우 나머지 컬럼에 NULL을 삽입합니다. ```sql -- wind_speed와 rainfall이 없는 경우 INSERT INTO weather_station VALUES ('WS-01', NOW, 22.5, 65.0, NULL, NULL); ``` ### METADATA 컬럼 설계 METADATA 컬럼은 DATA 행마다 반복 저장할 값이 아니라 태그별 현재 속성을 저장할 때 사용합니다. 설치 위치, 단위, 장비 설정, 관리 상태처럼 태그 이름에 붙는 속성이 여기에 해당합니다. DATA와 METADATA를 분리하면 관측 이력은 유지하면서 태그의 현재 속성만 따로 조회하거나 변경할 수 있습니다. 이 항목에서는 각 속성을 METADATA로 올릴지 DATA 컬럼으로 남길지 확정합니다. 관측마다 값이 달라지면 DATA, 태그 수명 동안 대체로 고정이면 METADATA입니다. 입력, 조회, 수정 예제는 [METADATA 사용](../tag-metadata/#original-85-tag-metadata)을 참고합니다. ### JSON METADATA 컬럼 설계 태그별 속성이 계층 구조를 갖거나 속성 집합이 자주 바뀌면 JSON METADATA 컬럼을 검토합니다. 예를 들어 설비 위치, 제조사 정보, 설치 옵션을 하나의 JSON 문서로 저장하고 필요한 path만 조회할 수 있습니다. 이 항목에서는 METADATA를 개별 컬럼으로 둘지 JSON 문서 하나로 둘지 확정합니다. 속성 집합이 고정이고 조건 조회가 잦으면 개별 컬럼이, 태그마다 속성 구성이 다르면 JSON이 유리합니다. 자주 조건으로 사용하는 path는 인덱스 설계도 함께 검토합니다. 자세한 문법과 예제는 [JSON METADATA](../tag-metadata/#metadata-design-json)를 참고합니다. ### 이진 데이터 컬럼 설계 TAG 테이블의 `BINARY(n)`은 1~32767바이트의 센서 프레임을 저장할 때 사용합니다. 입력 literal, 길이 제약과 드라이버 동작은 [Binary 컬럼](#original-85-binary-columns)을 참고하십시오. 큰 이미지나 파형은 외부 스토리지에 두고 참조 키만 저장하는 설계도 검토하십시오. ### VARCHAR 스토리지 최적화 `VARCHAR`는 실제 최대 길이에 맞춰 선언합니다. 저장 option의 정확한 문법은 [DDL 사전](/dbms/reference/sql/syntax/ddl-syntax/)을 참고하십시오. 태그 이름에는 사이트, 설비, 센서 식별자를 일관된 구분자로 조합해 범위 조회가 가능하도록 설계합니다. ### 스토리지 전략 데이터는 태그별로 분리된 컬럼 스토리지에 저장됩니다. 데이터 양과 조회 패턴에 따라 적절한 전략을 선택합니다. #### 단일 테이블 vs 다중 테이블 ##### 단일 TAG 테이블 (권장) 같은 종류의 센서는 하나의 TAG 테이블에 모아서 관리합니다. ```sql -- 권장: 모든 온도 센서를 하나의 테이블로 CREATE TAG TABLE temperature_sensor ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); ``` **장점** - 관리 포인트 최소화 - 크로스-태그 집계 용이 - 운영 간소화 ##### 다중 TAG 테이블 컬럼 구성이 다르거나 보존 기간·접근 권한·운영 주기를 따로 관리해야 하면 테이블 분리를 검토합니다. 센서 수가 늘었다는 이유만으로 센서별 테이블을 만들지는 않습니다. ```sql -- 온도·습도 센서 (DOUBLE 값) CREATE TAG TABLE thermo_sensor ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, temp DOUBLE, humid DOUBLE ); -- 진동 센서 (DOUBLE + BINARY 파형) CREATE TAG TABLE vibration_sensor ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, rms DOUBLE, waveform BINARY(4096) ); ``` #### 태그 수 관리 - 태그 수 증가에 따른 태그 인덱스와 메타데이터 메모리 사용량을 운영 규모의 데이터로 측정합니다. - 센서 계층 구조를 태그 이름에 인코딩하여 관리합니다. - 태그 이름이 매 레코드마다 고유한 값이 되는 설계는 피합니다 (안티패턴). #### 파티션 전략 시간축 TAG는 `BASETIME`을 기준으로 범위를 제한해 조회합니다. 시스템 저장 객체나 파티션 이름에 의존하지 말고, 보존 기간은 [데이터 보존 정책](/dbms/operations-configuration-recovery/policy-data-retention/)으로 관리하십시오. ### LSL·USL 설계 LSL(Lower Specification Limit)은 하한 규격값, USL(Upper Specification Limit)은 상한 규격값입니다. TAG 테이블의 METADATA 컬럼에 태그별 허용 범위를 설정하면 범위 밖 DATA 입력을 차단할 수 있으므로, 태그마다 다른 입력 품질 기준을 적용할 수 있습니다. 값을 보정하는 기능이 아니라 입력을 거부하는 기능입니다. 범위를 벗어난 입력은 수집 오류 정책에 따라 기록·재처리해야 합니다. #### 제약 조건 다음 제약 조건이 적용됩니다. * LSL/USL을 Cluster 전체에서 미지원으로 묶지 않습니다. 테이블 생성 시 한계 정의와 메타데이터 값 설정, DATA INSERT·Append의 한계 검사는 공통 경로입니다. 기존 METADATA에 한계 컬럼을 ALTER로 추가하는 아래 실습은 Standard Edition에서 수행합니다. * LSL/USL을 설정하려면 Tag 테이블의 세 번째 컬럼인 __Value__가 __SUMMARIZED__로 설정되어야 합니다. * LSL은 USL보다 작거나 같아야 하며, __Value__ 컬럼의 입력 값은 LSL과 USL 사이에 있어야 합니다 (포함). __(LSL <= Value <= USL)__ * LSL/USL 설정을 부여하기 전에 입력된 데이터는 검증되지 않습니다. * LSL/USL 컬럼을 NULL로 설정하면 입력 데이터를 검증하지 않습니다. * LSL/USL 기능은 개별적으로 사용할 수 있습니다. 상한 사양만 일치시키려면 USL만 설정할 수 있습니다. * USL만 설정하면 상한 초과만 검사하고, LSL만 설정하면 하한 미달만 검사합니다. #### 지원되는 데이터 타입 한계 컬럼은 대상 __Value__ 컬럼과 타입이 같아야 합니다. 아래는 기본 숫자 타입의 규격 범위 설정을 설명합니다. SUMMARIZED 자체의 전체 지원 타입에는 JSON도 있으므로 SUMMARIZED 선언 가능 여부와 숫자 한계 설정을 같은 조건으로 해석하지 않습니다. |타입|설명|범위|유효 자릿수| |----|------|-----|----| |short|16비트 부호 있는 정수 데이터 타입|-32767 ~ 32767|-| |ushort|16비트 부호 없는 정수 데이터 타입|0 ~ 65534|-| |integer|32비트 부호 있는 정수 데이터 타입|-2147483647 ~ 2147483647|-| |uinteger|32비트 부호 없는 정수 데이터 타입|0 ~ 4294967294|-| |long|64비트 부호 있는 정수 데이터 타입|-9223372036854775807 ~ 9223372036854775807|-| |ulong|64비트 부호 없는 정수 데이터 타입|0~18446744073709551614|-| |float|32비트 부동 소수점 데이터|-|6[^1]| |double|64비트 부동 소수점 데이터|-|15[^1]| #### LSL/USL 설정 및 사용 다음 CREATE 예제들은 서로 다른 선택을 보여 줍니다. 기본 `example`만 이후 INSERT·UPDATE 실습에서 사용하고, 대안 테이블은 별도로 생성합니다. 태그 메타데이터 테이블의 컬럼에 `LOWER LIMIT`(LSL) 또는 `UPPER LIMIT`(USL) 키워드를 지정합니다. Tag 테이블 생성 시 또는 메타데이터 컬럼 추가 시 설정할 수 있습니다. ##### CREATE ```sql CREATE TAG TABLE example ( tag_id VARCHAR(50) PRIMARY KEY, time DATETIME BASETIME, value INTEGER SUMMARIZED) METADATA ( lsl INTEGER LOWER LIMIT, usl INTEGER UPPER LIMIT ); ``` 두 컬럼을 함께 사용하거나 하나만 사용할 수 있습니다. LSL만 설정하면 `Value >= LSL`을 검사하고 상한은 제한하지 않습니다. USL 값을 `NULL`로 둔 것과 같은 의미입니다. ```sql CREATE TAG TABLE example_lower_only ( tag_id VARCHAR(50) PRIMARY KEY, time DATETIME BASETIME, value INTEGER SUMMARIZED) METADATA ( lsl INTEGER LOWER LIMIT ); ``` ##### ADD COLUMN 데이터가 이미 입력된 후 `ADD COLUMN`을 사용하여 추가하는 경우 기본값은 __NULL__입니다. ```sql CREATE TAG TABLE example_alter_limits ( tag_id VARCHAR(50) PRIMARY KEY, time DATETIME BASETIME, value INTEGER SUMMARIZED ); ALTER TABLE example_alter_limits METADATA ADD COLUMN (lsl INTEGER LOWER LIMIT); ALTER TABLE example_alter_limits METADATA ADD COLUMN (usl INTEGER UPPER LIMIT); ``` [CREATE](#create)와 마찬가지로 하나의 속성만 추가할 수도 있습니다. ```sql CREATE TAG TABLE example_alter_upper ( tag_id VARCHAR(50) PRIMARY KEY, time DATETIME BASETIME, value INTEGER SUMMARIZED ); ALTER TABLE example_alter_upper METADATA ADD COLUMN (usl INTEGER UPPER LIMIT); ``` ##### INSERT 특정 TAG ID에 대한 LSL/USL 값을 설정합니다. ```sql INSERT INTO example metadata VALUES ('TAG_01', 100, 200); ``` 설정 후 태그 데이터를 입력하면 다음과 같이 동작합니다. ```sql INSERT INTO example VALUES ('TAG_01', NOW, 95); -- Failure ``` ```text [ERR-02342: SUMMARIZED value is less than LOWER LIMIT.] ``` ```sql INSERT INTO example VALUES ('TAG_01', NOW, 100); -- Success (Inclusive) ``` ```text 1 row(s) inserted. Elapsed time: 0.000 ``` ```sql INSERT INTO example VALUES ('TAG_01', NOW, 150); -- Success ``` ```text 1 row(s) inserted. Elapsed time: 0.000 ``` ```sql INSERT INTO example VALUES ('TAG_01', NOW, 200); -- Success (Inclusive) ``` ```text 1 row(s) inserted. Elapsed time: 0.000 ``` ```sql INSERT INTO example VALUES ('TAG_01', NOW, 205); -- Failure ``` ```text [ERR-02341: SUMMARIZED value is greater than UPPER LIMIT.] ``` Tag 테이블을 조회하면 규격 범위 내 데이터만 입력된 것을 확인할 수 있습니다. ```sql SELECT * FROM example; ``` ```text TAG_ID TIME VALUE LSL USL ------------------------------------------------------------------------------------------------------------------------------ TAG_01 2023-09-12 09:31:27 923:289:631 100 100 200 TAG_01 2023-09-12 09:31:27 929:013:232 150 100 200 TAG_01 2023-09-12 09:31:27 939:209:248 200 100 200 [3] row(s) selected. Elapsed time: 0.001 ``` ##### UPDATE LSL/USL 컬럼의 값을 수정합니다. 이미 입력된 데이터에는 소급 적용되지 않으므로 주의가 필요합니다. ```sql UPDATE example metadata SET lsl = 10, usl = 100 WHERE tag_id = 'TAG_01'; ``` ```text 1 row(s) updated. Elapsed time: 0.001 ``` ```sql SELECT tag_id, lsl, usl FROM example METADATA; ``` ```text TAG_ID LSL USL ---------------------------------------------------------------------------------------- TAG_01 10 100 [1] row(s) selected. Elapsed time: 0.001 ``` ##### DELETE LSL/USL 컬럼은 `DROP COLUMN`으로 제거하지 않고 값을 NULL로 설정해 제약을 해제합니다. ```sql UPDATE EXAMPLE METADATA SET lsl = NULL, usl = NULL WHERE tag_id = 'TAG_01'; ``` ```text 1 row(s) updated. Elapsed time: 0.001 ``` ```sql SELECT tag_id, lsl, usl FROM example METADATA; ``` ```text TAG_ID LSL USL ---------------------------------------------------------------------------------------- TAG_01 NULL NULL [1] row(s) selected. Elapsed time: 0.001 ``` #### TRACE 로그로 LSL/USL 위반 확인 - 위치: `$MACHBASE_HOME/trc/machbase.trc` - 빠른 필터: ```bash grep LIMIT_DROP $MACHBASE_HOME/trc/machbase.trc | tail -n 20 ``` - 로그 포맷: `LIMIT_DROP (TYPE=) TABLE=<테이블명> TAG= <컬럼명=값 ...>` - TYPE=LOWER/UPPER로 어떤 한계가 위반됐는지 구분. - DATETIME은 `YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn` 형태. - 실제 예시: ``` [2025-11-29 13:50:34 P-151395 T-126343511537344][QP-INFO] LIMIT_DROP (TYPE=LOWER) TABLE=TAG3 TAG=tag-1 TIME=2020-01-01 00:00:00 000:000:000 VALUE=5.55 [2025-11-29 13:50:34 P-151395 T-126343511537344][QP-INFO] LIMIT_DROP (TYPE=UPPER) TABLE=TAG3 TAG=tag-1 TIME=2020-01-01 00:00:04 000:000:000 VALUE=30.55 [2025-11-29 13:50:35 P-151395 T-126344475694784][QP-INFO] LIMIT_DROP (TYPE=LOWER) TABLE=TAG3 TAG=tag-2 TIME=1998-12-24 09:00:00 000:000:000 VALUE=0 [2025-11-29 13:50:35 P-151395 T-126344475694784][QP-INFO] LIMIT_DROP (TYPE=UPPER) TABLE=TAG3 TAG=tag-2 TIME=1998-12-24 09:00:00 000:000:008 VALUE=45 ``` - 활용 포인트 - TAG별로 LOWER/UPPER 위반 시각과 값을 한눈에 파악. - grep으로 TAG/테이블명을 추가 필터하면 특정 대상만 추적 가능. - 주의: 한 줄 최대 약 4KB라 컬럼이 많을 때 뒤가 잘릴 수 있으며, 기동 직후 메타 캐시 준비 전에는 테이블명이 ID로 보일 수 있습니다. [^1]: [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) ### 보정과 중복 정책 값 보정과 중복 제거는 컬럼 정의만으로 끝나는 항목은 아니지만, 스키마 설계 단계에서 미리 정해야 합니다. 보정이 필요하면 어떤 값 컬럼을 수정할 수 있는지, 원본을 별도 컬럼이나 별도 테이블에 보존할지, 보정 후 ROLLUP을 어떻게 재구성할지 결정합니다. DATA의 UPDATE는 Standard Edition 전용이므로, Cluster Edition에서는 보정 대신 재입력과 재집계 절차를 설계합니다. 자세한 내용은 [데이터 보정](../tag-data-update-correction/#design-correction-tag)을 참고합니다. 같은 태그와 같은 축 값이 반복 입력될 수 있으면 중복을 허용할지, 수집 단계에서 제거할지, Machbase의 자동 중복 제거 기능을 사용할지 정합니다. 설정 변경과 운영 검증 절차는 [자동 중복 제거](../operations-lifecycle/#original-85-duplication-removal)를 참고합니다. ### 제약 및 지원 범위 TAG 테이블은 반복 관측 이력에 맞춘 구조이므로 모든 SQL 기능을 일반 관계형 테이블처럼 지원하지 않습니다. 축 컬럼, METADATA, 값 보정, ROLLUP, Edition별 지원 범위는 설계 전에 확인해야 합니다. 이 항목에서는 앞에서 정한 설계안이 지원 범위 안에 있는지 점검하고, 벗어나면 해당 항목으로 돌아가 스키마를 조정합니다. 지원하지 않는 기능과 대표 오류는 [제약 및 주의사항](../constraints-errors-troubleshooting/#limitations-tag)을 참고합니다. ## Binary 컬럼 `BINARY(n)`은 Tag 테이블에서 센서 프레임용 고정 길이 바이너리 값을 저장합니다. TAG의 길이 생략형 `BINARY`는 32767바이트로 처리됩니다. 필요한 프레임 크기를 명시하면 저장·전송 크기를 이해하기 쉽습니다. TAG 외 테이블에는 `BINARY(n)` 길이 지정형을 선언할 수 없습니다. 길이는 1~32K-1 (1~32767)바이트만 유효하며, 인덱스를 생성할 수 없습니다. 명시적 binary literal로 `BINARY` 값을 입력합니다. ### DDL 규칙 ```sql CREATE TAG TABLE t1( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, frame BINARY(4) ); ``` - 유효 길이: `1 <= n <= 32767` (32K-1). - 범위를 벗어나면 생성 시 오류가 발생합니다(`BINARY(0)` 등). - `DESC`와 테이블 메타데이터는 선언된 바이트 길이(헥스 폭 아님)를 표시합니다. SQL `LENGTH(binary_col)`는 짧은 입력값에 붙은 뒤쪽 0 패딩을 제외한 표시 값 길이를 반환합니다. ### 지원 입력 형식 ```sql X'hex_digits' x'hex_digits' B'bit_digits' b'bit_digits' O'octal_digits' o'octal_digits' ``` | 형식 | 의미 | 단위 | | --- | --- | --- | | `X'...'`, `x'...'` | 16진수 literal | 16진수 2자리 = 1바이트 | | `B'...'`, `b'...'` | 2진수 literal | bit 8자리 = 1바이트 | | `O'...'`, `o'...'` | 8진수 literal | 8진수 3자리 = 1바이트 | prefix는 대소문자 모두 허용됩니다. ```sql CREATE TAG TABLE t_bin ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value BINARY(4) ); INSERT INTO t_bin VALUES('hex1', '2024-01-01 00:00:00', X'0A'); INSERT INTO t_bin VALUES('hex2', '2024-01-01 00:00:01', x'00010203'); INSERT INTO t_bin VALUES('bit1', '2024-01-01 00:00:02', B'00001010'); INSERT INTO t_bin VALUES('oct1', '2024-01-01 00:00:03', O'012'); ``` `X'0A'`, `B'00001010'`, `O'012'`는 모두 1바이트 값 `0x0A`를 의미합니다. ### Binary literal 규칙 #### 16진수 literal `X'...'`와 `x'...'`에는 `0-9`, `A-F`, `a-f`를 사용할 수 있습니다. ```sql X'00' X'0AFF' x'abcdef' ``` 16진수 문자는 반드시 짝수 개여야 합니다. 두 자리가 1바이트에 해당합니다. #### 2진수 literal `B'...'`와 `b'...'`에는 `0`과 `1`만 사용할 수 있습니다. ```sql B'00000000' -- 0x00 B'00001010' -- 0x0A b'11111111' -- 0xFF ``` bit 수는 반드시 8의 배수여야 합니다. 8자리가 1바이트에 해당합니다. #### 8진수 literal `O'...'`와 `o'...'`에는 `0-7`만 사용할 수 있습니다. ```sql O'000' -- 0x00 O'012' -- 0x0A o'377' -- 0xFF ``` 8진수 문자는 반드시 3자리 단위여야 합니다. 각 3자리 값은 `000`부터 `377`까지의 1바이트 범위여야 합니다. #### 빈 값 작은따옴표 안을 비워 길이 0인 binary 값을 표현합니다. ```sql X'' B'' O'' ``` ### 길이 제한 `BINARY(n)` 컬럼에는 최대 `n`바이트까지만 입력 가능합니다. ```sql CREATE TAG TABLE t_limit ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value BINARY(2) ); INSERT INTO t_limit VALUES('ok_hex', '2024-01-01 00:00:00', X'0AFF'); INSERT INTO t_limit VALUES('ok_bit', '2024-01-01 00:00:01', B'0000101011111111'); INSERT INTO t_limit VALUES('ok_oct', '2024-01-01 00:00:02', O'012377'); INSERT INTO t_limit VALUES('bad_hex', '2024-01-01 00:00:03', X'000102'); -- 실패: 3바이트 ``` source 종류와 무관하게 최종 binary 값이 대상 `BINARY(n)` 길이를 초과하면 입력은 실패합니다. 이 규칙은 binary literal뿐 아니라 일반 문자열, 기존 `'0x...'` 문자열 입력, 다른 `BINARY` 컬럼 값을 `INSERT ... SELECT`로 복사하는 경우에도 동일하게 적용됩니다. ```sql CREATE TAG TABLE t_src ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value BINARY(8) ); CREATE TAG TABLE t_dst ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value BINARY(4) ); INSERT INTO t_src VALUES('k1', '2024-01-01 00:00:00', X'0102030405060708'); INSERT INTO t_dst SELECT name, time, value FROM t_src; -- 실패: 8바이트 값을 BINARY(4)에 입력 ``` `CASE`, `INSERT ... SELECT`, view 등 SQL expression 안에서 사용하더라도 최종 binary 값이 대상 `BINARY(n)` 길이를 초과하면 입력은 실패합니다. ### 올바르지 않은 입력 다음 입력은 유효하지 않습니다. ```sql X'0' -- 16진수 문자가 홀수 개 X'0G' -- G는 16진수 문자가 아님 B'0101' -- bit 수가 8의 배수가 아님 B'00000002' -- 2는 2진수 문자가 아님 O'12' -- 8진수 문자가 3자리 단위가 아님 O'400' -- 1바이트 범위 초과 X'0102 -- 닫는 작은따옴표가 없음 ``` 잘못된 값이나 길이 초과 입력은 다음 오류로 실패합니다. ```text [ERR-02233: Error occurred at column (n): (Invalid insert value.)] ``` ### 기존 문자열 입력과의 차이 기존 호환성을 위해 문자열 형태의 `'0x...'` 입력도 사용할 수 있습니다. `'0x...'`는 문자열에서 `BINARY` 컬럼으로 변환되는 방식이고, `X'...'`, `B'...'`, `O'...'`는 SQL에서 binary 값임을 명확히 표시하는 binary literal입니다. 일반 문자열을 `BINARY(n)` 컬럼에 입력하는 것도 가능하지만, 문자열 byte 길이가 `n`을 초과하면 실패합니다. 새 SQL 작성 시에는 의미가 명확한 binary literal 형식을 권장합니다. `'0b...'`, `'0o...'`, 따옴표 없는 `0x...`, `0b...`, `0o...` 형식은 binary literal로 지원하지 않습니다. ### 출력 및 도구 메모 - machsql은 `0x` 없는 대문자 헥스로 출력합니다. 짧은 입력값에 붙은 뒤쪽 0 패딩은 텍스트 출력에 표시되지 않습니다. - machloader: 스키마에 `BINARY(n)`을 선언하고, 잘못된 값이나 길이 초과는 실패합니다. - Machbase SQLCLI, ODBC, Java, C#, Node.js 드라이버는 고정 길이 버퍼로 송수신하며 메타데이터 `LENGTH`는 바이트 길이입니다. ## 예제 정리 이 페이지에서 실제로 만든 테이블만 정리합니다. 대안 DDL을 실행하지 않았다면 그 테이블의 DROP 문도 실행하지 않습니다. ```sql DROP TABLE time_sensor; DROP TABLE rail_sensor; DROP TABLE weather_station; DROP TABLE temperature_sensor; DROP TABLE thermo_sensor; DROP TABLE vibration_sensor; DROP TABLE example; DROP TABLE example_lower_only; DROP TABLE example_alter_limits; DROP TABLE example_alter_upper; DROP TABLE t1; DROP TABLE t_bin; DROP TABLE t_limit; DROP TABLE t_dst; DROP TABLE t_src; ``` --- title: "5.3 생성, 변경, 삭제" url: https://docs.machbase.com/kr/dbms/tag-table-usage/create-alter-drop/ language: kr kind: page --- # 5.3 생성, 변경, 삭제 TAG 테이블은 태그 식별자와 하나의 축 컬럼을 필수로 가집니다. 이 페이지에서는 실행 가능한 기본 예제를 제공하고, 전체 옵션은 SQL 레퍼런스로 연결합니다. ## TAG 테이블 생성 TAG 테이블은 앞의 두 컬럼이 고정된 역할을 가집니다. 이름 컬럼과 축 컬럼은 생략할 수 없고, 순서를 바꾸거나 다른 위치에 지정하면 생성에 실패합니다. | 컬럼 위치 | 용도와 특성 | | --- | --- | | 첫 번째 | 태그 이름입니다. `VARCHAR` 컬럼에 `PRIMARY KEY`를 지정하며 다른 타입은 사용할 수 없습니다. 센서·설비·검사 회차처럼 반복해서 관측할 대상을 식별하고, 같은 태그 이름으로 여러 DATA 행을 입력할 수 있으므로 관계형 테이블의 행별 고유 키와 다르게 이해합니다. | | 두 번째 | 관측값을 시간 또는 거리·위치 기준으로 정렬·조회합니다. 시간축은 `DATETIME BASETIME`, 거리축은 `DOUBLE`, `LONG`, `ULONG` 중 하나에 `BASEDISTANCE`를 지정합니다. 한 TAG 테이블에는 시간축 또는 거리축 중 하나만 정의할 수 있습니다. | | 세 번째 이후 | 온도, 압력, 상태, 품질 코드처럼 관측마다 달라지는 DATA 컬럼입니다. 숫자형, `VARCHAR`, `DATETIME`, JSON, 숫자 ARRAY, BINARY를 사용할 수 있습니다. 여러 DATA 컬럼 중 그 태그를 대표하는 값 하나를 정해 두면 태그별 값 통계와 자동 ROLLUP의 기준으로 쓸 수 있고, 이때 `SUMMARIZED`를 지정합니다. 선택 사항이며 세 번째 컬럼에만 지정할 수 있습니다. | ARRAY는 이름 컬럼, 축 컬럼과 `SUMMARIZED` 컬럼에는 사용할 수 없고 그 밖의 DATA 컬럼에만 사용합니다. `TEXT`, `CLOB`, `BLOB`은 LOG 테이블과 달리 TAG 테이블의 DATA 컬럼에 사용할 수 없으므로, 긴 문자열은 `VARCHAR`로, 이진 데이터는 `BINARY`로 저장합니다. 타입별 표기와 값 범위는 [데이터 타입 사전](/dbms/reference/sql/types/)을 참고합니다. 태그마다 한 번만 저장하는 속성은 위 컬럼 위치가 아니라 `METADATA` 절에 따로 선언합니다. 아래 시간축 예제의 `location`이 여기에 해당합니다. 다음 두 테이블은 이 페이지의 독립 실습용입니다. 기존 객체가 없는지 확인한 뒤 순서대로 실행합니다. 시간축과 거리축 중 데이터의 실제 의미에 맞는 축을 선택합니다. 거리축 TAG에는 ROLLUP을 사용할 수 없습니다. ### 시간축 TAG ```sql CREATE TAG TABLE ch5_tag_ddl ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ) METADATA ( location VARCHAR(64) ); ``` `SUMMARIZED`를 지정하면 두 가지가 따라옵니다. 첫째, 태그별 STAT 뷰에 `MIN_VALUE`, `MAX_VALUE`처럼 값 자체의 통계가 이 컬럼을 기준으로 쌓입니다. `SUMMARIZED` 컬럼이 없으면 STAT에는 행 수와 축 범위만 남고 값 통계는 저장되지 않습니다. 둘째, `WITH ROLLUP` 자동 생성과 JSON 문서 전체 ROLLUP이 이 컬럼을 대상으로 삼습니다. 반대로 일반 숫자 ROLLUP은 `CREATE ROLLUP ... ON table(column)`에서 컬럼을 직접 지정하므로 `SUMMARIZED`가 없어도 만들 수 있습니다. 값 통계가 필요 없고 ROLLUP도 직접 만들 계획이면 지정하지 않아도 됩니다. 지정할 수 있는 타입은 지원 숫자 타입과 JSON이며, ROLLUP 생성 조건은 [6장](../../tag-rollup-usage/)을 참고합니다. 위치·단위처럼 태그마다 한 번 저장하는 속성은 `METADATA`, 매 측정마다 달라지는 값은 일반 데이터 컬럼에 정의합니다. ### 거리축 TAG ```sql CREATE TAG TABLE ch5_distance_ddl ( name VARCHAR(32) PRIMARY KEY, distance DOUBLE BASEDISTANCE, value DOUBLE ); ``` 소수 거리값에는 `DOUBLE`, 정수 축에는 값 범위에 맞는 `LONG` 또는 `ULONG`을 사용합니다. ## TAG 테이블 변경 이 절의 METADATA ADD/DROP 실습은 Standard Edition을 전제로 합니다. TAG 데이터 컬럼의 임의 변경은 제한됩니다. 스키마를 확장해야 한다면 새 테이블로 전환하는 방법을 먼저 검토하십시오. 메타데이터 컬럼은 지원 구문으로 추가하거나 삭제할 수 있습니다. ```sql ALTER TABLE ch5_tag_ddl METADATA ADD COLUMN (team VARCHAR(32)); ALTER TABLE ch5_tag_ddl METADATA ADD COLUMN (limits DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]); ALTER TABLE ch5_tag_ddl METADATA DROP COLUMN (team); ALTER TABLE ch5_tag_ddl METADATA DROP COLUMN (limits); ``` Standard Edition에서는 TAG METADATA에 고정 길이 숫자 ARRAY 컬럼을 추가할 수 있습니다. ALTER 전에 존재한 metadata row에는 명시한 ARRAY DEFAULT를 적용합니다. ALTER 뒤 TAG DATA 입력으로 자동 등록되는 metadata row에는 DEFAULT를 다시 적용하지 않으며 새 ARRAY 컬럼은 whole NULL입니다. TAG DATA의 일반 ARRAY 컬럼은 `CREATE TABLE`에서 선언할 수 있지만 ALTER로 추가할 수 없습니다. TAG METADATA ARRAY에는 자동 index를 만들지 않으며 명시적 index도 지원하지 않습니다. 자세한 규칙은 [TAG 메타데이터](../tag-metadata/)와 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. 데이터가 있는 운영 테이블에서는 변경 전에 의존 쿼리, SDK 컬럼 순서와 재입력 경로를 확인합니다. ALTER 후 `DESC ch5_tag_ddl;`과 METADATA 조회로 변경 결과를 확인합니다. ## TAG 테이블 삭제 `DROP TABLE`은 원시 데이터와 메타데이터를 함께 제거합니다. ROLLUP 등 의존 객체가 있으면 먼저 의존 순서에 따라 정리해야 합니다. TAG의 `DROP TABLE ... CASCADE`는 연결된 ROLLUP도 제거할 수 있으므로 단순 정리의 기본 명령으로 사용하지 않습니다. Custom ROLLUP의 대상 테이블은 별도 의존 제약도 확인합니다. ```sql DROP TABLE ch5_distance_ddl; DROP TABLE ch5_tag_ddl; ``` 정확한 속성, 허용 범위와 DDL은 [DDL 구문 사전](/dbms/reference/sql/syntax/ddl-syntax/)을 참고하십시오. 다음으로 읽을 내용: - [TAG 테이블 구조와 스키마](../table-structure-schema/) - [TAG 데이터 입력과 변경](../data-input-mutation/) - [TAG 메타데이터](../tag-metadata/) --- title: "5.4 데이터 입력과 변경" url: https://docs.machbase.com/kr/dbms/tag-table-usage/data-input-mutation/ language: kr kind: page --- # 5.4 데이터 입력과 변경 TAG 데이터는 SQL `INSERT`, Append API 또는 파일 적재 도구로 입력합니다. SQL 예제는 기능 확인과 소량 입력에 사용하고, 지속적인 수집은 Append API를 우선 검토합니다. ## SQL INSERT 다음 예제는 시간축과 거리축 TAG를 각각 만들고 데이터를 확인한 뒤 정리합니다. 실습 이름이 기존 테이블과 겹치지 않는지 먼저 확인합니다. 시간축 이름은 센서, 거리축 이름은 한 검사 대상·회차를 식별합니다. ```sql CREATE TAG TABLE ch5_input_time ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); INSERT INTO ch5_input_time VALUES ('TEMP_001', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 25.5); INSERT INTO ch5_input_time VALUES ('TEMP_001', TO_DATE('2026-01-01 10:01:00', 'YYYY-MM-DD HH24:MI:SS'), 25.7); CREATE TAG TABLE ch5_input_distance ( name VARCHAR(32) PRIMARY KEY, distance DOUBLE BASEDISTANCE, value DOUBLE, quality INTEGER ); INSERT INTO ch5_input_distance VALUES ('PIPE_A', 0.0, 10.1, 100); INSERT INTO ch5_input_distance VALUES ('PIPE_A', 500.5, 11.2, 100); EXEC TABLE_FLUSH(ch5_input_time); EXEC TABLE_FLUSH(ch5_input_distance); SELECT name, time, value FROM ch5_input_time ORDER BY time; SELECT name, distance, value, quality FROM ch5_input_distance ORDER BY distance; SELECT COUNT(*) FROM ch5_input_time; SELECT COUNT(*) FROM ch5_input_distance; DROP TABLE ch5_input_distance; DROP TABLE ch5_input_time; ``` 두 테이블은 각각 두 행을 반환합니다. 시간축 값은 25.5, 25.7이고 거리축은 0.0, 500.5입니다. 태그를 미리 등록하지 않았으므로 첫 DATA 입력에서 해당 이름이 자동 등록됩니다. 실제 발생 시각과 재전송 여부는 애플리케이션에서 관리합니다. `TABLE_FLUSH`는 pending storage/input buffer를 명시적으로 flush해야 하는 검증·운영 절차에 사용합니다. transaction commit이나 조회 가시성 보장 수단은 아니며, 일반 수집 루프에서 행마다 실행하지 마십시오. 인자와 오류 계약은 [EXEC procedure 정본](/dbms/reference/sql/syntax/execute-procedure-syntax/#table-flush)을 참고합니다. ## 메타데이터와 함께 입력 사용자 메타데이터가 있는 TAG도 DATA만 입력해 새 태그를 자동 등록할 수 있습니다. 위치·단위 같은 값을 먼저 정해야 하면 `INSERT ... METADATA`로 등록한 뒤 DATA를 입력합니다. 데이터와 메타데이터를 함께 전달하는 지원 구문도 있습니다. 이미 등록된 속성을 일반 DATA 입력마다 갱신하는 것으로 가정하지 마십시오. 시스템 관리 컬럼은 입력 목록에 포함하지 않습니다. 메타데이터 값의 등록·갱신·삭제는 [TAG 메타데이터](../tag-metadata/)를 참고하십시오. ## 입력 경로 선택 | 경로 | 적합한 경우 | 확인할 사항 | | --- | --- | --- | | SQL `INSERT` | 기능 확인, 저빈도 입력 | 문장별 파싱·왕복 비용 | | SDK Append | 지속적인 고처리량 입력 | 배치 크기, flush와 오류 처리 | | `csvimport` / `machloader` | 클라이언트 파일 일괄 적재 | 컬럼 순서, 날짜 형식, bad 파일 | | `LOAD DATA INFILE` | 서버가 접근할 수 있는 파일 | 서버 경로·권한, 오류 정책 | SDK별 연결과 Append 예제는 [개발 도구 연동](/dbms/development-tools-integration/)을, 파일 형식과 명령은 [데이터 입력·적재·반출](/dbms/development-tools-integration/data-input-load-export/)을 참고하십시오. ## 데이터 정정 TAG data UPDATE는 Standard Edition에서 태그 선택 조건과 BASETIME 범위를 함께 지정해 실행합니다. 태그명, 축과 메타데이터 컬럼은 일반 data UPDATE 대상으로 사용하지 않습니다. 정정 후 이미 계산된 ROLLUP은 자동으로 바뀌지 않으므로, ROLLUP이 있다면 대상 범위를 명시적으로 재구성합니다. 재구성 절차는 [ROLLUP_REBUILD](../../tag-rollup-usage/rollup-rebuild/)를 참고합니다. 성공 응답·실패 행 수와 재시도 정책을 선택한 입력 API에서 확인합니다. SQL의 NULL, SDK의 NULL 표현과 숫자 0을 구분하고, 같은 관측을 재전송할 때의 중복 정책도 정합니다. 숫자 ARRAY·희소 입력은 [ARRAY Append 예제](../../development-tools-integration/data-input-load-export/array-append/)를 사용합니다. 자세한 절차는 [TAG 데이터 정정](../tag-data-update-correction/)을 참고하십시오. --- title: "5.5 조회와 분석" url: https://docs.machbase.com/kr/dbms/tag-table-usage/query-analysis/ language: kr kind: page --- # 5.5 조회와 분석 TAG 테이블에서 시계열 데이터를 조회하는 주요 패턴을 다룹니다. 시간축·거리축 범위 조회, 다중 태그 검색과 통계 뷰 활용을 포함합니다. ## Tag 데이터 조회 ### 샘플 스키마 (시간축) 다음 예제는 TAG 테이블에 두 태그를 등록하고 태그마다 10개 행을 입력합니다. `TAG_0001`은 2018년 1월 1일부터 10일까지, `TAG_0002`는 2월 1일부터 10일까지의 데이터를 사용합니다. ```sql create tag table TAG (name varchar(20) primary key, time datetime basetime, value double summarized); insert into tag metadata values ('TAG_0001'); insert into tag metadata values ('TAG_0002'); insert into tag values('TAG_0001', '2018-01-01 01:00:00 000:000:000', 1); insert into tag values('TAG_0001', '2018-01-02 02:00:00 000:000:000', 2); insert into tag values('TAG_0001', '2018-01-03 03:00:00 000:000:000', 3); insert into tag values('TAG_0001', '2018-01-04 04:00:00 000:000:000', 4); insert into tag values('TAG_0001', '2018-01-05 05:00:00 000:000:000', 5); insert into tag values('TAG_0001', '2018-01-06 06:00:00 000:000:000', 6); insert into tag values('TAG_0001', '2018-01-07 07:00:00 000:000:000', 7); insert into tag values('TAG_0001', '2018-01-08 08:00:00 000:000:000', 8); insert into tag values('TAG_0001', '2018-01-09 09:00:00 000:000:000', 9); insert into tag values('TAG_0001', '2018-01-10 10:00:00 000:000:000', 10); insert into tag values('TAG_0002', '2018-02-01 01:00:00 000:000:000', 11); insert into tag values('TAG_0002', '2018-02-02 02:00:00 000:000:000', 12); insert into tag values('TAG_0002', '2018-02-03 03:00:00 000:000:000', 13); insert into tag values('TAG_0002', '2018-02-04 04:00:00 000:000:000', 14); insert into tag values('TAG_0002', '2018-02-05 05:00:00 000:000:000', 15); insert into tag values('TAG_0002', '2018-02-06 06:00:00 000:000:000', 16); insert into tag values('TAG_0002', '2018-02-07 07:00:00 000:000:000', 17); insert into tag values('TAG_0002', '2018-02-08 08:00:00 000:000:000', 18); insert into tag values('TAG_0002', '2018-02-09 09:00:00 000:000:000', 19); insert into tag values('TAG_0002', '2018-02-10 10:00:00 000:000:000', 20); exec table_flush(tag); ``` 예제 마지막의 `TABLE_FLUSH`는 저장 버퍼를 명시적으로 처리하는 절차입니다. 트랜잭션 커밋이나 조회 가시성을 보장하는 명령으로 해석하지 않습니다. 자세한 동작은 [TABLE_FLUSH](/dbms/reference/sql/syntax/execute-procedure-syntax/#table-flush)를 참고하십시오. ### 모든 TAG 데이터 추출 ```sql select * from tag ORDER BY name, time; ``` ```text NAME TIME VALUE -------------------------------------------------------------------------------------- TAG_0001 2018-01-01 01:00:00 000:000:000 1 TAG_0001 2018-01-02 02:00:00 000:000:000 2 TAG_0001 2018-01-03 03:00:00 000:000:000 3 TAG_0001 2018-01-04 04:00:00 000:000:000 4 TAG_0001 2018-01-05 05:00:00 000:000:000 5 TAG_0001 2018-01-06 06:00:00 000:000:000 6 TAG_0001 2018-01-07 07:00:00 000:000:000 7 TAG_0001 2018-01-08 08:00:00 000:000:000 8 TAG_0001 2018-01-09 09:00:00 000:000:000 9 TAG_0001 2018-01-10 10:00:00 000:000:000 10 TAG_0002 2018-02-01 01:00:00 000:000:000 11 TAG_0002 2018-02-02 02:00:00 000:000:000 12 TAG_0002 2018-02-03 03:00:00 000:000:000 13 TAG_0002 2018-02-04 04:00:00 000:000:000 14 TAG_0002 2018-02-05 05:00:00 000:000:000 15 TAG_0002 2018-02-06 06:00:00 000:000:000 16 TAG_0002 2018-02-07 07:00:00 000:000:000 17 TAG_0002 2018-02-08 08:00:00 000:000:000 18 TAG_0002 2018-02-09 09:00:00 000:000:000 19 TAG_0002 2018-02-10 10:00:00 000:000:000 20 [20] row(s) selected. ``` 위 출력은 예제 실행 결과입니다. 결과 순서를 보장해야 하면 `ORDER BY name, time`을 명시합니다. 조건 없는 조회의 출력 순서는 실행 계획과 스캔 방향에 의존할 수 있습니다. ### 특정 tag 이름으로 데이터 추출 TAG 이름이 TAG_0002인 데이터를 조회하는 예제입니다. ```sql select * from tag where name='TAG_0002' ORDER BY name, time; ``` ```text NAME TIME VALUE -------------------------------------------------------------------------------------- TAG_0002 2018-02-01 01:00:00 000:000:000 11 TAG_0002 2018-02-02 02:00:00 000:000:000 12 TAG_0002 2018-02-03 03:00:00 000:000:000 13 TAG_0002 2018-02-04 04:00:00 000:000:000 14 TAG_0002 2018-02-05 05:00:00 000:000:000 15 TAG_0002 2018-02-06 06:00:00 000:000:000 16 TAG_0002 2018-02-07 07:00:00 000:000:000 17 TAG_0002 2018-02-08 08:00:00 000:000:000 18 TAG_0002 2018-02-09 09:00:00 000:000:000 19 TAG_0002 2018-02-10 10:00:00 000:000:000 20 [10] row(s) selected. ``` ### 시간 범위 조회 TAG_0002에 대해 시간 범위를 지정해 데이터를 조회하는 예제입니다. > `BETWEEN`은 양쪽 경계를 모두 포함하며 `>=`와 `<=`를 함께 쓴 조건과 같습니다. > 아래 예제는 경계 시각에 데이터가 없어서 `>`·`<` 조건도 같은 결과를 반환합니다. > 연속된 조회 구간이 경계 행을 중복해서 읽지 않도록 하려면 `time >= 시작 AND time < 종료`를 > 사용합니다. ```sql select * from tag where name = 'TAG_0002' and time between to_date('2018-02-01') and to_date('2018-02-05') ORDER BY name, time; ``` ```text NAME TIME VALUE -------------------------------------------------------------------------------------- TAG_0002 2018-02-01 01:00:00 000:000:000 11 TAG_0002 2018-02-02 02:00:00 000:000:000 12 TAG_0002 2018-02-03 03:00:00 000:000:000 13 TAG_0002 2018-02-04 04:00:00 000:000:000 14 [4] row(s) selected. ``` ```sql select * from tag where name = 'TAG_0002' and time > to_date('2018-02-01') and time < to_date('2018-02-05') ORDER BY name, time; ``` ```text NAME TIME VALUE -------------------------------------------------------------------------------------- TAG_0002 2018-02-01 01:00:00 000:000:000 11 TAG_0002 2018-02-02 02:00:00 000:000:000 12 TAG_0002 2018-02-03 03:00:00 000:000:000 13 TAG_0002 2018-02-04 04:00:00 000:000:000 14 [4] row(s) selected. ``` ### 거리축 샘플 스키마 거리축(`BASE DISTANCE`) Tag 테이블 기준의 예제입니다. ```sql CREATE TAG TABLE trip_tag ( name VARCHAR(20) PRIMARY KEY, distance_m DOUBLE BASE DISTANCE, value DOUBLE, quality INTEGER ); INSERT INTO trip_tag VALUES('ODO_A', 0, 10.1, 100); INSERT INTO trip_tag VALUES('ODO_A', 500, 11.2, 101); INSERT INTO trip_tag VALUES('ODO_A', 1000, 12.3, 102); INSERT INTO trip_tag VALUES('ODO_B', 1000.1, 21.5, 100); INSERT INTO trip_tag VALUES('ODO_B', 1500, 22.1, 101); INSERT INTO trip_tag VALUES('ODO_B', 2000, 22.9, 102); EXEC TABLE_FLUSH(trip_tag); ``` ### 거리 구간 조회 ```sql SELECT name, distance_m, value, quality FROM trip_tag WHERE name = 'ODO_A' AND distance_m BETWEEN 0 AND 1000 ORDER BY distance_m; ``` 시간축과 마찬가지로 거리축도 축 범위를 좁히는 것이 기본 조회 패턴입니다. ### DOUBLE 거리축의 소수 경계 조회 ```sql SELECT name, distance_m, value, quality FROM trip_tag WHERE name = 'ODO_B' AND distance_m BETWEEN 1000.1 AND 2000 ORDER BY distance_m; ``` 숫자값 그대로 비교하므로 `1000`은 제외되고 `1500`, `2000`은 포함됩니다. ### 거리축 실행 계획 확인 대용량 거리축 조회에서는 `EXPLAIN`으로 거리 조건이 key range로 들어가는지 확인합니다. ```sql EXPLAIN SELECT name, distance_m, value FROM trip_tag WHERE name = 'ODO_B' AND distance_m BETWEEN 1000.1 AND 2000 ORDER BY distance_m; ``` 확인할 포인트: - `KEYVALUE INDEX SCAN` 또는 유사한 인덱스 스캔이 보이는지 - `KEY RANGE` 아래에 `distance_m between ...` 조건이 보이는지 ### 거리 버킷 집계 아래 예제는 0 이상인 거리를 500 단위 구간으로 나눕니다. TRUNC는 0 방향으로 자르므로 음수 좌표의 구간이 필요하면 FLOOR 등 원하는 경계 규칙을 따로 선택합니다. ```sql SELECT TRUNC(distance_m / 500, 0) * 500 AS dist_bucket, COUNT(*) AS sample_count, MIN(value) AS min_v, MAX(value) AS max_v, AVG(value) AS avg_v FROM trip_tag WHERE name = 'ODO_B' GROUP BY TRUNC(distance_m / 500, 0) * 500 ORDER BY dist_bucket; ``` 예를 들어 `1750`은 `1500` 버킷에 집계됩니다. ### 다중 tag에 대한 시간 범위 검색 두 개 이상의 태그에 같은 시간 범위를 적용하는 예제입니다. 대상 이름 목록이 정해져 있으면 `IN`으로 표현합니다. 대상 태그가 많을 때의 성능은 목록 크기와 시간 범위를 함께 측정합니다. ```sql select * from tag where name in ('TAG_0002', 'TAG_0001') and time between to_date('2018-01-05') and to_date('2018-02-05') ORDER BY name, time; ``` ```text NAME TIME VALUE -------------------------------------------------------------------------------------- TAG_0001 2018-01-05 05:00:00 000:000:000 5 TAG_0001 2018-01-06 06:00:00 000:000:000 6 TAG_0001 2018-01-07 07:00:00 000:000:000 7 TAG_0001 2018-01-08 08:00:00 000:000:000 8 TAG_0001 2018-01-09 09:00:00 000:000:000 9 TAG_0001 2018-01-10 10:00:00 000:000:000 10 TAG_0002 2018-02-01 01:00:00 000:000:000 11 TAG_0002 2018-02-02 02:00:00 000:000:000 12 TAG_0002 2018-02-03 03:00:00 000:000:000 13 TAG_0002 2018-02-04 04:00:00 000:000:000 14 [10] row(s) selected. ``` ### 특정 값 이상의 데이터 검색 tag 값에 대한 조건도 지정할 수 있습니다. TAG_0002의 값 중 12보다 크고 15보다 작은 값에 대해 필터링한 결과입니다. ```sql select * from tag where name = 'TAG_0002' and value > 12 and value < 15 and time between to_date('2018-02-01') and to_date('2018-02-05') ORDER BY name, time; ``` ```text NAME TIME VALUE -------------------------------------------------------------------------------------- TAG_0002 2018-02-03 03:00:00 000:000:000 13 TAG_0002 2018-02-04 04:00:00 000:000:000 14 [2] row(s) selected. ``` ### TAG별 통계 뷰 `V$
_STAT` tag 테이블을 생성하면, tag ID별 통계 정보를 집계하는 가상 테이블이 자동으로 만들어집니다. 이 가상 테이블의 이름은 v${tag 테이블 이름}_stat입니다. 태그명·축 관련 통계와 세 번째 SUMMARIZED 컬럼의 값 통계를 구분합니다. STAT는 백그라운드 인덱스·통계 처리 상태를 반영하므로 직전 입력의 검증은 원본 SELECT와 함께 수행합니다. 즉시 통계 확인이 필요한 실습에서는 TABLE_FLUSH 후 INDEX_FLUSH로 처리를 기다립니다. BASE DISTANCE 축별 STAT 스키마는 Machbase 8.7.0부터 지원 축 관련 컬럼 이름과 타입은 TAG 테이블의 축에 따라 달라집니다. | TAG 축 | 최소/최대 축 | 최소/최대값 발생 축 | 최근 입력 row 축 | 축 통계 타입 | |--------|--------------|----------------------|------------------|--------------| | `DATETIME BASE TIME` | `MIN_TIME`, `MAX_TIME` | `MIN_VALUE_TIME`, `MAX_VALUE_TIME` | `RECENT_ROW_TIME` | `DATETIME` | | `DOUBLE/LONG/ULONG BASE DISTANCE` | `MIN_DISTANCE`, `MAX_DISTANCE` | `MIN_VALUE_DISTANCE`, `MAX_VALUE_DISTANCE` | `RECENT_ROW_DISTANCE` | 원본 BASE DISTANCE 타입 | 두 Edition의 공통 컬럼은 `NAME`, `ROW_COUNT`, `MIN_VALUE`, `MAX_VALUE`입니다. Cluster Edition에서는 스키마 맨 앞에 `HOSTNAME VARCHAR(64)`가 추가됩니다. #### BASE TIME STAT 스키마 ```sql DESC v$tag_stat; ``` ```text [ COLUMN ] ---------------------------------------------------------------------------------------------------- NAME NULL? TYPE LENGTH ---------------------------------------------------------------------------------------------------- NAME varchar 100 ROW_COUNT ulong 20 MIN_TIME datetime 31 MAX_TIME datetime 31 MIN_VALUE double 17 MIN_VALUE_TIME datetime 31 MAX_VALUE double 17 MAX_VALUE_TIME datetime 31 RECENT_ROW_TIME datetime 31 ``` 세 번째 컬럼에 SUMMARIZED 키워드가 없으면 VALUE 관련 정보(MIN_VALUE, MAX_VALUE, MIN_VALUE_TIME, MAX_VALUE_TIME)는 저장되지 않습니다. 수집되는 통계 정보는 다음과 같습니다. |컬럼 이름|정보| |--|--| |NAME|Tag ID의 이름| |ROW_COUNT|행 수| |MIN_TIME|해당 tag ID 행 중 가장 작은 basetime 컬럼 값| |MAX_TIME|해당 tag ID 행 중 가장 큰 basetime 컬럼 값| |MIN_VALUE|해당 tag ID 행 중 가장 작은 summarized 컬럼 값| |MIN_VALUE_TIME|MIN_VALUE와 함께 삽입된 basetime 컬럼 값| |MAX_VALUE|해당 tag ID 행 중 가장 큰 summarized 컬럼 값| |MAX_VALUE_TIME|MAX_VALUE와 함께 삽입된 basetime 컬럼 값| |RECENT_ROW_TIME|가장 최근에 삽입된 basetime 컬럼 값| 다음 통계 실습은 앞의 20행 조회 예제와 분리한 테이블을 사용합니다. 따라서 아래 두 태그만 조회되며, 앞의 TAG_0001·TAG_0002가 통계 예상 결과에 섞이지 않습니다. 1. SUMMARIZED 컬럼이 존재하는 경우 ```sql CREATE TAG TABLE ch5_stat_time (name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED); ``` ```sql INSERT INTO ch5_stat_time VALUES('tag-0', TO_DATE('2021-08-12'), 10); INSERT INTO ch5_stat_time VALUES('tag-0', TO_DATE('2021-08-13'), 10); INSERT INTO ch5_stat_time VALUES('tag-0', TO_DATE('2021-08-14'), 20); INSERT INTO ch5_stat_time VALUES('tag-0', TO_DATE('2021-08-11'), 5); INSERT INTO ch5_stat_time VALUES('tag-1', TO_DATE('2022-08-12'), 100); INSERT INTO ch5_stat_time VALUES('tag-1', TO_DATE('2022-08-11'), 200); INSERT INTO ch5_stat_time VALUES('tag-1', TO_DATE('2022-08-10'), 50); ``` ```sql EXEC TABLE_FLUSH(ch5_stat_time); EXEC INDEX_FLUSH(ch5_stat_time); SELECT * FROM v$ch5_stat_time_stat ORDER BY name; ``` ```text NAME ROW_COUNT MIN_TIME MAX_TIME MIN_VALUE --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- MIN_VALUE_TIME MAX_VALUE MAX_VALUE_TIME RECENT_ROW_TIME --------------------------------------------------------------------------------------------------------------------------------- tag-0 4 2021-08-11 00:00:00 000:000:000 2021-08-14 00:00:00 000:000:000 5 2021-08-11 00:00:00 000:000:000 20 2021-08-14 00:00:00 000:000:000 2021-08-11 00:00:00 000:000:000 tag-1 3 2022-08-10 00:00:00 000:000:000 2022-08-12 00:00:00 000:000:000 50 2022-08-10 00:00:00 000:000:000 200 2022-08-11 00:00:00 000:000:000 2022-08-10 00:00:00 000:000:000 [2] row(s) selected. ``` 2. SUMMARIZED 컬럼이 존재하지 않는 경우 ```sql CREATE TAG TABLE other_tag (name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE); ``` ```text Executed successfully. ``` ```sql INSERT INTO other_tag VALUES('tag-0', TO_DATE('2021-08-12'), 10); INSERT INTO other_tag VALUES('tag-0', TO_DATE('2021-08-13'), 10); INSERT INTO other_tag VALUES('tag-0', TO_DATE('2021-08-14'), 20); INSERT INTO other_tag VALUES('tag-0', TO_DATE('2021-08-11'), 5); INSERT INTO other_tag VALUES('tag-1', TO_DATE('2022-08-12'), 100); INSERT INTO other_tag VALUES('tag-1', TO_DATE('2022-08-11'), 200); INSERT INTO other_tag VALUES('tag-1', TO_DATE('2022-08-10'), 50); ``` ```sql EXEC TABLE_FLUSH(other_tag); EXEC INDEX_FLUSH(other_tag); SELECT * FROM v$other_tag_stat ORDER BY name; ``` ```text NAME ROW_COUNT MIN_TIME MAX_TIME MIN_VALUE --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- MIN_VALUE_TIME MAX_VALUE MAX_VALUE_TIME RECENT_ROW_TIME --------------------------------------------------------------------------------------------------------------------------------- tag-0 4 2021-08-11 00:00:00 000:000:000 2021-08-14 00:00:00 000:000:000 NULL NULL NULL NULL 2021-08-11 00:00:00 000:000:000 tag-1 3 2022-08-10 00:00:00 000:000:000 2022-08-12 00:00:00 000:000:000 NULL NULL NULL NULL 2022-08-10 00:00:00 000:000:000 [2] row(s) selected. ``` #### BASE DISTANCE STAT 스키마 거리축 TAG 테이블의 통계 뷰는 거리값을 숫자 타입으로 제공합니다. ```sql CREATE TAG TABLE distance_sensor ( name VARCHAR(32) PRIMARY KEY, odometer_m DOUBLE BASE DISTANCE, value DOUBLE SUMMARIZED ); INSERT INTO distance_sensor VALUES('sensor', 20.5, 8); INSERT INTO distance_sensor VALUES('sensor', 10.25, 3); INSERT INTO distance_sensor VALUES('sensor', 30.75, 5); EXEC TABLE_FLUSH(distance_sensor); EXEC INDEX_FLUSH(distance_sensor); ``` Standard Edition에서 `DOUBLE BASE DISTANCE` 테이블의 스키마는 다음과 같습니다. ```sql DESC V$DISTANCE_SENSOR_STAT; ``` ```text [ COLUMN ] ---------------------------------------------------------------------------------------------------- NAME NULL? TYPE LENGTH ---------------------------------------------------------------------------------------------------- NAME varchar 100 ROW_COUNT ulong 20 MIN_DISTANCE double 17 MAX_DISTANCE double 17 MIN_VALUE double 17 MIN_VALUE_DISTANCE double 17 MAX_VALUE double 17 MAX_VALUE_DISTANCE double 17 RECENT_ROW_DISTANCE double 17 ``` 거리축 통계 컬럼 다섯 개의 타입은 원본 BASE DISTANCE 컬럼 타입을 따릅니다. | BASE DISTANCE 타입 | STAT 컬럼 타입 | `DESC` 길이 | |--------------------|----------------|-------------| | `DOUBLE` | `double` | 17 | | `LONG` | `long` | 20 | | `ULONG` | `ulong` | 20 | ```sql SELECT name, row_count, min_distance, max_distance, min_value, min_value_distance, max_value, max_value_distance, recent_row_distance FROM V$DISTANCE_SENSOR_STAT WHERE name = 'sensor'; ``` - `MIN_DISTANCE`와 `MAX_DISTANCE`는 해당 통계 row의 최소·최대 거리입니다. - `MIN_VALUE_DISTANCE`와 `MAX_VALUE_DISTANCE`는 각각 최소·최대 summarized value가 발생한 거리입니다. - `RECENT_ROW_DISTANCE`는 가장 큰 거리가 아니라 가장 최근에 입력된 row의 거리입니다. - `SUMMARIZED` 컬럼이 없으면 `MIN_VALUE_DISTANCE`와 `MAX_VALUE_DISTANCE`는 `NULL`입니다. ##### Cluster Edition에서 조회 Cluster Edition의 통계 뷰에는 `HOSTNAME`이 추가되고 warehouse별 통계 row가 반환될 수 있습니다. 먼저 warehouse별 값을 확인합니다. ```sql SELECT hostname, name, row_count, min_distance, max_distance, min_value, min_value_distance, max_value, max_value_distance, recent_row_distance FROM V$DISTANCE_SENSOR_STAT ORDER BY hostname, name; ``` row 수와 거리 경계는 tag 이름으로 안전하게 집계할 수 있습니다. ```sql SELECT name, SUM(row_count) AS row_count, MIN(min_distance) AS min_distance, MAX(max_distance) AS max_distance FROM V$DISTANCE_SENSOR_STAT GROUP BY name; ``` {{< callout type="warning" >}} `MIN_VALUE`와 `MIN_VALUE_DISTANCE`, `MAX_VALUE`와 `MAX_VALUE_DISTANCE`는 같은 warehouse row의 짝을 유지해야 합니다. 두 컬럼을 각각 독립적으로 `MIN` 또는 `MAX`하면 서로 다른 warehouse의 값이 결합될 수 있습니다. `RECENT_ROW_DISTANCE`도 warehouse별 최근 입력 거리이므로 `MAX(RECENT_ROW_DISTANCE)`를 전체 cluster의 최근 입력 row로 해석하지 않습니다. {{< /callout >}} ##### 8.7.0 호환성 BASE DISTANCE 통계 뷰의 기존 이름은 alias로 제공되지 않습니다. 기존 테이블도 8.7.0 서버가 재시작되면 새 스키마로 구성됩니다. | 8.7.0 이전 이름 | 8.7.0 이름 | |-----------------|------------| | `MIN_TIME` | `MIN_DISTANCE` | | `MAX_TIME` | `MAX_DISTANCE` | | `MIN_VALUE_TIME` | `MIN_VALUE_DISTANCE` | | `MAX_VALUE_TIME` | `MAX_VALUE_DISTANCE` | | `RECENT_ROW_TIME` | `RECENT_ROW_DISTANCE` | BASE TIME TAG 테이블은 기존 `*_TIME DATETIME` 스키마를 유지합니다. ### scan 방향 hint 축 방향의 순회와 결과 정렬을 구분합니다. 역방향 축 조회에서의 최신값은 가장 큰 축 값이며 마지막에 입력한 행을 나타내는 STAT의 RECENT_ROW 값과 다를 수 있습니다. 같은 축 값의 여러 행까지 순서를 구분해야 하면 애플리케이션의 추가 기준이 필요합니다. ```sql SELECT * FROM tag WHERE name = 'TAG_0001' ORDER BY time LIMIT 10; SELECT /*+ SCAN_FORWARD(tag) */ name, time, value FROM tag WHERE name = 'TAG_0001' LIMIT 10; SELECT /*+ SCAN_BACKWARD(tag) */ name, time, value FROM tag WHERE name = 'TAG_0001' LIMIT 10; ``` hint가 없을 때의 기본 방향은 [TABLE_SCAN_DIRECTION](/dbms/reference/configuration/configuration/)을 참고합니다. ## 정리 ```sql DROP TABLE ch5_stat_time; DROP TABLE distance_sensor; DROP TABLE trip_tag; DROP TABLE other_tag; DROP TABLE tag; ``` --- title: "5.6 인덱스와 성능" url: https://docs.machbase.com/kr/dbms/tag-table-usage/index-performance/ language: kr kind: page --- # 5.6 인덱스와 성능 TAG 조회는 태그명과 축 범위를 먼저 제한하는 것이 기본입니다. 추가 인덱스는 실제 조회 조건과 실행 계획을 측정한 뒤 선택합니다. ## 기본 조회 경로 TAG 테이블은 `PRIMARY KEY` 태그명과 `BASETIME` 또는 `BASEDISTANCE` 축을 기준으로 조회할 수 있도록 필요한 구조를 자동 관리합니다. 애플리케이션은 생성되는 시스템 객체의 이름이나 저장 단계에 의존하지 마십시오. | 조회 조건 | 조정 방향 | | --- | --- | | 한 태그의 축 범위 | 태그명과 축 범위를 모두 명시 | | 여러 태그의 같은 시간 범위 | 시간 범위를 먼저 제한하고 대상 태그 수를 관리 | | 메타데이터 속성 | TAG `METADATA` 컬럼으로 정의 | | 반복적인 시간 집계 | ROLLUP 검토 | | 값 조건 중심 조회 | 값 컬럼 보조 인덱스를 실행 계획으로 검증 | 태그명이나 축 범위 없이 넓은 데이터를 조회하면 읽어야 할 범위가 커집니다. “항상 빠르다”는 가정 대신 실제 데이터량으로 `EXPLAIN` 결과와 실행 시간을 확인하십시오. ## METADATA 컬럼 설치 위치, 장치 유형처럼 태그마다 한 번 정의하는 속성은 `METADATA` 컬럼으로 설계합니다. 일반 스칼라 METADATA 컬럼에는 검색 인덱스가 자동으로 제공됩니다. 다만 JSON 컬럼 자체에는 자동 인덱스가 없으므로 필요한 JSON 경로에 인덱스를 정의합니다. 숫자 ARRAY 메타데이터 컬럼에는 자동·명시적 인덱스가 모두 지원되지 않습니다. 자세한 규칙은 [TAG 메타데이터](../tag-metadata/)를 참고하십시오. 시계열 값과 자주 바뀌는 상태를 METADATA에 넣으면 갱신 경로와 의미가 불명확해집니다. 값의 성격에 따라 TAG 데이터 컬럼, LOOKUP, VOLATILE 또는 TRANSACTION(Standard Edition 전용) 테이블을 검토하십시오. ## 값 컬럼 보조 인덱스 값 조건을 자주 사용하는 경우 `INDEX_TYPE TAG` secondary index를 검토할 수 있습니다. 추가 인덱스는 입력·저장 비용을 늘리므로 생성 전후의 대표 쿼리를 비교합니다. 다음 예제는 격리된 이름을 사용해 값 및 JSON path 인덱스를 만들고, 실행 계획을 확인한 뒤 모든 객체를 정리합니다. 작은 표본은 문법과 결과 검증용이며 성능 우위를 증명하지 않습니다. 이름이 겹치지 않는 별도 환경에서 실행합니다. ```sql CREATE TAG TABLE ch5_index_tag ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, payload JSON ) METADATA ( location VARCHAR(64) ); INSERT INTO ch5_index_tag METADATA VALUES ('TEMP-01', 'LINE-A'); INSERT INTO ch5_index_tag METADATA VALUES ('TEMP-02', 'LINE-B'); INSERT INTO ch5_index_tag VALUES ('TEMP-01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, '{"state":"normal"}'); INSERT INTO ch5_index_tag VALUES ('TEMP-01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 90.0, '{"state":"alarm"}'); INSERT INTO ch5_index_tag VALUES ('TEMP-02', TO_DATE('2026-01-01 00:02:00', 'YYYY-MM-DD HH24:MI:SS'), 95.0, '{"state":"alarm"}'); CREATE INDEX idx_ch5_index_tag_value ON ch5_index_tag (value) INDEX_TYPE TAG; CREATE INDEX idx_ch5_index_tag_json ON ch5_index_tag (payload->'$.state'); EXPLAIN SELECT name, time, value FROM ch5_index_tag WHERE name = 'TEMP-01' AND time BETWEEN TO_DATE('2026-01-01', 'YYYY-MM-DD') AND TO_DATE('2026-01-02', 'YYYY-MM-DD') AND value > 80.0; SELECT name, value FROM ch5_index_tag WHERE location = 'LINE-A' AND value > 80.0 ORDER BY name, time; SELECT name, value FROM ch5_index_tag WHERE payload->'$.state' = 'alarm' ORDER BY name, time; DROP INDEX idx_ch5_index_tag_json; DROP INDEX idx_ch5_index_tag_value; DROP TABLE ch5_index_tag; ``` 첫 SELECT는 TEMP-01의 90.0 한 행, 두 번째는 TEMP-01의 90.0과 TEMP-02의 95.0을 반환합니다. 인덱스 생성 전후에 같은 조회 결과를 확인하고, 운영 규모의 데이터에서는 조회 시간뿐 아니라 입력 비용과 인덱스 크기도 비교합니다. JSON path 연산자의 반환 타입과 비교 값의 타입을 맞추고, 원하는 경로가 실제로 인덱스를 사용하는지 `EXPLAIN`으로 확인합니다. ## 사용하지 않는 조정 - TAG의 축 컬럼에는 별도 인덱스를 만들지 않습니다. - LOG용 `MINMAX_CACHE_SIZE` 설정을 TAG 값 컬럼에 적용하지 않습니다. - 넓은 기간의 반복 집계를 secondary index만으로 해결하려 하지 말고 ROLLUP을 검토합니다. 정확한 인덱스 구문은 [SQL 문법 사전](/dbms/reference/sql/syntax/)을, 측정과 튜닝 절차는 [쿼리 튜닝](/dbms/performance-tuning/performance-query-tuning/)을 참고하십시오. --- title: "5.7 운영과 데이터 생명주기" url: https://docs.machbase.com/kr/dbms/tag-table-usage/operations-lifecycle/ language: kr kind: page --- # 5.7 운영과 데이터 생명주기 TAG 데이터의 수명 주기는 수동 삭제, Retention Policy와 중복 입력 방지로 관리합니다. 이 페이지는 운영 선택 기준을 설명하며 전체 SQL 문법은 관련 레퍼런스로 연결합니다. ## TAG 데이터 삭제 TAG 데이터는 태그 식별자와 축 조건을 사용해 삭제 범위를 제한합니다. 시간축 TAG의 대표적인 선택은 다음과 같습니다. | 목적 | 조건 | | --- | --- | | 한 태그의 전체 데이터 삭제 | 태그 `PRIMARY KEY` 일치 | | 한 태그의 시간 범위 삭제 | 태그 일치 + BASETIME 범위 | | 모든 태그의 과거 데이터 삭제 | BASETIME 조건 또는 `BEFORE` | | 테이블의 전체 데이터 삭제 | 조건 없는 TAG `DELETE` | 다음 예제는 별도 검증 테이블을 만들고 삭제 범위를 확인한 뒤 정리합니다. ```sql CREATE TAG TABLE ch5_lifecycle ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); INSERT INTO ch5_lifecycle VALUES ('TAG_0001', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1.0); INSERT INTO ch5_lifecycle VALUES ('TAG_0001', TO_DATE('2026-01-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 2.0); INSERT INTO ch5_lifecycle VALUES ('TAG_0002', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 3.0); DELETE FROM ch5_lifecycle WHERE name = 'TAG_0001' AND time < TO_DATE('2026-01-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS'); SELECT name, time, value FROM ch5_lifecycle ORDER BY name, time; DELETE FROM ch5_lifecycle; SELECT COUNT(*) FROM ch5_lifecycle; DROP TABLE ch5_lifecycle; ``` 첫 삭제 후 TAG_0001의 2026-01-02 값 2.0과 TAG_0002의 값 3.0이 남습니다. 전체 삭제 후 COUNT는 0입니다. DATA 삭제와 METADATA 삭제는 별개입니다. 태그 등록 정보까지 제거하려면 [METADATA 삭제](../tag-metadata/) 조건도 확인합니다. 삭제 조건의 연산자와 Edition별 지원 범위는 [TAG DELETE 구문](/dbms/reference/sql/syntax/dml-syntax/)을 참고하십시오. 수동 삭제를 주기적으로 반복해야 한다면 [Retention Policy](/dbms/operations-configuration-recovery/policy-data-retention/)를 사용하십시오. ### ROLLUP 데이터 처리 원시 TAG 데이터의 삭제와 이미 계산된 ROLLUP의 처리는 별개입니다. 원시 데이터를 정정하거나 삭제한 뒤 집계도 바뀌어야 한다면 대상 범위의 ROLLUP을 재구성합니다. ROLLUP 삭제 구문을 보관 정책처럼 반복 실행하지 마십시오. - [ROLLUP 부분 삭제와 재구성](/dbms/tag-rollup-usage/rollup-rebuild/) - [TAG 데이터 정정 후 ROLLUP 재구성](../tag-data-update-correction/) ## 자동 중복 제거 `TAG_DUPLICATE_CHECK_DURATION`은 중복 검사 기간을 분 단위로 설정합니다. Standard Edition에서는 0~43200분을 지정할 수 있으며 0은 비활성화입니다. Cluster Edition에서는 0만 허용하므로 아래 활성화 실습은 Standard Edition 전용입니다. 서버 시각을 기준으로 검사 기간에 해당하는 데이터에서 태그·축·데이터 값이 같은 행을 중복으로 판정합니다. 인덱스 처리와 중복 행 정리 과정에서 수행되므로 Append 성공 응답을 “이미 중복 제거된 행 수”로 해석하지 않습니다. 늦게 도착한 데이터가 검사 기간 밖이면 기대한 중복 제거가 되지 않을 수 있으며 업무 키의 유일성 제약을 대신하지 않습니다. ```sql CREATE TAG TABLE ch5_dedup ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ) TAG_DUPLICATE_CHECK_DURATION = 1440; INSERT INTO ch5_dedup VALUES ('TAG_0001', NOW, 1.0); INSERT INTO ch5_dedup SELECT name, time, value FROM ch5_dedup; EXEC TABLE_FLUSH(ch5_dedup); EXEC INDEX_FLUSH(ch5_dedup); SELECT name, time, value FROM ch5_dedup WHERE name = 'TAG_0001'; ALTER TABLE ch5_dedup SET TAG_DUPLICATE_CHECK_DURATION = 60; DROP TABLE ch5_dedup; ``` 두 번째 입력은 첫 행을 복사하므로 축 시각도 정확히 같습니다. 이 실습은 운영 중 동시 입력이 없는 전제입니다. 저장 버퍼와 인덱스 처리를 기다린 뒤 한 행이 남는지 확인합니다. 일반 수집 루프에서 행마다 두 flush를 호출하는 패턴은 사용하지 않습니다. 전체 속성과 제약은 현재 버전의 [CREATE TAG TABLE 구문](/dbms/reference/sql/syntax/ddl-syntax/)을 기준으로 확인하십시오. 이미 보관 정책으로 삭제된 데이터는 중복 판정 대상에 남아 있지 않습니다. ## 운영 점검 순서 1. 원시 데이터와 ROLLUP의 보관 기간을 각각 정합니다. 2. 늦게 도착하는 데이터의 최대 지연을 측정해 중복 검사 기간을 정합니다. 3. 삭제·정정 전에 대상 태그와 시간 범위를 `SELECT`로 확인합니다. 4. 대량 변경 후 ROLLUP과 대표 조회 결과를 검증합니다. 5. 입력량, 디스크 사용량과 Retention 실행 상태를 함께 모니터링합니다. --- title: "5.8 제약, 오류, 문제 해결" url: https://docs.machbase.com/kr/dbms/tag-table-usage/constraints-errors-troubleshooting/ language: kr kind: page --- # 5.8 제약, 오류, 문제 해결 이 페이지의 UPDATE 실습은 Standard Edition을 전제로 합니다. 먼저 다음 테이블을 만들고, 정상 SQL과 의도적으로 실패하는 SQL을 구분해 실행합니다. 실패 예제를 한꺼번에 정상 스크립트에 넣지 마십시오. 이름이 겹치는 기존 객체를 삭제하지 않습니다. ```sql CREATE TAG TABLE ch5_error_time ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, status INTEGER ) METADATA (location VARCHAR(64)); INSERT INTO ch5_error_time METADATA VALUES ('sensor-01', 'zone-1'); INSERT INTO ch5_error_time METADATA VALUES ('sensor-02', 'zone-2'); INSERT INTO ch5_error_time VALUES ('sensor-01', TO_DATE('2026-07-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 0); INSERT INTO ch5_error_time VALUES ('sensor-02', TO_DATE('2026-07-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 0); CREATE TAG TABLE ch5_error_distance ( name VARCHAR(64) PRIMARY KEY, distance DOUBLE BASEDISTANCE, value DOUBLE SUMMARIZED ); ``` ## TAG data UPDATE WHERE 조건 오류 WHERE 절에 태그 선택 조건과 BASETIME 조건이 모두 있어야 합니다. 조건이 모호하거나 허용되지 않는 형태이면 UPDATE가 거부됩니다. {{< callout type="warning" >}} **필수 조건** `WHERE name ...` 형태의 태그 선택 조건과 `time ...` 형태의 BASETIME 조건을 함께 지정합니다. 조건 없는 전체 UPDATE, 태그 조건만 있는 UPDATE, 시간 조건만 있는 UPDATE는 허용되지 않습니다. {{< /callout >}} ### 증상 다음과 같은 UPDATE가 오류로 거부됩니다. ```sql -- 시간 조건 없음 UPDATE ch5_error_time SET value = 99.9 WHERE name = 'sensor-01'; -- 태그 선택 조건 없음 UPDATE ch5_error_time SET value = 99.9 WHERE time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); -- OR 조건 사용 UPDATE ch5_error_time SET value = 99.9 WHERE name = 'sensor-01' OR name = 'sensor-02'; ``` ### 원인 대상 태그와 시간 범위를 명확하게 제한할 수 없는 조건은 거부됩니다. | 조건 형태 | 지원 여부 | |-----------|:--------:| | `name = 'sensor-01' AND time >= ...` | O | | `name IN ('sensor-01', 'sensor-02') AND time BETWEEN ...` | O | | `name LIKE 'sensor-%' AND time < ...` | O | | `value > 10`만 사용 | X | | `name = 'sensor-01'`만 사용 | X | | `time >= ...`만 사용 | X | | `OR`, 서브쿼리, 집계 조건 | X | ### 해결 방법 태그 선택 조건과 시간 조건을 함께 명시합니다. ```sql UPDATE ch5_error_time SET value = 99.9 WHERE name = 'sensor-01' AND time = TO_DATE('2026-07-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'); UPDATE ch5_error_time SET status = 1 WHERE name IN ('sensor-01', 'sensor-02') AND time >= TO_DATE('2026-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND time < TO_DATE('2026-07-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS'); ``` ### NAME 또는 TIME bind가 `ERR-02190`으로 거부될 때 Machbase 8.7.0부터 Standard Edition에서는 다음과 같이 NAME과 BASETIME 조건 값에 bind parameter를 사용할 수 있습니다. ```sql UPDATE ch5_error_time SET value = ? WHERE name = ? AND time = ?; ``` 태그 선택 조건과 BASETIME 조건이 모두 있는데도 이 문장이 `ERR-02190: Invalid UPDATE/DELETE condition. Specify it as (primary key column) = (value)`로 거부되면 서버 버전을 확인합니다. 구버전 서버는 TAG data UPDATE의 NAME/TIME bind를 지원하지 않습니다. 서버를 8.7.0 이상으로 업그레이드하고, named marker를 사용하면 해당 이름 기반 API를 지원하는 8.7.0 SDK를 함께 사용합니다. `?` 예제는 SDK에서 준비·바인딩할 SQL이며 machsql에 값 없이 그대로 실행할 문장은 아닙니다. 같은 prepared statement를 재사용할 때는 SET, NAME, TIME 값을 모두 다시 바인딩합니다. 자세한 marker 규칙은 [TAG data UPDATE bind](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/#tag-data-update-predicate-bind)를 참고하십시오. 대량 UPDATE 전에는 같은 WHERE 조건으로 `SELECT COUNT(*)`를 실행해 수정 대상 row 수를 확인합니다. ## TAG data UPDATE SET 대상 컬럼 오류 SET 절은 실제 데이터 컬럼만 대상으로 합니다. PRIMARY KEY, BASETIME, 메타데이터 컬럼을 SET 대상으로 지정하면 오류가 발생합니다. {{< callout type="warning" >}} **SET 대상** `value`와 사용자 데이터 컬럼은 UPDATE할 수 있습니다. `name`, `time`, METADATA 블록의 컬럼은 TAG data UPDATE의 SET 대상이 아닙니다. {{< /callout >}} ### 증상 ```sql -- 오류: PRIMARY KEY 컬럼(name) 업데이트 시도 UPDATE ch5_error_time SET name = 'new-sensor' WHERE name = 'old-sensor' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); -- 오류: BASETIME 컬럼(time) 업데이트 시도 UPDATE ch5_error_time SET time = NOW WHERE name = 'sensor-01' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); -- 오류: 메타데이터 컬럼을 data UPDATE에서 수정 UPDATE ch5_error_time SET location = 'zone-2' WHERE name = 'sensor-01' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); ``` ### 컬럼 유형별 UPDATE 가능 여부 | 컬럼 유형 | 설명 | data UPDATE | |-----------|------|:-----------:| | PRIMARY KEY (`name`) | TAG를 식별하는 고유 키 | X | | BASETIME (`time`) | 시계열 데이터의 타임스탬프 | X | | 데이터 컬럼 (`value`, 보조 컬럼) | 실제 row 값 | O | | `SUMMARIZED` 데이터 컬럼 | 통계 대상 데이터 컬럼 | O | | 메타데이터 컬럼 | METADATA 블록의 태그 속성 | X | ### 해결 방법 데이터 값은 일반 UPDATE로 수정합니다. ```sql UPDATE ch5_error_time SET value = 99.9, status = 1 WHERE name = 'sensor-01' AND time = TO_DATE('2026-07-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'); ``` 메타데이터는 별도 구문을 사용합니다. ```sql UPDATE ch5_error_time METADATA SET location = 'zone-2' WHERE name = 'sensor-01'; ``` 태그 이름이나 시간 축을 변경해야 하는 경우에는 새 `name`/`time` 값으로 데이터를 삽입한 뒤 운영 정책에 따라 기존 데이터를 삭제합니다. ## BASE DISTANCE TAG STAT 컬럼 오류 Machbase 8.7.0은 BASE DISTANCE TAG의 `V$
_STAT` 축 컬럼을 거리 이름과 숫자 타입으로 제공합니다. 업그레이드 전 SQL이 `MIN_TIME`, `MAX_TIME`, `RECENT_ROW_TIME` 등을 조회하면 컬럼을 찾을 수 없다는 오류가 발생할 수 있습니다. ### 증상 - 업그레이드 후 BASE DISTANCE 통계 뷰에서 기존 `*_TIME` 컬럼을 조회할 수 없습니다. - 구버전 서버에서는 거리값이 `DATETIME`으로 해석되어 의미 없는 날짜처럼 보일 수 있습니다. - Cluster Edition에서 Standard와 같은 ordinal result mapping을 사용하면 선두의 `HOSTNAME` 때문에 이후 컬럼이 어긋날 수 있습니다. ### 진단 대상 테이블의 축과 실제 통계 뷰 스키마를 함께 확인합니다. ```sql DESC ch5_error_distance; DESC V$CH5_ERROR_DISTANCE_STAT; ``` BASE DISTANCE 테이블이면 `MIN_DISTANCE`, `MAX_DISTANCE`, `MIN_VALUE_DISTANCE`, `MAX_VALUE_DISTANCE`, `RECENT_ROW_DISTANCE`가 원본 거리축 타입으로 표시되어야 합니다. Cluster Edition에서는 `HOSTNAME VARCHAR(64)`가 첫 컬럼에 추가됩니다. ### 해결 방법 1. SQL의 기존 `*_TIME` 이름을 대응하는 `*_DISTANCE` 이름으로 변경합니다. 2. SDK result mapping을 `DATETIME`이 아니라 원본 `DOUBLE`, `LONG`, `ULONG` 타입으로 변경합니다. 3. Cluster 결과는 컬럼 이름으로 읽거나 `HOSTNAME`을 포함한 ordinal을 다시 확인합니다. 4. BASE TIME TAG 쿼리는 기존 `*_TIME DATETIME` mapping을 유지합니다. 전체 변환표와 Cluster 집계 주의사항은 [TAG별 통계 뷰](../query-analysis/#tag-stat-axis-schema)를 참고하십시오. ## 제약 및 주의사항 ### 기능 지원 범위 | 기능 | 상태 | |------|------| | 실제 시계열 데이터 UPDATE | Standard Edition에서 지원 (태그/BASETIME 조건 필요) | | 메타데이터 UPDATE | 지원 (`UPDATE ... METADATA`) | | DELETE | 지원 (`BEFORE`, 태그/축 조건 또는 전체 삭제) | | 다중 PRIMARY KEY | 미지원 (단일 컬럼만) | | BASETIME과 BASEDISTANCE 동시 사용 | 미지원 | | TAG DATA 일반 컬럼 ALTER ADD/DROP | 미지원 | | TAG METADATA 컬럼 ALTER ADD/DROP | 지원 (Standard Edition) | ### 태그 수 제한 - 단일 TAG 테이블에 생성 가능한 태그 수는 시스템 설정에 따라 제한됩니다. - 태그 수가 늘면 태그 인덱스와 메타데이터의 메모리 사용량도 증가하므로 운영 규모의 데이터로 조회와 입력 성능을 측정합니다. - 태그 이름이 레코드마다 고유한 값이 되도록 설계하면 안 됩니다 (안티패턴 — [센서별 테이블 생성](/dbms/data-modeling-table-design/table-types-patterns-type-anti/#per-sensor-create) 참고). ### 지연 도착 데이터 - BASETIME 컬럼에는 임의의 과거 시각을 삽입할 수 있습니다. - 늦게 도착한 데이터가 많은 워크로드는 실제 입력률과 조회 성능을 별도로 측정합니다. ### Cluster Edition 지원 TAG 테이블은 Cluster Edition에서 지원됩니다. 단, TAG data UPDATE는 Standard Edition 전용이며 Cluster Edition에서는 지원되지 않습니다. ### 요약 ``` TAG 테이블 = 센서 이름 (PK) + 시간/거리 축 + 계측값 - INSERT/APPEND: O - UPDATE: 실제 DATA는 Standard Edition의 태그/BASETIME 조건, METADATA는 별도 SQL - DELETE: O (BEFORE 또는 태그/축 조건) - METADATA: O (별도 속성 저장, UPDATE 가능) ``` --- **다음 읽을 내용** - [TRANSACTION 테이블 설계](/dbms/rdb-table-usage/) ## 실습 정리 정상 수정 결과는 SELECT로 확인하고 이번 실습 테이블만 제거합니다. ```sql SELECT name, time, value, status FROM ch5_error_time ORDER BY name, time; DROP TABLE ch5_error_time; DROP TABLE ch5_error_distance; ``` --- title: "5.9 활용 패턴과 시나리오" url: https://docs.machbase.com/kr/dbms/tag-table-usage/patterns-scenarios/ language: kr kind: page --- # 5.9 활용 패턴과 시나리오 각 예제는 별도 실습입니다. 테이블을 생성하기 전에 같은 이름의 객체가 없는지 확인합니다. 태그 이름, 관측 한 건의 의미, 값의 단위와 결측 정책을 먼저 정하고 DDL을 적용합니다. ## 활용 사례 ### IoT 센서 데이터 공장, 빌딩, 인프라에 설치된 다양한 센서 데이터를 단일 TAG 테이블에서 관리합니다. ```sql CREATE TAG TABLE factory_sensor ( name VARCHAR(128) PRIMARY KEY, time DATETIME BASETIME, temperature DOUBLE, vibration DOUBLE, current DOUBLE ); -- One row groups measurements from the same equipment observation. INSERT INTO factory_sensor VALUES ( 'F01/LINE-A/MOTOR-01', NOW, 75.3, 0.15, 2.4 ); ``` 이 모델은 한 장비의 같은 시각 관측을 여러 컬럼에 저장합니다. 항목별 태그 `.../TEMP`, `.../VIBRATION`을 사용하려면 `name, time, value` 형태의 단일 값 모델을 검토합니다. 항목별 측정 시각이 다르면 NULL과 품질 상태로 그 차이를 표현하십시오. ### 에너지 모니터링 전력, 가스, 수도 계량기 데이터를 시간별로 수집합니다. ```sql CREATE TAG TABLE energy_meter ( meter_id VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, kwh DOUBLE, voltage DOUBLE, current DOUBLE ); ``` `kwh`가 누적 계량값이면 AVG(kwh)는 지시값의 평균이지 구간 사용량이 아닙니다. 소비량은 구간 경계값의 차이에 초기화·교체·누락 처리 규칙을 적용해 계산합니다. 전력 kW와 전력량 kWh를 구분하고 메타데이터나 수집 계약에 단위를 명시합니다. ### 차량·이동체 추적 GPS 좌표와 속도를 시계열로 기록합니다. ```sql CREATE TAG TABLE vehicle_track ( vehicle_id VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, lat DOUBLE, lon DOUBLE, speed DOUBLE, heading DOUBLE ); ``` 좌표계, 위도·경도와 속도의 단위를 수집 계약에 명시합니다. 위치 결측을 숫자 0으로 대체하면 실제 좌표와 구분할 수 없습니다. 이 테이블을 만들었다고 공간 인덱스나 경로 매칭이 자동 제공되는 것은 아니며, 차량·시간 구간으로 먼저 조회하고 필요한 분석을 수행합니다. ### 부적합한 경우 - 태그 이름이 매 레코드마다 달라지는 경우 (태그 수 폭발) - 태그/시간 범위 없이 전체 데이터를 자주 UPDATE해야 하는 경우 - 단순 이벤트 로그 (LOG 테이블 권장) ## 결과 확인과 정리 IoT 예제는 다음 조회에서 세 측정값이 같은 한 행으로 나오는지 확인합니다. ```sql SELECT name, time, temperature, vibration, current FROM factory_sensor; DROP TABLE factory_sensor; DROP TABLE energy_meter; DROP TABLE vehicle_track; ``` 정리는 이 페이지에서 실제로 생성한 테이블에만 적용합니다. --- title: "5.10 TAG 메타데이터" url: https://docs.machbase.com/kr/dbms/tag-table-usage/tag-metadata/ language: kr kind: page --- # 5.10 TAG 메타데이터 ## Tag 메타데이터 ### 개요 METADATA는 태그마다 한 행인 현재 속성입니다. 일반 TAG 조회에서는 같은 속성이 각 DATA 행에 함께 나타납니다. 현재 속성을 바꾸면 과거 DATA를 조회한 결과에도 새 속성이 보일 수 있으므로 발생 당시 속성이 필요하면 DATA나 별도 속성 이력에 보존합니다. 아래는 기본 메타데이터, JSON 메타데이터와 전체 예제를 서로 다른 테이블로 분리합니다. 태그의 정적 속성을 저장하는 영역으로 사용하며 센서 위치, 장비 상태, 설치 정보, 외부 식별자, JSON 문서형 속성을 메타데이터에 둘 수 있습니다. 메타데이터 전용 SQL로 다음 작업을 처리할 수 있습니다. 아래 예제의 `TAG`는 테이블 이름이며, 사용할 때 실제 TAG 테이블 이름으로 바꿉니다. - 메타데이터 전용 조회 - 메타데이터 조건 기반 `UPDATE` / `DELETE` - metadata row 마지막 변경 시각 조회 - ARRAY metadata 컬럼 ADD/DROP과 기존 row DEFAULT - `JSON` 타입 메타데이터 컬럼 선언 - JSON path 조회와 JSON path 인덱스 - JSON 문서 일부만 수정하는 부분 갱신 사용자는 내부 저장 테이블을 직접 다룰 필요 없이 `TAG METADATA` 문법만 사용하면 됩니다. ### 메타데이터 컬럼 정의 메타데이터 컬럼은 `CREATE TAG TABLE` 의 `METADATA (...)` 절에서 정의합니다. ```sql CREATE TAG TABLE ch5_meta ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ) METADATA ( location VARCHAR(100), status VARCHAR(20), srcip IPV4 ); ``` 메타데이터 컬럼은 태그 이름별로 1행만 저장됩니다. ### ARRAY 메타데이터 컬럼 추가와 삭제 Standard Edition에서는 기존 TAG 테이블의 METADATA 영역에 고정 길이 숫자 ARRAY 컬럼을 추가하고 삭제할 수 있습니다. ```sql INSERT INTO ch5_meta (name, time, value) VALUES ('TEMP_OLD', TO_DATE('2026-09-05 00:00:00'), 10.0); ALTER TABLE ch5_meta METADATA ADD COLUMN (limits DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]); INSERT INTO ch5_meta (name, time, value) VALUES ('TEMP_NEW', TO_DATE('2026-09-05 00:00:01'), 20.0); SELECT name, limits FROM ch5_meta METADATA ORDER BY name; ``` ALTER 전에 존재한 `TEMP_OLD` metadata row에는 `[0.0000, NULL]`을 backfill합니다. ALTER 뒤 TAG DATA 입력으로 자동 등록된 `TEMP_NEW` metadata row에는 ADD COLUMN DEFAULT를 다시 적용하지 않으며 `limits`는 whole NULL입니다. DEFAULT가 없는 경우에는 ALTER 전 row도 whole NULL입니다. 추가한 ARRAY metadata 컬럼은 일반 TAG 조회의 명시 projection과 `SELECT *`에도 포함됩니다. ARRAY metadata 컬럼에는 자동 index를 만들지 않으며 다음과 같은 명시적 index도 지원하지 않습니다. ```sql -- 지원하지 않으며 오류를 반환합니다. CREATE INDEX idx_sensor_limits ON ch5_meta METADATA(limits); ``` 컬럼을 제거할 때도 `METADATA`를 지정합니다. ```sql ALTER TABLE ch5_meta METADATA DROP COLUMN (limits); ``` TAG DATA의 일반 ARRAY 컬럼은 `CREATE TAG TABLE`에서 선언할 수 있지만 ALTER로 추가할 수 없습니다. 지원 요소 타입, cardinality와 DEFAULT 규칙은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ### 메타데이터 입력 메타데이터는 `INSERT INTO ... METADATA` 로 입력합니다. ```sql INSERT INTO ch5_meta METADATA VALUES ( 'TEMP_001', 'Building-A/F1', 'READY', '192.168.0.11' ); ``` 컬럼 목록을 지정할 수도 있습니다. ```sql INSERT INTO ch5_meta METADATA (name, status, srcip, location) VALUES ('TEMP_002', 'STOP', '192.168.0.12', 'Building-A/F2'); ``` 주의사항: - 컬럼 목록을 생략한 VALUES는 태그명 다음에 메타데이터 선언 순서를 따릅니다. - 컬럼 목록을 지정했다면 그 목록의 순서대로 값을 전달합니다. - 생략한 입력의 NULL·DEFAULT 처리는 해당 DDL과 입력 경로의 규칙을 따릅니다. - 식별자는 TAG의 태그명 컬럼입니다. 이 예제에서는 그 이름을 `name`으로 선언했습니다. - 메타데이터 행이 생성되면 `_LAST_UPDATE_TIME` 이 서버 시각으로 자동 기록됩니다. ### 메타데이터 조회 #### 메타데이터만 조회 메타데이터 전용 조회는 `FROM TAG METADATA` 를 사용합니다. ```sql SELECT name, location, status, srcip FROM ch5_meta METADATA ORDER BY name; ``` 이 조회는 태그 이름당 1행을 반환합니다. ```sql SELECT * FROM ch5_meta METADATA ORDER BY name; ``` `SELECT *` 와 `table_alias.*` 는 `NAME` 과 메타데이터 컬럼만 반환합니다. `_LAST_UPDATE_TIME` 같은 시스템 관리 컬럼은 `SELECT *` 결과에 표시되지 않습니다. 필요한 경우 컬럼명을 명시합니다. #### 마지막 변경 시각 조회 TAG metadata에는 각 metadata row의 마지막 변경 시각을 나타내는 시스템 관리 컬럼 `_LAST_UPDATE_TIME` 이 있습니다. `_LAST_UPDATE_TIME` 은 tag data row의 마지막 입력 시각이 아니라, tag metadata row가 생성되거나 실제 metadata 값이 변경된 시각입니다. ##### 조회 방법 `_LAST_UPDATE_TIME` 은 명시적으로 컬럼명을 지정해서 조회합니다. ```sql SELECT name, _last_update_time FROM ch5_meta METADATA; ``` 다른 metadata 컬럼과 함께 조회하거나 조건에 사용할 수 있습니다. ```sql SELECT name, location, status, _last_update_time FROM ch5_meta METADATA WHERE name = 'TEMP_001'; ``` `SELECT *` 와 `table_alias.*` 결과에는 `_LAST_UPDATE_TIME` 이 표시되지 않습니다. ##### 자동 기록 및 갱신 규칙 metadata row가 새로 생성되면 `_LAST_UPDATE_TIME` 이 자동으로 기록됩니다. ```sql INSERT INTO ch5_meta METADATA(name, location, status) VALUES('TEMP_003', 'Building-A/F3', 'READY'); ``` 사용자 metadata 값이 실제로 변경되면 `_LAST_UPDATE_TIME` 이 갱신됩니다. ```sql UPDATE ch5_meta METADATA SET status = 'DONE' WHERE name = 'TEMP_003'; ``` 같은 값으로 update하거나 JSON missing path 제거처럼 저장 결과가 바뀌지 않는 update는 실제 변경으로 보지 않습니다. 이 경우 `_LAST_UPDATE_TIME` 은 유지됩니다. ```sql UPDATE ch5_meta METADATA SET status = 'DONE' WHERE name = 'TEMP_003'; ``` JSON의 존재하지 않는 경로 제거도 저장값을 바꾸지 않으면 no-op입니다. 실행 예제는 아래 JSON 테이블을 생성한 뒤 확인합니다. ##### 직접 입력 및 수정 제한 `_LAST_UPDATE_TIME` 은 시스템 관리 컬럼이므로 사용자가 직접 값을 넣거나 수정할 수 없습니다. 다음 문장은 허용되지 않습니다. ```sql INSERT INTO ch5_meta METADATA(name, location, status, _last_update_time) VALUES('TEMP_004', 'Building-A/F4', 'READY', now); ``` ```sql UPDATE ch5_meta METADATA SET _last_update_time = now WHERE name = 'TEMP_003'; ``` 또한 TAG name 컬럼명, TAG metadata 컬럼명, `ALTER TABLE ... METADATA ADD COLUMN` 대상 컬럼명으로 `_LAST_UPDATE_TIME` 을 사용할 수 없습니다. `ALTER TABLE ... METADATA DROP COLUMN` 으로 `_LAST_UPDATE_TIME` 을 삭제할 수도 없습니다. ```sql CREATE TAG TABLE invalid_sensor ( _last_update_time VARCHAR(128) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); ``` ```sql CREATE TAG TABLE invalid_sensor_meta ( name VARCHAR(128) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ) METADATA ( _last_update_time DATETIME ); ``` `_LAST_UPDATE_TIME2` 처럼 prefix만 같은 이름은 별도 사용자 컬럼으로 사용할 수 있습니다. ##### 시간 조건 조회 및 자동 인덱스 `_LAST_UPDATE_TIME` 에는 시간 조건 조회를 위한 인덱스가 자동으로 제공됩니다. ```sql SELECT name, location, _last_update_time FROM ch5_meta METADATA WHERE _last_update_time >= TO_DATE('2026-06-08 00:00:00') ORDER BY _last_update_time; ``` 따라서 같은 컬럼에 사용자가 별도 인덱스를 중복 생성할 필요가 없습니다. ##### machloader / tagmetaimport 사용 시 주의사항 TAG metadata를 import할 때 입력 파일이나 form 파일에는 `NAME` 과 사용자 metadata 컬럼만 포함합니다. 내부 컬럼인 `_ID` 와 시스템 관리 컬럼 `_LAST_UPDATE_TIME` 은 입력 대상이 아닙니다. metadata가 `location`, `status` 인 경우 입력 데이터는 다음 형태를 사용합니다. ```text TEMP_001,Building-A/F1,READY TEMP_002,Building-A/F2,STOP ``` `_LAST_UPDATE_TIME` 은 import 시 서버가 자동으로 채웁니다. 일반 LOG, LOOKUP, VOLATILE 테이블에서 사용자가 `_LAST_UPDATE_TIME` 이라는 이름의 컬럼을 정의한 경우에는 일반 사용자 컬럼으로 동작합니다. 예약 동작은 TAG metadata 시스템 컬럼에만 적용됩니다. #### 데이터와 함께 조회 메타데이터 조건으로 시계열 데이터를 조회할 때는 일반 `FROM TAG` 를 사용합니다. ```sql SELECT name, status, time, value FROM ch5_meta WHERE status = 'READY' ORDER BY name, time; ``` 이 조회는 데이터 row 기준으로 반환되므로, 같은 tag의 메타데이터 값은 각 데이터 row마다 반복됩니다. 주의사항: - `FROM TAG METADATA` 에서는 `TIME`, `VALUE` 같은 데이터 컬럼을 조회할 수 없습니다. - `FROM TAG` 는 데이터 조회 모드이고, `FROM TAG METADATA` 는 메타데이터 조회 모드입니다. - `TAG METADATA` 에서는 내부 컬럼 `_ID`, `_RID` 를 사용할 수 없습니다. ### 메타데이터 수정 메타데이터 수정은 `UPDATE TAG METADATA` 를 사용합니다. ```sql UPDATE ch5_meta METADATA SET status = 'DONE', srcip = '10.0.0.20' WHERE name = 'TEMP_001'; ``` 메타데이터 조건으로 여러 태그를 한 번에 수정할 수도 있습니다. ```sql UPDATE ch5_meta METADATA SET status = 'DONE' WHERE status = 'READY'; ``` 주의사항: - 수정 대상은 `NAME` 과 메타데이터 컬럼입니다. - `TIME`, `VALUE` 같은 데이터 컬럼은 `UPDATE ... METADATA` 에서 수정할 수 없습니다. - 내부 컬럼은 수정할 수 없습니다. - 실제 metadata 값이 바뀐 경우에만 `_LAST_UPDATE_TIME` 이 갱신됩니다. ### 메타데이터 삭제 메타데이터 삭제는 `DELETE FROM TAG METADATA` 를 사용합니다. 특정 태그의 메타데이터를 삭제하려면 `WHERE` 절에서 tag name 조건을 지정합니다. ```sql DELETE FROM ch5_meta METADATA WHERE name = 'TEMP_002'; ``` 메타데이터 조건으로 여러 태그를 한 번에 삭제할 수도 있습니다. ```sql DELETE FROM ch5_meta METADATA WHERE status = 'STOP'; ``` `WHERE` 절을 생략하면 전체 메타데이터가 대상입니다. 이 실습에는 앞서 입력한 TEMP_OLD·TEMP_NEW의 DATA가 남아 있으므로 아래 전체 삭제는 의도적으로 실패합니다. ```sql DELETE FROM ch5_meta METADATA; ``` 주의사항: - 삭제 대상 중 하나라도 실제 데이터 row를 가지고 있으면 문장 전체가 실패합니다. - 즉, 사용 중인 tag의 메타데이터는 삭제할 수 없습니다. - 전체 삭제 시에도 사용 중인 tag가 하나라도 있으면 일부만 삭제하지 않고 문장 전체가 실패합니다. 사용 중인 tag의 메타데이터를 삭제하려면 먼저 해당 tag의 데이터 row를 삭제한 뒤 메타데이터 삭제를 다시 수행합니다. ```sql DELETE FROM ch5_meta WHERE name = 'TEMP_001'; DELETE FROM ch5_meta METADATA WHERE name = 'TEMP_001'; ``` ### JSON 메타데이터 컬럼 메타데이터에 `JSON` 컬럼을 선언할 수 있습니다. ```sql CREATE TAG TABLE ch5_meta_json ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ) METADATA ( status VARCHAR(20), info JSON ); ``` JSON 메타데이터 입력 예: ```sql INSERT INTO ch5_meta_json METADATA VALUES ( 'SHIP_001', 'READY', '{"name":"alpha","ship":{"status":"READY"}}' ); ``` 주의사항: - `JSON` 메타데이터 컬럼은 길이를 지정하지 않습니다. - 유효하지 않은 JSON 문자열은 오류가 발생합니다. - raw JSON 컬럼 자체에는 자동 인덱스가 생성되지 않습니다. ### JSON 값이 바뀌지 않는 경우 ```sql SELECT name, _last_update_time FROM ch5_meta_json METADATA; UPDATE ch5_meta_json METADATA SET info = JSON_REMOVE(info, '$.missing') WHERE name = 'SHIP_001'; SELECT name, _last_update_time FROM ch5_meta_json METADATA; ``` 경로가 없어 저장값이 그대로이면 변경 시각도 유지됩니다. ### JSON path 조회 JSON 메타데이터는 `->` 연산자로 조회할 수 있습니다. ```sql SELECT name, info->'$.name', info->'$.ship.status' FROM ch5_meta_json METADATA WHERE info->'$.ship.status' = 'READY' ORDER BY name; ``` 데이터 조회에서도 같은 방식으로 사용할 수 있습니다. ```sql SELECT name, time, value FROM ch5_meta_json WHERE info->'$.ship.status' = 'READY' ORDER BY name, time; ``` #### 경로 표기 규칙 조회와 부분 갱신에서 사용하는 path는 full JSONPath를 사용합니다. - 일반 key: `$.name` - 중첩 key: `$.ship.status` - key 이름에 `.` 또는 `-` 가 포함되면 bracket 표기 사용 ```sql SELECT info->'$[''ship.owner'']' FROM ch5_meta_json METADATA; SELECT info->'$[''ship-owner'']' FROM ch5_meta_json METADATA; ``` ### JSON path 인덱스 #### 테이블 생성 시 함께 선언 자주 조회하는 JSON path는 메타데이터 정의 시 함께 인덱싱할 수 있습니다. ```sql CREATE TAG TABLE ch5_meta_json_indexed ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ) METADATA ( status VARCHAR(20), info JSON INDEX('name', 'ship.status') ); ``` `INDEX(...)` 안의 문자열은 다음 규칙으로 해석합니다. - `'name'` 은 `$.name` - `'ship.status'` 는 `$.ship.status` - 특수 문자가 있는 key나 복잡한 경로는 full JSONPath를 직접 사용 ```sql INFO JSON INDEX('$[''ship.owner'']') ``` #### 생성 후 인덱스 추가 테이블 생성 후에도 JSON path 인덱스를 추가할 수 있습니다. ```sql CREATE INDEX idx_ship_owner ON ch5_meta_json METADATA (info->'$.owner'); ``` #### 인덱스 삭제 인덱스 이름만으로 삭제합니다. ```sql SHOW INDEX idx_ship_owner; DROP INDEX idx_ship_owner; ``` 명시적으로 만든 인덱스는 생성 시 정한 이름으로 관리합니다. `SHOW INDEX idx_ship_owner;`는 DROP 전에 실행합니다. 삭제 후 같은 이름을 조회하면 존재하지 않는 객체입니다. #### 인덱스 사용 시 주의사항 현재 JSON path 인덱스는 문자열 비교 중심으로 동작합니다. ```sql SELECT name FROM ch5_meta_json METADATA WHERE info->'$.status' = 'READY'; ``` 문자열 literal 비교는 인덱스를 사용할 수 있습니다. 반면 숫자 literal 비교는 full scan으로 처리될 수 있습니다. 예: - `info->'$.num' = '10'` : 인덱스 사용 가능 - `info->'$.num' = 10` : full scan 가능 ### JSON 부분 갱신 JSON 함수는 지정 경로를 바꾼 새 문서 값을 반환합니다. UPDATE는 그 결과를 컬럼에 저장합니다. 이는 경로 단위의 논리적 갱신이며 저장 파일 일부만 제자리에서 수정한다는 성능 보장은 아닙니다. #### JSON_SET SQL scalar 값을 JSON scalar로 저장합니다. ```sql UPDATE ch5_meta_json METADATA SET info = JSON_SET(info, '$.ship.status', 'DONE') WHERE name = 'SHIP_001'; ``` #### JSON_SET_JSON 입력 문자열을 JSON으로 해석해 object 또는 array를 저장합니다. ```sql UPDATE ch5_meta_json METADATA SET info = JSON_SET_JSON(info, '$.owner', '{"name":"machbase","team":"db"}') WHERE name = 'SHIP_001'; ``` #### JSON_REMOVE 특정 멤버 또는 하위 경로를 삭제합니다. ```sql UPDATE ch5_meta_json METADATA SET info = JSON_REMOVE(info, '$.owner.team') WHERE name = 'SHIP_001'; ``` #### 부분 갱신 규칙 - `JSON_SET(..., path, NULL)` 은 JSON `null` 을 저장합니다. - `JSON_SET_JSON(..., path, NULL)` 의 결과는 SQL `NULL` 입니다. - JSON 문서 인자가 `NULL` 이면 함수 결과는 SQL `NULL` 입니다. - path 가 `NULL` 이거나 빈 문자열이면 오류가 발생합니다. - 존재하지 않는 경로에 대한 `JSON_REMOVE` 는 오류가 아니라 no-op 입니다. - `JSON_REMOVE(..., '$')` 는 허용되지 않습니다. - 부분 갱신은 object 경로 중심으로 지원합니다. - array element 경로 갱신 예: `$.items[0]` 는 지원하지 않습니다. ### 전체 예제 ```sql CREATE TAG TABLE ch5_meta_complete ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ) METADATA ( status VARCHAR(20), srcip IPV4, info JSON INDEX('name', 'ship.status') ); INSERT INTO ch5_meta_complete METADATA VALUES ( 'SHIP_001', 'READY', '192.168.0.11', '{"name":"alpha","ship":{"status":"READY"}}' ); INSERT INTO ch5_meta_complete VALUES ('SHIP_001', '2026-04-01 00:00:00', 10.5); SELECT name, status, info FROM ch5_meta_complete METADATA; SELECT name, time, value FROM ch5_meta_complete WHERE info->'$.ship.status' = 'READY'; CREATE INDEX idx_ship_owner ON ch5_meta_complete METADATA (info->'$.owner'); UPDATE ch5_meta_complete METADATA SET info = JSON_SET(info, '$.ship.status', 'DONE') WHERE name = 'SHIP_001'; DROP INDEX idx_ship_owner; ``` ### 요약 - 메타데이터 전용 조회는 `FROM TAG METADATA` - 데이터 조회는 `FROM TAG` - 메타데이터 수정/삭제는 `UPDATE/DELETE ... METADATA` - ARRAY metadata 컬럼 변경은 `ALTER TABLE ... METADATA ADD/DROP COLUMN` - JSON 메타데이터는 `INFO JSON` - `_LAST_UPDATE_TIME` 은 metadata row의 마지막 변경 시각이며 명시적으로 조회할 수 있습니다 - JSON path 인덱스는 `INFO JSON INDEX(...)` 또는 `CREATE INDEX ... ON TAG METADATA (...)` - JSON 부분 갱신은 `JSON_SET`, `JSON_SET_JSON`, `JSON_REMOVE` - `_LAST_UPDATE_TIME` 은 서버가 자동 관리하며 standard와 cluster 환경에서 동일하게 동작합니다 ## 실습 정리 전체 삭제 실패 예제와 달리 DROP은 테이블과 DATA·METADATA를 모두 제거합니다. 아래 이름이 이번 실습에서 만든 객체인지 확인한 뒤 실행합니다. ```sql DROP TABLE ch5_meta; DROP TABLE ch5_meta_json; DROP TABLE ch5_meta_json_indexed; DROP TABLE ch5_meta_complete; ``` --- title: "5.11 TAG data UPDATE와 데이터 보정" url: https://docs.machbase.com/kr/dbms/tag-table-usage/tag-data-update-correction/ language: kr kind: page --- # 5.11 TAG data UPDATE와 데이터 보정 TAG DATA 보정은 원본 값을 직접 바꾸는 방식과 원본을 보존하고 조회 시 보정값을 적용하는 방식으로 나눕니다. 두 방식은 ROLLUP에 미치는 영향도 다릅니다. 이 페이지의 UPDATE 실습은 Machbase DBMS 8.7.0 Standard Edition을 전제로 합니다. ## 직접 값 정정 태그명과 BASETIME 조건을 함께 지정합니다. SET 우변은 기존 행 컬럼을 참조할 수 없으므로 `SET value = value + 1` 같은 식 대신 계산한 값이나 바인딩 매개변수를 전달합니다. 이름이 겹치지 않는 다음 테이블을 준비합니다. ROLLUP 정정까지 확인하도록 기본 ROLLUP도 생성합니다. ```sql CREATE TAG TABLE ch5_correction ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED, status INTEGER ) WITH ROLLUP; INSERT INTO ch5_correction VALUES ('TEMP-01', TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 0); INSERT INTO ch5_correction VALUES ('TEMP-01', TO_DATE('2026-01-01 12:30:00', 'YYYY-MM-DD HH24:MI:SS'), 99.0, 0); INSERT INTO ch5_correction VALUES ('TEMP-02', TO_DATE('2026-01-01 12:30:00', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 0); ``` ### 범위 확인, 수정, 재조회 ```sql SELECT COUNT(*), MIN(value), MAX(value) FROM ch5_correction WHERE name = 'TEMP-01' AND time >= TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') AND time < TO_DATE('2026-01-01 13:00:00', 'YYYY-MM-DD HH24:MI:SS'); UPDATE ch5_correction SET value = 25.0, status = 1 WHERE name = 'TEMP-01' AND time >= TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') AND time < TO_DATE('2026-01-01 13:00:00', 'YYYY-MM-DD HH24:MI:SS'); SELECT name, time, value, status FROM ch5_correction ORDER BY name, time; ``` 첫 조회는 2건, 최솟값 10.0, 최댓값 99.0입니다. UPDATE 후 TEMP-01의 두 행은 value=25.0, status=1이고 TEMP-02의 20.0은 유지됩니다. 사전 COUNT는 대상을 잠그지 않으므로 동시 입력이 있는 작업은 기준 시각과 범위를 별도로 통제합니다. ### 여러 태그와 오류 처리 태그 선택에는 `=`, `IN`, `LIKE`를 사용할 수 있습니다. 넓은 패턴이나 긴 시간 범위를 수정하기 전에 대상 이름과 건수를 확인하고 작은 범위로 나눕니다. 여러 태그는 순차 처리될 수 있으므로 실패했을 때 문장 전체가 원자적으로 취소됐다고 가정하지 않습니다. 수정된 값과 남은 대상을 다시 조회한 뒤 재시도합니다. 연속된 시간 구간은 `>= 시작 AND < 끝`으로 정의하면 경계 행을 중복 처리하지 않습니다. 하루 전체를 `23:59:59`까지로 지정하면 그 뒤의 소수 초 데이터를 놓칠 수 있습니다. 정확한 허용 조건과 바인딩은 [TAG UPDATE](../../reference/sql/syntax/dml-syntax/tag-data-update-syntax/)를 참고합니다. ### ROLLUP 재구성 원본 수정은 이미 계산된 ROLLUP을 자동으로 바꾸지 않습니다. 위 실습의 수정 구간을 다음처럼 재구성하고 [ROLLUP 재구성](../../tag-rollup-usage/rollup-rebuild/)의 진행 상태와 조회 검증 절차를 따릅니다. ```sql EXEC ROLLUP_REBUILD(ch5_correction, 'TEMP-01', TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), TO_DATE('2026-01-01 13:00:00', 'YYYY-MM-DD HH24:MI:SS')); ``` ## 원본 보존과 NULL 보정 원본과 보정값을 별도 컬럼에 저장하면 최초 값을 유지할 수 있습니다. 보정값이 NULL일 수도 있으므로 `corrected_value IS NOT NULL`만으로 보정 여부를 판정하지 않고 플래그를 사용합니다. ```sql CREATE TAG TABLE ch5_correction_overlay ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, raw_value DOUBLE, corrected_value DOUBLE, is_corrected SHORT ); INSERT INTO ch5_correction_overlay VALUES ('TEMP-01', TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 99.0, NULL, 0); UPDATE ch5_correction_overlay SET corrected_value = NULL, is_corrected = 1 WHERE name = 'TEMP-01' AND time = TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'); SELECT name, raw_value, is_corrected, CASE WHEN is_corrected = 1 THEN corrected_value ELSE raw_value END AS effective_value FROM ch5_correction_overlay; ``` raw_value는 99.0, is_corrected는 1, effective_value는 NULL입니다. 플래그가 0이면 원본을 사용합니다. 이 방식은 조회식으로 값을 선택하며 raw_value는 바꾸지 않습니다. 기본 ROLLUP이 이 CASE 식을 자동 집계하거나 raw_value의 재구성만으로 보정을 반영한다고 가정하지 말고, 유효값을 집계하는 별도 조회·집계 모델을 정합니다. ## 보정 이력과 검증 여러 번의 변경 사유와 담당자를 남기려면 별도 이력을 기록합니다. ```sql CREATE LOG TABLE ch5_correction_log ( sensor_name VARCHAR(64), target_time DATETIME, old_value DOUBLE, new_value DOUBLE, reason VARCHAR(256), corrected_by VARCHAR(64) ); ``` TAG 수정과 LOG 이력 기록은 하나의 TRANSACTION 트랜잭션으로 묶이지 않습니다. 순서·부분 실패·재시도 시 같은 작업을 식별할 번호를 애플리케이션에서 설계합니다. 테이블 생성만으로 감사 이력이 자동 기록되지는 않습니다. 정정 뒤 원본/유효값, 영향 행 수, 구간 통계와 보고서를 확인합니다. 완료 후 이번 실습에서 만든 객체만 정리합니다. 아래 CASCADE는 ch5_correction의 ROLLUP도 함께 삭제합니다. ```sql DROP TABLE ch5_correction CASCADE; DROP TABLE ch5_correction_overlay; DROP TABLE ch5_correction_log; ``` --- title: "5.12 tagmetaimport와 메타데이터 일괄 등록" url: https://docs.machbase.com/kr/dbms/tag-table-usage/tagmetaimport/ language: kr kind: page --- # 5.12 tagmetaimport와 메타데이터 일괄 등록 ## tagmetaimport로 메타데이터 등록 `tagmetaimport`는 CSV의 태그명과 사용자 메타데이터를 가져오는 도구입니다. 일반 SQL의 논리 TAG 이름과 `-t` 입력 대상을 구분해야 합니다. 현재 래퍼는 `-t`를 machloader에 전달합니다. 아래 논리 테이블 `ch5_meta_import`의 메타데이터 입력 대상은 `_CH5_META_IMPORT_META`입니다. `-t ch5_meta_import`가 자동으로 METADATA를 선택한다고 가정하지 마십시오. 이 이름은 도구의 대상 지정에 사용합니다. SQL 조회·변경은 `ch5_meta_import METADATA`를 사용하며 저장 객체를 직접 수정하는 절차로 확장하지 않습니다. 기본 대상에 의존하지 말고 `-t`를 명시합니다. ## 1. 테이블 준비 기존 객체가 없는 실습 데이터베이스에서 다음 SQL을 실행합니다. ```sql CREATE TAG TABLE ch5_meta_import ( name VARCHAR(40) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ) METADATA ( location VARCHAR(40), status VARCHAR(20) ); ``` ## 2. CSV 준비 다음 내용을 클라이언트의 `ch5_metadata.csv`로 저장합니다. ```csv name,location,status TEMP_001,Building-A/F1,READY TEMP_002,Building-A/F2,STOP TEMP_003,Building-B/F3,READY ``` 파일에는 태그명 다음에 METADATA 선언 순서대로 값을 넣습니다. DATA의 time·value와 시스템 컬럼 `_ID`·`_LAST_UPDATE_TIME`은 넣지 않습니다. 헤더가 있으면 `-H`를 지정하며, 헤더가 임의의 컬럼 순서를 자동으로 맞춰 준다고 가정하지 않습니다. ## 3. 입력과 결과 확인 주소·계정은 실제 실습 서버에 맞추고, 사용하는 8.7.0 패키지의 `MACHBASE_HOME`과 라이브러리 환경에서 실행합니다. ```bash tagmetaimport -s 127.0.0.1 -P 5656 -u SYS -p MANAGER \ -t _CH5_META_IMPORT_META -d ch5_metadata.csv -H \ -l ch5_import.log -b ch5_import.bad ``` 첫 실행은 성공 3건·실패 0건을 기대합니다. 아래 METADATA 조회는 세 행을 반환하고 DATA COUNT는 0입니다. 메타데이터 등록은 측정값 입력과 다릅니다. ```sql SELECT name, location, status, _last_update_time FROM ch5_meta_import METADATA ORDER BY name; SELECT COUNT(*) FROM ch5_meta_import; ``` ## 4. 기존 태그와 재실행 같은 파일을 다시 입력해도 기존 태그가 자동 갱신되지 않습니다. 현재 경로는 일반 METADATA INSERT이므로 중복 태그는 오류 행으로 집계됩니다. 두 번째 실행은 성공 0건·실패 3건을 기대하며 기존 속성은 유지됩니다. 종료 상태만 보지 말고 성공·실패 건수와 bad/log 파일을 함께 확인합니다. 새 행과 잘못된 행이 섞인 파일도 전체가 하나의 트랜잭션이라고 가정하지 않습니다. 이미 반영된 태그를 확인하고 실패 행만 고쳐 재처리합니다. 기존 속성은 명시적인 UPDATE 또는 지원 UPSERT로 바꿉니다. ```sql UPDATE ch5_meta_import METADATA SET status = 'DONE' WHERE name = 'TEMP_001'; INSERT INTO ch5_meta_import METADATA VALUES ('TEMP_002', 'Building-C/F2', 'READY') ON DUPLICATE KEY UPDATE; SELECT name, location, status FROM ch5_meta_import METADATA ORDER BY name; ``` TEMP_001은 DONE으로, TEMP_002는 Building-C/F2·READY로 바뀝니다. 실제 값이 바뀌면 변경 시각이 갱신되고 같은 값의 no-op은 유지됩니다. `tagmetaimport`에 자동 UPSERT 옵션이 있다고 해석하지 마십시오. ## 정리와 관련 문서 결과 확인 후 `DROP TABLE ch5_meta_import;`로 이번 실습 테이블만 정리합니다. CSV·로그·bad 파일은 재처리에 필요하지 않은지 확인한 뒤 정리합니다. 상세 옵션은 [tagmetaimport 명령 사전](../../reference/command-line-tools/tagmetaimport/)을, SQL 등록·변경 규칙은 [TAG 메타데이터](../tag-metadata/)를 참고하십시오. --- title: "6. TAG 테이블 ROLLUP 활용" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/ language: kr kind: section --- # 6. TAG 테이블 ROLLUP 활용 ROLLUP은 시간축 TAG의 데이터를 미리 집계하고, 조회할 때 그 통계를 합쳐 반복 분석 비용을 줄이는 기능입니다. 이 장은 Machbase DBMS 8.7.0의 기본·조건·확장·JSON·Custom ROLLUP을 구분하고 생성부터 결과 검증과 재구성까지 설명합니다. ## 먼저 구분할 세 가지 간격 | 개념 | 의미 | 예 | |---|---|---| | 생성 INTERVAL | 저장할 집계 버킷의 간격 | 1 MIN | | WAKEUP INTERVAL | 집계 작업을 깨우는 주기 | 10 SEC | | 조회 버킷 | 보고서가 요청하는 결과 구간 | `rollup('min', 5, time)` | 같은 버킷에 여러 차례의 부분 집계가 저장될 수 있습니다. 기본 ROLLUP은 공개 조회 구문이 필요한 통계를 병합하고, Custom 대상 TAG는 사용자가 최종 재집계 쿼리를 작성합니다. 원본 보존 기간과 ROLLUP 보존·재구성 정책은 별도로 정합니다. ## 이 장의 구성 | 절 | 내용 | |---|---| | [개요와 사용 기준](./overview-use-criteria/) | 기본 실습과 원본·집계 비교 | | [대상 TAG 설계](./target-tag-table-design/) | ON/FROM, 계층 제약과 용량 산정 | | [생성과 삭제](./create-delete-rollup/) | CREATE, WITH ROLLUP, IF NOT EXISTS와 의존성 | | [조회 문법](./query-syntax-rollup/) | 후보 선택, 시간 단위와 origin | | [조건 ROLLUP](./conditional-rollup/) | 원본 필터와 명시적인 후보 선택 | | [Custom ROLLUP](./custom-rollup/) | 증분 결과 재집계와 OHLCV 계층 | | [확장 ROLLUP](./extension-rollup/) | FIRST/LAST와 OHLC 검증 | | [JSON ROLLUP](./json-summarized-rollup/) | 경로·문서 전체 집계와 NULL | | [제어와 상태](./ingestion-control-rollup/) | STOP/START/WAKEUP/FORCE, V$ROLLUP과 gap | | [REBUILD](./rollup-rebuild/) | 실제 지원 대상, 버킷 경계와 정정 | | [성능 튜닝](./performance-tuning-rollup/) | 같은 결과를 기준으로 비용 비교 | | [활용 시나리오](./patterns-scenarios/) | 다중 태그와 원본·집계의 역할 분담 | 각 페이지는 독립 실습이며 객체 이름을 `ch6_`로 구분합니다. 기존 업무 객체와 이름이 겹치지 않는지 확인하고, 의도적 오류 예제는 성공 스크립트와 분리합니다. 고정 시각 데이터는 표시한 고정 구간으로 조회합니다. 실습 테이블·ROLLUP만 정리합니다. Custom과 REBUILD는 Standard Edition 전용입니다. ROLLUP이 생성됐거나 입력에 성공했다는 사실만으로 집계가 끝났다고 판단하지 않습니다. 실습에서는 이름을 지정한 FORCE로 처리 범위를 따라잡고 결과를 확인합니다. [지원 범위](../reference/support-scope-constraints/rollup/)와 [문제 해결](../troubleshooting/rollup/)에서 제약과 진단을 이어 확인합니다. --- title: "6.1 ROLLUP 개요와 사용 기준" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/overview-use-criteria/ language: kr kind: page --- # 6.1 ROLLUP 개요와 사용 기준 ROLLUP은 원시 행에서 같은 집계를 반복하는 비용을 줄입니다. 원본 보관 정책이나 임의 쿼리 결과 캐시가 아니며, 집계에 저장하지 않은 원본 정보까지 복원할 수는 없습니다. ## 사용 기준 | 요구 | 검토할 방식 | |---|---| | 한 숫자 컬럼의 반복 구간 통계 | 일반 ROLLUP | | 특정 품질 조건을 통과한 표본만 집계 | 조건 ROLLUP | | 구간의 첫 값·마지막 값 필요 | EXTENSION ROLLUP | | 여러 집계식을 별도 TAG에 저장 | Custom ROLLUP(Standard Edition 전용) | | JSON 경로나 문서의 숫자 값 집계 | JSON 경로 또는 문서 전체 ROLLUP | | 거리축 TAG | 일반 숫자 구간 집계; ROLLUP은 미지원 | 일반 숫자 컬럼을 명시해 만드는 ROLLUP에는 SUMMARIZED가 필수가 아닙니다. WITH ROLLUP 자동 생성과 JSON 문서 전체 집계에는 별도의 SUMMARIZED 조건이 있습니다. 자세한 차이는 [생성 문법](../create-delete-rollup/)을 참고합니다. ## 기본 실습 ### 1. 생성과 입력 ```sql CREATE TAG TABLE ch6_basic ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, quality INTEGER ); CREATE ROLLUP ch6_basic_ru ON ch6_basic(value) INTERVAL 1 MIN; INSERT INTO ch6_basic VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1); INSERT INTO ch6_basic VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1); INSERT INTO ch6_basic VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1); INSERT INTO ch6_basic VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1); ``` ### 2. 집계 완료 범위 확인 ```sql EXEC TABLE_FLUSH(ch6_basic); ALTER ROLLUP ch6_basic_ru FORCE; SHOW ROLLUPGAP; ``` SHOW ROLLUPGAP은 machsql 명령입니다. SDK에서는 V$ROLLUP 등 지원되는 SQL 조회를 사용합니다. TABLE_FLUSH는 저장 버퍼 처리이고 FORCE는 해당 ROLLUP의 처리 범위를 따라잡는 작업입니다. 지속 입력 중 미래에 들어올 행까지 완료시킨다는 뜻은 아닙니다. ### 3. 원본과 비교 ```sql SELECT DATE_TRUNC('minute', time) AS bucket, COUNT(value), MIN(value), MAX(value), AVG(value) FROM ch6_basic WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; SELECT rollup('min', 1, time) AS bucket, COUNT(value), MIN(value), MAX(value), AVG(value) FROM ch6_basic WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; ``` | 버킷 | COUNT(value) | MIN | MAX | AVG | |---|---:|---:|---:|---:| | 2026-01-01 00:00:00 | 2 | 10 | 20 | 15 | | 2026-01-01 00:01:00 | 1 | 30 | 30 | 30 | 두 쿼리는 같은 결과를 반환해야 합니다. DATE_TRUNC의 원본 집계가 단지 ROLLUP이 존재한다는 이유로 자동 전환되는 것으로 설명하지 않습니다. ROLLUP 조회에서는 `rollup()`을 명시합니다. ### 4. 정리 ```sql DROP ROLLUP ch6_basic_ru; DROP TABLE ch6_basic; ``` ## 도입 전 확인 대표 태그 수, 입력량, 조회 빈도와 허용 집계 지연을 정합니다. 필요한 가장 세밀한 구간과 원본 보존 기간을 먼저 결정하고 [계층 설계](../target-tag-table-design/)로 이어갑니다. 긴 기간의 성능은 운영과 유사한 데이터로 측정하며 이 작은 표본의 시간으로 추정하지 않습니다. --- title: "6.2 ROLLUP 대상 TAG 테이블 설계" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/target-tag-table-design/ language: kr kind: page --- # 6.2 ROLLUP 대상 TAG 테이블 설계 ## ON과 FROM `ON source(column)`은 원본 시간축 TAG의 컬럼을 집계합니다. `FROM rollup_name`은 기존 일반/확장 ROLLUP의 통계를 더 큰 구간으로 합칩니다. Custom의 대상도 TAG이지만, 다음 Custom 단계는 대상 TAG를 SELECT하는 INTO...AS 구문으로 구성합니다. ## 계층 실습 ```sql CREATE TAG TABLE ch6_design ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, quality INTEGER ); CREATE ROLLUP ch6_design_sec ON ch6_design(value) INTERVAL 1 SEC; CREATE ROLLUP ch6_design_min FROM ch6_design_sec INTERVAL 1 MIN; CREATE ROLLUP ch6_design_hour FROM ch6_design_min INTERVAL 1 HOUR; INSERT INTO ch6_design VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1); INSERT INTO ch6_design VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1); INSERT INTO ch6_design VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1); INSERT INTO ch6_design VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1); EXEC TABLE_FLUSH(ch6_design); ALTER ROLLUP ch6_design_sec FORCE; ALTER ROLLUP ch6_design_min FORCE; ALTER ROLLUP ch6_design_hour FORCE; SELECT name, rollup('hour', 1, time) AS bucket, SUM(value), COUNT(value), AVG(value) FROM ch6_design GROUP BY name, bucket ORDER BY name, bucket; ``` TEMP_01은 합계 60, 건수 3, 평균 20이고 TEMP_02는 100, 1, 100입니다. 하위부터 FORCE해야 상위가 새로 생성된 하위 결과까지 처리할 수 있습니다. ### 계층 제약 - 상위 간격은 소스 간격보다 커야 하며 정수배여야 합니다. 같은 간격도 허용되지 않습니다. - 일반 ROLLUP에서 FROM으로 확장 ROLLUP으로 바꾸거나 그 반대로 바꿀 수 없습니다. EXTENSION 속성을 계층에서 일치시킵니다. - JSON 경로·문서 모드도 소스와 맞아야 합니다. - 필요한 조회의 최소 구간보다 거친 통계로 더 세밀한 원본을 복원할 수 없습니다. 다음은 각각 의도적으로 실패하는 생성 예제입니다. ```sql CREATE ROLLUP ch6_design_bad_same FROM ch6_design_min INTERVAL 1 MIN; CREATE ROLLUP ch6_design_bad_divisor FROM ch6_design_min INTERVAL 90 SEC; CREATE ROLLUP ch6_design_bad_ext FROM ch6_design_sec INTERVAL 1 MIN EXTENSION; ``` ## 간격과 저장량 결정 모든 태그가 모든 구간에 값을 갖는다고 가정하면 논리 버킷 수는 대략 `(보관 시간 / 버킷 간격) × 태그 수`입니다. 태그 1만 개의 1초 버킷을 365일 보관하면 약 3,154억 버킷입니다. 실제 저장 행 수는 부분 집계·빈 구간·컬럼 수의 영향을 받고 디스크 크기는 압축과 저장 부가 비용까지 측정해야 합니다. 초 단위 관측을 분 단위로만 조회한다면 처음부터 모든 초 계층이 필요한지 검토합니다. 생성 간격은 SEC/MIN/HOUR로 표현하지만 DAY/WEEK/MONTH/YEAR는 조회 버킷 단위입니다. 특히 일 조회에 24 HOUR 저장 간격을 그대로 적용할 수 있다고 가정하지 말고 [후보 선택 규칙](../query-syntax-rollup/)을 확인합니다. 새 ROLLUP은 소스에 남아 있는 기존 데이터도 초기 집계하므로 초기 처리량과 gap을 확인합니다. 원본 보정은 FORCE로 되감기지 않습니다. [REBUILD 지원 범위](../rollup-rebuild/)를 따릅니다. ## 정리 ```sql DROP ROLLUP ch6_design_hour; DROP ROLLUP ch6_design_min; DROP ROLLUP ch6_design_sec; DROP TABLE ch6_design; ``` --- title: "6.3 ROLLUP 생성과 삭제" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/create-delete-rollup/ language: kr kind: page --- # 6.3 ROLLUP 생성과 삭제 ## 생성 구문 ```text CREATE ROLLUP [IF NOT EXISTS] name ON source_tag [(column_or_json_path)] INTERVAL n (SEC|MIN|HOUR) [WAKEUP INTERVAL m (SEC|MIN|HOUR)] [EXTENSION] [WHERE predicate]; CREATE ROLLUP [IF NOT EXISTS] name FROM source_rollup INTERVAL n (SEC|MIN|HOUR) [WAKEUP INTERVAL m (SEC|MIN|HOUR)] [EXTENSION] [WHERE predicate]; ``` EXTENSION 뒤에 별도 확장 이름은 쓰지 않습니다. CREATE의 단위는 SEC/MIN/HOUR이며, 조회 함수의 DAY/MONTH 등과 구분합니다. 간격은 양수이며 현행 검증 상한은 365일에 해당하는 간격입니다. 소스·계층·집계 모드의 조건도 함께 만족해야 합니다. | 대상 | 조건 | |---|---| | 일반 숫자 컬럼 | 지원 숫자 타입을 지정; SUMMARIZED는 필수가 아님 | | JSON 경로 | 해당 JSON 컬럼과 유효한 경로 지정 | | JSON 문서 전체 | JSON SUMMARIZED 컬럼 필요 | | WITH ROLLUP 자동 생성 | 세 번째 SUMMARIZED 컬럼 필요 | | METADATA·거리축·비TAG | 일반 ROLLUP 대상이 아님 | ## 생성·중복 확인·조회 실습 ```sql CREATE TAG TABLE ch6_create ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, quality INTEGER ); CREATE ROLLUP IF NOT EXISTS ch6_create_ru ON ch6_create(value) INTERVAL 1 MIN; CREATE ROLLUP IF NOT EXISTS ch6_create_ru ON ch6_create(value) INTERVAL 1 MIN; INSERT INTO ch6_create VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1); INSERT INTO ch6_create VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1); INSERT INTO ch6_create VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1); INSERT INTO ch6_create VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1); EXEC TABLE_FLUSH(ch6_create); ALTER ROLLUP ch6_create_ru FORCE; SELECT DISTINCT ROLLUP_NAME, COLUMN_NAME, INTERVAL_TIME, WAKEUP_INTERVAL FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CREATE_RU'; SELECT rollup('min', 1, time) AS bucket, AVG(value) FROM ch6_create WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; ``` 같은 이름의 재생성은 기존 정의를 유지합니다. IF NOT EXISTS는 정의 변경·동일성 확인 기능이 아니며, 잘못된 SQL이나 소스 검증을 모두 생략해 주는 옵션도 아닙니다. 두 간격 컬럼은 60000ms이고 조회 평균은 00:00에 15, 00:01에 30입니다. IF NOT EXISTS 없이 같은 이름을 생성하면 오류입니다. 정상 실습과 분리해 확인합니다. ```sql CREATE ROLLUP ch6_create_ru ON ch6_create(value) INTERVAL 1 MIN; ``` ## WITH ROLLUP 자동 생성 다음은 별도 테이블입니다. SEC부터 MIN·HOUR까지의 기본 계층을 자동 생성합니다. ```sql CREATE TAG TABLE ch6_auto ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ) WITH ROLLUP (SEC); SELECT DISTINCT ROLLUP_NAME, ROOT_TABLE, INTERVAL_TIME, EXT_TYPE FROM V$ROLLUP WHERE ROOT_TABLE = 'CH6_AUTO' ORDER BY INTERVAL_TIME; ``` INTERVAL_TIME이 1000·60000·3600000인 세 행이 조회됩니다. EXTENSION 자동 생성은 `WITH ROLLUP (SEC) EXTENSION` 형태입니다. 실제 생성 이름은 V$ROLLUP에서 확인하고, 이름 충돌을 자동으로 해소한다고 가정하지 않습니다. 인자에 따라 만들어지는 계층이 달라집니다. `(SEC)`는 SEC·MIN·HOUR 세 개를, `(MIN)`은 MIN·HOUR 두 개를, `(HOUR)`는 HOUR 하나를 만들며, 인자를 생략하면 `(SEC)`와 같습니다. 이때 첫 단계만 원본 TAG를 소스로 삼고 다음 단계는 직전 ROLLUP을 소스로 삼는 계층으로 연결됩니다. 인자에는 SEC·MIN·HOUR만 사용할 수 있으며, 조회 함수가 받는 DAY 같은 단위를 지정하면 오류입니다. ## 삭제와 정의 변경 다른 ROLLUP이 참조하는 소스는 상위 의존 객체부터 제거합니다. Custom 대상 TAG는 작업을 삭제하기 전에 DROP할 수 없습니다. 정의를 바꾸려면 읽는 애플리케이션과 재집계 시간을 고려해 새 객체로 전환하거나 기존 정의를 제거한 뒤 다시 생성합니다. ```sql DROP ROLLUP ch6_create_ru; DROP TABLE ch6_create; DROP TABLE ch6_auto CASCADE; ``` 마지막 CASCADE는 이 실습의 자동 ROLLUP도 함께 제거합니다. 일반 운영 정리의 기본값으로 사용하지 않습니다. Custom 소스 CASCADE는 관련 작업을 제거하더라도 사용자 대상 TAG까지 자동 삭제하는 것으로 해석하지 마십시오. 조건·확장·JSON·Custom 실습은 각 절에서 독립적으로 제공합니다. 전체 구문은 [SQL 레퍼런스](../../reference/sql/syntax/rollup-syntax/)를 참고합니다. --- title: "6.4 ROLLUP 조회 문법" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/query-syntax-rollup/ language: kr kind: page --- # 6.4 ROLLUP 조회 문법 ## 명시적인 ROLLUP 조회 ```text rollup(time_unit, period, basetime_column [, origin]) ``` | 인자 | 계약 | |---|---| | time_unit | SECOND/SEC, MINUTE/MIN, HOUR, DAY, WEEK, MONTH, YEAR; 대소문자 무관 | | period | 양의 정수 리터럴; 컬럼이나 매개변수 자리표시자가 아님 | | basetime_column | 원본 시간축 TAG의 BASETIME 컬럼 | | origin | 생략 시 시간대 오프셋을 반영한 기본 기준점; 명시 시 단위별 제약 확인 | 반환값은 DATETIME 버킷입니다. 저장된 집계의 최소 간격보다 세밀한 결과를 복원할 수는 없습니다. 적용 가능한 ROLLUP이 없으면 원시 스캔으로 자동 전환하지 않고 오류가 발생합니다. 원본 집계가 필요하면 별도의 DATE_TRUNC/DATE_BIN + GROUP BY 쿼리를 작성합니다. ## 준비와 기본 조회 ```sql CREATE TAG TABLE ch6_query ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, quality INTEGER ); CREATE ROLLUP ch6_query_sec ON ch6_query(value) INTERVAL 1 SEC; INSERT INTO ch6_query VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1); INSERT INTO ch6_query VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1); INSERT INTO ch6_query VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1); INSERT INTO ch6_query VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1); EXEC TABLE_FLUSH(ch6_query); ALTER ROLLUP ch6_query_sec FORCE; SELECT name, rollup('min', 1, time) AS bucket, COUNT(value), SUM(value), MIN(value), MAX(value), AVG(value) FROM ch6_query WHERE time >= TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND time < TO_DATE('2026-01-01 00:02:00', 'YYYY-MM-DD HH24:MI:SS') GROUP BY name, bucket ORDER BY name, bucket; ``` TEMP_01의 00:00은 COUNT=2, SUM=30, AVG=15이고 00:01은 1, 30, 30입니다. TEMP_02는 00:00의 1, 100, 100입니다. SELECT에서 name을 반환한다면 GROUP BY에도 name을 넣습니다. name을 빼면 여러 태그를 합친 집계이므로 단위가 다른 센서를 섞지 않습니다. ## 후보 선택과 힌트 1. ROLLUP_TABLE 힌트가 있으면 해당 후보의 호환성을 검사합니다. 2. 자동 선택은 집계 컬럼·JSON 경로·모드와 요청 간격이 맞는 후보를 찾습니다. 3. 조건 없는 후보를 먼저 찾고, 없을 때 조건 후보를 포함해 탐색합니다. 4. 적용 가능한 가장 큰 간격을 선택하며 같은 간격에서는 먼저 등록된 후보가 유지됩니다. 일반/확장 여부만으로 “일반이 항상 우선”이라고 단정하지 않습니다. 조건 ROLLUP만 있을 때는 필터된 데이터가 자동 선택될 수 있으므로 결과가 나타내는 표본 집합을 확인합니다. 반드시 특정 집계를 사용해야 하면 힌트를 명시합니다. ```sql SELECT /*+ ROLLUP_TABLE(ch6_query_sec) */ rollup('min', 1, time) AS bucket, AVG(value) FROM ch6_query WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; ``` ### 저장 후보의 간격과 조회 버킷 현재 후보 간격 검사는 SEC 요청을 period초, MIN 요청을 period분으로 계산합니다. HOUR와 DAY/WEEK/MONTH/YEAR 요청은 후보 선택 단계에서 period시간을 기준으로 검사하고, 선택된 통계를 요청한 달력·시간 버킷으로 다시 합칩니다. 따라서 `rollup('day', 1, time)`을 위해 24 HOUR ROLLUP을 만들면 자동으로 사용할 수 있다고 가정하면 안 됩니다. 해당 요청은 1시간 기준에 맞는 HOUR/MIN/SEC 후보를 검토합니다. 생성 INTERVAL과 결과 버킷의 의미를 구분하고 실제 실행 계획을 확인합니다. ## 집계 함수와 표본 일반 숫자 ROLLUP은 MIN, MAX, SUM, COUNT, AVG, SUMSQ를 지원합니다. ROLLUP 조회의 FIRST/LAST에는 EXTENSION이 필요합니다. Custom 결과는 일반 TAG에 저장되므로 [Custom 재집계](../custom-rollup/)의 합계·건수 규칙을 사용합니다. JSON 문서 전체 집계의 COUNT도 [JSON 절](../json-summarized-rollup/)에서 별도로 설명합니다. ### SUMSQ와 분산·표준편차 ROLLUP 조회는 STDDEV, STDDEV_POP, VARIANCE, VAR_POP을 직접 지원하지 않습니다. `rollup()` 조회에서 이 함수들을 사용하면 `ERR-02816: Only rollup column with aggregate function can be referenced in ROLLUP SELECT query.` 가 발생합니다. 표준편차와 분산은 구간별 결과를 다시 더할 수 없기 때문입니다. 두 구간의 표준편차를 평균해도 전체 구간의 표준편차가 되지 않습니다. 대신 ROLLUP은 값의 제곱합인 SUMSQ를 COUNT, SUM과 함께 저장합니다. 이 세 값은 모두 더할 수 있으므로 저장 간격보다 큰 버킷으로 다시 합쳐도 유효하며, 조회 시점에 분산과 표준편차를 계산할 수 있습니다. SUMSQ는 일반 ROLLUP과 EXTENSION ROLLUP 모두에 포함됩니다. | 값 | 계산식 | |---|---| | 모분산 | `SUMSQ/N - (SUM/N)^2` | | 모표준편차 | 모분산의 제곱근 | | 표본분산 | `(SUMSQ - SUM^2/N) / (N-1)` | | 표본표준편차 | 표본분산의 제곱근 | N은 `COUNT(value)`이며 NULL을 제외한 유효 값의 개수입니다. ROLLUP 조회 블록에는 지원되는 집계만 두고, 분산과 표준편차는 인라인 뷰 밖에서 계산합니다. COUNT, SUM, SUMSQ를 한 번만 읽고 파생 계산을 분리하므로 계산식을 바꿔도 ROLLUP 조회 부분은 그대로 사용할 수 있습니다. ```sql SELECT bucket, n, s, sq, sq/n - POWER(s/n, 2) AS var_pop, SQRT(sq/n - POWER(s/n, 2)) AS stddev_pop FROM ( SELECT rollup('min', 1, time) AS bucket, COUNT(value) AS n, SUM(value) AS s, SUMSQ(value) AS sq FROM ch6_query WHERE name = 'TEMP_01' GROUP BY bucket ) t ORDER BY bucket; ``` 00:00 버킷은 값 10과 20이므로 n=2, s=30, sq=500이고 모분산 25, 모표준편차 5입니다. 00:01 버킷은 값이 하나이므로 모분산과 모표준편차가 0입니다. 표본분산은 분모가 `N-1`이므로 값이 하나인 버킷을 먼저 걸러야 합니다. 이 판단도 인라인 뷰 밖에서 수행합니다. `ELSE NULL`을 명시하면 `ERR-02042`가 발생하므로 `ELSE`를 생략합니다. ```sql SELECT bucket, n, CASE WHEN n > 1 THEN (sq - POWER(s, 2)/n) / (n - 1) END AS var_samp FROM ( SELECT rollup('min', 1, time) AS bucket, COUNT(value) AS n, SUM(value) AS s, SUMSQ(value) AS sq FROM ch6_query WHERE name = 'TEMP_01' GROUP BY bucket ) t ORDER BY bucket; ``` 00:00 버킷의 표본분산은 50이고, 값이 하나인 00:01 버킷은 NULL입니다. 값이 하나인 구간을 결과에서 빼려면 인라인 뷰 밖에 `WHERE n > 1`을 사용합니다. 원본 테이블의 `VARIANCE`와 `STDDEV`는 같은 구간에서 NULL이 아니라 0을 반환하므로, 두 결과를 함께 사용할 때는 표시 정책을 맞춥니다. 위 예제의 값에서는 원본 테이블에 `VAR_POP`, `STDDEV_POP`, `VARIANCE`, `STDDEV`를 직접 사용한 결과와 같습니다. 다만 두 계산식은 평균이 크고 편차가 작을수록 자리수가 손실됩니다. 예를 들어 값이 100000 부근에서 소수점 이하로만 흔들리면 `SUMSQ/N`과 `(SUM/N)^2`가 거의 같은 크기여서 뺄셈 결과의 유효 자리수가 줄어듭니다. 부동소수 오차로 분산이 아주 작은 음수가 되면 `SQRT`의 결과도 유효하지 않습니다. 정밀도가 중요한 구간에서는 원본 테이블의 `STDDEV`, `VAR_POP` 결과와 비교해 사용할 수 있는 범위를 확인합니다. ## 달력 단위와 origin 같은 준비 데이터를 사용해 월 단위 결과와 월요일 기준 주 단위를 확인합니다. ```sql SELECT rollup('month', 1, time, '2000-01-01 00:00:00') AS bucket, SUM(value), COUNT(value), AVG(value) FROM ch6_query WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; SELECT rollup('week', 1, time, '1970-01-05 00:00:00') AS bucket, AVG(value) FROM ch6_query WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; ``` 월 조회는 2026-01-01 버킷에 SUM=60, COUNT=3, AVG=20을 반환합니다. 월·년 origin은 시간대 해석 후 월의 1일이어야 하며 결과는 해당 월 경계의 자정입니다. 임의 날짜나 시각 오프셋으로 월간 업무 시작 시각을 옮기는 기능으로 해석하지 않습니다. 일·주 같은 고정 간격의 origin과 월·년의 달력 계산을 구분합니다. 문자열의 의미는 접속 시간대와 함께 확인합니다. DST를 자동으로 원하는 업무 달력에 맞춰 준다고 가정하지 말고 경계 전후의 원본 DATE_BIN 집계와 비교합니다. 저장 집계를 쪼개야 하는 origin이나 조회 경계에는 원본과 동일한 결과를 기대할 수 있는지 검증합니다. ## 정리 ```sql DROP ROLLUP ch6_query_sec; DROP TABLE ch6_query; ``` --- title: "6.5 조건 ROLLUP" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/conditional-rollup/ language: kr kind: page --- # 6.5 조건 ROLLUP ## 원본을 필터한 뒤 집계 조건 ROLLUP은 원본의 품질·상태 조건을 적용한 통계를 유지합니다. 이미 계산한 평균에서 불량 표본만 나중에 제거하는 것과 다릅니다. 조건에 사용한 quality 컬럼 자체가 집계 결과에 보존되는 것도 아닙니다. ## 1. 테이블과 두 ROLLUP 준비 ```sql CREATE TAG TABLE ch6_condition ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, quality INTEGER ); CREATE ROLLUP ch6_condition_all ON ch6_condition(value) INTERVAL 1 MIN EXTENSION; CREATE ROLLUP ch6_condition_good ON ch6_condition(value) INTERVAL 1 MIN EXTENSION WHERE quality = 1; INSERT INTO ch6_condition VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1); INSERT INTO ch6_condition VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1); INSERT INTO ch6_condition VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1); INSERT INTO ch6_condition VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1); INSERT INTO ch6_condition VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:50', 'YYYY-MM-DD HH24:MI:SS'), 90.0, 0); EXEC TABLE_FLUSH(ch6_condition); ALTER ROLLUP ch6_condition_all FORCE; ALTER ROLLUP ch6_condition_good FORCE; ``` ## 2. 원본 조건과 ROLLUP 비교 ```sql SELECT DATE_TRUNC('minute', time) AS bucket, COUNT(value), AVG(value), MIN(value), MAX(value), FIRST(time, value), LAST(time, value) FROM ch6_condition WHERE name = 'TEMP_01' AND quality = 1 GROUP BY bucket ORDER BY bucket; SELECT /*+ ROLLUP_TABLE(ch6_condition_good) */ rollup('min', 1, time) AS bucket, COUNT(value), AVG(value), MIN(value), MAX(value), FIRST(time, value), LAST(time, value) FROM ch6_condition WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; SELECT /*+ ROLLUP_TABLE(ch6_condition_all) */ rollup('min', 1, time) AS bucket, COUNT(value), AVG(value), MIN(value), MAX(value), FIRST(time, value), LAST(time, value) FROM ch6_condition WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; ``` | 버킷·집합 | COUNT | AVG | MIN | MAX | FIRST | LAST | |---|---:|---:|---:|---:|---:|---:| | 00:00 전체 | 3 | 40 | 10 | 90 | 10 | 90 | | 00:00 quality=1 | 2 | 15 | 10 | 20 | 10 | 20 | | 00:01 두 집합 공통 | 1 | 30 | 30 | 30 | 30 | 30 | 이 표본은 불량값이 구간의 마지막에 있으므로 LAST도 달라집니다. FIRST/LAST는 시각·값 쌍이 아니라 선택한 value를 반환합니다. ## 명시적인 후보 선택 이 예제는 전체 통계와 정상 통계의 결과 집합을 고정하기 위해 힌트를 사용합니다. 자동 선택은 조건 없는 후보를 우선하지만 조건 후보만 있을 때는 필터된 통계를 선택할 수 있습니다. “조건 ROLLUP은 항상 무시된다”거나 “EXTENSION에는 항상 힌트가 필수”라는 규칙으로 해석하지 않습니다. ## 문법과 제약 일반 ROLLUP의 필터는 INTERVAL·EXTENSION 뒤의 WHERE에 작성합니다. 비교, BETWEEN, IN, LIKE, 논리 연산과 지원 스칼라 함수를 사용할 수 있지만 서브쿼리, 집계 함수와 태그 이름 PRIMARY KEY 조건은 지원하지 않습니다. Custom은 SELECT 내부 WHERE를 사용하므로 구문을 섞지 않습니다. ## 상태 확인과 정리 ```sql SELECT DISTINCT ROLLUP_NAME, PREDICATE, ENABLED FROM V$ROLLUP WHERE ROOT_TABLE = 'CH6_CONDITION'; DROP ROLLUP ch6_condition_good; DROP ROLLUP ch6_condition_all; DROP TABLE ch6_condition; ``` 업무 품질 기준이 바뀌면 조건과 재집계 계획도 갱신합니다. 기존 집계가 새로운 조건으로 자동 전환되지는 않습니다. [제어](../ingestion-control-rollup/)와 [재구성 범위](../rollup-rebuild/)를 확인합니다. --- title: "6.6 Custom ROLLUP" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/custom-rollup/ language: kr kind: page --- # 6.6 Custom ROLLUP Standard Edition 전용 ## Custom과 일반 ROLLUP Custom ROLLUP은 SELECT의 증분 집계 결과를 미리 만든 대상 TAG에 누적합니다. 일반 ROLLUP의 내부 통계를 `rollup()`으로 읽는 방식과 달리, 대상 TAG를 직접 조회하면서 부분 집계를 다시 합쳐야 합니다. Cluster Edition에서는 생성할 수 없습니다. ```text CREATE ROLLUP [IF NOT EXISTS] name INTO (destination_tag) AS (SELECT ... FROM source_tag [WHERE ...] GROUP BY ...) INTERVAL n (SEC|MIN|HOUR) [WAKEUP INTERVAL m (SEC|MIN|HOUR)]; ``` 소스는 시간축 TAG 하나이며 JOIN·FROM 서브쿼리는 사용할 수 없습니다. 대상은 미리 만든 TAG이고 SELECT의 컬럼 순서·타입과 호환되어야 합니다. SELECT 내부 WHERE는 사용할 수 있지만 BASETIME 직접 조건은 허용되지 않습니다. 일반 ROLLUP처럼 INTERVAL 뒤에 외부 WHERE를 붙이지 않습니다. 생성 간격과 SELECT가 계산하는 시간 버킷을 일치시키십시오. 작업이 새 입력만 처리하므로 같은 버킷에 여러 결과 행이 있을 수 있습니다. 행 수를 버킷 수로 해석하지 않습니다. ## 1. 합계·유효 건수 실습 ```sql CREATE TAG TABLE ch6_custom_src ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ); CREATE TAG TABLE ch6_custom_dst ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, sum_value DOUBLE, valid_count LONG, total_count LONG ); CREATE ROLLUP ch6_custom_ru INTO (ch6_custom_dst) AS ( SELECT name, DATE_TRUNC('minute', time) AS time, SUM(value), COUNT(value), COUNT(*) FROM ch6_custom_src GROUP BY name, time ) INTERVAL 1 MIN; INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:05', 'YYYY-MM-DD HH24:MI:SS'), 10); EXEC TABLE_FLUSH(ch6_custom_src); ALTER ROLLUP ch6_custom_ru FORCE; INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:10', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:15', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:20', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:25', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:35', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:40', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:45', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch6_custom_src VALUES ('S1', TO_DATE('2026-01-01 00:00:55', 'YYYY-MM-DD HH24:MI:SS'), NULL); EXEC TABLE_FLUSH(ch6_custom_src); ALTER ROLLUP ch6_custom_ru FORCE; SELECT name, DATE_TRUNC('minute', time) AS bucket, SUM(value), COUNT(value), COUNT(*), AVG(value) FROM ch6_custom_src GROUP BY name, bucket ORDER BY name, bucket; SELECT name, time, SUM(sum_value) AS sum_value, SUM(valid_count) AS valid_count, SUM(total_count) AS total_count, CASE WHEN SUM(valid_count) = 0 THEN NULL ELSE SUM(sum_value) / SUM(valid_count) END AS avg_value FROM ch6_custom_dst GROUP BY name, time ORDER BY name, time; ``` 한 버킷의 합계는 180, 유효 값은 10개, 전체 행은 11개, 평균은 18입니다. 부분 평균 10과 20을 단순 평균한 15와 다릅니다. NULL을 평균의 분모에 넣지 않도록 COUNT(value)를 사용하고, NULL 포함 행 수는 COUNT(*)로 따로 보관합니다. 모든 값이 NULL인 버킷까지 처리하려면 유효 건수 0에서 나누지 않는 결과 정책도 정합니다. ### 표본 비율과 시간 가동률 조건을 만족한 표본 수를 전체 표본 수로 나눈 값은 표본 비율입니다. 시간 가동률로 해석하려면 관측 간격·결측 처리와 상태 지속 시간을 반영해야 합니다. 비율도 부분 비율끼리 평균하지 말고 분자와 분모를 각각 합산합니다. ## 2. OHLCV와 1분→10분 Custom 계층 별도 실습입니다. FIRST/LAST 재집계를 위해 원본의 첫·마지막 관측 시각도 저장합니다. ```sql CREATE TAG TABLE ch6_ticks ( code VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, price DOUBLE, volume DOUBLE ); CREATE TAG TABLE ch6_candle_min ( code VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, open_price DOUBLE, high_price DOUBLE, low_price DOUBLE, close_price DOUBLE, volume DOUBLE, cnt LONG, firsttime DATETIME, lasttime DATETIME ); CREATE TAG TABLE ch6_candle_10m ( code VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, open_price DOUBLE, high_price DOUBLE, low_price DOUBLE, close_price DOUBLE, volume DOUBLE, cnt LONG, firsttime DATETIME, lasttime DATETIME ); CREATE ROLLUP ch6_candle_ru_min INTO (ch6_candle_min) AS ( SELECT code, DATE_TRUNC('minute', time) AS time, FIRST(time, price), MAX(price), MIN(price), LAST(time, price), SUM(volume), COUNT(*), MIN(time), MAX(time) FROM ch6_ticks GROUP BY code, time ) INTERVAL 1 MIN; CREATE ROLLUP ch6_candle_ru_10m INTO (ch6_candle_10m) AS ( SELECT code, DATE_BIN('min', 10, time, TO_DATE('2000-01-01 00:00:00')) AS time, FIRST(firsttime, open_price), MAX(high_price), MIN(low_price), LAST(lasttime, close_price), SUM(volume), SUM(cnt), MIN(firsttime), MAX(lasttime) FROM ch6_candle_min GROUP BY code, time ) INTERVAL 10 MIN; INSERT INTO ch6_ticks VALUES ('AAPL', TO_DATE('2026-01-01 09:00:00'), 100, 2); INSERT INTO ch6_ticks VALUES ('AAPL', TO_DATE('2026-01-01 09:00:30'), 105, 3); INSERT INTO ch6_ticks VALUES ('AAPL', TO_DATE('2026-01-01 09:01:00'), 103, 1); INSERT INTO ch6_ticks VALUES ('AAPL', TO_DATE('2026-01-01 09:01:30'), 99, 4); EXEC TABLE_FLUSH(ch6_ticks); ALTER ROLLUP ch6_candle_ru_min FORCE; EXEC TABLE_FLUSH(ch6_candle_min); ALTER ROLLUP ch6_candle_ru_10m FORCE; SELECT code, time, FIRST(firsttime, open_price), MAX(high_price), MIN(low_price), LAST(lasttime, close_price), SUM(volume), SUM(cnt) FROM ch6_candle_10m GROUP BY code, time ORDER BY code, time; ``` 09:00의 10분 버킷은 Open=100, High=105, Low=99, Close=99, volume=10, cnt=4입니다. 가격·거래량이 NULL인 경우에는 어떤 행의 시각과 값을 사용할지 별도 규칙과 테스트가 필요합니다. 동일 시각의 여러 거래도 업무상 순서가 있다면 추가 식별 기준을 설계합니다. 이 10분 Custom은 생성·조회 예제입니다. 현행 REBUILD의 Custom 시간 범위 처리에 지원되는 간격과 같다고 가정하지 마십시오. [REBUILD 제한](../rollup-rebuild/)을 확인합니다. ## 상태와 정리 생성 후 자동 시작되므로 START를 즉시 반복하지 않습니다. STOP/START와 FORCE는 작업 상태에 맞춰 호출합니다. 작업이 존재하는 대상 TAG의 DROP은 차단됩니다. ```sql SELECT DISTINCT ROLLUP_NAME, ROLLUP_TABLE, ROOT_TABLE, EXT_TYPE, INTERVAL_TIME, WAKEUP_INTERVAL FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CUSTOM_RU'; DROP ROLLUP ch6_candle_ru_10m; DROP ROLLUP ch6_candle_ru_min; DROP TABLE ch6_candle_10m; DROP TABLE ch6_candle_min; DROP TABLE ch6_ticks; DROP ROLLUP ch6_custom_ru; DROP TABLE ch6_custom_dst; DROP TABLE ch6_custom_src; ``` EXT_TYPE=2는 Custom이며 PREDICATE에는 SELECT 본문이 기록됩니다. 정리할 때는 실행한 실습의 객체만 대상으로 합니다. 원본 보정과 상위 재집계의 재시도·완료 확인은 [제어와 상태](../ingestion-control-rollup/) 및 REBUILD 절차를 따릅니다. --- title: "6.7 확장 ROLLUP과 FIRST/LAST" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/extension-rollup/ language: kr kind: page --- # 6.7 확장 ROLLUP과 FIRST/LAST ## EXTENSION과 원본 FIRST/LAST의 차이 EXTENSION은 ROLLUP에 첫·마지막 값과 관련 시각 정보를 추가합니다. 일반 원본 GROUP BY에서 FIRST/LAST를 사용하는 것과, 저장된 ROLLUP에서 FIRST/LAST를 조회하는 것은 다릅니다. 후자에는 적용 가능한 확장 ROLLUP이 필요합니다. EXTENSION이 추가하는 것은 첫·마지막 값과 시각뿐입니다. MIN, MAX, SUM, COUNT와 제곱합인 SUMSQ는 일반 ROLLUP에도 함께 저장됩니다. SUMSQ로 분산과 표준편차를 계산하는 방법은 [SUMSQ와 분산·표준편차](../query-syntax-rollup/#query-sumsq-stddev-rollup)를 참고합니다. ## 준비와 입력 ```sql CREATE TAG TABLE ch6_ext ( code VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, price DOUBLE ); CREATE ROLLUP ch6_ext_first ON ch6_ext(price) INTERVAL 1 MIN EXTENSION; CREATE ROLLUP ch6_ext_plain ON ch6_ext(price) INTERVAL 1 MIN; INSERT INTO ch6_ext VALUES ('AAPL', TO_DATE('2026-01-01 09:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100); INSERT INTO ch6_ext VALUES ('AAPL', TO_DATE('2026-01-01 09:00:10', 'YYYY-MM-DD HH24:MI:SS'), 105); INSERT INTO ch6_ext VALUES ('AAPL', TO_DATE('2026-01-01 09:00:20', 'YYYY-MM-DD HH24:MI:SS'), 99); INSERT INTO ch6_ext VALUES ('AAPL', TO_DATE('2026-01-01 09:00:30', 'YYYY-MM-DD HH24:MI:SS'), 103); EXEC TABLE_FLUSH(ch6_ext); ALTER ROLLUP ch6_ext_first FORCE; ALTER ROLLUP ch6_ext_plain FORCE; ``` ## 원본과 OHLC 비교 ```sql SELECT DATE_TRUNC('minute', time) AS bucket, FIRST(time, price) AS open_price, MAX(price) AS high_price, MIN(price) AS low_price, LAST(time, price) AS close_price FROM ch6_ext WHERE code = 'AAPL' GROUP BY bucket ORDER BY bucket; SELECT /*+ ROLLUP_TABLE(ch6_ext_first) */ rollup('min', 1, time) AS bucket, FIRST(time, price) AS open_price, MAX(price) AS high_price, MIN(price) AS low_price, LAST(time, price) AS close_price FROM ch6_ext WHERE code = 'AAPL' GROUP BY bucket ORDER BY bucket; ``` 두 쿼리는 09:00 버킷에 Open=100, High=105, Low=99, Close=103을 반환합니다. ROLLUP의 FIRST/LAST 첫 인자는 BASETIME, 두 번째는 집계 대상 컬럼을 사용합니다. 같은 시각의 여러 값은 추가적인 업무 순서가 필요한 경우를 별도로 설계합니다. ## 일반 후보와 확장 후보가 함께 있을 때 일반 ROLLUP이 확장 ROLLUP보다 무조건 우선하는 것은 아닙니다. 같은 조건·간격의 후보는 등록 순서의 영향을 받습니다. 이 예제는 확장을 먼저 만들었지만, 결과가 특정 후보에 의존해야 한다면 위처럼 명시합니다. 확장만 적용 가능한 환경에서는 힌트 없이 선택될 수도 있습니다. 다음은 일반 ROLLUP을 강제하므로 의도적으로 실패하는 조회입니다. ```sql SELECT /*+ ROLLUP_TABLE(ch6_ext_plain) */ rollup('min', 1, time) AS bucket, FIRST(time, price) FROM ch6_ext WHERE code = 'AAPL' GROUP BY bucket; ``` 자동 계층에 확장이 필요하면 별도 테이블의 CREATE에서 `WITH ROLLUP (SEC) EXTENSION`을 사용합니다. FROM으로 계층을 만들 때도 확장 속성을 일치시켜야 합니다. Custom OHLCV 재집계는 [Custom 실습](../custom-rollup/)을 참고합니다. ## 정리 ```sql DROP ROLLUP ch6_ext_plain; DROP ROLLUP ch6_ext_first; DROP TABLE ch6_ext; ``` --- title: "6.8 JSON SUMMARIZED ROLLUP" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/json-summarized-rollup/ language: kr kind: page --- # 6.8 JSON SUMMARIZED ROLLUP ## JSON 경로와 문서 전체 집계 JSON 경로 ROLLUP은 지정 경로에서 숫자 값을 집계합니다. 문서 전체 ROLLUP은 JSON SUMMARIZED 컬럼의 숫자 경로별 통계를 유지합니다. 경로가 없는 값, JSON null, SQL NULL과 배열을 같은 표본으로 해석하지 않습니다. ## 준비 ```sql CREATE TAG TABLE ch6_json ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value JSON SUMMARIZED ); CREATE ROLLUP ch6_json_metric ON ch6_json(value.metric) INTERVAL 1 MIN; CREATE ROLLUP ch6_json_whole ON ch6_json(value) INTERVAL 1 MIN; INSERT INTO ch6_json VALUES ('S1', TO_DATE('2026-01-01 00:00:00'), '{"metric":10,"nested":{"x":2},"status":"OK","items":[1,2]}'); INSERT INTO ch6_json VALUES ('S1', TO_DATE('2026-01-01 00:00:10'), '{"metric":20,"nested":{"x":4},"status":"WARN","items":[3,4]}'); INSERT INTO ch6_json VALUES ('S1', TO_DATE('2026-01-01 00:00:20'), '{"metric":null,"status":false}'); INSERT INTO ch6_json VALUES ('S1', TO_DATE('2026-01-01 00:00:30'), NULL); EXEC TABLE_FLUSH(ch6_json); ALTER ROLLUP ch6_json_metric FORCE; ALTER ROLLUP ch6_json_whole FORCE; ``` ## 경로 집계 비교 ```sql SELECT DATE_TRUNC('minute', time) AS bucket, COUNT(JSON_EXTRACT_DOUBLE(value, '$.metric')), AVG(JSON_EXTRACT_DOUBLE(value, '$.metric')) FROM ch6_json WHERE name = 'S1' GROUP BY bucket ORDER BY bucket; SELECT /*+ ROLLUP_TABLE(ch6_json_metric) */ rollup('min', 1, time) AS bucket, COUNT(value.metric), AVG(value.metric) FROM ch6_json WHERE name = 'S1' GROUP BY bucket ORDER BY bucket; SELECT /*+ ROLLUP_TABLE(ch6_json_metric) */ rollup('min', 1, time) AS bucket, AVG(value->'$.metric') FROM ch6_json WHERE name = 'S1' GROUP BY bucket ORDER BY bucket; ``` metric의 유효 숫자 표본은 2개이고 평균은 15입니다. dot과 arrow는 같은 경로를 표현합니다. 특정 배열 요소를 경로로 지정하는 것과 문서 전체 집계가 배열을 자동 전개하는 것은 다릅니다. 경로 선언의 예로 `value.items[0]."metric-id"`를 사용할 수 있지만 해당 JSON 구조와 숫자 표본이 실제로 있는지 먼저 확인해야 합니다. ## 문서 전체 집계와 COUNT ```sql SELECT COUNT(*) AS raw_rows, COUNT(value) AS raw_documents FROM ch6_json; SELECT /*+ ROLLUP_TABLE(ch6_json_whole) */ rollup('min', 1, time) AS bucket, COUNT(value), AVG(value), MIN(value), MAX(value), SUM(value) FROM ch6_json WHERE name = 'S1' GROUP BY bucket ORDER BY bucket; ``` 원본 COUNT(*)는 4이고 COUNT(value)는 3입니다. 문서 전체 ROLLUP의 COUNT(value)는 저장된 문서 집계 건수를 합산합니다. 현재 이 집계의 건수는 원본 COUNT(*)로 만들어지므로 위처럼 숫자 경로를 가진 문서와 SQL NULL이 섞인 버킷에서는 4입니다. 일반 원본 COUNT(value)의 NULL 제외 규칙을 그대로 적용하면 안 됩니다. AVG 결과의 metric은 15, nested.x는 3이며 각 경로의 유효 숫자 표본으로 계산합니다. 문자열·불리언·JSON null·배열은 숫자 집계에서 제외되고 비숫자 경로는 null 또는 생략으로 나타날 수 있습니다. JSON 직렬화의 키 순서를 고정된 문자열 결과로 비교하지 않습니다. 숫자 경로가 전혀 없는 문서나 SQL NULL만 있는 구간은 별도로 표본을 만들어 확인합니다. JSON 경로 집계와 문서 전체 집계는 후보 모드가 다릅니다. 서로 다른 모드의 ROLLUP을 힌트로 강제해서 같은 결과가 된다고 가정하지 마십시오. 유효하지 않은 JSON은 입력 오류입니다. ## 정리 ```sql DROP ROLLUP ch6_json_whole; DROP ROLLUP ch6_json_metric; DROP TABLE ch6_json; ``` --- title: "6.9 ROLLUP 제어와 상태 확인" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/ingestion-control-rollup/ language: kr kind: page --- # 6.9 ROLLUP 제어와 상태 확인 ## 작업 상태와 처리 완료 ROLLUP은 생성 시 자동 시작됩니다. START를 즉시 반복하거나 이미 중지한 작업을 다시 STOP하면 상태 오류가 날 수 있습니다. 아래 실습은 생성 → STOP → 입력 → START → WAKEUP → FORCE 순서로 상태를 구분합니다. ```sql CREATE TAG TABLE ch6_control ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, quality INTEGER ); CREATE ROLLUP ch6_control_ru ON ch6_control(value) INTERVAL 1 MIN WAKEUP INTERVAL 10 SEC; ALTER ROLLUP ch6_control_ru STOP; INSERT INTO ch6_control VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1); INSERT INTO ch6_control VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1); INSERT INTO ch6_control VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1); INSERT INTO ch6_control VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1); EXEC TABLE_FLUSH(ch6_control); SELECT DISTINCT ROLLUP_NAME, ENABLED, INTERVAL_TIME, WAKEUP_INTERVAL FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CONTROL_RU'; ALTER ROLLUP ch6_control_ru START; ALTER ROLLUP ch6_control_ru WAKEUP; ALTER ROLLUP ch6_control_ru FORCE; SELECT rollup('min', 1, time) AS bucket, AVG(value) FROM ch6_control WHERE name = 'TEMP_01' GROUP BY bucket ORDER BY bucket; ``` 중지 상태의 ENABLED는 0, INTERVAL_TIME은 60000ms, WAKEUP_INTERVAL은 10000ms입니다. 마지막 조회는 TEMP_01의 00:00 평균 15와 00:01 평균 30을 반환합니다. | 명령 | 목적 | 완료 의미 | |---|---|---| | STOP | 작업 중지 | 이후 처리하지 않은 입력분은 남아 있음 | | START | 중지한 작업 재개 | 처리 위치부터 계속 진행 | | WAKEUP | 작업을 깨움 | 처리 완료를 기다리지 않음 | | FORCE | 대상 소스의 처리 범위를 따라잡도록 기다림 | 과거 수정분의 재계산이나 미래 입력 완료는 아님 | | ROLLUP_REBUILD | 지원 대상의 과거 버킷 재계산 | 원본 보정 후 집계를 다시 구성 | SQL ALTER 대신 이름을 지정한 `EXEC ROLLUP_START(name)`, `ROLLUP_STOP(name)`, `ROLLUP_FORCE(name)`도 사용할 수 있습니다. 동일 전환을 두 형식으로 연속 실행하지 않습니다. 이름 없는 일괄 제어와 특정 작업 제어의 범위를 혼동하지 마십시오. ## WAKEUP INTERVAL 생략하면 생성 INTERVAL과 같습니다. 양수이고 집계 간격보다 크지 않아야 하며 집계 간격을 나누어떨어지게 해야 합니다. 더 자주 깨우면 지연을 줄일 수 있지만 처리 부하도 증가합니다. ```sql ALTER ROLLUP ch6_control_ru SET WAKEUP INTERVAL 5 SEC; SELECT DISTINCT ROLLUP_NAME, INTERVAL_TIME, WAKEUP_INTERVAL FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CONTROL_RU'; ``` WAKEUP_INTERVAL이 5000ms로 바뀝니다. 다음은 60초의 약수가 아닌 의도적 오류입니다. ```sql ALTER ROLLUP ch6_control_ru SET WAKEUP INTERVAL 7 SEC; ``` ## V$ROLLUP 읽기 | 컬럼 | 해석 | |---|---| | ROLLUP_NAME | 제어할 작업 이름 | | ROLLUP_TABLE | 집계 대상 테이블; Custom은 사용자 대상 TAG | | SOURCE_TABLE, ROOT_TABLE | 직접 소스와 작업을 해석할 원본 관계 | | COLUMN_NAME | 일반·경로 집계 대상 컬럼 | | INTERVAL_TIME, WAKEUP_INTERVAL | 밀리초 단위 생성·실행 간격 | | LAST_WAKEUP_TIME, NEXT_WAKEUP_TIME | 직전에 깨어난 시각과 다음 예정 시각 | | EXT_TYPE | 0 일반, 1 확장, 2 Custom | | PREDICATE | 일반 조건식 또는 Custom SELECT 본문 | | ENABLED | 작업 활성화 여부 | | RUN_STATE | `I` 초기, `S` 대기, `R` 처리 중 | | END_RID | 소스 처리 위치 | | LAST_ELAPSED_MSEC | 직전 처리 시간(ms) | | DATABASE_NAME, USER_ID | 데이터베이스·소유자 구분 | ```sql SELECT ROLLUP_NAME, ROLLUP_TABLE, SOURCE_TABLE, ROOT_TABLE, INTERVAL_TIME, WAKEUP_INTERVAL, LAST_WAKEUP_TIME, NEXT_WAKEUP_TIME, ENABLED, RUN_STATE, LAST_ELAPSED_MSEC FROM V$ROLLUP WHERE ROLLUP_NAME = 'CH6_CONTROL_RU' ORDER BY ROLLUP_NAME; SHOW ROLLUPGAP; ``` SHOW ROLLUPGAP은 machsql 전용 클라이언트 명령이며 SDK의 일반 SQL API에 보내지 않습니다. GAP은 소스와 ROLLUP 처리 RID의 차이입니다. 시간 지연 자체가 아니고, 모든 계층·관련 노드의 상태와 함께 봅니다. gap=0이어도 이미 집계한 원본 보정이 반영되었다는 뜻은 아닙니다. 지속 입력 중의 값은 관측 시점에 따라 변하므로 재현 실습에서는 입력을 멈춘 상태로 비교합니다. 중지 기간에 원본이 보존 정책으로 삭제되면 START만으로 그 데이터를 되살릴 수 없습니다. FORCE는 하위에서 상위 순서로 실행하고, 실패하면 최초 오류·상태·소스 접근 가능성을 확인합니다. ## 정리 ```sql DROP ROLLUP ch6_control_ru; DROP TABLE ch6_control; ``` 자세한 명령 계약은 [EXEC 레퍼런스](../../reference/sql/syntax/execute-procedure-syntax/)와 [ROLLUP 문제 해결](../../troubleshooting/rollup/)을 참고합니다. --- title: "6.10 ROLLUP_REBUILD" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/rollup-rebuild/ language: kr kind: page --- # 6.10 ROLLUP_REBUILD ROLLUP_REBUILD는 원본의 과거 값 보정 이후 집계를 다시 계산하는 프로시저이며 Standard Edition 전용입니다. FORCE와 달리 영향 버킷을 삭제·재생성합니다. 모든 ROLLUP 정의를 임의로 재구성하는 범용 명령은 아닙니다. ## 지원 대상을 먼저 확인 | 대상 | 현행 경로 | |---|---| | WITH ROLLUP으로 만든 완전한 SEC→MIN→HOUR 계층 | 기본 숫자·확장·문서 전체 JSON 경로 | | 원본에 연결된 지원 Custom 트리 | 1 SEC·1 MIN·1 HOUR 간격과 재구성 경계에 맞는 SELECT 확인 | | 임의 이름·간격의 수동 일반 ROLLUP | 자동 계층과 같은 지원을 가정하지 않음 | | SEC가 없는 MIN/HOUR만의 자동 계층 | 완전한 기본 계층으로 간주하지 않음 | | 10 MIN 등 다른 간격의 Custom | 현행 시간 경계 생성 경로에 지원되지 않음 | | Cluster Edition | 미지원 | Custom의 SELECT가 만드는 버킷과 INTERVAL, 기준 시간대가 재구성 경계와 일치해야 합니다. 지원하지 않는 작업이 트리에 섞였는지 먼저 조사합니다. 함수가 모든 Custom 표현식·간격을 생성 가능하다는 이유만으로 재구성할 수 있다고 가정하지 마십시오. ## 시간 인수와 버킷 경계 문자열 시각 또는 상수 문자열을 사용하는 TO_DATE를 지정합니다. 일반 DATETIME 식 전체를 평가하는 경로가 아니므로 NOW 산술식이나 바인딩 매개변수로 예제를 작성하지 않습니다. 시작과 종료 시각이 속한 버킷을 모두 포함하고 버킷 전체로 넓힙니다. 1분 단계에서 00:00:30~00:01:00을 지정하면 00:00과 00:01 두 버킷, 즉 [00:00:00, 00:02:00) 범위가 재계산됩니다. 시작=종료도 그 시각의 버킷을 재계산하며 시작이 종료보다 크면 오류입니다. 상위 시간 단계는 더 넓은 버킷으로 확장됩니다. ## 기본 계층과 Custom 정정 실습 ### 1. 준비와 최초 집계 ```sql CREATE TAG TABLE ch6_rebuild ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ) WITH ROLLUP (SEC); CREATE TAG TABLE ch6_rebuild_dst ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, sum_value DOUBLE, cnt LONG ); CREATE ROLLUP ch6_rebuild_custom INTO (ch6_rebuild_dst) AS ( SELECT name, DATE_TRUNC('minute', time) AS time, SUM(value), COUNT(value) FROM ch6_rebuild GROUP BY name, time ) INTERVAL 1 MIN; INSERT INTO ch6_rebuild VALUES ('S1', TO_DATE('2026-01-01 00:00:00'), 1); INSERT INTO ch6_rebuild VALUES ('S1', TO_DATE('2026-01-01 00:00:30'), 200); INSERT INTO ch6_rebuild VALUES ('S1', TO_DATE('2026-01-01 00:01:00'), 300); INSERT INTO ch6_rebuild VALUES ('S1', TO_DATE('2026-01-01 00:01:30'), 4); EXEC TABLE_FLUSH(ch6_rebuild); SELECT DISTINCT ROLLUP_NAME, INTERVAL_TIME FROM V$ROLLUP WHERE ROOT_TABLE = 'CH6_REBUILD' ORDER BY INTERVAL_TIME, ROLLUP_NAME; ALTER ROLLUP _CH6_REBUILD_ROLLUP_SEC FORCE; ALTER ROLLUP _CH6_REBUILD_ROLLUP_MIN FORCE; ALTER ROLLUP _CH6_REBUILD_ROLLUP_HOUR FORCE; ALTER ROLLUP ch6_rebuild_custom FORCE; SELECT rollup('min', 1, time) AS bucket, AVG(value) FROM ch6_rebuild WHERE name = 'S1' GROUP BY bucket ORDER BY bucket; ``` 제어 명령의 자동 생성 이름은 위 V$ROLLUP 결과와 일치하는지 확인합니다. 다른 객체의 이름을 추측해 실행하지 않습니다. 최초 분 평균은 100.5와 152입니다. ### 2. 원본 보정과 재구성 ```sql UPDATE ch6_rebuild SET value = 20 WHERE name = 'S1' AND time = TO_DATE('2026-01-01 00:00:30'); UPDATE ch6_rebuild SET value = 30 WHERE name = 'S1' AND time = TO_DATE('2026-01-01 00:01:00'); EXEC ROLLUP_REBUILD(ch6_rebuild, 'S1', TO_DATE('2026-01-01 00:00:30'), TO_DATE('2026-01-01 00:01:00')); ``` ### 3. 원본·일반·Custom 결과 비교 ```sql SELECT DATE_TRUNC('minute', time) AS bucket, SUM(value), COUNT(value), AVG(value) FROM ch6_rebuild WHERE name = 'S1' GROUP BY bucket ORDER BY bucket; SELECT rollup('min', 1, time) AS bucket, SUM(value), COUNT(value), AVG(value) FROM ch6_rebuild WHERE name = 'S1' GROUP BY bucket ORDER BY bucket; SELECT time, SUM(sum_value), SUM(cnt), SUM(sum_value) / SUM(cnt) FROM ch6_rebuild_dst WHERE name = 'S1' GROUP BY time ORDER BY time; SHOW ROLLUPGAP; ``` 세 결과는 00:00에 합계 21·건수 2·평균 10.5, 00:01에 합계 34·건수 2·평균 17입니다. 종료 시각 뒤의 00:01:30 값 4도 같은 버킷을 재계산할 때 포함되어야 합니다. ## 운영 영향과 실패 복구 관련 작업은 처리 위치를 따라잡고 중지·재계산·재시작됩니다. 원본의 안정적인 조회를 위한 내부 처리도 수행하므로 “재구성 중 정상 작업이 그대로 계속된다”고 설명하지 않습니다. 사용자 쿼리·수집의 허용 지연과 중지 시간을 검증 환경에서 먼저 측정합니다. 여러 단계가 하나의 원자적 트랜잭션으로 취소된다고 가정하지 않습니다. 실패 시 상태 복구는 최선 시도로 수행되므로 실제 데이터와 V$ROLLUP을 확인합니다. 원래 중지 상태를 그대로 복원해 준다는 보장도 없습니다. 재실행 전 지원 대상·원본 잔존 여부·재집계 범위를 확인합니다. 존재하지 않는 태그는 유효한 재구성 대상이 준비된 상황에서 no-op일 수 있습니다. 성공 응답만으로 의도한 태그를 처리했다고 판단하지 말고 실제 결과를 비교합니다. 원본이 삭제되어 없으면 원래 통계를 복원할 수 없습니다. ## 정리 ```sql DROP ROLLUP ch6_rebuild_custom; DROP TABLE ch6_rebuild_dst; DROP TABLE ch6_rebuild CASCADE; ``` Custom 대상은 따로 제거합니다. 일반 정의 자체를 바꿔야 하거나 이 프로시저 범위를 벗어나면 지원되는 새 정의·데이터 이전 절차를 마련합니다. 내부 저장 테이블을 임의로 삭제하는 SQL을 대체 절차로 제시하지 않습니다. 정확한 인수는 [REBUILD 레퍼런스](../../reference/sql/syntax/rollup-rebuild-syntax/)를 참고합니다. --- title: "6.11 ROLLUP 성능 튜닝" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/performance-tuning-rollup/ language: kr kind: page --- # 6.11 ROLLUP 성능 튜닝 ## 같은 결과를 만든 뒤 비용 비교 ROLLUP의 효과는 읽는 원시 행을 줄이는 데 있습니다. 먼저 태그, 시간 구간, NULL 처리와 집계 기준이 같은 결과인지 확인한 뒤 실행 시간·CPU·I/O·메모리를 비교합니다. 작은 예제의 실행 시간이 운영 성능을 보장하지는 않습니다. ## 비교 실습 ```sql CREATE TAG TABLE ch6_perf ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE, quality INTEGER ); CREATE ROLLUP ch6_perf_ru ON ch6_perf(value) INTERVAL 1 MIN; INSERT INTO ch6_perf VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10.0, 1); INSERT INTO ch6_perf VALUES ('TEMP_01', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), 20.0, 1); INSERT INTO ch6_perf VALUES ('TEMP_01', TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS'), 30.0, 1); INSERT INTO ch6_perf VALUES ('TEMP_02', TO_DATE('2026-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 100.0, 1); EXEC TABLE_FLUSH(ch6_perf); ALTER ROLLUP ch6_perf_ru FORCE; SELECT DATE_TRUNC('minute', time) AS bucket, SUM(value), COUNT(value), AVG(value), MIN(value), MAX(value) FROM ch6_perf WHERE name = 'TEMP_01' AND time >= TO_DATE('2026-01-01 00:00:00') AND time < TO_DATE('2026-01-01 00:02:00') GROUP BY bucket ORDER BY bucket; SELECT rollup('min', 1, time) AS bucket, SUM(value), COUNT(value), AVG(value), MIN(value), MAX(value) FROM ch6_perf WHERE name = 'TEMP_01' AND time >= TO_DATE('2026-01-01 00:00:00') AND time < TO_DATE('2026-01-01 00:02:00') GROUP BY bucket ORDER BY bucket; EXPLAIN SELECT rollup('min', 1, time) AS bucket, AVG(value) FROM ch6_perf WHERE name = 'TEMP_01' GROUP BY bucket; ``` 두 결과는 00:00의 합계 30·건수 2·평균 15·최솟값 10·최댓값 20과, 00:01의 합계 30·건수 1·평균 30·최솟값 30·최댓값 30입니다. 원본 DATE_TRUNC 쿼리의 실행 계획도 같은 방식으로 확인합니다. ## 운영 부하 측정 | 항목 | 함께 기록할 조건 | |---|---| | 입력 처리량 | 태그 수·입력률·행 폭·동시 입력자 | | 조회 지연 | 태그 범위·버킷·동시 쿼리·캐시가 차갑거나 준비된 상태 | | 집계 지연 | 계층별 gap·작업 상태·처리 시간 | | 저장량 | 원본·집계·인덱스·압축·복제본 | | 변경 영향 | WAKEUP 주기와 계층 변경 전후의 입력·조회 비용 | WAKEUP을 짧게 하면 같은 버킷에 더 자주 부분 집계가 생길 수 있습니다. 집계 버킷을 작게 하면 저장·재집계 양이 늘어납니다. 두 간격을 같은 튜닝 항목으로 취급하지 않습니다. 단일 실행뿐 아니라 입력과 조회가 동시에 진행되는 상황을 측정합니다. ## 계층 크기의 의미 태그 하나가 30일 동안 모든 구간에 데이터를 가진다는 가정의 논리 버킷 수입니다. | 조회 구간 | 버킷 수 | |---|---:| | 1초 | 2,592,000 | | 1분 | 43,200 | | 1시간 | 720 | | 1일 | 30 | 이는 물리 저장 행 수가 아닙니다. 여러 부분 집계, 빈 구간, NULL과 조건 필터를 포함한 실제 저장량은 표본 적재로 측정합니다. 가장 거친 후보가 항상 올바른 결과를 만드는 것도 아니므로 필요한 해상도·origin·후보 선택 제약을 함께 확인합니다. 일 단위 결과에는 적용 가능한 HOUR/MIN/SEC 집계를 재집계합니다. 24 HOUR ROLLUP을 `rollup('day', 1, ...)`의 대체 저장 계층으로 권장하지 않습니다. [조회 후보 규칙](../query-syntax-rollup/)과 실제 EXPLAIN을 기준으로 합니다. ## 지연·불일치의 분리 gap=0은 처리 위치를 따라잡았다는 뜻이지 원본 보정이 이미 반영됐다는 뜻은 아닙니다. 성능 비교 전에는 집계 진행과 과거 정정 상태를 구분하고, 정의·필터가 같은지 확인합니다. 적용 가능한 ROLLUP이 없는 `rollup()` 쿼리를 원본 성능 측정용으로 사용하지 않습니다. ## 정리 ```sql DROP ROLLUP ch6_perf_ru; DROP TABLE ch6_perf; ``` [제어와 상태](../ingestion-control-rollup/), [계층 설계](../target-tag-table-design/), [문제 해결](../../troubleshooting/rollup/)에서 다음 조정을 선택합니다. --- title: "6.12 ROLLUP 활용 시나리오" url: https://docs.machbase.com/kr/dbms/tag-rollup-usage/patterns-scenarios/ language: kr kind: page --- # 6.12 ROLLUP 활용 시나리오 ## 센서별 원본과 구간 통계 두 센서가 같은 시각에 측정해도 단위가 다르면 평균을 섞지 않습니다. 아래는 현재 단위를 메타데이터로 관리하고 태그별 집계를 조회하는 독립 실습입니다. ### 1. 생성과 입력 ```sql CREATE TAG TABLE ch6_scenario ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE ) METADATA (unit VARCHAR(16)); INSERT INTO ch6_scenario METADATA VALUES ('TEMP_01', 'celsius'); INSERT INTO ch6_scenario METADATA VALUES ('PRESS_01', 'bar'); INSERT INTO ch6_scenario VALUES ('TEMP_01', TO_DATE('2026-01-01 10:00:00'), 20); INSERT INTO ch6_scenario VALUES ('TEMP_01', TO_DATE('2026-01-01 10:00:30'), 22); INSERT INTO ch6_scenario VALUES ('PRESS_01', TO_DATE('2026-01-01 10:00:00'), 1.02); CREATE ROLLUP ch6_scenario_ru ON ch6_scenario(value) INTERVAL 1 MIN; EXEC TABLE_FLUSH(ch6_scenario); ALTER ROLLUP ch6_scenario_ru FORCE; ``` 원본 입력 뒤 ROLLUP을 만들었어도 남아 있는 데이터를 초기 집계합니다. 생성 완료만으로 초기 집계까지 끝났다고 판단하지 않습니다. ### 2. 결과 확인 ```sql SELECT name, unit FROM ch6_scenario METADATA ORDER BY name; SELECT name, DATE_TRUNC('minute', time) AS bucket, COUNT(value), AVG(value) FROM ch6_scenario GROUP BY name, bucket ORDER BY name, bucket; SELECT name, rollup('min', 1, time) AS bucket, COUNT(value), AVG(value) FROM ch6_scenario GROUP BY name, bucket ORDER BY name, bucket; SHOW ROLLUPGAP; ``` TEMP_01은 2건·평균 21°C, PRESS_01은 1건·평균 1.02bar입니다. 메타데이터 조회는 현재 단위이며 과거 단위 변경 이력을 자동 보존하지 않습니다. ### 3. 개별 관측 확인 고정 시각 데이터를 입력했으므로 같은 고정 범위를 조회합니다. 이 데이터에 현재 시각의 “최근 5분” 조건을 적용하면 실행 날짜에 따라 빈 결과가 됩니다. ```sql SELECT name, time, value FROM ch6_scenario WHERE name = 'TEMP_01' AND time >= TO_DATE('2026-01-01 10:00:00') AND time < TO_DATE('2026-01-01 10:01:00') ORDER BY time; ``` 두 원본 값은 20과 22입니다. 통계 평균 21에서 각 관측이나 발생 순서를 복원할 수는 없습니다. ### 4. 정리 ```sql DROP ROLLUP ch6_scenario_ru; DROP TABLE ch6_scenario; ``` ## 업무별 적용 | 요구 | 확인할 설계 | |---|---| | 정상 품질 통계 | 조건 필터와 후보 힌트를 고정해 원본과 비교 | | OHLC | 확장 ROLLUP의 FIRST/LAST 또는 Custom의 보조 시각 재집계 | | 다중 센서 비교 | 태그별 단위와 같은 버킷·조회 범위 사용 | | 누적 계량기 소비량 | 경계 차이, 초기화·교체·누락 규칙; 표본 평균과 구분 | | 가동률 | 표본 비율과 시간 비율 구분, 결측 구간 정책 | | 최신 원본과 장기 집계 결합 | 집계 완료가 확인된 기준점에서 구간을 겹치지 않게 분리 | 원본과 ROLLUP 결과를 UNION ALL로 합칠 때는 경계의 중복·누락과 서로 다른 표본 수를 확인합니다. “최근 2분은 언제나 미집계” 같은 고정 지연을 보장으로 사용하지 않습니다. 부분 결과를 다시 합쳐 평균을 구한다면 합계와 유효 건수를 전달합니다. 기능별 완결 실습은 [조건](../conditional-rollup/), [확장](../extension-rollup/), [JSON](../json-summarized-rollup/), [Custom](../custom-rollup/)에 있습니다. 문제가 생기면 [진단 순서](../../troubleshooting/rollup/)로 상태와 의미를 먼저 확인합니다. --- title: "7. LOG 테이블 활용" url: https://docs.machbase.com/kr/dbms/log-table-usage/ language: kr kind: section --- # 7. LOG 테이블 활용 로그를 저장하는 것과 필요한 로그를 찾아내는 것은 다른 일입니다. 장애를 분석하다 보면 “이 시각은 발생 시각인가, 수집 시각인가?”, “메시지에 있는 단어가 왜 검색되지 않는가?”처럼 입력할 때는 생각하지 못했던 문제를 만나게 됩니다. 이 장에서는 LOG 테이블을 선택하고 설계하는 단계부터 입력, 검색, 보존 관리까지 연결해서 살펴봅니다. 명령을 실행하는 방법뿐 아니라 결과를 확인하는 기준도 함께 익혀 보세요. LOG는 원본 이벤트를 계속 추가하는 모델입니다. 저장한 행을 반복해서 수정하는 업무 테이블과는 사용법이 다릅니다. ## 이 장의 구성 처음이라면 개요와 스키마를 읽고 생성·입력·조회를 순서대로 따라가는 편이 좋습니다. 시간 조건이 헷갈리면 7.10을, 메시지 검색이 목적이라면 7.11을 먼저 읽어도 됩니다. | 절 | 읽고 나면 할 수 있는 일 | |---|---| | [7.1 개요와 사용 기준](./overview-use-criteria/) | LOG와 다른 테이블의 용도 구분 | | [7.2 테이블 구조와 스키마](./table-structure-schema/) | 발생 시각·검색 필드·원문 분리 | | [7.3 생성, 변경, 삭제](./create-alter-drop/) | 기존 데이터를 확인하며 스키마 변경 | | [7.4 데이터 입력](./data-input-mutation/) | INSERT·Append·파일 적재 경로 선택 | | [7.5 조회와 분석](./query-analysis/) | 시간 범위 조회와 기준 정보 조인 | | [7.6 인덱스와 성능](./index-performance/) | 인덱스 선택과 실행 계획 확인 | | [7.7 운영과 데이터 생명주기](./operations-lifecycle/) | 삭제 경계 확인과 보존 정책 적용 | | [7.8 제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 증상에서 원인과 조치 찾기 | | [7.9 활용 패턴과 시나리오](./patterns-scenarios/) | 애플리케이션 로그 입력·검색·집계 | | [7.10 _arrival_time 시간 모델](./arrival-time-model/) | 자동 시각·명시 시각·역전 입력 구분 | | [7.11 텍스트 검색과 KEYWORD 인덱스](./text-search-keyword-index/) | 검색 방식에 따른 결과 차이 이해 | | [7.12 네트워크 타입 조회](./regex-network-query/) | IPV4·IPV6 주소와 범위 조회 | ## 실습 환경 이 장의 예제는 DBMS 8.7 문서 기준이며, 별도 표시가 없는 SQL 실습은 Standard Edition의 검증용 환경을 기준으로 합니다. 테이블과 인덱스를 만들 수 있는 계정을 사용하세요. Cluster 환경은 [Edition별 차이](/dbms/reference/support-scope-constraints/edition/)와 해당 운영 절차를 함께 확인해야 합니다. 실습 객체는 `ch7_`로 시작합니다. 각 절에서 필요한 테이블을 직접 만들기 때문에 다른 절을 실행하지 않아도 따라갈 수 있습니다. 같은 절을 재실행할 때는 마지막 정리 SQL까지 실행했는지 확인하세요. 의도적으로 실패하는 SQL은 정상 실습과 분리해 두었습니다. 주의: `DELETE`, `TRUNCATE`, `DROP`은 데이터를 지우는 명령입니다. 예제 이름을 운영 테이블 이름으로 바꾸어 실행하지 마세요. 실습 결과가 다르다면 실행한 SQL과 실제 결과를 함께 살펴보세요. 어느 단계부터 차이가 났는지 확인하면 원인을 훨씬 좁히기 좋습니다. --- title: "7.1 개요와 사용 기준" url: https://docs.machbase.com/kr/dbms/log-table-usage/overview-use-criteria/ language: kr kind: page --- # 7.1 개요와 사용 기준 시간이 붙은 데이터라고 모두 같은 테이블에 저장할 필요는 없습니다. 주기적으로 측정한 온도와 “장치가 재접속했다”는 이벤트는 분석 방법이 다르기 때문입니다. LOG를 선택할 때는 시간 컬럼의 유무보다 한 행이 무엇을 나타내는지부터 생각해 보세요. ## LOG 테이블의 특성 애플리케이션 오류, 보안 장비의 차단 기록, 작업 시작·종료 이력처럼 사건을 하나씩 추가하는 데이터에 LOG가 적합합니다. 사용자 컬럼 외에 `_arrival_time`이 자동으로 생기며, 시간 범위 조건과 메시지 검색을 함께 사용할 수 있습니다. 다음 예제로 이벤트 발생 시각과 입력 시각의 차이를 확인해 보겠습니다. ```sql CREATE LOG TABLE ch7_overview ( event_time DATETIME, device VARCHAR(32), message VARCHAR(128) ); INSERT INTO ch7_overview VALUES ( TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'DEV-01', 'connection restored' ); SELECT _arrival_time, event_time, device, message FROM ch7_overview; DROP TABLE ch7_overview; ``` 한 행이 조회됩니다. `event_time`은 예제에 넣은 고정 시각이고, `_arrival_time`은 이번 입력에서 자동으로 정해진 시각입니다. 따라서 실행 날짜가 달라도 `event_time`은 바뀌지 않습니다. 명시 입력과 보정 규칙은 [시간 모델](../arrival-time-model/)에서 다룹니다. ## 사용 기준 “지난 한 시간의 오류”, “이 IP 주소가 보낸 요청”, “timeout이 들어간 메시지”를 자주 찾고, 저장한 행을 수정할 필요가 없다면 LOG부터 검토하세요. 지속 입력에는 Append API를, 초기 데이터나 파일 묶음에는 적재 도구를 사용할 수 있습니다. 입력 후 수정할 수 없다는 이유만으로 감사 데이터의 위변조 방지가 보장되지는 않습니다. 삭제 권한, 접근 통제, 보존 정책과 백업은 별도로 설계해야 합니다. 실수하기 쉬운 부분은 재전송입니다. 같은 이벤트를 다시 보내면 또 다른 행으로 저장될 수 있습니다. LOG에는 PRIMARY KEY·UNIQUE 제약이 없으므로 자동 중복 제거를 기대하면 안 됩니다. 원본 이벤트 ID를 보관하고 수집 단계에서 재시도와 중복 처리 정책을 정하세요. ## 다른 테이블과의 비교 | 중심 업무 | 먼저 검토할 테이블 | 판단 기준 | |---|---|---| | 센서 이름별 측정값과 시계열 집계 | TAG | 이름·시간축 조회와 ROLLUP이 중심 | | 장비 이름·설치 위치 같은 현재 기준 정보 | LOOKUP | 작은 참조 데이터를 조회·변경 | | 주문 상태 변경·행 삭제·트랜잭션 처리 | TRANSACTION | 행을 수정하고 거래 단위로 처리 | | 재시작 후 사라져도 되는 메모리 상태 | VOLATILE | 영구 보관할 원본 로그와 분리 | LOG는 일반 `UPDATE`와 임의 조건 `DELETE WHERE`를 지원하지 않습니다. 정정 이벤트를 새로 추가하는 설계는 가능하지만, 원본 ID와 정정 사유를 저장하고 조회 시 어떤 정정을 반영할지 애플리케이션에서 정해야 합니다. 수정이 일상적인 업무라면 처음부터 다른 테이블을 사용하는 편이 명확합니다. ## 설계 기준 분석에 필요한 시간이 발생 시각인지 수집 시각인지 먼저 구분합니다. 이어서 장치·등급·IP처럼 자주 사용하는 조건을 정하세요. 이런 값은 원문에서 매번 잘라내기보다 별도 컬럼으로 두어야 쿼리를 이해하고 관리하기 좋습니다. 보관 기간도 이때 정해 두세요. 입력 경로만 준비하고 삭제를 미루면 디스크 사용량은 계속 증가합니다. 다음 [스키마 설계](../table-structure-schema/)에서는 이 질문을 실제 컬럼으로 옮겨 봅니다. 선택이 애매할 때는 대표 이벤트 몇 건과 자주 사용할 쿼리를 나란히 놓고 비교해 보세요. --- title: "7.2 테이블 구조와 스키마" url: https://docs.machbase.com/kr/dbms/log-table-usage/table-structure-schema/ language: kr kind: page --- # 7.2 테이블 구조와 스키마 메시지 전체를 한 컬럼에 넣으면 수집을 빨리 시작할 수 있습니다. 하지만 나중에 장비별 오류 건수를 구하려고 매번 원문을 파싱하면 쿼리가 복잡해집니다. 원문은 보존하되, 반복해서 조회하고 집계할 값은 별도 컬럼으로 꺼내 두는 것이 이 절의 핵심입니다. ## 컬럼 구성 다음은 보안 이벤트 한 건을 저장하고 확인하는 독립 실습입니다. ```sql CREATE LOG TABLE ch7_schema ( event_time DATETIME, event_id VARCHAR(64), device VARCHAR(32), severity SHORT, src_ip IPV4, dst_port INTEGER, message TEXT ); INSERT INTO ch7_schema VALUES ( TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'evt-0001', 'FW-01', 3, '192.0.2.10', 65535, 'connection blocked by policy' ); SELECT event_id, device, severity, src_ip, dst_port, message FROM ch7_schema; ``` `evt-0001` 한 행과 포트 `65535`가 조회됩니다. 여기서 `event_id`는 추적용 값이지, 중복 입력을 막는 키 제약이 아닙니다. `event_time`에는 원본이 기록한 발생 시각을 저장합니다. 자동 컬럼인 `_arrival_time`을 DDL에 다시 선언하지 마세요. 네트워크 지연이나 일괄 이관이 있으면 두 시각이 달라지는 것은 자연스러운 일입니다. ## 데이터 타입 선택 | 값 | 선택할 타입 | 확인할 점 | |---|---|---| | 장치 이름·짧은 코드·이벤트 ID | `VARCHAR(n)` | LOG의 선언 범위는 1~32,767바이트이며 문자 수가 아님 | | 긴 원문 메시지 | `TEXT` | 최대 64MiB, 원문 자체의 정렬·그룹화는 지원하지 않음 | | 오류 등급·작은 코드 | `SHORT` 또는 `INTEGER` | 코드별 의미를 수집기와 조회 코드에서 통일 | | 포트 | `INTEGER` | `USHORT`의 65535는 NULL 예약값이므로 전체 포트 범위에 부적합 | | 누적 바이트 수 | `LONG` | 예상 최댓값과 NULL 예약값 확인 | | 주소 | `IPV4` 또는 `IPV6` | 주소 형식과 원본 문자열 보관 필요 구분 | LOG에서는 VARCHAR와 TEXT 모두 KEYWORD 인덱스로 단어 검색을 할 수 있습니다. 전문 검색을 한다는 이유만으로 짧은 코드까지 TEXT로 만들 필요는 없습니다. 실수하기 쉬운 부분은 문자열 길이입니다. `VARCHAR(100)`은 한글 100글자를 뜻하지 않습니다. UTF-8 표본의 바이트 수를 확인하고, 길이가 넘는 값을 실제 입력 경로로 보내 보세요. 길이 초과가 잘림이나 오류 중 어떤 형태로 드러나는지는 사용할 SDK·적재 도구까지 함께 확인해야 합니다. ## 정렬·집계 컬럼 위 스키마에서 `device`·`severity`는 검색 조건과 집계 기준이고, `message`는 원문을 읽거나 단어를 찾는 데 사용합니다. `ORDER BY message`나 `GROUP BY message`처럼 TEXT 자체를 대상으로 삼으면 오류가 납니다. 메시지를 전부 정렬하기보다 장치, 오류 코드, 발생 시각 중 실제로 필요한 기준을 정하세요. KEYWORD 인덱스는 형태소 분석기나 검색 점수 기반 검색 엔진과 같지 않습니다. 단어 검색과 원문 부분 문자열 검색의 차이는 [텍스트 검색](../text-search-keyword-index/)에서 확인할 수 있습니다. ## 스키마 변경과 입력 매핑 LOG는 컬럼 추가·삭제·이름 변경과 제한된 속성 변경을 지원합니다. 그렇다고 저장한 행의 값을 UPDATE로 바꿀 수 있는 것은 아닙니다. 특히 컬럼 순서에 맞춰 보내는 Appender와 CSV 매핑은 DDL 변경의 영향을 받으므로 변경 시점과 입력 프로그램 배포를 함께 계획하세요. 실습 테이블을 정리한 뒤 [컬럼 변경 실습](../create-alter-drop/)으로 이어갑니다. ```sql DROP TABLE ch7_schema; ``` 어떤 값을 컬럼으로 꺼낼지 막히면 실제로 답해야 하는 질문부터 적어 보세요. 그 질문을 SQL로 표현해 보면 필요한 컬럼도 더 분명해집니다. --- title: "7.3 생성, 변경, 삭제" url: https://docs.machbase.com/kr/dbms/log-table-usage/create-alter-drop/ language: kr kind: page --- # 7.3 생성, 변경, 삭제 컬럼을 추가하는 SQL은 짧지만, 운영에서는 “기존 행에 어떤 값이 보이는가?”와 “기존 입력 프로그램이 계속 동작하는가?”까지 확인해야 합니다. 이 절에서는 데이터를 넣어 둔 상태에서 스키마를 바꾸고 그 결과를 살펴봅니다. ## LOG 테이블 생성 테이블 타입을 생략한 `CREATE TABLE`은 TRANSACTION 테이블을 만듭니다. 이 실습에서는 `CREATE LOG TABLE`을 사용하세요. ```sql CREATE LOG TABLE ch7_ddl ( event_id INTEGER, category VARCHAR(32), severity SHORT, message VARCHAR(128) ); INSERT INTO ch7_ddl VALUES (1, 'network', 3, 'connection timeout'); ``` 자동 컬럼 `_arrival_time`은 DDL에 다시 선언하지 않습니다. 실제 발생 시각이 필요하면 별도 DATETIME 컬럼을 두세요. PRIMARY KEY·UNIQUE 제약은 LOG에서 지원하지 않습니다. ## 컬럼 추가와 기본값 ```sql ALTER TABLE ch7_ddl ADD COLUMN (host_name VARCHAR(64)); ALTER TABLE ch7_ddl ADD COLUMN (source_kind VARCHAR(16) DEFAULT 'agent'); ALTER TABLE ch7_ddl ADD COLUMN (channels INT32[3] DEFAULT [1, NULL, 3]); SELECT event_id, host_name, source_kind, channels FROM ch7_ddl ORDER BY event_id; ``` 기존 1번 행의 `host_name`은 NULL, `source_kind`는 `agent`, `channels`는 `[1, NULL, 3]`으로 조회됩니다. DEFAULT가 있는 컬럼과 없는 컬럼의 차이를 여기서 확인하세요. ARRAY DEFAULT는 요소 수가 선언한 길이와 같아야 합니다. 이어서 컬럼 이름을 바꾸고 문자열 길이를 늘립니다. ```sql ALTER TABLE ch7_ddl RENAME COLUMN category TO event_category; ALTER TABLE ch7_ddl MODIFY COLUMN (message VARCHAR(4096)); ALTER TABLE ch7_ddl MODIFY COLUMN severity SET MINMAX_CACHE_SIZE = 1048576; SELECT event_id, event_category, severity, message FROM ch7_ddl; ``` 기존 행의 값은 유지됩니다. 이름을 바꾼 뒤에는 조회 SQL도 `event_category`를 사용해야 합니다. MINMAX 예제는 숫자 컬럼에 1MiB를 지정한 것으로, 모든 테이블에 적용할 권장값은 아닙니다. 실수하기 쉬운 부분은 타입 변경입니다. VARCHAR 길이 확장은 TEXT를 VARCHAR로 변환하는 명령이 아닙니다. `MINMAX_CACHE_SIZE` 역시 VARCHAR·TEXT 같은 가변 길이 컬럼에는 설정할 수 없습니다. ## NOT NULL과 기존 데이터 현재 `event_category`에는 값이 있으므로 다음 변경이 가능합니다. ```sql ALTER TABLE ch7_ddl MODIFY COLUMN event_category NOT NULL; ALTER TABLE ch7_ddl MODIFY COLUMN event_category NULL; ``` 옵션 없는 `NOT NULL`은 기존 행을 검사합니다. NULL이 있는 `host_name`에는 적용할 수 없습니다. 아래 SQL은 실패를 확인하고 싶을 때만 따로 실행하세요. ```sql -- 의도적으로 실패: 기존 행의 host_name이 NULL입니다. ALTER TABLE ch7_ddl MODIFY COLUMN host_name NOT NULL; ``` `NOT NULL NOCHECK`는 기존 NULL 검사를 생략합니다. 기존 NULL을 채우거나 과거 데이터까지 조건을 만족한다고 보장하는 옵션은 아닙니다. 이 실습의 정상 흐름에서는 사용하지 않습니다. ## 인덱스와 컬럼 삭제 ```sql CREATE INDEX ch7_ddl_host_idx ON ch7_ddl(host_name) INDEX_TYPE LSM; DROP INDEX ch7_ddl_host_idx; ALTER TABLE ch7_ddl DROP COLUMN (host_name); ALTER TABLE ch7_ddl DROP COLUMN (source_kind); ALTER TABLE ch7_ddl DROP COLUMN (channels); SELECT event_id, event_category, severity, message FROM ch7_ddl; ``` 인덱스가 참조하는 컬럼은 인덱스를 먼저 삭제해야 합니다. 내부 컬럼 `_ARRIVAL_TIME`·`_RID`는 삭제·이름 변경·속성 변경 대상이 아니며, 사용자 컬럼은 최소 하나가 남아 있어야 합니다. VARCHAR 길이는 기존보다 크게만 변경할 수 있고 최대 32,767바이트 범위여야 합니다. ## 데이터 삭제와 테이블 삭제 주의: 다음 명령은 실습 데이터를 지웁니다. LOG 데이터는 TRANSACTION 테이블의 `ROLLBACK`으로 되돌릴 수 없습니다. ```sql TRUNCATE TABLE ch7_ddl; SELECT COUNT(*) AS remaining_rows FROM ch7_ddl; DROP TABLE ch7_ddl; ``` TRUNCATE 뒤 건수는 0이고 정의는 남습니다. 마지막 DROP은 정의도 삭제합니다. 일부 오래된 데이터만 정리하려면 [보존형 삭제](../operations-lifecycle/)를 사용하세요. ## DDL 운영 주의사항 변경 전에는 입력 작업과 DDL 시점을 조정하고, 변경 후에는 SQL·Appender·파일 매핑의 컬럼 이름·순서·타입을 확인합니다. 리소스 사용 중 오류가 나면 반복 실행하기보다 대상 테이블을 사용하는 작업부터 확인하세요. 어느 변경에서 막혔는지 모르겠다면 변경 전 DDL과 실패한 SQL을 함께 준비해 보세요. 그 두 가지가 있으면 원인을 훨씬 빠르게 좁힐 수 있습니다. --- title: "7.4 데이터 입력" url: https://docs.machbase.com/kr/dbms/log-table-usage/data-input-mutation/ language: kr kind: page --- # 7.4 데이터 입력 한두 행이 잘 들어간다고 수집 준비가 끝난 것은 아닙니다. 지속 수집에서는 전송 버퍼, 일부 행의 실패, 연결이 끊긴 뒤의 재전송까지 생각해야 합니다. 먼저 SQL로 컬럼과 시간을 확인한 뒤 실제 처리량에 맞는 입력 경로를 선택하세요. ## SQL INSERT ```sql CREATE LOG TABLE ch7_input ( event_time DATETIME, event_id VARCHAR(32), device VARCHAR(32), message VARCHAR(128) ); INSERT INTO ch7_input(event_time, event_id, device, message) VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'evt-001', 'DEV-01', 'connection timeout'); SELECT _arrival_time, event_time, event_id, device, message FROM ch7_input; ``` 한 행이 조회되며 `event_time`은 고정된 발생 시각입니다. `_arrival_time`을 생략했으므로 입력 경로에서 서버 시각을 사용합니다. 기본 설정에서는 시각 역전 시 보정이 일어날 수 있으므로 항상 실제 수신 시각과 정확히 같다고 가정하지 마세요. 자세한 규칙은 [시간 모델](../arrival-time-model/)에서 확인합니다. 같은 이벤트를 다시 넣으면 어떻게 되는지도 확인해 보겠습니다. ```sql INSERT INTO ch7_input(event_time, event_id, device, message) VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'evt-001', 'DEV-01', 'connection timeout'); SELECT event_id, COUNT(*) AS received_rows FROM ch7_input GROUP BY event_id; DROP TABLE ch7_input; ``` `evt-001`의 건수는 2입니다. 이름이 같아도 LOG가 중복을 제거하지 않습니다. 수집 애플리케이션의 “전송 완료”와 원본 이벤트의 “한 번만 저장”은 별도 문제입니다. ## 입력 경로 선택 | 상황 | 시작할 경로 | 함께 확인할 항목 | |---|---|---| | 소량 입력·기능 확인 | SQL INSERT | 컬럼 목록, 타입, 날짜 형식 | | 애플리케이션의 지속 대량 입력 | SDK Append | 버퍼 전송, 행별 실패, 재접속 정책 | | 클라이언트에서 읽는 CSV | csvimport·machloader | 컬럼 매핑, 실패 행 파일 | | 서버에서 읽을 수 있는 적재 파일 | LOAD DATA INFILE | 서버 경로와 파일 접근 권한 | SQL INSERT는 문장마다 처리 비용이 발생합니다. 지속적인 대량 입력에는 여러 행을 묶어 보내는 Append API를 검토하세요. 언어별 실행 코드는 [개발 및 애플리케이션 연동](/dbms/development-tools-integration/)에서 선택하면 됩니다. ## Append 전송과 오류 처리 Appender에 행을 전달한 시점에는 데이터가 클라이언트 버퍼에 남아 있을 수 있습니다. 사용하는 SDK의 flush·close 동작을 확인하고, 정상 종료뿐 아니라 예외 경로에서도 남은 버퍼와 연결을 처리해야 합니다. 호출이 성공했다고 모든 행이 저장되었다고 단정하지 마세요. SDK에 따라 반환값, 오류 콜백, 종료 시 성공·실패 건수처럼 결과를 확인하는 경로가 다릅니다. 입력 길이 초과, NULL, 날짜 변환 오류를 일부러 포함한 작은 배치로 먼저 확인하는 편이 좋습니다. 실수하기 쉬운 상황은 응답을 받기 전에 연결이 끊기는 경우입니다. 이미 저장된 배치를 다시 보낼 수 있으므로 원본 이벤트 ID와 처리 위치를 기록하세요. LOG의 INSERT·Append는 TRANSACTION 테이블 트랜잭션의 ROLLBACK 대상도 아닙니다. ## 파일 적재와 매핑 한글·빈 문자열·NULL·긴 메시지·서로 다른 시간대를 포함한 표본을 준비하세요. 원본 필드 수와 대상 컬럼 순서가 일치하는지 확인한 다음 전체 파일을 처리합니다. 실패 행 파일과 로그도 보관해야 같은 오류를 다시 분석할 수 있습니다. 전체 명령은 [데이터 입력·적재·반출](/dbms/development-tools-integration/data-input-load-export/)을 참고하세요. 과거 데이터 이관에서 `_arrival_time`을 보존하려면 정렬 순서와 기존 대상 데이터까지 확인해야 합니다. 일반 수집의 과거 발생 시각은 별도 `event_time`에 저장하는 편이 안전합니다. 막히면 전체 배치보다 실패한 원본 한 행부터 확인해 보세요. 필드 값, 대상 타입, 사용한 입력 API를 함께 보면 원인을 찾기 수월합니다. --- title: "7.5 조회와 분석" url: https://docs.machbase.com/kr/dbms/log-table-usage/query-analysis/ language: kr kind: page --- # 7.5 조회와 분석 시간 범위를 조금만 다르게 잡아도 조회 건수가 달라집니다. 특히 하루씩 나누어 집계할 때 끝 시각을 양쪽 구간에 모두 포함하면 경계의 행이 중복됩니다. 이 절에서는 고정 시각 데이터로 범위와 결과 순서를 확인한 뒤 기준 정보를 조인합니다. ## 실습 데이터 준비 ```sql CREATE LOG TABLE ch7_query ( event_id INTEGER, device VARCHAR(32), value DOUBLE ); INSERT INTO ch7_query(_arrival_time, event_id, device, value) VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1, 'DEV-01', 10); INSERT INTO ch7_query(_arrival_time, event_id, device, value) VALUES (TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'), 2, 'DEV-01', 20); INSERT INTO ch7_query(_arrival_time, event_id, device, value) VALUES (TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'), 3, 'DEV-02', 30); INSERT INTO ch7_query(_arrival_time, event_id, device, value) VALUES (TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS'), 4, 'DEV-02', 40); SELECT _arrival_time, event_id, device, value FROM ch7_query ORDER BY _arrival_time, event_id; ``` 결과는 1·2·3·4번 순서입니다. 2·3번처럼 같은 시각의 행도 있으므로 순서를 고정하려면 시간뿐 아니라 이벤트 번호까지 정렬 기준에 넣습니다. 이 번호는 예제에서 직접 관리하는 값이며 LOG의 자동 고유 키가 아닙니다. ## 연속 구간과 시간 경계 ```sql SELECT event_id, value FROM ch7_query WHERE _arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') AND _arrival_time < TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY event_id; ``` 1·2·3번이 선택됩니다. 다음 구간을 12:00부터 시작하면 4번은 그 구간에서만 집계됩니다. `BETWEEN`은 양 끝을 포함하므로 이런 연속 구간과 의미가 다릅니다. 사용자 정의 `event_time`을 기준으로 분석할 때도 같은 WHERE 패턴을 사용하면 됩니다. ## DURATION 조회 ```sql SELECT event_id FROM ch7_query DURATION 1 HOUR BEFORE TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY event_id; SELECT event_id FROM ch7_query DURATION 1 HOUR AFTER TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY event_id; SELECT event_id FROM ch7_query DURATION FROM TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') TO TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY event_id; ``` | 쿼리 | 시간 범위 | 선택되는 event_id | |---|---|---| | 12:00 이전 1시간 | 11:00~12:00, 양 끝 포함 | 2, 3, 4 | | 10:00 이후 1시간 | 10:00~11:00, 양 끝 포함 | 1, 2, 3 | | 10:00부터 12:00까지 | 양 끝 포함 | 1, 2, 3, 4 | `DURATION 1 HOUR`처럼 기준 시각을 생략하면 현재 시각을 기준으로 합니다. 따라서 오래된 고정 시각 실습에 그대로 사용하면 결과가 없을 수 있습니다. 문법 위치는 WHERE 다음, GROUP BY·ORDER BY 앞입니다. 실수하기 쉬운 부분은 DURATION이 모든 시간 컬럼에 적용되는 조건이라고 생각하는 것입니다. DURATION은 LOG 전용이며 `_arrival_time`을 사용합니다. TAG의 시간 컬럼이나 사용자 DATETIME 조건은 WHERE로 지정하세요. 자세한 문법은 [상대 시간·DURATION 사전](/dbms/reference/sql/relative-time/#log-duration)을 참고하세요. ## 스캔 방향과 정렬 DURATION의 BEFORE는 최신 쪽부터, AFTER는 오래된 쪽부터 읽는 방향을 지정합니다. FROM … TO는 두 시각의 순서에 따라 방향이 달라집니다. 아래는 역순 범위와 같은 시각의 경계를 확인하는 예제입니다. ```sql SELECT event_id FROM ch7_query DURATION FROM TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') TO TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY event_id DESC; SELECT event_id FROM ch7_query DURATION FROM TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS') TO TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY event_id; ``` 첫 결과는 4·3·2·1번이고, 같은 시각을 지정한 두 번째 결과는 2·3번입니다. 여기서는 스캔 방향과 별개로 ORDER BY를 명시해 출력 순서를 고정했습니다. 하지만 집계·조인 등까지 포함한 최종 결과의 순서가 필요하다면 ORDER BY를 명시하세요. 특히 LIMIT만 사용해서 어떤 행이 나올지 추측하지 않는 것이 좋습니다. 전역 `TABLE_SCAN_DIRECTION`은 다른 쿼리에도 영향을 줄 수 있습니다. 화면 출력 순서를 바꾸기 위해 서버 설정부터 바꾸지는 마세요. [쿼리 튜닝](/dbms/performance-tuning/performance-query-tuning/)에서 실행 계획을 확인한 뒤 필요한 접근 경로를 조정하세요. ## LOOKUP 조인 ```sql CREATE LOOKUP TABLE ch7_query_device ( device VARCHAR(32) PRIMARY KEY, label VARCHAR(64) ); INSERT INTO ch7_query_device VALUES ('DEV-01', 'Boiler'); INSERT INTO ch7_query_device VALUES ('DEV-02', 'Pump'); SELECT q.event_id, d.label, q.value FROM ch7_query q JOIN ch7_query_device d ON q.device = d.device WHERE q._arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') AND q._arrival_time < TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY q.event_id; ``` 결과는 `(1, Boiler, 10)`, `(2, Boiler, 20)`, `(3, Pump, 30)`입니다. LOG와 LOOKUP을 섞은 쿼리이므로 DURATION 대신 LOG 컬럼의 WHERE 범위를 사용합니다. 이 INNER JOIN에서는 기준 정보가 없는 이벤트가 결과에서 빠진다는 점도 확인하세요. 또한 현재 LOOKUP 값을 조인한다고 과거 시점의 장치 설명이 복원되는 것은 아닙니다. 이력이 필요하면 당시 설명을 이벤트에 저장하거나 유효 기간을 가진 별도 이력을 설계해야 합니다. ```sql DROP TABLE ch7_query_device; DROP TABLE ch7_query; ``` 조회 건수가 예상과 다르면 시간 경계와 조인 전 건수를 먼저 확인하세요. 조건을 하나씩 추가하면 어디에서 행이 빠지는지 찾기 쉽습니다. --- title: "7.6 인덱스와 성능" url: https://docs.machbase.com/kr/dbms/log-table-usage/index-performance/ language: kr kind: page --- # 7.6 인덱스와 성능 인덱스를 만들었는데도 조회가 빨라지지 않으면 먼저 쿼리가 그 인덱스를 사용할 수 있는지 확인해야 합니다. 인덱스는 읽기 비용을 줄이는 대신 입력·저장·백그라운드 처리 비용을 늘립니다. 컬럼마다 하나씩 만드는 것보다 대표 조건을 정하는 편이 출발점으로 좋습니다. ## 인덱스 선택 | 조회 조건 | 검토할 인덱스 | 확인할 점 | |---|---|---| | 숫자·DATETIME 등의 값과 범위 | LSM | 조건에 맞는 지원 타입과 실제 실행 계획 | | VARCHAR·TEXT의 단어·토큰 패턴 | KEYWORD | SEARCH·ESEARCH 사용, LIKE와 결과 의미가 다름 | | 지원 타입의 반복 값 분석 | BITMAP | 값 분포와 인코딩, 입력·저장 비용 | LOG의 `_arrival_time` 범위는 기본 시간 접근 경로를 먼저 활용합니다. 같은 목적의 인덱스를 관성적으로 추가할 필요는 없습니다. 지원 타입과 속성은 [INDEX 문법](/dbms/reference/sql/syntax/index-syntax/)을 기준으로 확인하세요. 일반적인 RDBMS의 복합 인덱스 설계를 그대로 적용하지 마세요. ## 인덱스 생성 전후 비교 ```sql CREATE LOG TABLE ch7_index ( event_id INTEGER, event_time DATETIME, severity SHORT, message VARCHAR(256) ); INSERT INTO ch7_index VALUES ( 1, TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1, 'service started'); INSERT INTO ch7_index VALUES ( 2, TO_DATE('2026-01-01 10:01:00', 'YYYY-MM-DD HH24:MI:SS'), 3, 'database timeout'); INSERT INTO ch7_index VALUES ( 3, TO_DATE('2026-01-01 10:02:00', 'YYYY-MM-DD HH24:MI:SS'), 3, 'connection timeout'); EXPLAIN SELECT event_id FROM ch7_index WHERE severity = 3; CREATE INDEX ch7_index_time ON ch7_index(event_time) INDEX_TYPE LSM; CREATE INDEX ch7_index_message ON ch7_index(message) INDEX_TYPE KEYWORD; CREATE INDEX ch7_index_severity ON ch7_index(severity) INDEX_TYPE BITMAP BITMAP_ENCODE = RANGE; EXEC TABLE_FLUSH(ch7_index); EXEC INDEX_FLUSH(ch7_index); EXPLAIN SELECT event_id FROM ch7_index WHERE severity = 3; EXPLAIN SELECT event_id FROM ch7_index WHERE message SEARCH 'timeout'; SELECT event_id FROM ch7_index WHERE message SEARCH 'timeout' ORDER BY event_id; SHOW INDEXES; ``` 검색 결과는 2·3번입니다. 실행 계획에서 값 조건과 SEARCH가 사용하는 접근 경로를 비교하고, SHOW INDEXES로 생성된 이름을 확인하세요. 이 세 행은 동작을 이해하기 위한 표본이지 성능 측정 데이터는 아닙니다. ## 데이터와 인덱스 반영 `TABLE_FLUSH`는 테이블 데이터를 반영하는 작업이고, `INDEX_FLUSH`는 인덱스 빌드가 진행될 때까지 기다리는 작업입니다. 실습에서 생성 전후 계획과 시간을 비교할 때는 위처럼 구분해서 사용하세요. 인덱스가 존재하는 것과 입력된 데이터 전체의 인덱스 반영이 끝난 것은 같지 않습니다. 빌드 지연이 검색 비용에 영향을 줄 수 있습니다. 그렇다고 매 행 입력마다 두 명령을 호출하면 배치 입력의 이점을 줄이게 됩니다. 운영에서는 입력률과 백그라운드 처리 속도를 함께 관찰하고 필요한 동기화 시점만 정하세요. 실수하기 쉬운 부분은 인덱스 반영이 늦다는 이유로 같은 데이터를 재입력하는 것입니다. 재입력 전에 원본 조회 건수와 인덱스 상태를 각각 확인해야 중복을 피할 수 있습니다. ## 성능 측정 기준 생성 전후에 같은 데이터량·조건값·동시 입력 부하를 사용하세요. 한 번의 실행 시간뿐 아니라 반복 조회 시간, 입력 처리량, 인덱스 공간과 빌드 지연을 함께 기록합니다. LIKE·REGEXP는 원문 문자열에 조건을 평가하므로 먼저 시간 범위를 제한하면 검사할 대상을 줄일 수 있습니다. KEYWORD 인덱스가 있다고 LIKE가 SEARCH로 바뀌는 것은 아닙니다. ```sql DROP INDEX ch7_index_severity; DROP INDEX ch7_index_message; DROP INDEX ch7_index_time; DROP TABLE ch7_index; ``` 계획을 읽기 어렵다면 쿼리와 EXPLAIN 결과를 함께 비교해 보세요. [인덱스 튜닝](/dbms/performance-tuning/index-tuning/)의 진단 순서가 다음 확인 지점을 잡는 데 도움이 됩니다. --- title: "7.7 운영과 데이터 생명주기" url: https://docs.machbase.com/kr/dbms/log-table-usage/operations-lifecycle/ language: kr kind: page --- # 7.7 운영과 데이터 생명주기 입력은 잘 되는데 디스크가 계속 늘어난다면 삭제 기준부터 확인할 차례입니다. LOG는 업무 조건으로 특정 행을 골라 지우는 모델이 아니라, 오래된 영역부터 정리하는 모델입니다. 보관할 기간과 실제로 삭제되는 경계를 함께 확인해야 합니다. ## 삭제 방법 선택 | 목적 | 명령 | 기준 | |---|---|---| | 오래된 N행 삭제 | OLDEST n ROWS | 오래된 입력 행부터 | | 최근 N행만 남김 | EXCEPT n ROWS | 남길 행 수 | | 최근 기간만 남김 | EXCEPT n DAY 등 | 서버 현재 시각에서 기간을 뺀 경계 | | 고정 시각까지 삭제 | BEFORE datetime_expr | 해당 _arrival_time 경계 포함 | | 전체 데이터 삭제 | 조건 없는 DELETE 또는 TRUNCATE | 전체 행 | | 주기적인 기간 관리 | Retention Policy | 보존 기간과 실행 주기 | 주의: 삭제는 되돌릴 수 있다고 가정하면 안 됩니다. LOG 데이터는 TRANSACTION 테이블의 ROLLBACK 대상이 아닙니다. 운영에서는 백업과 실제 대상 범위를 확인한 뒤 실행하세요. ## 삭제 방식 비교 명령을 연속해서 실행하면 앞선 삭제가 다음 결과에 영향을 줍니다. 여기서는 같은 세 행을 서로 다른 테이블에 복사해 비교하겠습니다. ```sql CREATE LOG TABLE ch7_lifecycle (event_id INTEGER); CREATE LOG TABLE ch7_oldest (event_id INTEGER); CREATE LOG TABLE ch7_keep (event_id INTEGER); CREATE LOG TABLE ch7_before (event_id INTEGER); INSERT INTO ch7_lifecycle(_arrival_time, event_id) VALUES (TO_DATE('2026-01-01', 'YYYY-MM-DD'), 1); INSERT INTO ch7_lifecycle(_arrival_time, event_id) VALUES (TO_DATE('2026-01-02', 'YYYY-MM-DD'), 2); INSERT INTO ch7_lifecycle(_arrival_time, event_id) VALUES (TO_DATE('2026-01-03', 'YYYY-MM-DD'), 3); INSERT INTO ch7_oldest(_arrival_time, event_id) SELECT _arrival_time, event_id FROM ch7_lifecycle ORDER BY _arrival_time; INSERT INTO ch7_keep(_arrival_time, event_id) SELECT _arrival_time, event_id FROM ch7_lifecycle ORDER BY _arrival_time; INSERT INTO ch7_before(_arrival_time, event_id) SELECT _arrival_time, event_id FROM ch7_lifecycle ORDER BY _arrival_time; SELECT COUNT(*) AS delete_candidates FROM ch7_before WHERE _arrival_time <= TO_DATE('2026-01-02', 'YYYY-MM-DD'); DELETE FROM ch7_oldest OLDEST 1 ROWS; DELETE FROM ch7_keep EXCEPT 1 ROWS; DELETE FROM ch7_before BEFORE TO_DATE('2026-01-02', 'YYYY-MM-DD'); SELECT event_id FROM ch7_oldest ORDER BY event_id; SELECT event_id FROM ch7_keep ORDER BY event_id; SELECT event_id FROM ch7_before ORDER BY event_id; ``` 삭제 전 확인 건수는 2입니다. 각 테이블에 남는 이벤트는 다음과 같습니다. | 테이블 | 남는 event_id | |---|---| | ch7_oldest | 2, 3 | | ch7_keep | 3 | | ch7_before | 3 | 실수하기 쉬운 부분은 BEFORE라는 이름입니다. 현재 LOG 삭제는 지정한 시각과 같은 행도 포함합니다. `WHERE _arrival_time < 경계`로 사전 건수를 세면 삭제 대상과 달라질 수 있으므로 위처럼 `<=`로 확인하세요. ```sql DELETE FROM ch7_lifecycle; SELECT COUNT(*) AS remaining_rows FROM ch7_lifecycle; DROP TABLE ch7_before; DROP TABLE ch7_keep; DROP TABLE ch7_oldest; DROP TABLE ch7_lifecycle; ``` 전체 DELETE 뒤 건수는 0이고 테이블 정의는 남습니다. ## 상대 기간 삭제 ```sql CREATE LOG TABLE ch7_period (event_id INTEGER); INSERT INTO ch7_period(_arrival_time, event_id) VALUES (SYSDATE - 2d, 1); INSERT INTO ch7_period(_arrival_time, event_id) VALUES (SYSDATE, 2); DELETE FROM ch7_period EXCEPT 1 DAY; SELECT event_id FROM ch7_period ORDER BY event_id; DROP TABLE ch7_period; ``` 생성부터 조회까지 바로 실행하면 2번만 남습니다. 기준은 “마지막으로 들어온 행의 시각”이 아니라 서버 현재 시각입니다. 입력이 멈춰 있어도 시간은 계속 흐른다는 점을 보존 정책에도 반영하세요. ## Retention Policy 다음은 1일 보존·1분 주기의 검증용 예제입니다. 운영 권장값이 아니며, 정책 생성과 테이블 연결에 필요한 권한이 있는 계정을 사용하세요. ```sql CREATE LOG TABLE ch7_retention (event_id INTEGER); INSERT INTO ch7_retention(_arrival_time, event_id) VALUES (SYSDATE - 2d, 1); INSERT INTO ch7_retention(_arrival_time, event_id) VALUES (SYSDATE, 2); CREATE RETENTION ch7_policy DURATION 1 DAY INTERVAL 1 MIN; ALTER TABLE ch7_retention ADD RETENTION ch7_policy; SELECT * FROM M$RETENTION WHERE POLICY_NAME = 'CH7_POLICY'; SELECT TABLE_NAME, POLICY_NAME, STATE, LAST_DELETED_TIME FROM V$RETENTION_JOB WHERE TABLE_NAME = 'CH7_RETENTION'; SELECT event_id FROM ch7_retention ORDER BY event_id; ``` DURATION은 남길 기간, INTERVAL은 삭제를 실행하는 주기입니다. 연결 직후에는 두 행이 보일 수 있습니다. 한 주기와 작업 처리 시간이 지난 뒤 아래 쿼리를 다시 실행해 2번만 남는지 확인하세요. LAST_DELETED_TIME은 삭제 기준 시각이지 벽시계 기준의 작업 완료 시각이 아닙니다. ```sql SELECT TABLE_NAME, STATE, LAST_DELETED_TIME FROM V$RETENTION_JOB WHERE TABLE_NAME = 'CH7_RETENTION'; SELECT event_id FROM ch7_retention ORDER BY event_id; ``` 실습을 마쳤으면 연결부터 해제한 뒤 정책과 테이블을 삭제합니다. ```sql ALTER TABLE ch7_retention DROP RETENTION; SELECT TABLE_NAME FROM V$RETENTION_JOB WHERE TABLE_NAME = 'CH7_RETENTION'; DROP RETENTION ch7_policy; DROP TABLE ch7_retention; ``` 해제 후 작업 조회는 0건입니다. 이미 삭제된 행이 복원되는 것은 아닙니다. 한 테이블에는 하나의 정책을 연결하며, 사용 중인 정책은 먼저 해제해야 삭제할 수 있습니다. 전체 운영 기준은 [데이터 보존 정책](/dbms/operations-configuration-recovery/policy-data-retention/)을 참고하세요. ## 삭제 전 백업 검증 백업 파일이 있다는 사실만으로 복구 준비가 끝나지는 않습니다. 삭제할 기간을 Mount 또는 격리된 Restore 환경에서 실제로 조회해 보세요. 원본, 백업, 별도 집계 데이터의 보관 기간도 각각 정해야 합니다. 환경별 명령은 [백업·복원·마운트](/dbms/operations-configuration-recovery/backup-restore-mount/)를 따릅니다. ## 데이터와 디스크 공간 행이 조회에서 사라지는 것과 운영체제에서 파일 공간이 회수되는 시점은 같지 않을 수 있습니다. 입력량, 남은 행의 가장 오래된 시각, 인덱스·저장소 정리 상태, 디스크 사용량을 함께 확인하세요. 삭제 직후 디스크가 줄지 않는다는 이유로 더 넓은 기간을 지우지 마세요. 삭제 결과가 예상과 다르면 경계 시각과 적용된 정책부터 확인해 보세요. 보관 의무가 있는 데이터라면 추가 삭제를 멈추고 담당자와 범위를 먼저 맞추는 것이 좋습니다. --- title: "7.8 제약, 오류, 문제 해결" url: https://docs.machbase.com/kr/dbms/log-table-usage/constraints-errors-troubleshooting/ language: kr kind: page --- # 7.8 제약, 오류, 문제 해결 오류가 났을 때 같은 SQL을 반복 실행하면 원인은 그대로이고 상태만 더 복잡해질 수 있습니다. 먼저 지원하지 않는 작업인지, 입력값이나 객체 상태의 문제인지 구분해 보세요. 이 절에서는 확인할 대상을 증상별로 좁혀 봅니다. ## 기능 지원 범위 | 요청 | LOG 지원 여부 | 대안 | |---|---|---| | 일반 UPDATE | 미지원 | 정정 이벤트 설계 또는 변경 가능한 테이블 선택 | | 임의 조건 DELETE WHERE | 미지원 | BEFORE·OLDEST·EXCEPT 또는 모델 변경 | | PRIMARY KEY·UNIQUE 제약 | 미지원 | 수집 단계의 중복 처리 또는 다른 테이블 선택 | | 값·범위 인덱스 | LSM 지원 | 타입과 조건에 맞춰 선택 | | 단어 검색 | KEYWORD 지원 | VARCHAR·TEXT에 생성 후 SEARCH·ESEARCH 사용 | | BITMAP 분석 인덱스 | 지원 조건 내 사용 | 타입·인코딩·값 분포 확인 | | TEXT 자체의 ORDER BY·GROUP BY | 미지원 | 별도 코드·등급·시각 컬럼 사용 | ## 증상별 진단 | 증상 | 먼저 확인할 것 | 조치 | |---|---|---| | 명시한 도착 시각과 저장 시각이 다름 | 직전 시각과 TIME_INVERSION_MODE | 보정 여부 확인, 발생 시각은 별도 보존 | | SEARCH에서 인덱스 관련 오류 | 해당 컬럼의 KEYWORD 인덱스 | 타입·테이블·인덱스 이름 확인 | | 단어가 있는데 검색되지 않음 | SEARCH 토큰과 LIKE 부분 문자열의 차이 | 원문 한 행으로 두 결과 비교 | | 인덱스를 만들었는데 조회가 느림 | 실행 계획·빌드 상태·시간 범위 | 반영 상태 확인 후 대표 부하 측정 | | 컬럼 길이 변경 실패 | 기존 타입·새 길이 | VARCHAR 확장만 사용, 최대 길이 확인 | | MINMAX 변경 실패 | 가변 길이 타입 여부 | LOG의 지원 고정 길이 컬럼만 대상 | | NOT NULL 변경 실패 | 기존 NULL 행 | 기존 데이터 검사와 NOCHECK 의미 구분 | | 같은 이벤트가 여러 번 보임 | 원본 ID·재시도·파일 재처리 | 재전송 정책 점검, 임의 행 삭제로 해결하지 않음 | | DDL 실행 중 리소스 사용 오류 | 입력·조회 작업과 DDL 충돌 | 작업 시점 조정 후 재확인 | 서버 설정과 인덱스 목록은 다음처럼 확인할 수 있습니다. ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME IN ('DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE', 'TABLE_SCAN_DIRECTION'); SHOW INDEXES; ``` 설정값을 확인하는 것과 설정을 바꾸는 것은 별도 작업입니다. 원인 확인 없이 운영 서버의 전역 설정부터 바꾸지 마세요. ## 오류 재현과 해결 ```sql CREATE LOG TABLE ch7_error (event_id INTEGER, message TEXT); INSERT INTO ch7_error VALUES (1, 'connection timeout'); SELECT event_id, message FROM ch7_error; ``` 한 행이 조회됩니다. 아래는 각각 의도적으로 실패하는 SQL입니다. 정상 실습에 묶어 실행하지 말고 확인하려는 문장만 따로 실행하세요. ```sql -- KEYWORD 인덱스가 없는 상태입니다. SELECT event_id FROM ch7_error WHERE message SEARCH 'timeout'; -- TEXT 자체의 정렬·그룹화는 지원하지 않습니다. SELECT message FROM ch7_error ORDER BY message; SELECT message, COUNT(*) FROM ch7_error GROUP BY message; -- LOG는 일반 UPDATE·조건 DELETE를 지원하지 않습니다. UPDATE ch7_error SET message = 'fixed' WHERE event_id = 1; DELETE FROM ch7_error WHERE event_id = 1; -- TEXT를 VARCHAR로 변환하는 명령이 아닙니다. ALTER TABLE ch7_error MODIFY COLUMN (message VARCHAR(4096)); ``` 이제 인덱스를 만들고 지원되는 검색 경로로 확인합니다. ```sql CREATE INDEX ch7_error_msg ON ch7_error(message) INDEX_TYPE KEYWORD; EXEC TABLE_FLUSH(ch7_error); EXEC INDEX_FLUSH(ch7_error); SELECT event_id, message FROM ch7_error WHERE message SEARCH 'timeout' ORDER BY event_id; DROP TABLE ch7_error; ``` 1번 행이 조회되어야 합니다. 인덱스를 추가했다고 TEXT 정렬이나 LOG UPDATE까지 가능해지는 것은 아닙니다. ## 보존 정책 진단 기간이 지난 행이 남아 있다면 적용 정책, 실행 주기, LAST_DELETED_TIME과 실제 `_arrival_time`을 함께 확인하세요. `event_time`만 보고 삭제 실패로 판단하지 마세요. 관련 실습은 [운영과 데이터 생명주기](../operations-lifecycle/)에 있습니다. 문제가 계속되면 서버 버전·Edition, 테이블 DDL, 실패 SQL, 오류 전체와 대표 입력값을 준비해 주세요. 비밀번호와 민감한 로그는 가린 뒤 공유하면 됩니다. 큰 데이터 전체를 보내기보다 같은 증상을 보이는 작은 예제가 원인 파악에 더 도움이 됩니다. --- title: "7.9 활용 패턴과 시나리오" url: https://docs.machbase.com/kr/dbms/log-table-usage/patterns-scenarios/ language: kr kind: page --- # 7.9 활용 패턴과 시나리오 실제 분석에서는 시간, 호스트, 오류 등급, 메시지를 함께 봅니다. 각 기능을 따로 실행해 보았다면 이제 “어느 서버에서 어떤 오류가 늘었는가?”라는 질문으로 연결해 보겠습니다. 이 실습은 앞 절의 테이블 없이도 실행할 수 있습니다. ## 원본 이벤트와 현재 상태 LOG에는 사건이 발생할 때마다 새 행을 추가합니다. 장비의 현재 이름·위치처럼 변경 가능한 기준 정보는 LOOKUP에 분리할 수 있지만, 과거 상태까지 필요하다면 당시 값을 이벤트에 남기거나 이력을 별도로 설계해야 합니다. 보안 이벤트나 작업 추적에도 같은 접근을 사용할 수 있습니다. 센서 이름별 계측 집계가 중심이라면 TAG를, 업무 행의 수정·트랜잭션이 중심이라면 TRANSACTION을 먼저 검토하세요. ## 로그와 인덱스 생성 시간 조건을 실행 날짜와 무관하게 비교하기 위해 도착 시각을 명시합니다. 빈 테이블에 오름차순으로 입력하는 실습이며, 일반 수집에서는 원본 발생 시각을 별도 DATETIME 컬럼에 보관하는 편이 좋습니다. ```sql CREATE LOG TABLE ch7_app ( event_id INTEGER, host VARCHAR(32), level VARCHAR(16), message TEXT ); CREATE INDEX ch7_app_message ON ch7_app(message) INDEX_TYPE KEYWORD; INSERT INTO ch7_app(_arrival_time, event_id, host, level, message) VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1, 'web-01', 'INFO', 'service started'); INSERT INTO ch7_app(_arrival_time, event_id, host, level, message) VALUES (TO_DATE('2026-01-01 10:10:00', 'YYYY-MM-DD HH24:MI:SS'), 2, 'web-01', 'WARN', 'slow response'); INSERT INTO ch7_app(_arrival_time, event_id, host, level, message) VALUES (TO_DATE('2026-01-01 10:20:00', 'YYYY-MM-DD HH24:MI:SS'), 3, 'web-02', 'ERROR', 'database timeout'); INSERT INTO ch7_app(_arrival_time, event_id, host, level, message) VALUES (TO_DATE('2026-01-01 10:30:00', 'YYYY-MM-DD HH24:MI:SS'), 4, 'web-02', 'ERROR', 'connection refused'); INSERT INTO ch7_app(_arrival_time, event_id, host, level, message) VALUES (TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'), 5, 'web-01', 'INFO', 'normal service'); EXEC TABLE_FLUSH(ch7_app); EXEC INDEX_FLUSH(ch7_app); SELECT COUNT(*) AS received_rows FROM ch7_app; ``` 입력 건수는 5입니다. 실습을 반복하기 전에 마지막 DROP까지 실행했는지 확인하세요. 단순 재전송은 중복 행이 될 수 있습니다. ## 오류와 시간대별 조회 ```sql SELECT event_id, host, message FROM ch7_app WHERE _arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') AND _arrival_time < TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS') AND message SEARCH 'timeout' ORDER BY event_id; SELECT event_id, host, level, message FROM ch7_app WHERE _arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') AND _arrival_time < TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS') AND level = 'ERROR' ORDER BY event_id; ``` 첫 쿼리는 web-02의 3번, 두 번째는 3·4번을 선택합니다. 특정 단어로 시작하되 분석할 때는 같은 호스트·시간대의 다른 오류도 함께 확인하는 흐름입니다. 검색 범위를 넓힐 때는 시간 조건을 통째로 제거하기보다 필요한 구간만 늘리세요. ## 시간별·등급별 집계 ```sql SELECT TO_CHAR(_arrival_time, 'YYYY-MM-DD HH24') AS event_hour, level, COUNT(*) AS event_count FROM ch7_app WHERE _arrival_time >= TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') AND _arrival_time < TO_DATE('2026-01-01 12:00:00', 'YYYY-MM-DD HH24:MI:SS') GROUP BY TO_CHAR(_arrival_time, 'YYYY-MM-DD HH24'), level ORDER BY event_hour, level; ``` | event_hour | level | event_count | |---|---|---| | 2026-01-01 10 | ERROR | 2 | | 2026-01-01 10 | INFO | 1 | | 2026-01-01 10 | WARN | 1 | | 2026-01-01 11 | INFO | 1 | 이 쿼리는 원본 LOG를 읽어 수행하는 집계입니다. TAG의 ROLLUP을 자동으로 사용하는 쿼리가 아닙니다. 데이터가 커지면 조회 구간과 실행 계획을 확인하고, 사전 집계가 필요한지 별도로 판단하세요. 실수하기 쉬운 부분은 고정 시각 데이터에 `DURATION 1 HOUR`를 적용하는 것입니다. 그 조건은 현재 시각 기준이므로 실행 날짜가 바뀌면 표본이 선택되지 않을 수 있습니다. 운영의 최근 로그 조회와 재현용 고정 시각 조회를 구분하세요. ## 수집과 보존 관리 지속 수집은 [Append 입력](../data-input-mutation/)에서 이어서 구성할 수 있습니다. 장기 운영에서는 [보존 정책](../operations-lifecycle/)을 함께 정하세요. 원본 로그, 백업, 별도로 만드는 집계 데이터는 필요한 보관 기간이 다를 수 있습니다. ```sql DROP TABLE ch7_app; ``` 여기까지 결과가 맞으면 실제 로그 몇 건으로 필드와 메시지를 바꾸어 보세요. 한 번에 전체 수집을 옮기기보다 작은 표본으로 같은 질문에 답할 수 있는지 확인하는 편이 문제를 찾기 쉽습니다. --- title: "7.10 _arrival_time 시간 모델" url: https://docs.machbase.com/kr/dbms/log-table-usage/arrival-time-model/ language: kr kind: page --- # 7.10 _arrival_time 시간 모델 원본 로그에는 어제 시각이 적혀 있는데 조회에서는 오늘 수집한 데이터로 보일 수 있습니다. 발생 시각과 수집 시각을 섞어서 사용하면 정상 입력도 누락처럼 보입니다. LOG에서는 두 시간을 구분하는 것이 조회와 보존 정책의 출발점입니다. ## 발생 시각과 도착 시각 `event_time` 같은 사용자 DATETIME 컬럼은 “사건이 언제 발생했는가?”에 답합니다. 자동 생성되는 `_arrival_time`은 LOG의 시간 범위 접근과 보존형 삭제 기준입니다. 값을 생략하면 서버 시각을 사용하지만, 명시 입력과 역전 보정도 있으므로 항상 실제 네트워크 수신 시각이라고 단정해서는 안 됩니다. DATETIME은 나노초 단위 값을 표현합니다. 이는 서버 시계가 매번 나노초 정밀도로 시간을 측정한다는 뜻이 아닙니다. 저장 후 LOG의 UPDATE로 시각을 고칠 수도 없습니다. ## 지연 이벤트 조회 아래는 빈 실습 테이블에 도착 시각을 오름차순으로 지정한 예제입니다. ```sql CREATE LOG TABLE ch7_time ( event_id INTEGER, event_time DATETIME, message VARCHAR(64) ); INSERT INTO ch7_time(_arrival_time, event_id, event_time, message) VALUES (TO_DATE('2026-01-02 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1, TO_DATE('2026-01-02 09:59:00', 'YYYY-MM-DD HH24:MI:SS'), 'normal arrival'); INSERT INTO ch7_time(_arrival_time, event_id, event_time, message) VALUES (TO_DATE('2026-01-02 10:01:00', 'YYYY-MM-DD HH24:MI:SS'), 2, TO_DATE('2026-01-01 23:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'delayed arrival'); SELECT event_id FROM ch7_time WHERE event_time >= TO_DATE('2026-01-01', 'YYYY-MM-DD') AND event_time < TO_DATE('2026-01-02', 'YYYY-MM-DD') ORDER BY event_id; SELECT event_id FROM ch7_time DURATION FROM TO_DATE('2026-01-02 10:00:00', 'YYYY-MM-DD HH24:MI:SS') TO TO_DATE('2026-01-02 10:01:00', 'YYYY-MM-DD HH24:MI:SS'); ``` 발생 시각 조회는 2번만, 도착 시각 조회는 1·2번 모두를 선택합니다. 늦게 들어온 이벤트의 발생 시각을 억지로 바꿀 필요는 없습니다. ## 시각 역전과 보정 `DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE`는 직전 도착 시각보다 작은 값이 들어올 때의 처리를 정합니다. 현재 Standard 코드 기준 동작은 다음과 같습니다. | 값 | 역전 입력 처리 | |---|---| | 1, 기본값 | 직전 저장 `_arrival_time`보다 1ns 큰 값으로 보정 | | 0 | 시각 역전 오류로 입력 거부 | 같은 시각은 역전이 아니므로 이 규칙만으로 행마다 고유한 시간이 보장되지는 않습니다. 시간이 같을 때도 순서를 고정해야 하면 이벤트 번호 등 추가 기준을 ORDER BY에 넣으세요. 다음은 앞의 실습 테이블을 그대로 사용하는 선택 실습입니다. 먼저 설정을 조회하고, 값이 1인 검증 환경에서만 실행하세요. 이 예제를 위해 운영 서버 설정을 변경하지는 마세요. ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME = 'DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE'; ``` ```sql -- 설정값 1에서 실행합니다. 값이 0이면 입력 오류가 예상됩니다. INSERT INTO ch7_time(_arrival_time, event_id, event_time, message) VALUES (TO_DATE('2026-01-02 09:00:00', 'YYYY-MM-DD HH24:MI:SS'), 3, TO_DATE('2026-01-02 08:59:00', 'YYYY-MM-DD HH24:MI:SS'), 'inverted arrival'); SELECT event_id, TO_CHAR(_arrival_time, 'YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn') AS stored_time FROM ch7_time ORDER BY event_id; ``` 설정값 1에서는 3번의 저장 시각이 입력한 09:00이 아니라 `2026-01-02 10:01:00 000:000:001`이 됩니다. 발생 시각은 입력한 값 그대로입니다. 따라서 시각 역전 “허용”을 과거 시각의 무조건 보존으로 해석하면 안 됩니다. ## 데이터 이관 주의사항 원본 `_arrival_time`을 보존하려면 빈 대상에 오름차순으로 입력하는 것이 기본입니다. 정렬했더라도 대상에 더 최신 시각의 행이 있거나 다른 입력이 끼어들면 보정·오류가 발생할 수 있습니다. 이관과 일반 실시간 수집을 같은 테이블에서 섞지 마세요. `DURATION`은 `event_time`이 아닌 `_arrival_time`을 사용합니다. 시간대와 날짜 형식까지 맞췄는데 범위가 다르다면 어떤 시간 컬럼을 조회하고 있는지 다시 확인해 보세요. 경계와 출력 방향은 [조회 실습](../query-analysis/)에서 이어집니다. ```sql DROP TABLE ch7_time; ``` 시간 문제를 문의할 때는 원본 시각, 저장된 시각, 해당 설정값을 함께 준비하면 좋습니다. 세 값을 나란히 놓으면 변환과 보정 중 어느 단계인지 구분하기 쉬워집니다. --- title: "7.11 텍스트 검색과 KEYWORD 인덱스" url: https://docs.machbase.com/kr/dbms/log-table-usage/text-search-keyword-index/ language: kr kind: page --- # 7.11 텍스트 검색과 KEYWORD 인덱스 메시지에 같은 글자가 들어 있어도 SEARCH와 LIKE의 결과는 다를 수 있습니다. SEARCH는 색인된 단어를 찾고, LIKE는 원문 문자열에 패턴을 적용하기 때문입니다. 성능을 비교하기 전에 “어떤 행을 찾으려는가”를 먼저 맞춰 보겠습니다. ## 검색 데이터 준비 ```sql CREATE LOG TABLE ch7_search ( event_id INTEGER, message TEXT ); CREATE INDEX ch7_search_msg ON ch7_search(message) INDEX_TYPE KEYWORD; INSERT INTO ch7_search VALUES (1, 'ERROR connection timeout'); INSERT INTO ch7_search VALUES (2, 'connection slowly refused'); INSERT INTO ch7_search VALUES (3, 'pretimeout marker'); INSERT INTO ch7_search VALUES (4, 'normal service'); INSERT INTO ch7_search VALUES (5, NULL); INSERT INTO ch7_search VALUES (6, '대한민국 연결 오류'); INSERT INTO ch7_search VALUES (7, 'ERR-1001 network'); INSERT INTO ch7_search VALUES (8, 'timeout refused connection'); EXEC TABLE_FLUSH(ch7_search); EXEC INDEX_FLUSH(ch7_search); ``` 표본의 `event_id`로 결과를 비교합니다. 메시지는 TEXT이므로 정렬 기준으로 사용하지 않습니다. LOG의 VARCHAR에도 같은 KEYWORD 검색을 사용할 수 있습니다. ## SEARCH와 NOT SEARCH ```sql SELECT event_id FROM ch7_search WHERE message SEARCH 'timeout' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message SEARCH 'connection refused' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message SEARCH 'connection' AND message SEARCH 'refused' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message NOT SEARCH 'timeout' ORDER BY event_id; ``` | 조건 | 선택되는 event_id | 이유 | |---|---|---| | SEARCH 'timeout' | 1, 8 | pretimeout은 별도 단어 | | SEARCH 'connection refused' | 2, 8 | 두 단어가 모두 존재 | | SEARCH 두 조건을 AND로 결합 | 2, 8 | 같은 메시지에서 두 단어 확인 | | NOT SEARCH 'timeout' | 2, 3, 4, 6, 7 | 해당 단어가 없으며 NULL 행은 제외 | 다중 단어 SEARCH는 단어의 어순이나 인접성을 보장하는 구문 검색이 아닙니다. 2번에는 중간 단어가 있고 8번에는 순서가 뒤집혀 있지만 둘 다 선택됩니다. 다른 컬럼까지 SEARCH하려면 그 컬럼에도 해당 인덱스가 필요합니다. 기본 토큰화에서 일반 ASCII 단어는 소문자로 정규화됩니다. 예를 들어 1번의 `ERROR`는 `SEARCH 'error'`로도 검색됩니다. 이를 모든 Unicode 문자의 언어별 대소문자 처리로 확대해서 해석하지 마세요. ### 한글 토큰 분리 ```sql SELECT event_id FROM ch7_search WHERE message SEARCH '대한' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message SEARCH '연결' ORDER BY event_id; ``` 둘 다 6번이 선택됩니다. 기본 모드에서 `대한민국`은 `대한`, `한민`, `민국`처럼 겹치는 2-gram으로 색인됩니다. 형태소나 문장 의미를 이해하는 검색은 아닙니다. 공백·구두점·한 글자·영문과 한글이 섞인 값은 토큰 경계가 달라질 수 있으므로 실제 표본으로 확인하세요. MODE 같은 인덱스 옵션을 바꾸면 토큰화도 달라질 수 있습니다. ## ESEARCH 확장 검색 ```sql SELECT event_id FROM ch7_search WHERE message ESEARCH 'time%' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message ESEARCH '%time%' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message ESEARCH 'err%' ORDER BY event_id; ``` | 패턴 | 선택되는 event_id | 의미 | |---|---|---| | time% | 1, 8 | time으로 시작하는 단어 | | %time% | 1, 3, 8 | time을 포함하는 단어 | | err% | 1, 7 | error 또는 err처럼 err로 시작하는 단어 | `time%`가 단어 중간의 time까지 찾는 것은 아닙니다. 또한 ESEARCH는 원문 전체에 LIKE를 적용하는 것과 같지 않습니다. 공백·구두점을 가로지르는 원문 패턴을 그대로 ESEARCH에 옮기지 마세요. 복잡한 다중 조건은 별도 SEARCH·ESEARCH 조건을 AND·OR로 결합해 의미를 명시하세요. 현재 비교 경로에서 ESEARCH는 ASCII 대소문자를 구분하지 않습니다. 대상 키워드가 넓게 매칭될수록 검색 비용도 커지므로 항상 LIKE보다 빠르다고 단정할 수는 없습니다. 이 예제는 ASCII 키워드 패턴을 기준으로 합니다. `NOT ESEARCH` 구문은 지원하지 않습니다. `NOT SEARCH`나 `NOT LIKE`로 바꾸면 검색 의미도 바뀝니다. 어떤 행을 제외할지 다시 정의하고 NULL 처리까지 확인하세요. ## LIKE와 NOT LIKE ```sql SELECT event_id FROM ch7_search WHERE message LIKE '%TIMEOUT%' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message LIKE 'ERR-____' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message NOT LIKE '%timeout%' ORDER BY event_id; ``` 첫 쿼리는 1·3·8번, 두 번째는 0건, 세 번째는 2·4·6·7번입니다. 7번 메시지는 `ERR-1001` 뒤에도 문자열이 있으므로 `ERR-____` 전체 패턴과 맞지 않습니다. `%`는 0개 이상의 문자, `_`는 한 문자를 나타냅니다. 리터럴 `%`·`_`·역슬래시를 찾을 때는 역슬래시 이스케이프 규칙도 확인하세요. LIKE도 현재 ASCII 비교에서는 대소문자를 구분하지 않습니다. KEYWORD 인덱스는 사용하지 않으며, WHERE의 다른 조건이나 시간 범위로 검사할 행을 줄일 수 있습니다. 따라서 LIKE가 있다는 이유만으로 항상 테이블 전체를 읽는다고 설명하는 것도 정확하지 않습니다. ## REGEXP와 REGEXP_LIKE ```sql SELECT event_id FROM ch7_search WHERE message REGEXP '^ERR-[0-9]+' ORDER BY event_id; SELECT event_id FROM ch7_search WHERE message NOT REGEXP 'timeout' ORDER BY event_id; ``` 첫 쿼리는 7번, 두 번째는 2·4·6·7번입니다. REGEXP는 정규식 패턴에 맞는 부분이 있는지 검사합니다. 문자열 시작이나 끝을 제한하려면 `^`·`$`를 명시하세요. 함수로 사용할 때는 `REGEXP_LIKE`를 사용합니다. 현재 이 함수의 입력은 VARCHAR여야 하고, 패턴과 옵션도 상수 VARCHAR여야 합니다. 앞의 TEXT 컬럼을 그대로 전달하면 타입 오류가 나므로 별도 표본을 준비합니다. ```sql CREATE LOG TABLE ch7_regexp_fn (event_id INTEGER, message VARCHAR(200)); INSERT INTO ch7_regexp_fn VALUES (1, 'ERROR connection timeout'); INSERT INTO ch7_regexp_fn VALUES (5, NULL); SELECT event_id, REGEXP_LIKE(message, 'error') AS case_sensitive, REGEXP_LIKE(message, 'error', 'i') AS case_insensitive FROM ch7_regexp_fn WHERE event_id IN (1, 5) ORDER BY event_id; ``` 1번은 각각 0·1, NULL 메시지인 5번은 두 결과 모두 NULL입니다. 기본 정규식 비교는 대소문자를 구분하며, `i` 옵션은 비구분 비교입니다. 명시적인 구분 비교에는 `c`를 사용할 수 있습니다. 정규식은 KEYWORD 인덱스로 직접 처리되지 않습니다. 시간 조건이나 SEARCH로 대상을 줄일 수 있지만, 선행 조건이 원하는 행을 놓치면 뒤의 정규식이 그 행을 되살려 주지는 못합니다. ## TEXT 제약과 검색 성능 TEXT는 최대 64MiB 원문을 담을 수 있지만, TEXT 자체의 ORDER BY·GROUP BY는 지원하지 않습니다. 정렬·집계할 장치·오류 코드·등급은 별도 컬럼에 두세요. 같은 데이터에서 인덱스 존재 여부, 빌드 상태, 조회 범위를 확인한 뒤 성능을 비교합니다. ```sql DROP TABLE ch7_regexp_fn; DROP TABLE ch7_search; ``` 검색 결과가 다르면 원문 한 행과 사용한 패턴을 함께 확인해 보세요. “단어를 찾는지, 원문 일부를 찾는지”를 구분하는 것만으로 해결되는 경우가 많습니다. --- title: "7.12 네트워크 타입 조회" url: https://docs.machbase.com/kr/dbms/log-table-usage/regex-network-query/ language: kr kind: page --- # 7.12 네트워크 타입 조회 IP 주소를 문자열로 보관하면 눈으로 읽기는 편하지만, 문자열 정렬과 주소 범위의 순서가 같다고 가정하기 쉽습니다. 주소 비교가 목적이라면 IPV4·IPV6 타입을 사용하고, 원문 표기까지 필요할 때만 별도 문자열을 함께 보관하세요. ## 네트워크 데이터 준비 ```sql CREATE LOG TABLE ch7_network ( event_id INTEGER, src_ip IPV4, dst_ip IPV6, dst_port INTEGER ); INSERT INTO ch7_network VALUES (1, '192.0.2.1', '2001:db8::1', 80); INSERT INTO ch7_network VALUES (2, '192.0.2.255', '2001:db8::2', 65535); INSERT INTO ch7_network VALUES (3, '198.51.100.1', '2001:db8::3', 443); INSERT INTO ch7_network VALUES (4, NULL, NULL, NULL); SELECT event_id, src_ip, dst_ip, dst_port FROM ch7_network ORDER BY event_id; ``` 네 행이 조회됩니다. 포트는 INTEGER를 사용합니다. USHORT의 최댓값 65535는 NULL 예약값이므로 포트 전체 범위를 그대로 표현하기에 맞지 않습니다. ## 동등 비교와 범위 조회 ```sql SELECT event_id FROM ch7_network WHERE src_ip BETWEEN '192.0.2.1' AND '192.0.2.255' ORDER BY event_id; SELECT event_id FROM ch7_network WHERE src_ip IN ('192.0.2.1', '198.51.100.1') ORDER BY event_id; SELECT event_id FROM ch7_network WHERE dst_ip = '2001:db8::2' ORDER BY event_id; SELECT event_id FROM ch7_network WHERE dst_ip BETWEEN '2001:db8::1' AND '2001:db8::2' ORDER BY event_id; SELECT event_id FROM ch7_network WHERE src_ip IS NULL ORDER BY event_id; ``` | 조건 | 선택되는 event_id | |---|---| | IPv4 BETWEEN | 1, 2 | | IPv4 IN | 1, 3 | | IPv6 동등 비교 | 2 | | IPv6 BETWEEN | 1, 2 | | IPv4 IS NULL | 4 | BETWEEN은 양 끝 주소를 포함합니다. 이 예제의 IPv4 범위는 CIDR 대역의 의미를 자동 적용한 것이 아니라, 직접 지정한 두 주소 사이의 범위입니다. 대역 자체로 판정하려면 아래 `CONTAINED`를 사용하세요. NULL은 `= NULL`이 아니라 `IS NULL`로 검사하세요. ## CIDR 대역 판정 주소가 특정 네트워크에 속하는지 검사할 때는 `CONTAINED`를 사용합니다. 대역은 반드시 `주소/prefix` 형태로 지정하며 IPv4와 IPv6 모두 사용할 수 있습니다. ```sql SELECT event_id FROM ch7_network WHERE src_ip CONTAINED '192.0.2.0/24' ORDER BY event_id; SELECT event_id FROM ch7_network WHERE dst_ip CONTAINED '2001:db8::/32' ORDER BY event_id; SELECT event_id FROM ch7_network WHERE src_ip NOT CONTAINED '192.0.2.0/24' ORDER BY event_id; ``` | 조건 | 선택되는 event_id | |---|---| | IPv4 `CONTAINED '192.0.2.0/24'` | 1, 2 | | IPv6 `CONTAINED '2001:db8::/32'` | 1, 2, 3 | | IPv4 `NOT CONTAINED '192.0.2.0/24'` | 3 | 방향을 뒤집은 `'192.0.2.0/24' CONTAINS src_ip`도 같은 의미입니다. 좌변에 네트워크, 우변에 주소가 오는 형태가 `CONTAINS`이고 그 반대가 `CONTAINED`입니다. prefix 표기를 빼고 `CONTAINED '192.0.2.0'`처럼 쓰면 네트워크로 해석하지 못해 오류입니다. NULL 주소인 4번은 어느 조건에도 선택되지 않으므로, 미수집 주소를 함께 세야 하면 `IS NULL` 조건을 따로 두십시오. ## 주소 형식과 원문 보존 IPv4만 들어오면 IPV4로, IPv6가 들어오면 IPV6로 스키마를 정합니다. 두 종류를 함께 다루는 경우에는 입력 변환·컬럼 분리 정책을 먼저 정하고 실제 표본으로 검증하세요. 암시적 변환이 원본 주소를 항상 의도대로 정규화해 줄 것이라고 가정하지 마세요. 실수하기 쉬운 부분은 출력 문자열입니다. 같은 IPv6 주소라도 원문 압축 표기와 조회 결과의 표기가 다를 수 있습니다. 원문을 증빙으로 남겨야 한다면 주소 타입 컬럼과 원문 컬럼을 분리하는 편이 명확합니다. 대량 데이터에서는 주소 조건과 함께 `_arrival_time` 범위를 제한하고 실행 계획을 확인하세요. 메시지의 정규식 검색은 [텍스트 검색](../text-search-keyword-index/#regex)과 [SQL 함수 사전](/dbms/reference/sql/functions/)에서 이어서 살펴볼 수 있습니다. ```sql DROP TABLE ch7_network; ``` 주소 조회가 예상과 다르면 원본 문자열, 입력 타입, 범위의 양 끝부터 나란히 비교해 보세요. 문자열 표현의 차이인지 실제 주소 범위의 차이인지 구분하기 쉬워집니다. --- title: "8. TRANSACTION 테이블 활용" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/ language: kr kind: section --- # 8. TRANSACTION 테이블 활용 원본 로그를 쌓는 일과 주문 상태·재고·장비 정보를 바꾸는 일은 성격이 다릅니다. 상태를 변경하는 업무에서는 “몇 행이 바뀌었는가?”, “중간에 실패하면 어디까지 되돌리는가?”, “다른 연결이 동시에 수정하면 어떻게 되는가?”까지 확인해야 합니다. TRANSACTION 테이블은 이런 관계형 조회와 변경을 위한 Standard Edition 전용 테이블입니다. 이 장에서는 작은 표본으로 결과를 확인하면서 스키마, 갱신, 트랜잭션, 동시 접근과 복구를 연결해 봅니다. 내부에 SQLite 저장소를 사용하지만 공개 문법과 지원 범위는 Machbase SQL을 기준으로 해야 합니다. SQLite나 다른 RDBMS의 기능을 모두 그대로 사용할 수 있는 것은 아닙니다. ## 이 장의 구성 | 절 | 확인할 내용 | |---|---| | [8.1 개요와 사용 기준](./overview-use-criteria/) | LOG·TAG·LOOKUP과의 역할 구분 | | [8.2 테이블 구조와 스키마](./table-structure-schema/) | 식별자·업무 키·타입·제약 설계 | | [8.3 생성, 변경, 삭제](./create-alter-drop/) | DDL 실행과 기존 데이터 확인 | | [8.4 데이터 입력과 변경](./data-input-mutation/) | 조건부 갱신·삭제·복사·Append | | [8.5 조회와 분석](./query-analysis/) | 필터·정렬·집계·JSON 조회 | | [8.6 인덱스와 성능](./index-performance/) | PK·UNIQUE·복합·JSON path 인덱스 | | [8.7 운영과 데이터 생명주기](./operations-lifecycle/) | 배치 정리와 운영 확인 기준 | | [8.8 제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 증상별 확인과 재시도 판단 | | [8.9 트랜잭션](./transaction/) | 문장 실패·ROLLBACK·커밋 보장 범위 | | [8.10 잠금, 충돌, busy timeout](./locking-conflict-timeout/) | 두 연결의 충돌과 스냅샷 재시도 | | [8.11 JOIN과 관계형 조회 설계](./join-relational-query/) | 조인으로 늘거나 빠지는 행 확인 | | [8.12 백업, 복원, 마운트](./backup-restore-mount/) | 백업 시점의 데이터를 실제로 검증 | | [8.13 INSERT ON DUPLICATE KEY UPDATE](./insert-on-duplicate-key-update/) | 삽입·갱신 분기와 중복 처리 | 자동 번호의 공통 문법은 [AUTO_INCREMENT](/dbms/reference/sql/syntax/auto-increment-syntax/)에 있습니다. ## 실습 환경과 실행 단위 SQL 실습은 DBMS 8.7 Standard Edition의 검증 환경을 기준으로 합니다. 각 절에서 `ch8_` 접두사의 객체를 준비하고 정리하므로 다른 절의 실행 결과에 의존하지 않습니다. 테이블·인덱스 생성 권한이 필요하며, 백업 실습에는 별도의 권한과 서버 경로가 필요합니다. BEGIN부터 COMMIT·ROLLBACK까지는 같은 연결에서 실행하세요. 두 세션 실습은 지정한 A·B 순서를 지켜야 합니다. 의도적으로 실패하는 SQL은 정상 흐름과 분리해 두었습니다. 실습을 다시 실행하기 전에는 정리 SQL까지 끝냈는지 확인하세요. 주의: 테이블 이름에 TRANSACTION이 들어간다고 모든 작업과 모든 장애가 한 번에 되돌려지는 것은 아닙니다. DDL, 다른 타입의 쓰기, 여러 테이블의 장애 시 커밋 경계는 [8.9 트랜잭션](./transaction/)에서 먼저 확인하세요. --- title: "8.1 개요와 사용 기준" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/overview-use-criteria/ language: kr kind: page --- # 8.1 개요와 사용 기준 장비의 측정값은 계속 쌓이지만, 점검 상태나 재고 수량은 기존 값을 바꾸어야 합니다. 둘을 같은 모델로 처리하려 하면 원본 보관과 상태 변경의 요구가 섞이기 쉽습니다. TRANSACTION은 변경 가능한 업무 데이터를 맡기고, 원본 시계열은 TAG·LOG에 남기는 구성을 먼저 생각해 보세요. ## TRANSACTION 테이블의 특성 TRANSACTION은 SELECT·INSERT·UPDATE·DELETE, PRIMARY KEY, UNIQUE INDEX와 보조 인덱스를 지원합니다. Standard Edition 전용이며 다음 세 문법은 같은 테이블을 만듭니다. | 문법 | 의미 | |---|---| | CREATE TABLE | 타입을 생략한 기본 TRANSACTION 생성 | | CREATE TRANSACTION TABLE | 타입을 명시한 생성 | | CREATE TXN TABLE | 축약형 생성 | 공개 문서와 운영 스크립트에서는 타입이 드러나는 CREATE TRANSACTION TABLE을 권장합니다. CREATE RDB TABLE·CREATE TRX TABLE은 지원하지 않습니다. Cluster에서는 위 세 생성 문법을 모두 사용할 수 없으며 LOG는 CREATE LOG TABLE로 명시합니다. ## 상태 변경과 롤백 ```sql CREATE TRANSACTION TABLE ch8_overview ( item_id LONG PRIMARY KEY, qty INTEGER NOT NULL ); INSERT INTO ch8_overview VALUES (42, 10); BEGIN; UPDATE ch8_overview SET qty = qty - 3 WHERE item_id = 42 AND qty >= 3; SELECT item_id, qty FROM ch8_overview; ROLLBACK; SELECT item_id, qty FROM ch8_overview; DROP TABLE ch8_overview; ``` 같은 연결에서 트랜잭션 안의 조회는 수량 7, ROLLBACK 뒤 조회는 10입니다. `qty >= 3`은 재고가 부족하면 변경하지 않도록 하는 업무 조건입니다. 실수하기 쉬운 부분은 UPDATE가 오류 없이 끝나면 업무도 성공했다고 판단하는 것입니다. 조건에 맞는 행이 없으면 변경 건수는 0일 수 있습니다. 애플리케이션에서는 영향 행 수가 기대한 1인지 확인하고 다음 작업이나 롤백을 결정해야 합니다. SQL 예제에 등장하는 숫자만 그대로 바꾸는 것보다 이 확인 절차가 더 중요합니다. ## 다른 테이블과의 비교 | 주된 요구 | 먼저 검토할 테이블 | |---|---| | 센서 이름별 측정값과 ROLLUP | TAG | | 수정하지 않는 로그·이벤트 원본 | LOG | | 작은 현재 기준 정보 | LOOKUP | | 관계형 DML과 명시적 트랜잭션 | TRANSACTION | | 재시작 후 사라져도 되는 메모리 상태 | VOLATILE | TRANSACTION에는 장비 점검 상태, 업무 이력, 별도 요약 결과 등을 둘 수 있습니다. 대량 원본 수집은 TAG·LOG와 처리량·입력 경로를 비교하세요. TRANSACTION도 Append를 지원하지만 TAG·LOG와 같은 처리량이나 배치 경계를 가정해서는 안 됩니다. [입력 방식](../data-input-mutation/)에서 구체적으로 구분합니다. LOOKUP은 TRANSACTION의 모든 기능을 대신하는 테이블이 아닙니다. Cluster에서 관계형 트랜잭션이 꼭 필요하다면 별도 RDBMS를 포함한 구성을 검토해야 합니다. ## 설계 기준 한 행을 식별할 키와 중복을 막을 업무 키를 구분하세요. 내부 번호에는 PRIMARY KEY, 외부 시스템 코드 같은 별도 고유값에는 UNIQUE INDEX가 필요할 수 있습니다. 자동 번호를 사용해도 업무 키의 중복이 저절로 없어지지는 않습니다. 이어서 자주 실행할 WHERE 조건과 업무의 성공 기준을 정합니다. 트랜잭션을 어디서 끝내고 오류가 나면 무엇을 확인할지까지 정해 두면, 동시 요청이나 연결 장애가 생겨도 처리 방향이 흔들리지 않습니다. [스키마](../table-structure-schema/)와 [트랜잭션](../transaction/)을 함께 읽어 보세요. --- title: "8.2 테이블 구조와 스키마" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/table-structure-schema/ language: kr kind: page --- # 8.2 테이블 구조와 스키마 자동 번호가 있으니 중복 걱정은 끝났다고 생각하기 쉽습니다. 하지만 같은 외부 장비가 서로 다른 번호로 두 번 들어오는 일은 여전히 가능합니다. 행을 식별하는 키와 업무상 중복을 막는 키를 구분하는 것이 스키마 설계의 출발점입니다. ## 내부 식별자와 업무 키 다음 예제는 내부 번호와 외부 장비 코드를 따로 관리합니다. ```sql CREATE TRANSACTION TABLE ch8_schema ( id LONG PRIMARY KEY AUTO_INCREMENT, external_code VARCHAR(64) NOT NULL, device_name VARCHAR(128) NOT NULL, price DECIMAL(18,2), state JSON, updated_at DATETIME ); CREATE UNIQUE INDEX ch8_schema_code ON ch8_schema(external_code); INSERT INTO ch8_schema(external_code, device_name, price, state, updated_at) VALUES ('ERP-01', 'Pump A', 19900.25, '{"status":"NORMAL"}', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')); SELECT external_code, device_name, price, state->'$.status' AS status FROM ch8_schema; ``` ERP-01 한 행, 가격 19900.25, 상태 NORMAL이 조회됩니다. id는 서버가 부여합니다. 연속 번호나 빈 번호 없는 발급을 업무 조건으로 삼지는 마세요. 발급 번호를 받는 방법은 사용하는 SDK와 [AUTO_INCREMENT](/dbms/reference/sql/syntax/auto-increment-syntax/)를 확인하세요. ## PRIMARY KEY와 UNIQUE TRANSACTION의 PRIMARY KEY는 테이블당 하나이며 단일 컬럼입니다. 컬럼 뒤에 PRIMARY KEY를 지정하거나, 기존 테이블에 CREATE PRIMARY KEY INDEX로 추가할 수 있습니다. NULL과 중복이 있는 데이터에는 만들 수 없습니다. 여러 컬럼의 조합을 고유하게 만들려면 복합 UNIQUE INDEX를 사용합니다. CREATE TABLE 내부의 UNIQUE·FOREIGN KEY·테이블 수준 PRIMARY KEY 문법을 다른 DBMS에서 그대로 가져오지 마세요. 고유성은 테이블 생성 후 CREATE UNIQUE INDEX로 지정합니다. UNIQUE INDEX의 키에 NULL이 포함되면 NULL 포함 키끼리는 중복으로 보지 않습니다. “코드가 반드시 있고 고유해야 한다”면 예제처럼 NOT NULL도 함께 선언해야 합니다. 실수하기 쉬운 또 다른 값은 빈 문자열입니다. Machbase의 빈 문자열과 NULL 처리도 실제 입력 경로에서 확인하고, 필수 코드는 수집 단계에서 검증하세요. 다음 SQL은 UNIQUE 위반을 확인하는 선택 실습입니다. 정상 입력과 분리해서 실행하세요. ```sql -- 의도적으로 실패: external_code 중복 INSERT INTO ch8_schema(external_code, device_name) VALUES ('ERP-01', 'Duplicate Pump'); ``` 실패 후 ERP-01은 여전히 한 행이어야 합니다. 중복 시 갱신하려면 [UPSERT](../insert-on-duplicate-key-update/)의 별도 규칙을 사용합니다. ## 데이터 타입 선택 | 값 | 타입 선택 | 확인할 사항 | |---|---|---| | 식별자·수량 | SHORT·INTEGER·LONG 및 지원 unsigned 타입 | 범위와 NULL 예약값 | | 측정값·근삿값 | FLOAT·DOUBLE | 부동소수점 반올림 | | 금액·정확한 소수 | DECIMAL(M,D)와 NUMERIC 등 별칭 | precision·scale·입력 변환 | | 코드·이름 | VARCHAR(n) | 문자 수가 아닌 바이트 길이 | | 긴 문자열·바이너리 | TEXT/CLOB·BINARY/BLOB | 저장 지원과 정렬·함수·인덱스 지원을 구분 | | 발생·변경 시각 | DATETIME | 원본 시간대와 변환 형식 | | 네트워크 주소 | IPV4·IPV6 | 주소 형식과 비교 의미 | | 부가 속성 | JSON | 자주 검색할 경로와 타입 | | 고정 길이 수치 묶음 | 숫자 ARRAY | 요소 타입·길이·whole NULL과 요소 NULL | 전체 범위는 [데이터 타입 사전](/dbms/reference/sql/types/)을, 금액은 [DECIMAL](/dbms/reference/sql/types/decimal-numeric-fixed-point/)을 기준으로 확인하세요. 예제의 DOUBLE을 모든 금액 컬럼에 관성적으로 사용하지 않는 것이 좋습니다. ## 제약과 입력 검증 TRANSACTION에는 최소 하나의 사용자 컬럼이 필요합니다. LOG의 자동 도착 시각이나 TAG의 METADATA·BASETIME·BASEDISTANCE를 사용할 수 없습니다. 외래 키가 자동으로 참조 무결성을 검사한다고 가정하지 말고, 필요한 관계 검증을 애플리케이션과 데이터 점검 절차에 포함하세요. 예제의 UNIQUE INDEX가 있다고 장비 이름·가격의 업무 유효성까지 검사되는 것은 아닙니다. 필수값, 허용 상태, 수량 범위는 별도로 정의해야 합니다. ```sql SELECT COUNT(*) AS device_count FROM ch8_schema; DROP TABLE ch8_schema; ``` 정리 전 건수는 1입니다. 스키마 변경은 [생성·변경·삭제](../create-alter-drop/), 조회 경로는 [인덱스 설계](../index-performance/)에서 이어서 확인하세요. --- title: "8.3 생성, 변경, 삭제" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/create-alter-drop/ language: kr kind: page --- # 8.3 생성, 변경, 삭제 스키마 변경에서는 명령의 성공 여부만큼 기존 데이터와 입력 프로그램의 상태가 중요합니다. 이름을 바꾼 뒤 예전 SQL을 계속 실행하거나, 인덱스가 참조하는 컬럼부터 삭제하면 오류를 만나게 됩니다. 이 절에서는 표본을 넣은 상태에서 변경 전후를 확인합니다. ## 테이블 생성 ```sql CREATE TRANSACTION TABLE ch8_ddl ( id LONG, code VARCHAR(32), qty INTEGER ); INSERT INTO ch8_ddl VALUES (1, 'P-01', 10); ``` ## 키와 인덱스 생성 ```sql CREATE PRIMARY KEY INDEX ch8_ddl_pk ON ch8_ddl(id); CREATE UNIQUE INDEX ch8_ddl_code ON ch8_ddl(code); CREATE INDEX ch8_ddl_qty ON ch8_ddl(qty); SHOW INDEX ch8_ddl_code; ``` id는 단일 PRIMARY KEY, code는 별도 업무 키입니다. 기존 데이터에 중복이나 PRIMARY KEY의 NULL이 있으면 생성이 실패할 수 있습니다. 복합 고유성은 CREATE UNIQUE INDEX로 만들며, CREATE TABLE 내부의 UNIQUE 제약 문법은 지원하지 않습니다. 자동 번호가 필요한 경우에는 생성 시 `LONG PRIMARY KEY AUTO_INCREMENT`를 지정합니다. 이 절의 id는 직접 입력한 값이며 자동 번호 컬럼이 아닙니다. 두 생성 방식을 같은 객체에 중복 실행하지 마세요. [AUTO_INCREMENT](/dbms/reference/sql/syntax/auto-increment-syntax/)에 별도 실습이 있습니다. ## 컬럼 추가와 기본값 ```sql ALTER TABLE ch8_ddl ADD COLUMN (label VARCHAR(64)); ALTER TABLE ch8_ddl ADD COLUMN (status VARCHAR(16) DEFAULT 'NEW'); ALTER TABLE ch8_ddl ADD COLUMN (limits DECIMAL(12)[2] DEFAULT [10, 20]); SELECT id, label, status, limits FROM ch8_ddl ORDER BY id; ``` 1번 행에서 label은 NULL, status는 NEW, limits는 [10, 20]입니다. DEFAULT가 없는 ARRAY 컬럼을 추가하면 기존 행은 배열 전체가 NULL입니다. `ADD COLUMN`의 `DEFAULT`와 `CREATE TABLE`의 `DEFAULT`는 허용 범위가 다릅니다. 위처럼 `ADD COLUMN`에는 타입에 맞는 값을 지정할 수 있지만, `CREATE TABLE`의 컬럼 정의에서는 `DATETIME` 컬럼의 `DEFAULT SYSDATE`만 사용할 수 있습니다. 다른 타입이나 다른 값을 지정하면 각각 `ERR-02346`, `ERR-02347`로 거부됩니다. 생성 시점에 기본값이 필요하면 컬럼을 먼저 만들고 `ADD COLUMN`으로 추가하거나, 입력 구문에서 값을 지정하십시오. 배열 안의 일부 요소가 NULL인 경우와 구분하세요. 컬럼 정의는 괄호로 묶습니다. 이 문법은 다른 DBMS의 ALTER TABLE 형식과 혼동하기 쉽습니다. TRANSACTION은 MODIFY COLUMN으로 길이·타입을 변경하는 기능을 지원하지 않습니다. 필요하면 새 스키마로 이관하는 절차를 별도로 준비하세요. ## 컬럼 변경과 의존 객체 ```sql DROP INDEX ch8_ddl_qty; ALTER TABLE ch8_ddl DROP COLUMN (qty); ALTER TABLE ch8_ddl DROP COLUMN (label); ALTER TABLE ch8_ddl DROP COLUMN (limits); ALTER TABLE ch8_ddl RENAME COLUMN code TO product_code; SELECT id, product_code, status FROM ch8_ddl; SHOW INDEX ch8_ddl_code; ``` 기존 1번 행의 P-01·NEW 값과 업무 키 인덱스가 유지됩니다. PRIMARY KEY·UNIQUE·일반·JSON path 인덱스가 참조하는 컬럼은 해당 인덱스부터 확인해야 합니다. 마지막 사용자 컬럼은 삭제할 수 없습니다. VIEW가 테이블이나 컬럼을 참조하면 관련 이름 변경·삭제가 거부될 수 있습니다. VIEW뿐 아니라 애플리케이션 SQL과 prepared statement도 변경 영향을 받습니다. DDL 이후에는 기존 prepared statement를 무조건 재사용하지 말고 다시 준비할 필요를 확인하세요. ```sql ALTER TABLE ch8_ddl RENAME TO ch8_product; SELECT id, product_code, status FROM ch8_product; ``` 테이블 이름을 바꾼 뒤에는 ch8_product로 조회합니다. ## 전체 삭제와 테이블 삭제 ```sql BEGIN; TRUNCATE TABLE ch8_product; SELECT COUNT(*) AS during_delete FROM ch8_product; ROLLBACK; SELECT COUNT(*) AS after_rollback FROM ch8_product; DROP TABLE ch8_product; ``` 건수는 각각 0과 1입니다. 현재 TRANSACTION의 TRUNCATE는 전체 행 삭제로 처리되어 명시적 트랜잭션 안에서 롤백할 수 있습니다. 이 동작을 LOG·TAG의 TRUNCATE에 확대해서 적용하지 마세요. 마지막 DROP은 데이터·정의·관련 인덱스를 삭제합니다. ## DDL 운영 주의사항 TRUNCATE의 위 동작과 CREATE·ALTER·DROP 같은 스키마 작업을 구분해야 합니다. 스키마 변경을 BEGIN 안에 넣어 나중에 ROLLBACK할 수 있다고 가정하지 마세요. 같은 테이블의 활성 트랜잭션이나 열린 커서는 DDL을 차단할 수 있으므로 먼저 결과 집합과 업무 트랜잭션을 정리합니다. ADD·DROP COLUMN은 카탈로그와 별도 저장 파일을 함께 변경합니다. 작업 중 서버가 중단되었다면 재시작 복구가 끝나기 전에 같은 DDL을 반복하지 마세요. 복구 후 DESC, 대표 SELECT·INSERT, 인덱스·VIEW를 확인하고 서버 로그도 점검합니다. 내부 저장 파일을 직접 이동·수정·삭제하는 방식으로 복구하려 하지 마세요. 예상과 다른 스키마가 보이면 변경 전 DDL, 실행 순서, 첫 오류를 함께 살펴보세요. 마지막 오류만 보는 것보다 원인을 찾기 쉽습니다. --- title: "8.4 데이터 입력과 변경" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/data-input-mutation/ language: kr kind: page --- # 8.4 데이터 입력과 변경 UPDATE가 성공했다는 응답과 주문 한 건이 원하는 상태로 바뀌었다는 사실은 다릅니다. 조건에 맞는 행이 없으면 오류 없이 0행이 처리될 수 있기 때문입니다. 수정 전 대상, 영향 행 수, 수정 후 값을 함께 확인하는 습관이 필요합니다. ## 조건부 UPDATE와 DELETE ```sql CREATE TRANSACTION TABLE ch8_mutation ( order_id LONG PRIMARY KEY, amount DECIMAL(18,2), status VARCHAR(16), ordered DATETIME ); INSERT INTO ch8_mutation VALUES ( 1001, 19900.25, 'PENDING', TO_DATE('2026-01-01', 'YYYY-MM-DD')); INSERT INTO ch8_mutation VALUES ( 1002, 29900.50, 'CANCELLED', TO_DATE('2026-01-02', 'YYYY-MM-DD')); BEGIN; UPDATE ch8_mutation SET status = 'SHIPPED' WHERE order_id = 1001 AND status = 'PENDING'; SELECT order_id, status FROM ch8_mutation ORDER BY order_id; COMMIT; ``` 1001은 SHIPPED, 1002는 CANCELLED입니다. 같은 UPDATE를 다시 실행하면 PENDING이 아니므로 영향 행 수가 0입니다. 애플리케이션은 SDK의 영향 행 수를 확인해서 작업 성공·이미 처리됨·대상 없음 등을 업무 규칙에 맞게 구분해야 합니다. 아래 삭제도 대상 건수를 확인한 뒤 트랜잭션 안에서 실행합니다. ```sql BEGIN; SELECT COUNT(*) AS delete_candidates FROM ch8_mutation WHERE status = 'CANCELLED'; DELETE FROM ch8_mutation WHERE status = 'CANCELLED'; SELECT order_id, amount, status FROM ch8_mutation ORDER BY order_id; COMMIT; ``` 대상은 1행이고 삭제 후 1001 한 행만 남습니다. WHERE가 없는 UPDATE·DELETE는 전체 행 대상입니다. 운영에서 사전 SELECT와 실제 변경 사이에 다른 세션이 데이터를 바꿀 수 있으므로 사전 건수만을 성공의 근거로 삼지 마세요. ## INSERT SELECT와 자기 참조 ```sql CREATE TRANSACTION TABLE ch8_archive ( order_id LONG PRIMARY KEY, amount DECIMAL(18,2), status VARCHAR(16), ordered DATETIME ); INSERT INTO ch8_archive(order_id, amount, status, ordered) SELECT order_id, amount, status, ordered FROM ch8_mutation WHERE ordered < TO_DATE('2026-02-01', 'YYYY-MM-DD'); INSERT INTO ch8_mutation(order_id, amount, status, ordered) SELECT order_id + 10000, amount, status, ordered FROM ch8_mutation WHERE order_id = 1001; SELECT order_id FROM ch8_archive ORDER BY order_id; SELECT order_id FROM ch8_mutation ORDER BY order_id; ``` 아카이브는 1001, 원본은 1001·11001입니다. 자기 테이블에서 읽은 결과를 다시 삽입해도 이 예제가 새 행을 끝없이 재입력하지는 않습니다. 다만 키를 바꾸지 않고 복사하면 고유성 위반이 날 수 있습니다. 대상 컬럼 수와 타입을 맞추고 복사 범위를 재실행 가능하게 나누세요. 일반 제약 오류와 연결 장애를 같은 실패로 취급하지 마세요. 문장 실패가 전체 BEGIN을 자동 롤백하지는 않으며, 커밋 응답을 잃었을 때는 반영 여부를 다시 조회해야 합니다. [트랜잭션](../transaction/)에서 처리 경계를 설명합니다. ```sql DROP TABLE ch8_archive; DROP TABLE ch8_mutation; ``` ## 대량 입력과 배치 경계 TRANSACTION도 Append API를 지원합니다. 예전 파일명이나 앵커에 reject·unsupported가 남아 있다는 이유로 현재 지원하지 않는다고 판단하지 마세요. 언어별 공개 API는 [SDK 기능 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-append)를 기준으로 선택합니다. | 입력 방식 | 확인할 기준 | |---|---| | SQL INSERT·prepared 실행 | 문장별 오류와 명시적 트랜잭션 경계 | | 드라이버 batch | 실제 전송 단위, 부분 성공, 자동 커밋 | | Append | SDK 버퍼·서버 배치 경계, 오류 콜백·반환값 | | machloader | 매핑과 실패 행, 처리 구간 기록 | 현재 SQLCLI SQLAppendBatch 경로는 별도의 활성 트랜잭션이 없으면 서버 배치를 트랜잭션으로 처리합니다. 이 경로의 제약 오류 회귀에서는 실패한 배치 전체가 롤백됩니다. 이를 모든 SDK의 논리 배치, 여러 번의 flush, Appender 전체 수명에 대한 하나의 원자적 작업으로 확대해석해서는 안 됩니다. AUTO_INCREMENT와 DECIMAL 입력은 [SQLCLI와 ODBC](/dbms/development-tools-integration/cli-odbc/)의 전용 규칙도 확인하세요. Append 프로토콜의 도착 시각 필드가 TRANSACTION에 LOG의 자동 시간 컬럼을 만드는 것은 아닙니다. 실제 도입 전에는 정상 행에 중복 키·NULL 오류 한 행을 섞어 성공·실패 건수와 저장 결과를 확인하세요. 네트워크 오류 뒤 재전송은 이미 커밋된 데이터의 중복 처리까지 고려해야 합니다. --- title: "8.5 조회와 분석" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/query-analysis/ language: kr kind: page --- # 8.5 조회와 분석 조회 SQL이 문법상 맞아도 입력한 상태값과 조건이 다르면 결과는 0건입니다. 정렬 기준이 부족하면 같은 시간의 행이 조회할 때마다 다르게 보일 수도 있습니다. 다양한 상태와 경계 시각을 가진 표본으로 필터·정렬·집계를 함께 확인하겠습니다. ## 실습 데이터 준비 ```sql CREATE TRANSACTION TABLE ch8_query ( order_id LONG PRIMARY KEY, customer VARCHAR(32), item_id LONG, amount DECIMAL(18,2), status VARCHAR(16), ordered DATETIME ); CREATE TRANSACTION TABLE ch8_query_product (id LONG PRIMARY KEY, name VARCHAR(64)); INSERT INTO ch8_query_product VALUES (42, 'Pump'); INSERT INTO ch8_query_product VALUES (43, 'Valve'); INSERT INTO ch8_query VALUES ( 1001, 'C-01', 42, 10.25, 'PENDING', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')); INSERT INTO ch8_query VALUES ( 1002, 'C-01', 43, 20.50, 'PENDING', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS')); INSERT INTO ch8_query VALUES ( 1003, 'C-02', 42, 30.75, 'SHIPPED', TO_DATE('2026-01-02', 'YYYY-MM-DD')); SELECT order_id, amount, status FROM ch8_query WHERE order_id = 1001; ``` 1001·10.25·PENDING이 조회됩니다. ## 시간 조건과 정렬 ```sql SELECT order_id, amount FROM ch8_query WHERE ordered >= TO_DATE('2026-01-01', 'YYYY-MM-DD') AND ordered < TO_DATE('2026-01-02', 'YYYY-MM-DD') AND status = 'PENDING' ORDER BY ordered DESC, order_id DESC LIMIT 1; ``` 1002 한 행이 선택됩니다. ordered가 같아도 order_id로 순서를 고정했습니다. LIMIT만 지정해 원하는 순서를 기대하지 마세요. 연속된 일별 집계는 시작 포함·끝 제외 조건으로 경계의 중복을 피할 수 있습니다. LOG 전용 DURATION이나 자동 _arrival_time은 TRANSACTION에 적용하지 않습니다. ## 기준 정보 조인 ```sql SELECT o.order_id, p.name, o.amount FROM ch8_query o JOIN ch8_query_product p ON o.item_id = p.id WHERE o.customer = 'C-01' ORDER BY o.order_id; ``` 결과는 (1001, Pump, 10.25), (1002, Valve, 20.50)입니다. 이 INNER JOIN에서는 기준 정보가 없는 주문이 빠집니다. 조인 상대에 같은 키가 여러 행이면 결과가 늘어나므로 키의 고유성도 확인해야 합니다. 다른 타입과의 조인은 [JOIN 실습](../join-relational-query/)에서 다룹니다. ## 집계 조회 ```sql SELECT status, COUNT(*) AS cnt, SUM(amount) AS total_amount FROM ch8_query GROUP BY status ORDER BY status; ``` PENDING은 2건·30.75, SHIPPED는 1건·30.75입니다. 인덱스가 있다고 모든 집계가 자동으로 빨라지는 것은 아닙니다. 범위·그룹 수·반환량을 확인하고 반복 업무라면 별도 요약을 검토하세요. ## JSON 경로 조회 ```sql CREATE TRANSACTION TABLE ch8_query_json (id INTEGER PRIMARY KEY, state JSON); INSERT INTO ch8_query_json VALUES (1, '{"status":"ALARM","score":90}'); INSERT INTO ch8_query_json VALUES (2, '{"status":"NORMAL","score":10}'); INSERT INTO ch8_query_json VALUES (3, '{"score":20}'); SELECT id, state->'$.status' AS status FROM ch8_query_json WHERE state->'$.status' = 'ALARM' ORDER BY id; ``` 1번만 선택됩니다. 없는 경로와 조건값이 다른 행을 구분해서 표본을 준비하세요. 화살표 경로의 문자열 비교와 숫자 추출 함수의 비교를 혼동하지 마세요. 자주 쓰는 경로는 [JSON path 인덱스](../index-performance/#index-strategy-rdb-json-path)를 검토할 수 있습니다. ## 인덱스와 실행 계획 ```sql CREATE INDEX ch8_query_status_time ON ch8_query(status, ordered); EXPLAIN SELECT order_id FROM ch8_query WHERE status = 'PENDING' AND ordered >= TO_DATE('2026-01-01', 'YYYY-MM-DD'); ``` 복합 인덱스의 선두 컬럼과 조건을 맞추되, 실제 사용 여부는 EXPLAIN으로 확인하세요. Machbase가 관계형 쿼리 전체를 내부 SQLite에 그대로 맡긴다고 가정하면 인덱스 선택과 함수 조건을 잘못 해석할 수 있습니다. ```sql DROP TABLE ch8_query_json; DROP TABLE ch8_query_product; DROP TABLE ch8_query; ``` 결과가 다르면 집계나 JOIN부터 복잡하게 분석하기보다 원본 건수, WHERE 조건, 정렬 기준을 차례로 확인해 보세요. --- title: "8.6 인덱스와 성능" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/index-performance/ language: kr kind: page --- # 8.6 인덱스와 성능 인덱스는 조회 속도뿐 아니라 데이터의 고유성을 결정하기도 합니다. 업무 키를 보장할 인덱스와 읽기를 줄일 인덱스를 구분해야, 성능 정리 과정에서 필요한 제약을 실수로 없애지 않습니다. ## 인덱스 유형 | 종류 | 용도 | 복합 컬럼 | NULL | |---|---|---|---| | PRIMARY KEY | 행 식별, 테이블당 하나 | 미지원 | 불허 | | UNIQUE INDEX | 업무 키의 고유성 | 지원 | NULL 포함 키끼리는 중복 아님 | | 일반 인덱스 | 조건 조회의 접근 경로 | 지원 | 고유성 검사 없음 | TRANSACTION 인덱스는 BTREE로 표시됩니다. 컬럼의 PRIMARY KEY 또는 사후 CREATE PRIMARY KEY INDEX를 사용할 수 있습니다. LOG의 LSM·KEYWORD 인덱스 구문을 TRANSACTION에 그대로 적용하지 마세요. ## UNIQUE INDEX와 NULL ```sql CREATE TRANSACTION TABLE ch8_index_account ( id LONG PRIMARY KEY, email VARCHAR(120), tenant INTEGER NOT NULL, login VARCHAR(64) ); INSERT INTO ch8_index_account VALUES (1, 'a@example.com', 1, 'alpha'); INSERT INTO ch8_index_account VALUES (2, 'b@example.com', 1, 'beta'); INSERT INTO ch8_index_account VALUES (3, NULL, 2, NULL); INSERT INTO ch8_index_account VALUES (4, NULL, 2, NULL); CREATE UNIQUE INDEX ch8_index_email ON ch8_index_account(email); CREATE UNIQUE INDEX ch8_index_login ON ch8_index_account(tenant, login); SELECT id FROM ch8_index_account WHERE email IS NULL ORDER BY id; SHOW INDEX ch8_index_email; ``` 3·4번이 모두 존재하고 인덱스도 생성됩니다. 반드시 값이 있어야 하는 업무 키라면 UNIQUE뿐 아니라 각 컬럼의 NOT NULL도 필요합니다. CREATE TABLE 내부에 UNIQUE를 붙이는 대신 별도 CREATE UNIQUE INDEX를 사용하세요. 다음 두 문장은 각각 고유성 위반을 확인하는 선택 실습입니다. ```sql -- 의도적으로 실패: email 중복 INSERT INTO ch8_index_account VALUES (5, 'a@example.com', 1, 'gamma'); UPDATE ch8_index_account SET email = 'a@example.com' WHERE id = 2; ``` 실패 후에는 네 행과 2번의 b@example.com이 유지되어야 합니다. 현재 일반 UNIQUE 위반은 ERR-01418로 보고됩니다. 오류 메시지만 보고 어떤 업무 키가 중복되었는지 단정하지 말고 인덱스와 입력값을 함께 확인하세요. ## UNIQUE INDEX 삭제 ```sql DROP INDEX ch8_index_email; INSERT INTO ch8_index_account VALUES (5, 'a@example.com', 1, 'gamma'); SELECT id, email FROM ch8_index_account WHERE email = 'a@example.com' ORDER BY id; ``` 이제 1·5번이 모두 저장됩니다. 중복이 있는 상태에서 인덱스를 재생성하면 실패합니다. ```sql -- 의도적으로 실패: 기존 데이터에 중복이 있습니다. CREATE UNIQUE INDEX ch8_index_email ON ch8_index_account(email); ``` 실습 중 추가한 5번을 제거한 뒤 다시 생성합니다. ```sql DELETE FROM ch8_index_account WHERE id = 5; CREATE UNIQUE INDEX ch8_index_email ON ch8_index_account(email); SELECT COUNT(*) AS remaining_rows FROM ch8_index_account; ``` 건수는 4입니다. 운영에서는 인덱스를 없애기 전에 이것이 성능용인지 고유성 보장용인지부터 확인해야 합니다. ## 일반·복합 인덱스 ```sql CREATE TRANSACTION TABLE ch8_index_event ( id LONG PRIMARY KEY, status VARCHAR(16), created DATETIME, state JSON ); INSERT INTO ch8_index_event VALUES ( 1, 'OPEN', TO_DATE('2026-01-01', 'YYYY-MM-DD'), '{"status":"ALARM","code":500}'); INSERT INTO ch8_index_event VALUES ( 2, 'CLOSED', TO_DATE('2026-01-02', 'YYYY-MM-DD'), '{"status":"NORMAL","code":200}'); INSERT INTO ch8_index_event VALUES ( 3, 'OPEN', TO_DATE('2026-01-03', 'YYYY-MM-DD'), '{"code":500}'); EXPLAIN SELECT id FROM ch8_index_event WHERE status = 'OPEN' AND created >= TO_DATE('2026-01-01', 'YYYY-MM-DD'); CREATE INDEX ch8_index_status_time ON ch8_index_event(status, created); EXPLAIN SELECT id FROM ch8_index_event WHERE status = 'OPEN' AND created >= TO_DATE('2026-01-01', 'YYYY-MM-DD'); SELECT id FROM ch8_index_event WHERE status = 'OPEN' AND created >= TO_DATE('2026-01-01', 'YYYY-MM-DD') ORDER BY id; ``` 결과는 생성 전후 의미가 같아야 하며 마지막 조회는 1·3번입니다. 선두 컬럼을 조건에 맞추되 모든 복합 조건이 반드시 해당 인덱스를 사용한다고 단정하지 마세요. Machbase의 계획 선택과 지원되는 조건 모양을 EXPLAIN으로 확인합니다. 세 행의 실행 시간은 성능 벤치마크가 아닙니다. ## JSON path 인덱스 ```sql CREATE INDEX ch8_index_json_status ON ch8_index_event(state->'$.status'); CREATE INDEX ch8_index_json_code ON ch8_index_event(state->'$.code'); EXPLAIN SELECT id FROM ch8_index_event WHERE state->'$.status' = 'ALARM'; SELECT id FROM ch8_index_event WHERE state->'$.status' = 'ALARM' ORDER BY id; SELECT id FROM ch8_index_event WHERE state->'$.code' = '500' ORDER BY id; ``` 상태 조회는 1번, code 조회는 1·3번입니다. 인덱스를 만들었다고 모든 JSON 함수 표현식이 이 경로를 사용하는 것은 아닙니다. 화살표 경로와 숫자 추출 함수는 결과 타입과 비교 의미를 구분하세요. 복잡한 조건이 자주 반복되면 일반 컬럼으로 분리한 설계도 비교할 수 있습니다. JSON path UNIQUE INDEX를 만들 수 있는 경우에도 TRANSACTION UPSERT의 충돌 선택 키로는 사용하지 않습니다. [UPSERT 제약](../insert-on-duplicate-key-update/)을 확인하세요. ## 읽기·쓰기 비용 인덱스 생성 전후에 같은 데이터량·조건값·동시 입력률을 사용하세요. 조회 시간뿐 아니라 INSERT·UPDATE·DELETE 처리량과 인덱스 공간도 함께 비교합니다. LIMIT은 반환량을 줄이지만 어떤 행을 반환할지 고정하려면 ORDER BY가 필요합니다. ```sql DROP TABLE ch8_index_event; DROP TABLE ch8_index_account; ``` 성능 때문에 UNIQUE INDEX를 일반 인덱스로 바꾸면 고유성 보장은 사라집니다. 튜닝 전후에 결과와 제약이 같은지 먼저 확인하는 것이 좋은 출발점입니다. --- title: "8.7 운영과 데이터 생명주기" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/operations-lifecycle/ language: kr kind: page --- # 8.7 운영과 데이터 생명주기 오래된 업무 데이터를 지울 때는 날짜만 보는 것으로 충분하지 않습니다. 취소된 주문과 아직 처리 중인 주문은 보관 기준이 다르고, 정리 작업 자체도 다른 쓰기와 충돌할 수 있습니다. 업무 조건과 작업 단위를 먼저 정한 뒤 삭제를 실행하세요. ## 데이터 보관 기준 TAG·LOG에는 원본 시계열을, TRANSACTION에는 변경 가능한 상태나 요약 결과를 둘 수 있습니다. 두 종류의 보관 기간이 반드시 같을 필요는 없습니다. TRANSACTION에는 TAG·LOG의 Retention Policy를 그대로 적용하지 않습니다. 업무 조건에 맞는 DELETE와 외부 작업 스케줄을 설계하세요. ## 삭제와 롤백 ```sql CREATE TRANSACTION TABLE ch8_cleanup ( id LONG PRIMARY KEY, status VARCHAR(16), created DATETIME ); INSERT INTO ch8_cleanup VALUES (1, 'CANCELLED', TO_DATE('2025-12-01', 'YYYY-MM-DD')); INSERT INTO ch8_cleanup VALUES (2, 'PENDING', TO_DATE('2025-12-01', 'YYYY-MM-DD')); INSERT INTO ch8_cleanup VALUES (3, 'CANCELLED', TO_DATE('2026-02-01', 'YYYY-MM-DD')); BEGIN; SELECT COUNT(*) AS delete_candidates FROM ch8_cleanup WHERE status = 'CANCELLED' AND created < TO_DATE('2026-01-01', 'YYYY-MM-DD'); DELETE FROM ch8_cleanup WHERE status = 'CANCELLED' AND created < TO_DATE('2026-01-01', 'YYYY-MM-DD'); SELECT id, status FROM ch8_cleanup ORDER BY id; ROLLBACK; SELECT COUNT(*) AS after_rollback FROM ch8_cleanup; ``` 대상은 1건, 삭제 중에는 2·3번, 롤백 후에는 3건입니다. 확인용 실습에서는 롤백했지만 운영에서 확정할 때는 업무 승인과 결과 확인 후 COMMIT을 선택합니다. 결과 커서는 종료 전에 닫아야 합니다. 실수하기 쉬운 부분은 사전 조회 건수와 실제 영향 행 수를 같은 것으로 보는 것입니다. 동시 변경과 스냅샷 충돌을 고려하고, 실행 시 받은 영향 행 수와 최종 상태까지 기록하세요. [잠금과 재시도](../locking-conflict-timeout/)에서 관련 상황을 확인할 수 있습니다. ## 배치 처리와 재개 전체 삭제를 하나의 긴 트랜잭션으로 처리하기보다 날짜 구간이나 고유 키 범위로 나눕니다. 각 구간의 경계, 처리 건수와 커밋 결과를 기록하세요. 중간 장애 뒤 어디부터 다시 처리해야 하는지 알 수 있어야 합니다. 기준 정보 이동을 위해 다른 테이블에 복사한 뒤 원본을 삭제할 때도 주의가 필요합니다. 현재 여러 TRANSACTION 테이블의 장애 시 원자적 커밋을 보장한다고 가정하지 마세요. [트랜잭션의 보장 범위](../transaction/)에 맞춰 복사 결과 확인과 재개 절차를 설계합니다. 외부 API 호출과 긴 파일 작업을 BEGIN 안에서 기다리지 않는 것도 중요합니다. ## 백업 검증 백업 명령의 성공만 확인하지 말고, 마운트한 백업에서 업무 키·행 수·합계·인덱스를 점검하세요. 마운트 조회에는 `마운트명.소유자.테이블명`의 세 부분 이름을 사용합니다. 두 부분 이름을 사용해 운영 데이터와 혼동하지 마세요. TRANSACTION은 증분 백업에서도 해당 백업 시점의 전체 테이블 저장소 스냅샷이 포함됩니다. 변경한 행 수만큼만 공간이 늘어날 것이라고 계산하지 마세요. 구체적인 실습은 [백업·복원·마운트](../backup-restore-mount/)에 있습니다. ## 정리 후 운영 점검 행이 줄었다고 운영체제의 파일 크기가 곧바로 같은 비율로 줄어드는 것은 아닙니다. 업무 데이터 건수, 실제 파일 사용량, 백업 보관량을 각각 살펴보세요. 파일 크기를 줄이려고 내부 SQLite 파일에 직접 접속하거나 임의로 파일을 삭제하지 마세요. ```sql DROP TABLE ch8_cleanup; ``` 정리 작업이 실패하면 추가 삭제부터 시도하지 말고 마지막 성공 구간과 커밋 결과부터 확인하세요. 이런 기록이 있어야 데이터 누락과 중복 처리를 줄일 수 있습니다. --- title: "8.8 제약, 오류, 문제 해결" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/constraints-errors-troubleshooting/ language: kr kind: page --- # 8.8 제약, 오류, 문제 해결 다른 RDBMS에서 쓰던 SQL을 옮길 때는 이름이 비슷하다는 이유만으로 같은 기능을 기대하기 쉽습니다. 먼저 Edition과 공개 문법을 확인하고, 그다음 데이터 제약과 동시 접근 문제를 나누어 살펴보세요. ## 기능 지원 범위 TRANSACTION은 Standard Edition 전용입니다. Cluster에서는 CREATE TABLE·CREATE TRANSACTION TABLE·CREATE TXN TABLE이 모두 거부됩니다. LOG는 CREATE LOG TABLE로 명시해야 합니다. | 요구 | 지원·대안 | |---|---| | 일반 SELECT·INSERT·UPDATE·DELETE | 지원, WHERE 없는 변경은 전체 행 대상 | | 단일 PRIMARY KEY | 지원, 테이블당 하나 | | 단일·복합 UNIQUE INDEX | 지원, 테이블 생성 후 별도 생성 | | 컬럼 뒤 UNIQUE·테이블 수준 PRIMARY KEY | 해당 생성 문법 미지원 | | FOREIGN KEY·Trigger·Stored Procedure | 미지원 | | BEGIN·COMMIT·ROLLBACK | 지원, 중첩 BEGIN·SAVEPOINT 미지원 | | ADD·DROP·RENAME COLUMN, RENAME TO | 지원 조건 확인 | | MODIFY COLUMN | 미지원 | | Append | SDK별 공개 경로와 배치 경계 확인 | | TAG의 METADATA·BASETIME·BASEDISTANCE | TRANSACTION에는 적용하지 않음 | Cluster에서 작은 기준 정보 변경은 LOOKUP을 검토할 수 있지만, 명시적 관계형 트랜잭션의 완전한 대체는 아닙니다. 원본 이벤트는 LOG·TAG, 관계형 트랜잭션은 별도 RDBMS 등 요구에 맞게 나누어야 합니다. ## 오류별 진단 | 증상 | 확인할 부분 | 다음 조치 | |---|---|---| | ERR-01418 고유성 위반 | PK·UNIQUE 키와 기존 데이터 | 입력 수정 또는 UPSERT 규칙 검토 | | NOT NULL 위반 | 생략·NULL·빈 문자열·DEFAULT | 입력값과 제약 확인 | | UPDATE가 0행 처리 | 키와 현재 상태 조건 | 대상 없음·이미 처리됨 등 업무 상태 확인 | | Resource busy | 다른 쓰기, 오래된 읽기 스냅샷, 열린 커서 | 원인에 따라 대기·새 트랜잭션·커서 종료 | | COMMIT·ROLLBACK이 busy | 같은 연결의 열린 결과 집합 | 결과 집합 정리 후 종료 재시도 | | 오류 뒤 후속 SQL도 거부 | 롤백 전용 상태 여부 | ROLLBACK 후 새 작업 시작 | | DDL 실패 | 참조 인덱스·VIEW·활성 트랜잭션 | 의존 관계와 변경 시점 조정 | | 마운트에서 값이 예상과 다름 | 마운트명·소유자·테이블명 | 운영 데이터와 백업 데이터 구분 | 같은 Resource busy라도 재시도 방법은 다릅니다. 자세한 두 연결 실습은 [잠금과 busy timeout](../locking-conflict-timeout/)에서 확인하세요. 오류 문자열에 TRANSACTION이 있다는 이유만으로 반복 실행하지 마세요. ## 제약 오류와 데이터 보존 ```sql CREATE TRANSACTION TABLE ch8_error ( id LONG PRIMARY KEY, code VARCHAR(32) NOT NULL, value INTEGER ); CREATE UNIQUE INDEX ch8_error_code ON ch8_error(code); INSERT INTO ch8_error VALUES (1, 'A', 10); ``` 아래는 각각 의도적으로 실패하는 선택 실습입니다. 정상 SQL과 묶지 말고 확인할 문장만 실행하세요. ```sql -- PRIMARY KEY 중복 INSERT INTO ch8_error VALUES (1, 'B', 20); -- UNIQUE 중복 INSERT INTO ch8_error VALUES (2, 'A', 20); -- 필수값 위반 INSERT INTO ch8_error VALUES (3, NULL, 30); -- 지원하지 않는 스키마 변경 ALTER TABLE ch8_error MODIFY COLUMN (code VARCHAR(64)); ``` ```sql SELECT id, code, value FROM ch8_error ORDER BY id; DROP TABLE ch8_error; ``` 마지막 조회에는 (1, A, 10)만 남습니다. 단일 문장 실패가 상태를 보존한다는 점과, BEGIN 안의 앞선 성공 문장까지 자동으로 취소되는 것은 아니라는 점을 구분해야 합니다. [트랜잭션](../transaction/)에 해당 비교 예제가 있습니다. ## 진단 정보 수집 서버 버전·Edition, DDL과 인덱스, 실행 SQL, 오류 코드와 전체 메시지, 실제 영향 행 수를 함께 준비하면 좋습니다. 연결 장애라면 COMMIT 요청·응답 시각과 업무 키도 필요합니다. 비밀번호·개인정보·민감한 업무 값은 가린 뒤 재현에 필요한 최소 표본만 공유하세요. 복구를 위해 내부 저장 파일을 직접 수정하거나 운영 테이블을 재생성하지 마세요. 원인과 반영 상태를 먼저 확인하는 것이 데이터 손실을 줄이는 길입니다. --- title: "8.9 트랜잭션" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/transaction/ language: kr kind: page --- # 8.9 트랜잭션 여러 SQL을 실행하다가 중간 문장이 실패하면 앞선 변경도 사라졌다고 생각하기 쉽습니다. 하지만 일반 제약 오류는 실패한 문장과 전체 트랜잭션을 구분해서 처리합니다. 이 절에서는 같은 테이블 안의 변경으로 COMMIT·ROLLBACK 경계를 먼저 확인합니다. ## 트랜잭션 실행 ```sql CREATE TRANSACTION TABLE ch8_tx ( item_id LONG PRIMARY KEY, qty INTEGER NOT NULL ); INSERT INTO ch8_tx VALUES (1, 10); INSERT INTO ch8_tx VALUES (2, 20); BEGIN; UPDATE ch8_tx SET qty = qty - 3 WHERE item_id = 1 AND qty >= 3; UPDATE ch8_tx SET qty = qty + 3 WHERE item_id = 2; SELECT item_id, qty FROM ch8_tx ORDER BY item_id; ROLLBACK; SELECT item_id, qty FROM ch8_tx ORDER BY item_id; ``` 트랜잭션 안에서는 7·23, ROLLBACK 뒤에는 10·20이 조회됩니다. 애플리케이션은 두 UPDATE가 각각 기대한 1행을 처리했는지 확인해야 합니다. 조건 미일치로 0행이 갱신된 것은 SQL 오류가 아니므로 DB가 업무 실패를 자동 판단하지는 않습니다. 다음은 같은 변경을 확정하는 정상 실습입니다. ```sql BEGIN; UPDATE ch8_tx SET qty = qty - 3 WHERE item_id = 1 AND qty >= 3; UPDATE ch8_tx SET qty = qty + 3 WHERE item_id = 2; COMMIT; SELECT item_id, qty FROM ch8_tx ORDER BY item_id; ``` 확정 후 값은 7·23입니다. 공개 문법은 BEGIN이며 BEGIN TRANSACTION, 중첩 BEGIN, SAVEPOINT는 지원하지 않습니다. 명시적 트랜잭션이 없으면 TRANSACTION DML은 문장 단위로 처리됩니다. 드라이버의 자동 커밋·트랜잭션 API는 별도로 확인하세요. ## 문장 오류와 롤백 아래는 오류 처리 학습용 흐름입니다. 중복 INSERT는 의도적으로 실패합니다. SQL 실행 도구가 오류에서 중단되면 같은 연결에서 반드시 ROLLBACK까지 실행하세요. ```sql BEGIN; UPDATE ch8_tx SET qty = 100 WHERE item_id = 1; ``` ```sql -- 의도적으로 실패: item_id 중복 INSERT INTO ch8_tx VALUES (1, 999); ``` ```sql SELECT item_id, qty FROM ch8_tx ORDER BY item_id; ROLLBACK; SELECT item_id, qty FROM ch8_tx ORDER BY item_id; ``` 첫 조회는 100·23, ROLLBACK 뒤는 7·23입니다. 일반 제약 오류가 실패한 문장을 되돌려도 앞선 성공 문장은 트랜잭션 안에 남습니다. 업무 전체를 취소하려면 애플리케이션이 ROLLBACK을 선택해야 합니다. 모든 오류 뒤 계속 실행할 수 있는 것은 아닙니다. 복구 과정에서 롤백 전용 상태가 되었다면 ROLLBACK으로 종료해야 합니다. ## TRUNCATE와 롤백 ```sql BEGIN; TRUNCATE TABLE ch8_tx; SELECT COUNT(*) AS during_truncate FROM ch8_tx; ROLLBACK; SELECT COUNT(*) AS after_rollback FROM ch8_tx; ``` 결과는 0과 2입니다. 현재 TRANSACTION의 TRUNCATE는 전체 행 삭제로 명시적 트랜잭션에 포함됩니다. LOG·TAG의 데이터 정리나 CREATE·ALTER·DROP 같은 스키마 변경과 혼동하지 마세요. 스키마 작업은 업무 트랜잭션 밖에서 수행하는 것이 운영 기준입니다. ## 테이블 타입별 트랜잭션 범위 활성 TRANSACTION 트랜잭션에서도 LOG·TAG·LOOKUP·VOLATILE 조회와 혼합 조인은 허용됩니다. 반면 이 타입들의 쓰기를 같은 트랜잭션에 묶을 수는 없습니다. 허용된 조회라고 해서 해당 타입이 TRANSACTION과 동일한 스냅샷·롤백 보장을 얻는 것도 아닙니다. 원본 수집과 업무 상태 변경 사이의 일관성은 별도로 설계하세요. TRANSACTION 테이블의 읽기는 다른 세션의 미커밋 변경을 보지 않는 스냅샷을 사용합니다. BEGIN 호출 시 모든 테이블에 공통인 읽기 시점이 한꺼번에 고정된다고 가정하지 마세요. 읽은 뒤 쓰기로 전환할 때의 충돌은 [잠금과 재시도](../locking-conflict-timeout/)에서 다룹니다. ## 다중 테이블 커밋과 장애 여러 TRANSACTION 테이블의 DML을 BEGIN으로 묶고 정상적으로 COMMIT·ROLLBACK할 수 있습니다. 다만 현재 저장소는 테이블별 핸들을 사용하고 COMMIT도 핸들별로 순차 처리합니다. 따라서 커밋 도중 장애가 나도 여러 테이블 전체가 반드시 함께 확정되거나 함께 취소되는 원자적 커밋을 보장한다고 해석하면 안 됩니다. 여러 테이블의 불가분 처리가 필수인 업무는 이 제한을 먼저 검토하세요. 커밋 실패나 응답 유실 후 ROLLBACK을 보냈다는 이유만으로 모든 테이블이 원상 복구되었다고 판단하지 말고, 업무 키로 반영 상태를 다시 확인해야 합니다. 이 절의 기본 실습을 한 테이블의 두 행으로 구성한 이유이기도 합니다. ## 커서와 트랜잭션 종료 열린 TRANSACTION 커서가 있으면 COMMIT·ROLLBACK이 Resource busy로 실패할 수 있습니다. SDK의 결과 집합·statement를 정리한 뒤 종료 문을 다시 실행하세요. 연결 종료 시 미커밋 변경은 롤백되지만, 연결을 잃기 전에 서버에서 이미 커밋했는지는 클라이언트가 별도로 확인해야 합니다. ```sql DROP TABLE ch8_tx; ``` BEGIN 안에서 외부 API 호출이나 긴 계산을 기다리지 마세요. 트랜잭션을 짧게 유지하고, 실패할 때 “다시 실행할 것인가, 결과부터 확인할 것인가”를 구분해 두면 운영 중 판단이 훨씬 명확해집니다. --- title: "8.10 잠금, 충돌, busy timeout" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/locking-conflict-timeout/ language: kr kind: page --- # 8.10 잠금, 충돌, busy timeout 서로 다른 행을 바꾸는데도 Resource busy가 발생하면 행 잠금만 떠올려서는 원인을 찾기 어렵습니다. TRANSACTION의 쓰기 충돌은 같은 테이블의 다른 행 사이에서도 발생할 수 있습니다. 기다리면 풀리는 충돌과 트랜잭션을 새로 시작해야 하는 충돌을 구분해 보겠습니다. ## 실습 환경 이 실습은 기본 WAL 설정인 검증용 Standard 환경을 기준으로 합니다. A·B는 같은 계정·데이터베이스에 접속한 서로 다른 연결입니다. 각 코드 블록을 표시된 순서대로 실행하고, SELECT 결과는 끝까지 읽어 커서를 닫으세요. 스냅샷 실습을 위해 운영 서버의 저널 모드를 바꾸지는 마세요. A에서 준비합니다. ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME = 'TRANSACTION_JOURNAL_MODE'; CREATE TRANSACTION TABLE ch8_lock (id INTEGER PRIMARY KEY, val INTEGER); INSERT INTO ch8_lock VALUES (1, 10); INSERT INTO ch8_lock VALUES (2, 20); ``` TRANSACTION_JOURNAL_MODE=4가 WAL입니다. 다른 값이면 아래 WAL 스냅샷 실습의 결과를 그대로 기대하지 말고 환경부터 확인하세요. ## 동시 쓰기 충돌 A에서 트랜잭션을 열고 종료하지 않은 채 기다립니다. ```sql BEGIN; UPDATE ch8_lock SET val = 11 WHERE id = 1; ``` 이어서 B에서 조회합니다. 실습용 B 연결만 대기 시간을 0으로 바꿉니다. ```sql ALTER SESSION SET TRANSACTION_BUSY_TIMEOUT_MS = 0; SELECT id, val FROM ch8_lock ORDER BY id; ``` B는 아직 커밋되지 않은 A의 11이 아니라 기존 값 10·20을 봅니다. 다음 B의 UPDATE는 의도적으로 Resource busy 오류가 나는 단계입니다. ```sql -- B: A와 다른 행이지만 같은 테이블 쓰기이므로 실패가 예상됩니다. UPDATE ch8_lock SET val = val + 1 WHERE id = 2; ``` A에서 COMMIT한 다음 B의 UPDATE를 다시 실행합니다. ```sql -- A COMMIT; ``` ```sql -- B UPDATE ch8_lock SET val = val + 1 WHERE id = 2; SELECT id, val FROM ch8_lock ORDER BY id; ``` 이제 값은 11·21입니다. 같은 테이블의 쓰기 충돌을 일반적인 행 단위 잠금으로 해석하면 안 된다는 것을 보여 줍니다. ## WAL 스냅샷 충돌 이전 단계가 모두 끝난 뒤 A에서 읽기 트랜잭션을 엽니다. ```sql -- A BEGIN; SELECT val FROM ch8_lock WHERE id = 1; ``` A가 11을 읽은 뒤 B에서 값을 변경합니다. B는 명시적 트랜잭션이 없는 상태입니다. ```sql -- B UPDATE ch8_lock SET val = val + 10 WHERE id = 1; ``` 이어서 A에서 쓰기로 전환하면 스냅샷 충돌이 예상됩니다. ```sql -- A: 의도적으로 실패하는 단계 UPDATE ch8_lock SET val = val + 1 WHERE id = 1; ``` 다른 연결이 이미 커밋했으므로 A의 오래된 읽기 스냅샷을 그대로 쓰기로 전환할 수 없습니다. 현재 이 충돌은 busy timeout을 늘리거나 -1로 설정해도 기다려서 해결되지 않습니다. 같은 UPDATE만 반복하지 말고 A의 트랜잭션을 종료한 뒤 새 상태에서 다시 판단합니다. ```sql -- A ROLLBACK; BEGIN; UPDATE ch8_lock SET val = val + 1 WHERE id = 1; COMMIT; SELECT id, val FROM ch8_lock ORDER BY id; ``` 최종 값은 22·21입니다. 업무에서 읽은 값으로 다음 변경을 계산했다면 새로운 트랜잭션에서 읽기와 판단부터 다시 해야 합니다. ## busy timeout 서버 기본 TRANSACTION_BUSY_TIMEOUT_MS는 30000ms이고 새 세션에 복사됩니다. 현재 세션은 ALTER SESSION으로 바꿀 수 있습니다. | 값 | 일시적인 잠금 충돌 처리 | |---|---| | -1 | 취소·연결 종료 또는 잠금 해제까지 대기 | | 0 | 대기 없이 busy 반환 | | 양수 | 해당 밀리초 범위 내 대기 후 처리 또는 busy 반환 | 스냅샷 전환 충돌처럼 재시도로 풀리지 않는 경우는 이 정책의 예외입니다. -1을 모든 충돌에 대한 무한 재시도라고 해석하지 마세요. DDL_LOCK_TIMEOUT은 별도의 DDL 잠금 대기 설정이며 이를 바꾼다고 스냅샷 충돌이 해결되지는 않습니다. ## 오류와 재시도 메시지에 TRANSACTION이 있다는 이유만으로 재시도하면 타입·제약·권한 오류까지 반복하게 됩니다. 드라이버의 오류 코드와 전체 진단, 작업 종류를 함께 보고 재시도 가능한 잠금 충돌인지 구분하세요. 명시적 트랜잭션에서 재시도하려면 열린 결과 집합을 닫고 ROLLBACK한 뒤, 제한된 횟수·전체 요청 시간 안에서 새 트랜잭션으로 재실행합니다. 연결 유실이나 COMMIT 응답 유실은 별도 경우입니다. 이미 처리되었는지 업무 키로 확인하지 않고 카운터 증가나 주문 처리를 다시 실행하면 중복 반영될 수 있습니다. ## 충돌 진단 ```sql SELECT id, user_name, user_ip, transaction_busy_timeout_ms FROM V$SESSION WHERE closed = 0 ORDER BY id; SELECT id, sess_id, state, query FROM V$STMT WHERE state LIKE 'Execute in progress%' OR state LIKE 'Fetch in progress%'; ``` 이 조회가 잠금 소유자를 직접 매핑해 주는 것은 아닙니다. V$MUTEX도 서버 내부 뮤텍스 통계이지 업무 행 잠금 목록은 아닙니다. 접속 정보와 애플리케이션의 BEGIN·종료 기록을 함께 확인하세요. 세션 강제 종료는 미커밋 업무를 취소할 수 있으므로 첫 조치로 사용하지 마세요. 두 연결에 열린 트랜잭션이 없는지 확인한 뒤 A에서 정리합니다. ```sql DROP TABLE ch8_lock; ``` 실습용 연결 A·B를 종료하면 B에 지정한 세션별 timeout도 더 이상 영향을 주지 않습니다. 운영에서는 외부 API 호출·긴 계산을 BEGIN 밖으로 옮기는 것만으로도 대기 원인을 줄일 수 있습니다. --- title: "8.11 JOIN과 관계형 조회 설계" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/join-relational-query/ language: kr kind: page --- # 8.11 JOIN과 관계형 조회 설계 JOIN을 붙였는데 주문 건수가 줄거나 늘어났다면 먼저 관계의 모양을 확인해야 합니다. INNER JOIN은 상대가 없는 행을 제외하고, 한 건에 여러 상대가 맞으면 결과를 늘립니다. SQL이 오류 없이 실행되는 것과 업무 건수를 올바르게 집계하는 것은 다른 문제입니다. ## TRANSACTION–LOOKUP 조인 ```sql CREATE TRANSACTION TABLE ch8_join_order ( order_id LONG PRIMARY KEY, item_id LONG, qty INTEGER ); CREATE LOOKUP TABLE ch8_join_product (id LONG PRIMARY KEY, name VARCHAR(64)); CREATE TRANSACTION TABLE ch8_join_payment (order_id LONG PRIMARY KEY, status VARCHAR(16)); INSERT INTO ch8_join_order VALUES (1, 42, 2); INSERT INTO ch8_join_order VALUES (2, 99, 1); INSERT INTO ch8_join_product VALUES (42, 'Pump'); INSERT INTO ch8_join_payment VALUES (1, 'PAID'); SELECT o.order_id, p.name, o.qty FROM ch8_join_order o JOIN ch8_join_product p ON o.item_id = p.id ORDER BY o.order_id; SELECT o.order_id, p.name, o.qty FROM ch8_join_order o LEFT JOIN ch8_join_product p ON o.item_id = p.id ORDER BY o.order_id; ``` INNER JOIN은 1번 주문만, LEFT JOIN은 1·2번 주문을 반환합니다. 2번의 제품 이름은 NULL입니다. 제품 99가 없어도 주문 입력 자체는 외래 키로 차단되지 않으므로, 필요한 참조 검증은 별도로 설계해야 합니다. 실수하기 쉬운 부분은 LEFT JOIN 뒤 오른쪽 테이블 조건을 WHERE에 넣는 것입니다. 예를 들어 `WHERE p.name = 'Pump'`를 추가하면 NULL 행이 제외됩니다. 상대를 찾는 조건인지 최종 결과를 거르는 조건인지 구분하세요. ## TRANSACTION 간 조인 ```sql SELECT o.order_id, o.qty, p.status FROM ch8_join_order o JOIN ch8_join_payment p ON o.order_id = p.order_id ORDER BY o.order_id; ``` (1, 2, PAID) 한 행이 조회됩니다. 실제 결제 이력이 주문당 여러 행이면 이 결과도 여러 행이 됩니다. 주문 금액을 조인 후 합산할 때 중복 합계가 생기지 않도록 관계와 집계 단위를 확인하세요. ## TAG 근접 시간 조인 다음은 알람 전후 5초에 있는 모든 측정값을 연결하는 예제입니다. ```sql CREATE TRANSACTION TABLE ch8_join_alarm ( alarm_id LONG PRIMARY KEY, sensor VARCHAR(32), occurred DATETIME ); CREATE TAG TABLE ch8_join_sensor ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); INSERT INTO ch8_join_alarm VALUES ( 1, 'TEMP-01', TO_DATE('2026-01-01 10:00:05', 'YYYY-MM-DD HH24:MI:SS')); INSERT INTO ch8_join_sensor VALUES ( 'TEMP-01', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 10); INSERT INTO ch8_join_sensor VALUES ( 'TEMP-01', TO_DATE('2026-01-01 10:00:10', 'YYYY-MM-DD HH24:MI:SS'), 20); INSERT INTO ch8_join_sensor VALUES ( 'TEMP-01', TO_DATE('2026-01-01 10:00:11', 'YYYY-MM-DD HH24:MI:SS'), 30); SELECT a.alarm_id, s.time, s.value FROM ch8_join_alarm a JOIN ch8_join_sensor s ON a.sensor = s.name WHERE s.time >= a.occurred - 5s AND s.time <= a.occurred + 5s ORDER BY a.alarm_id, s.time; ``` 알람 1번에 값 10·20의 두 행이 연결됩니다. 양쪽 경계를 포함하며 값 30은 제외됩니다. 이 쿼리는 가장 가까운 측정값 하나나 정확히 같은 시각의 값을 고르는 기능이 아닙니다. 한 값만 필요하면 최근 이전 값·최단 거리 등 선택 기준과 동률 처리 규칙을 별도로 정하세요. ## 조인 설계 기준 조인 키의 타입과 값 형식을 맞추고 시간 범위를 제한한 뒤 실행 계획을 확인합니다. 필요한 컬럼만 반환하고, 함수·형 변환을 조인 키에 무심코 추가해 접근 경로가 달라지지 않는지 비교하세요. 조인 순서나 알고리즘이 다른 RDBMS와 같다고 가정하지 마세요. 혼합 조인이 허용되어도 다른 테이블 타입이 TRANSACTION과 동일한 트랜잭션 스냅샷을 공유하는 것은 아닙니다. 또한 현재 LOOKUP 설명을 붙인 결과가 과거 시점의 설명까지 재현하지는 않습니다. ```sql DROP TABLE ch8_join_sensor; DROP TABLE ch8_join_alarm; DROP TABLE ch8_join_payment; DROP TABLE ch8_join_product; DROP TABLE ch8_join_order; ``` 결과 건수가 맞지 않으면 조인 전 건수와 키별 상대 행 수를 먼저 비교해 보세요. 이 두 가지가 확인되면 쿼리를 어떻게 고쳐야 할지도 명확해집니다. --- title: "8.12 TRANSACTION 백업, 복원, 마운트" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/backup-restore-mount/ language: kr kind: page --- # 8.12 TRANSACTION 백업, 복원, 마운트 백업 명령이 성공했다고 복구 준비가 끝난 것은 아닙니다. 특히 운영 테이블과 백업 테이블의 이름이 같으면 잘못된 쪽을 조회하고도 검증이 끝났다고 생각하기 쉽습니다. 백업 이후 운영 값을 바꾸어 두 결과가 다른지 확인하는 방법으로 검증해 보겠습니다. ## 백업·복원 지원 범위 | 작업 | TRANSACTION 관련 기준 | |---|---| | BACKUP DATABASE | 대상 범위의 영속 TRANSACTION 데이터 포함 | | BACKUP TABLE | 지정한 테이블과 필요한 메타데이터 백업 | | 증분 백업 | TRANSACTION 저장소는 그 백업 시점의 전체 스냅샷으로 포함 | | MOUNT DATABASE | 백업을 읽기 전용으로 조회 | | 오프라인 인스턴스 복원 | 서버를 중단하고 machadmin -r 사용 | | 온라인 논리 데이터베이스 복원 | 지원되는 논리 백업을 RESTORE DATABASE로 복원 | 증분 백업의 TRANSACTION 데이터를 변경 행만의 델타라고 계산하지 마세요. 내부 파일을 직접 복사하는 대신 지원되는 백업 명령을 사용해야 합니다. TRANSACTION이 포함된 백업을 Cluster에서 사용하기 위한 우회 경로로 삼을 수도 없습니다. ## 백업과 마운트 검증 이 실습은 검증용 Standard 환경의 SYS 계정을 기준으로 합니다. 백업·마운트 권한과 서버 파일 접근 권한이 필요합니다. 경로는 서버 기준 예시이며 실행할 때마다 존재하지 않는 새 경로로 바꾸세요. 상위 디렉터리와 여유 공간을 확인하고, 경로를 재사용하려고 기존 백업을 삭제하지 마세요. ```sql CREATE TRANSACTION TABLE ch8_backup ( id LONG PRIMARY KEY, code VARCHAR(32) NOT NULL, amount DECIMAL(18,2) ); CREATE UNIQUE INDEX ch8_backup_code ON ch8_backup(code); INSERT INTO ch8_backup VALUES (1, 'A', 10.25); INSERT INTO ch8_backup VALUES (2, 'B', 20.50); BACKUP TABLE ch8_backup INTO DISK = '/backup/ch8_table_20260907_a'; UPDATE ch8_backup SET amount = 99.00 WHERE id = 1; MOUNT DATABASE '/backup/ch8_table_20260907_a' TO ch8_bak; SELECT id, code, amount FROM ch8_bak.SYS.ch8_backup ORDER BY id; SELECT id, code, amount FROM ch8_backup ORDER BY id; ``` 백업 쪽은 10.25·20.50, 운영 쪽은 99.00·20.50입니다. 마운트 조회는 `마운트명.소유자.테이블명`의 세 부분 이름을 사용합니다. 다른 소유 계정으로 실습했다면 SYS 부분도 실제 소유자에 맞춰야 합니다. 실수하기 쉬운 부분은 `SELECT ... FROM ch8_backup`만 실행하는 것입니다. 이것은 마운트 백업 검증이 아니라 현재 연결의 운영 테이블 조회입니다. 백업에 포함하지 않은 다른 테이블이 당연히 있어야 한다고 기대하지도 마세요. 마운트는 읽기 전용이며 UPDATE·DDL을 수행하는 복구 환경이 아닙니다. 검증을 마치고 열린 커서를 정리한 뒤 마운트를 해제합니다. ```sql UMOUNT DATABASE ch8_bak; DROP TABLE ch8_backup; ``` 이 정리는 실습 테이블과 마운트만 제거합니다. 백업 디렉터리는 남겨 두었으므로 이후 보관 정책에 따라 별도로 관리하세요. ## 복원 검증 항목 격리된 복원 환경에서는 소유자, 행 수, 업무 키, 금액 합계와 대표 JSON 값을 확인하세요. PRIMARY KEY·UNIQUE INDEX가 남아 있는지, 필요한 권한과 애플리케이션의 COMMIT·ROLLBACK 흐름이 정상인지도 점검합니다. 마운트 조회만으로 실제 쓰기 복구 검증까지 완료한 것은 아닙니다. 온라인 RESTORE DATABASE는 논리 백업과 대상 데이터베이스 조건을 따릅니다. 여러 데이터베이스를 포함한 전체 인스턴스 이미지와 혼동하지 마세요. 기존 인스턴스를 지우는 오프라인 복원이나 REPLACE는 이 실습에 포함하지 않습니다. [복원 문법](/dbms/reference/sql/syntax/backup-restore-mount-syntax/)과 [운영 절차](/dbms/operations-configuration-recovery/backup-restore-mount/)에서 권한·중단·대상 교체 조건을 확인한 뒤 별도로 실행하세요. 여러 테이블의 업무 일관성까지 검증해야 한다면 백업 명령의 성공만으로 판단하지 마세요. 쓰기 중단·업무 기준 시점과 교차 테이블 검증 기준을 함께 정하는 것이 좋습니다. --- title: "8.13 TRANSACTION INSERT ON DUPLICATE KEY UPDATE" url: https://docs.machbase.com/kr/dbms/rdb-table-usage/insert-on-duplicate-key-update/ language: kr kind: page --- # 8.13 TRANSACTION INSERT ON DUPLICATE KEY UPDATE “없으면 추가하고 있으면 수정”하는 작업은 간단해 보이지만, 무엇을 중복으로 보는지에 따라 다른 행이 바뀔 수 있습니다. 특히 카운터 증가를 재전송하면 같은 이벤트가 두 번 반영될 수 있습니다. 키와 갱신 대상, 재시도 정책을 함께 확인하세요. TRANSACTION의 `INSERT ... ON DUPLICATE KEY UPDATE`는 PRIMARY KEY 또는 일반 UNIQUE INDEX 충돌 시 기존 행을 갱신합니다. 아래 실습은 페이지 안에서 필요한 객체를 직접 준비합니다. 오류 확인 SQL은 정상 흐름과 분리하고, 마지막 정리까지 실행한 뒤 재실행하세요. ## 지원 문법 TRANSACTION 테이블에서는 `INSERT ... VALUES ...` 형태의 upsert를 지원합니다. ```text INSERT INTO table_name VALUES (...) ON DUPLICATE KEY UPDATE; INSERT INTO table_name VALUES (...) ON DUPLICATE KEY UPDATE SET column_name = expression [, ...]; INSERT INTO table_name(column_name, ...) VALUES (...) ON DUPLICATE KEY UPDATE; INSERT INTO table_name(column_name, ...) VALUES (...) ON DUPLICATE KEY UPDATE SET column_name = expression [, ...]; ``` 충돌 판정 키로 인정되는 대상은 다음과 같습니다. - TRANSACTION PRIMARY KEY - TRANSACTION UNIQUE INDEX - TRANSACTION 복합 UNIQUE INDEX ## 기본 동작 중복이 없으면 일반 INSERT와 같이 새 행을 추가합니다. ```sql CREATE TRANSACTION TABLE ch8_up_device_state ( device_id INTEGER PRIMARY KEY, status VARCHAR(16), alarm_count INTEGER, updated_at DATETIME ); INSERT INTO ch8_up_device_state VALUES (1, 'NORMAL', 0, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS')) ON DUPLICATE KEY UPDATE SET status = 'NORMAL', updated_at = TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'); ``` PRIMARY KEY가 중복되면 기존 행을 UPDATE합니다. ```sql INSERT INTO ch8_up_device_state VALUES (1, 'ALARM', 1, TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS')) ON DUPLICATE KEY UPDATE SET status = 'ALARM', alarm_count = alarm_count + 1, updated_at = TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'); ``` `SET` 절에서는 PRIMARY KEY를 변경할 수 없습니다. 오른쪽 식은 충돌한 기존 행을 기준으로 평가됩니다. 따라서 `alarm_count = alarm_count + 1`은 INSERT하려던 값이 아니라 기존 행의 alarm_count에 1을 더합니다. ```sql SELECT device_id, status, alarm_count, updated_at FROM ch8_up_device_state WHERE device_id = 1; ``` 예상 결과 형태는 다음과 같습니다. ```text DEVICE_ID STATUS ALARM_COUNT UPDATED_AT --------- ------ ----------- ----------------------------- 1 ALARM 1 2026-07-10 09:05:00 000:000:000 ``` `ON DUPLICATE KEY UPDATE` 뒤에 `SET` 절을 쓰지 않을 수 있습니다. ```sql CREATE TRANSACTION TABLE ch8_up_asset_cache ( asset_id INTEGER PRIMARY KEY, asset_name VARCHAR(80), location VARCHAR(80), keep_value INTEGER ); INSERT INTO ch8_up_asset_cache VALUES (1, 'compressor-a', 'plant-1', 100); INSERT INTO ch8_up_asset_cache(asset_id, asset_name, location) VALUES (1, 'compressor-a-renamed', 'plant-2') ON DUPLICATE KEY UPDATE; ``` `SET` 절이 없으면 INSERT 대상 컬럼 중 PRIMARY KEY가 아닌 컬럼만 기존 행에 반영됩니다. 위 예에서는 `asset_name`, `location`이 갱신되고, 컬럼 목록에 없던 `keep_value`는 기존 값을 유지합니다. ```sql SELECT asset_id, asset_name, location, keep_value FROM ch8_up_asset_cache ORDER BY asset_id; ``` 예상 결과 형태는 다음과 같습니다. ```text ASSET_ID ASSET_NAME LOCATION KEEP_VALUE -------- -------------------- -------- ---------- 1 compressor-a-renamed plant-2 100 ``` PRIMARY KEY 컬럼만 있는 테이블에서 중복 행에 `SET` 없는 upsert를 실행하면 갱신 대상 컬럼이 없으므로 행은 변경되지 않습니다. ## UNIQUE INDEX 중복 처리 PRIMARY KEY뿐 아니라 UNIQUE INDEX 충돌도 갱신 경로를 실행합니다. ```sql CREATE TRANSACTION TABLE ch8_up_account_profile ( id INTEGER PRIMARY KEY, email VARCHAR(120), display_name VARCHAR(80), login_count INTEGER ); CREATE UNIQUE INDEX ch8_up_uidx_account_profile_email ON ch8_up_account_profile(email); INSERT INTO ch8_up_account_profile VALUES (1, 'ops@example.com', 'ops-user', 1); INSERT INTO ch8_up_account_profile VALUES (2, 'ops@example.com', 'ops-renamed', 1) ON DUPLICATE KEY UPDATE SET display_name = 'ops-renamed', login_count = login_count + 1; SELECT id, email, display_name, login_count FROM ch8_up_account_profile ORDER BY id; ``` `email` UNIQUE INDEX가 중복되므로 `id = 1` 행이 UPDATE됩니다. INSERT하려던 `id = 2` 행은 새로 추가되지 않습니다. 복합 UNIQUE INDEX는 키 조합 전체가 같은 경우 중복으로 처리됩니다. ```sql CREATE TRANSACTION TABLE ch8_up_daily_device_summary ( id INTEGER PRIMARY KEY, device_id INTEGER, summary_day VARCHAR(10), event_count INTEGER, last_status VARCHAR(16) ); CREATE UNIQUE INDEX ch8_up_uidx_daily_device_summary ON ch8_up_daily_device_summary(device_id, summary_day); INSERT INTO ch8_up_daily_device_summary VALUES (1, 101, '2026-07-10', 3, 'NORMAL'); INSERT INTO ch8_up_daily_device_summary VALUES (2, 101, '2026-07-10', 1, 'ALARM') ON DUPLICATE KEY UPDATE SET event_count = event_count + 1, last_status = 'ALARM'; INSERT INTO ch8_up_daily_device_summary VALUES (3, 101, '2026-07-11', 1, 'NORMAL') ON DUPLICATE KEY UPDATE SET event_count = event_count + 1; ``` 첫 번째 upsert는 `(device_id, summary_day) = (101, '2026-07-10')` 행을 UPDATE합니다. 두 번째 upsert는 날짜가 다르므로 새 행을 INSERT합니다. UNIQUE KEY에 NULL이 포함된 행끼리는 중복으로 처리하지 않습니다. 다음 두 입력은 서로 덮어쓰지 않고 각각 새 행이 됩니다. ```sql INSERT INTO ch8_up_daily_device_summary VALUES (4, NULL, '2026-07-10', 1, 'NORMAL') ON DUPLICATE KEY UPDATE; INSERT INTO ch8_up_daily_device_summary VALUES (5, NULL, '2026-07-10', 2, 'ALARM') ON DUPLICATE KEY UPDATE; SELECT id, device_id, summary_day, event_count FROM ch8_up_daily_device_summary ORDER BY id; ``` 결과 id는 1·3·4·5이며 event_count는 각각 4·1·1·2입니다. 필수 업무 키라면 UNIQUE INDEX뿐 아니라 구성 컬럼의 NOT NULL도 필요합니다. ## 활용 예제 TAG 테이블에는 시간순 측정값이 계속 쌓이고, TRANSACTION 테이블에는 장비별 최신 상태만 유지할 수 있습니다. ```sql CREATE TRANSACTION TABLE ch8_up_latest_device_status ( device_name VARCHAR(80) PRIMARY KEY, last_value DOUBLE, last_state VARCHAR(16), event_count LONG, updated_at DATETIME ); INSERT INTO ch8_up_latest_device_status VALUES ('compressor-a', 72.5, 'NORMAL', 1, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS')) ON DUPLICATE KEY UPDATE SET last_value = 72.5, last_state = 'NORMAL', event_count = event_count + 1, updated_at = TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'); INSERT INTO ch8_up_latest_device_status VALUES ('compressor-a', 91.2, 'ALARM', 1, TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS')) ON DUPLICATE KEY UPDATE SET last_value = 91.2, last_state = 'ALARM', event_count = event_count + 1, updated_at = TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'); ``` 이 패턴은 대시보드가 최신 상태만 빠르게 조회해야 할 때 사용할 수 있습니다. 외부 시스템에서 같은 업무 키로 기준 데이터를 반복 전송할 때 UNIQUE INDEX를 기준으로 upsert할 수 있습니다. ```sql CREATE TRANSACTION TABLE ch8_up_customer_device ( id LONG PRIMARY KEY AUTO_INCREMENT, external_device_id VARCHAR(64), device_name VARCHAR(80), owner_name VARCHAR(80), enabled INTEGER ); CREATE UNIQUE INDEX ch8_up_uidx_customer_device_external_id ON ch8_up_customer_device(external_device_id); INSERT INTO ch8_up_customer_device(external_device_id, device_name, owner_name, enabled) VALUES ('ERP-DEV-10001', 'compressor-a', 'line-1', 1) ON DUPLICATE KEY UPDATE SET device_name = 'compressor-a', owner_name = 'line-1', enabled = 1; INSERT INTO ch8_up_customer_device(external_device_id, device_name, owner_name, enabled) VALUES ('ERP-DEV-10001', 'compressor-a-renamed', 'line-2', 1) ON DUPLICATE KEY UPDATE SET device_name = 'compressor-a-renamed', owner_name = 'line-2', enabled = 1; ``` 첫 INSERT는 새 행을 생성하고, 두 번째 INSERT는 `external_device_id` UNIQUE INDEX 충돌로 기존 행을 UPDATE합니다. 내부 `id`는 유지됩니다. 소스 데이터의 컬럼 값을 그대로 최신 캐시에 반영하고 싶으면 `SET` 절 없는 upsert를 사용할 수 있습니다. ```sql CREATE TRANSACTION TABLE ch8_up_tag_alias_cache ( alias_name VARCHAR(80) PRIMARY KEY, tag_name VARCHAR(80), unit VARCHAR(16), description VARCHAR(160), manually_checked INTEGER ); INSERT INTO ch8_up_tag_alias_cache VALUES ('compressor-a-temp', 'comp_a.temp', 'celsius', 'main compressor temp', 1); INSERT INTO ch8_up_tag_alias_cache(alias_name, tag_name, unit, description) VALUES ('compressor-a-temp', 'comp_a.temperature', 'celsius', 'renamed tag') ON DUPLICATE KEY UPDATE; ``` 위 구문은 `tag_name`, `unit`, `description`만 갱신합니다. 컬럼 목록에 없는 `manually_checked`는 기존 값을 유지합니다. 집계 테이블에서 키별 발생 횟수를 누적할 수 있습니다. ```sql CREATE TRANSACTION TABLE ch8_up_alarm_counter ( alarm_code VARCHAR(32) PRIMARY KEY, first_seen DATETIME, last_seen DATETIME, hit_count LONG ); INSERT INTO ch8_up_alarm_counter VALUES ( 'OVER_TEMP', TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'), TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1 ) ON DUPLICATE KEY UPDATE SET last_seen = TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'), hit_count = hit_count + 1; INSERT INTO ch8_up_alarm_counter VALUES ( 'OVER_TEMP', TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'), TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'), 1 ) ON DUPLICATE KEY UPDATE SET last_seen = TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'), hit_count = hit_count + 1; ``` `hit_count = hit_count + 1`은 기존 행 기준으로 계산되므로 누적 카운터 구현에 사용할 수 있습니다. JSON 컬럼도 update 대상 컬럼으로 사용할 수 있습니다. ```sql CREATE TRANSACTION TABLE ch8_up_device_json_state ( device_id INTEGER PRIMARY KEY, state JSON, updated_at DATETIME ); INSERT INTO ch8_up_device_json_state VALUES ( 1, '{"status":"NORMAL","score":10}', TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS') ) ON DUPLICATE KEY UPDATE SET state = '{"status":"NORMAL","score":10}', updated_at = TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS'); INSERT INTO ch8_up_device_json_state VALUES ( 1, '{"status":"ALARM","score":90}', TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS') ) ON DUPLICATE KEY UPDATE SET state = '{"status":"ALARM","score":90}', updated_at = TO_DATE('2026-07-10 09:05:00', 'YYYY-MM-DD HH24:MI:SS'); SELECT JSON_EXTRACT_STRING(state, '$.status') AS status, JSON_EXTRACT_INTEGER(state, '$.score') AS score FROM ch8_up_device_json_state WHERE device_id = 1; ``` 제한: JSON path UNIQUE INDEX는 충돌 판정 키 후보에서 제외됩니다. JSON path UNIQUE INDEX 충돌은 upsert 갱신 경로로 전환되지 않고 고유성 제약 오류로 처리됩니다. ## 트랜잭션과 권한 TRANSACTION upsert는 일반 INSERT/UPDATE와 같이 트랜잭션 안에서 COMMIT 또는 ROLLBACK됩니다. ```sql CREATE TRANSACTION TABLE ch8_up_tx_device_state ( id INTEGER PRIMARY KEY, status VARCHAR(16), count_value INTEGER ); INSERT INTO ch8_up_tx_device_state VALUES (1, 'NORMAL', 10); BEGIN; INSERT INTO ch8_up_tx_device_state VALUES (2, 'NORMAL', 1) ON DUPLICATE KEY UPDATE SET count_value = count_value + 1; INSERT INTO ch8_up_tx_device_state VALUES (1, 'ALARM', 1) ON DUPLICATE KEY UPDATE SET status = 'ALARM', count_value = count_value + 1; ROLLBACK; SELECT id, status, count_value FROM ch8_up_tx_device_state ORDER BY id; ``` 위 예에서는 삽입 경로와 갱신 경로가 모두 ROLLBACK됩니다. 일반 제약 위반으로 중복 갱신 문장이 실패해도 명시적 트랜잭션의 앞선 성공 변경은 남을 수 있습니다. 응용 프로그램은 오류를 확인한 뒤 후속 처리를 결정하거나 ROLLBACK으로 업무 변경을 취소해야 합니다. TRANSACTION upsert 문장에는 `INSERT` 권한과 `UPDATE` 권한이 모두 필요합니다. 실제 실행 결과가 삽입 경로가 되더라도 문장에 갱신 경로가 포함되므로 두 권한을 모두 부여해야 합니다. 아래는 권한 형식만 보여 주는 예입니다. 실제 소유자·테이블·기존 애플리케이션 계정에 맞춰 별도의 관리 작업으로 적용하세요. ```text GRANT INSERT ON owner.table_name TO app_user; GRANT UPDATE ON owner.table_name TO app_user; ``` `SELECT` 권한은 TRANSACTION upsert 문장 실행 자체에는 필요하지 않습니다. 단, 응용 프로그램이 결과 확인을 위해 `SELECT`를 실행한다면 별도로 `SELECT` 권한이 필요합니다. ## 지원 타입과 제약 `SET` 절에서 갱신할 수 있는 컬럼 타입은 일반 TRANSACTION `UPDATE`와 같은 공개 타입 지원 범위를 따릅니다. | 분류 | 타입 | | --- | --- | | 정수 | `SHORT`, `INT16`, `USHORT`, `UINT16`, `INT`, `INTEGER`, `INT32`, `UINTEGER`, `UINT32`, `LONG`, `INT64`, `ULONG`, `UINT64` | | 실수 | `FLOAT`, `DOUBLE` | | 고정소수 | `DECIMAL`, `NUMERIC`, `DEC`, `FIXED`, `NUMBER` | | 문자열/LOB | `VARCHAR`, `TEXT`, `CLOB`, `BINARY`, `BLOB` | | 기타 | `DATETIME`, `IPV4`, `IPV6`, `JSON` | 위 표는 TRANSACTION에서 사용하는 스칼라 타입을 정리한 것입니다. 숫자 ARRAY의 지원 범위는 [데이터 타입 사전](/dbms/reference/sql/types/)을 참고하십시오. 충돌 판정 키가 되는 키·인덱스 타입은 TRANSACTION PRIMARY KEY 및 UNIQUE INDEX의 타입 정책을 따릅니다. 이 기능은 key 타입의 지원 범위를 확장하지 않습니다. 다음 구문은 지원하지 않습니다. ```text -- 지원하지 않는 문법을 보여 주는 설명용 구역입니다. -- INSERT SELECT와 ON DUPLICATE KEY UPDATE 결합은 지원하지 않습니다. INSERT INTO ch8_up_device_state(device_id, status, alarm_count, updated_at) SELECT device_id, status, alarm_count, updated_at FROM staging_device_state ON DUPLICATE KEY UPDATE SET status = 'UPDATED'; -- MySQL VALUES(col) 함수는 지원하지 않습니다. INSERT INTO ch8_up_device_state VALUES (1, 'ALARM', 1, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS')) ON DUPLICATE KEY UPDATE SET status = VALUES(status); -- EXCLUDED alias는 지원하지 않습니다. INSERT INTO ch8_up_device_state VALUES (1, 'ALARM', 1, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS')) ON DUPLICATE KEY UPDATE SET status = EXCLUDED.status; -- conflict target syntax는 지원하지 않습니다. INSERT INTO ch8_up_device_state VALUES (1, 'ALARM', 1, TO_DATE('2026-07-10 09:00:00', 'YYYY-MM-DD HH24:MI:SS')) ON CONFLICT (device_id) DO UPDATE SET status = 'ALARM'; ``` 대상 테이블 제한은 다음과 같습니다. - 이 페이지는 TRANSACTION의 PRIMARY KEY·UNIQUE 충돌 동작만 다룹니다. 같은 SQL 형태는 LOOKUP과 VOLATILE의 PRIMARY KEY 충돌에도 지원되며 공통 문법은 [DML 사전](/dbms/reference/sql/syntax/dml-syntax/#on-duplicate-key-update)을 정본으로 사용합니다. - LOG와 TAG DATA 행에는 지원하지 않습니다. TAG METADATA의 태그 이름 충돌 처리는 [DML 사전](/dbms/reference/sql/syntax/dml-syntax/#on-duplicate-key-update)을 참고합니다. - TRANSACTION 테이블이라도 PRIMARY KEY 또는 UNIQUE INDEX가 없으면 사용할 수 없습니다. - JSON path UNIQUE INDEX는 충돌 판정 키로 사용하지 않습니다. ## 충돌과 오류 처리 여러 UNIQUE INDEX가 같은 기존 행을 가리키면 해당 행을 한 번 UPDATE합니다. 반면 서로 다른 기존 행과 충돌하면 어느 행을 갱신해야 할지 결정할 수 없어 문장이 실패합니다. 다음은 두 업무 키가 서로 다른 행과 충돌하는 표본입니다. ```sql CREATE TRANSACTION TABLE ch8_up_user_contact ( id INTEGER PRIMARY KEY, email VARCHAR(120), phone VARCHAR(40), note VARCHAR(80) ); CREATE UNIQUE INDEX ch8_up_uidx_user_contact_email ON ch8_up_user_contact(email); CREATE UNIQUE INDEX ch8_up_uidx_user_contact_phone ON ch8_up_user_contact(phone); INSERT INTO ch8_up_user_contact VALUES (1, 'a@example.com', '010-0000-0001', 'user-a'); INSERT INTO ch8_up_user_contact VALUES (2, 'b@example.com', '010-0000-0002', 'user-b'); ``` 다음 INSERT만 의도적으로 실패하는 선택 실습입니다. ```sql -- email은 id=1과 충돌하고 phone은 id=2와 충돌합니다. -- 서로 다른 row와 충돌하므로 갱신 경로를 선택하지 않고 실패합니다. INSERT INTO ch8_up_user_contact VALUES (3, 'a@example.com', '010-0000-0002', 'ambiguous') ON DUPLICATE KEY UPDATE SET note = 'updated'; ``` `SET` 결과가 다른 UNIQUE 제약 또는 `NOT NULL` constraint를 위반해도 문장은 실패하며 기존 행은 보존됩니다. ## 재전송과 최신 상태 카운터 증가 UPSERT는 자동 중복 제거가 아닙니다. 같은 이벤트를 다시 실행하면 기존 카운터가 다시 증가합니다. COMMIT 응답을 잃었다면 업무 키·이벤트 처리 기록으로 결과부터 확인하세요. 또한 “최신 상태”는 입력 순서와 이벤트 발생 순서가 같을 때만 단순 덮어쓰기로 유지됩니다. 늦게 도착한 과거 이벤트가 최신 값을 덮어쓰지 않도록 원본 시각 비교와 수집 정책을 별도로 정하세요. 여러 테이블을 함께 변경하는 작업은 [트랜잭션의 장애 시 커밋 범위](../transaction/)도 확인해야 합니다. ## 운영 권장 사항 - 업무 key가 명확하면 PRIMARY KEY 또는 UNIQUE INDEX를 먼저 정의합니다. - 카운터 누적에는 `SET count_col = count_col + 1` 형태를 사용합니다. - 소스 행의 값을 그대로 반영하려면 `SET` 없는 upsert를 사용할 수 있습니다. 이때 컬럼 목록에서 생략한 컬럼은 보존됩니다. - 여러 UNIQUE INDEX가 있는 테이블에서는 서로 다른 행과 동시에 충돌할 수 있는 입력을 사전에 정리합니다. - MySQL 호환 SQL을 이식할 때 `VALUES(col)`, `EXCLUDED`, `ON CONFLICT` 구문은 Machbase 지원 구문으로 바꿉니다. - JSON path UNIQUE INDEX를 upsert key로 사용하는 설계는 피하고, 필요한 경우 별도 일반 컬럼에 key 값을 저장한 뒤 UNIQUE INDEX를 생성합니다. ## 결과 확인과 정리 정상 실습이 끝났으면 대표 값과 건수를 다시 확인합니다. 아래 예상값은 의도적인 오류 외에 모든 정상 SQL을 한 번씩 실행한 기준입니다. ```sql SELECT id, email, display_name, login_count FROM ch8_up_account_profile ORDER BY id; SELECT device_name, last_state, event_count FROM ch8_up_latest_device_status; SELECT external_device_id, device_name, owner_name FROM ch8_up_customer_device; SELECT alias_name, tag_name, manually_checked FROM ch8_up_tag_alias_cache; SELECT alarm_code, hit_count FROM ch8_up_alarm_counter; SELECT id, status, count_value FROM ch8_up_tx_device_state ORDER BY id; SELECT id, note FROM ch8_up_user_contact ORDER BY id; ``` | 표본 | 확인할 결과 | |---|---| | account_profile | id=1 유지, display_name=ops-renamed, login_count=2 | | latest_device_status | ALARM, event_count=2 | | customer_device | 외부 키 한 행, 이름 compressor-a-renamed, 소유 line-2 | | tag_alias_cache | tag_name=comp_a.temperature, manually_checked=1 유지 | | alarm_counter | OVER_TEMP의 hit_count=2 | | tx_device_state | ROLLBACK 후 기존 id=1, NORMAL, count_value=10만 존재 | | user_contact | id=1·2의 user-a·user-b가 그대로 유지 | ```sql DROP TABLE ch8_up_user_contact; DROP TABLE ch8_up_tx_device_state; DROP TABLE ch8_up_device_json_state; DROP TABLE ch8_up_alarm_counter; DROP TABLE ch8_up_tag_alias_cache; DROP TABLE ch8_up_customer_device; DROP TABLE ch8_up_latest_device_status; DROP TABLE ch8_up_daily_device_summary; DROP TABLE ch8_up_account_profile; DROP TABLE ch8_up_asset_cache; DROP TABLE ch8_up_device_state; ``` DROP은 이 페이지의 실습 객체만 대상으로 합니다. 키가 복잡할수록 새 입력값과 기존 충돌 행을 나란히 비교해 보세요. 어느 행을 갱신하려 했는지 분명해지면 오류의 원인도 찾기 쉬워집니다. --- title: "9. LOOKUP 테이블 활용" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/ language: kr kind: section --- # 9. LOOKUP 테이블 활용 LOOKUP 테이블은 영속 저장된 기준 정보와 마스터 데이터를 서버 기동 시 메모리에 적재하고, PRIMARY KEY를 key로 사용해 빠르게 참조하는 테이블입니다. 메모리 상주 구조, JSON/SEQUENCE, JOIN과 일반 조건식 기반 DML을 다룹니다. ## 이 장의 구성 | 절 | 내용 | |----|------| | [개요와 사용 기준](./overview-use-criteria/) | LOOKUP 테이블의 용도와 적합한 사용 조건 | | [테이블 구조와 스키마](./table-structure-schema/) | 컬럼 구성, PK 구조, 스키마 설계 | | [생성, 변경, 삭제](./create-alter-drop/) | DDL: CREATE, ALTER, DROP | | [데이터 입력과 변경](./data-input-mutation/) | INSERT, Append, 리로드, 삭제 | | [조회와 분석](./query-analysis/) | SELECT, JOIN, 조건 조회 | | [인덱스와 성능](./index-performance/) | Red-Black 인덱스, 보조 인덱스, 튜닝 | | [운영과 데이터 생명주기](./operations-lifecycle/) | 백업·복구, 데이터 영속성 | | [제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 제한 사항, 오류 원인과 대응 | | [활용 패턴과 시나리오](./patterns-scenarios/) | 코드 테이블, 기준 정보, 임계값 관리 | | [PRIMARY KEY 정책](./primary-key-policy/) | 자연키 vs 대리키, PK 불변 원칙 | | [SEQUENCE 컬럼](./sequence-column/) | 자동 증가 번호 설정과 NEXTVAL 사용법 | | [JSON 컬럼과 JSON 조회](./json-column-query/) | JSON 컬럼 지원 범위, path 조건 조회, primary key 제약 | | [일반 predicate UPDATE/DELETE](./predicate-update-delete/) | non-PK, 범위, 문자열, 날짜, JSON path 조건 기반 변경 | --- title: "9.1 개요와 사용 기준" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/overview-use-criteria/ language: kr kind: page --- # 9.1 개요와 사용 기준 LOOKUP 테이블은 기준 코드, 장비 마스터, 임계값, 설정값처럼 비교적 작고 자주 참조되는 데이터를 저장하는 테이블입니다. 데이터는 영속 저장되지만 SQL 조회에 사용하는 전체 행은 메모리에 상주합니다. 따라서 반복적인 키 기반 조회와 갱신에 적합합니다. ## LOOKUP 테이블의 특성 LOOKUP 테이블은 `CREATE LOOKUP TABLE` 문으로 생성하며, `PRIMARY KEY`가 필수입니다. ```sql CREATE LOOKUP TABLE ch9_overview ( sensor_id VARCHAR(64) PRIMARY KEY, site VARCHAR(32), unit VARCHAR(16), status VARCHAR(16) ); ``` LOOKUP 테이블의 주요 특성은 다음과 같습니다. | 항목 | 내용 | |------|------| | 주요 용도 | 코드 테이블, 장비 마스터, 임계값, 참조 데이터 | | 필수 조건 | `PRIMARY KEY` 필요 | | 저장 방식 | 영속 저장 후 서버 기동 시 전체 행을 메모리에 적재 | | 조회 패턴 | PK 조회에 최적화, 일반 조건 조회와 다른 테이블 JOIN 지원 | | 변경 패턴 | INSERT, UPDATE, DELETE | | 부가 기능 | SEQUENCE 컬럼, Append 중복 키 정책 | ## 사용 기준 다음 조건에 해당하면 LOOKUP 테이블을 사용합니다. - 데이터 건수가 상대적으로 작고 전체 데이터가 자주 참조됩니다. - 전체 행과 필요한 보조 인덱스를 서버 메모리에 유지할 수 있습니다. - 코드, 이름, 위치, 단위, 상태 같은 기준 정보를 관리합니다. - TAG 또는 LOG 테이블의 원본 데이터에 설명 정보를 JOIN해야 합니다. - 임계값이나 설정값처럼 운영 중 변경될 수 있는 참조값을 저장합니다. - `PRIMARY KEY`로 행을 명확하게 식별할 수 있습니다. TAG 또는 LOG 데이터에 위치와 단위 같은 설명을 붙이는 JOIN 예제는 [조회와 분석](/dbms/lookup-table-usage/query-analysis/)에서 다룹니다. ## 다른 테이블을 검토할 경우 다음 요구사항에는 다른 테이블 타입을 검토합니다. | 요구사항 | 권장 테이블 | |----------|-------------| | 대량 시계열 계측 데이터 저장 | TAG | | append 중심 원본 이벤트 저장 | LOG | | 메모리에 전체 행을 적재하기 어려운 대규모 관계형 데이터 | TRANSACTION | | 트랜잭션과 관계형 업무 처리가 필요한 데이터 | TRANSACTION | | 서버 메모리에서만 유지할 최신 상태 캐시 | VOLATILE | LOOKUP 테이블은 참조 데이터에 적합하지만, 데이터가 영속 저장된다는 이유로 디스크 중심의 대용량 테이블처럼 사용해서는 안 됩니다. 원본 데이터는 LOG 또는 TAG 테이블에 저장하고, LOOKUP 테이블에는 메모리에 상주시켜 반복 조회할 기준 정보를 저장합니다. 관계형 데이터가 메모리 용량보다 커지거나 복합적인 업무 처리가 필요하면 TRANSACTION 테이블을 사용합니다. 이 절의 실습 테이블은 다음과 같이 정리합니다. ```sql DROP TABLE ch9_overview; ``` ## 설계 순서 LOOKUP 테이블을 설계할 때는 다음 순서로 결정합니다. 1. 행을 식별할 `PRIMARY KEY`를 정합니다. 2. 자연키를 사용할지, SEQUENCE 또는 AUTO_INCREMENT 기반 대리키를 사용할지 결정합니다. 3. 자주 조회하거나 JOIN하는 컬럼에 인덱스를 추가합니다. 4. 예상 행 크기와 행 수, 보조 인덱스를 포함한 메모리 사용량을 검증합니다. 5. 운영 중 갱신되는 컬럼과 불변 컬럼을 구분합니다. 6. 대량 변경 전에는 대상 범위를 확인하는 쿼리를 준비합니다. 스키마와 키 설계는 [테이블 구조와 스키마](/dbms/lookup-table-usage/table-structure-schema/)와 [PRIMARY KEY 정책](/dbms/lookup-table-usage/primary-key-policy/)에서 다룹니다. --- title: "9.2 테이블 구조와 스키마" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/table-structure-schema/ language: kr kind: page --- # 9.2 테이블 구조와 스키마 LOOKUP 테이블의 구조와 스키마 설계를 다룹니다. ## LOOKUP 테이블 설계 LOOKUP 테이블은 코드 테이블과 기준 정보를 저장하는 타입입니다. PRIMARY KEY로 각 행을 식별하고 Primary key 또는 일반 조건식의 UPDATE/DELETE를 지원하며 디스크에 영속 저장됩니다. ### 영속 저장과 메모리 조회 구조 LOOKUP 테이블은 영속성과 메모리 조회 성능을 함께 제공하는 2계층 구조입니다. 1. 변경된 행은 재시작 후에도 유지할 수 있도록 영속 저장소에 기록됩니다. 2. 서버가 기동되면 영속 저장소의 LOOKUP 행을 모두 읽어 각 컬럼 값을 포함한 메모리 행으로 복원합니다. 3. 각 메모리 행은 필수 `PRIMARY KEY`의 Red-Black 인덱스에 등록됩니다. 4. SQL 조회는 복원된 메모리 행과 인덱스를 사용합니다. 개념적으로 한 행은 다음과 같은 key-value 항목으로 볼 수 있습니다. ``` PRIMARY KEY 나머지 컬럼 값 sensor_id = 'TEMP-01' ───────► { site, unit, status, ... } key value ``` 이는 LOOKUP이 SQL 테이블 인터페이스와 일반 조건 조회, JOIN, 보조 인덱스를 지원하면서도 `PRIMARY KEY` 조회에 특히 적합한 이유입니다. 영속 저장소가 있다고 해서 조회 시 필요한 행만 디스크에서 가져오는 구조는 아닙니다. 전체 행과 생성한 Red-Black 보조 인덱스가 메모리를 사용하므로 스키마를 설계할 때 행 수뿐 아니라 가변 길이 컬럼, JSON 값, 보조 인덱스 크기도 함께 고려합니다. - **[활용 사례](/dbms/lookup-table-usage/patterns-scenarios/#use-cases-lookup)** - **[PRIMARY KEY 설계](/dbms/lookup-table-usage/primary-key-policy/#design-primary-key)** - **[컬럼 및 시퀀스 설계](/dbms/lookup-table-usage/sequence-column/#design-column-lookup-sequence)** - **[JSON 컬럼과 조회](/dbms/lookup-table-usage/json-column-query/#condition-query-lookup-json)** - **[참조 설계 패턴](/dbms/lookup-table-usage/patterns-scenarios/#patterns-reference-design)** - **[인덱스 전략](/dbms/lookup-table-usage/index-performance/#index-strategy-lookup)** - **[PRIMARY KEY 정책](/dbms/lookup-table-usage/primary-key-policy/#policy-lookup-primary-key)** - **[일반 조건식 기반 UPDATE·DELETE](/dbms/lookup-table-usage/predicate-update-delete/)** - **[백업·복구 지원 범위](/dbms/lookup-table-usage/operations-lifecycle/#recovery-support-scope-backup-lookup)** - **[제약 및 주의사항](/dbms/lookup-table-usage/constraints-errors-troubleshooting/#limitations-lookup)** --- title: "9.3 생성, 변경, 삭제" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/create-alter-drop/ language: kr kind: page --- # 9.3 생성, 변경, 삭제 LOOKUP 테이블의 생성, 변경, 삭제 방법을 다룹니다. ## Lookup 테이블 생성 및 관리 참조 테이블을 생성하는 방법은 다음과 같습니다. LOOKUP 테이블은 반드시 `PRIMARY KEY`를 지정해야 합니다. ## LOOKUP 테이블 생성 ```sql CREATE LOOKUP TABLE ch9_ddl (id INTEGER PRIMARY KEY, name VARCHAR(20)); ``` 운영에서 사용하는 기준 정보는 의미 있는 컬럼명을 사용해 정의합니다. ```sql CREATE LOOKUP TABLE ch9_ddl_equip ( equip_id LONG PRIMARY KEY, equip_name VARCHAR(128), location VARCHAR(64), status VARCHAR(16), updated_at DATETIME ); ``` `PRIMARY KEY`는 행을 고유하게 식별하고 UPDATE, DELETE, JOIN의 기준이 됩니다. 여러 컬럼 조합이 비즈니스 키라면 조합 문자열을 별도 키 컬럼으로 만들거나 SEQUENCE 기반 대리키를 사용합니다. ```sql CREATE LOOKUP TABLE ch9_ddl_price ( price_key VARCHAR(64) PRIMARY KEY, product_id VARCHAR(32), region VARCHAR(16), price DOUBLE ); ``` ## AUTO_INCREMENT PRIMARY KEY 서버가 숫자 PRIMARY KEY를 생성해야 하면 단일 `LONG` 또는 `INT64` 컬럼에 `AUTO_INCREMENT`를 지정합니다. ```sql CREATE LOOKUP TABLE ch9_ddl_registry ( equip_id LONG PRIMARY KEY AUTO_INCREMENT, equip_name VARCHAR(128), location VARCHAR(64) ); INSERT INTO ch9_ddl_registry(equip_name, location) VALUES ('compressor-01', 'SEOUL-A'); ``` PK 컬럼을 생략하거나 NULL로 지정하면 서버가 값을 생성합니다. 단일 `INSERT ... VALUES`에서 `0..INT64_MAX` 범위의 PK 값을 직접 지정할 수도 있습니다. 지정값이 현재 다음 자동값 이상이면 다음 자동값은 `지정값 + 1`로 진행하며, 작은 값을 지정해도 되감기지 않습니다. 데이터와 다음 자동값은 정상 재시작 후 유지됩니다. AUTO_INCREMENT를 사용하는 LOOKUP 테이블에서는 `INSERT ... SELECT`와 `ON DUPLICATE KEY UPDATE`를 사용할 수 없습니다. SDK에서 INSERT 결과 ID를 받는 방법은 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. ## SEQUENCE 컬럼 사용 자동 증가 번호가 필요하면 `LONG PROPERTY(SEQUENCE=1)` 컬럼을 사용합니다. 입력 시에는 `NEXTVAL()` 함수로 다음 값을 가져옵니다. ```sql CREATE LOOKUP TABLE ch9_ddl_alarm ( seq LONG PROPERTY(SEQUENCE=1) PRIMARY KEY, sensor_id VARCHAR(64), alarm_type VARCHAR(32), occurred_at DATETIME, message VARCHAR(256) ); INSERT INTO ch9_ddl_alarm VALUES (NEXTVAL(seq), 'TEMP-01', 'HIGH', NOW, '온도 초과'); ``` SEQUENCE 컬럼의 세부 정책은 [SEQUENCE 컬럼](/dbms/lookup-table-usage/sequence-column/)에서 다룹니다. `PROPERTY(SEQUENCE)`와 `AUTO_INCREMENT`는 별개의 기능이며 같은 컬럼에 함께 지정하지 않습니다. ## 컬럼 추가와 삭제 Standard Edition에서는 LOOKUP 테이블에 고정 길이 숫자 ARRAY 컬럼을 추가하고 삭제할 수 있습니다. ```sql ALTER TABLE ch9_ddl_equip ADD COLUMN (limits DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]); ALTER TABLE ch9_ddl_equip DROP COLUMN (limits); ``` DEFAULT가 없으면 기존 row의 새 ARRAY 컬럼은 whole NULL입니다. DEFAULT를 지정하면 기존 row에도 해당 값을 적용합니다. ARRAY DEFAULT의 요소 수는 선언 cardinality와 정확히 같아야 합니다. ARRAY 컬럼은 PRIMARY KEY나 index key로 사용할 수 없습니다. 지원 타입과 제약은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ## 인덱스 추가 자주 조회하거나 JOIN 조건에 사용하는 컬럼에는 인덱스를 추가합니다. ```sql CREATE INDEX ch9_ddl_loc_idx ON ch9_ddl_equip(location); CREATE INDEX ch9_ddl_status_idx ON ch9_ddl_equip(status); ``` PRIMARY KEY 컬럼에는 기본 인덱스가 생성되므로, 같은 컬럼에 별도 인덱스를 중복 생성하지 않습니다. 인덱스가 많으면 입력과 갱신 비용이 증가하므로 조회 조건이 명확한 컬럼에만 추가합니다. ## 데이터 삭제와 테이블 삭제 행을 삭제하려면 `DELETE` 문을 사용합니다. 단건 삭제는 PK 조건을 사용하는 것이 가장 명확합니다. ```sql DELETE FROM ch9_ddl_equip WHERE equip_id = 1001; ``` 일괄 삭제는 일반 조건식을 사용할 수 있습니다. 운영 데이터에서는 먼저 같은 조건으로 대상 건수를 확인합니다. ```sql SELECT COUNT(*) FROM ch9_ddl_equip WHERE status = 'RETIRED'; DELETE FROM ch9_ddl_equip WHERE status = 'RETIRED'; ``` 테이블 자체를 삭제하려면 `DROP TABLE`을 사용합니다. ```sql DROP TABLE ch9_ddl_alarm; DROP TABLE ch9_ddl_registry; DROP TABLE ch9_ddl_price; DROP TABLE ch9_ddl_equip; DROP TABLE ch9_ddl; ``` `DROP TABLE`은 테이블 정의와 데이터를 모두 삭제합니다. 필요한 경우 삭제 전에 백업 또는 내보내기 절차를 수행합니다. ## 주의사항 - LOOKUP 테이블은 `PRIMARY KEY`가 필수입니다. - `PRIMARY KEY` 컬럼은 하나만 지정합니다. - `PRIMARY KEY` 값을 변경해야 하면 기존 행을 삭제한 뒤 새 키로 삽입합니다. - 기준 데이터가 커지고 조회·갱신 패턴이 복잡해지면 TRANSACTION 테이블을 검토합니다. --- title: "9.4 데이터 입력과 변경" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/data-input-mutation/ language: kr kind: page --- # 9.4 데이터 입력과 변경 LOOKUP 테이블의 데이터 입력, 갱신, 삭제 방법을 하나의 실행 가능한 예제로 설명합니다. ## 예제 테이블 준비 다음 예제는 마지막의 정리 구문까지 순서대로 실행할 수 있습니다. ```sql CREATE LOOKUP TABLE ch9_mutation ( code VARCHAR(32) PRIMARY KEY, label VARCHAR(64), status VARCHAR(16), updated_at DATETIME ); INSERT INTO ch9_mutation VALUES ('TEMP', 'Temperature', 'ACTIVE', NOW); INSERT INTO ch9_mutation VALUES ('PRESS', 'Pressure', 'ACTIVE', NOW); ``` SEQUENCE 키가 필요하면 [SEQUENCE](/dbms/lookup-table-usage/sequence-column/)를 참고합니다. ## UPDATE 단건 변경은 PRIMARY KEY 조건으로 처리합니다. ```sql UPDATE ch9_mutation SET status = 'INACTIVE', updated_at = NOW WHERE code = 'TEMP'; ``` 여러 행을 변경할 때는 먼저 같은 `WHERE` 절로 대상을 조회합니다. 단건 변경은 대상이 명확하고 인덱스를 사용할 수 있는 `PRIMARY KEY` 조건을 권장합니다. PRIMARY KEY 컬럼 자체는 UPDATE할 수 없습니다. 키를 바꿔야 하면 기존 행을 삭제하고 새 키로 다시 입력합니다. 두 문장을 하나의 TRANSACTION 트랜잭션으로 묶을 수 없으므로 중간 실패와 참조 데이터의 변경 절차도 함께 설계합니다. ## 중복 키 처리 SQL INSERT에서 키 중복 시 갱신이 필요하면 `ON DUPLICATE KEY UPDATE`를 사용합니다. ```sql INSERT INTO ch9_mutation VALUES ('TEMP', 'Temperature sensor', 'ACTIVE', NOW) ON DUPLICATE KEY UPDATE SET label = 'Temperature sensor', status = 'ACTIVE', updated_at = NOW; ``` Append로 데이터를 삽입할 때 primary key가 중복되면 `LOOKUP_APPEND_UPDATE_ON_DUPKEY` 설정에 따라 해당 행을 업데이트할 수 있습니다. 이 설정은 LOOKUP 테이블 append 경로의 중복 키 처리 정책이므로, 운영 환경에서는 현재 설정값을 확인한 뒤 사용합니다. ```sql SELECT name, value FROM v$property WHERE name = 'LOOKUP_APPEND_UPDATE_ON_DUPKEY'; ``` ## TABLE_REFRESH 영속 저장된 LOOKUP 내용을 실행 중인 메모리 테이블에 다시 반영해야 할 때는 `TABLE_REFRESH`를 실행합니다. ```sql EXEC TABLE_REFRESH(ch9_mutation); ``` 일반 SQL DML 직후마다 실행하는 명령은 아닙니다. 대상은 현재 database의 LOOKUP table이며, READ ONLY database에서는 실행할 수 없습니다. 이름 범위·권한·오류 계약은 [EXEC procedure 정본](/dbms/reference/sql/syntax/execute-procedure-syntax/#table-refresh)을 참고하고, 클러스터 운영 절차는 [운영과 수명주기](/dbms/lookup-table-usage/operations-lifecycle/)를 참고합니다. ## Lookup 데이터 삭제 단건 삭제는 PRIMARY KEY 조건을 사용합니다. ```sql DELETE FROM ch9_mutation WHERE code = 'PRESS'; ``` 일반 조건식을 사용하면 조건에 맞는 모든 행을 삭제합니다. 모든 행을 삭제하려면 WHERE 절을 생략합니다. ```sql DELETE FROM ch9_mutation; DROP TABLE ch9_mutation; ``` ## 변경 작업 체크리스트 - 단건 변경은 PRIMARY KEY 조건을 사용합니다. - 일반 조건식으로 일괄 변경하기 전에 같은 조건으로 대상 범위를 조회합니다. - 모든 행을 삭제하기 전에는 백업 또는 재입력 원본을 확인합니다. - PRIMARY KEY 값 변경은 DELETE 후 INSERT로 처리합니다. - Append 중복 키 처리는 `LOOKUP_APPEND_UPDATE_ON_DUPKEY` 설정을 확인합니다. - 영속 LOOKUP을 runtime memory에 다시 반영해야 할 때만 `EXEC TABLE_REFRESH(table_name)`을 사용합니다. --- title: "9.5 조회와 분석" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/query-analysis/ language: kr kind: page --- # 9.5 조회와 분석 LOOKUP 테이블의 키 조회, 일반 조건 조회, TAG 데이터와의 JOIN을 실행 가능한 예제로 설명합니다. ## 예제 데이터 준비 다음 LOOKUP 테이블과 TAG 테이블을 준비합니다. ```sql CREATE LOOKUP TABLE ch9_query_master ( sensor_id VARCHAR(32) PRIMARY KEY, site VARCHAR(32), unit VARCHAR(16), status VARCHAR(16) ); INSERT INTO ch9_query_master VALUES ('TEMP-01', 'SEOUL', 'C', 'ACTIVE'); INSERT INTO ch9_query_master VALUES ('TEMP-02', 'BUSAN', 'C', 'INACTIVE'); CREATE TAG TABLE ch9_query_data ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); INSERT INTO ch9_query_data VALUES ('TEMP-01', TO_DATE('2026-01-01 00:00:00'), 23.5); INSERT INTO ch9_query_data VALUES ('TEMP-02', TO_DATE('2026-01-01 00:00:00'), 19.0); ``` ## PRIMARY KEY 조회 단건 조회는 `PRIMARY KEY` 조건을 사용합니다. ```sql SELECT sensor_id, site, unit, status FROM ch9_query_master WHERE sensor_id = 'TEMP-01'; ``` ## 일반 조건 조회 LOOKUP 테이블은 일반 컬럼 조건으로도 조회할 수 있습니다. 자주 사용하는 조건 컬럼에는 인덱스를 추가합니다. ```sql SELECT sensor_id, site, unit FROM ch9_query_master WHERE site = 'SEOUL' AND status = 'ACTIVE'; ``` 자주 사용하는 일반 조건 컬럼에는 인덱스를 추가할 수 있습니다. 인덱스 설계와 생성 방법은 [인덱스](/dbms/lookup-table-usage/index-performance/)를 참고합니다. ## TAG·LOG 테이블과 JOIN LOOKUP 테이블은 TAG 또는 LOG 테이블의 원본 데이터에 설명 정보를 붙이는 용도로 자주 사용합니다. ```sql SELECT d.name, m.site, m.unit, d.time, d.value FROM ch9_query_data d JOIN ch9_query_master m ON d.name = m.sensor_id WHERE m.status = 'ACTIVE'; ``` ## 분석 패턴 LOOKUP 테이블은 원본 데이터를 저장하기보다 분석 기준을 제공합니다. 다음과 같은 패턴에 적합합니다. | 패턴 | 설명 | |------|------| | 코드 변환 | 상태 코드, 알람 코드, 장비 타입을 라벨로 변환 | | 기준값 비교 | 센서값을 임계값 테이블과 JOIN해 초과 여부 판단 | | 그룹 기준 제공 | 위치, 부서, 라인 등 집계 기준 제공 | | 최신 설정 반영 | 운영 중 변경되는 설정값을 조회 시점에 반영 | 같은 방식으로 임계값 LOOKUP 테이블과 TAG 원본을 JOIN해 기준값 초과 여부를 판정할 수 있습니다. 시간 범위가 큰 TAG 또는 LOG 테이블은 JOIN 전에 시간 조건으로 조회 범위를 제한합니다. ```sql DROP TABLE ch9_query_data CASCADE; DROP TABLE ch9_query_master; ``` ## 조회 성능 기준 - 단건 조회와 JOIN 기준 컬럼은 `PRIMARY KEY` 또는 인덱스 컬럼을 사용합니다. - 조건에 자주 쓰는 일반 컬럼은 별도 인덱스를 검토합니다. - 대량 원본 데이터와 JOIN할 때는 원본 테이블의 시간 범위를 먼저 좁힙니다. - LOOKUP 테이블에는 기준 정보를 저장하고, 장기 원본 데이터는 TAG 또는 LOG 테이블에 저장합니다. --- title: "9.6 인덱스와 성능" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/index-performance/ language: kr kind: page --- # 9.6 인덱스와 성능 LOOKUP 테이블의 인덱스 구조와 성능 튜닝을 다룹니다. ## LOOKUP 인덱스 튜닝 LOOKUP의 PRIMARY KEY에는 Red-Black 트리 인덱스가 자동 생성됩니다. 전체 행과 인덱스는 SQL 조회 중 메모리에 상주합니다. 필요하면 non-PK 컬럼에도 Red-Black 보조 인덱스를 추가할 수 있습니다. ### LOOKUP 테이블 인덱스 #### PK 자동 Red-Black 트리 인덱스 LOOKUP 테이블을 생성하면 PRIMARY KEY 컬럼에 Red-Black 트리 인덱스가 자동으로 생성됩니다. 인덱스 항목은 해당 key의 전체 컬럼 값이 들어 있는 메모리 행을 가리킵니다. 따라서 영속 테이블이지만 조회 실행 경로는 메모리 기반이며, 소규모 기준 정보를 key로 반복 조회하는 패턴에 최적화되어 있습니다. ```sql CREATE LOOKUP TABLE ch9_index_device ( device_id VARCHAR(64) PRIMARY KEY, -- Red-Black 트리 자동 생성 device_name VARCHAR(128), location VARCHAR(256), category VARCHAR(32) ); INSERT INTO ch9_index_device VALUES ('DEV-01', 'Boiler', 'Seoul', 'temperature'); INSERT INTO ch9_index_device VALUES ('DEV-02', 'Pump', 'Busan', 'pressure'); ``` ```sql -- PK 조회: Red-Black 트리 인덱스 사용 SELECT device_id, device_name, location FROM ch9_index_device WHERE device_id = 'DEV-01'; ``` 한 행이 조회됩니다. 이벤트 로그와 결합할 때도 LOOKUP 쪽은 PK로 찾습니다. ```sql CREATE LOG TABLE ch9_index_event (device_id VARCHAR(64), level SHORT); INSERT INTO ch9_index_event VALUES ('DEV-01', 3); INSERT INTO ch9_index_event VALUES ('DEV-02', 1); EXEC TABLE_FLUSH(ch9_index_event); SELECT e.device_id, e.level, d.location FROM ch9_index_event e, ch9_index_device d WHERE e.device_id = d.device_id ORDER BY e.device_id; ``` 두 행이 조회됩니다. LOG 쪽에 시간 범위를 함께 걸면 읽을 원본을 더 줄일 수 있습니다. #### non-PK 컬럼 보조 인덱스 non-PK 컬럼에도 Red-Black 보조 인덱스를 생성할 수 있습니다. 보조 인덱스가 없는 컬럼으로 필터링하면 테이블 전체를 순차 스캔합니다. ```sql -- 자주 필터링하는 non-PK 컬럼에 보조 인덱스 생성 CREATE INDEX ch9_index_device_location ON ch9_index_device(location); SELECT device_id FROM ch9_index_device WHERE location = 'Seoul'; -- 인덱스가 없는 컬럼은 전체 스캔 가능 SELECT device_id FROM ch9_index_device WHERE category = 'temperature'; ``` 두 조회 모두 DEV-01을 반환합니다. 결과가 같아도 접근 경로가 다르므로, 실제 비교는 운영 규모의 데이터에서 실행 계획과 함께 확인합니다. 보조 인덱스는 조회를 빠르게 하지만 갱신 비용과 메모리 사용량이 늘어납니다. 자주 사용하는 조건 컬럼에만 생성합니다. #### LOOKUP 테이블 사용 가이드라인 Primary key와 Red-Black 보조 인덱스의 조회 비용은 트리 크기에 따라 증가하고, 인덱스가 없는 조건은 메모리에 적재된 전체 행을 스캔합니다. 행 수만으로 사용 한계를 정하지 말고 전체 행의 실제 크기, 가변 길이 값, 인덱스 수, 조회와 갱신 비율을 같은 워크로드로 측정합니다. 서버 기동 시 영속 데이터를 모두 읽어 메모리 행과 인덱스를 구성하므로 기동 시간도 함께 확인합니다. | 사용 패턴 | 적합성 | |----------|-------| | PK로 장치 정보 조회 | 적합 (PK 사용) | | non-PK 컬럼으로 목록 검색 | 보조 인덱스 생성 시 적합 | | 소수의 기준 코드 테이블 | 적합 | | 전체 행이 서버 메모리에 들어가지 않는 대규모 기준 정보 | TRANSACTION 테이블 검토 | | 관계형 트랜잭션이 필요한 기준 정보 | TRANSACTION 테이블 검토 | ### VOLATILE과의 구분 VOLATILE은 재시작 때 데이터가 사라지는 별도 테이블 타입입니다. VOLATILE 인덱스 설계는 [VOLATILE 인덱스와 성능](/dbms/volatile-table-usage/index-performance/)을 참고하십시오. ### 대용량 기준 정보가 필요한 경우 LOOKUP 테이블의 크기와 보조 인덱스 갱신 비용 때문에 다음 시나리오에서는 대안을 고려합니다. **시나리오**: 장치 기준 정보를 여러 컬럼으로 필터링하고 변경 이력도 함께 보존해야 하는 경우 ```sql -- 대안: LOG 테이블 + LSM/BITMAP 인덱스 CREATE LOG TABLE ch9_index_device_hist ( device_id VARCHAR(64), device_name VARCHAR(128), location VARCHAR(256), category VARCHAR(32), updated_at DATETIME ); -- non-PK 컬럼에 인덱스 생성 가능 CREATE INDEX ch9_index_hist_location ON ch9_index_device_hist (location); CREATE INDEX ch9_index_hist_category ON ch9_index_device_hist (category) INDEX_TYPE BITMAP; ``` 단, LOG 테이블은 Append 전용이므로 기준 정보 갱신 패턴에 맞게 설계해야 합니다. 실습에 사용한 객체는 다음과 같이 정리합니다. ```sql DROP TABLE ch9_index_device_hist; DROP INDEX ch9_index_device_location; DROP TABLE ch9_index_event; DROP TABLE ch9_index_device; ``` ### 핵심 정리 - PRIMARY KEY 인덱스는 자동 생성됩니다. - 반복하는 non-PK 조건에만 보조 인덱스를 만듭니다. - 행·가변 길이 값·인덱스의 메모리, 기동 시간과 갱신 부하를 함께 측정합니다. --- title: "9.7 운영과 데이터 생명주기" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/operations-lifecycle/ language: kr kind: page --- # 9.7 운영과 데이터 생명주기 LOOKUP 테이블의 백업·복구와 데이터 영속성을 다룹니다. LOOKUP 테이블은 디스크에 영속 저장되는 참조 데이터 테이블입니다. 운영 중 값이 바뀔 수 있으므로 변경 절차, 백업, 복구, 조회 반영 시점을 함께 관리합니다. 영속 저장과 조회 시 데이터 위치는 구분해야 합니다. 서버가 기동되면 영속 저장본의 모든 LOOKUP 행을 메모리 테이블에 복원하고 `PRIMARY KEY`와 보조 인덱스를 구성합니다. 서비스 중 SQL 조회는 이 메모리 구조를 사용합니다. ## 데이터 생명주기 LOOKUP 데이터는 생성, 입력, 갱신, 참조, 백업, 복구의 흐름으로 관리합니다. ``` 테이블 생성 └── 기준 데이터 입력 └── TAG/LOG/TRANSACTION 조회에서 JOIN 또는 참조 └── 운영 중 UPDATE/DELETE └── 백업 / 복구 / 마운트 ``` 기준 데이터는 원본 이벤트보다 작지만, 조회 결과 해석에 직접 영향을 줍니다. 따라서 변경 전후의 값과 적용 시점을 기록하는 운영 절차를 둡니다. ## 기준 데이터 변경 절차 운영 중 LOOKUP 데이터를 변경할 때는 다음 순서로 진행합니다. 1. 변경 대상 행을 조회합니다. 2. 영향 범위를 확인합니다. 3. UPDATE 또는 DELETE를 실행합니다. 4. 필요한 경우 `EXEC TABLE_REFRESH(table_name)`을 실행합니다. 5. 대표 조회 쿼리로 반영 여부를 확인합니다. `TABLE_REFRESH`는 일반 SQL DML 직후마다 실행하는 명령이 아닙니다. 영속 LOOKUP 내용을 runtime memory table에 다시 반영해야 할 때 사용하며, 이름 범위·권한·오류 계약은 [EXEC procedure 정본](/dbms/reference/sql/syntax/execute-procedure-syntax/#table-refresh)을 참고합니다. 다음은 이 절차를 그대로 따라가는 실습입니다. ```sql CREATE LOOKUP TABLE ch9_ops_sensor ( sensor_id VARCHAR(32) PRIMARY KEY, site VARCHAR(16), status VARCHAR(16), updated_at DATETIME ); INSERT INTO ch9_ops_sensor VALUES ('TEMP-01', 'SEOUL', 'READY', NOW); INSERT INTO ch9_ops_sensor VALUES ('TEMP-02', 'SEOUL', 'READY', NOW); INSERT INTO ch9_ops_sensor VALUES ('TEMP-03', 'BUSAN', 'READY', NOW); -- 변경 대상을 조회합니다. SELECT sensor_id, site, status FROM ch9_ops_sensor WHERE sensor_id = 'TEMP-01'; -- 변경합니다. UPDATE ch9_ops_sensor SET status = 'INACTIVE', updated_at = NOW WHERE sensor_id = 'TEMP-01'; -- 필요하면 memory table에 다시 반영합니다. EXEC TABLE_REFRESH(ch9_ops_sensor); -- 대표 조회로 확인합니다. SELECT sensor_id, status FROM ch9_ops_sensor ORDER BY sensor_id; ``` TEMP-01만 `INACTIVE`가 되고 나머지 두 행은 `READY`로 남습니다. 일괄 변경은 반드시 대상 건수를 먼저 확인합니다. ```sql SELECT COUNT(*) FROM ch9_ops_sensor WHERE site = 'SEOUL' AND status = 'READY'; ``` TEMP-01이 이미 바뀌었으므로 COUNT는 1입니다. 변경 전에 세지 않으면 대상이 달라집니다. ```sql DROP TABLE ch9_ops_sensor; ``` ## 백업·복구 지원 범위 LOOKUP은 disk에 영속 저장되고 database backup에 포함되며, restore 뒤 row를 memory table과 index로 다시 구성합니다. 공통 BACKUP·RESTORE·MOUNT 명령과 Edition 범위는 [백업·복원·마운트](/dbms/operations-configuration-recovery/backup-restore-mount/)를 정본으로 사용합니다. 복구 뒤 대표 key와 JOIN 결과를 검증합니다. ## 운영 점검 항목 - 기준 데이터 변경 이력을 별도 로그나 운영 절차로 남깁니다. - 대량 UPDATE/DELETE 전에는 대상 건수를 확인합니다. - 자주 JOIN하는 컬럼에는 인덱스를 검토합니다. - 서버 기동 시간과 LOOKUP 행·인덱스의 메모리 사용량을 실제 데이터 규모로 점검합니다. - Append 중복 키 처리를 사용하는 경우 `LOOKUP_APPEND_UPDATE_ON_DUPKEY` 설정을 확인합니다. - 백업 복구 후 대표 JOIN 쿼리로 참조 결과를 확인합니다. --- title: "9.8 제약, 오류, 문제 해결" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/constraints-errors-troubleshooting/ language: kr kind: page --- # 9.8 제약, 오류, 문제 해결 LOOKUP 테이블의 제약 사항, 발생 가능한 오류, 문제 해결 방법을 다룹니다. ## 제약 요약 | 항목 | 제약 | 대표 오류 | |---|---|---| | PRIMARY KEY | 필수이며 하나만 지정 | `ERR-02322`, `ERR-02171` | | PRIMARY KEY 컬럼 | `SET` 대상으로 지정 불가 | `ERR-02176` | | 컬럼 타입 | `TEXT`·`CLOB`·`BLOB`·`BINARY` 사용 불가 | `ERR-02173` | | JSON 컬럼 | 일반 컬럼으로는 가능, PRIMARY KEY로는 불가 | — | | 메모리 | VOLATILE과 하나의 한도를 공유 | `ERR-01344` | | UPDATE | `WHERE` 필수. 생략 시 실행되지 않음 | — | ## PRIMARY KEY 오류 LOOKUP은 PRIMARY KEY 없이 만들 수 없고, 두 개 이상 지정할 수도 없습니다. 다음 두 문장은 각각 실패합니다. ```sql -- 실패: PRIMARY KEY가 없습니다. (ERR-02322) CREATE LOOKUP TABLE ch9_err_nopk (code VARCHAR(16), label VARCHAR(64)); -- 실패: PRIMARY KEY가 두 개입니다. (ERR-02171) CREATE LOOKUP TABLE ch9_err_twopk ( code VARCHAR(16) PRIMARY KEY, name VARCHAR(32) PRIMARY KEY ); ``` 두 문장 모두 테이블을 만들지 않으므로 정리할 객체가 없습니다. 복합 키가 필요하면 구분자로 조합한 단일 키 컬럼을 두고, [PRIMARY KEY 정책](../primary-key-policy/)의 설계 기준을 따릅니다. PRIMARY KEY 컬럼은 값을 바꿀 수 없습니다. ```sql CREATE LOOKUP TABLE ch9_err_pk (code VARCHAR(16) PRIMARY KEY, label VARCHAR(64)); INSERT INTO ch9_err_pk VALUES ('KR', '대한민국'); -- 실패: PRIMARY KEY 컬럼은 SET 대상이 아닙니다. (ERR-02176) UPDATE ch9_err_pk SET code = 'KO' WHERE code = 'KR'; ``` 키를 바꿔야 하면 기존 행을 삭제하고 새 키로 다시 입력합니다. ## 지원하지 않는 컬럼 타입 `TEXT`, `CLOB`, `BLOB`, `BINARY`는 LOOKUP 컬럼으로 사용할 수 없습니다. 긴 문자열은 `VARCHAR`로 선언하고, 원문 보관이 필요하면 LOG 테이블에 분리합니다. ```sql -- 실패: 지원하지 않는 컬럼 타입입니다. (ERR-02173) ALTER TABLE ch9_err_pk ADD COLUMN (memo TEXT); ``` JSON은 일반 컬럼으로 사용할 수 있습니다. 사용 범위는 [JSON 컬럼과 조회](../json-column-query/)를 참고합니다. ```sql DROP TABLE ch9_err_pk; ``` ## 메모리 한도 LOOKUP은 디스크에 영속 저장되지만 조회 실행 경로는 메모리입니다. 서버가 기동하면 전체 행과 인덱스를 메모리에 올리므로 사용량이 한도를 넘으면 `ERR-01344`가 발생합니다. 이 한도는 **VOLATILE 테이블과 공유합니다.** 설정 이름이 `VOLATILE_`로 시작하지만 LOOKUP도 같은 한도에 포함되므로, 두 타입을 함께 쓰는 환경에서는 합계로 판단해야 합니다. ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME = 'VOLATILE_TABLESPACE_MEMORY_MAX_SIZE'; SELECT * FROM V$STORAGE_DC_VOLATILE_TABLE; ``` 한도에 가까워지면 보존 범위를 줄이거나, 사용하지 않는 보조 인덱스를 제거하거나, 대규모 기준 정보는 [인덱스와 성능](../index-performance/)의 기준에 따라 다른 테이블 타입을 검토합니다. ## 다중 row 변경 범위 일반 predicate UPDATE/DELETE는 여러 row에 적용될 수 있습니다. 실행 전 같은 predicate로 대상 수를 확인하고 [일반 predicate UPDATE/DELETE](../predicate-update-delete/)의 계약을 따릅니다. ## LOOKUP JSON primary key 오류 JSON column은 일반 column으로 사용할 수 있지만 PRIMARY KEY로 선언할 수 없습니다. 식별자를 별도 scalar column으로 두고 [JSON 컬럼과 조회](../json-column-query/)의 type·path 규칙을 따릅니다. ## 제약 및 주의사항 - PRIMARY KEY 정책은 [PRIMARY KEY 정책](../primary-key-policy/)을 참고합니다. - memory 규모와 index 비용은 [인덱스와 성능](../index-performance/)에서 측정합니다. - 시계열 원본은 TAG, 재시작 후 사라져도 되는 cache는 VOLATILE을 선택합니다. - Append gate는 [SDK Append matrix](/dbms/development-tools-integration/sdk-support-scope/#append-table-type-matrix)를 따릅니다. --- title: "9.9 활용 패턴과 시나리오" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/patterns-scenarios/ language: kr kind: page --- # 9.9 활용 패턴과 시나리오 LOOKUP 테이블의 활용 패턴과 시나리오를 다룹니다. ## 활용 사례 LOOKUP 테이블은 다음과 같은 데이터를 저장하는 데 적합합니다. ## 적합한 데이터 유형 | 유형 | 예시 | |------|------| | 코드 테이블 | 국가 코드, 언어 코드, 상태 코드 | | 기준 정보 | 설비 목록, 제품 분류, 부서 정보 | | 실시간 갱신 참조 | 환율 테이블, 임계값 설정 | | 태그 메타데이터 대체 | 센서 정보 (소규모) | ## 코드 테이블 ```sql CREATE LOOKUP TABLE ch9_pattern_country ( code VARCHAR(4) PRIMARY KEY, name VARCHAR(64), region VARCHAR(32) ); INSERT INTO ch9_pattern_country VALUES ('KR', '대한민국', 'Asia'); INSERT INTO ch9_pattern_country VALUES ('US', '미국', 'America'); UPDATE ch9_pattern_country SET name = 'United States' WHERE code = 'US'; SELECT code, name FROM ch9_pattern_country ORDER BY code; ``` 두 행이 조회되며, US 행의 name만 `United States`로 바뀌어 있습니다. 상태 코드나 알람 코드도 같은 방식으로 관리하며, 이벤트 원본과 JOIN해 사용합니다. ```sql CREATE LOOKUP TABLE ch9_pattern_status ( code VARCHAR(16) PRIMARY KEY, label VARCHAR(64), color VARCHAR(16) ); INSERT INTO ch9_pattern_status VALUES ('RUN', '가동', 'green'); INSERT INTO ch9_pattern_status VALUES ('STOP', '정지', 'red'); CREATE LOG TABLE ch9_pattern_event ( event_time DATETIME, device_id VARCHAR(64), status VARCHAR(16) ); INSERT INTO ch9_pattern_event VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'DEV-01', 'RUN'); INSERT INTO ch9_pattern_event VALUES (TO_DATE('2026-01-01 10:05:00', 'YYYY-MM-DD HH24:MI:SS'), 'DEV-01', 'STOP'); EXEC TABLE_FLUSH(ch9_pattern_event); SELECT e.event_time, e.device_id, c.label FROM ch9_pattern_event e JOIN ch9_pattern_status c ON e.status = c.code ORDER BY e.event_time; ``` 두 행이 코드 대신 `가동`·`정지` 라벨로 조회됩니다. 코드에 없는 상태가 이벤트에 들어오면 INNER JOIN에서 그 행이 빠지므로, 코드 테이블의 누락도 함께 점검합니다. ## 설비 마스터 ```sql CREATE LOOKUP TABLE ch9_pattern_equip ( equip_id VARCHAR(32) PRIMARY KEY, equip_name VARCHAR(128), location VARCHAR(64), dept VARCHAR(64), install_dt DATETIME ); INSERT INTO ch9_pattern_equip VALUES ('TEMP-01', 'Boiler', 'Seoul', 'Production', TO_DATE('2025-01-01', 'YYYY-MM-DD')); INSERT INTO ch9_pattern_equip VALUES ('TEMP-02', 'Chiller', 'Busan', 'Facility', TO_DATE('2025-01-01', 'YYYY-MM-DD')); ``` TAG 테이블의 센서 데이터와 JOIN하면 위치, 부서 같은 기준 정보를 함께 조회할 수 있습니다. ```sql CREATE TAG TABLE ch9_pattern_sensor ( name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); INSERT INTO ch9_pattern_sensor VALUES ('TEMP-01', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 90.0); INSERT INTO ch9_pattern_sensor VALUES ('TEMP-02', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 20.0); EXEC TABLE_FLUSH(ch9_pattern_sensor); SELECT d.name, m.location, m.dept, d.value FROM ch9_pattern_sensor d JOIN ch9_pattern_equip m ON d.name = m.equip_id WHERE m.dept = 'Production' ORDER BY d.name; ``` TEMP-01 한 행만 조회됩니다. 부서 조건은 LOOKUP 쪽 컬럼이므로, 조건을 바꾸면 읽는 TAG 원본은 그대로여도 결과 집합이 달라집니다. ## 임계값 설정 ```sql CREATE LOOKUP TABLE ch9_pattern_threshold ( sensor_name VARCHAR(64) PRIMARY KEY, low_limit DOUBLE, high_limit DOUBLE, alert_level SHORT ); INSERT INTO ch9_pattern_threshold VALUES ('TEMP-01', 0.0, 100.0, 1); INSERT INTO ch9_pattern_threshold VALUES ('TEMP-02', 0.0, 100.0, 1); -- 실시간 임계값 변경 UPDATE ch9_pattern_threshold SET high_limit = 85.0 WHERE sensor_name = 'TEMP-01'; ``` 임계값 테이블은 TAG 데이터와 결합해 알람 조건을 판단하는 데 사용합니다. ```sql SELECT s.name, s.value, t.high_limit FROM ch9_pattern_sensor s JOIN ch9_pattern_threshold t ON s.name = t.sensor_name WHERE s.value > t.high_limit ORDER BY s.name; ``` TEMP-01만 초과로 판정됩니다. 값 90은 변경 전 한계 100에서는 정상이었으므로, 같은 원본이라도 임계값을 언제 바꿨는지에 따라 판정이 달라진다는 점을 함께 기록합니다. ## SEQUENCE 기반 이력 번호 작은 규모의 관리 이력이나 운영 이벤트에 순번이 필요하면 SEQUENCE 컬럼을 사용할 수 있습니다. ```sql CREATE LOOKUP TABLE ch9_pattern_note ( seq LONG PROPERTY(SEQUENCE=1) PRIMARY KEY, target_id VARCHAR(64), note VARCHAR(512), created_at DATETIME ); INSERT INTO ch9_pattern_note VALUES (NEXTVAL(seq), 'TEMP-01', 'threshold changed', NOW); INSERT INTO ch9_pattern_note VALUES (NEXTVAL(seq), 'TEMP-02', 'inspection done', NOW); SELECT seq, target_id FROM ch9_pattern_note ORDER BY seq; ``` seq는 1과 2입니다. 대량 원본 이력은 LOOKUP보다 LOG 테이블에 저장합니다. 이 페이지의 실습 객체는 다음과 같이 정리합니다. ```sql DROP TABLE ch9_pattern_note; DROP TABLE ch9_pattern_threshold; DROP TABLE ch9_pattern_sensor; DROP TABLE ch9_pattern_equip; DROP TABLE ch9_pattern_event; DROP TABLE ch9_pattern_status; DROP TABLE ch9_pattern_country; ``` ## 부적합한 경우 - 명시적 트랜잭션과 일반 관계형 DML이 필요한 데이터 → TRANSACTION 테이블 권장 - UPDATE 불필요한 추가 전용 이력 → LOG 테이블 권장 - 센서 계측값 → TAG 테이블 권장 - 재시작 후 사라져도 되는 최신 상태 캐시 → VOLATILE 테이블 권장 --- title: "9.10 PRIMARY KEY 정책" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/primary-key-policy/ language: kr kind: page --- # 9.10 PRIMARY KEY 정책 LOOKUP 테이블의 PRIMARY KEY 설계 원칙과 정책을 다룹니다. SDK가 SELECT 결과에서 PK 여부를 확인하는 방법은 [PRIMARY KEY 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-primary-key-metadata)를 참고합니다. ## PRIMARY KEY 설계 LOOKUP 테이블은 `PRIMARY KEY`가 필수입니다. PRIMARY KEY는 행을 고유하게 식별하고 중복 입력을 제어합니다. UPDATE와 DELETE의 `WHERE` 절에는 일반 조건식을 사용할 수 있지만, Primary key 컬럼 자체는 UPDATE할 수 없습니다. ### 기본 문법 ```text CREATE LOOKUP TABLE table_name ( pk_col type PRIMARY KEY, col2 type, ... ); ``` ### 단일 컬럼 PRIMARY KEY ```sql CREATE LOOKUP TABLE ch9_pk_country_code ( code VARCHAR(4) PRIMARY KEY, name VARCHAR(64), region VARCHAR(32) ); ``` ### 복합 키가 필요한 경우 ```sql CREATE LOOKUP TABLE ch9_pk_price ( price_key VARCHAR(64) PRIMARY KEY, product_id VARCHAR(32), region VARCHAR(16), price DOUBLE ); -- 삽입 INSERT INTO ch9_pk_price VALUES ('PROD-01:KR', 'PROD-01', 'KR', 99.0); INSERT INTO ch9_pk_price VALUES ('PROD-01:US', 'PROD-01', 'US', 79.0); -- 조합 키 기반 UPDATE UPDATE ch9_pk_price SET price = 89.0 WHERE price_key = 'PROD-01:KR'; ``` LOOKUP 테이블은 PRIMARY KEY 컬럼을 하나만 지정할 수 있습니다. 여러 컬럼의 조합이 비즈니스 키라면 조합 문자열 또는 대리키를 별도 PRIMARY KEY 컬럼으로 둡니다. ### PRIMARY KEY 타입 선택 | 타입 | 장점 | 단점 | |------|------|------| | `VARCHAR(n)` | 가독성, 의미 있는 키 | 문자열 비교 비용 | | `INTEGER` / `LONG` | 비교 빠름, 저장 효율 | 의미 없음, 별도 매핑 필요 | ### 주의사항 - PRIMARY KEY 값은 중복될 수 없습니다. - PRIMARY KEY 값은 변경할 수 없습니다 (변경 시 DELETE + INSERT). - PRIMARY KEY 컬럼에는 인덱스가 자동 생성됩니다. - PRIMARY KEY 컬럼은 하나만 지정합니다. ## PRIMARY KEY 정책 PRIMARY KEY 설계 시 고려할 정책과 모범 사례입니다. ### 자연키 vs 대리키 #### 자연키 (Natural Key) 비즈니스 의미를 갖는 값을 그대로 PRIMARY KEY로 사용합니다. ```sql -- 국가 코드: 표준화된 자연키 CREATE LOOKUP TABLE ch9_pk_country ( iso_code VARCHAR(4) PRIMARY KEY, -- ISO 3166-1 alpha-2 name VARCHAR(64) ); ``` **장점**: 의미 파악 쉬움, 별도 조회 불필요 **단점**: 키 변경 시 참조 무결성 문제 #### 대리키 (Surrogate Key) SEQUENCE 컬럼이나 UUID처럼 의미 없는 값을 PRIMARY KEY로 사용합니다. ```sql -- 설비 마스터: 대리키 CREATE LOOKUP TABLE ch9_pk_equip ( equip_id LONG PROPERTY(SEQUENCE=1) PRIMARY KEY, code VARCHAR(32), -- 비즈니스 키 name VARCHAR(128) ); CREATE INDEX ch9_pk_equip_idx ON ch9_pk_equip(code); ``` **장점**: 불변, 조인 효율적 **단점**: 코드-ID 변환 필요 ### PRIMARY KEY 불변 원칙 PRIMARY KEY 값은 UPDATE할 수 없습니다. 변경이 필요하면 DELETE + INSERT를 사용합니다. ```sql -- 잘못된 패턴 (PK 변경은 DELETE + INSERT로) -- UPDATE는 PK 변경 불가 -- 올바른 패턴 DELETE FROM ch9_pk_country WHERE iso_code = 'OLD'; INSERT INTO ch9_pk_country VALUES ('NEW', '새 국가명'); ``` LOOKUP 테이블 DML은 개별 문장 단위로 실행합니다. `BEGIN`/`COMMIT`으로 묶는 TRANSACTION 트랜잭션에는 LOOKUP DML을 포함할 수 없습니다. 따라서 두 문장 사이의 조회와 삽입 실패에 대비해야 합니다. 기존 값을 보관하고 참조하는 키의 전환 순서를 정한 뒤 변경하며, 전체 변경의 원자성이 필요하면 TRANSACTION 테이블을 검토합니다. 이 페이지의 실습 테이블은 다음과 같이 정리합니다. ```sql DROP INDEX ch9_pk_equip_idx; DROP TABLE ch9_pk_equip; DROP TABLE ch9_pk_country; DROP TABLE ch9_pk_price; DROP TABLE ch9_pk_country_code; ``` --- title: "9.11 SEQUENCE 컬럼" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/sequence-column/ language: kr kind: page --- # 9.11 SEQUENCE 컬럼 LOOKUP 테이블의 SEQUENCE 컬럼 설정과 활용을 다룹니다. ## LOOKUP SEQUENCE 컬럼 정의 SEQUENCE 컬럼은 `NEXTVAL`로 입력 번호를 생성할 때 사용합니다. LOOKUP 테이블은 [PRIMARY KEY가 필수](../primary-key-policy/)이므로, SEQUENCE 컬럼 자체를 PRIMARY KEY로 지정할지 다른 컬럼을 PRIMARY KEY로 둘지 함께 정합니다. 이 번호를 이벤트 발생 시각의 순서와 동일하게 해석하지 않습니다. ## SEQUENCE 컬럼이 필요한 이유 같은 시각에 발생한 알람이나 관리 이력을 서로 다른 행으로 식별해야 할 때 사용할 수 있습니다. LOOKUP은 전체 행을 메모리에 유지하므로 이 예제는 소규모 관리 이력에 해당합니다. 장기간 누적하는 대량 이벤트는 LOG 테이블을 검토합니다. ## SEQUENCE 컬럼 선언 SEQUENCE는 `LONG` 또는 `INT64` 타입 컬럼에 지정할 수 있습니다. PROPERTY 절의 `SEQUENCE` 파라미터로 시작값을 설정하며, 이 속성은 LOOKUP 테이블에서만 사용할 수 있습니다. ```sql CREATE LOOKUP TABLE ch9_sequence ( seq LONG PROPERTY(SEQUENCE=1) PRIMARY KEY, sensor_id VARCHAR(40), alarm_type VARCHAR(20), occurred_at DATETIME, message VARCHAR(200) ); ``` - `SEQUENCE=1`: seq 컬럼이 1부터 자동 증가 - 시작값은 1 이상 4,294,967,295 미만의 정수만 지정할 수 있습니다. - 위 예제처럼 SEQUENCE 컬럼을 PRIMARY KEY로 지정하면 번호의 고유성까지 함께 보장됩니다. ## SEQUENCE 값 삽입: NEXTVAL() 서버는 SEQUENCE 컬럼마다 다음에 사용할 번호를 카운터로 보관하며, `NEXTVAL()`은 그 값을 가져와 삽입합니다. 매번 테이블의 최댓값을 다시 계산하지 않습니다. ```sql -- NEXTVAL()로 자동 증가값 입력 INSERT INTO ch9_sequence (seq, sensor_id, alarm_type, occurred_at, message) VALUES (NEXTVAL(seq), 'TEMP-01', 'HIGH', NOW, '온도 초과'); INSERT INTO ch9_sequence (seq, sensor_id, alarm_type, occurred_at, message) VALUES (NEXTVAL(seq), 'PRESS-02', 'LOW', NOW, '압력 저하'); -- 조회 SELECT * FROM ch9_sequence ORDER BY seq; -- seq=1, seq=2 순으로 정렬됨 ``` ## 일반 컬럼처럼 직접 값 지정 SEQUENCE 컬럼에 직접 값을 입력하는 것도 허용됩니다. 입력값이 현재 카운터보다 크면 카운터가 `입력값 + 1`로 올라갑니다. 현재 카운터보다 작은 값을 입력해도 카운터는 내려가지 않습니다. ```sql -- 직접 값 지정 (nextval을 쓰지 않아도 됨) INSERT INTO ch9_sequence (seq, sensor_id, alarm_type, occurred_at, message) VALUES (100, 'FLOW-03', 'NORMAL', NOW, '정상 복구'); -- 이후 NEXTVAL() 호출 시 101이 됩니다 INSERT INTO ch9_sequence (seq, sensor_id, alarm_type, occurred_at, message) VALUES (NEXTVAL(seq), 'TEMP-01', 'NORMAL', NOW, '온도 정상'); -- seq = 101 ``` ## 활용 패턴 ```sql -- 최신 알람 N건 조회 SELECT * FROM ch9_sequence ORDER BY seq DESC LIMIT 10; -- 특정 seq 이후 알람 조회 SELECT * FROM ch9_sequence WHERE seq > 500 ORDER BY seq; -- 알람 확인 처리 (PK 기준 UPDATE) UPDATE ch9_sequence SET alarm_type = 'ACKNOWLEDGED' WHERE seq = 101; ``` 실습 테이블은 다음과 같이 정리합니다. ```sql DROP TABLE ch9_sequence; ``` ## 주의 사항 - SEQUENCE 컬럼은 `LONG`, `INT64` 타입을 지원합니다. - 시작값은 양수(`SEQUENCE=1` 이상)만 허용되며 4,294,967,295 미만이어야 합니다. - 카운터는 서버에 저장되고 한 방향으로만 올라갑니다. 가장 큰 seq 행을 DELETE해도 그 번호는 다시 사용되지 않으므로, 번호가 비어 있는 구간이 생길 수 있습니다. - SEQUENCE 컬럼을 PRIMARY KEY로 지정하지 않으면 `NEXTVAL()`을 쓰지 않고 중복 값을 직접 입력할 수 있습니다. 번호의 고유성이 필요하면 이 컬럼을 PRIMARY KEY로 지정합니다. --- title: "9.12 JSON 컬럼과 JSON 조회" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/json-column-query/ language: kr kind: page --- # 9.12 JSON 컬럼과 JSON 조회 LOOKUP 테이블의 JSON 컬럼 지원 범위와 JSON 조건 조회를 다룹니다. ## LOOKUP JSON 조건 조회 LOOKUP 테이블은 `JSON` 컬럼을 일반 컬럼으로 지원합니다. JSON 컬럼은 유동적인 속성 값을 참조 데이터와 함께 저장할 때 사용할 수 있습니다. ```sql CREATE LOOKUP TABLE ch9_json ( sensor_id VARCHAR(80) PRIMARY KEY, location VARCHAR(200), config JSON ); INSERT INTO ch9_json VALUES ( 'TEMP-01', 'factory1', '{"unit":"celsius","level":3,"threshold":{"high":90.0}}' ); SELECT sensor_id, config FROM ch9_json WHERE config->'$.unit' = 'celsius'; ``` ## 타입별 JSON 조건 숫자 값을 숫자로 비교할 때는 타입별 JSON 추출 함수를 사용합니다. ```sql SELECT sensor_id FROM ch9_json WHERE JSON_EXTRACT_INTEGER(config, '$.level') >= 3 AND JSON_EXTRACT_DOUBLE(config, '$.threshold.high') > 80.0; ``` JSON 구조 자체를 확인할 수도 있습니다. ```sql SELECT sensor_id FROM ch9_json WHERE JSON_IS_VALID(config) = 1 AND JSON_TYPEOF(config, '$.threshold') = 'Object'; ``` ## PRIMARY KEY 제약 LOOKUP 테이블은 JSON 컬럼을 저장할 수 있지만, JSON 컬럼을 `PRIMARY KEY`로 사용할 수 없습니다. 행 식별자는 `INTEGER`, `LONG`, `VARCHAR` 등 안정적인 일반 타입으로 둡니다. ```sql -- 실패: JSON 컬럼은 primary key로 사용하지 않습니다. CREATE LOOKUP TABLE ch9_json_bad ( config JSON PRIMARY KEY, note VARCHAR(80) ); ``` ```sql -- 권장: 별도 식별자를 primary key로 사용합니다. CREATE LOOKUP TABLE ch9_json_ok ( sensor_id VARCHAR(80) PRIMARY KEY, config JSON, note VARCHAR(80) ); ``` ## 설계 기준 | 상황 | 권장 접근 | |------|----------| | 조인/검색에 자주 쓰는 값 | 별도 컬럼 | | 장비별로 다른 유동 속성 | JSON 컬럼 | | 숫자 조건 검색 | 일반 숫자 컬럼으로 분리 | | primary key | 안정적인 식별자 컬럼 사용 | | 고빈도 path 검색 | 별도 컬럼으로 추출 | JSON path별 전용 인덱스는 지원하지 않습니다. 고빈도 검색 조건은 별도 컬럼으로 분리하고, 해당 컬럼에 인덱스를 적용하는 설계를 우선 검토합니다. ```sql CREATE LOOKUP TABLE ch9_json_fast ( sensor_id VARCHAR(80) PRIMARY KEY, unit VARCHAR(16), level INTEGER, config JSON ); CREATE INDEX ch9_json_unit_idx ON ch9_json_fast(unit); ``` ## UPDATE·DELETE 조건 ```sql UPDATE ch9_json SET location = 'factory2' WHERE config->'$.unit' = 'celsius'; DELETE FROM ch9_json WHERE JSON_EXTRACT_INTEGER(config, '$.level') < 2; ``` 대상 범위가 넓을 수 있으므로 UPDATE/DELETE 전에는 같은 조건으로 건수를 확인합니다. 이 페이지의 실습 객체는 다음과 같이 정리합니다. `ch9_json_bad`는 생성에 실패하는 예제이므로 정리 대상이 아닙니다. ```sql DROP TABLE ch9_json_fast; DROP TABLE ch9_json_ok; DROP TABLE ch9_json; ``` ## 주의사항 - LOOKUP 테이블은 JSON 컬럼을 일반 컬럼으로 지원합니다. - JSON 컬럼은 primary key로 사용할 수 없습니다. - JSON path별 전용 인덱스는 지원하지 않습니다. - 자주 검색하는 값은 LOOKUP 일반 컬럼으로 분리합니다. - JSON path 인덱스가 필요하면 TRANSACTION 또는 TAG 테이블을 검토합니다. --- title: "9.13 일반 predicate UPDATE/DELETE" url: https://docs.machbase.com/kr/dbms/lookup-table-usage/predicate-update-delete/ language: kr kind: page --- # 9.13 일반 predicate UPDATE/DELETE LOOKUP 테이블은 기본 키뿐 아니라 일반 조건식으로 여러 행을 수정하거나 삭제할 수 있습니다. 변경 전 같은 조건으로 대상 행 수를 확인하십시오. 이 페이지의 실습은 다음 테이블 하나로 진행하며 마지막에 정리합니다. ```sql CREATE LOOKUP TABLE ch9_predicate ( equip_id VARCHAR(32) PRIMARY KEY, site VARCHAR(16), status VARCHAR(16), score INTEGER ); INSERT INTO ch9_predicate VALUES ('EQ-01', 'SEOUL', 'READY', 10); INSERT INTO ch9_predicate VALUES ('EQ-02', 'SEOUL', 'READY', 20); INSERT INTO ch9_predicate VALUES ('EQ-03', 'SEOUL', 'RETIRED', 30); INSERT INTO ch9_predicate VALUES ('EQ-04', 'BUSAN', 'READY', 40); ``` ## UPDATE 조건에 맞는 모든 행을 갱신합니다. `SET` 표현식은 현재 행 값을 참조할 수 있지만 기본 키 컬럼 자체는 변경할 수 없습니다. LOOKUP UPDATE에는 `WHERE` 조건이 필요합니다. 전체 행을 갱신하려는 경우에도 지원되는 조건식을 명시합니다. `WHERE` 없이 전체 삭제가 가능한 DELETE와 구분하십시오. ```sql -- 변경 전 영향 범위를 먼저 셉니다. SELECT COUNT(*) FROM ch9_predicate WHERE site = 'SEOUL' AND status = 'READY'; UPDATE ch9_predicate SET status = 'ACTIVE', score = score + 10 WHERE site = 'SEOUL' AND status = 'READY'; SELECT equip_id, site, status, score FROM ch9_predicate ORDER BY equip_id; ``` COUNT는 2이고, EQ-01과 EQ-02만 `ACTIVE`로 바뀌면서 score가 20·30이 됩니다. 같은 SEOUL이라도 status가 다른 EQ-03과, 같은 READY라도 site가 다른 EQ-04는 그대로입니다. ## DELETE 조건에 맞는 모든 행을 삭제합니다. `WHERE`를 생략하면 테이블의 모든 행이 삭제됩니다. ```sql SELECT COUNT(*) FROM ch9_predicate WHERE status = 'RETIRED'; DELETE FROM ch9_predicate WHERE status = 'RETIRED'; SELECT equip_id, status FROM ch9_predicate ORDER BY equip_id; ``` COUNT는 1이고 EQ-03이 삭제되어 세 행이 남습니다. ## 조건 설계 지침 1. 단건 변경에는 기본 키 조건을 사용합니다. 2. 일괄 변경 전 동일한 조건의 `SELECT COUNT(*)`로 영향 범위를 확인합니다. 3. 자주 필터링하는 JSON 값은 일반 컬럼으로 분리해 인덱스를 적용하는 방안을 검토합니다. 4. 기본 키를 바꿔야 한다면 기존 행을 삭제하고 새 키로 삽입합니다. 지원 연산자와 JSON 조건의 정확한 범위는 SQL 레퍼런스를 기준으로 확인하십시오. - [LOOKUP predicate UPDATE](/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-update-syntax/) - [LOOKUP predicate DELETE](/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-delete-syntax/) ## 권한과 성능 UPDATE와 DELETE에는 각각 대상 테이블의 `UPDATE`, `DELETE` 권한이 필요합니다. 애플리케이션이 변경 전후 값을 직접 조회할 때만 `SELECT`도 부여합니다. 권한 SQL은 [권한 관리](/dbms/security-access-control/privileges/)를 정본으로 사용합니다. 기본 키 equality는 단건 대상을 직접 찾고, 일반 조건식은 조건을 평가해 변경 대상을 수집합니다. 반복 단건 변경에는 prepared statement와 bind를 사용하고, 대량 변경은 같은 조건의 행 수와 실행 시간을 검증 환경에서 측정하십시오. ```sql DROP TABLE ch9_predicate; ``` --- title: "10. VOLATILE 테이블 활용" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/ language: kr kind: section --- # 10. VOLATILE 테이블 활용 VOLATILE 테이블은 서버 프로세스 범위에서 공유되는 메모리 테이블입니다. 재시작 시 데이터 소실, UPSERT와 재생성 가능한 캐시 패턴을 다룹니다. ## 이 장의 구성 | 절 | 내용 | |----|------| | [개요와 사용 기준](./overview-use-criteria/) | VOLATILE 테이블의 특성과 적용 판단 기준 | | [테이블 구조와 스키마](./table-structure-schema/) | PRIMARY KEY 설계, 컬럼 타입, 스키마 구성 | | [생성, 변경, 삭제](./create-alter-drop/) | CREATE VOLATILE TABLE, DROP, 영속성 차이 | | [데이터 입력과 변경](./data-input-mutation/) | INSERT, ON DUPLICATE KEY UPDATE, DELETE | | [조회와 분석](./query-analysis/) | SELECT, 조건 조회, LIKE | | [인덱스와 성능](./index-performance/) | Red-Black 트리 인덱스, PK 인덱스 | | [운영과 데이터 생명주기](./operations-lifecycle/) | 운영 절차와 데이터 관리 | | [제약, 오류, 문제 해결](./constraints-errors-troubleshooting/) | 기능 제약, 오류 대응 | --- title: "10.1 개요와 사용 기준" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/overview-use-criteria/ language: kr kind: page --- # 10.1 개요와 사용 기준 VOLATILE 테이블은 데이터를 메모리에 저장하는 임시 테이블입니다. 서버가 재시작되면 데이터가 소멸하므로, 재생성 가능한 최신 상태, 임시 집계, 세션 간 공유 캐시 용도로 사용합니다. ## VOLATILE 테이블의 특성 VOLATILE 테이블은 `CREATE VOLATILE TABLE` 문으로 생성합니다. ```sql CREATE VOLATILE TABLE ch10_overview ( sensor_id VARCHAR(64) PRIMARY KEY, value DOUBLE, updated_at DATETIME ); ``` VOLATILE 테이블의 주요 특성은 다음과 같습니다. | 항목 | 내용 | |------|------| | 주요 용도 | 최신 상태 캐시, 임시 집계, 중간 결과 | | 저장 위치 | 메모리 | | 재시작 후 데이터 | 소멸 | | 공유 범위 | 서버 수준 공유 | | 키 | PRIMARY KEY 선택 | | 주요 기능 | UPDATE, DELETE, `ON DUPLICATE KEY UPDATE`, Red-Black 트리 인덱스 | | 백업 | 지원하지 않음 | ## 사용 기준 다음 조건에 해당하면 VOLATILE 테이블을 사용합니다. - 서버 재시작 후 데이터가 없어져도 됩니다. - 원본 데이터에서 언제든 다시 계산하거나 재구성할 수 있습니다. - 최신 상태, 최근 집계, 임시 작업 결과를 빠르게 조회해야 합니다. - 여러 세션에서 같은 임시 상태를 공유해야 합니다. - 디스크 영속성보다 메모리 기반 응답 시간이 중요합니다. 최신 센서 상태를 유지하는 예시는 다음과 같습니다. ```sql INSERT INTO ch10_overview VALUES ('TEMP-01', 23.5, NOW) ON DUPLICATE KEY UPDATE SET value = 23.5, updated_at = NOW; SELECT * FROM ch10_overview WHERE sensor_id = 'TEMP-01'; -- 다음 절에서 같은 이름을 다시 사용하므로 정리합니다. DROP TABLE ch10_overview; ``` ### 활용 패턴 | 패턴 | key | 재생성 원본 | 권장 만료 방식 | |------|-----|-------------|----------------| | 장비 최신 상태 | 장비 ID | TAG 또는 LOG | 같은 key 갱신 | | 짧은 주기 집계 | 대상과 시간 bucket | TAG 또는 LOG | bucket 교체 또는 재구성 | | 작업 진행 상태 | 작업 ID | 작업 시스템 | 완료 후 key 삭제 | | 임시 조회 cache | 요청 또는 객체 ID | 영속 table | 전체 재구성 | 최신 상태 갱신은 [데이터 입력과 변경](../data-input-mutation/)을, 임시 집계는 [조회와 분석](../query-analysis/)을 참고합니다. ## 다른 테이블을 검토할 경우 다음 요구사항에는 다른 테이블 타입을 사용합니다. | 요구사항 | 권장 테이블 | |----------|-------------| | 재시작 후에도 반드시 보존해야 하는 원본 데이터 | TAG 또는 LOG | | 기준 코드나 장비 마스터처럼 영속 참조 데이터 | LOOKUP | | 트랜잭션과 관계형 갱신이 필요한 업무 데이터 | TRANSACTION | | 장기 분석 대상 시계열 데이터 | TAG | VOLATILE 테이블에만 저장된 데이터는 서버 종료 시 복구할 수 없습니다. 중요한 데이터는 TAG, LOG, LOOKUP, TRANSACTION 중 적합한 영속 테이블에 저장하고, VOLATILE 테이블은 캐시나 중간 결과로 사용합니다. ## 설계 순서 VOLATILE 테이블 설계 시 다음 순서로 결정합니다. 1. 데이터가 재생성 가능한지 확인합니다. 2. PRIMARY KEY가 필요한지 결정합니다. 3. 예상 행 수와 메모리 사용량을 산정합니다. 4. 재시작 후 초기 적재 절차를 준비합니다. 5. 보존해야 하는 결과는 애플리케이션의 명시적인 쓰기 작업으로 영속 테이블에 저장합니다. VOLATILE을 영속화하는 전용 flush 명령은 없습니다. 스키마와 PRIMARY KEY 설계는 [테이블 구조와 스키마](/dbms/volatile-table-usage/table-structure-schema/)에서, 재시작 대응은 [재시작과 데이터 소실](/dbms/volatile-table-usage/operations-lifecycle/)에서 다룹니다. --- title: "10.2 테이블 구조와 스키마" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/table-structure-schema/ language: kr kind: page --- # 10.2 테이블 구조와 스키마 VOLATILE 테이블의 PRIMARY KEY 설계와 스키마 구성 방법을 다룹니다. ## PRIMARY KEY 설계 VOLATILE 테이블은 PRIMARY KEY 없이도 생성할 수 있습니다. 단, PK 기반 조회나 `ON DUPLICATE KEY UPDATE`를 사용하려면 PRIMARY KEY가 필요합니다. ### 단일 PRIMARY KEY ```sql CREATE VOLATILE TABLE ch10_schema_state ( device_id VARCHAR(32) PRIMARY KEY, state VARCHAR(16), updated_at DATETIME ); ``` ### 복합 키가 필요한 경우 여러 컬럼 조합으로 행을 고유하게 식별해야 하면 조합 키를 별도 PRIMARY KEY 컬럼으로 둡니다. ```sql CREATE VOLATILE TABLE ch10_schema_hourly ( key_id VARCHAR(96) PRIMARY KEY, sensor_id VARCHAR(64), hour_ts DATETIME, avg_value DOUBLE, sample_cnt INTEGER ); -- 조합 키 삽입 INSERT INTO ch10_schema_hourly VALUES ('TEMP-01:2026010110', 'TEMP-01', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 23.5, 60); INSERT INTO ch10_schema_hourly VALUES ('TEMP-01:2026010111', 'TEMP-01', TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'), 24.1, 60); INSERT INTO ch10_schema_hourly VALUES ('TEMP-02:2026010110', 'TEMP-02', TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 21.0, 60); -- 조합 키 조회 SELECT sensor_id, avg_value FROM ch10_schema_hourly WHERE key_id = 'TEMP-01:2026010110'; ``` ### ON DUPLICATE KEY UPDATE와 함께 사용 ```sql -- PRIMARY KEY 중복 시 UPDATE 실행 INSERT INTO ch10_schema_state VALUES ('DEV-01', 'ONLINE', NOW) ON DUPLICATE KEY UPDATE SET state = 'ONLINE', updated_at = NOW; ``` ### 주의사항 - PRIMARY KEY 없이 생성할 수 있지만, PK 기반 동작(UPSERT, PK 조회 등)은 사용할 수 없습니다. - PRIMARY KEY가 같은 행을 둘 이상 저장할 수 없습니다. `ON DUPLICATE KEY UPDATE`는 중복 행을 추가하는 대신 기존 행을 갱신합니다. - PRIMARY KEY 컬럼은 하나만 지정합니다. ## VOLATILE 테이블 설계 VOLATILE 테이블은 데이터를 메모리에만 두고, 서버 재시작 시 데이터가 소멸합니다. 테이블 정의는 남으므로 설계에서 정할 것은 "무엇을 잃어도 되는가"와 "어떻게 다시 채우는가" 두 가지입니다. 여러 세션이 공유하되 재시작 후 복구할 필요가 없는 상태나 캐시에 사용합니다. 스키마를 정할 때는 다음 항목을 함께 검토합니다. - [활용 사례](../overview-use-criteria/#use-cases-volatile) - [영속성 차이와 DDL](../create-alter-drop/#differences-persistence-ddl) - [메모리 생명주기](../operations-lifecycle/#lifecycle-memory) - [Red-Black 트리 인덱스](../index-performance/#index-strategy-red-black) - [ON DUPLICATE KEY UPDATE](../data-input-mutation/#on-duplicate-key-update) - [재시작과 데이터 재구성](../operations-lifecycle/#data-loss) 이 페이지의 실습 테이블은 다음과 같이 정리합니다. ```sql DROP TABLE ch10_schema_hourly; DROP TABLE ch10_schema_state; ``` --- title: "10.3 생성, 변경, 삭제" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/create-alter-drop/ language: kr kind: page --- # 10.3 생성, 변경, 삭제 VOLATILE 테이블의 생성·삭제 방법과 다른 테이블 타입과의 영속성 차이를 다룹니다. ## Volatile 테이블 생성 및 관리 volatile 테이블의 생성 및 삭제 방법은 다음과 같습니다. ### 생성 ```sql create volatile table vtable (id1 integer, name varchar(20)); ``` ### 삭제 ```sql drop table vtable; ``` ## 영속성 차이·DDL VOLATILE 테이블은 다른 테이블 타입과 달리 메모리에만 존재합니다. ### 영속성 비교 | 항목 | VOLATILE | LOOKUP | TRANSACTION | TAG | LOG | |------|----------|--------|-----|-----|-----| | 저장 위치 | 메모리 | 디스크 | 디스크 | 디스크 | 디스크 | | 서버 재시작 후 데이터 유지 | X | O | O | O | O | | 테이블 구조(DDL) 유지 | O | O | O | O | O | ### DDL 특성 VOLATILE에서 사라지는 것은 데이터이고, 테이블 정의는 다른 테이블 타입과 마찬가지로 영속 저장됩니다. 재시작 후 필요한 작업은 재생성이 아니라 초기 적재입니다. ```sql -- 테이블 정의는 한 번만 만들면 됩니다. CREATE VOLATILE TABLE ch10_ddl ( sensor_id VARCHAR(64) PRIMARY KEY, value DOUBLE, updated_at DATETIME ); ``` ### 생성 문법 기본 구문은 `CREATE VOLATILE TABLE 테이블명 (컬럼 정의, ...)`입니다. 키 기반 갱신이나 삭제가 필요할 때 한 컬럼에 `PRIMARY KEY`를 지정합니다. - `PRIMARY KEY`는 선택 사항입니다. - PRIMARY KEY 컬럼은 하나만 지정합니다. ### AUTO_INCREMENT PRIMARY KEY 서버가 숫자 PRIMARY KEY를 생성해야 하면 단일 `LONG` 또는 `INT64` 컬럼에 `AUTO_INCREMENT`를 지정합니다. ```sql CREATE VOLATILE TABLE ch10_ddl_seq ( request_id LONG PRIMARY KEY AUTO_INCREMENT, payload VARCHAR(256) ); INSERT INTO ch10_ddl_seq(payload) VALUES('refresh'); ``` PK 컬럼을 생략하거나 NULL로 지정하면 서버가 값을 생성합니다. 단일 `INSERT ... VALUES`에서 `0..INT64_MAX` 범위의 PK 값을 직접 지정할 수도 있습니다. 지정값이 현재 다음 자동값 이상이면 다음 자동값은 `지정값 + 1`로 진행하며, 작은 값을 지정해도 되감기지 않습니다. AUTO_INCREMENT를 사용하는 VOLATILE 테이블에서는 `INSERT ... SELECT`와 `ON DUPLICATE KEY UPDATE`를 사용할 수 없습니다. VOLATILE 테이블은 서버 재시작 시 데이터가 사라지므로 다음 자동값도 1부터 다시 시작합니다. 테이블 정의는 유지되므로 다시 만들 필요는 없습니다. SDK에서 INSERT 결과 ID를 받는 방법은 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. ### 컬럼 추가와 삭제 Standard Edition에서는 VOLATILE 테이블에 고정 길이 숫자 ARRAY 컬럼을 추가하고 삭제할 수 있습니다. ```sql ALTER TABLE ch10_ddl ADD COLUMN (thresholds DOUBLE[2] DEFAULT [10.0, 20.0]); ALTER TABLE ch10_ddl DROP COLUMN (thresholds); ``` VOLATILE은 기존 scalar `ADD COLUMN`과 마찬가지로 ALTER 전에 존재한 row를 DEFAULT로 다시 쓰지 않습니다. ARRAY DEFAULT를 지정해도 기존 row의 새 컬럼은 whole NULL입니다. 이 동작은 LOG, LOOKUP, TRANSACTION과 TAG METADATA의 backfill 규칙과 다릅니다. ARRAY의 지원 요소 타입, cardinality와 DEFAULT 규칙은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ### 삭제 ```sql DROP TABLE ch10_ddl; DROP TABLE ch10_ddl_seq; ``` ### 주의사항 - VOLATILE 테이블의 DDL(구조 정의)은 데이터베이스에 저장되며 서버 재시작 후에도 남습니다. - 데이터는 재시작 후 자동 복원되지 않으므로, 초기 적재 스크립트(예: 시작 시 machsql 실행)를 구성해야 합니다. --- title: "10.4 데이터 입력과 변경" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/data-input-mutation/ language: kr kind: page --- # 10.4 데이터 입력과 변경 VOLATILE 테이블의 `INSERT`, 중복 키 갱신, `DELETE`를 실행 가능한 예제로 설명합니다. ## 데이터 입력과 갱신 다음 예제는 마지막 정리 구문까지 순서대로 실행할 수 있습니다. 최신 상태처럼 같은 키의 값을 계속 바꿔야 한다면 `PRIMARY KEY`를 정의하고 `ON DUPLICATE KEY UPDATE`를 사용합니다. ```sql CREATE VOLATILE TABLE ch10_mutation ( id INTEGER PRIMARY KEY, direction VARCHAR(10), refcnt INTEGER ); INSERT INTO ch10_mutation VALUES (1, 'west', 0); INSERT INTO ch10_mutation VALUES (2, 'east', 0); INSERT INTO ch10_mutation VALUES (1, 'south', 0) ON DUPLICATE KEY UPDATE; INSERT INTO ch10_mutation VALUES (1, 'south', 0) ON DUPLICATE KEY UPDATE SET refcnt = 1; SELECT * FROM ch10_mutation ORDER BY id; ``` 중복 키가 없으면 새 행이 입력됩니다. 중복 키가 있으면 `SET` 절이 없는 구문은 입력값으로 행 전체를 갱신하고, `SET` 절이 있는 구문은 지정한 컬럼만 갱신합니다. `PRIMARY KEY` 자체는 갱신 대상으로 지정할 수 없습니다. 대량 입력 API는 언어와 드라이버에 따라 초기화·바인딩·오류 처리가 다릅니다. 불완전한 코드 조각을 복사하지 말고 [SDK 및 통합](/dbms/development-tools-integration/)의 해당 드라이버 예제를 사용합니다. ## 조건부 갱신 이미 있는 행의 일부 컬럼만 바꿀 때는 `UPDATE`를 사용합니다. `INSERT ... ON DUPLICATE KEY UPDATE`가 "없으면 넣고 있으면 바꾼다"인 것과 달리, `UPDATE`는 대상 행이 있을 때만 값을 바꿉니다. VOLATILE 테이블의 `UPDATE`에는 `WHERE`가 필요하며, 조건은 삭제와 마찬가지로 `PRIMARY KEY = 값` 형태만 지원합니다. 다른 컬럼 조건이나 복합 조건은 사용할 수 없습니다. `WHERE`를 생략한 전체 갱신도 지원하지 않습니다. ```sql UPDATE ch10_mutation SET refcnt = refcnt + 1 WHERE id = 2; SELECT * FROM ch10_mutation ORDER BY id; ``` `SET` 절에는 `PRIMARY KEY` 컬럼을 지정할 수 없습니다. 키를 바꿔야 하면 기존 행을 삭제하고 새 키로 다시 입력합니다. ## 데이터 삭제 조건부 삭제는 `PRIMARY KEY = 값` 형태만 지원합니다. 다른 컬럼 조건이나 복합 조건은 사용할 수 없습니다. ```sql DELETE FROM ch10_mutation WHERE id = 2; SELECT * FROM ch10_mutation ORDER BY id; DROP TABLE ch10_mutation; ``` 테이블 정의를 유지한 채 모든 행을 비우려면 `DELETE FROM 테이블명`처럼 WHERE를 생략합니다. 스키마까지 초기화해야 하면 DROP 후 다시 생성합니다. 서버를 재시작하면 데이터만 사라지고 테이블 정의는 남으므로, 재생성이 아니라 재적재 스크립트를 별도로 관리합니다. --- title: "10.5 조회와 분석" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/query-analysis/ language: kr kind: page --- # 10.5 조회와 분석 VOLATILE 테이블의 키 조회, 일반 조건 조회, 임시 집계를 실행 가능한 예제로 설명합니다. ## 예제 데이터 준비 VOLATILE 테이블은 다른 테이블 타입과 동일하게 `SELECT` 문으로 조회합니다. 다음 예제는 마지막 정리 구문까지 순서대로 실행할 수 있습니다. ```sql CREATE VOLATILE TABLE ch10_query ( device_id VARCHAR(64) PRIMARY KEY, status VARCHAR(16), value DOUBLE, updated_at DATETIME ); INSERT INTO ch10_query VALUES ('DEV-01', 'RUNNING', 42.5, NOW); INSERT INTO ch10_query VALUES ('DEV-02', 'STOPPED', 0, NOW); ``` ## PRIMARY KEY 조회 키 조건은 최신 상태 캐시의 단건 조회에 적합합니다. ```sql SELECT device_id, status, value, updated_at FROM ch10_query WHERE device_id = 'DEV-01'; ``` ## 일반 조건 조회 `PRIMARY KEY`가 아닌 컬럼으로 반복 조회한다면 보조 인덱스를 검토합니다. ```sql CREATE INDEX ch10_query_status_idx ON ch10_query(status); SELECT device_id, value, updated_at FROM ch10_query WHERE status = 'RUNNING'; ``` 인덱스도 메모리를 사용하므로 실제 조회에 필요한 컬럼에만 생성합니다. ## 임시 집계 조회 짧은 주기의 집계 결과를 저장하면 대시보드나 알람 판단에서 반복 계산을 줄일 수 있습니다. ```sql CREATE VOLATILE TABLE ch10_query_summary ( summary_key VARCHAR(96) PRIMARY KEY, sensor_id VARCHAR(64), bucket_time DATETIME, avg_value DOUBLE, max_value DOUBLE, sample_cnt LONG ); INSERT INTO ch10_query_summary VALUES ('TEMP-01:2026-01-01T00:00', 'TEMP-01', TO_DATE('2026-01-01 00:00:00'), 21.5, 23.0, 60); SELECT sensor_id, bucket_time, avg_value, max_value FROM ch10_query_summary WHERE sensor_id = 'TEMP-01' ORDER BY bucket_time DESC LIMIT 10; DROP TABLE ch10_query_summary; DROP TABLE ch10_query; ``` 집계 결과를 장기 보관해야 하면 LOG 또는 TRANSACTION 테이블로 주기적으로 복사합니다. ## 조회 시 주의사항 - 서버 재시작 후에는 데이터가 비어 있으므로 초기 적재 여부를 먼저 확인합니다. - 키 조회가 중심이면 `PRIMARY KEY`를 지정합니다. - 범위 조회나 정렬에 자주 쓰는 컬럼은 보조 인덱스를 검토합니다. - 중요한 원본 데이터는 영속 테이블에 저장하고 VOLATILE은 캐시로 사용합니다. --- title: "10.6 인덱스와 성능" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/index-performance/ language: kr kind: page --- # 10.6 인덱스와 성능 VOLATILE 테이블의 인덱스 생성과 선택 기준을 설명합니다. ## 지원 인덱스 `PRIMARY KEY`를 선언하면 키 조회용 인덱스가 생성됩니다. 일반 컬럼에는 `REDBLACK` 인덱스를 추가할 수 있습니다. `BITMAP`과 `KEYWORD` 인덱스는 VOLATILE 테이블에서 지원하지 않습니다. 다음 예제는 생성부터 정리까지 순서대로 실행할 수 있습니다. ```sql CREATE VOLATILE TABLE ch10_index ( id INTEGER PRIMARY KEY, name VARCHAR(20), status VARCHAR(16) ); CREATE INDEX ch10_index_name_idx ON ch10_index(name) INDEX_TYPE REDBLACK; INSERT INTO ch10_index VALUES (1, 'west device', 'ACTIVE'); INSERT INTO ch10_index VALUES (2, 'east device', 'INACTIVE'); SELECT id, name FROM ch10_index WHERE name = 'west device'; DROP INDEX ch10_index_name_idx; DROP TABLE ch10_index; ``` ## 설계 기준 - 키 기반 단건 조회와 갱신에는 `PRIMARY KEY`를 사용합니다. - 일반 컬럼의 동등·범위 조건이 반복될 때만 보조 인덱스를 추가합니다. - 데이터뿐 아니라 인덱스도 메모리를 사용하므로 불필요한 인덱스를 제거합니다. - 실제 쿼리의 조건과 행 수를 기준으로 생성 전후 응답 시간과 메모리를 비교합니다. 구문 세부사항은 [인덱스 구문](/dbms/reference/sql/syntax/index-syntax/)을 참고합니다. --- title: "10.7 운영과 데이터 생명주기" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/operations-lifecycle/ language: kr kind: page --- # 10.7 운영과 데이터 생명주기 VOLATILE 테이블의 생성·적재·사용·소멸·재구성 절차를 정리합니다. ## 데이터 생명주기 1. 서버 시작 후 테이블 생성 SQL을 실행합니다. 2. 필요하면 영속 원본에서 초기 데이터를 적재합니다. 3. 애플리케이션이 조회와 갱신을 시작합니다. 4. 보존해야 할 결과는 영속 테이블에 기록합니다. 5. 서버가 종료되면 데이터가 소멸합니다. 테이블 정의는 남습니다. ## 세션 공유 VOLATILE 테이블은 서버 수준에서 공유됩니다. 한 세션이 입력한 행을 다른 세션이 조회할 수 있으며, 연결 종료만으로 데이터가 사라지지 않습니다. ## 영속 데이터와의 경계 VOLATILE에는 원본에서 재생성 가능한 최신 상태나 중간 결과만 둡니다. 감사 기록, 원본 이벤트, 복구할 수 없는 결과는 TAG, LOG, LOOKUP 또는 TRANSACTION 테이블에 저장합니다. 복사 SQL은 원본과 대상의 컬럼, 중복 처리, 실행 주기를 포함해 별도의 작업으로 관리합니다. ## 재시작 절차 - 테이블이 있는지 확인합니다. 재시작만으로는 정의가 사라지지 않으므로 보통 새로 만들지 않습니다. - 영속 원본이 있으면 정의된 기준 시점의 데이터만 적재합니다. - 예상 행 수와 최신 시각을 확인합니다. - 검증이 끝난 뒤 수집기와 애플리케이션 쓰기를 재개합니다. - 재구성이 실패하면 빈 캐시 상태에서 서비스가 안전하게 동작하는지 확인합니다. ## 운영 체크리스트 - 초기 적재 SQL을 형상 관리합니다. 최초 구축용 생성 SQL도 함께 보관합니다. - 스크립트를 운영 계정과 실제 접속 정보로 사전 검증합니다. - 행 수와 메모리 한도를 관찰합니다. - 보존이 필요한 데이터가 VOLATILE에만 남지 않도록 점검합니다. - 재시작 훈련에서 적재와 검증 순서를 확인합니다. ## 메모리 확인과 캐시 재구성 ```sql SELECT * FROM V$STORAGE_DC_VOLATILE_TABLE; SELECT * FROM V$SYSMEM; SELECT * FROM V$SESMEM; SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME = 'VOLATILE_TABLESPACE_MEMORY_MAX_SIZE'; ``` 특정 내부 컬럼명에 의존하지 말고 배포 버전의 view 정의를 확인합니다. 캐시를 다시 만들 때는 행 수와 표본 값을 기록하고, 사용 흐름을 전환한 뒤 테이블 생성·초기 적재·검증 순서로 진행합니다. 실패 시 빈 캐시로 안전하게 동작할 수 있어야 합니다. --- title: "10.8 제약, 오류, 문제 해결" url: https://docs.machbase.com/kr/dbms/volatile-table-usage/constraints-errors-troubleshooting/ language: kr kind: page --- # 10.8 제약, 오류, 문제 해결 VOLATILE 테이블의 제약 사항, 발생 가능한 오류, 문제 해결 방법을 다룹니다. 대부분의 문제는 메모리 한도, PRIMARY KEY 설계, 지원하지 않는 컬럼 타입, 재시작 후 데이터 소실에서 발생합니다. ## 제약 사항 VOLATILE 테이블 사용 시 다음 제약을 고려합니다. | 항목 | 제약 | |------|------| | 저장 위치 | 메모리 | | 재시작 후 데이터 | 소멸 | | 백업·마운트 | 지원하지 않음 | | JSON 컬럼 | 지원하지 않음 | | PRIMARY KEY | 선택 사항, 하나만 지정 | | UPDATE/DELETE | `PRIMARY KEY = 값` 조건만 지원 | | 메모리 한도 | Volatile/Lookup 테이블 전체 메모리 한도 영향 | ```sql -- 실패 예: VOLATILE 테이블에는 JSON 컬럼을 사용하지 않습니다. CREATE VOLATILE TABLE ch10_err_json ( session_id VARCHAR(64) PRIMARY KEY, payload JSON ); ``` 유동 속성이 필요하면 자주 조회하는 값을 일반 컬럼으로 분리하거나, 영속 JSON 컬럼이 필요한 경우 TRANSACTION 또는 TAG 테이블을 검토합니다. ## 메모리 부족 VOLATILE 테이블의 데이터와 인덱스는 메모리를 사용합니다. 행 수가 증가하거나 인덱스가 많으면 메모리 한도에 도달할 수 있습니다. 진단은 다음 순서로 수행합니다. 아래 실습 테이블은 이 페이지 끝에서 정리합니다. ```sql -- 진단 예제용 실습 테이블입니다. CREATE VOLATILE TABLE ch10_diag ( device_id VARCHAR(64) PRIMARY KEY, value DOUBLE ); INSERT INTO ch10_diag VALUES ('DEV-01', 10.0); -- 1. 대상 테이블의 행 수를 확인합니다. SELECT COUNT(*) FROM ch10_diag; ``` ```sql -- 2. VOLATILE 테이블 전체의 메모리 사용을 확인합니다. SELECT * FROM V$STORAGE_DC_VOLATILE_TABLE; ``` 필요하면 설정의 `VOLATILE_TABLESPACE_MEMORY_MAX_SIZE` 값을 확인합니다. ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME = 'VOLATILE_TABLESPACE_MEMORY_MAX_SIZE'; ``` 해결 방법은 다음과 같습니다. - 불필요한 행을 삭제합니다. - 캐시 보관 범위를 줄입니다. - 사용하지 않는 인덱스를 제거합니다. - 중요한 데이터는 영속 테이블로 옮긴 뒤 VOLATILE 테이블을 재구성합니다. - 운영 정책에 맞게 메모리 한도 조정을 검토합니다. ## PRIMARY KEY 관련 오류 `ON DUPLICATE KEY UPDATE`, [PK 기반 UPDATE](../data-input-mutation/#volatile-primary-key-update), PK 기반 DELETE를 사용하려면 PRIMARY KEY가 필요합니다. ```sql CREATE VOLATILE TABLE ch10_err_device ( device_id VARCHAR(64) PRIMARY KEY, status VARCHAR(16), updated_at DATETIME ); ``` PRIMARY KEY 값은 중복될 수 없습니다. 중복 입력을 갱신으로 처리하려면 `ON DUPLICATE KEY UPDATE`를 사용합니다. ```sql INSERT INTO ch10_err_device VALUES ('DEV-01', 'ONLINE', NOW) ON DUPLICATE KEY UPDATE SET status = 'ONLINE', updated_at = NOW; ``` PRIMARY KEY 컬럼 자체는 UPDATE할 수 없습니다. 키를 변경해야 하는 경우 기존 행을 삭제하고 새 키로 삽입합니다. ## 재시작 후 데이터 소실 서버가 정상 종료, 비정상 종료, 재시작되면 VOLATILE 테이블 데이터는 소멸합니다. 이는 오류가 아니라 테이블 타입의 특성입니다. 문제가 발생했을 때는 다음을 확인합니다. 1. 서버 재시작 이력이 있는지 확인합니다. 2. VOLATILE 테이블 생성 스크립트가 실행되었는지 확인합니다. 3. 초기 적재 쿼리가 정상 실행되었는지 확인합니다. 4. 원본 TAG/LOG/TRANSACTION 테이블에서 캐시를 재구성합니다. ```sql SELECT COUNT(*) FROM ch10_diag; ``` 결과가 0이면 초기 적재 절차를 다시 실행합니다. 이 페이지의 실습 테이블은 다음과 같이 정리합니다. ```sql DROP TABLE ch10_diag; DROP TABLE ch10_err_device; ``` ## 문제 해결 체크리스트 - 테이블에 저장한 데이터가 재생성 가능한지 확인합니다. - PRIMARY KEY가 필요한 작업인지 확인합니다. - `COUNT(*)`와 `V$STORAGE_DC_VOLATILE_TABLE`로 규모와 메모리 사용을 확인합니다. - 재시작 후에는 초기 적재 SQL을 다시 실행합니다. 테이블은 다시 만들지 않아도 됩니다. - 영속 보존이 필요하면 VOLATILE이 아니라 TAG, LOG, LOOKUP, TRANSACTION 테이블을 사용합니다. --- title: "11. 개발 및 애플리케이션 연동" url: https://docs.machbase.com/kr/dbms/development-tools-integration/ language: kr kind: section --- # 11. 개발 및 애플리케이션 연동 애플리케이션 요구사항에 맞는 연동 방식을 선택하고, SDK별 설치·API와 공통 운영 원칙을 확인합니다. 언어별 구현은 SDK 페이지를, 여러 SDK에 공통인 판단 기준은 선택·개념·지원 범위 페이지를 참고합니다. ## 읽는 순서 1. [연동 방식 선택](selection-integration-method/)에서 언어와 입력 방식에 맞는 인터페이스를 고릅니다. 2. [공통 연동 개념](concepts-common/)에서 인증, 시간값, 바인딩, 트랜잭션과 재시도를 확인합니다. 3. [SDK 기능 지원 범위](sdk-support-scope/)에서 필요한 기능의 실제 지원 여부를 비교합니다. 4. 해당 언어의 SDK 페이지에서 설치, 연결과 실행 코드를 확인합니다. 5. [데이터 입력과 반출](data-input-load-export/)에서 작업별 절차를 적용합니다. SQL의 ROWID 의미는 [ROWID](../reference/sql/rowid/)를 참고하십시오. Machbase DBMS 8.7.0의 ARRAY 부분 입력은 [Sparse ARRAY와 선택 컬럼 Append API](data-input-load-export/array-append/)를 참고하십시오. ## SDK별 레퍼런스 | 환경 | 문서 | |---|---| | C/C++ 네이티브 또는 ODBC | [Machbase SQLCLI와 ODBC](cli-odbc/) | | Java·Spring | [JDBC](jdbc/) | | Python | [Python](python/) | | Node.js·TypeScript | [Node.js / TypeScript](node-js-typescript/) | | C#·VB.NET | [.NET Connector](net-connector/) | | Go 네이티브·`database/sql` | [Go](go/) | ## 공통 연결 정보 | 항목 | 기본값 | 설명 | |---|---|---| | HOST | `127.0.0.1` | Machbase 서버 호스트명 또는 IP | | PORT | `5656` | Machbase 서버 포트 (`machbase.conf`의 `PORT_NO`) | | USER | `SYS` | 사용자 ID | | PASSWORD | `MANAGER` | 사용자 비밀번호 | 운영 환경에서는 별도 사용자와 필요한 최소 권한을 사용합니다. 계정과 인증 설정은 [계정, 권한, 접속 제어](../security-access-control/)를 참고하십시오. ## 기존 지원 범위 링크 NULL·PRIMARY KEY 메타데이터, ROWID, Append와 AUTH KEY의 SDK별 지원 여부는 [SDK 기능 지원 범위](sdk-support-scope/)에서 확인하십시오. --- title: "11.1 연동 방식 선택" url: https://docs.machbase.com/kr/dbms/development-tools-integration/selection-integration-method/ language: kr kind: section --- # 11.1 연동 방식 선택 프로젝트의 언어, 입력 특성, 배포 환경에 맞는 연동 방식을 선택합니다. ## 선택 기준 | 요구사항 | 우선 검토할 방식 | |----------|------------------| | C/C++ 네이티브 수집기 | SQLCLI | | ODBC 관리자·DSN 기반 애플리케이션 | ODBC | | Java·Spring | JDBC | | Python 분석·자동화 | `machbaseapi` | | C#·VB.NET | .NET Connector | | Go 수집기·서비스 | 네이티브 `machgo` 또는 `database/sql` | | Node.js·TypeScript 백엔드 | `@machbase/ts-client` | | R 분석 환경 | Machbase ODBC 드라이버와 RODBC | | 큰 파일의 일괄 입력·반출 | machloader, csvimport, csvexport | | 지속적인 TAG·LOG 대량 입력 | 선택한 SDK의 Append API | 브라우저에서 5656 포트로 직접 연결하지 않습니다. 백엔드에서 쿼리를 실행하고 필요한 결과만 전달합니다. ## 결정 순서 1. 애플리케이션 언어에서 유지보수 가능한 공식 드라이버를 고릅니다. 2. 5656 포트 연결, 운영체제와 실행 시점 호환성을 확인합니다. 3. SQL, 준비된 문장, Append, 트랜잭션 중 필요한 기능을 정합니다. 4. [SDK 기능 지원표](../sdk-support-scope/)에서 실제 지원 여부를 확인합니다. 5. 표본 데이터로 타임스탬프, NULL, 숫자, 문자열을 왕복 검증합니다. 6. 목표 행 크기·동시 연결·배치 크기로 부하 테스트합니다. Append 지원만으로 드라이버를 결정하지 않습니다. flush 지연, 오류 서버 처리 응답, 재연결, 실패 행 처리까지 실제 SDK에서 확인합니다. TRANSACTION DML에는 명시적 트랜잭션 지원 범위를 확인하고, TAG·LOG Append를 같은 롤백 단위로 가정하지 않습니다. ## 주제별 상세 문서 | 내용 | 상세 문서 | |------|------| | SDK 설치·연결·API·완전한 코드 | 이 장의 SDK별 페이지 | | 공통 인증·바인딩·트랜잭션·재시도 | [공통 연동 개념](../concepts-common/) | | SDK별 기능 지원 여부 | [SDK 기능 지원 범위](../sdk-support-scope/) | | SQL·설정·명령줄 상세 | [16장 레퍼런스](/dbms/reference/) | | 입력·반출 방식 선택 | [데이터 입력과 반출](../data-input-load-export/) | 연동 방식을 선택한 뒤에는 해당 SDK 페이지에서 설치와 연결 예제를 실행합니다. 기능 지원 여부는 문서에 표시된 버전과 실제 배포 산출물을 함께 확인합니다. --- title: "11.2 공통 연동 개념" url: https://docs.machbase.com/kr/dbms/development-tools-integration/concepts-common/ language: kr kind: section --- # 11.2 공통 연동 개념 드라이버나 언어에 관계없이 공통으로 적용되는 연결, 바인딩, 트랜잭션, 대량 입력, 오류 처리 원칙을 설명합니다. SDK별 함수명과 완전한 코드는 이 장의 SDK별 페이지를 참고합니다. ## 연결 문자열과 인증 연결에는 호스트, 네이티브 포트, 사용자, 인증 정보를 사용합니다. 기본 포트는 `5656`이지만 배포 환경의 `machbase.conf` 설정을 확인합니다. | 항목 | 확인 사항 | |------|-----------| | 호스트·포트 | 애플리케이션 실행 위치에서 TCP 연결 가능 여부 | | 사용자 | 대상 데이터베이스와 테이블에 필요한 최소 권한 | | 비밀번호 | 환경 변수나 비밀 관리 시스템으로 주입 | | 데이터베이스 | SDK가 초기 데이터베이스 선택을 지원하는지 확인 | | 시간 초과 | 연결·명령·읽기 제한을 워크로드에 맞게 설정 | | 시간대 | SDK와 서버가 지원하는 옵션명과 적용 범위 확인 | 예제의 `SYS`/`MANAGER`는 로컬 검증용입니다. 운영 애플리케이션에는 전용 계정을 만들고 소스, 명령 이력, 로그에 비밀번호를 기록하지 않습니다. AUTH KEY는 비밀번호 대신 개인키로 challenge에 서명하는 방식입니다. 키 형식, 파일 권한, SDK별 옵션은 [AUTH KEY 인증](/dbms/security-access-control/authentication-auth-key/)과 해당 드라이버 문서를 함께 확인합니다. 연결 풀을 사용하면 반환된 연결의 현재 데이터베이스, 세션 설정, 열린 문장이 다음 요청에 영향을 주지 않도록 초기화 동작을 검증합니다. ## 타임존과 시간값 Machbase `DATETIME`은 나노초 정밀도를 지원합니다. 애플리케이션에서는 시간값의 의미와 표현을 분리해 관리합니다. - 수집 시각의 기준대(UTC 또는 업무 지역)를 명시합니다. - 문자열을 바인딩할 때 형식과 시간대를 함께 고정합니다. - epoch 값을 전달할 때 SDK가 요구하는 단위가 초, 밀리초, 마이크로초, 나노초 중 무엇인지 확인합니다. - 조회 문자열의 시간대는 연결 옵션이나 `TO_CHAR()` 등 실제 사용 경로에서 왕복 테스트합니다. - `NOW`와 `SYSDATE`를 업무 규칙에 혼용하지 말고 필요한 의미를 SQL 레퍼런스에서 확인합니다. 문자열 왕복 검증은 동일한 연결에서 입력값, 조회값, 시간대 변경 후 조회값을 비교합니다. 상세 함수는 [SQL 함수](/dbms/reference/sql/functions/functions-full/)를 참고합니다. ## Prepared statement 준비된 문장(prepared statement)은 SQL 구조와 값을 분리하고 같은 SQL을 반복 실행할 때 사용합니다. ```text INSERT INTO sensor_data VALUES (?, ?, ?) SELECT value FROM sensor_data WHERE name = ? AND time >= ? ``` 문장을 준비한 연결과 현재 데이터베이스가 바뀌지 않았는지 확인하고, 사용 후 닫습니다. 문장 캐시를 제공하는 SDK는 캐시 범위와 항목 제거 정책, 데이터베이스 전환 시 초기화 동작을 확인합니다. CTE, LIMIT 등 문법 위치에 따라 매개변수 자리표시자가 허용되지 않을 수 있습니다. 문법 오류가 나면 값을 문자열로 합치기 전에 [Named Bind Parameter](/dbms/reference/sql/syntax/named-bind-parameter-syntax/)와 해당 SDK의 자리표시자 지원 범위를 확인합니다. ## Parameter binding | 값 종류 | 권장 방식 | |---------|-----------| | 정수·실수 | 언어의 고정 폭 타입과 SQL 타입 범위를 맞춤 | | 문자열 | 인코딩과 최대 길이를 확인 | | DATETIME | SDK의 시간 객체 또는 명시된 epoch 단위 사용 | | NULL | 언어별 NULL 표현과 SQL 타입을 함께 지정 | | DECIMAL | 문자열 변환보다 SDK의 정확한 고정소수 타입을 우선 | | binary·IP | SDK가 요구하는 바이트 배열 또는 전용 타입 사용 | 위치 기반 자리표시자 `?`는 등장 순서대로 값을 바인딩합니다. 이름 기반 자리표시자 `:name`은 지원하는 서버와 SDK에서만 사용하며, 같은 이름의 반복 처리 규칙을 확인합니다. 식별자나 SQL keyword는 값 매개변수로 바인딩할 수 없으므로 허용 목록으로 검증한 뒤 SQL을 구성합니다. ## DML 영향 행 수 `INSERT`, `UPDATE`, `DELETE` 뒤에는 SDK가 반환한 영향 행 수를 확인합니다. 성공 응답만으로 업무 대상이 실제 변경됐다고 가정하지 않습니다. - 단건 변경은 기대값이 1인지 확인합니다. - 0건이면 조건에 맞는 행이 없는지, 이미 같은 변경이 반영되었는지 확인합니다. - 권한 부족이나 지원하지 않는 DML은 영향 행 수와 별도로 오류 코드와 예외를 확인합니다. - 일괄 변경은 실행 전 같은 조건의 `COUNT(*)`로 범위를 확인합니다. - Append는 SQL 영향 행 수 대신 close·서버 처리 응답의 성공 건수와 실패 건수를 확인합니다. ## 트랜잭션 명시적 `BEGIN`, `COMMIT`, `ROLLBACK`은 TRANSACTION 테이블의 관계형 DML에 사용합니다. LOG·TAG Append를 같은 롤백 단위로 가정하지 않습니다. 1. 실제 SDK가 트랜잭션 API를 제공하는지 확인합니다. 2. 제공하지 않으면 지원되는 SQL 제어 구문을 같은 연결에서 실행합니다. 3. 오류 경로에서 롤백하고 연결을 재사용할 수 있는지 확인합니다. 4. 연결 풀 반환 전에 미완료 트랜잭션이 남지 않도록 합니다. 5. 여러 테이블 타입을 섞은 작업은 각 문장의 커밋 범위를 사전 검증합니다. 완전한 SQL 예제는 [TRANSACTION 테이블의 트랜잭션](/dbms/rdb-table-usage/transaction/)을 참고합니다. ## Append API와 batch 입력 방식 선택과 결과 확인 항목은 [데이터 입력과 반출](../data-input-load-export/)에서, 클라이언트·테이블 타입별 Append gate는 [SDK Append 지원표](../sdk-support-scope/#append-table-type-matrix)에서 다룹니다. Append 연결은 일반 쿼리 연결과 분리하고 flush·close와 실패 행을 확인합니다. ## 오류 처리와 재시도 오류는 연결, 인증·권한, SQL·스키마, 데이터, 자원 부족으로 분류합니다. - 연결 단절과 일시적 시간 초과만 제한된 횟수와 재시도 대기 시간으로 재시도합니다. - 인증 실패, 권한 부족, 문법 오류, 타입 오류는 수정 전 자동 재시도하지 않습니다. - 재시도 전에 문장·커서·Append 핸들과 연결을 정리합니다. - INSERT 재시도는 업무 키나 중복 처리 정책으로 멱등성을 확보합니다. - 서버 오류 코드와 메시지는 기록하되 자격 증명과 원문 민감 데이터는 제거합니다. - 연결 풀에서 오류가 난 연결은 유효성 검사 후 반환하거나 폐기합니다. 운영 오류 분류와 진단 순서는 [문제 해결](/dbms/troubleshooting/)을 참고합니다. --- title: "11.3 SDK 기능 지원 범위" url: https://docs.machbase.com/kr/dbms/development-tools-integration/sdk-support-scope/ language: kr kind: section --- # 11.3 SDK 기능 지원 범위 애플리케이션 요구사항에 맞는 SDK를 선택할 수 있도록 기능별 지원 여부와 API 진입점을 비교합니다. 설치, 연결, 함수와 실행 코드는 각 SDK 페이지를 참고합니다. SDK를 처음 고를 때는 [연동 방식 선택](../selection-integration-method/)을 먼저 읽고, 이 페이지에서는 필요한 기능과 정확한 API 경로를 대조합니다. ## Nullable 메타데이터 Machbase는 SELECT 결과 컬럼의 NULL 가능성을 `NO_NULLS`, `NULLABLE`, `UNKNOWN`으로 구분합니다. 애플리케이션은 `NULLABLE`과 `UNKNOWN`을 모두 NULL 처리 대상으로 가정합니다. | SDK | 조회 방법 | |---|---| | JDBC | `ResultSetMetaData.isNullable()` | | Python | `cursor.description[i][6]` | | Go 네이티브 | `api.Column.Nullability` | | Go `database/sql` | `Rows.ColumnTypeNullable()` | | Node.js | `ColumnMeta.nullable` | | .NET | `GetSchemaTable()`의 `AllowDBNull` | | SQLCLI·ODBC | 디스크립터의 nullable 속성 | 표현식, 집계, VIEW와 JOIN 결과는 `UNKNOWN`이 될 수 있습니다. 직접 컬럼의 스키마 제약과 조회 결과 메타데이터를 같은 것으로 가정하지 마십시오. ## PRIMARY KEY 메타데이터 | SDK | 결과 컬럼 | 테이블 카탈로그 | |---|:---:|:---:| | JDBC | O | `DatabaseMetaData.getPrimaryKeys()` | | Python | O | 카탈로그 SQL | | Go 네이티브 | O | 카탈로그 SQL | | Go `database/sql` | 표준 API 없음 | 카탈로그 SQL | | Node.js | O | 카탈로그 SQL | | .NET | O | 카탈로그 SQL | | ODBC | 별도 결과 API 없음 | `SQLPrimaryKeys()` | 표현식·집계·외부 JOIN의 NULL 공급 측은 원본 컬럼의 PK 속성을 그대로 전달하지 않을 수 있습니다. ## INSERT 결과 ROWID Machbase 8.7.0 Standard Edition에서 성공한 단일 `INSERT ... VALUES`는 지원 SDK에 ROWID를 제공할 수 있습니다. | SDK·도구 | 확인 방법 | 값이 없을 때 | |---|---|---| | machsql | `SHOW LAST ROWID` | `NULL` | | Machbase SQLCLI | `SQLGetGeneratedRowID()` | `SQL_NO_DATA` | | 표준 ODBC | 전용 표준 API 없음 | - | | JDBC | `Statement.getGeneratedKeys()` | 빈 `ResultSet` | | Python | `cursor.lastrowid` | `None` | | .NET | `MachCommand.RowId` | `null` | | Go `database/sql` | `Result.LastInsertId()` | 오류 | | Go 네이티브 | 미지원 | - | | Node.js | 실행 결과 `rowId` | `undefined` | 배치, `executemany()`, Append, loader, `INSERT ... SELECT`와 UPSERT에서는 단일 ROWID를 반환하지 않습니다. SQL 의미와 테이블별 제약은 [ROWID](/dbms/reference/sql/rowid/)를 참고하십시오. ## Append API와 table type | API 경로 | LOG | TAG | LOOKUP | VOLATILE | TRANSACTION | 기준 | |---|:---:|:---:|:---:|:---:|:---:|---| | SQLCLI `SQLAppend*` extension | O | O | O | O | O | TRANSACTION은 Standard | | JDBC `MachStatement.executeAppend*` | O | O | O | O | O | NFX cce422d source/test | | Python 2.4 `append*` | O | O | O | O | O | NFX cce422d source/test | | .NET `MachAppendWriter` | O | O | O | O | O | NFX cce422d 프로바이더 | | Go v1.8.4 네이티브 `Appender` | O | O | X | X | O | TRANSACTION은 Standard | | Go `database/sql` 표준 API | X | X | X | X | X | `sql.Conn.Raw()` 확장은 네이티브 계약 | | Node 소스 `appendBatch()` | O | △ | △ | △ | O | NFX cce422d 소스 빌드 | | Node 소스 `appendOpen()` | O | O | △ | △ | △ | 범용 native/fallback, 테이블별 검증 필요 | 표준 쿼리 연결과 Append 핸들의 생명 주기를 구분하고 close·flush 결과를 확인합니다. `O`는 해당 source/test에서 확인된 범위이고 `△`는 범용 경로만 있어 테이블별 회귀 검증이 더 필요한 범위입니다. Append extension은 표준 ODBC나 `database/sql` 기능이 아닙니다. ### ARRAY와 선택 컬럼 Append Machbase DBMS 8.7.0의 ARRAY 지원 범위는 다음과 같습니다. | SDK | 밀집 ARRAY 조회·입력 | 희소 ARRAY | 선택 컬럼 Append | |---|:---:|:---:|:---:| | SQLCLI·C++ | O | O | O | | Machbase ODBC extension | O | O | O | | JDBC | O | O | O | | Python | O | O | O | | Node.js | O | O | O | | .NET full/legacy 프로바이더 | O | O | O | | Go | v2 main 소스 | v2 main 소스 | v2 main 소스 | Machbase DBMS 8.7.0 서버와 ARRAY 기능이 포함된 SDK 빌드를 함께 사용합니다. ARRAY의 SQL 요소 위치와 Machbase 전용 SDK 위치는 0부터 시작하는 인덱스입니다. 기존 전체 행 scalar Append API는 유지됩니다. Node.js prepared 대체 경로도 `SparseArray`를 지원합니다. Go는 [`neo-client` PR #17](https://github.com/machbase/neo-client/pull/17) 이후의 v2 main 소스를 사용해야 하며 공개 v2 릴리스가 지정되기 전에는 공개 모듈 버전만으로 지원을 가정하지 않습니다. 자세한 입력 방식과 API는 [Sparse ARRAY와 선택 컬럼 Append API](../data-input-load-export/array-append/)를 참고하십시오. ## AUTH KEY | SDK·도구 | 지원 | |---|:---:| | machsql, Machbase SQLCLI, ODBC, JDBC | O | | Go 네이티브, Go `database/sql` | O (neo-client v1.5.0+) | | Python, Node.js, .NET | X | 키 생성·등록·교체는 [AUTH KEY 인증](/dbms/security-access-control/authentication-auth-key/)을, 연결 옵션은 지원 SDK 페이지를 참고합니다. ## Transaction, prepare와 bind | SDK | Transaction API | Server prepared | Named bind API | |---|:---:|:---:|:---:| | JDBC | O | O | △ (Machbase extension) | | Python | X | O | O | | Go 네이티브 | △ | O | O | | Go `database/sql` | O | O | O | | .NET | X | X | △ | | Node.js | X | O | O | | SQLCLI | △ | O | O | | ODBC | △ | O | 순번 bind | 준비된 문장(prepared statement)과 매개변수 바인딩은 트랜잭션 지원과 별개입니다. 자리표시자 문법은 [Named Bind Parameter](/dbms/reference/sql/syntax/named-bind-parameter-syntax/)를 참고하십시오. Machbase 8.7.0 Standard Edition의 TAG 데이터 UPDATE는 각 SDK의 기존 positional/named API로 NAME과 BASETIME 조건 값을 바인딩할 수 있습니다. NFX #4127 회귀 테스트는 C/C++ SQLCLI, Go `database/sql`, JDBC, Node.js, Python과 .NET 경로를 검증합니다. ODBC는 별도 SDK 회귀 매트릭스에 포함되지 않으며 `?` 또는 이름 기반 SQL의 자리표시자를 표준 `SQLBindParameter()` 순번으로 바인딩합니다. TAG UPDATE 조건 계약은 [TAG 데이터 UPDATE bind](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/#tag-data-update-predicate-bind)를 참고합니다. Transaction 열의 `△`는 전용 객체 대신 같은 연결에서 트랜잭션 SQL을 직접 실행하는 경로입니다. Named bind 열의 `△`는 표준 이름 기반 바인딩 API가 아니라 드라이버 확장 또는 클라이언트가 값을 SQL 리터럴로 변환하는 경로를 뜻합니다. ## 최소 확인 version과 provenance | SDK | 이 문서의 확인 기준 | |---|---| | SQLCLI·JDBC·Python | NFX `cce422d2972`; Python 패키지 2.4 | | Node.js | NFX `cce422d2972` 소스 빌드 (`package.json` 1.0.1) | | .NET | Uni 8.0.55, limited 3.1.3, full 3.2.2 | | Go | released neo-client v1.8.4; AUTH KEY는 v1.5.0+, 데이터베이스 선택은 v1.8.3+ | NFX cce422d의 Node 기능 일부는 공개 npm 1.0.1 배포 이후 추가되었습니다. registry 패키지의 버전 문자열만으로 동일 기능을 가정하지 말고 배포 산출물의 커밋 출처를 확인하거나 NFX 소스 빌드를 사용합니다. ARRAY와 선택 컬럼 Append의 확인 기준은 NFX `655d1333870313c4951698b89c9a3c9ada11d630`과 병합 커밋 `f756986c4836982723e2aa7727ec05b7e05e9707`입니다. 서버는 Machbase DBMS 8.7.0을, 클라이언트는 해당 변경 이후의 검증된 SDK 산출물을 기준으로 판단합니다. ## SDK 레퍼런스 | SDK | 상세 문서 | |---|---| | Machbase SQLCLI·ODBC | [SQLCLI와 ODBC](../cli-odbc/) | | JDBC | [JDBC](../jdbc/) | | Python | [Python](../python/) | | Node.js / TypeScript | [Node.js / TypeScript](../node-js-typescript/) | | .NET Connector | [.NET Connector](../net-connector/) | | Go | [Go](../go/) | --- title: "11.4 Machbase SQLCLI와 ODBC" url: https://docs.machbase.com/kr/dbms/development-tools-integration/cli-odbc/ language: kr kind: section --- # 11.4 Machbase SQLCLI와 ODBC Machbase SQLCLI는 C/C++ 애플리케이션에서 사용하는 함수 호출 인터페이스(Call-Level Interface)입니다. ODBC 드라이버는 표준 ODBC 애플리케이션에서 사용합니다. 두 인터페이스는 환경·연결·문장 핸들을 사용하는 실행 흐름을 공유하며, SQLCLI에는 고속 Append를 위한 확장 함수가 추가되어 있습니다. ## 선택 기준 | 요구사항 | 인터페이스 | |----------|------------| | Machbase 설치 패키지와 함께 C/C++ 애플리케이션 개발 | SQLCLI | | 범용 ODBC 도구 또는 드라이버 관리자 사용 | ODBC | | Append 확장 API로 대량 입력 | SQLCLI | | 표준 SQL 실행과 결과 조회 | SQLCLI 또는 ODBC | ## 헤더와 라이브러리 설치 디렉터리에서 다음 파일을 확인합니다. ```bash test -f "$MACHBASE_HOME/include/machbase_sqlcli.h" test -f "$MACHBASE_HOME/lib/libmachbasecli_dll.so" ``` Linux 동적 링크 예시는 다음과 같습니다. ```bash gcc cli_quickstart.c -I"$MACHBASE_HOME/include" -L"$MACHBASE_HOME/lib" -lmachbasecli_dll -o cli_quickstart LD_LIBRARY_PATH="$MACHBASE_HOME/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" MACHBASE_PASSWORD='your-password' ./cli_quickstart ``` 운영 빌드는 설치 패키지의 `install/machbase_env.mk`와 플랫폼별 링커 설정을 기준으로 구성합니다. ## 연결 문자열 SQLCLI에서 기본 연결 문자열은 다음 키를 사용합니다. ```text SERVER=127.0.0.1;PORT_NO=5656;UID=APP_USER;PWD=secret;CONNTYPE=1 ``` ODBC 데이터 소스를 사용한다면 DSN, 계정, 비밀번호를 지정합니다. ```text DSN=MACHBASE;UID=APP_USER;PWD=secret ``` 다중 데이터베이스 초기값을 지원하는 드라이버에서는 `DATABASE` 또는 `DBNAME`을 사용할 수 있습니다. 실제 배포 드라이버의 지원 여부를 확인하고, 연결 후 `SELECT CURRENT_DATABASE()`로 선택 결과를 검증합니다. ## 빠른 시작 다음 프로그램은 5656 포트에 연결해 시스템 테이블 조회를 실행한 뒤 모든 핸들을 해제합니다. 비밀번호는 환경 변수로 전달합니다. ```c #include #include #include int main(void) { SQLHENV env = SQL_NULL_HENV; SQLHDBC dbc = SQL_NULL_HDBC; SQLHSTMT stmt = SQL_NULL_HSTMT; char conn[512]; const char *password = getenv("MACHBASE_PASSWORD"); if (password == NULL) { fputs("MACHBASE_PASSWORD is required\n", stderr); return 2; } snprintf(conn, sizeof(conn), "SERVER=127.0.0.1;PORT_NO=5656;" "UID=SYS;PWD=%s;CONNTYPE=1", password); if (SQLAllocEnv(&env) != SQL_SUCCESS) { return 3; } if (SQLAllocConnect(env, &dbc) != SQL_SUCCESS) { SQLFreeEnv(env); return 4; } if (SQLDriverConnect( dbc, NULL, (SQLCHAR *)conn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT) != SQL_SUCCESS) { SQLFreeConnect(dbc); SQLFreeEnv(env); return 5; } if (SQLAllocStmt(dbc, &stmt) != SQL_SUCCESS) { SQLDisconnect(dbc); SQLFreeConnect(dbc); SQLFreeEnv(env); return 6; } if (SQLExecDirect( stmt, (SQLCHAR *)"SELECT COUNT(*) FROM V$TABLES", SQL_NTS) != SQL_SUCCESS) { SQLFreeStmt(stmt, SQL_DROP); SQLDisconnect(dbc); SQLFreeConnect(dbc); SQLFreeEnv(env); return 7; } puts("query succeeded"); SQLFreeStmt(stmt, SQL_DROP); SQLDisconnect(dbc); SQLFreeConnect(dbc); SQLFreeEnv(env); return 0; } ``` 실패 원인을 출력해야 하는 애플리케이션은 `SQLGetDiagRec()` 또는 기존 코드의 `SQLError()`로 SQLSTATE, 네이티브 오류 코드, 메시지를 읽습니다. ## 표준 실행 흐름 1. 환경과 연결 핸들을 할당합니다. 2. `SQLDriverConnect()` 또는 `SQLConnect()`로 연결합니다. 3. 문장 핸들을 할당합니다. 4. `SQLPrepare()`와 `SQLExecute()` 또는 `SQLExecDirect()`로 SQL을 실행합니다. 5. SELECT 결과는 `SQLBindCol()`과 `SQLFetch()`로 읽습니다. 6. 문장, 연결, 환경 순서로 자원을 해제합니다. 입력값은 문자열로 연결하지 말고 `SQLBindParameter()`로 바인딩합니다. nullable 여부는 `SQLDescribeCol()`의 마지막 인자 또는 `SQLColAttribute(..., SQL_DESC_NULLABLE, ...)`로 확인합니다. ## Named Bind Parameter 서버와 드라이버가 이름 기반 매개변수를 지원하면 `:name` 자리표시자를 사용하고 `SQLBindParameterByName()`으로 바인딩할 수 있습니다. 같은 이름이 여러 번 나오면 하나의 값이 모든 위치에 적용됩니다. 공통 제약과 예제는 [Named Bind Parameter](/dbms/reference/sql/syntax/named-bind-parameter-syntax/)를 참고합니다. 지원 여부를 확인하지 못한 환경에서는 표준 `?` 자리표시자와 `SQLBindParameter()`를 사용합니다. ## INSERT 결과 ROWID Standard Edition에서 단일 `INSERT ... VALUES`가 성공한 뒤 생성된 ROWID가 필요하면 다음 방식을 사용합니다. - SQLCLI 확장: `SQLGetGeneratedRowID()` - 표준 ODBC: generated ROWID 전용 표준 API 없음 배치, Append, `INSERT ... SELECT`, UPSERT에서는 같은 반환을 가정하지 않습니다. 자세한 범위는 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고합니다. ## Append 확장 API 고속 입력은 일반 문장과 분리된 Append 흐름을 사용합니다. | 단계 | 주요 함수 | |------|-----------| | 열기 | `SQLAppendOpen()`, 선택 컬럼은 `SQLAppendOpenColumns()`/`W()` | | 단건 입력 | `SQLAppendDataV2()` 또는 지원 버전의 Append 함수 | | 배치 입력 | `SQLAppendBatch()` | | 서버 반영 | `SQLAppendFlush()` | | 오류 콜백 | `SQLAppendSetErrorCallback()` | | 닫기 | `SQLAppendClose()` | Append 행의 컬럼 순서와 타입은 대상 테이블 스키마와 정확히 일치해야 합니다. 문자열, binary, IP, DATETIME, NULL 표현은 설치된 `machbase_sqlcli.h`의 `SQL_APPEND_PARAM` 정의를 기준으로 작성합니다. 오류 콜백에서는 실패한 행과 서버 오류를 기록하되 비밀번호나 원문 민감정보를 로그에 남기지 않습니다. 활성 Append 핸들이 있는 연결은 일반 쿼리와 공유하지 말고, close 결과의 성공·실패 건수를 확인합니다. ## 멀티스레드와 자원 관리 - 스레드마다 연결과 문장을 분리합니다. - 하나의 문장 또는 Append 핸들을 여러 스레드가 동시에 사용하지 않습니다. - 모든 오류 경로에서도 핸들이 역순으로 해제되도록 정리 함수를 둡니다. - 재시도 전에 이전 연결과 Append 상태가 완전히 닫혔는지 확인합니다. - 대량 입력은 성공 건수와 실패 건수를 모두 기록합니다. ## API 세부사항 확인 함수 원형, 상수, 구조체는 설치된 `$MACHBASE_HOME/include/machbase_sqlcli.h`가 해당 라이브러리와 일치하는 기준입니다. 샘플을 다른 버전의 헤더와 혼용하지 말고, 컴파일·링크·5656 연결 테스트를 배포 파이프라인에 포함합니다. ## DECIMAL Append `SQLAppendDataV2()`와 `SQLAppendBatch()`로 `DECIMAL` 또는 `NUMERIC` 값을 입력할 때는 32바이트 opaque 타입 `SQL_APPEND_NUMERIC`과 공개 생성 함수를 사용합니다. 내부 바이트를 애플리케이션에서 직접 만들거나 수정하지 않습니다. | 입력 | 함수 | |---|---| | UTF-8 숫자 문자열 | `SQLAppendNumericFromString()` | | signed·unsigned 정수 | `SQLAppendNumericFromInt64()`, `SQLAppendNumericFromUInt64()` | | `SQL_NUMERIC_STRUCT` | `SQLAppendNumericFromSQLNumeric()` | | NULL | `SQLAppendNumericSetNull()` | 정확한 값을 보존하려면 문자열 또는 `SQL_NUMERIC_STRUCT`를 우선 사용합니다. 타입 배열에는 `SQL_APPEND_TYPE_NUMERIC` 또는 `SQL_APPEND_TYPE_DECIMAL`을 지정하고, 대상 컬럼의 전체 자릿수와 소수 자릿수를 기준으로 overflow와 반올림을 확인하십시오. ## ARRAY와 선택 컬럼 Append Machbase DBMS 8.7.0은 `SQL_MACHBASE_ARRAY_DESC`를 사용한 typed ARRAY 조회·bind와 `SQL_MACHBASE_SPARSE_ARRAY_DESC`를 사용한 희소 입력을 지원합니다. 일반 Open에서도 ARRAY 컬럼에 희소 디스크립터를 전달할 수 있습니다. ```c SQLAppendOpen(statement, (SQLCHAR *)"ARRAY_APPEND_FULL_EXAMPLE", 0); row[0].mLong = 1; row[1].mVar.mData = &sparse; row[1].mVar.mLength = SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH; SQLAppendDataV3(statement, row, 2); SQLAppendClose(statement, &success, &failure); ``` 위 코드는 `ID LONG, A INT32[4]` 테이블의 입력 순서를 따릅니다. 연결·디스크립터·버퍼 준비와 오류 처리를 포함한 [일반 Open 전체 예제](../data-input-load-export/array-append/#c-full-open)를 먼저 확인하십시오. 구형 `SQLAppendData(void *[])`에 디스크립터를 넘기는 방식과는 다릅니다. 일부 컬럼이나 고정 ARRAY 요소만 선택할 때는 `SQLAppendOpenColumns()` 또는 `SQLAppendOpenColumnsW()`를 사용합니다. ```c SQLCHAR *targets[] = { (SQLCHAR *)"ID", (SQLCHAR *)"CHANNELS[0]", (SQLCHAR *)"CHANNELS[3]", NULL }; SQLAppendOpenColumns(statement, (SQLCHAR *)"SENSOR_ARRAY", targets, 0); ``` ARRAY 요소 대상과 희소 디스크립터의 위치는 0부터 시작하는 인덱스입니다. 컬럼명 목록은 마지막 원소가 `NULL`이어야 합니다. `SQLAppendBatch()`는 ARRAY를 지원하지 않습니다. 디스크립터 정의, 배열 전체 NULL와 요소 NULL 처리, direct ODBC 핸들 제약은 [Sparse ARRAY와 선택 컬럼 Append API](../data-input-load-export/array-append/)를 참고하십시오. --- title: "11.5 JDBC" url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/ language: kr kind: section --- # 11.5 JDBC Machbase JDBC 드라이버는 Java 8을 기준으로 JDBC 4.2 핵심 API를 제공합니다. 표준 JDBC API로 서버에 연결하고 PreparedStatement, 타입 지정 조회와 바인딩, 데이터베이스 메타데이터, 로컬 트랜잭션과 커넥션 풀을 사용할 수 있습니다. | 항목 | 값 | |------|----| | Java 바이트코드 기준 | Java 8 | | 드라이버가 보고하는 JDBC 버전 | 4.2 | | 드라이버 버전 | 3.0.0 | | JDBC URL | `jdbc:machbase:///[database]` | | `Driver.jdbcCompliant()` | `false` | `jdbcCompliant()`의 `false`는 JDBC 4.2 API 지원 여부가 아니라 SQL-92 Entry Level 전체 지원 여부를 나타냅니다. 애플리케이션에서는 필요한 선택 기능을 `DatabaseMetaData`의 기능 메서드로 확인합니다. ## 다중 데이터베이스 URL 경로 또는 `database` 연결 속성으로 초기 데이터베이스를 지정할 수 있습니다. ```java String url = "jdbc:machbase://127.0.0.1:5656/factory_a"; Connection conn = DriverManager.getConnection(url, "APP_A", password); System.out.println(conn.getCatalog()); conn.setCatalog("FACTORY_A"); ``` `getCatalog()`와 `setCatalog()`는 서버 현재 데이터베이스와 동기화됩니다. URL 경로와 설정 속성을 동시에 지정하면 값이 같아야 하며, JDBC 메타데이터에서 카탈로그는 데이터베이스, 스키마는 소유자입니다. pooled 연결은 반환 시 초기 카탈로그로 복원되는지 확인하고, 준비된 문장과 append 핸들은 생성 시점 데이터베이스에 고정된다는 점을 고려합니다. ## 드라이버 설치 ### JAR 파일 사용 Machbase 설치 디렉터리의 `machbase.jar`를 classpath에 추가합니다. ```bash ls -l "$MACHBASE_HOME/lib/machbase.jar" javac -classpath ".:$MACHBASE_HOME/lib/machbase.jar" MyApp.java java -classpath ".:$MACHBASE_HOME/lib/machbase.jar" MyApp ``` JAR에는 `META-INF/services/java.sql.Driver`가 포함되어 있습니다. JDBC 4.0 이후 환경에서는 `Class.forName("com.machbase.jdbc.MachDriver")`를 호출하지 않아도 드라이버가 자동으로 등록됩니다. 기존 애플리케이션의 명시적 호출은 그대로 사용할 수 있습니다. ### Maven ```xml com.machbase machjdbc {{< jdbc_version >}} ``` ### Gradle ```groovy dependencies { implementation 'com.machbase:machjdbc:{{< jdbc_version >}}' } ``` 배포 산출물 버전은 [Maven Central](https://mvnrepository.com/artifact/com.machbase/machjdbc)에서 확인합니다. 드라이버가 런타임 메타데이터로 반환하는 `3.0.0`과 배포 산출물 버전은 서로 다른 버전 체계입니다. ## 서버에 연결 사용자 이름과 비밀번호는 소스 코드에 기록하지 않고 환경 변수나 비밀 관리 시스템으로 전달합니다. ```java import java.sql.Connection; import java.sql.DriverManager; import java.util.Properties; String url = "jdbc:machbase://127.0.0.1:5656/machbasedb"; Properties properties = new Properties(); properties.setProperty("user", "SYS"); properties.setProperty("password", System.getenv("MACHBASE_PASSWORD")); try (Connection connection = DriverManager.getConnection(url, properties)) { // SQL을 실행합니다. } ``` ### 연결 옵션 연결 옵션은 `Properties` 또는 URL 쿼리 문자열로 지정합니다. `randomHost`는 `Properties`에서 지정하거나 다중 호스트 URL의 `^` 구분자를 사용합니다. | 옵션 | 설명 | |------|------| | `user`, `password` | 비밀번호 인증 정보 | | `TIMEZONE` | 세션 타임존. `+0900` 형식을 사용합니다. | | `randomHost` | 호스트 목록에서 첫 연결 대상을 무작위로 선택합니다. | | `maxStatements` | 풀링 연결의 최대 캐시 Statement 수 | | `CONNECTION_TIMEOUT` | 소켓 연결 시간 초과(초). `0`은 제한 없음입니다. | | `SOCKET_TIMEOUT` | 소켓 읽기 시간 초과(초). `0`은 제한 없음입니다. | | `characterEncoding` | 클라이언트 문자 인코딩 | | `AUTH_MODE` | `PASSWORD` 또는 `CHALLENGE` | | `AUTH_SIG_SCHEME` | `ECDSA`, `RSA_PKCS1_V15`, `RSA_PSS` | | `AUTH_KEY_FILE` | PEM 개인키 파일 경로 | ```java String url = "jdbc:machbase://127.0.0.1:5656/machbasedb?TIMEZONE=+0900"; ``` ### 다중 호스트 연결 Machbase 8.7.0 JDBC 드라이버는 하나의 URL에 여러 호스트를 지정할 수 있습니다. | 선택 방식 | 지정 방법 | 동작 | |-----------|-----------|------| | 순차 선택 | 호스트를 `,`로 구분 | URL에 작성한 순서대로 연결을 시도합니다. | | 무작위 시작 | 호스트를 `^`로 구분 | 호스트 목록에서 첫 연결 대상을 무작위로 선택합니다. | | 무작위 시작 | `Properties`에 `randomHost=true` 지정 | `,`로 구분한 목록에서 첫 연결 대상을 무작위로 선택합니다. | 다음 URL은 `db1` 연결에 실패하면 `db2`에 연결을 시도합니다. ```java String url = "jdbc:machbase://db1.example.com:5656,db2.example.com:5656/" + "machbasedb?CONNECTION_TIMEOUT=5"; ``` `^` 구분자를 사용하면 첫 연결 대상을 무작위로 선택합니다. ```java String url = "jdbc:machbase://db1.example.com:5656^db2.example.com:5656/" + "machbasedb?CONNECTION_TIMEOUT=5"; ``` `randomHost` 설정 속성을 사용하려면 `,`로 호스트를 구분합니다. ```java Properties properties = new Properties(); properties.setProperty("randomHost", "true"); String url = "jdbc:machbase://db1.example.com:5656,db2.example.com:5656/" + "machbasedb?CONNECTION_TIMEOUT=5"; ``` - `,`와 `^` 구분자를 하나의 URL에서 함께 사용할 수 없습니다. - 연결 refused, 연결 시간 초과, 소켓 오류 등 연결 단계의 I/O 오류가 발생하면 다음 호스트로 연결을 시도합니다. 모든 호스트가 실패하면 `DriverManager.getConnection()`이 `SQLException`을 반환합니다. - `CONNECTION_TIMEOUT`은 호스트별 연결 시도에 적용됩니다. 따라서 전체 연결 대기 시간은 호스트 수와 각 호스트의 응답 시간에 따라 길어질 수 있습니다. - `SOCKET_TIMEOUT`은 연결된 소켓의 읽기 대기 시간을 제한하며 호스트 선택 순서를 변경하지 않습니다. 다중 호스트 전환은 새 연결 또는 재연결 과정의 소켓 연결에 적용됩니다. 연결이 끊긴 뒤 자동 reconnect가 성공해도 이전 Statement, PreparedStatement와 ResultSet은 재사용하지 않습니다. 진행 중이던 SQL의 성공 여부나 안전한 재실행을 보장하지 않으므로, 활성 트랜잭션에서 연결 오류가 발생하면 연결을 폐기하고 업무의 멱등성 정책에 따라 전체 트랜잭션을 다시 실행합니다. ## AUTH KEY 인증 공개키 기반 challenge 인증에서는 비밀번호 대신 로컬 개인키로 서버 challenge에 서명합니다. ```java Properties properties = new Properties(); properties.setProperty("user", "app_user"); properties.setProperty("AUTH_MODE", "CHALLENGE"); properties.setProperty("AUTH_SIG_SCHEME", "ECDSA"); properties.setProperty( "AUTH_KEY_FILE", "/opt/machbase/keys/app_user_ecdsa.pem"); Connection connection = DriverManager.getConnection( "jdbc:machbase://127.0.0.1:5656/machbasedb", properties); ``` - `AUTH_MODE=CHALLENGE`에서는 `password`를 인증에 사용하지 않습니다. - `AUTH_KEY_FILE`은 필수입니다. - `AUTH_SIG_SCHEME`을 생략하면 키 종류에 맞는 기본 서명 방식을 선택합니다. - POSIX 환경에서는 개인키 파일 권한을 `600`으로 제한합니다. ## 빠른 시작 다음 예제는 LOG 테이블에 값을 입력하고 다시 조회합니다. ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; public class JdbcQuickStart { public static void main(String[] args) throws Exception { Properties properties = new Properties(); properties.setProperty("user", "SYS"); properties.setProperty( "password", System.getenv("MACHBASE_PASSWORD")); try (Connection connection = DriverManager.getConnection( "jdbc:machbase://127.0.0.1:5656/machbasedb", properties); Statement statement = connection.createStatement()) { statement.execute( "CREATE LOG TABLE jdbc_sensor " + "(ts DATETIME, name VARCHAR(40), value DOUBLE)"); try (PreparedStatement insert = connection.prepareStatement( "INSERT INTO jdbc_sensor VALUES (?, ?, ?)")) { insert.setLong(1, System.currentTimeMillis() * 1_000_000L); insert.setString(2, "sensor-1"); insert.setDouble(3, 25.3); insert.executeUpdate(); } try (ResultSet result = statement.executeQuery( "SELECT name, value FROM jdbc_sensor")) { while (result.next()) { System.out.printf("%s %.1f%n", result.getString("NAME"), result.getDouble("VALUE")); } } } } } ``` DATETIME에 epoch 나노초 값을 전달할 때는 `long`을 사용합니다. 예제의 테이블이 이미 존재하면 `CREATE LOG TABLE`을 생략하거나 다른 이름을 사용합니다. ## INSERT 결과 ROWID Standard Edition에서 단일 `INSERT ... VALUES`가 성공하면 JDBC 표준 generated keys API로 입력된 행의 ROWID를 확인할 수 있습니다. ```java String sql = "INSERT INTO jdbc_sensor VALUES (?, ?, ?)"; try (PreparedStatement insert = connection.prepareStatement( sql, Statement.RETURN_GENERATED_KEYS)) { insert.setLong(1, System.currentTimeMillis() * 1_000_000L); insert.setString(2, "sensor-2"); insert.setDouble(3, 26.1); insert.executeUpdate(); try (ResultSet keys = insert.getGeneratedKeys()) { if (keys.next()) { java.sql.RowId rowId = keys.getRowId("ROWID"); } } } ``` 결과는 `ROWID` 컬럼 하나와 최대 한 행으로 구성됩니다. 반환할 ROWID가 없으면 빈 `ResultSet`입니다. 지원 여부는 `DatabaseMetaData.supportsGetGeneratedKeys()`로 확인합니다. 배치, Append, `INSERT ... SELECT`, UPSERT의 차이는 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. ## 버전 확인 ```java import java.sql.DatabaseMetaData; DatabaseMetaData metadata = connection.getMetaData(); System.out.println(metadata.getDriverName()); System.out.println(metadata.getDriverVersion()); System.out.println(metadata.getJDBCMajorVersion()); // 4 System.out.println(metadata.getJDBCMinorVersion()); // 2 ``` ## 관련 문서 | 문서 | 내용 | |------|------| | [PreparedStatement와 타입](./prepared-types/) | 매개변수 메타데이터, 이름 기반 bind, SQLType, NULL과 타입 변환 | | [ResultSet, Statement와 LOB](./resultset-lob/) | typed 조회, 스트림, LOB, 시간 초과와 자원 관리 | | [트랜잭션과 커넥션 풀](./transaction-pooling/) | Standard 로컬 트랜잭션, DataSource와 연결 풀 | | [DatabaseMetaData](./database-metadata/) | 테이블, 컬럼, 키, 인덱스와 기능 조회 | | [Append API](./append-api/) | `MachStatement` 기반 고속 입력 | | [마이그레이션과 문제 해결](./migration-troubleshooting/) | 이전 드라이버 전환, 미지원 기능과 오류 처리 | --- title: "11.5.1 PreparedStatement와 타입" url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/prepared-types/ language: kr kind: page --- # 11.5.1 PreparedStatement와 타입 PreparedStatement는 SQL을 서버에서 prepare한 뒤 파라미터만 바꾸어 반복 실행합니다. Machbase JDBC는 표준 위치 기반 매개변수와 이름 기반 확장 매개변수를 제공합니다. ## ParameterMetaData `PreparedStatement.getParameterMetaData()`로 실행 전에 파라미터 개수와 타입을 확인할 수 있습니다. INSERT, UPDATE와 SELECT에서 동일하게 사용합니다. ```java import java.sql.ParameterMetaData; import java.sql.PreparedStatement; try (PreparedStatement statement = connection.prepareStatement( "INSERT INTO sensor_tx " + "(id, value, created_at) VALUES (?, ?, ?)")) { ParameterMetaData metadata = statement.getParameterMetaData(); for (int index = 1; index <= metadata.getParameterCount(); index++) { System.out.printf( "%d: type=%s precision=%d scale=%d nullable=%d%n", index, metadata.getParameterTypeName(index), metadata.getPrecision(index), metadata.getScale(index), metadata.isNullable(index)); } } ``` 파라미터 인덱스는 1부터 시작합니다. 0 또는 파라미터 개수를 초과하는 인덱스에는 `SQLException`이 발생합니다. precision은 타입에 따라 해석합니다. 숫자 타입에서는 저장 바이트 수가 아니라 JDBC의 10진수 자릿수를 나타냅니다. DATETIME은 `java.sql.Types.TIMESTAMP`로 매핑되지만 데이터베이스 타입 이름은 `DATETIME`입니다. ## Named Bind Parameter `:name` 자리표시자와 `MachPreparedStatement.setObject(String, Object)`는 Machbase 확장 기능입니다. 같은 이름이 여러 번 나오면 해당 이름의 모든 위치에 값이 적용됩니다. ```java import com.machbase.jdbc.MachPreparedStatement; try (MachPreparedStatement statement = (MachPreparedStatement) connection.prepareStatement( "SELECT id FROM sensor_tx " + "WHERE id = :id OR parent_id = :id")) { statement.setObject("id", Integer.valueOf(10)); try (ResultSet result = statement.executeQuery()) { while (result.next()) { System.out.println(result.getInt("ID")); } } } ``` - 이름은 선행 콜론을 포함하거나 생략할 수 있습니다. - 이름은 대소문자를 구분합니다. - 이름 기반 setter와 숫자 인덱스 setter를 한 문장에서 혼용하지 않습니다. - SQL에 없는 이름 또는 named/positional 혼용에는 SQLState `07009`가 발생합니다. - 이름 기반 bind를 지원하지 않는 이전 서버에는 SQLState `0A000`이 발생합니다. 이식성이 필요한 애플리케이션은 JDBC 표준인 `?`와 숫자 인덱스 setter를 사용합니다. 공통 이름 문법은 [Named Bind Parameter](/dbms/reference/sql/syntax/named-bind-parameter-syntax/)를 참고합니다. ## Prepared SELECT 재실행 같은 PreparedStatement로 SELECT를 반복 실행할 수 있습니다. 이전 ResultSet을 닫고 값을 다시 바인딩하면 다음 실행에서 새 ResultSet을 반환합니다. ```java try (PreparedStatement statement = connection.prepareStatement( "SELECT id, name FROM sensor_tx WHERE id = ?")) { ResultSetMetaData metadata = statement.getMetaData(); System.out.println(metadata.getColumnCount()); for (int id = 1; id <= 2; id++) { statement.setInt(1, id); try (ResultSet result = statement.executeQuery()) { while (result.next()) { System.out.println(result.getString("NAME")); } } } } ``` Machbase JDBC는 한 Statement의 multiple open results를 지원하지 않습니다. 각 실행의 ResultSet을 소비하고 닫은 뒤 다음 실행을 시작합니다. prepare 단계의 `ResultSetMetaData`는 Statement가 열려 있는 동안 다시 조회할 수 있습니다. ## JDBC 4.2 SQLType 바인딩 Java 8의 `JDBCType`으로 파라미터의 SQL 타입을 지정할 수 있습니다. ```java import java.math.BigDecimal; import java.sql.JDBCType; import java.sql.Timestamp; try (PreparedStatement statement = connection.prepareStatement( "INSERT INTO sensor_tx " + "(id, name, value, created_at, payload) " + "VALUES (?, ?, ?, ?, ?)")) { statement.setObject(1, Integer.valueOf(1), JDBCType.INTEGER); statement.setObject(2, "sensor-1", JDBCType.VARCHAR); statement.setObject( 3, new BigDecimal("12.3400"), JDBCType.DECIMAL, 4); statement.setObject( 4, Timestamp.valueOf("2026-07-26 10:00:00"), JDBCType.TIMESTAMP); statement.setObject(5, new byte[] {1, 2, 3}, JDBCType.BINARY); statement.executeUpdate(); } ``` 알 수 없는 vendor `SQLType`에는 `SQLFeatureNotSupportedException`이 발생합니다. DECIMAL과 NUMERIC은 `BigDecimal`을 사용하며 지정한 소수 자릿수에 맞게 바인딩합니다. ### SQL NULL `setNull()` 또는 `setObject(index, null, JDBCType)`을 사용하면 대상 타입에 맞는 SQL NULL이 전달됩니다. 다음 타입도 typed NULL을 지원합니다. - `REAL`, `BIT`, `TINYINT`, `BOOLEAN` - `VARBINARY`, `LONGVARBINARY`, `BLOB`, `CLOB` - `LONGVARCHAR` unsigned 파라미터의 NULL은 ParameterMetaData를 기준으로 네이티브 NULL 값으로 변환됩니다. unsigned 최대 데이터 값보다 하나 큰 wire NULL sentinel은 실제 데이터로 저장할 수 없으며, 해당 값을 전달하면 SQLState `22003`이 발생합니다. | Machbase 타입 | Java 타입 | 데이터 범위 | |---------------|-----------|-------------| | `USHORT` | `Integer` | 0~65534 | | `UINTEGER` | `Long` | 0~4294967294 | | `ULONG` | `BigInteger` | 0~18446744073709551614 | NULL을 입력할 때 sentinel 값을 직접 전달하지 말고 `setNull()`을 사용합니다. ## Boolean `setBoolean()`과 `setObject(index, value, JDBCType.BOOLEAN)`은 `true`를 1, `false`를 0으로 전달합니다. 문자열은 대소문자와 관계없이 `true`와 `false`만 허용하며, 그 밖의 값에는 SQLState `22018`이 발생합니다. ## IPv4와 IPv6 IP 주소 컬럼에는 `MachPreparedStatement` 확장 setter를 사용할 수 있습니다. ```java MachPreparedStatement statement = (MachPreparedStatement) connection.prepareStatement( "INSERT INTO net_log(ts, src_ip, dst_ip) VALUES (?, ?, ?)"); statement.setLong(1, System.currentTimeMillis() * 1_000_000L); statement.setIpv4(2, "192.168.1.100"); statement.setIpv6(3, "::1"); statement.executeUpdate(); ``` ## Nullable 메타데이터 `ParameterMetaData.isNullable()`은 `parameterNoNulls`, `parameterNullable` 또는 `parameterNullableUnknown`을 반환합니다. `parameterNullableUnknown`을 NOT NULL로 해석하지 않습니다. SQL별 판정 규칙은 [Nullable 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-nullable-metadata)를 참고합니다. --- title: "11.5.2 ResultSet, Statement와 LOB" url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/resultset-lob/ language: kr kind: page --- # 11.5.2 ResultSet, Statement와 LOB Machbase JDBC ResultSet은 forward-only, 읽기 전용 커서입니다. Connection, Statement와 ResultSet은 try-with-resources로 닫습니다. ```java statement.getResultSetType(); // ResultSet.TYPE_FORWARD_ONLY statement.getResultSetConcurrency(); // ResultSet.CONCUR_READ_ONLY ``` scrollable 또는 updateable ResultSet은 지원하지 않습니다. `next()` 전, 마지막 행 이후, close 이후의 getter 호출과 범위를 벗어난 컬럼 인덱스에는 `SQLException`이 발생합니다. ## 타입 지정 조회 `getObject(index, Class)`와 `getObject(label, Class)`를 지원합니다. ```java import java.math.BigDecimal; import java.sql.Timestamp; try (ResultSet result = statement.executeQuery( "SELECT id, value, created_at FROM sensor_tx")) { while (result.next()) { Integer id = result.getObject("ID", Integer.class); BigDecimal value = result.getObject("VALUE", BigDecimal.class); Timestamp createdAt = result.getObject("CREATED_AT", Timestamp.class); } } ``` | Java 타입 | 일반적인 Machbase 타입 | |-----------|-------------------------| | `String` | CHAR, VARCHAR, TEXT, IPV4, IPV6, JSON | | `Short`, `Integer`, `Long` | 정수 타입 | | `Float`, `Double` | 실수 타입 | | `BigDecimal` | DECIMAL, NUMERIC | | `Boolean` | BOOLEAN | | `Timestamp`, `Date`, `Time` | DATETIME | | `byte[]` | BINARY | | `Blob`, `Clob` | BLOB, CLOB | SQL NULL은 객체 getter에서 Java `null`을 반환합니다. primitive getter는 0 또는 `false`를 반환하며, 바로 뒤의 `wasNull()`로 SQL NULL 여부를 확인합니다. 지원하지 않는 변환과 null 대상 클래스에는 `SQLException`이 발생합니다. Machbase SQL에서 빈 문자열 리터럴 `''`은 SQL `NULL`입니다. 따라서 해당 결과 컬럼의 `ResultSetMetaData.isNullable()`은 `columnNullable`이고 `getObject()`는 `null`을 반환합니다. `''''`는 작은따옴표 한 글자이므로 NULL이 아닌 문자열입니다. unsigned 타입의 기본 객체 매핑은 다음과 같습니다. | Machbase 타입 | `getObject()` 반환 타입 | |---------------|-------------------------| | `USHORT` | `Integer` | | `UINTEGER` | `Long` | | `ULONG` | `BigInteger` | `SHORT`는 `Integer`, 32비트 `FLOAT`는 `Float` 객체로 반환합니다. BOOLEAN 문자열은 `true`와 `false`만 허용합니다. ## 문자와 바이너리 stream `setAsciiStream()`, `setBinaryStream()`과 `setCharacterStream()`은 `int` 길이, `long` 길이와 길이 없는 오버로드를 제공합니다. `setNCharacterStream()`과 `setNString()`은 별도 NCHAR 저장 타입이 아니라 VARCHAR 경로의 별칭입니다. ResultSet에서는 다음 getter를 인덱스 또는 컬럼 이름으로 사용할 수 있습니다. - `getAsciiStream()`, `getBinaryStream()` - `getCharacterStream()`, `getNCharacterStream()` - `getNString()` 길이를 지정한 입력이 선언한 길이보다 짧거나, 길이가 음수이거나, `Integer.MAX_VALUE`를 초과하면 `SQLException`이 발생합니다. 현재 스트림은 클라이언트 메모리에 materialize하므로 일정한 메모리만 사용하는 대용량 스트리밍 용도로 사용하지 않습니다. ## BLOB과 CLOB LOG 테이블의 BLOB/CLOB 컬럼은 표준 `Blob`과 `Clob` 객체로 조회하고 바인딩할 수 있습니다. ```java import java.io.ByteArrayInputStream; import java.io.StringReader; import java.sql.Blob; import java.sql.Clob; try (PreparedStatement insert = connection.prepareStatement( "INSERT INTO event_log (payload, message) VALUES (?, ?)")) { insert.setBlob(1, new ByteArrayInputStream(payload)); insert.setClob(2, new StringReader(message)); insert.executeUpdate(); } try (ResultSet result = statement.executeQuery( "SELECT payload, message FROM event_log")) { while (result.next()) { Blob payloadObject = result.getBlob("PAYLOAD"); Clob messageObject = result.getClob("MESSAGE"); byte[] payloadBytes = payloadObject.getBytes( 1, (int) payloadObject.length()); String messageText = messageObject.getSubString( 1, (int) messageObject.length()); payloadObject.free(); messageObject.free(); } } ``` `Connection.createBlob()`과 `createClob()`으로 mutable 객체를 만들고 `setBytes()`, `setString()`, `setBinaryStream()`, `setCharacterStream()`과 `truncate()`를 사용할 수 있습니다. 사용을 마치면 `free()`를 호출합니다. LOB 위치는 JDBC 표준대로 1부터 시작합니다. 부분 스트림의 전체 요청 범위가 실제 값 안에 있어야 하며 끝을 넘어가면 짧게 잘라 반환하지 않고 SQLState `22003`이 발생합니다. 음수 길이 또는 Java 배열로 표현할 수 없는 길이에는 `HY090`이 발생합니다. `free()` 이후 객체를 다시 사용하면 `SQLException`이 발생합니다. LOB은 전체 값을 클라이언트 메모리에 materialize합니다. 수백 MiB 이상의 값을 일정한 메모리로 처리하는 스트리밍 LOB 구현은 아닙니다. ## ResultSet 메타데이터 `ResultSetMetaData.isNullable()`로 SELECT 결과 컬럼의 NULL 가능 여부를 확인합니다. ```java ResultSetMetaData metadata = result.getMetaData(); int nullable = metadata.isNullable(columnIndex); ``` `columnNullableUnknown`은 NOT NULL을 의미하지 않습니다. 테이블 컬럼의 제약은 `DatabaseMetaData.getColumns()`에서 확인하고 PRIMARY KEY는 `DatabaseMetaData.getPrimaryKeys()`로 별도 조회합니다. ## 행 수와 fetch 설정 JDBC 4.2 large update API는 update 건수를 `long`으로 반환합니다. ```java long count = statement.executeLargeUpdate( "DELETE FROM sensor_tx WHERE id < 100"); long[] counts = statement.executeLargeBatch(); ``` `setLargeMaxRows()`와 `getLargeMaxRows()`도 사용할 수 있습니다. `setMaxRows()` 또는 `setLargeMaxRows()`는 ResultSet에서 가져올 최대 행 수를 제한하지만 Statement의 `fetchSize` 값을 변경하지 않습니다. ## Statement 수명주기 - `closeOnCompletion()`을 설정하면 마지막 ResultSet이 닫힐 때 Statement도 닫힙니다. - 한 Statement의 이전 ResultSet은 재실행 전에 닫습니다. - 커밋은 ResultSet을 닫지만 Statement와 PreparedStatement는 다시 사용할 수 있습니다. - 한 ResultSet의 `next()`와 getter를 여러 스레드에서 동시에 호출하지 않습니다. ## 취소와 query timeout `Statement.cancel()`은 현재 실행 중인 문장을 별도 세션으로 취소합니다. 실행 중인 문장이 없으면 아무 작업도 하지 않으며 PreparedStatement의 bind와 메타데이터는 유지됩니다. `setQueryTimeout(seconds)`이 만료되면 `SQLTimeoutException`과 SQLState `HYT00`이 발생합니다. 같은 Statement는 예외 처리가 끝난 뒤 다음 쿼리에 재사용할 수 있습니다. 이전 실행의 시간 초과 작업은 다음 실행을 취소하지 않습니다. 독립 쿼리를 병렬 실행하려면 같은 Connection을 여러 worker가 공유하지 말고 커넥션 풀에서 worker별 논리 Connection을 대여합니다. 진행 중 fetch를 종료해야 할 때는 다른 스레드에서 `close()`, `cancel()` 또는 `Connection.abort()`를 호출할 수 있습니다. --- title: "11.5.3 트랜잭션과 커넥션 풀" url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/transaction-pooling/ language: kr kind: page --- # 11.5.3 트랜잭션과 커넥션 풀 Machbase JDBC는 Standard Edition의 TRANSACTION 테이블에서 표준 JDBC 로컬 트랜잭션을 지원합니다. Cluster Edition에는 TRANSACTION 테이블이 없으므로 이 페이지의 트랜잭션 기능을 적용하지 않습니다. ## TRANSACTION 테이블 생성 ```sql CREATE TRANSACTION TABLE sensor_tx ( id INTEGER PRIMARY KEY, parent_id INTEGER, name VARCHAR(64), value DECIMAL(20, 4), created_at DATETIME, payload BINARY ); ``` ## commit과 rollback `setAutoCommit(false)`, `commit()`과 `rollback()`을 사용합니다. 애플리케이션에서 SQL `BEGIN`을 직접 전송할 필요가 없습니다. ```java import java.sql.PreparedStatement; import java.sql.SQLException; connection.setAutoCommit(false); try (PreparedStatement statement = connection.prepareStatement( "INSERT INTO sensor_tx (id, value) VALUES (?, ?)")) { statement.setInt(1, 1); statement.setBigDecimal(2, new BigDecimal("10.5000")); statement.executeUpdate(); connection.commit(); } catch (SQLException exception) { try { connection.rollback(); } catch (SQLException rollbackException) { exception.addSuppressed(rollbackException); } throw exception; } ``` `setAutoCommit(false)`는 즉시 `BEGIN`을 보내지 않습니다. 수동 모드의 첫 Statement를 실행할 때 트랜잭션을 시작합니다. 커밋 또는 롤백 뒤에도 auto-commit은 `false`로 유지되며 다음 Statement가 새 트랜잭션을 시작합니다. - `setAutoCommit(true)`로 전환할 때 활성 트랜잭션이 있으면 먼저 커밋합니다. - auto-commit이 `true`일 때 `commit()`이나 `rollback()`을 호출하면 SQLState `25000`이 발생합니다. - Connection을 닫을 때 완료하지 않은 트랜잭션은 롤백됩니다. - 커밋과 롤백은 열린 ResultSet을 닫지만 Statement는 재사용할 수 있습니다. ## 격리 수준과 cursor 지원하는 격리 수준은 `Connection.TRANSACTION_SERIALIZABLE`입니다. 다른 격리 수준을 요청하면 `SQLFeatureNotSupportedException`이 발생합니다. 지원하는 holdability는 `ResultSet.CLOSE_CURSORS_AT_COMMIT`입니다. 커밋 전에 필요한 ResultSet을 소비하거나 커밋 후 쿼리를 다시 실행합니다. ## 테이블 종류별 동작 | 작업 | 수동 트랜잭션 동작 | |------|--------------------------| | TRANSACTION 테이블 DML/SELECT | 트랜잭션에 참여합니다. | | LOG/TAG 테이블 SELECT | 실행할 수 있습니다. | | TRANSACTION 변경 전 첫 LOG DML | 호환 경로에서 auto-commit으로 재실행될 수 있습니다. | | 독립 TAG DML | 트랜잭션에 참여하며 롤백할 수 있습니다. | | TRANSACTION 변경 후 LOG/TAG DML 또는 DDL | 오류가 발생합니다. | 호환 경로로 auto-commit 재실행된 LOG DML은 이후 롤백 대상이 아닙니다. 롤백이 필요한 데이터는 TRANSACTION 테이블을 사용합니다. 여러 TRANSACTION 테이블에 대한 정상 커밋과 롤백은 지원하지만 백엔드 커밋 도중 장애가 발생했을 때 전역 원자성을 보장하지 않습니다. 중요한 원자 작업은 하나의 TRANSACTION 테이블 범위로 설계합니다. ## DataSource `MachDataSource`는 애플리케이션 서버나 프레임워크에 연결 속성을 주입할 때 사용합니다. ```java import com.machbase.jdbc.MachDataSource; import java.sql.Connection; MachDataSource dataSource = new MachDataSource(); dataSource.setUrl("jdbc:machbase://127.0.0.1:5656/machbasedb"); dataSource.setUser("SYS"); dataSource.setPassword(System.getenv("MACHBASE_PASSWORD")); dataSource.setLoginTimeout(10); try (Connection connection = dataSource.getConnection()) { // SQL을 실행합니다. } ``` DataSource는 URL, 사용자, password, login 시간 초과, 로그 writer와 JDBC `Wrapper` 계약을 지원합니다. ## ConnectionPoolDataSource `MachConnectionPoolDataSource`는 물리 연결을 직접 노출하지 않고 논리 Connection을 반환합니다. ```java import com.machbase.jdbc.MachConnectionPoolDataSource; import java.sql.Connection; import javax.sql.PooledConnection; MachConnectionPoolDataSource source = new MachConnectionPoolDataSource(); source.setUrl("jdbc:machbase://127.0.0.1:5656/machbasedb"); source.setUser("SYS"); source.setPassword(System.getenv("MACHBASE_PASSWORD")); PooledConnection pooled = source.getPooledConnection(); try { try (Connection logical = pooled.getConnection()) { // logical connection을 사용합니다. } } finally { pooled.close(); } ``` 한 PooledConnection에서는 논리 핸들 하나만 활성화합니다. 논리 Connection을 닫으면 다음 상태를 초기화한 뒤 `connectionClosed` 이벤트가 한 번 발생합니다. - 완료하지 않은 트랜잭션 롤백 - auto-commit 복원 - URL에서 결정한 초기 카탈로그 복원 - 네트워크 시간 초과 복원 close를 시작한 논리 핸들의 호출이 끝나기 전에 다음 연결 대여 기간을 대여하지 않습니다. 닫힌 Connection, Statement 또는 DatabaseMetaData는 다음 연결을 대여한 요청에서 재사용할 수 없으며 SQLState `08003`이 발생합니다. Statement pooling은 지원하지 않습니다. SQLState 클래스 `08`의 치명적 연결 오류는 물리 연결을 폐기하고 `connectionErrorOccurred`를 발생시킵니다. duplicate 키와 같은 클래스 `23` 오류는 연결 손상이 아니므로 연결 오류 이벤트를 발생시키지 않습니다. ## HikariCP ```java import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:machbase://127.0.0.1:5656/machbasedb"); config.setUsername("SYS"); config.setPassword(System.getenv("MACHBASE_PASSWORD")); config.setMaximumPoolSize(10); config.setMinimumIdle(2); config.setConnectionTimeout(30_000); config.setIdleTimeout(600_000); config.setMaxLifetime(1_800_000); config.addDataSourceProperty("TIMEZONE", "+0900"); try (HikariDataSource dataSource = new HikariDataSource(config); Connection connection = dataSource.getConnection()) { // SQL을 실행합니다. } ``` 논리 Connection은 try-with-resources로 즉시 반환하고 반환한 핸들을 보관하지 않습니다. ## Network timeout `setNetworkTimeout(executor, milliseconds)`는 소켓 읽기 시간 초과를 밀리초 단위로 설정하며 `0`은 제한 없음입니다. 음수 값, null executor 또는 작업을 거부하는 executor에는 `SQLException`이 발생합니다. 실제 네트워크 시간 초과가 만료되면 SQLState 클래스 `08`의 예외가 발생하고 물리 연결은 유효하지 않은 상태가 됩니다. 해당 연결에서 만든 Statement와 ResultSet을 재사용하지 말고 새 연결을 대여합니다. 활성 트랜잭션의 I/O 실패는 자동으로 재실행하지 않습니다. --- title: "11.5.4 DatabaseMetaData" url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/database-metadata/ language: kr kind: page --- # 11.5.4 DatabaseMetaData `Connection.getMetaData()`는 드라이버, 서버, 스키마 객체와 JDBC 기능 정보를 반환합니다. 결과 컬럼은 JDBC 표준 이름과 순서를 사용하므로 숫자 위치보다 컬럼 이름으로 읽는 것을 권장합니다. ```java import java.sql.DatabaseMetaData; DatabaseMetaData metadata = connection.getMetaData(); System.out.println(metadata.getDriverName()); System.out.println(metadata.getDriverVersion()); System.out.println(metadata.getDatabaseProductName()); System.out.println(metadata.getDatabaseProductVersion()); ``` ## 테이블과 VIEW ```java try (ResultSet tables = metadata.getTables( null, null, "%", new String[] {"TABLE", "VIEW"})) { while (tables.next()) { System.out.printf("%s %s%n", tables.getString("TABLE_NAME"), tables.getString("TABLE_TYPE")); } } ``` `getTables()`는 TABLE과 VIEW를 구분합니다. Machbase 세부 테이블 종류는 `REMARKS`에서 확인합니다. 애플리케이션은 특정 순번에 의존하지 말고 표준 컬럼 이름을 사용합니다. ## 컬럼 ```java try (ResultSet columns = metadata.getColumns( null, null, "SENSOR_TX", "%")) { while (columns.next()) { System.out.printf( "%s %s size=%d nullable=%s%n", columns.getString("COLUMN_NAME"), columns.getString("TYPE_NAME"), columns.getInt("COLUMN_SIZE"), columns.getString("IS_NULLABLE")); } } ``` `NULLABLE`은 숫자 상수, `IS_NULLABLE`은 `YES`, `NO` 또는 빈 문자열로 반환됩니다. LOOKUP과 VOLATILE 테이블의 PRIMARY KEY는 명시적인 NOT NULL 절이 없어도 `columnNoNulls`와 `NO`로 반환됩니다. VARCHAR, DATETIME, BINARY, BLOB과 CLOB처럼 수치 속성이 적용되지 않는 컬럼의 `DECIMAL_DIGITS`와 `NUM_PREC_RADIX`는 SQL NULL입니다. `getInt()`의 0만 확인하지 말고 `getObject()` 또는 `wasNull()`로 NULL 여부를 확인합니다. ## PRIMARY KEY와 인덱스 ```java try (ResultSet keys = metadata.getPrimaryKeys( null, null, "SENSOR_TX")) { while (keys.next()) { System.out.printf("%s position=%d%n", keys.getString("COLUMN_NAME"), keys.getShort("KEY_SEQ")); } } try (ResultSet indexes = metadata.getIndexInfo( null, null, "SENSOR_TX", false, false)) { while (indexes.next()) { System.out.printf("%s %s%n", indexes.getString("INDEX_NAME"), indexes.getString("COLUMN_NAME")); } } ``` PRIMARY KEY 여부를 `ResultSetMetaData.isNullable()` 값으로 추정하지 않습니다. `getPrimaryKeys()`와 `getIndexInfo()`를 사용합니다. SELECT 결과의 직접 컬럼에 대한 PK 여부는 Machbase JDBC 확장인 `MachResultSetMetaData.isPrimaryKey(column)`으로 확인할 수 있습니다. ```java import com.machbase.jdbc.MachResultSetMetaData; try (ResultSet result = statement.executeQuery( "SELECT ID, ID + 1 AS ID_EXPR FROM SENSOR_TX")) { MachResultSetMetaData resultMetadata = (MachResultSetMetaData) result.getMetaData(); System.out.println(resultMetadata.isPrimaryKey(1)); // true 또는 false System.out.println(resultMetadata.isPrimaryKey(2)); // expression: false } ``` `getPrimaryKeys()`는 테이블 카탈로그의 PK를 조회하고, `isPrimaryKey()`는 현재 SELECT 결과의 컬럼 메타데이터를 조회합니다. 이전 버전 서버 또는 SDK와 연결한 경우에는 결과 컬럼의 PK 플래그가 `false`로 반환될 수 있습니다. ## 스키마와 타입 정보 다음 메서드는 JDBC 표준 형태의 ResultSet을 반환합니다. - `getSchemas()`, `getCatalogs()`, `getTableTypes()` - `getTypeInfo()` - `getTables()`, `getColumns()` - `getPrimaryKeys()`, `getIndexInfo()` 지원하지 않는 선택적 메타데이터 조회는 null이나 비표준 ResultSet 대신 표준 컬럼을 가진 빈 ResultSet을 반환할 수 있습니다. 기능을 사용하기 전에 기능 메서드를 확인합니다. ```java if (metadata.supportsSavepoints()) { // 지원되는 환경에서만 savepoint를 사용합니다. } ``` ## catalog `Connection.getCatalog()`과 `setCatalog()`은 드라이버가 노출하는 현재 카탈로그 값을 관리합니다. 메타데이터 메서드의 카탈로그 인자는 이 값과 일치하는 요청을 필터링하는 데 사용합니다. ```java String initialCatalog = connection.getCatalog(); connection.setCatalog(initialCatalog); ``` 커넥션 풀에서 논리 Connection을 반환하면 카탈로그는 URL에서 결정한 초기 값으로 복원됩니다. 이전 연결 대여 기간에서 얻은 DatabaseMetaData 객체는 다음 연결 대여 기간에서 재사용하지 않습니다. ## 지원 범위 확인 Machbase JDBC는 실제 지원 범위를 기능에 반영합니다. 예를 들어 트랜잭션 격리 수준, ResultSet 종류, savepoint, generated 키와 multiple open results 지원 여부를 다음과 같이 확인합니다. ```java System.out.println(metadata.supportsTransactions()); System.out.println(metadata.supportsTransactionIsolationLevel( Connection.TRANSACTION_SERIALIZABLE)); System.out.println(metadata.supportsResultSetType( ResultSet.TYPE_FORWARD_ONLY)); System.out.println(metadata.supportsSavepoints()); System.out.println(metadata.supportsGetGeneratedKeys()); System.out.println(metadata.supportsMultipleOpenResults()); ``` `Driver.jdbcCompliant()`이 `false`인 것과 개별 JDBC API의 지원 여부는 별개입니다. 애플리케이션은 필요한 기능을 직접 확인합니다. Standard Edition에서 ROWID를 지원하는 서버와 연결하면 `supportsGetGeneratedKeys()`는 `true`, `getRowIdLifetime()`은 `ROWID_VALID_OTHER`를 반환합니다. 지원하지 않는 서버 또는 Cluster Edition에서는 각각 `false`와 `ROWID_UNSUPPORTED`를 반환합니다. 사용 예제는 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. --- title: "11.5.5 Append API" url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/append-api/ language: kr kind: page --- # 11.5.5 Append API Machbase Append API는 여러 행을 연속 입력하는 대량 입력 API입니다. JDBC에서는 `MachStatement` 확장 메서드를 사용하며, 이 페이지는 LOG 입력 예제를 중심으로 설명합니다. 다른 테이블 타입의 지원 범위는 [SDK 지원표](../../sdk-support-scope/#append-table-type-matrix)를 함께 확인합니다. ## API | 메서드 | 설명 | |--------|------| | `executeAppendOpen(tableName, errorCheckCount)` | Append 세션을 시작하고 컬럼 메타데이터를 반환합니다. | | `executeAppendOpen(tableName, inputColumns, errorCheckCount)` | Machbase DBMS 8.7.0에서 선택 컬럼 또는 ARRAY 요소 대상으로 Append 세션을 시작합니다. | | `executeAppendData(metadata, data)` | 한 행을 전송합니다. | | `executeAppendDataByTime(metadata, time, data)` | 나노초 시간을 지정해 한 행을 전송합니다. | | `executeAppendFlush()` | 대기 중인 응답을 동기화합니다. | | `executeAppendClose()` | Append 세션을 종료합니다. | | `executeSetAppendErrorCallback(callback)` | 행 오류 콜백을 등록합니다. | | `getAppendSuccessCount()` | 성공한 행 수를 반환합니다. | | `getAppendFailureCount()` | 실패한 행 수를 반환합니다. | 공개 `executeAppendData()`는 성공하면 `1`을 반환하고 유효하지 않은 내부 결과에는 `SQLException`을 던집니다. 최종 성공·실패 건수와 콜백도 함께 확인합니다. ## 입력 예제 ```sql CREATE LOG TABLE sensor_data ( time DATETIME, name VARCHAR(40), value DOUBLE ); ``` ```java import com.machbase.jdbc.MachStatement; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.ArrayList; try (MachStatement statement = (MachStatement) connection.createStatement()) { ResultSet appendResult = statement.executeAppendOpen("sensor_data", 100); ResultSetMetaData metadata = appendResult.getMetaData(); statement.executeSetAppendErrorCallback( (errorNumber, errorMessage, rowMessage) -> System.err.printf( "Append error [%05d]: %s%n%s%n", errorNumber, errorMessage, rowMessage)); long baseTime = System.currentTimeMillis() * 1_000_000L; for (int index = 0; index < 10_000; index++) { ArrayList row = new ArrayList<>(); row.add(baseTime + index); row.add("sensor-" + (index % 10)); row.add(20.0 + index * 0.001); int result = statement.executeAppendData(metadata, row); if (result != 1 && result != 2) { throw new SQLException( "Append failed at row " + index); } } statement.executeAppendFlush(); statement.executeAppendClose(); appendResult.close(); System.out.printf("success=%d failure=%d%n", statement.getAppendSuccessCount(), statement.getAppendFailureCount()); } ``` ## ARRAY와 선택 컬럼 희소 ARRAY 입력에는 컬럼 선택이 필수가 아닙니다. 일반 `executeAppendOpen(tableName, errorCheckCount)`으로 열고 반환된 메타데이터에 맞춰 `MachSparseArray`를 행의 ARRAY 값으로 전달합니다. ```java ResultSet opened = statement.executeAppendOpen("ARRAY_APPEND_FULL_EXAMPLE", 0); ``` 연결·입력·Close·조회까지의 [일반 Open 예제](../../data-input-load-export/array-append/#jdbc-full-open)를 먼저 확인하십시오. `ID`와 ARRAY 컬럼을 선언 순서대로 전달하며 자동 `_arrival_time`은 행에 추가하지 않습니다. Machbase DBMS 8.7.0에서는 `executeAppendOpen()` 오버로드에 컬럼명이나 `ARRAY_COLUMN[position]`을 전달할 수 있습니다. ```java ResultSet appendResult = statement.executeAppendOpen( "sensor_array", new String[] {"ID", "CHANNELS[0]", "CHANNELS[3]"}, 0); ``` 행마다 다른 ARRAY 위치를 입력할 때는 `MachConnection.createSparseArrayOf()`로 `MachSparseArray`를 생성합니다. map 키는 0부터 시작하며 빈 맵은 모든 요소가 NULL인 ARRAY입니다. Java `null`은 배열 전체 NULL입니다. ```java Map entries = new HashMap(); entries.put(Integer.valueOf(1), Integer.valueOf(200)); entries.put(Integer.valueOf(3), Integer.valueOf(400)); MachSparseArray sparse = connection.createSparseArrayOf( "INT32", 4, entries); ``` 밀집 ARRAY의 조회와 prepared 입력에는 `java.sql.Array`, `Connection.createArrayOf()`와 `PreparedStatement.setArray()`를 사용합니다. 전체 예제와 대상 충돌 규칙은 [Sparse ARRAY와 선택 컬럼 Append API](../../data-input-load-export/array-append/)를 참고하십시오. SQL ARRAY 요소 대상과 `MachSparseArray` 위치는 0부터 시작하는 인덱스입니다. JDBC 표준의 매개변수 순번과 `java.sql.Array.getArray(index, count)` slice 인덱스는 기존처럼 1부터 시작하는 인덱스이므로 서로 혼동하지 마십시오. ## DATETIME Append의 DATETIME 값은 epoch 나노초 단위의 `long`으로 전달합니다. ```java long epochNanoseconds = System.currentTimeMillis() * 1_000_000L; ``` `executeAppendDataByTime()`은 별도의 시간 값을 받는 테이블 입력 경로에서 사용합니다. 입력 컬럼 순서와 Java 타입은 `executeAppendOpen()`이 반환한 ResultSetMetaData에 맞춥니다. ## flush와 close 1. `executeAppendOpen()`으로 세션을 시작합니다. 2. `executeAppendData()`를 반복 호출합니다. 3. 중간 확인이 필요하면 `executeAppendFlush()`를 호출합니다. 4. 모든 입력을 보낸 뒤 `executeAppendClose()`를 호출합니다. 5. 성공·실패 건수와 콜백 결과를 확인합니다. 예외가 발생해도 Append 세션과 Statement가 닫히도록 try-with-resources와 `finally`를 사용합니다. 콜백에서는 실패 행을 별도 저장하거나 로깅하고, 무조건적인 재시도로 중복 입력을 만들지 않도록 업무 키를 사용합니다. ## 크기와 사용 범위 ordered append는 프로토콜 패킷 한도를 공유하므로 한 행의 전체 인코딩 크기를 64KiB 미만으로 유지합니다. BLOB/CLOB처럼 큰 값을 입력할 때는 행 크기와 클라이언트 메모리 사용량을 함께 확인합니다. TRANSACTION 테이블의 Append 배치는 SQL 트랜잭션에 포함되지 않고 독립적으로 반영됩니다. 롤백이 필요한 여러 DML은 [JDBC 트랜잭션](../transaction-pooling/)을 사용합니다. --- title: "11.5.6 마이그레이션과 문제 해결" url: https://docs.machbase.com/kr/dbms/development-tools-integration/jdbc/migration-troubleshooting/ language: kr kind: page --- # 11.5.6 마이그레이션과 문제 해결 최신 Machbase JDBC는 Java 8/JDBC 4.2를 기준으로 버전 보고, 메타데이터, 타입 변환, 트랜잭션과 자원 수명주기를 표준 JDBC 계약에 맞춥니다. 이전 동작에 의존하는 애플리케이션은 다음 차이를 확인합니다. ## 이전 드라이버에서 전환 | 영역 | 현재 동작 | 애플리케이션 확인 사항 | |------|-----------|------------------------| | Java/JDBC 기준 | Java 8 바이트코드, JDBC 4.2 보고 | 실행 JDK를 Java 8 이상으로 사용합니다. | | 드라이버 버전 | 드라이버와 메타데이터가 3.0.0 보고 | 버전 판별 로직을 갱신합니다. | | 자동 검색 | JDBC 서비스 프로바이더 제공 | 명시적 `Class.forName()`은 선택 사항입니다. | | ParameterMetaData | JDBC precision, DB 타입 이름과 Java 클래스 반환 | precision의 타입별 의미를 확인하고 저장 바이트 크기로 해석하지 않습니다. | | 트랜잭션 | lazy `BEGIN`, 실제 commit/rollback | Standard TRANSACTION 작업을 명시적으로 완료합니다. | | holdability | `CLOSE_CURSORS_AT_COMMIT` | 커밋 후 ResultSet을 다시 조회합니다. | | DatabaseMetaData | 표준 결과 구조와 지원 기능 반환 | 드라이버 고유의 컬럼 순번 대신 표준 컬럼 이름을 사용합니다. | | 타입 API | typed `getObject()`, `JDBCType`, Boolean, unsigned, LOB | 메타데이터의 Java 클래스로 조회·바인딩합니다. | | 오류 | 잘못된 상태에서 표준 `SQLException` 반환 | SQLState로 오류를 분기합니다. | | 시간 초과 | query/network 시간 초과 지원 | 네트워크 시간 초과 뒤 연결을 폐기합니다. | | 커넥션 풀 | 논리 연결 대여 기간과 상태 초기화 | 닫힌 핸들과 메타데이터를 재사용하지 않습니다. | | generated keys | Standard 단일 INSERT의 ROWID 반환 | `getGeneratedKeys()`의 `ROWID`를 읽습니다. | 이름 기반 bind는 호환 서버에서 사용합니다. 이전 서버가 이름 기반 bind를 지원하지 않으면 SQLState `0A000`이 발생하므로 `?` 위치 기반 매개변수로 전환합니다. ## 미지원 기능 다음 JDBC 선택 기능은 지원하지 않습니다. - savepoint - XA와 분산 트랜잭션 - 저장 프로시저와 CallableStatement 성공 경로 - scrollable 또는 updateable ResultSet - Statement pooling - multiple open results - Struct, Ref, SQLXML과 UDT 타입 매핑 - 별도 NClob 스토리지와 factory - Machbase 전용 RowSet 프로바이더 - JDBC 4.3 sharding과 request boundary API Machbase DBMS 8.7.0과 ARRAY 지원 JDBC 빌드에서는 `java.sql.Array`, `createArrayOf()`와 `setArray()`를 사용할 수 있습니다. 기존 드라이버의 ARRAY 미지원 안내를 적용하지 말고 [ARRAY와 선택 컬럼 Append](../append-api/#array와-선택-컬럼)의 버전과 인덱스 기준을 확인합니다. 미지원 기능은 일반적으로 `SQLFeatureNotSupportedException`과 SQLState `0A000`을 반환합니다. 기능을 호출하기 전에 DatabaseMetaData 기능을 확인합니다. ## `No suitable driver` **증상** `DriverManager.getConnection()`에서 `No suitable driver`가 발생합니다. **확인 및 해결** 1. 실행 classpath에 `machbase.jar`가 있는지 확인합니다. 2. JAR에 `META-INF/services/java.sql.Driver`가 있는지 확인합니다. 3. URL이 `jdbc:machbase://:/machbasedb` 형식인지 확인합니다. 4. 여러 버전의 Machbase JDBC JAR가 동시에 포함되지 않았는지 확인합니다. ## SQLState `0A000` 선택한 기능이나 서버가 해당 API를 지원하지 않습니다. savepoint, scrollable 커서와 XA에는 대체 흐름을 사용합니다. generated keys는 Standard Edition과 ROWID를 지원하는 서버·JDBC 조합에서 사용하며, `DatabaseMetaData.supportsGetGeneratedKeys()`로 확인합니다. 이름 기반 bind에서 발생하면 위치 기반 매개변수인 `?`를 사용합니다. ## commit 이후 ResultSet이 닫힘 정상 동작입니다. Machbase의 트랜잭션 holdability는 `CLOSE_CURSORS_AT_COMMIT`입니다. 커밋 전에 결과를 소비하거나 커밋 후 쿼리를 다시 실행합니다. Statement와 PreparedStatement는 다시 사용할 수 있습니다. ## LOG DML이 rollback되지 않음 수동 트랜잭션에서 TRANSACTION 테이블을 변경하기 전에 실행한 첫 LOG DML은 호환 경로에서 auto-commit으로 재실행될 수 있습니다. 이 입력은 이후 롤백 대상이 아닙니다. 롤백이 필요한 데이터는 TRANSACTION 테이블을 사용합니다. ## 여러 TRANSACTION 테이블을 함께 commit 정상적인 커밋과 롤백은 지원하지만 백엔드 커밋 중 장애가 발생했을 때 여러 TRANSACTION 테이블의 전역 원자성을 보장하지 않습니다. 중요한 원자 작업은 하나의 TRANSACTION 테이블 범위로 설계합니다. ## network timeout 또는 연결 오류 소켓 읽기 시간 초과와 SQLState 클래스 `08` 연결 오류가 발생하면 해당 물리 연결을 재사용하지 않습니다. 커넥션 풀에서 새 연결을 대여하고, 활성 트랜잭션은 업무 멱등성 정책에 따라 처음부터 다시 실행합니다. 커밋 성공 여부를 예외만으로 추정하지 않습니다. ## close한 pool 객체 재사용 논리 Connection을 닫은 뒤 그 Connection에서 얻은 Statement, ResultSet과 DatabaseMetaData를 다음 연결 대여 기간에서 재사용하면 SQLState `08003`이 발생합니다. 각 연결 대여 기간의 객체를 try-with-resources 범위 안에서만 사용합니다. --- title: "11.6 Python" url: https://docs.machbase.com/kr/dbms/development-tools-integration/python/ language: kr kind: section --- # 11.6 Python ## 개요 2.4 패키지 기준입니다. PyPI 패키지명은 `machbaseapi`(소문자)이고, 순수 Python 구현이라 네이티브 바이너리(`.so/.dll/.dylib`)가 필요 없습니다. 기존 `machbase` 사용 흐름은 그대로 유지됩니다. - 패키지 설치명: `machbaseapi` - 기존과 동일하게 `import machbaseAPI` 사용 - DB-API 방식 `connect()`, `cursor()` 지원 - 2.4부터 `cursor(prepared=True)`로 서버 문장을 여러 호출에서 재사용 - `append*`는 `on_ack` 콜백을 추가할 수 있어 ACK 관찰 가능 - `append()`, `appendByTime()`, `appendData()`, `appendDataByTime()`는 타입 리스트를 생략해도 동작합니다. 서버 메타데이터 기반으로 타입을 자동 추론합니다. - 2.3부터 append 행의 마지막 일부 컬럼을 생략하면 append null-bit를 통해 `NULL`로 저장합니다. - TAG 테이블은 `value` 컬럼까지 필수이며, 이후 추가 컬럼과 메타데이터 컬럼은 생략 시 `NULL`로 저장할 수 있습니다. - 커넥션 풀 옵션(`pool_name`, `pool_size`, `pool_reset_session`) 미지원 ## 다중 데이터베이스 `connect(database=...)`로 초기 데이터베이스를 지정할 수 있습니다. current 카탈로그 getter/setter는 없으므로 연결 후 `SELECT CURRENT_DATABASE()`로 확인하고 SQL `USE`로 변경합니다. ```python conn = connect( host='127.0.0.1', port=5656, user='APP_A', password='secret', database='FACTORY_A', ) cur = conn.cursor() cur.execute('SELECT CURRENT_DATABASE()') print(cur.fetchone()) cur.execute('USE FACTORY_B') ``` 기존 호환 `machbase.open()`에는 데이터베이스 인자가 없습니다. multi-database 작업에는 최신 `connect()`를 사용하십시오. 자세한 연결 풀·문장 바인딩 규칙은 [다중 데이터베이스 운영 가이드](/dbms/operations-configuration-recovery/multi-database/#94-python)를 참조하십시오. ## 설치 ### 요구 사항 - `pip`을 사용할 수 있는 Python 3.6 이상 - 접속 가능한 Machbase 서버와 계정 정보(기본 계정 `SYS/MANAGER`, 포트 `5656`) - 2.4는 네이티브 라이브러리 의존성이 없습니다. ### PyPI에서 설치 ```bash pip3 install machbaseapi ``` `pip3`가 PATH에 없다면 `python3 -m pip install machbaseapi` 명령을 사용합니다. ### 설치 패키지에서 오프라인 설치 인터넷에 연결할 수 없는 환경에서는 Machbase 설치 패키지에 포함된 wheel을 설치합니다. ```bash python3 -m pip install \ $MACHBASE_HOME/3rd-party/python3-module/machbaseapi-2.4-py3-none-any.whl ``` 같은 디렉터리의 `machbaseapi-2.4.tar.gz` 소스 배포 파일도 사용할 수 있습니다. 설치 전에 Python 3.6 이상인지 확인합니다. ### 모듈 확인 ```bash python3 - <<'PY' from machbaseAPI import machbase, connect print('machbase 클래스 import:', bool(machbase)) print('connect 함수 존재:', callable(connect)) print('module import:', __import__('machbaseAPI')) PY ``` 위 명령이 성공하면 패키지를 정상적으로 import할 수 있습니다. ## 빠르게 시작하기 다음 DB-API 예제는 샘플 LOG 테이블을 만들고 입력·조회한 뒤 테이블과 연결을 정리합니다. 비밀번호는 환경 변수로 전달합니다. ```python import os from machbaseAPI import connect conn = connect( host=os.getenv('MACH_HOST', '127.0.0.1'), port=int(os.getenv('MACH_PORT', '5656')), user=os.getenv('MACH_USER', 'SYS'), password=os.environ['MACHBASE_PASSWORD'], ) cur = conn.cursor() try: cur.execute( 'CREATE LOG TABLE py_sample ' '(ts DATETIME, device VARCHAR(40), value DOUBLE)' ) cur.execute( "INSERT INTO py_sample VALUES (" "TO_DATE('2026-01-01','YYYY-MM-DD'), 'sensor-1', 20.5)" ) cur.execute('SELECT device, value FROM py_sample') print(cur.fetchall()) finally: cur.execute('DROP TABLE py_sample') cur.close() conn.close() ``` ## 결과 처리 DB-API 커서는 `execute()`, `fetchone()`, `fetchall()`을 제공합니다. 작업이 끝나면 커서와 연결을 닫고, 샘플 객체가 운영 데이터베이스에 남지 않도록 정리합니다. ### INSERT 결과 ROWID Standard Edition에서 DB-API 커서로 단일 `INSERT ... VALUES`를 실행한 뒤 `cursor.lastrowid`에서 입력된 행의 ROWID를 확인할 수 있습니다. ```python cursor.execute( "INSERT INTO orders(item) VALUES(%s)", ("pump",), ) row_id = cursor.lastrowid ``` 값은 임의 정밀도 Python `int`이며 unsigned 64비트 ROWID를 양수로 보존합니다. ROWID가 없는 실행에서는 `None`입니다. `executemany()`, Append, `INSERT ... SELECT`, UPSERT에서는 ROWID를 반환하지 않습니다. 실행 실패 후에도 이전 값을 재사용하지 마십시오. 자세한 조건은 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. ### DB-API 결과의 Nullable 메타데이터 DB-API 커서에서는 `cursor.description[i][6]`의 `null_ok` 값으로 SELECT 결과 컬럼의 NULL 가능 여부를 확인합니다. ```python cursor.execute(sql) for column in cursor.description: name = column[0] null_ok = column[6] print(name, null_ok) ``` | `null_ok` | 의미 | |-----------|------| | `False` | NULL이 될 수 없음 | | `True` | NULL이 될 수 있음 | | `None` | 판정할 수 없음 | `None`은 `NOT NULL`을 의미하지 않으므로 NULL이 발생할 수 있는 것으로 처리합니다. SQL 결과의 판정 규칙은 [Nullable 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-nullable-metadata)를 참고합니다. Machbase SQL에서 `''`은 SQL `NULL`이므로 `null_ok`는 `True`입니다. 다만 Python connector는 문자열 SQL `NULL`을 기존 호환성에 따라 Python 빈 문자열 `""`으로 반환할 수 있습니다. `null_ok`는 컬럼의 NULL 가능성을 설명하며 개별 행이 NULL인지 알려 주지는 않습니다. 개별 행을 구분해야 하면 SQL의 `IS NULL` 조건이나 이를 이용한 CASE 결과를 함께 조회하십시오. ### SELECT 결과의 PRIMARY KEY 메타데이터 Machbase 8.7.0 서버와 해당 버전 SDK를 사용하면 `cursor.column_metadata`의 `is_primary_key`에서 SELECT 결과 직접 컬럼의 PRIMARY KEY 여부를 확인할 수 있습니다. ```python cursor.execute("SELECT ID, VALUE, ID + 1 AS ID_EXPR FROM T_PK") for column in cursor.column_metadata: print(column.name, column.is_primary_key) ``` `cursor.description`의 DB-API 표준 일곱 번째 값(`null_ok`)은 그대로 NULL 가능 여부만 나타냅니다. 표현식·집계식·외부 조인의 NULL 공급 측 컬럼은 PK가 아니므로 `is_primary_key`가 `False`입니다. 이전 버전 서버 또는 SDK와 연결한 경우에는 PK 플래그가 제공되지 않을 수 있습니다. ### Named Bind Parameter Python DB-API 모듈의 `paramstyle`은 `"named"`입니다. `cursor.execute()`와 `cursor.executemany()`에 매핑을 전달하면 `:name` SQL을 서버의 prepare/bind 경로로 실행합니다. ```python from decimal import Decimal from machbaseAPI import connect conn = connect(host="127.0.0.1", port=5656, user="SYS", password="MANAGER") cur = conn.cursor(dictionary=False) cur.execute( """INSERT INTO SENSOR_DATA (ID, NAME, VALUE) VALUES (:id, :name, :value)""", { "id": 600, "name": "python-client", "value": Decimal("52.125000"), }, ) cur.execute( """SELECT ID, NAME FROM SENSOR_DATA WHERE ID = :id OR PARENT_ID = :id""", {"id": 600}, ) ``` `executemany()`는 각 행을 매핑으로 전달합니다. ```python cur.executemany( "INSERT INTO SENSOR_DATA (ID, NAME, VALUE) " "VALUES (:id, :name, :value)", [ {"id": 601, "name": "batch-a", "value": Decimal("1.5")}, {"id": 602, "name": "batch-b", "value": None}, ], ) ``` 서버 Prepared Statement의 수명은 커서 종류와 호출 방식에 따라 다릅니다. | Cursor | 호출 | 서버 문장 재사용 범위 | |--------|------|----------------------------| | 일반 커서 | `execute(sql, params)` | 해당 호출만 | | 일반 커서 | `executemany(sql, rows)` | 해당 호출 내부 | | 준비된 커서 | `execute()` / `executemany()` | 동일한 원본 SQL을 사용하는 후속 호출 | 일반 커서의 `:name`과 매핑은 서버 prepare/bind를 사용하지만 호출이 끝나면 문장을 닫습니다. 여러 호출에서 같은 문장을 재사용하려면 `cursor(prepared=True)`를 사용합니다. 매핑 키는 선행 콜론 없이 지정하며 대소문자를 구분합니다. 같은 이름이 반복되면 한 값을 모든 위치에 적용합니다. 이름 누락, extra 키와 named/positional 혼용은 `ProgrammingError`를 반환합니다. 이전 서버에서 이름 기반 API를 사용하면 SQLSTATE `0A000`의 `NotSupportedError`를 반환합니다. 호환을 위해 `%s`와 `%(name)s` 문법도 유지합니다. 이 두 형식은 클라이언트에서 SQL 리터럴을 렌더링하는 일반 커서의 기존 경로입니다. 준비된 커서에서는 `%s`를 `?`로, `%(name)s`를 `:name`으로 변환하여 서버 prepare/bind 경로로 실행합니다. 공통 이름 문법은 [Named Bind Parameter syntax](../../reference/sql/syntax/named-bind-parameter-syntax/)를 참고하십시오. ## Prepared Cursor (2.4) `connection.cursor(prepared=True)`는 서버 Prepared Statement 하나를 보유하고 동일한 SQL을 여러 번 실행할 때 재사용합니다. 반복 INSERT, 반복 조건 조회와 동일 SQL의 배치 실행에 사용합니다. ```python from machbaseAPI import connect conn = connect( host="127.0.0.1", port=5656, user="SYS", password="MANAGER", ) cur = conn.cursor(dictionary=False, raw=False, prepared=True) sql = "INSERT INTO SENSOR_DATA (ID, NAME, VALUE) VALUES (%s, %s, %s)" cur.execute(sql, (700, "sensor-a", 21.5)) cur.execute(sql, (701, "sensor-b", 22.1)) cur.executemany( sql, [ (702, "sensor-c", 23.0), (703, "sensor-d", None), ], ) cur.close() conn.close() ``` `cursor()`의 관련 인자는 다음과 같습니다. - `dictionary=True`: 조회 결과를 컬럼 이름 기반 dictionary로 반환합니다. - `dictionary=False`: 조회 결과를 tuple로 반환합니다. - `raw=True`: 기존 raw 결과 계약을 유지합니다. - `prepared=True`: 공개 타입인 `MachbasePreparedCursor`를 반환합니다. - `prepared=False`: 기존 일반 커서를 반환하는 기본값입니다. ### Parameter marker 준비된 커서는 Python DB-API 형식과 Machbase 네이티브 형식을 모두 지원합니다. | 공개 자리표시자 | 서버 자리표시자 | Parameter 형태 | |---------------|-------------|----------------| | `%s` | `?` | tuple, list 등의 sequence | | `?` | `?` | tuple, list 등의 sequence | | `%(name)s` | `:name` | dictionary 등의 매핑 | | `:name` | `:name` | dictionary 등의 매핑 | 문자열 리터럴, 따옴표로 묶은 식별자, `--` 주석과 `/* ... */` 주석 안의 자리표시자 모양은 변환하지 않습니다. 한 SQL에서 위치 기반 자리표시자와 이름 기반 자리표시자를 혼용할 수 없습니다. 이름 기반 자리표시자 이름은 영문자, `_`, `$`로 시작하고 이후에는 숫자도 사용할 수 있습니다. 이름 기반 자리표시자는 Machbase 8.7.0 서버와 해당 버전 SDK에서 지원합니다. ```python sql = ( "SELECT ID, NAME FROM SENSOR_DATA " "WHERE ID = %(target)s OR PARENT_ID = %(target)s" ) cur.execute(sql, {"target": 700}) rows = cur.fetchall() ``` ### Statement 재사용 준비된 커서는 원본 SQL 문자열이 이전 호출과 정확히 같을 때 캐시된 서버 문장을 재사용합니다. 공백이나 주석을 포함하여 문자열이 달라지면 기존 문장을 해제하고 새 문장을 준비합니다. ```python insert_cur = conn.cursor(prepared=True) select_cur = conn.cursor(prepared=True) ``` 커서 하나는 서버 문장 하나만 보유합니다. 여러 SQL을 각각 계속 재사용하려면 위와 같이 SQL별 준비된 커서를 생성합니다. `executemany()`가 끝난 뒤에도 문장은 유지되며 동일 SQL의 후속 `execute()` 또는 `executemany()`에서 재사용됩니다. 빈 매개변수 목록을 전달하면 문장을 준비하거나 실행하지 않고 `0`을 반환합니다. ### 오류와 종료 다음 입력에는 `ProgrammingError`가 발생합니다. - 자리표시자가 있지만 매개변수를 전달하지 않은 경우 - 위치 기반 자리표시자에 매핑을 전달하거나 이름 기반 자리표시자에 sequence를 전달한 경우 - 위치 기반 자리표시자와 이름 기반 자리표시자를 혼용한 경우 - 이름 기반 매개변수 키가 누락되거나 불필요한 키가 추가된 경우 - 자리표시자가 없는 SQL에 비어 있지 않은 매개변수를 전달한 경우 자리표시자가 없는 SQL에는 `None`, 빈 sequence 또는 빈 매핑을 매개변수 없음으로 전달할 수 있습니다. 빈 매핑은 내부적으로 `None`으로 정규화되므로 프로토콜 버전과 관계없이 같은 의미로 처리됩니다. 매개변수 오류가 발생해도 캐시된 문장은 유지되므로 올바른 매개변수로 같은 SQL을 다시 실행할 수 있습니다. 이전 버전 서버에서 이름 기반 매개변수를 사용하면 서버 PREPARE 전에 `NotSupportedError`와 SQLSTATE `0A000`이 발생합니다. 이 오류는 현재 캐시된 문장을 해제하거나 교체하지 않습니다. 구형 서버에서는 위치 기반 자리표시자를 사용합니다. `cursor.close()`는 캐시된 서버 문장을 해제합니다. 같은 커서를 두 번 닫아도 안전하며, 연결이 먼저 닫힌 경우에는 네트워크 요청 없이 로컬 상태만 정리합니다. 닫힌 준비된 커서에서 `execute()`, `executemany()` 또는 fetch API를 호출하면 `InterfaceError`가 발생합니다. 준비된 커서는 SQL의 허용 범위나 Python API의 auto-commit 동작을 변경하지 않습니다. 테이블별 DML 범위는 [지원 범위와 제약](../../reference/support-scope-constraints/)을 참고하십시오. ## 지원 API 매트릭스 | 클래스 | API | 설명 | 반환 | | -- | -- | -- | -- | | `machbase` | `open(host, user, password, port)` | 기본 계정과 포트로 Machbase 서버에 연결합니다. | 성공 시 `1`, 실패 시 `0` | | `machbase` | `openEx(host, user, password, port, conn_str)` | 추가 연결 문자열 속성을 사용해 확장 연결을 수행합니다. | `1` 또는 `0` | | `machbase` | `close()` | 현재 세션을 종료합니다. | `1` 또는 `0` | | `machbase` | `isOpened()` | 핸들이 열려 있는지 확인합니다. | `1` 또는 `0` | | `machbase` | `isConnected()` | 서버와의 연결 상태를 확인합니다. | `1` 또는 `0` | | `machbase` | `execute(sql)` | SQL을 직접 실행합니다. `SELECT`, `WITH`, `DESC`, `DESCRIBE`, `SHOW`는 `select()`로 처리하고, 그 외 SQL은 `exec_direct()`로 실행합니다. | `1` 또는 `0` | | `machbase` | `schema(sql)` | 스키마 관련 명령을 실행합니다. | `1` 또는 `0` | | `machbase` | `tables()` | 모든 테이블의 메타데이터를 조회합니다. | `1` 또는 `0` | | `machbase` | `columns(table_name)` | 특정 테이블의 컬럼 메타데이터를 조회합니다. | `1` 또는 `0` | | `machbase` | `column(table_name)` | 저수준 카탈로그 호출로 컬럼 레이아웃을 가져옵니다. | `1` 또는 `0` | | `machbase` | `statistics(table_name, user='SYS')` | CLI를 통해 테이블 통계를 요청합니다. | `1` 또는 `0` | | `machbase` | `select(sql)` | 스트리밍 `SELECT` 또는 `DESC`를 실행합니다. | `1` 또는 `0` | | `machbase` | `fetch()` | `select()` 호출 이후 다음 행을 가져옵니다. | `(rc, json_str)` | | `machbase` | `selectClose()` | 열린 결과 집합 커서를 닫습니다. | `1` 또는 `0` | | `machbase` | `result()` | 최신 JSON 페이로드를 반환합니다. | JSON 문자열 | | `machbase` | `appendOpen(table_name, types=None)` | 컬럼 타입 코드를 지정하여 Append 프로토콜을 시작합니다. 생략 시 서버 메타데이터로 타입을 사용할 수 있습니다. | `1` 또는 `0` | | `machbase` | `appendOpenColumns(table_name, columns, types=None)` | Machbase DBMS 8.7.0에서 선택 컬럼 또는 ARRAY 요소 대상으로 Append를 시작합니다. | `1` 또는 `0` | | `machbase` | `appendData(table_name, rows_or_types, values=None, format='YYYY-MM-DD HH24:MI:SS', on_ack=None)` | 활성 Append 세션으로 행을 추가합니다. 타입 리스트를 생략하려면 두 번째 인자로 행을 전달합니다. 호출 시 데이터 패킷을 즉시 전송합니다. | `1` 또는 `0` | | `machbase` | `appendDataByTime(table_name, rows_or_types, values=None, format='YYYY-MM-DD HH24:MI:SS', aTimes=None, on_ack=None)` | 명시적 타임스탬프로 행을 추가합니다. 타입 리스트를 생략하려면 두 번째 인자로 행을 전달하고 `aTimes`로 타임스탬프를 지정합니다. 호출 시 데이터 패킷을 즉시 전송합니다. | `1` 또는 `0` | | `machbase` | `appendFlush()` | 이미 전송된 Append 데이터의 아직 받지 않은 서버 응답을 확인하는 동기화 지점입니다. 전송 지연 버퍼를 비우는 API가 아닙니다. | `1` 또는 `0` | | `machbase` | `appendClose()` | Append 세션을 종료합니다. | `1` 또는 `0` | | `machbase` | `append(table_name, rows_or_types, aValues=None, format='YYYY-MM-DD HH24:MI:SS')` | 열기·추가·닫기를 한 번에 처리하는 편의 함수입니다. 타입 리스트를 생략하려면 두 번째 인자로 행을 전달합니다. | `1` 또는 `0` | | `machbase` | `appendByTime(table_name, rows_or_types, aValues=None, format='YYYY-MM-DD HH24:MI:SS', aTimes=None)` | 타임스탬프 인지 Append를 위한 편의 함수입니다. 타입 리스트를 생략하려면 두 번째 인자로 행을 전달하고 `aTimes`로 타임스탬프를 지정합니다. | `1` 또는 `0` | ## DB-API 스타일 API (2.4) | API | 설명 | 반환 | | -- | -- | -- | | `connect(**kwargs)` | DB-API 연결 생성. `host`, `port`, `user`, `password` 등은 키워드 인자로 전달합니다. | `MachbaseConnection` | | `cursor(dictionary=True, raw=False, prepared=False)` | 일반 또는 준비된 커서 생성 | `MachbaseCursor` 또는 `MachbasePreparedCursor` | | `cursor.execute(sql, params=None)` | SQL 실행 | `cursor` | | `cursor.executemany(sql, seq_of_params)` | 같은 SQL을 여러 매핑 또는 sequence로 실행 | 실행 횟수 | | `cursor.fetchone()` | 한 건 조회 | `tuple | dict | None` | | `cursor.fetchmany(size)` | 최대 `size`건 조회 | `list` | | `cursor.fetchall()` | 전체 조회 | `list` | | `cursor.description` | 결과 컬럼 메타데이터. 일곱 번째 값은 `null_ok`입니다. | `tuple | None` | | `cursor.lastrowid` | 성공한 단일 INSERT의 ROWID. 지원되지 않는 입력 방식이나 실패 후에는 `None`입니다. | `int | None` | | `cursor.close()` | 커서 종료 | `None` | | `cursor.rowcount` | 영향 행 수 | `int` | | `connection.append(table, rows, *, types=None, times=None, date_format=..., strict=False, columns=None)` | Append로 행을 추가합니다. `columns`는 선택 컬럼 또는 ARRAY 요소 대상을 지정합니다. | 입력 행 수 | ## 2.3 append 타입 생략과 trailing NULL padding (권장) `append()`와 `appendByTime()`는 타입 리스트를 생략하고 호출할 수 있습니다. 두 번째 인자로 행 집합을 그대로 전달하면 서버 메타데이터 기반으로 처리합니다. 2.3부터는 입력 행의 마지막 일부 컬럼을 생략할 수 있고, 생략된 컬럼은 append null-bit를 통해 `NULL`로 저장됩니다. ```python #!/usr/bin/env python3 from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) try: db.execute('drop table py_append_auto') db.result() ddl = 'create table py_append_auto(ts datetime, tag varchar(16), reading double)' if db.execute(ddl) == 0: raise SystemExit(db.result()) db.result() rows = [ ['2024-01-01 10:00:00', 'node-1', 30.0], ['2024-01-01 10:01:00', 'node-1', 30.5], ] if db.append('PY_APPEND_AUTO', rows) == 0: raise SystemExit(db.result()) print('append without types result:', db.result()) finally: if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` ### DB-API append trailing NULL 예제 `connect().append()`도 같은 trailing `NULL` padding 규칙을 사용합니다. 중간 컬럼을 건너뛰는 위치 기반 입력은 지원하지 않으므로, 중간 값을 `NULL`로 입력하려면 해당 위치에 `None`을 명시합니다. ```python from machbaseAPI import connect conn = connect(host='127.0.0.1', port=5656, user='SYS', password='MANAGER') cur = conn.cursor() try: cur.execute('drop table py_append_null') except Exception: pass cur.execute('create table py_append_null(ts datetime, name varchar(20), value double, note varchar(40))') conn.append('PY_APPEND_NULL', [ ['2024-01-01 10:00:00', 'sensor-1', 12.3], ['2024-01-01 10:00:01', 'sensor-2', None, 'manual null'], ]) cur.execute('select ts, name, value, note from py_append_null order by ts') print(cur.fetchall()) conn.close() ``` 첫 번째 행은 `note` 컬럼을 생략했으므로 `NULL`로 저장됩니다. 두 번째 행은 `value` 위치에 `None`을 명시했으므로 `value`가 `NULL`로 저장됩니다. ### TAG 테이블 append와 metadata NULL 예제 TAG 테이블은 `name`, `time`, `value`에 해당하는 값까지는 반드시 입력해야 합니다. `value` 뒤에 정의한 추가 컬럼 또는 메타데이터 컬럼은 생략할 수 있으며, 생략된 컬럼은 `NULL`로 저장됩니다. ```python from machbaseAPI import connect conn = connect(host='127.0.0.1', port=5656, user='SYS', password='MANAGER') cur = conn.cursor() try: cur.execute('drop table py_tag_append_null') except Exception: pass cur.execute(''' create tag table py_tag_append_null ( name varchar(40) primary key, time datetime basetime, value double summarized, status varchar(20) ) metadata ( site varchar(20), line integer ) ''') conn.append('PY_TAG_APPEND_NULL', [ ['tag-1', '2024-01-01 10:00:00', 12.3], ]) cur.execute('select name, time, value, status, site, line from py_tag_append_null') print(cur.fetchall()) conn.close() ``` 위 예제에서 `status`, `site`, `line`은 모두 `NULL`로 저장됩니다. 반대로 `value`를 생략한 TAG append는 오류로 처리됩니다. ## `machbase` 클래스 호환 API 기존 애플리케이션과의 호환을 위해 유지되는 `machbase` 클래스 사용법을 설명합니다. 신규 코드에는 앞의 DB-API `connect()` 방식을 권장합니다. `getSessionId()`, `count()`, `checkBit()`와 같은 API는 예전 네이티브 패키지에는 있었지만 현재 pure-Python 구현에서는 제공되지 않습니다. 필요 시 2.4 DB-API 예제를 참고하십시오. 각 스크립트에서 호스트·포트·계정 정보를 환경에 맞게 수정하십시오. 모든 예제는 독립 실행이 가능하며 `python3 script.py` 형태로 실행할 수 있습니다. ### 연결 관리 #### machbase.open(), machbase.isOpened(), machbase.isConnected(), machbase.close() ```python #!/usr/bin/env python3 from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() print('isOpened before open:', db.isOpened()) print('isConnected before open:', db.isConnected()) if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) print('isOpened after open:', db.isOpened()) print('isConnected after open:', db.isConnected()) if db.close() == 0: raise SystemExit(db.result()) print('isOpened after close:', db.isOpened()) print('isConnected after close:', db.isConnected()) if __name__ == '__main__': main() ``` #### machbase.openEx() ```python #!/usr/bin/env python3 from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() conn_str = 'APP_NAME=python-demo' if db.openEx('127.0.0.1', 'SYS', 'MANAGER', 5656, conn_str) == 0: raise SystemExit(db.result()) print('connected with openEx:', db.isConnected()) if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` ### DML과 결과 버퍼 #### machbase.execute(), machbase.result() ```python #!/usr/bin/env python3 import json from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) try: rc = db.execute('drop table py_exec_demo') print('drop table rc:', rc) print('drop table result:', db.result()) ddl = 'create table py_exec_demo(id integer, note varchar(32))' if db.execute(ddl) == 0: raise SystemExit(db.result()) print('create table result:', db.result()) for idx in range(2): sql = f"insert into py_exec_demo values ({idx}, 'row-{idx}')" if db.execute(sql) == 0: raise SystemExit(db.result()) print('insert result:', db.result()) if db.execute('select * from py_exec_demo order by id') == 0: raise SystemExit(db.result()) payload = db.result() print('select payload:', payload) rows = json.loads(payload) print('decoded rows:', rows) print('row count:', len(rows)) finally: if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` ### 스트리밍 SELECT 도우미 #### machbase.select(), machbase.fetch(), machbase.selectClose() ```python #!/usr/bin/env python3 import json from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) try: rc = db.execute('drop table py_select_demo') print('drop table rc:', rc) print('drop table result:', db.result()) ddl = 'create table py_select_demo(id integer, value double)' if db.execute(ddl) == 0: raise SystemExit(db.result()) print('create table result:', db.result()) for idx in range(5): sql = f"insert into py_select_demo values ({idx}, {idx * 1.5})" if db.execute(sql) == 0: raise SystemExit(db.result()) print('insert result:', db.result()) if db.select('select id, value from py_select_demo order by id') == 0: raise SystemExit(db.result()) fetched = 0 while True: rc, payload = db.fetch() if rc == 0: break print('fetched row:', json.loads(payload)) fetched += 1 print('fetched rows:', fetched) db.selectClose() finally: if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` ### 스키마 도우미 #### machbase.schema() ```python #!/usr/bin/env python3 from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) try: rc = db.schema('drop table py_schema_demo') print('schema drop rc:', rc) print('schema drop result:', db.result()) ddl = 'create table py_schema_demo(name varchar(20), created datetime)' if db.schema(ddl) == 0: raise SystemExit(db.result()) print('schema create result:', db.result()) finally: if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` ### 메타데이터와 통계 #### machbase.tables(), machbase.columns(), machbase.column(), machbase.statistics() ```python #!/usr/bin/env python3 from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) try: if db.tables() == 0: raise SystemExit(db.result()) print('tables metadata:', db.result()) if db.columns('PY_EXEC_DEMO') == 0: raise SystemExit(db.result()) print('columns metadata:', db.result()) if db.column('PY_EXEC_DEMO') == 0: raise SystemExit(db.result()) print('column metadata:', db.result()) if db.statistics('PY_EXEC_DEMO') == 0: raise SystemExit(db.result()) print('statistics output:', db.result()) finally: if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` ### Append 프로토콜 기본기 `appendOpen()`, `appendData()`, `appendFlush()`, `appendClose()`를 조합하면 행을 효율적으로 스트리밍할 수 있습니다. 2.1 이후에는 타입을 생략하고 `appendOpen()`으로 시작할 수 있습니다. `appendData()`와 `appendDataByTime()`는 호출 시 데이터 패킷을 즉시 전송합니다. `appendFlush()`는 이미 전송된 append 데이터의 아직 받지 않은 서버 응답을 확인하는 동기화 지점입니다. ```python #!/usr/bin/env python3 from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) try: rc = db.execute('drop table py_append_demo') print('drop table rc:', rc) print('drop table result:', db.result()) ddl = 'create table py_append_demo(ts datetime, device varchar(32), value double)' if db.execute(ddl) == 0: raise SystemExit(db.result()) print('create table result:', db.result()) if db.appendOpen('PY_APPEND_DEMO') == 0: raise SystemExit(db.result()) rows = [ ['2024-01-01 09:00:00', 'sensor-a', 21.5], ['2024-01-01 09:05:00', 'sensor-b', 22.1], ] if db.appendData('PY_APPEND_DEMO', rows) == 0: raise SystemExit(db.result()) print('appendData result:', db.result()) if db.appendFlush() == 0: raise SystemExit(db.result()) print('appendFlush result:', db.result()) if db.appendClose() == 0: raise SystemExit(db.result()) print('appendClose result:', db.result()) finally: if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` ### Append 편의 함수 #### machbase.append() ```python #!/usr/bin/env python3 from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) try: db.execute('drop table py_append_auto') db.result() ddl = 'create table py_append_auto(ts datetime, tag varchar(16), reading double)' if db.execute(ddl) == 0: raise SystemExit(db.result()) db.result() values = [ ['2024-01-01 10:00:00', 'node-1', 30.0], ['2024-01-01 10:01:00', 'node-1', 30.5], ] if db.append('PY_APPEND_AUTO', values) == 0: raise SystemExit(db.result()) print('append() result:', db.result()) finally: if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` #### machbase.appendDataByTime(), machbase.appendByTime() ```python #!/usr/bin/env python3 from machbaseAPI.machbaseAPI import machbase def main(): db = machbase() if db.open('127.0.0.1', 'SYS', 'MANAGER', 5656) == 0: raise SystemExit(db.result()) try: db.execute('drop table py_append_time') db.result() ddl = 'create table py_append_time(ts datetime, tag varchar(16), reading double)' if db.execute(ddl) == 0: raise SystemExit(db.result()) db.result() rows = [ ['2024-01-01 11:00:00', 'node-2', 40.1], ['2024-01-01 11:01:00', 'node-2', 40.7], ] epoch_times = [ 1704106800 * 1_000_000_000, 1704106860 * 1_000_000_000, ] if db.appendOpen('PY_APPEND_TIME') == 0: raise SystemExit(db.result()) if db.appendDataByTime('PY_APPEND_TIME', rows, aTimes=epoch_times) == 0: raise SystemExit(db.result()) print('appendDataByTime result:', db.result()) db.appendClose() if db.appendByTime('PY_APPEND_TIME', rows, aTimes=epoch_times) == 0: raise SystemExit(db.result()) print('appendByTime result:', db.result()) finally: if db.close() == 0: raise SystemExit(db.result()) if __name__ == '__main__': main() ``` `aTimes`는 행과 같은 순서의 epoch 나노초 sequence입니다. 초 단위 Unix 타임스탬프를 그대로 전달하지 마십시오. ## ARRAY와 선택 컬럼 Append Machbase DBMS 8.7.0은 ARRAY를 Python `list`로 반환하며 prepared 입력에는 `list` 또는 `tuple`을 사용할 수 있습니다. 요소 NULL은 컬렉션 내부의 `None`, 배열 전체 NULL은 컬럼 자체의 `None`입니다. 컬럼 목록 없이 `connection.append(table, rows)`에 `SparseArray`를 전달할 수도 있습니다. ```python from machbaseAPI import SparseArray sparse = SparseArray(4).set(1, 200).set(3, 400) connection.append("ARRAY_APPEND_FULL_EXAMPLE", [[2, sparse]]) ``` 이 코드는 `ID LONG, A INT32[4]` 테이블과 열린 연결을 전제로 합니다. 전체 NULL·빈 희소 배열과 결과 확인을 포함한 [일반 입력 예제](../data-input-load-export/array-append/#python-full-open)를 참고하십시오. legacy wrapper는 [`appendOpen(table)` 예제](../data-input-load-export/array-append/#python-legacy-full-open)를 제공합니다. 선택 대상은 `connection.append(..., columns=...)`로 지정합니다. 행마다 다른 ARRAY 위치를 입력할 때는 `SparseArray`를 사용합니다. 요소 위치를 지정한 대상과 `SparseArray.set()`의 위치는 0부터 시작하는 인덱스입니다. ```python from machbaseAPI import SparseArray, connect connection = connect( host="127.0.0.1", port=5656, user="SYS", password="MANAGER", ) try: connection.append( "ARRAY_APPEND_EXAMPLE", [[1, 10, 40]], columns=["ID", "A[0]", "A[3]"], ) sparse = SparseArray(4).set(1, 200).set(3, 400) connection.append( "ARRAY_APPEND_EXAMPLE", [[2, sparse]], columns=["ID", "A"], ) finally: connection.close() ``` `SparseArray.clear()`는 요소 수를 유지하면서 모든 요소를 NULL로 되돌립니다. 자세한 NULL 구분, 검증과 기존 호환 API 예제는 [Sparse ARRAY와 선택 컬럼 Append API](../data-input-load-export/array-append/)를 참고하십시오. --- title: "11.7 Node.js / TypeScript" url: https://docs.machbase.com/kr/dbms/development-tools-integration/node-js-typescript/ language: kr kind: section --- # 11.7 Node.js / TypeScript ## 개요 Machbase TypeScript 클라이언트(`@machbase/ts-client`)는 네이티브 바인딩 없이 Machbase Standard Edition 서버에 연결하는 라이브러리입니다. Node.js 애플리케이션에서 SQL 실행, 결과 조회, Prepared Statement 처리, 로그 데이터 Append를 수행할 수 있습니다. 이 문서에서는 설치, 핵심 API, 예제, 테스트 흐름, 동작 특성을 다룹니다. ## 다중 데이터베이스 연결 설정 또는 URL의 `database` 값으로 초기 데이터베이스를 지정합니다. 카탈로그 getter는 제공하지 않으므로 SQL `CURRENT_DATABASE()`와 `USE`로 확인·변경합니다. ```typescript const conn = createConnection({ host: '127.0.0.1', port: 5656, user: 'APP_A', password: 'secret', database: 'FACTORY_A', }); await conn.connect(); const [rows] = await conn.query('SELECT CURRENT_DATABASE()'); console.table(rows); ``` Appender와 준비된 문장은 open/prepare 시점의 데이터베이스에 고정됩니다. 세부 규칙은 [다중 데이터베이스 운영 가이드](/dbms/operations-configuration-recovery/multi-database/#95-nodejs)를 참조하십시오. ## 설치 ### 요구 사항 - Node.js 18 이상(LTS 권장) - 접속 가능한 Machbase 서버(스탠더드 에디션) ### npm에서 설치 패키지 매니저로 설치합니다. ```bash npm install @machbase/ts-client # or yarn add @machbase/ts-client # or pnpm add @machbase/ts-client ``` ### 오프라인 설치 Machbase에서 `.tgz` 패키지를 전달받은 경우: ```bash # example file name; your version may differ npm install ./machbase-ts-client-.tgz ``` ### 설치 확인 ```bash node -e "const { createConnection } = require('@machbase/ts-client'); console.log(typeof createConnection === 'function' ? 'ts-client import ok' : 'ts-client import failed')" ``` > **참고**: 이 클라이언트는 Node.js에서 TCP 소켓을 사용하며, 브라우저용 라이브러리(웹소켓 전송)를 제공하지 않습니다. > NFX `cce422d2972` 소스 트리의 `package.json`은 `@machbase/ts-client` 1.0.1입니다. 다만 > 이름 기반 bind·nullable·PK·ROWID·TRANSACTION 기능 일부는 공개 1.0.1 게시 뒤 같은 소스 > 버전 문자열 아래 추가되었습니다. npm 버전만으로 동일 기능을 가정하지 말고 배포 산출물의 > 커밋 출처를 확인하거나 이 NFX 소스에서 빌드하십시오. > > 이 문서의 기본 계정(`SYS`/`MANAGER`)은 로컬 테스트용 예시입니다. 운영 환경에서는 전용 계정과 비밀번호를 사용하십시오. ## 빠르게 시작하기 아래 예제는 로컬 서버에 연결해 시스템 테이블을 조회하고 세션을 종료합니다. ```typescript // src/example.ts import { createConnection } from '@machbase/ts-client'; const conn = createConnection({ host: process.env.MACH_HOST ?? '127.0.0.1', port: +(process.env.MACH_PORT ?? 5656), user: process.env.MACH_USER ?? 'SYS', password: process.env.MACH_PASS ?? 'MANAGER', }); await conn.connect(); const [rows] = await conn.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]); console.log(rows); await conn.end(); ``` > **트랜잭션 안내:** 서버는 TRANSACTION 테이블에 plain `BEGIN`, `COMMIT`, `ROLLBACK` SQL을 > 지원합니다. 이 클라이언트의 `beginTransaction`, `commit`, `rollback` 편의 메서드는 > 구현되어 있지 않으므로 `execute()`로 SQL을 직접 실행해야 합니다. ## 자주 발생하는 문제 - **ECONNREFUSED** – 서버 상태(`machadmin -e`), 호스트와 포트, 방화벽의 리스너 포트 허용 여부를 확인합니다. 기본 SQL 접속 포트는 5656입니다. - **Authentication failed** – 사용자·비밀번호와 계정의 접속 권한을 확인하십시오. ## API 참조 ### 연결 관리 #### createConnection(config) Machbase 리스너에 연결하고 데이터베이스 세션을 생성합니다. | 매개변수 | 타입 | 기본값 | 설명 | |-----------|------|---------|-------------| | `host` | 문자열 | `127.0.0.1` | Machbase 서버 IP 또는 호스트명 | | `port` | number | `5656` | 리스너 포트 | | `user` | 문자열 | – | 데이터베이스 사용자(기본 `SYS`) | | `password` | 문자열 | – | 비밀번호(기본 `MANAGER`) | | `database` | 문자열 | `data` | 데이터베이스 이름 | | `clientId` | 문자열 | `NPM` | 서버 로그에 표시될 클라이언트 ID | | `showHiddenColumns` | boolean | `false` | 메타데이터에 숨김 컬럼 포함 여부 | | `timezone` | 문자열 | 빈 값 | 선택적 타임존 식별자 | | `connectTimeout` | number | 5000 | 소켓 연결 타임아웃(ms) | | `queryTimeout` | number | 60000 | 명령별 타임아웃(ms) | ```javascript const conn = createConnection({ host: '192.168.1.10', user: 'SYS', password: 'MANAGER' }); await conn.connect(); ``` 소켓 연결 실패, 인증 오류, 핸드셰이크 응답 이상 시 프로미스가 reject됩니다. #### connect() 서버와의 연결을 엽니다. ```javascript await conn.connect(); ``` #### end() 소켓 연결을 종료합니다. `end()` 호출 이후 추가 작업을 시도하면 에러가 발생합니다. ```javascript await conn.end(); ``` ### SQL 실행 #### execute(sql, values?) 결과 집합을 반환하지 않을 수도 있는 명령을 실행합니다. DDL(`CREATE`, `ALTER`, `DROP`)이나 DML(`INSERT`, `UPDATE`, `DELETE`)에 사용하십시오. ```javascript const [create] = await conn.execute('CREATE TRANSACTION TABLE demo (ID INTEGER, NAME VARCHAR(32))'); console.log('Rows affected:', create.affectedRows); // -> 0 for DDL await conn.execute('BEGIN'); const [insert] = await conn.execute("INSERT INTO demo VALUES (1, 'alpha')"); console.log('Rows affected:', insert.affectedRows); // -> 1 await conn.execute('COMMIT'); ``` Standard Edition에서 단일 `INSERT ... VALUES`가 성공하면 실행 결과의 `rowId`에 ROWID가 포함됩니다. 64비트 정밀도를 보존하기 위해 `number`가 아닌 `bigint`로 처리합니다. ```javascript const [result] = await conn.execute( 'INSERT INTO sensor_log(message) VALUES(?)', ['started'] ); if (result.rowId !== undefined) { const rowId = result.rowId; // bigint } ``` ROWID가 없는 실행에는 `rowId` 값이 `undefined`입니다. 배치, Append, `INSERT ... SELECT`, UPSERT의 차이는 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. #### query(sql, values?) 행을 반환하는 쿼리를 실행합니다. 반환값은 `[rows, fields]` 형태의 2요소 튜플입니다. ```javascript const [rows, fields] = await conn.query('SELECT ID, NAME FROM demo ORDER BY ID'); console.table(rows); ``` #### Named Bind Parameter `execute()`, `query()`와 Prepared Statement의 `execute()`에서 배열은 위치 기반 입력, plain object는 이름 기반 입력입니다. ```typescript export type MachbaseNamedBindInput = Record; export type MachbaseExecuteInput = MachbaseBindInput[] | MachbaseNamedBindInput; ``` ```javascript await conn.execute( 'INSERT INTO demo (ID, NAME) VALUES (:id, :name)', { id: 1, name: 'node-client' }, ); const [rows] = await conn.query( 'SELECT ID, NAME FROM demo WHERE ID = :id OR PARENT_ID = :id', { id: 1 }, ); ``` Prepared Statement에서도 객체를 전달합니다. ```javascript const stmt = await conn.prepare( 'SELECT ID, NAME FROM demo WHERE ID = :id' ); try { const [rows] = await stmt.execute({ id: 1 }); } finally { await stmt.close(); } ``` 객체 키는 선행 콜론 없이 지정하며 대소문자를 구분합니다. 반복된 이름에는 같은 값이 적용됩니다. 객체 입력과 `?` 자리표시자를 함께 사용하거나, 필요한 키를 누락하거나, SQL에 없는 키를 전달하면 오류를 반환합니다. | 오류 코드 | 상황 | |---|---| | `ERR_MACHBASE_BIND_MISSING` | 필요한 이름이 누락됨 | | `ERR_MACHBASE_BIND_EXTRA` | SQL에 없는 이름을 전달함 | | `ERR_MACHBASE_BIND_MIXED` | 이름 기반 자리표시자와 anonymous 자리표시자를 혼용함 | | `ERR_MACHBASE_NAMED_BIND_UNSUPPORTED` | 서버가 이름 기반 바인딩을 지원하지 않음 | `fields`의 각 `ColumnMeta` 객체는 `nullable` 속성을 제공합니다. ```typescript import { ColumnNullable } from '@machbase/ts-client'; const [rows, fields] = await conn.query( 'SELECT ID, NAME, ID + 1 AS EXPR_VALUE FROM demo ORDER BY ID' ); for (const field of fields) { if (field.nullable === ColumnNullable.NoNulls) { console.log(field.name, 'NO_NULLS'); } else { console.log(field.name, 'NULL 처리 필요'); } } ``` | 열거형 | 숫자 값 | 의미 | |--------|:------:|------| | `ColumnNullable.NoNulls` | `0` | NULL이 될 수 없음 | | `ColumnNullable.Nullable` | `1` | NULL이 될 수 있음 | | `ColumnNullable.Unknown` | `2` | 판정할 수 없음 | `ColumnNullable.Unknown`은 `NOT NULL`을 의미하지 않습니다. NULL이 발생할 수 있는 것으로 처리합니다. SQL 결과의 판정 규칙은 [Nullable 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-nullable-metadata)를 참고합니다. Machbase SQL에서 `''`은 SQL `NULL`이므로 해당 `field.nullable`은 `ColumnNullable.Nullable`이고 결과 행의 값은 JavaScript `null`입니다. 반대로 `''''`는 작은따옴표 한 글자이므로 `ColumnNullable.NoNulls`와 문자열 값 `'`을 반환합니다. ### SELECT 결과의 PRIMARY KEY 메타데이터 Machbase 8.7.0 서버와 해당 버전 SDK를 사용하면 `query()` 또는 `execute()`가 반환하는 `fields` 배열의 `isPrimaryKey`에서 직접 컬럼의 PRIMARY KEY 여부를 확인할 수 있습니다. ```ts const [rows, fields] = await conn.query( 'SELECT ID, VALUE, ID + 1 AS ID_EXPR FROM T_PK' ); for (const field of fields) { console.log(field.name, field.isPrimaryKey); } ``` 표현식·집계식·외부 조인의 NULL 공급 측 컬럼은 `false`입니다. 이전 버전 서버 또는 SDK와 연결한 경우에는 PK 플래그가 제공되지 않을 수 있습니다. ### Prepared Statement 사용 #### prepare(sql) 서버에 Prepared Statement를 생성합니다. ```javascript const stmt = await conn.prepare('SELECT NAME FROM demo WHERE ID = ?'); try { const [rows] = await stmt.execute([1]); console.log(rows); // -> [ { NAME: 'alpha' } ] } finally { await stmt.close(); } ``` 반환된 객체에서 제공하는 메서드는 다음과 같습니다. - `execute(parameters?)` – 문을 실행하고 `[rowsOrPacket, fields]`를 반환합니다. - `getColumns()` – 컬럼 메타데이터 캐시를 반환합니다. - `getLastMessage()` – 최근 서버 메시지를 확인합니다. - `getStatementId()` – 내부 Statement ID를 조회합니다. - `close()` – 서버 리소스를 정리합니다. 여러 번 호출해도 안전합니다. `getColumns()`가 반환하는 `ColumnMeta`에도 같은 `nullable` 값이 포함됩니다. ```typescript const stmt = await conn.prepare('SELECT ID, NAME FROM demo WHERE ID = ?'); for (const column of stmt.getColumns()) { console.log(column.name, ColumnNullable[column.nullable]); } ``` #### Prepared Statement Examples **Prepared SELECT 재사용:** ```javascript const select = await conn.prepare('SELECT DEVICE_ID, SENSOR_VALUE FROM sensors WHERE DEVICE_ID = ?'); for (const { id } of samples) { const [rows] = await select.execute([id]); console.log(`selected ${id}:`, rows); } await select.close(); ``` **Prepared Upsert:** ```javascript const upsert = await conn.prepare( 'INSERT INTO devices (DEVICE_ID, SENSOR_VALUE) VALUES (?, ?) ' + 'ON DUPLICATE KEY UPDATE SET SENSOR_VALUE = ?', ); const [result] = await upsert.execute([deviceId, firstValue, firstValue]); console.log('Affected rows:', result.affectedRows); await upsert.close(); ``` **타입 지정 인자와 NULL 처리:** ```javascript await update.execute([ { value: null, type: 'varchar' }, { value: new Date(), type: 'varchar' }, { value: 'sensor-200', type: 'varchar' }, ]); ``` 실행 예제 스크립트는 보통 `npm run build` 후 `dist/examples/` 아래에 생성됩니다. 예제는 일반적으로 `MACHBASE_EXAMPLE_*`, `MACHBASE_SMOKE_*`, 마지막으로 `SYS/MANAGER@127.0.0.1` 순서로 접속 정보를 찾습니다. ### Append API #### appendBatch(table, columns, rows, options?) `appendBatch()`로 **로그 테이블**에 여러 행을 추가합니다. 사용자에게 보이는 컬럼만 전달하면 됩니다(로그 테이블에는 `_arrival_time`, `_rid`가 자동 포함됩니다). ```javascript const appendResult = await conn.appendBatch( 'sensor_log', [ { name: 'ID', type: 'int32' }, { name: 'NAME', type: 'varchar' }, { name: 'VALUE', type: 'float64' }, ], [ [1, 'alpha', 0.5], { values: [2, 'bravo', 1.25], arrivalTime: BigInt(Date.now()) * 1_000_000n }, ], ); console.log('Appended rows:', appendResult.rowsAppended); ``` 지원 컬럼 타입: `int32`, `int64`, `float64`, `varchar`. - `rows`는 값 배열 또는 `{ values, arrivalTime }` 객체 배열을 받을 수 있습니다. `null`은 Machbase 센티널 값으로 자동 인코딩됩니다. - `options`는 `arrivalTime`(기본값 1개) 또는 `arrivalTimes`(행별 배열)를 지정할 수 있습니다. - epoch 나노초를 직접 계산할 때는 먼저 `bigint`로 변환합니다. `number` 곱셈은 안전한 정수 범위를 넘습니다. 반환값은 `{ table, rowsAppended, rowsFailed, message }` 형태입니다. > **팁**: "컬럼 건수 does not match" 오류는 대상 테이블이 로그 테이블이 아니거나, 컬럼 순서가 스키마와 일치하지 않을 때 발생합니다. TAG 테이블에는 `appendOpen()`을 사용하십시오. #### appendOpen(table, columns, options?) 경량 Append 세션을 엽니다. 기본적으로 네이티브 APPEND open/data/close 흐름을 사용하며, 성공한 네이티브 쓰기는 청크별 응답을 반환하지 않습니다. ```javascript const stream = await conn.appendOpen('sensor_log', [ { name: 'ID', type: 'int32' }, { name: 'NAME', type: 'varchar' }, { name: 'VALUE', type: 'float64' }, ]); await stream.append([ [1, 'alpha', 0.5], [2, 'bravo', 1.25], ]); await stream.append({ values: [3, 'charlie', 2.5] }); await stream.close(); ``` 네이티브 Append를 끄고 Prepared Statement 기반으로 강제하려면 `MACHBASE_NATIVE_APPEND=0`을 설정하십시오. 서버가 특정 테이블 타입이나 세션에서 네이티브 Append를 지원하지 않으면 페이사드가 자동으로 Prepared Statement 방식으로 폴백합니다. TAG 테이블의 `DATETIME` 컬럼에는 `Date` 객체 또는 `bigint` epoch 값을 전달하십시오. 희소 ARRAY는 `appendOpen()`의 ARRAY 값으로 전달할 수 있습니다. 현재 `@machbase/ts-client`는 `columns` 인자가 필수이므로, 전체 행을 입력할 때도 테이블의 입력 컬럼을 순서대로 정의합니다. `appendOpen(table)`이나 빈 컬럼 목록을 통한 자동 추론은 지원하지 않습니다. 아래 예제의 `ID`, `A`가 테이블의 전체 입력 컬럼이면 전체 행 입력입니다. ARRAY 안에서 입력할 위치는 각 행의 `SparseArray`가 결정합니다. 연결부터 네 행 입력·Close·조회까지의 [전체 컬럼 정의 예제](../data-input-load-export/array-append/#node-full-columns)를 참고하십시오. Machbase DBMS 8.7.0의 선택 컬럼 Append에서는 `name`에 일반 컬럼 또는 `ARRAY_COLUMN[position]`을 지정합니다. 행마다 다른 위치를 입력할 때는 `SparseArray`를 배열 전체 대상에 전달합니다. 요소 위치를 지정한 대상과 `SparseArray.set()`의 위치는 0부터 시작하는 인덱스입니다. ```javascript const { SparseArray } = require('@machbase/ts-client'); const stream = await conn.appendOpen('array_append_example', [ { name: 'ID', type: 'int64' }, { name: 'A', type: 'int32-array' }, ]); const sparse = new SparseArray(4).set(1, 200).set(3, 400); await stream.append([[2n, sparse]]); await stream.close(); ``` `MACHBASE_NATIVE_APPEND=0`으로 prepared 대체 경로를 강제해도 `SparseArray`를 ARRAY-compatible 값으로 처리합니다. 전체 예제와 NULL 구분은 [Sparse ARRAY와 선택 컬럼 Append API](../data-input-load-export/array-append/)를 참고하십시오. #### append(rows) on an append stream 열린 Append 스트림으로 하나 이상의 행을 전송합니다. ```javascript const frames = await stream.append([ ['S-001', new Date(), 1.0], ['S-002', new Date(Date.now() + 1), 2.0], ]); console.log('frames sent:', frames); ``` 네이티브 모드에서는 최대 처리량을 위해 성공 응답이 생략되며, 오류가 있을 때만 실패 패킷이 반환됩니다. ### Helper Methods #### ping() `SELECT 1 FROM V$TABLES`로 연결 상태를 점검합니다. ```javascript await conn.ping(); ``` #### promise() 익숙한 `.promise()`와 같은 형태의 래퍼를 제공합니다. ```javascript const p = conn.promise(); await p.ping(); const [rows] = await p.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]); ``` #### escape, escapeId, format SQL 문자열을 안전하게 구성하기 위한 유틸리티입니다. ```javascript const safeName = conn.escapeId('table_name'); const safeValue = conn.escape('user input'); ``` ## 테스트 및 진단 ### 스크립트 - `npm run build` – TypeScript 컴파일 - `npm run lint` – `src/`에 ESLint 수행 - `npm run smoke` – 선택적 스모크 테스트(환경변수 없으면 생략) - `npm test` – 통합 스위트(실서버 필요) 1. 로그 테이블 생성 2. 샘플 데이터 INSERT/SELECT 3. 자리기반 바인딩 준비문 시연 4. append 부하 테스트(기본: 5배치 x 200행) 및 건수 검증 5. TRANSACTION 테이블에서 직접 SQL `BEGIN`/`ROLLBACK`/`COMMIT` 동작 확인 6. Machbase 페이사드와 `UPDATE` 제한 동작 검증 샘플 출력: ```text TRANSACTION transaction commit returned 1 row. machbase-facade-basic callback query returned 3 rows. machbase-facade-update-log-fails message: UPDATE is not supported for LOG tables. append-batch progress: batch 4/5 { table: 'TS_CLIENT_IT_...', rowsAppended: 200, rowsFailed: 0 } append-batch final count: 1004 ``` ## 튜토리얼 ### 빠른 시작 (로그 테이블) ```javascript // quickstart-log.js const { createConnection } = require('@machbase/ts-client'); (async () => { const conn = createConnection({ host: '127.0.0.1', port: 5656, user: 'SYS', password: 'MANAGER' }); await conn.connect(); const table = 'JS_LOG_' + Math.random().toString(36).slice(2, 7).toUpperCase(); try { await conn.execute(`CREATE LOG TABLE "${table}" (ID INTEGER, NAME VARCHAR(64), VALUE DOUBLE)`); await conn.execute(`INSERT INTO "${table}" VALUES (1, 'A', 0.5)`); const [rows] = await conn.query(`SELECT * FROM "${table}" ORDER BY ID`); console.table(rows); } finally { await conn.execute(`DROP TABLE "${table}"`); await conn.end(); } })(); ``` ### Prepared Statement 재사용 ```javascript // prepared-reuse.js const { createConnection } = require('@machbase/ts-client'); (async () => { const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' }); await conn.connect(); const table = 'JS_VOL_' + Math.random().toString(36).slice(2, 7).toUpperCase(); try { await conn.execute(`CREATE VOLATILE TABLE "${table}" (ID INTEGER PRIMARY KEY, NAME VARCHAR(64))`); for (let i = 1; i <= 3; i++) await conn.execute(`INSERT INTO "${table}" VALUES (${i}, 'N${i}')`); const stmt = await conn.prepare(`SELECT NAME FROM "${table}" WHERE ID = ?`); try { for (const id of [1, 2, 3]) { const [rows] = await stmt.execute([id]); console.log(id, rows[0]?.NAME); } } finally { await stmt.close(); } } finally { await conn.execute(`DROP TABLE "${table}"`); await conn.end(); } })(); ``` ### 로그 테이블 배치 Append ```javascript // append-batch.js const { createConnection } = require('@machbase/ts-client'); (async () => { const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' }); await conn.connect(); const table = 'JS_LOGAPP_' + Math.random().toString(36).slice(2, 7).toUpperCase(); try { await conn.execute(`CREATE LOG TABLE "${table}" (ID INTEGER, NAME VARCHAR(64), VALUE DOUBLE)`); const result = await conn.appendBatch( table, [ { name: 'ID', type: 'int32' }, { name: 'NAME', type: 'varchar' }, { name: 'VALUE', type: 'float64' }, ], [[1, 'X', 0.5], [2, 'Y', 1.25]], ); console.log(result); } finally { await conn.execute(`DROP TABLE "${table}"`); await conn.end(); } })(); ``` ### TAG 테이블 스트리밍 Append ```javascript // append-tag-stream.js const { createConnection } = require('@machbase/ts-client'); (async () => { const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' }); await conn.connect(); const table = 'JS_TAG_' + Math.random().toString(36).slice(2, 7).toUpperCase(); try { await conn.execute(`CREATE TAG TABLE "${table}" (name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED)`); const stream = await conn.appendOpen(table, [ { name: 'NAME', type: 'varchar' }, { name: 'TIME', type: 'int64' }, { name: 'VALUE', type: 'float64' }, ]); const now = Date.now(); await stream.append([ ['T-0001', new Date(now), 1.0], ['T-0002', new Date(now + 1), 2.0], ]); await stream.close(); const [rows] = await conn.query(`SELECT COUNT(*) AS CNT FROM "${table}"`); console.log('count', rows[0]?.CNT); } finally { await conn.execute(`DROP TABLE "${table}"`); await conn.end(); } })(); ``` > 네이티브 모드는 기본 활성화입니다. 비활성화하려면 `MACHBASE_NATIVE_APPEND=0`을 설정하십시오. 성공 시 청크별 응답은 생략되고, 오류만 실패 응답으로 전달됩니다. ### Promise 래퍼와 Ping ```javascript // promise-and-ping.js const { createConnection } = require('@machbase/ts-client'); (async () => { const conn = createConnection({ host: '127.0.0.1', user: 'SYS', password: 'MANAGER' }); await conn.connect(); try { const p = conn.promise(); await p.ping(); // SELECT 1 FROM V$TABLES const [rows] = await p.query('SELECT NAME FROM V$TABLES ORDER BY NAME LIMIT ?', [5]); console.log(rows.map(r => r.NAME)); } finally { await conn.end(); } })(); ``` ## 동작 특성과 한계 ### 트랜잭션 서버 SQL 트랜잭션은 TRANSACTION 테이블에서 동작하지만, 페이사드의 트랜잭션 편의 메서드는 구현되어 있지 않습니다. 동일한 연결에서 SQL을 직접 실행합니다. ```javascript await conn.execute('BEGIN'); await conn.execute('UPDATE orders SET status = ? WHERE order_id = ?', ['DONE', 1001]); await conn.execute('COMMIT'); ``` ### 결과 버퍼링 및 페이지네이션 래퍼의 `query` 메서드는 전체 결과 집합을 버퍼링한 뒤 반환합니다. 대용량 테이블에서는 `ORDER BY … LIMIT` 쿼리나 기본 키 범위를 이용해 직접 페이지를 나누십시오. ### 파라미터 바인딩 배열 입력은 `?` 위치 기반 자리표시자에, 객체 입력은 `:name` 자리표시자에 바인딩합니다. 지원 타입은 `int32`, `int64`, `float64`, `varchar` 등 범용 스칼라 타입입니다. `null`을 전달할 경우 명시적 타입을 함께 지정하십시오. ```javascript { value: null, type: 'varchar' } ``` 이름 규칙과 최대 파라미터 수는 [Named Bind Parameter syntax](../../reference/sql/syntax/named-bind-parameter-syntax/)를 참고하십시오. ### Append API 로그 테이블에는 `appendBatch`를, 점진적 입력에는 `appendOpen`/`append`를 사용합니다. 특정 테이블 타입(예: TAG 테이블)에서 이 입력 방식을 지원하지 않으면 준비된 문 반복 방식으로 자동 대체됩니다. 운영 시에는 데이터를 청크로 나누고 `rowsFailed`를 확인합니다. ### 오류 처리 오류는 기본 `Error` 객체(래퍼 사용 시 `QueryError`)로 전달됩니다. 문제를 진단하려면 `error.message` 또는 `QueryError`의 `code`, `sql` 필드를 확인하십시오. 통합 테스트는 존재하지 않는 테이블 조회와 지원하지 않는 `UPDATE`를 일부러 실행해 오류 메시지가 충분히 설명적인지 확인합니다. ### 테이블 타입별 SQL 유의사항 - **LOG 테이블**은 `UPDATE`를 지원하지 않습니다. - **TAG 테이블**의 데이터 UPDATE는 Standard Edition에서만 지원합니다. 태그 선택 조건과 BASETIME 조건이 필요하며, 태그명·시간축·메타데이터 컬럼은 데이터 UPDATE의 SET 대상이 될 수 없습니다. SET 우변에서 기존 행 컬럼을 참조할 수 없습니다. - **VOLATILE 테이블**의 UPDATE/DELETE는 기본 키 조건을 사용합니다. **LOOKUP 테이블**은 기본 키 조건과 일반 조건식을 모두 지원하며, 단건 변경에는 기본 키 조건이 효율적입니다. ## 모범 사례 1. **항상 연결을 닫기**: `try...finally` 블록으로 `conn.end()`가 호출되도록 보장하십시오. 2. **Prepared Statement 재사용**: 한 번 생성한 후 여러 번 실행하면 성능이 향상됩니다. 3. **배치 입력 활용**: 단건 INSERT 대신 `appendBatch`나 `appendOpen`으로 대량 적재를 수행하십시오. 4. **오류 처리**: DB 작업을 `try...catch`로 감싸고 적절히 로깅합니다. 5. **커넥션 풀 사용**: 운영 환경에서는 커넥션 풀을 도입해 동시 요청을 안정적으로 처리하십시오. 6. **쿼리 파라미터화**: SQL 인젝션을 방지하려면 문자열 결합 대신 바인딩(`?` 플레이스홀더)을 사용하십시오. --- title: "11.8 .NET Connector" url: https://docs.machbase.com/kr/dbms/development-tools-integration/net-connector/ language: kr kind: section --- # 11.8 .NET Connector ## 목차 {#index} * [개요](#overview) * [설치](#install) * [NuGet(통합 8.0.55)](#nuget-unified-connector) * [커넥션 문자열 참고](#connection-string-reference) * [API 레퍼런스](#api-reference) * [사용 예시](#usage-and-examples) * [프로토콜 4.0-full 전체 API](#full-provider-apis-protocol-40-full) ## 개요 {#overview} Machbase는 와이어 프로토콜 2.1~4.0을 지원하는 범용 ADO.NET 프로바이더 **UniMachNetConnector**를 제공합니다. 현재 통합 패키지는 `UniMachNetConnector` 8.0.55이며 `net452`, `net5.0`, `net6.0`, `net7.0`, `net8.0` 타깃을 빌드합니다. 자동 협상은 연결 문자열에 `PROTOCOL=auto` 또는 `auto-full`을 지정했을 때만 동작합니다. ## 설치 {#install} 설치된 Machbase 서버·클라이언트에는 `$MACHBASE_HOME/lib/` 경로에 범용 .NET 프로바이더가 함께 배포됩니다. 표준 Linux 설치에는 예를 들어 `UniMachNetConnector-net50-8.0.55.dll`과 `machNetConnector-40-net50-3.2.2.dll` 같은 프로토콜별 어셈블리가 포함될 수 있습니다. 소스 프로젝트는 필요한 .NET SDK가 있을 때 추가 target-framework flavor도 빌드할 수 있습니다. - **UniMachNetConnector**: 프레임워크에 구애받지 않는 진입점입니다. 소스 빌드 파일 이름은 `UniMachNetConnector-net{452|50|60|70|80}-.dll` 형식이며, 배포 대상 프레임워크에 맞는 파일을 선택합니다. - **레거시 프로토콜 커넥터**: `machNetConnector-XX-net{40|50|60|70|80}-.dll`과 같이 프로토콜별로 나뉜 어셈블리입니다. UniMachNetConnector가 필요 시 로드합니다. 응용 프로그램에서는 대상 프레임워크에 맞는 DLL을 참조하거나, 배포 시 실행 파일과 같은 위치에 함께 배치하면 됩니다. ## 다중 데이터베이스 MachConnector 4.0은 연결 문자열의 `DATABASE` 또는 `DB_NAME`으로 초기 데이터베이스를 선택할 수 있습니다. ```text SERVER=127.0.0.1;PORT_NO=5656;UID=APP_A;PWD=secret;DATABASE=FACTORY_A ``` 표준 `Database` 설정 속성과 `ChangeDatabase()`를 current 카탈로그 전환 API로 보장하지 않으므로 SQL `USE`와 `CURRENT_DATABASE()`를 사용합니다. 연결 풀 반환 시 카탈로그 초기화도 자동으로 가정하지 않습니다. 자세한 제한은 [다중 데이터베이스 운영 가이드](/dbms/operations-configuration-recovery/multi-database/#97-net)를 참조하십시오. ## NuGet로 설치 (통합 커넥터, 8.0.55) {#nuget-unified-connector} 통합 커넥터의 패키지 ID는 `UniMachNetConnector`입니다. 새 프로젝트에서는 DLL 복사 대신 NuGet 패키지 참조 방식을 권장합니다. - 지원 TFM: net452, net5.0, net6.0, net7.0, net8.0 - net5.0 이상 빌드는 self-contained입니다. net452 빌드는 소스 프로젝트 기준 `System.ValueTuple` 4.5.0을 복원합니다. ### 빠른 시작(명령줄) ```bash # 프로젝트 폴더에서 실행 dotnet add package UniMachNetConnector --version 8.0.55 dotnet build ``` 소스(피드)를 명시적으로 제어해야 하면 참조 추가만 하고, 별도로 복원하십시오. ```bash dotnet add package UniMachNetConnector --version 8.0.55 --no-restore # nuget.org 메타데이터를 강제로 갱신 dotnet nuget locals http-cache --clear dotnet restore --no-cache --source https://api.nuget.org/v3/index.json ``` ### Visual Studio - 프로젝트 마우스 오른쪽 클릭 → NuGet 패키지 관리 → 찾아보기 → “UniMachNetConnector” 검색 → 8.0.55 선택 → 설치. ### 프로젝트 파일 예시 ```xml ``` ### 로컬/사내 피드 사용(선택) 사내 레지스트리 또는 폴더 피드를 사용할 경우 다음과 같이 소스를 추가하고 복원합니다. 폴더 피드는 `UniMachNetConnector.8.0.55.nupkg`를 해당 디렉터리에 배치하면 됩니다. ```bash # 1회 설정 dotnet nuget add source /path/to/local-nuget -n mach-local # nuget.org와 병행 복원 dotnet restore --no-cache \ --source /path/to/local-nuget \ --source https://api.nuget.org/v3/index.json ``` 권한 제약이 있는 환경에서는 패키지 캐시 경로를 절대 경로로 지정하십시오. ```bash PKG_DIR="$(pwd)/.nuget-packages"; mkdir -p "$PKG_DIR" NUGET_PACKAGES="$PKG_DIR" dotnet restore --no-cache --source /path/to/local-nuget NUGET_PACKAGES="$PKG_DIR" dotnet run --no-restore ``` > 팁: 게시 직후 NU1102(지정 버전을 찾지 못함)나 “incompatible with 'all' frameworks”가 보이면 보통 인덱싱/캐시 이슈입니다. `dotnet nuget locals http-cache --clear` 후 `--no-cache`로 복원하면 해결됩니다. 패키지는 net452 및 net5.0~net8.0을 지원합니다. ### 최소 사용 예시 ```csharp using System; using Mach.Data.MachClient; var password = Environment.GetEnvironmentVariable("MACHBASE_PASSWORD") ?? throw new InvalidOperationException("MACHBASE_PASSWORD is required"); var cs = $"SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD={password};PROTOCOL=4.0-full"; using var conn = new MachConnection(cs); conn.Open(); using var cmd = new MachCommand("SELECT COUNT(*) FROM V$TABLES", conn); var count = Convert.ToInt64(cmd.ExecuteScalar()); Console.WriteLine($"Tables: {count}"); ``` ## 커넥션 문자열 참고 {#connection-string-reference} 커넥션 문자열의 각 항목은 세미콜론(`;`)으로 구분합니다. 표의 한 행에 표시된 키워드는 서로 동일한 의미를 갖습니다. | 키워드 | 설명 | 예시 | 기본값 | |----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------|--------------------------------------------------|---------| | `DSN`, `SERVER`, `HOST` | 호스트명 또는 IP 주소 | `SERVER=127.0.0.1` | 없음 | | `PORT`, `PORT_NO` | 수신 포트 | `PORT=5656` | `5656` | | `USERID`, `USERNAME`, `USER`, `UID` | 사용자 이름 | `UID=SYS` | `SYS` | | `PASSWORD`, `PWD` | 비밀번호 | `PWD=manager` | 없음 | | `CONNECT_TIMEOUT`, `ConnectionTimeout`, `connectTimeout` | 커넥션 타임아웃(밀리초) | `CONNECT_TIMEOUT=10000` | `60000` | | `COMMAND_TIMEOUT`, `CommandTimeout`, `commandTimeout` | 명령별 타임아웃(밀리초) | `COMMAND_TIMEOUT=50000` | `60000` | | `PROTOCOL`, `ProtocolVersion`, `MachProtocol` | 선호하는 와이어 프로토콜 (`2.1`, `3.0`, `4.0`, `4.0-full`, `auto`, `auto-full` 등). 입력하지 않으면 `4.0`을 사용합니다. | `PROTOCOL=auto` | `4.0` | 예시: ```csharp var connectionString = string.Format( "SERVER={0};PORT_NO={1};UID=SYS;PWD=MANAGER;COMMAND_TIMEOUT=50000;PROTOCOL=4.0-full", host, port); ``` ### 프로토콜 자동 감지 (`PROTOCOL=auto`) 서버 버전이 혼재된 환경이라면 `PROTOCOL=auto`를 지정해 UniMachNetConnector가 실행 시 적절한 레거시 프로토콜을 협상하도록 설정할 수 있습니다. 동작 방식은 다음과 같습니다. - `PROTOCOL=auto`는 4.0 → 3.0 → 2.2 → 2.1 순서로 핸드셰이크를 시도하며, 커넥션 문자열에 전달한 호스트·포트·사용자·비밀번호·데이터베이스·`CONNECT_TIMEOUT` 값을 그대로 사용합니다. - `PROTOCOL=auto-full`은 서버 major가 4이면 등록된 `4.0-full` 디스크립터를 선택합니다. 디스크립터가 없는 빌드에서만 limited 4.0을 선택하며, full 연결 실패 후 limited로 자동 재시도하지 않습니다. - `SERVER=hostA:5700,hostB:6000`처럼 여러 호스트를 지정하면 순차적으로 시도하며, 실패 메시지에는 각 호스트/프로토콜 조합이 기록되어 문제 지점을 파악할 수 있습니다. - 자격 증명은 기존 레거시 드라이버와 동일하게 대문자로 변환됩니다. 기본 데이터베이스(`data`)를 사용하지 않는다면 `DATABASE=` 값을 명시하십시오. - `CONNECT_TIMEOUT` 값이 각 감지 라운드 트립에 적용됩니다. 예외 메시지에 `Protocol probe received an invalid response`가 보이면 포트·방화벽·TLS 설정을 다시 확인하십시오. 이미 서버 버전을 알고 있다면 `PROTOCOL=2.1`, `3.0`, `4.0`, `4.0-full`처럼 명시적으로 지정해 자동 감지를 건너뛸 수도 있습니다. ## API 레퍼런스 {#api-reference} {{< callout type="warning" >}} 아래에 명시되지 않은 기능은 아직 구현되지 않았거나 정상적으로 동작하지 않을 수 있습니다.
선언된 API라도 구현하지 않거나 지원하지 않는 기능은 `NotImplementedException` 또는 `NotSupportedException`을 반환할 수 있습니다. 필요한 API가 설치한 프로바이더에 있는지 먼저 확인하십시오. {{< /callout >}} ### MachConnection ```cs public sealed class MachConnection : DbConnection ``` Machbase와의 연결을 담당하는 클래스입니다. `DbConnection`과 동일하게 `IDisposable`을 구현하므로 `Dispose()` 호출이나 `using` 문으로 안전하게 해제할 수 있습니다. #### 생성자 ``` MachConnection(string aConnectionString) ``` 커넥션 문자열을 입력 받아 `MachConnection` 인스턴스를 생성합니다. #### Open ```cs void Open() ``` 커넥션 문자열을 사용해 실제 연결을 수립합니다. #### Close ```cs void Close() ``` 열려 있는 연결을 종료합니다. #### SetConnectAppendFlush ```cs void SetConnectAppendFlush(bool activeFlush) ``` Append 작업 중 자동으로 flush를 수행할지 여부를 설정합니다. #### 필드 | 이름 | 설명 | |--|--| | `State` | `System.Data.ConnectionState` 값을 나타냅니다. | | `StatusString` | 현재 연결이 의존하는 `MachCommand`의 상태 문자열입니다. 내부 로깅용이므로 쿼리 상태 판단 용도로 사용하지 않는 것이 좋습니다. | ### MachCommand ```cs public sealed class MachCommand : DbCommand ``` `MachConnection`을 통해 SQL 명령이나 Append 작업을 실행하는 클래스입니다. `DbCommand`와 마찬가지로 `IDisposable`을 구현합니다. #### 생성자 ```cs MachCommand(string aQueryString, MachConnection aConn) ``` 실행할 쿼리와 연결 객체를 지정해 인스턴스를 생성합니다. ```cs MachCommand(MachConnection aConn) ``` 쿼리가 필요 없는 Append 전용 커맨드를 생성합니다. #### CreateParameter ```cs MachParameter CreateParameter() ``` 새로운 `MachParameter`를 생성합니다. #### AppendOpen ```cs MachAppendWriter AppendOpen( string aTableName, int aErrorCheckCount = 0, MachAppendOption option = MachAppendOption.None) ``` Append 세션을 열고 `MachAppendWriter`를 반환합니다. * `aTableName`: 대상 테이블 이름 * `aErrorCheckCount`: 지정한 레코드 수마다 서버에 전송해 실패 여부를 확인합니다. 즉, 자동 `APPEND-FLUSH` 지점을 설정합니다. * `option`: `None` 또는 `MicroSecTruncated` 옵션을 지정할 수 있습니다. #### AppendData ```cs void AppendData(MachAppendWriter writer, List dataList) ``` 리스트에 있는 값을 순서대로 Append 버퍼에 적재합니다. 각 값의 타입은 테이블 컬럼 타입과 일치해야 하며, 값이 부족하거나 초과하면 예외가 발생합니다. > **참고**: `_arrival_time`을 `ulong`으로 직접 지정할 때는 Machbase가 기대하는 1970-01-01 UTC 기준 나노초 값을 입력해야 합니다. ```cs void AppendDataWithTime( MachAppendWriter writer, List dataList, DateTime arrivalTime) ``` `_arrival_time`을 `DateTime`으로 명시적으로 지정합니다. ```cs void AppendDataWithTime( MachAppendWriter writer, List dataList, ulong arrivalTime) ``` `_arrival_time`을 나노초 단위 `ulong`으로 지정합니다. #### AppendFlush ```cs void AppendFlush(MachAppendWriter writer) ``` 버퍼에 쌓인 데이터를 서버로 전송합니다. 호출 주기를 줄이면 클라이언트 버퍼에 남는 데이터와 전송 지연을 줄일 수 있지만 통신 비용은 증가할 수 있습니다. 호출 성공만으로 디스크 내구성을 판단하지 말고, 서버 처리 결과와 대상 테이블의 내구성 정책을 함께 확인합니다. #### AppendClose ```cs void AppendClose(MachAppendWriter writer) ``` Append 세션을 종료합니다. 내부적으로 `AppendFlush()` 호출 후 프로토콜을 마무리합니다. #### ExecuteNonQuery ```cs int ExecuteNonQuery() ``` 쿼리를 실행하고 영향을 받은 레코드 수를 반환합니다. 주로 `INSERT`, `UPDATE`, `DELETE`, DDL에서 사용합니다. #### RowId ```cs UInt64? RowId ``` Standard Edition에서 단일 `INSERT ... VALUES`가 성공하면 MachConnector 4.0/4.0-full과 Universal .NET의 `ExecuteNonQuery()` 호출 후 입력된 행의 ROWID를 확인할 수 있습니다. ```cs using (var command = new MachCommand( "INSERT INTO orders(item) VALUES('pump')", connection)) { command.ExecuteNonQuery(); ulong? rowId = command.RowId; } ``` 반환할 ROWID가 없으면 `null`입니다. ROWID는 64비트 `RowId`로 읽고, 기존 32비트 `LastInsertedId`는 사용하지 않습니다. 배치와 Append 등의 차이는 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. #### ExecuteScalar ```cs object ExecuteScalar() ``` 쿼리를 실행하고 첫 번째 컬럼 값을 반환합니다. #### ExecuteDbDataReader ```cs DbDataReader ExecuteDbDataReader(CommandBehavior behavior) ``` 쿼리를 실행하고 결과를 순차적으로 읽을 수 있는 `DbDataReader`를 반환합니다. #### 필드 | 이름 | 설명 | |--|--| | `Connection` / `DbConnection` | 현재 연결된 `MachConnection`입니다. | | `ParameterCollection` / `DbParameterCollection` | 바인딩에 사용할 파라미터 컬렉션입니다. | | `CommandText` | 실행할 SQL 문자열입니다. | | `CommandTimeout` | 서버 응답을 기다리는 최대 시간(밀리초)입니다. 값은 `MachConnection` 설정을 따르며 여기서는 조회만 가능합니다. | | `FetchSize` | 서버에서 한 번에 가져올 레코드 수입니다. 기본값은 3000입니다. | | `IsAppendOpened` | Append 세션이 열려 있는지 여부입니다. | | `RowId` | 성공한 단일 INSERT의 64비트 ROWID입니다. 값이 없으면 `null`입니다. | ### MachDataReader ```cs public sealed class MachDataReader : DbDataReader ``` Fetch된 결과를 순차적으로 읽는 리더입니다. `MachCommand.ExecuteDbDataReader()`로 획득한 객체만 사용할 수 있습니다. #### GetName ```cs string GetName(int ordinal) ``` 지정한 인덱스의 컬럼 이름을 반환합니다. #### GetDataTypeName ```cs string GetDataTypeName(int ordinal) ``` Machbase 컬럼 타입 이름을 반환합니다. #### GetFieldType ```cs Type GetFieldType(int ordinal) ``` .NET 측 매핑 타입을 반환합니다. #### GetOrdinal ```cs int GetOrdinal(string name) ``` 컬럼 이름에 해당하는 인덱스를 반환합니다. #### GetValue ```cs object GetValue(int ordinal) ``` 현재 레코드의 값을 `object`로 반환합니다. #### IsDBNull ```cs bool IsDBNull(int ordinal) ``` 해당 컬럼 값이 `NULL`인지 확인합니다. #### GetValues ```cs int GetValues(object[] values) ``` 현재 레코드의 값을 배열에 채워 넣고 채워진 항목 수를 반환합니다. #### GetSchemaTable ```cs DataTable GetSchemaTable() ``` SELECT 결과 컬럼의 스키마 메타데이터를 반환합니다. `AllowDBNull`로 NULL 가능 여부를 확인합니다. 이 동작은 MachConnector40과 MachConnector40-full-API에 동일하게 적용됩니다. ```csharp using var reader = command.ExecuteReader(); DataTable schema = reader.GetSchemaTable(); foreach (DataRow row in schema.Rows) { string columnName = Convert.ToString(row["ColumnName"]); object allowDBNull = row["AllowDBNull"]; if (allowDBNull is bool value && !value) { Console.WriteLine($"{columnName}: NO_NULLS"); } else { // true 또는 DBNull.Value: NULL 처리 필요 Console.WriteLine($"{columnName}: NULL 처리 필요"); } } ``` | `AllowDBNull` | 의미 | |---------------|------| | `false` | NULL이 될 수 없음 | | `true` | NULL이 될 수 있음 | | `DBNull.Value` | 판정할 수 없음 | `DBNull.Value`는 `NOT NULL`을 의미하지 않습니다. NULL이 발생할 수 있는 것으로 처리합니다. SQL 결과의 판정 규칙은 [Nullable 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-nullable-metadata)를 참고합니다. Machbase SQL에서 `''`은 SQL `NULL`이므로 `GetSchemaTable()`의 `AllowDBNull`은 `true`이고 해당 행에서 `IsDBNull()`은 `true`입니다. `''''`는 작은따옴표 한 글자이므로 `AllowDBNull=false`인 NULL이 아닌 문자열 결과입니다. `GetSchemaTable()`은 `ColumnName`, `ColumnOrdinal`, `ColumnSize`, `NumericPrecision`, `NumericScale`, `DataType`, `ProviderType`, `IsLong`, `AllowDBNull`, `IsKey`를 제공합니다. `IsKey`가 `true`이면 SELECT 결과의 직접 컬럼이 PRIMARY KEY입니다. 표현식이나 집계식은 `false`입니다. 이전 버전 서버 또는 SDK와 연결한 경우에는 `IsKey`가 `false`로 반환될 수 있습니다. Nullable 메타데이터는 DECIMAL 전체 자릿수 `1~65`, 소수 자릿수 `0~30`과 실제 값을 변경하지 않습니다. .NET에서는 전체 자릿수가 29 이하이고 소수 자릿수가 28 이하인 DECIMAL을 `System.Decimal`로 반환합니다. 이 범위를 넘는 DECIMAL은 정밀도 손실을 방지하기 위해 `System.String`으로 반환합니다. 이때 `GetSchemaTable().DataType`, `GetFieldType()`, 실제 행 값의 CLR 형식도 모두 `System.String`입니다. #### Get*XXXX* ```cs bool GetBoolean(int ordinal) byte GetByte(int ordinal) char GetChar(int ordinal) short GetInt16(int ordinal) int GetInt32(int ordinal) long GetInt64(int ordinal) DateTime GetDateTime(int ordinal) string GetString(int ordinal) decimal GetDecimal(int ordinal) double GetDouble(int ordinal) float GetFloat(int ordinal) ``` 컬럼 값을 지정한 타입으로 반환합니다. #### Read ```cs bool Read() ``` 다음 레코드를 읽습니다. 결과가 더 이상 없으면 `false`를 반환합니다. #### 필드 | 이름 | 설명 | |--|--| | `FetchSize` | 서버에서 한 번에 가져올 레코드 수입니다. 기본값은 3000이며 여기에서는 수정할 수 없습니다. | | `FieldCount` | 결과 컬럼 수입니다. | | `this[int ordinal]` | `GetValue(int ordinal)`과 동일합니다. | | `this[string name]` | `GetValue(GetOrdinal(name))`과 동일합니다. | | `HasRows` | 결과가 존재하는지 여부입니다. | | `RecordsAffected` | Fetch된 레코드 수를 나타냅니다. | ### MachParameterCollection ```cs public sealed class MachParameterCollection : DbParameterCollection, IEnumerable ``` `MachCommand`에 바인딩할 파라미터 집합을 관리하는 클래스입니다. 파라미터를 설정한 뒤 실행하면 해당 값이 함께 전송됩니다. > `MachParameter` 바인딩은 Prepared Statement 의미의 실행 계획 캐시를 제공하지 않습니다. > 반복 실행 성능은 실제 쿼리와 서버 캐시 상태로 측정합니다. > > 현재 프로바이더는 파라미터를 타입별 SQL 리터럴로 렌더링한 뒤 ExecDirect로 실행합니다. > 따라서 `MachParameterCollection`은 서버의 Prepared Named Bind 프로토콜이나 파라미터 > 메타데이터를 사용하지 않습니다. #### Add ```cs MachParameter Add(string parameterName, DbType dbType) ``` 파라미터 이름과 타입을 지정해 `MachParameter`를 추가하고, 생성된 객체를 반환합니다. ```cs int Add(object value) ``` 값을 추가하고 추가된 인덱스를 반환합니다. ```cs void AddRange(Array values) ``` 단순 값 배열을 한 번에 추가합니다. ```cs MachParameter AddWithValue(string parameterName, object value) ``` 파라미터 이름과 값을 동시에 추가하고, 생성된 `MachParameter`를 반환합니다. #### Contains ```cs bool Contains(object value) ``` 해당 값이 이미 추가되어 있는지 확인합니다. ```cs bool Contains(string parameterName) ``` 지정한 파라미터 이름이 존재하는지 확인합니다. #### Clear ```cs void Clear() ``` 모든 파라미터를 제거합니다. #### IndexOf ```cs int IndexOf(object value) ``` 해당 값이 있는 인덱스를 반환합니다. ```cs int IndexOf(string parameterName) ``` 파라미터 이름이 위치한 인덱스를 반환합니다. #### Insert ```cs void Insert(int index, object value) ``` 지정한 위치에 값을 삽입합니다. #### Remove ```cs void Remove(object value) ``` 해당 값을 포함한 파라미터를 제거합니다. ```cs void RemoveAt(int index) ``` 인덱스에 위치한 파라미터를 제거합니다. ```cs void RemoveAt(string parameterName) ``` 지정한 이름의 파라미터를 제거합니다. #### 필드 | 이름 | 설명 | |--|--| | `Count` | 파라미터 개수입니다. | | `this[int index]` | 해당 인덱스의 `MachParameter`입니다. | | `this[string name]` | 이름과 일치하는 `MachParameter`입니다. | ### MachParameter ```cs public sealed class MachParameter : DbParameter ``` 개별 파라미터의 바인딩 정보를 저장하는 클래스입니다. #### 필드 | 이름 | 설명 | |--|--| | `ParameterName` | 파라미터 이름입니다. | | `Value` | 전송할 값입니다. | | `Size` | 값의 길이입니다. | | `Direction` | `ParameterDirection` 값입니다. 기본값은 `Input`입니다. | | `DbType` | .NET 측 DB 타입입니다. | | `MachDbType` | Machbase 고유 타입입니다. | | `IsNullable` | `NULL` 허용 여부입니다. | | `HasSetDbType` | `DbType`이 설정되었는지 여부입니다. | ### MachException ```cs public class MachException : DbException ``` Machbase에서 발생한 오류를 표현하는 예외 클래스입니다. #### 필드 | 이름 | 설명 | |--|--| | `MachErrorCode` | 가능한 경우 Machbase 오류 코드입니다. Universal 프로바이더가 기존 호환 예외를 번역하면 `0`일 수 있습니다. | ### MachAppendWriter ```cs public sealed class MachAppendWriter ``` Append 프로토콜을 다루기 위한 보조 클래스입니다. `MachCommand.AppendOpen()` 호출 시 인스턴스를 획득합니다. #### SetErrorDelegator ```cs void SetErrorDelegator(ErrorDelegateFuncType callback) void ErrorDelegateFuncType(MachAppendException e); ``` Append 중 오류가 발생했을 때 호출할 델리게이트를 등록합니다. #### 필드 | 이름 | 설명 | |--|--| | `SuccessCount` | 성공적으로 저장된 레코드 수입니다. `AppendClose()` 이후에 확인할 수 있습니다. | | `FailureCount` | 실패한 레코드 수입니다. `AppendClose()` 이후에 설정됩니다. | | `Option` | `AppendOpen()` 호출 시 사용한 `MachAppendOption` 값입니다. | ### MachAppendException ```cs public sealed class MachAppendException : MachException ``` Append 과정에서 발생한 오류 정보를 추가로 제공하는 예외입니다. 서버가 반환한 오류 메시지를 그대로 전달하며, 실패한 레코드를 문자열로 확인할 수 있습니다. #### GetRowBuffer ```cs string GetRowBuffer() ``` 오류가 발생한 원본 레코드를 문자열 형태로 반환합니다. ## 사용 예시 {#usage-and-examples} ### 연결 다음 예제는 환경 변수 비밀번호로 연결하고 LOG 테이블을 생성·입력·조회한 뒤 삭제합니다. ```csharp using System; using Mach.Data.MachClient; var password = Environment.GetEnvironmentVariable("MACHBASE_PASSWORD") ?? throw new InvalidOperationException("MACHBASE_PASSWORD is required"); var connString = $"SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD={password};PROTOCOL=4.0-full"; using var connection = new MachConnection(connString); connection.Open(); const string tableName = "NET_QUERY_DEMO"; using (var create = new MachCommand( $"CREATE LOG TABLE {tableName} (id INTEGER, name VARCHAR(40))", connection)) { create.ExecuteNonQuery(); } try { using (var insert = new MachCommand( $"INSERT INTO {tableName} VALUES (1, 'pump')", connection)) { insert.ExecuteNonQuery(); } using var query = new MachCommand($"SELECT id, name FROM {tableName}", connection); using var reader = query.ExecuteReader(); while (reader.Read()) { for (var column = 0; column < reader.FieldCount; column++) { Console.WriteLine($"{reader.GetName(column)} : {reader.GetValue(column)}"); } } } finally { using var drop = new MachCommand($"DROP TABLE {tableName}", connection); drop.ExecuteNonQuery(); } ``` ### 파라미터 바인딩 `MachParameterCollection`은 `:name`, `@name`, `?name` 자리표시자를 처리합니다. 공통 SQL 문법과 같은 `:name` 형식을 권장합니다. 이름 검색은 대소문자를 구분하지 않으며, 같은 이름이 반복되면 한 값이 모든 위치에 적용됩니다. `:name` 형식은 Machbase 8.7.0 서버 연결에서 사용합니다. 이전 서버에 연결하면 `MachException`을 반환합니다. `@name`과 `?name`은 기존 프로바이더 호환 형식입니다. ```csharp var password = Environment.GetEnvironmentVariable("MACHBASE_PASSWORD") ?? throw new InvalidOperationException("MACHBASE_PASSWORD is required"); var connString = $"SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD={password};PROTOCOL=4.0-full"; using var connection = new MachConnection(connString); connection.Open(); const string sql = @" SELECT NAME FROM V$TABLES WHERE NAME = :table_name OR NAME = :table_name"; using var command = new MachCommand(sql, connection); command.Parameters.AddWithValue(":table_name", "V$TABLES"); using var reader = command.ExecuteReader(); while (reader.Read()) { Console.WriteLine($"{reader.GetName(0)} : {reader.GetValue(0)}"); } ``` NULL은 `DBNull.Value`로 전달합니다. 공통 이름 문법은 [Named Bind Parameter syntax](../../reference/sql/syntax/named-bind-parameter-syntax/)를 참고하십시오. ### Append Append 프로토콜을 사용하면 대량의 시계열 데이터를 빠르게 적재할 수 있습니다. ```csharp using System; using System.Collections.Generic; using Mach.Data.MachClient; var password = Environment.GetEnvironmentVariable("MACHBASE_PASSWORD") ?? throw new InvalidOperationException("MACHBASE_PASSWORD is required"); var connString = $"SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD={password};PROTOCOL=4.0-full"; using var connection = new MachConnection(connString); connection.Open(); const string tableName = "NET_APPEND_DEMO"; using (var create = new MachCommand( $"CREATE LOG TABLE {tableName} (ID INTEGER, NAME VARCHAR(40))", connection)) { create.ExecuteNonQuery(); } try { using var appendCommand = new MachCommand(connection); var writer = appendCommand.AppendOpen(tableName); writer.SetErrorDelegator(error => Console.Error.WriteLine($"Append row error: {error.Message}\n{error.GetRowBuffer()}")); try { for (var i = 1; i <= 100000; i++) { appendCommand.AppendData(writer, new List { i, $"NAME_{i % 100}" }); if (i % 1000 == 0) appendCommand.AppendFlush(writer); } } finally { if (appendCommand.IsAppendOpened) appendCommand.AppendClose(writer); } Console.WriteLine($"Success Count : {writer.SuccessCount}"); Console.WriteLine($"Failure Count : {writer.FailureCount}"); if (writer.FailureCount != 0) throw new InvalidOperationException($"Append failed rows: {writer.FailureCount}"); } finally { if (connection.State == System.Data.ConnectionState.Open) { try { using var drop = new MachCommand($"DROP TABLE {tableName}", connection); drop.ExecuteNonQuery(); } catch (Exception cleanupError) { Console.Error.WriteLine($"cleanup failed: {cleanupError.Message}"); } } } ``` ### ARRAY와 선택 컬럼 Append Machbase DBMS 8.7.0 full/legacy 프로바이더는 ARRAY를 `object[]`로 반환합니다. 요소 NULL은 배열 안의 `null`, 배열 전체 NULL은 `IsDBNull()`로 구분합니다. 일반 `AppendOpen(table)`에서도 `MachSparseArray`를 ARRAY 컬럼 값으로 입력할 수 있습니다. ```csharp var writer = append.AppendOpen("ARRAY_APPEND_FULL_EXAMPLE"); ``` 이때 입력 행은 테이블의 컬럼 순서를 따릅니다. `ID LONG, A INT32[4]` 테이블에 희소 값, 빈 희소 배열과 전체 NULL을 입력하는 [일반 Open 예제](../data-input-load-export/array-append/#dotnet-full-open)에서 `AppendData()`와 Close·결과 확인까지 설명합니다. `AppendOpen()`의 `IList` 오버로드에는 일반 컬럼이나 `ARRAY_COLUMN[position]`을 전달할 수 있습니다. 행마다 다른 위치를 입력할 때는 `MachSparseArray`를 배열 전체 대상에 전달합니다. 요소 위치를 지정한 대상과 `MachSparseArray.Set()`의 위치는 0부터 시작하는 인덱스입니다. ```csharp using var append = new MachCommand(connection); var writer = append.AppendOpen( "ARRAY_APPEND_EXAMPLE", new List { "ID", "A" }); var sparse = new MachSparseArray(MachDBType.INT32_ARRAY, 4) .Set(1, 200) .Set(3, 400); try { append.AppendData(writer, new List { 2L, sparse }); } finally { if (append.IsAppendOpened) append.AppendClose(writer); } ``` 빈 `MachSparseArray`는 모든 요소가 NULL인 ARRAY이고 `DBNull.Value`는 배열 전체 NULL입니다. 오버로드와 전체 검증 예제는 [Sparse ARRAY와 선택 컬럼 Append API](../data-input-load-export/array-append/)를 참고하십시오. ### Error Delegator 설정 Append 중 행 오류는 위 예제처럼 writer를 연 직후 delegate로 받고, close 뒤 success/failure 건수를 확인합니다. ### 자동 AppendFlush 설정 AppendOpen은 자동 flush 스레드를 시작합니다. 끄려면 이미 열린 writer에 대해 `connection.SetConnectAppendFlush(false)`를 호출합니다. 자동 스레드 오류가 즉시 공개 exception으로 전달되지 않을 수 있으므로 명시 flush·close와 callback/count 확인을 유지합니다. ## 프로토콜 4.0-full 전체 API {#full-provider-apis-protocol-40-full} `PROTOCOL=4.0-full`을 사용하면 확장된 ADO.NET 표면을 사용할 수 있습니다. 8.0.55 소스 패키지에서 4.0 limited connector는 3.1.3, 4.0-full connector는 3.2.2입니다. 설치된 Linux 패키지는 `$MACHBASE_HOME/lib/` 아래에 net50 flavor만 포함할 수 있으므로, 다른 대상 framework가 필요하면 소스 빌드 또는 NuGet 복원 산출물을 사용하십시오. - `UniMachNetConnector-net50-8.0.55.dll` – DBMS Standard Linux 패키지에서 흔히 설치되는 universal entry point - `machNetConnector-40-net50-3.1.3.dll` – 프로토콜 4.0 limited connector - `machNetConnector-40-net50-3.2.2.dll` – 프로토콜 4.0-full connector ### 4.0-full에서 추가된 주요 타입 - `MachDbProviderFactory`: `Mach.Data` invariant 이름으로 프로바이더를 등록/생성할 수 있습니다. - `MachConnectionStringBuilder`: 키워드 오타 없이 커넥션 문자열을 구성할 수 있습니다. - `MachDataAdapter`, `MachRowUpdating`, `MachRowUpdated`: `DataTable`/`DataSet` 기반 워크플로를 지원합니다. - `MachCommandBuilder`: SELECT 문으로부터 INSERT/DELETE(조건에 따라 UPDATE) 구문을 자동 생성합니다. 자동 생성 SQL이 대상 테이블의 DML 제약에 맞는지 실행 전에 확인합니다. ### 전체 API 활성화 ```csharp var password = Environment.GetEnvironmentVariable("MACHBASE_PASSWORD") ?? throw new InvalidOperationException("MACHBASE_PASSWORD is required"); var connString = $"SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD={password};PROTOCOL=4.0-full"; using var connection = new MachConnection(connString); connection.Open(); ``` UPDATE/DELETE가 필요한 경우에는 테이블 타입별 조건과 드라이버의 SQL 생성 범위를 함께 확인합니다. LOG는 UPDATE를 지원하지 않습니다. Machbase DBMS 8.7.0 Standard Edition의 TAG UPDATE는 NAME과 BASETIME 조건을 요구하므로 범용 CommandBuilder가 만든 SQL에 의존하지 말고 [TAG UPDATE 구문](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/)에 맞는 명령과 바인딩을 사용합니다. ### 커넥션 문자열 빌더 사용 ```csharp var builder = new MachConnectionStringBuilder { Server = "127.0.0.1", Port = 5656, UserID = "SYS", Password = Environment.GetEnvironmentVariable("MACHBASE_PASSWORD") ?? throw new InvalidOperationException("MACHBASE_PASSWORD is required") }; builder["PROTOCOL"] = "4.0-full"; using var connection = new MachConnection(builder.ConnectionString); connection.Open(); ``` ### 예시: MachDataAdapter로 SQL INSERT Lookup 테이블을 `DataTable`로 가져와 새 행을 추가하면 command builder가 일반 SQL INSERT를 실행합니다. 이 경로는 Append 프로토콜이 아닙니다. ```csharp using Mach.Data.MachClient; using System; using System.Data; var password = Environment.GetEnvironmentVariable("MACHBASE_PASSWORD") ?? throw new InvalidOperationException("MACHBASE_PASSWORD is required"); var connString = $"SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD={password};PROTOCOL=4.0-full"; using var connection = new MachConnection(connString); connection.Open(); using (var create = new MachCommand( "CREATE LOOKUP TABLE dotnet_lookup_demo (id INTEGER PRIMARY KEY, name VARCHAR(80))", connection)) { create.ExecuteNonQuery(); } var adapter = new MachDataAdapter( "SELECT id, name FROM dotnet_lookup_demo ORDER BY id", connection); var builder = new MachCommandBuilder(adapter); var table = new DataTable(); adapter.Fill(table); var newRow = table.NewRow(); newRow["id"] = 2001; newRow["name"] = "Inserted from MachDataAdapter"; table.Rows.Add(newRow); adapter.MachRowUpdating += (sender, args) => { Console.WriteLine( $"About to run {args.StatementType} with SQL: {args.Command?.CommandText}"); }; adapter.Update(table); using var drop = new MachCommand("DROP TABLE dotnet_lookup_demo", connection); drop.ExecuteNonQuery(); ``` > **Tip**: 전송 전 SQL을 확인하려면 위처럼 `Update()` 전에 이벤트를 구독하십시오. ### 예시: DbProviderFactory 활용 `MachDbProviderFactory.Instance`를 사용하면 `DbProviderFactories`, Dapper 등 프로바이더 중립 구성에 Machbase를 연결할 수 있습니다. ```csharp using System; using System.Data.Common; using Mach.Data.MachClient; DbProviderFactory factory = MachDbProviderFactory.Instance; using DbConnection connection = factory.CreateConnection()!; var password = Environment.GetEnvironmentVariable("MACHBASE_PASSWORD") ?? throw new InvalidOperationException("MACHBASE_PASSWORD is required"); connection.ConnectionString = $"SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD={password};PROTOCOL=4.0-full"; connection.Open(); using DbCommand command = connection.CreateCommand(); command.CommandText = "SELECT COUNT(*) FROM V$TABLES"; var count = (long)command.ExecuteScalar(); Console.WriteLine($"Visible tables: {count}"); ``` 설정 기반 애플리케이션에서 팩터리를 자동으로 노출하려면 시작 시 `MachDbProviderFactory.Register()`를 한 번 호출해 `DbProviderFactories.GetFactory("Mach.Data")`가 동일한 인스턴스를 반환하도록 구성하십시오. `4.0-full` 프로토콜은 Machbase 7.x 이상 서버에서만 사용 가능합니다. 더 낮은 버전에서는 `PROTOCOL=4.0`(제한된 기능) 또는 2.x/3.x 프로토콜을 사용해야 합니다. --- title: "11.9 Go" url: https://docs.machbase.com/kr/dbms/development-tools-integration/go/ language: kr kind: section --- # 11.9 Go ## neo-client 개요 `neo-client`는 Machbase Neo용 Go 클라이언트 모듈입니다. v2부터는 표준 `database/sql` 드라이버를 중심으로 재구성되었으며, 이전 버전(v1)에서 제공하던 네이티브 `machgo` 패키지는 더 이상 제공되지 않습니다. v1 기반 코드는 v2와 호환되지 않으므로, `machgo.Config`나 `mdb.Connect()`를 사용하는 기존 코드는 아래 내용을 참고해 `database/sql` 기반으로 마이그레이션하십시오. `neo-client`는 다음 패키지를 제공합니다. - `client`(모듈 루트, import 경로 `github.com/machbase/neo-client/v2`): 표준 `database/sql` 드라이버, `Appender`, 구조체 스캔·Named 매개변수 헬퍼 - `api`: Machbase 전용 데이터 타입과 옵션 정의 - `machnet`: `client`가 내부적으로 사용하는 저수준 프로토콜/전송 구현으로, 응용 코드에서 직접 import할 필요는 거의 없음 ### 사전 요구사항 - **Machbase 서버**: 네이티브 포트(기본 `5656`)로 접근 가능한 실행 중인 DBMS 또는 Neo 서버 - **Go 1.22 이상** - **계정 정보**: 유효한 Machbase 사용자 계정(로컬 개발 환경에서는 `sys` / `manager` 등) ## 시작하기 ### 설치 ```sh go get github.com/machbase/neo-client/v2 ``` ### Import 드라이버 패키지는 blank identifier로 import합니다. 드라이버 이름 `machbase`로 자동 등록되므로 별도의 `sql.Register()` 호출은 필요하지 않습니다. ```go import ( "context" "database/sql" "fmt" _ "github.com/machbase/neo-client/v2" ) ``` ## 연결 ### DSN 형식 `neo-client`는 다음 DSN 형식을 지원합니다. - 서버 값만 지정: `host` 또는 `host:port` - URL 형식: `tcp://user:password@host:port/database?as=proxy&fetch_rows=100` - 세미콜론으로 구분한 `key=value` 목록: `key=value;key=value;...` (예: `user=sys;password=manager;server=127.0.0.1:5656`) `key=value` 형식에서는 다음 규칙을 따릅니다. - 값은 `"..."` 또는 `'...'`로 인용할 수 있습니다. - 인용된 값 안의 `;`는 리터럴 문자로 처리됩니다. - 인용된 값 안에서는 백슬래시 이스케이프를 사용할 수 있습니다: 큰따옴표 값의 `\"`, 작은따옴표 값의 `\'`, `\\` - 인용부호가 닫히지 않았거나 짝이 맞지 않으면 파싱 오류가 발생합니다. 예: ```text user="sys as demo";password="12;34";server=127.0.0.1:5656; password="a\"b";server=127.0.0.1:5656; ``` 지원하는 DSN 키는 다음과 같습니다. | 키 | 설명 | |----|------| | `server` | `tcp://user:password@127.0.0.1:5656` 형식의 서버 URL | | `host`, `port` | 서버 호스트와 포트를 별도로 지정(기본 포트: `5656`) | | `user`, `uid` | 로그인 사용자 | | `password`, `pwd` | 로그인 비밀번호 | | `database`, `db` | 초기 데이터베이스 | | `auth_mode` | 인증 방식: `password` 또는 `challenge` | | `auth_key_file`, `auth_key_pem` | `auth_mode=challenge`의 개인키 파일 경로 또는 인라인 PEM | | `auth_sig_scheme` | challenge 인증 서명 스킴 | | `fetch_rows`, `fetchrows` | 한 번의 fetch에서 가져올 최대 행 수(기본값 `1000`) | | `statement_cache`, `statementcache` | 문장 캐시 모드: `auto`, `on`, `off`(기본값 `auto`) | | `io_metrics`, `iometrics` | I/O 지표 활성화 여부: `true`, `false` | | `alternative_servers` | `127.0.0.2:5656,backup.example.com:5657`처럼 콤마로 구분한 대체 서버 목록 | `auth_key_file` 또는 `auth_key_pem`을 지정하고 `auth_mode`를 생략하면 challenge 인증으로 처리됩니다. URL 쿼리 파라미터도 같은 옵션 이름을 사용합니다. ```text tcp://sys:manager@127.0.0.1:5656/DATABASE_A?statement_cache=on&io_metrics=true ``` 알 수 없는 키는 `key=value` DSN에서는 오류이지만 URL 쿼리 문자열에서는 무시됩니다. URL 경로는 초기 데이터베이스를 함께 지정합니다(`tcp://sys:manager@127.0.0.1:5656/DATABASE_A`). 매 물리 연결마다 지정한 데이터베이스가 선택되며, 애플리케이션 코드가 직접 `USE`를 실행한 경우 해당 연결이 풀에서 재사용되기 전에 지정한 데이터베이스로 복원됩니다. ## 조회 예제 다음 예제는 표준 `database/sql` 패키지로 `M$SYS_TABLES` 시스템 테이블을 조회합니다. ```go package main import ( "context" "database/sql" "fmt" _ "github.com/machbase/neo-client/v2" ) func main() { db, err := sql.Open("machbase", "server=tcp://sys:manager@127.0.0.1:5656") if err != nil { panic(err) } defer db.Close() ctx := context.Background() rows, err := db.QueryContext(ctx, `SELECT NAME, ID, TYPE FROM M$SYS_TABLES ORDER BY NAME`) if err != nil { panic(err) } defer rows.Close() for rows.Next() { var ( name string id int64 typ int ) if err := rows.Scan(&name, &id, &typ); err != nil { panic(err) } fmt.Println(name, id, typ) } if err := rows.Err(); err != nil { panic(err) } } ``` ## 테이블 생성과 입력 다음 예제는 `database/sql`로 태그 테이블을 생성하고 `ExecContext`로 행을 입력합니다. ```sql CREATE TAG TABLE IF NOT EXISTS example ( name VARCHAR(100) PRIMARY KEY, time DATETIME BASE TIME, value DOUBLE ); ``` ```go package main import ( "context" "database/sql" "fmt" "time" _ "github.com/machbase/neo-client/v2" ) func main() { dsn := "server=tcp://sys:manager@127.0.0.1:5656" db, err := sql.Open("machbase", dsn) if err != nil { panic(err) } defer db.Close() ctx := context.Background() _, err = db.ExecContext(ctx, `CREATE TAG TABLE IF NOT EXISTS EXAMPLE ( NAME VARCHAR(100) PRIMARY KEY, TIME DATETIME BASE TIME, VALUE DOUBLE )`) if err != nil { panic(err) } ts := time.Now() for i := 0; i < 10; i++ { rec := []any{ "example-client", ts.Add(time.Duration(i) * time.Second), 3.14 * float64(i), } result, err := db.ExecContext(ctx, `INSERT INTO EXAMPLE VALUES (?, ?, ?)`, rec...) if err != nil { panic(err) } affected, err := result.RowsAffected() if err != nil { panic(err) } fmt.Println("Rows affected:", affected) } } ``` ROWID를 지원하는 Standard Edition에서 단일 `INSERT ... VALUES`가 성공하면 `Result.LastInsertId()`로 입력된 행의 ROWID를 확인할 수 있습니다. 반환 타입이 `int64`이므로 ROWID의 64비트 값을 보존하려면 `uint64`로 변환합니다. 배치, Appender, `INSERT ... SELECT`, UPSERT에서는 ROWID를 반환하지 않습니다. 자세한 조건은 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. ## 트랜잭션 Machbase는 `CREATE TABLE`로 만든 일반 테이블(TRANSACTION 테이블)에서 `BEGIN`/`COMMIT`/ `ROLLBACK`을 지원합니다. TAG/LOG 테이블은 트랜잭션을 지원하지 않으며, 트랜잭션 안에서 TAG/LOG 테이블에 DML을 실행하면 `MACHCLI-ERR-2362` 오류가 발생합니다. 표준 `database/sql` 트랜잭션 API를 그대로 사용할 수 있습니다. ```go tx, err := db.BeginTx(ctx, nil) if err != nil { panic(err) } if _, err := tx.ExecContext(ctx, `INSERT INTO EXAMPLE_TX VALUES (?, ?, ?)`, name, ts, value); err != nil { tx.Rollback() panic(err) } if err := tx.Commit(); err != nil { panic(err) } ``` 반복되는 준비 코드를 줄이려면 `client.Tx`/`client.TxConn` 클로저 헬퍼를 사용합니다. 함수가 `nil`을 반환하면 커밋하고, 오류를 반환하면 롤백한 뒤 그 오류를 그대로 반환하며, panic이 발생하면 롤백 후 다시 panic을 발생시킵니다. ```go import client "github.com/machbase/neo-client/v2" err := client.Tx(ctx, db, func(tx *sql.Tx) error { if _, err := tx.ExecContext(ctx, `INSERT INTO EXAMPLE_TX VALUES (?, ?, ?)`, name, ts, value); err != nil { return err // 자동 ROLLBACK } return nil // 자동 COMMIT }) // TxConn은 db.Conn(ctx)로 확보한 특정 연결에서 트랜잭션을 실행합니다. conn, _ := db.Conn(ctx) defer conn.Close() err = client.TxConn(ctx, conn, func(tx *sql.Tx) error { // ... return nil }) ``` 클로저가 반환한 오류는 그대로 반환되므로 `errors.Is`/`errors.As`를 계속 사용할 수 있습니다. 강제로 롤백하려면 sentinel 오류를 반환하는 방식이 관용적입니다. Machbase는 트랜잭션 옵션 (isolation level, read-only)을 지원하지 않으므로 드라이버가 이를 거부합니다. ## 고성능 대량 입력 (`Appender`) 행 단위 `INSERT` 대신 대량 시계열 입력에는 `client.Appender`를 사용합니다. appender는 클라이언트에서 레코드를 버퍼링한 뒤 전용 채널로 서버에 스트리밍하므로, 개별 INSERT 문보다 훨씬 빠릅니다. ```go import client "github.com/machbase/neo-client/v2" appender := &client.Appender{} // 컬럼 일부만 지정: 아래 Append()는 이 세 값만 전송하고 나머지 컬럼은 NULL로 입력됩니다. if err := appender.Connect(ctx, dsn, "EXAMPLE", "NAME", "TIME", "VALUE"); err != nil { panic(err) } defer func() { successCount, failCount, err := appender.Close() // 남은 버퍼를 flush if err != nil { panic(err) } fmt.Println("Append finished. Success:", successCount, "Fail:", failCount) }() for _, rec := range records { // Connect에 전달한 컬럼과 같은 순서로 값을 하나씩 전달합니다. if err := appender.Append(rec.Name, rec.Time, rec.Value); err != nil { panic(err) } } ``` 핵심 사항: - **컬럼 선택**: `Connect`(또는 `WithInputColumns`)에 전달한 컬럼 목록이 각 `Append` 호출이 순서대로 제공해야 하는 컬럼을 정확히 결정합니다. 목록에 없는 컬럼은 NULL로 입력됩니다. - **컬럼 목록을 생략**하면(예: `appender.Connect(ctx, dsn, "EXAMPLE")`) appender는 테이블의 **모든** 컬럼을 대상으로 하며, 각 `Append` 호출은 `nil`을 포함해 모든 컬럼의 값을 제공해야 합니다. 그렇지 않으면 값 개수 오류가 발생합니다. - `Append`는 행을 버퍼링합니다. 즉시 전송하려면 `Flush()`를 호출하고, `Close()`는 flush와 함께 세션 단위 성공/실패 건수를 반환합니다. - 버퍼링 동작은 `WithBatchMaxRows`, `WithBatchMaxBytes`, `WithBatchMaxDelay`로 조정할 수 있습니다. - `WithBatchMaxRows(rows)`: 기본값 `512`, 최소값 `1` - `WithBatchMaxBytes(bytes)`: 기본값 `512KB`, 최소값 `4KB` - `WithBatchMaxDelay(duration)`: 기본값 `5ms`, 최소값 `1ms`, `0`을 지정하면 시간 기반 임계값을 사용하지 않음 - appender는 TAG, LOG, TRANSACTION 테이블에서 동작하지만 SQL을 우회하므로 append는 어떤 트랜잭션에도 포함되지 않습니다. ```go appender := &client.Appender{} if err := appender.Connect(ctx, dsn, "EXAMPLE", "NAME", "TIME", "VALUE"); err != nil { panic(err) } defer appender.Close() appender. WithBatchMaxBytes(1024 * 1024). // 1 MB 임계값 WithBatchMaxRows(2000). // row 수 임계값 WithBatchMaxDelay(500 * time.Millisecond) // 최대 지연 임계값 ``` {{< callout type="warning" >}} 활성 appender를 사용하는 연결에서 일반 쿼리를 함께 실행하지 마십시오. append 워크로드에는 별도 연결을 사용하십시오. {{< /callout >}} ### ARRAY와 선택 컬럼 Append 일반 `appender.Connect(ctx, dsn, table)`에서 컬럼 인자를 생략하고 ARRAY 컬럼 값으로 `api.NewSparseArray()`가 만든 객체를 전달할 수 있습니다. 고정된 요소 선택과는 다릅니다. ```go if err := appender.Connect(ctx, dsn, "ARRAY_APPEND_FULL_EXAMPLE"); err != nil { return err } ``` `ID LONG, A INT32[4]` 테이블의 입력 순서, 희소 값 구성, 오류 시 Close와 조회 확인은 [일반 Connect 예제](../data-input-load-export/array-append/#go-full-open)를 참고하십시오. ```go func appendSelected(ctx context.Context, dsn string) error { appender := &client.Appender{} if err := appender.Connect( ctx, dsn, "ARRAY_APPEND_EXAMPLE", "ID", "A[0]", "A[3]", ); err != nil { return err } if err := appender.Append(int64(1), int32(10), int32(40)); err != nil { _, _, _ = appender.Close() return err } success, failed, err := appender.Close() if err != nil { return err } if failed != 0 { return fmt.Errorf( "append result: success=%d failed=%d", success, failed, ) } return nil } ``` 위 예제는 `context`, `fmt`와 `client "github.com/machbase/neo-client/v2"`를 import한 상태를 전제로 합니다. 행마다 다른 위치를 입력할 때는 `api.NewSparseArray()`를 사용합니다. `Array.Set()`, `Get()`, `Entries()`와 요소 위치를 지정한 Append 대상의 위치는 0부터 시작하는 인덱스입니다. API와 버전 제한은 [Sparse ARRAY와 선택 컬럼 Append API](../data-input-load-export/array-append/)를 참고하십시오. ## 구조체로 결과 스캔하기 컬럼 순서대로 모든 대상을 나열하는 대신, `db` 태그로 컬럼을 구조체 필드에 매핑할 수 있습니다. 헬퍼는 이미 확보한 `*sql.Rows`를 그대로 받으므로 표준 `database/sql` API와 함께 사용할 수 있습니다. ```go import client "github.com/machbase/neo-client/v2" type TagRecord struct { Name string `db:"NAME"` Time time.Time `db:"TIME"` Value float64 `db:"VALUE"` cached string // export되지 않았거나 태그가 없는 필드는 무시됨 } records, err := client.Select[TagRecord](ctx, db, `SELECT NAME, TIME, VALUE FROM EXAMPLE WHERE NAME = ? ORDER BY TIME LIMIT 100`, "sensor-1") ``` 제공하는 헬퍼: | 함수 | 용도 | | --- | --- | | `Select[T](ctx, q, query, args...)` | 쿼리를 실행하고 모든 행을 `[]T`로 스캔 | | `Get[T](ctx, q, query, args...)` | 쿼리를 실행하고 첫 행을 스캔. 결과가 없으면 `sql.ErrNoRows` | | `ScanAll[T](rows)` / `ScanOne[T](rows)` | 호출자가 이미 연 rows에 대해 동일하게 동작 | | `ScanEach[T](rows, fn)` | 메모리 사용량을 일정하게 유지하며 한 행씩 스트리밍 | | `NewCursor[T](rows)` | 명시적인 `Next`/`Value`/`Err` 이터레이터 | | `ScanStruct(rows, &dest)` | `rows.Next()`를 호출하지 않고 현재 행을 스캔 | | `ScanRow(rows, &dest)` / `ScanRows(rows, &slice)` | 제네릭을 사용하지 않는 형태 | `T`는 구조체, 구조체 포인터, 단일 컬럼 쿼리의 스칼라, 또는 `map[string]any`일 수 있습니다. 매핑 규칙: - 태그 키는 `db`이며, 기존 DTO를 그대로 사용할 수 있도록 `json` 태그를 대체 수단으로 사용합니다. - 컬럼 이름은 대소문자를 구분하지 않고 매칭되므로 `db:"id"`는 `ID` 컬럼과 매칭됩니다. - `db:"-"`는 필드를 제외하며, **태그가 없는 필드도 제외**됩니다. 태그 없는 필드를 이름으로 매핑하려면 `WithNameMapper(client.NameMapperIdentity())`를 호출하십시오. - 내장 구조체는 평탄화되고, 이름이 있는 중첩 구조체는 `parent.child`로 지정합니다. - NULL 컬럼은 `nil`이 되는 `*T` 필드로 받거나 `sql.Null[T]`로 받을 수 있습니다. 기본적으로 매핑은 엄격합니다. 매칭되는 필드가 없는 컬럼과 매칭되는 컬럼이 없는 필드는 모두 오류이며, 이는 변경된 `SELECT *`가 값을 조용히 누락시키는 것을 방지합니다. 호출별로 `WithLaxColumns()` 또는 `WithLaxFields()`로 완화할 수 있습니다. DATETIME 컬럼을 `string`, `int64`, `time.Time` 필드로 스캔할 때는 machbase-neo HTTP API의 `timeformat`/`tz` 쿼리 파라미터와 이름을 맞춘 추가 `db` 태그 옵션을 사용할 수 있습니다. ```go type Row struct { Time string `db:"TIME,timeformat=2006-01-02 15:04:05,tz=Local"` // 사용자 지정 레이아웃 + 표시 타임존 Epoch int64 `db:"TIME,timeformat=ms"` // 밀리초 단위 epoch At time.Time `db:"TIME,tz=UTC"` // 필드별 타임존 재정의 } ``` - `timeformat=`: `string`/`*string` 필드에서 Go time layout(또는 epoch을 숫자 문자열로 표현하는 `ns`/`us`/`ms`/`s`) - `timeformat=ns|us|ms|s`: `int64`/`*int64` 필드에서 epoch 단위 - `tz=|Local|UTC`: `string`/`time.Time` 필드(및 포인터 형태)의 타임존 이 옵션은 태그가 없어도 적용됩니다. DATETIME 컬럼에 매칭된 `string`, `int64`, `time.Time` 필드는 기본값으로 `WithDateTime(timeformat, tz)`를 사용하며, `WithDateTime`도 설정하지 않았다면 `timeformat="2006-01-02 15:04:05.999"`와 `tz="Local"`을 사용합니다. 필드 자체의 태그가 항상 `WithDateTime`보다 우선합니다. `Select`, `ScanAll`, `ScanRows`는 결과 전체를 메모리에 적재하므로 `WithMaxRows`(기본값 1000)를 넘으면 `ErrScanTooManyRows`로 중단됩니다. `WithMaxRows(n)`으로 상향하거나 `WithMaxRows(0)`으로 제거할 수 있으며, 제한이 없는 `ScanEach`나 `NewCursor`로 스트리밍할 수도 있습니다. ```go rows, err := db.QueryContext(ctx, `SELECT NAME, TIME, VALUE FROM EXAMPLE`) if err != nil { panic(err) } defer rows.Close() // 헬퍼는 전달받은 rows를 닫지 않음 var total float64 err = client.ScanEach(rows, func(rec TagRecord) error { total += rec.Value return nil }) ``` ## Named 매개변수 `NamedArgs`는 구조체 또는 `map[string]any`를 같은 `db` 태그를 사용해 `sql.Named` 인자로 변환합니다. SQL 텍스트를 검사하거나 재작성하지 않으며, `:name` 자리표시자는 서버가 직접 해석합니다. ```go type condition struct { Name string `db:"name"` From time.Time `db:"from"` To time.Time `db:"to"` } args, err := client.NamedArgs(condition{Name: "sensor-1", From: begin, To: end}) if err != nil { panic(err) } records, err := client.Select[TagRecord](ctx, db, ` SELECT NAME, TIME, VALUE FROM EXAMPLE WHERE NAME = :name AND TIME BETWEEN :from AND :to`, args...) ``` Named 매개변수는 파라미터 이름 메타데이터를 보고하는 서버(Machbase v8.7.0 이상)가 필요합니다. `client.SupportsNamedParameters(ctx, db)`로 확인하고, 지원하지 않으면 쿼리는 `client.ErrNamedParamsUnsupported`로 실패하므로 위치 기반 `?` 자리표시자를 사용해야 합니다. 일반 SQL 기능과 SDK별 차이는 [Named Bind Parameter syntax](../../reference/sql/syntax/named-bind-parameter-syntax/)를 참고하십시오. ## Machbase 8.7: DECIMAL과 Named 매개변수 Machbase 8.7은 정확한 DECIMAL 값, nullable 컬럼 정보, named 매개변수를 제공합니다. ```go import "database/sql" import client "github.com/machbase/neo-client/v2" amount, err := client.ParseDecimal("1234567890.125", 30, 3) if err != nil { panic(err) } result, err := conn.ExecContext(ctx, "INSERT INTO payments(id, amount) VALUES (:id, :amount)", sql.Named("id", int32(1)), sql.Named("amount", amount), ) if err != nil { panic(err) } ``` `database/sql` 드라이버는 `sql.Named`를 받아들이고 DECIMAL 조회 값을 정확한 문자열로 반환합니다. 파라미터 이름은 대소문자를 구분하지 않고 매칭되며, 반복된 자리표시자에는 한 번 전달한 값이 적용되고, named 인자와 positional 인자를 섞을 수 없습니다. `client.NamedArgs`는 구조체 또는 map으로부터 `sql.Named` 목록을 만듭니다. Machbase 8.5.x에 연결한 경우에는 해당 서버 버전이 지원하는 테이블·데이터 타입과 함께 위치 기반 `?` 매개변수를 사용하십시오. Named 매개변수와 Machbase 8.7 데이터 타입은 사용할 수 없으며, nullable 컬럼 정보를 알 수 없는 경우(`ColumnType.Nullable()`이 `ok=false` 반환)가 있을 수 있습니다. ### Prepared statement와 statement cache `db.PrepareContext`로 만든 문장은 여러 번 실행할 수 있습니다. 드라이버의 문장 캐시는 연결별로 동작하며, `statement_cache=auto|on|off` DSN 키로 설정합니다. 테이블을 삭제 후 다시 만들었거나 결과 컬럼 타입이 변경된 경우 캐시된 메타데이터가 갱신되도록 문장을 다시 준비합니다. `USE`로 세션 데이터베이스를 변경한 뒤에도 기존 준비된 문장이나 커서를 다른 데이터베이스의 작업에 재사용하지 말고 새로 준비하거나 열어야 합니다. ## 포함된 예제 실행 실행 가능한 예제는 neo-client 저장소의 `_example/` 아래에 포함되어 있습니다. ```sh go run ./_example/query.go -s 127.0.0.1:5656 -u sys -p manager go run ./_example/append.go -s 127.0.0.1:5656 -u sys -p manager go run ./_example/insert.go -s 127.0.0.1:5656 -u sys -p manager go run ./_example/scanbytag.go -s 127.0.0.1:5656 -u sys -p manager ``` ## 참고 사항 및 제한 사항 - 위치 기반 placeholder와 이름 기반 placeholder를 모두 사용할 수 있지만, 한 문장 안에서 두 방식을 섞을 수 없습니다. 이름 기반 API는 `sql.Named()`를 사용합니다. 공통 SQL 기능과 SDK별 차이는 [Named Bind Parameter syntax](../../reference/sql/syntax/named-bind-parameter-syntax/)를 참고하십시오. - `database/sql`의 연결 풀은 일반적인 `sql.DB` 방식대로 동작합니다. DSN에 `database`/`db`를 지정하면 매 물리 연결마다 지정한 데이터베이스를 선택하며, 애플리케이션 코드가 직접 `USE`를 실행한 세션은 풀에 반환되기 전에 설정된 데이터베이스로 복원됩니다. - ROWID를 지원하는 Standard Edition에서는 단일 INSERT 결과에서 `Result.LastInsertId()`를 호출할 수 있습니다. 반환된 `int64`는 `uint64`로 변환해 ROWID의 bit pattern을 보존합니다. 자세한 내용은 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. - 사용을 마친 `Rows`, `Stmt`, `sql.Conn`, `sql.DB`는 항상 닫으십시오. 구조체 스캔 헬퍼는 전달받은 rows를 닫지 않습니다. - `Appender.Close()`는 append 세션의 성공/실패 건수를 반환합니다. - 파라미터 타입은 드라이버 구현을 따릅니다. 일반적인 SQL 타입, `time.Time`, `[]byte`, `net.IP`, `api.Decimal`을 지원하지만 `bool` 파라미터는 지원하지 않습니다. --- title: "11.10 데이터 입력과 반출" url: https://docs.machbase.com/kr/dbms/development-tools-integration/data-input-load-export/ language: kr kind: section --- # 11.10 데이터 입력과 반출 SQL, Append API, 파일 도구 중 데이터 양과 운영 방식에 맞는 경로를 선택합니다. 이 페이지는 선택과 검증 흐름을 설명하며, 전체 옵션은 각 도구·SQL 레퍼런스를 참고합니다. ## 입력 방식 선택 | 방식 | 적합한 경우 | 주요 확인값 | |------|-------------|-------------| | 단건 INSERT | 소량 입력, 즉시 오류 확인 | 영향 행 수, generated ID | | prepared 배치 | 같은 SQL의 반복 실행 | 항목별 결과와 실패 위치 | | Append API | 지속적인 TAG·LOG 대량 수집 | 서버 처리 응답, 성공·실패 건수 | | `LOAD DATA INFILE` | 서버가 읽을 수 있는 파일 적재 | 서버 파일 권한, 입력 건수 | | `machloader`·`csvimport` | 클라이언트 파일 적재 | 로그·오류 행 파일, 입력·실패 건수 | ### 경로와 도구 비교 | 경로·도구 | 실행 위치와 용도 | |---|---| | SDK Append | 애플리케이션이 지속적으로 여러 TAG·LOG 행 전송 | | SQL INSERT | 소량 입력과 일반 SQL 연동 | | `LOAD DATA INFILE` | 서버가 읽을 수 있는 파일을 SQL로 적재 | | `machloader` | 클라이언트 파일의 매핑·로그·오류 행 파일을 세밀하게 제어 | | `csvimport`·`csvexport` | 단순 CSV 입출력 래퍼 | | `tagmetaimport` | TAG 메타데이터를 일괄 등록·변경 | `tagmetaimport`는 TAG 측정값 입력 도구가 아닙니다. 정확한 옵션은 [명령행 도구](/dbms/reference/command-line-tools/)를 확인하십시오. 원본 보존이 필요한 시계열·이벤트는 TAG 또는 LOG에 넣습니다. 관계형 변경은 TRANSACTION, 작은 참조 데이터는 LOOKUP, 재생성 가능한 메모리 캐시는 VOLATILE을 사용합니다. 테이블 선택이 끝난 뒤 예상 건수, 지연 허용치, 재시도 단위, 중복 정책을 기준으로 입력 방식을 결정합니다. ## SQL INSERT 다음 예제는 생성부터 정리까지 순서대로 실행할 수 있습니다. ```sql CREATE LOG TABLE integration_insert_demo ( event_time DATETIME, sensor_id VARCHAR(32), value DOUBLE ); INSERT INTO integration_insert_demo VALUES (TO_DATE('2026-01-01 00:00:00'), 'TEMP-01', 25.3); SELECT sensor_id, value FROM integration_insert_demo; DROP TABLE integration_insert_demo; ``` 애플리케이션에서는 값을 prepared 매개변수로 바인딩하고 반환된 영향 행 수를 확인합니다. ## Append API Append는 각 SDK의 전용 API로 테이블을 열고 여러 행을 보낸 뒤 flush·close하는 흐름입니다. 컬럼 순서와 타입을 대상 스키마에 맞추고, 일반 쿼리와 연결을 분리합니다. 언어별 완전한 코드는 이 장의 SDK별 페이지를 참고합니다. Machbase DBMS 8.7.0에서는 Append Open 단계에서 입력할 컬럼이나 `ARRAY` 요소 대상을 선택할 수 있습니다. 행마다 다른 ARRAY 위치를 입력할 때는 SDK의 희소 ARRAY 객체를 사용합니다. 선택 기준, API와 검증 예제는 [Sparse ARRAY와 선택 컬럼 Append API](array-append/)를 참고하십시오. ## LOAD DATA INFILE `LOAD DATA INFILE`은 서버가 접근할 수 있는 파일을 SQL로 적재합니다. 파일 경로는 서버 프로세스 관점에서 해석되므로 다음을 확인합니다. - 서버 호스트에 파일이 존재하는지 - 서버 프로세스 계정이 파일을 읽을 수 있는지 - 구분자, 인용 문자, 인코딩, 날짜 형식이 원본과 일치하는지 - 실패 행을 식별할 로그·오류 행 파일을 어디에 남길지 구문과 지원 옵션은 [LOAD DATA INFILE](/dbms/reference/sql/syntax/load-data-infile-syntax/)을 참고합니다. ## CSV 파일 준비 첫 행을 헤더로 사용할지 결정하고, 모든 행에서 컬럼 수와 순서를 일정하게 유지합니다. NULL, 빈 문자열, 구분자가 포함된 문자열, 줄바꿈, DATETIME 형식을 표본 파일로 먼저 검증합니다. 대량 파일은 전체 실행 전에 작은 표본으로 테이블 스키마와 변환 규칙을 확인합니다. ## machloader로 가져오기 기본 구문은 다음과 같습니다. ```bash "$MACHBASE_HOME/bin/machloader" -s 127.0.0.1 -P 5656 -u APP_USER -p "$MACH_SAMPLE_PASSWORD" -i -t SENSOR_LOG -d /data/sensor.csv -l /data/sensor.log -b /data/sensor.bad ``` 헤더가 있으면 `-H`, 구분자가 쉼표가 아니면 `-D`, 날짜 형식이 다르면 `-F`를 명시합니다. 전체 옵션은 [machloader](/dbms/reference/command-line-tools/machloader/)를 참고합니다. ## csvimport로 가져오기 `csvimport`는 machloader의 자주 쓰는 CSV 옵션을 간소화한 도구입니다. ```bash "$MACHBASE_HOME/bin/csvimport" -s 127.0.0.1 -P 5656 -u APP_USER -p "$MACH_SAMPLE_PASSWORD" -t SENSOR_LOG -d /data/sensor.csv -H -l /data/sensor.log -b /data/sensor.bad ``` `-C` 자동 생성은 모든 컬럼을 의도한 업무 타입으로 만들지 않을 수 있습니다. 운영 적재는 테이블을 명시적으로 생성하고 스키마를 확인한 뒤 실행합니다. ## 반출 방식 선택 | 방식 | 적합한 경우 | |------|-------------| | `SAVE DATA INTO` | SQL 조건과 조회 컬럼 선택으로 서버 파일 생성 | | `machloader -o` | 테이블 단위 반출과 상세 옵션 사용 | | `csvexport` | 단순 CSV 반출 | | SDK SELECT | 애플리케이션이 행을 변환·전송해야 하는 경우 | ## 파일 소유권과 경로 `SAVE DATA INTO`의 경로와 파일 권한은 서버 프로세스 기준입니다. machloader와 csvexport가 만드는 파일은 도구를 실행한 OS 사용자 기준입니다. 상대 경로를 피하고, 기존 파일 덮어쓰기 정책과 사용 가능한 디스크 공간을 먼저 확인합니다. ## SAVE DATA INTO SQL 조건으로 결과를 반출할 때 사용합니다. 운영 경로에서 실행하기 전에 작은 결과와 별도 검증 경로로 파일 생성·인코딩·헤더를 확인합니다. 전체 구문은 [SAVE DATA INTO](/dbms/reference/sql/syntax/save-data-into-syntax/)를 참고합니다. ## machloader로 내보내기 ```bash "$MACHBASE_HOME/bin/machloader" -s 127.0.0.1 -P 5656 -u APP_USER -p "$MACH_SAMPLE_PASSWORD" -o -t SENSOR_LOG -d /data/sensor-export.csv -H -l /data/sensor-export.log ``` ## csvexport로 내보내기 ```bash "$MACHBASE_HOME/bin/csvexport" -s 127.0.0.1 -P 5656 -u APP_USER -p "$MACH_SAMPLE_PASSWORD" -t SENSOR_LOG -d /data/sensor-export.csv -H -l /data/sensor-export.log ``` ## Batch 처리 - 배치 크기는 행 크기와 지연 요구사항을 기준으로 부하 테스트합니다. - 각 배치의 소스 오프셋과 대상 성공 건수를 기록합니다. - 부분 실패 시 전체 재실행보다 실패 행만 분리해 재처리합니다. - 같은 행을 재전송해도 안전하도록 업무 키와 중복 정책을 정의합니다. ## 대량 입력 오류 처리 1. 도구 종료 코드와 요약 건수를 확인합니다. 2. 로그에서 서버 오류 코드와 최초 실패 원인을 확인합니다. 3. 오류 행 파일의 컬럼 수, 타입, NULL, 날짜 형식, 인코딩을 원본과 비교합니다. 4. 수정한 소량 파일로 재검증한 뒤 실패 행만 다시 입력합니다. 5. 대상 테이블의 최종 건수와 시간 범위, 표본 행을 확인합니다. 자격 증명과 원문 민감 데이터가 로그·오류 행 파일에 남을 수 있으므로 접근 권한과 보존 기간을 설정합니다. --- title: "11.10.1 Sparse ARRAY와 선택 컬럼 Append API" url: https://docs.machbase.com/kr/dbms/development-tools-integration/data-input-load-export/array-append/ language: kr kind: page --- # 11.10.1 Sparse ARRAY와 선택 컬럼 Append API Machbase DBMS 8.7.0에서는 고정 길이 `ARRAY`의 일부 위치만 입력할 수 있습니다. 행마다 입력 위치가 달라지는 경우에는 희소 ARRAY를 사용하고, 여러 Append 행이 같은 위치를 입력하는 경우에는 Append Open 단계에서 선택 컬럼을 지정합니다. 희소 ARRAY는 **한 컬럼에 넣을 값의 표현 방식**이고, 선택 컬럼은 **한 행에서 입력할 컬럼이나 요소를 고르는 방식**입니다. 일반 Open으로 전체 행을 입력할 때도 ARRAY 컬럼에 희소 객체를 전달할 수 있습니다. Node.js는 `appendOpen()`의 컬럼 정의 인자가 필수이므로 전체 컬럼을 나열하는 방식으로 같은 입력을 수행합니다. `ARRAY` 타입 선언, 일반 입력, 조회와 SDK별 밀집 ARRAY 처리는 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ## 입력 방식 선택 | 요구사항 | 권장 방식 | |---|---| | SQL 한 행에서 값이 있는 위치만 지정 | `ARRAY_SPARSE(position => value, ...)` | | 여러 Append 행이 항상 같은 위치를 입력 | Append Open의 `A[0]`, `A[3]` 대상 | | 일반 Open으로 전체 행을 입력하며 ARRAY 위치가 달라짐 | 전체 행의 ARRAY 값에 SDK 희소 객체 전달 | | 일부 컬럼만 입력하며 ARRAY 위치도 달라짐 | 선택 목록의 whole `A` 대상과 SDK 희소 객체 | | 모든 요소가 NULL인 non-NULL ARRAY | 빈 희소 객체 | | ARRAY 자체가 NULL | SQL `NULL` 또는 SDK의 whole-NULL 값 | 위치는 SQL과 모든 Machbase 전용 SDK API에서 0부터 시작합니다. ## SQL sparse 입력 ### ARRAY_SPARSE 대상 컬럼이 있는 INSERT 또는 UPDATE 문맥에서는 위치와 값만 지정합니다. ```sql CREATE LOG TABLE ARRAY_APPEND_EXAMPLE ( ID LONG, A INT32[4] ); INSERT INTO ARRAY_APPEND_EXAMPLE (ID, A) VALUES (1, ARRAY_SPARSE(0 => 10, 3 => 40)); ``` SELECT처럼 대상 타입을 추론할 수 없는 문맥에서는 요소 타입과 요소 수를 먼저 지정합니다. ```sql SELECT ARRAY_SPARSE(INT32[4], 0 => 10, 3 => 40); SELECT ARRAY_SPARSE(DECIMAL(12,4)[4], 1 => 1.2500); ``` - 위치는 `0..cardinality-1` 범위의 정수 literal이어야 합니다. - pair 순서는 자유지만 같은 위치를 중복 지정할 수 없습니다. - 생략한 위치와 `position => NULL`은 요소 NULL입니다. - `ARRAY_SPARSE()` 또는 `ARRAY_SPARSE(INT32[4])`는 모든 요소가 NULL인 ARRAY입니다. - 배열 전체 NULL은 `ARRAY_SPARSE()`가 아니라 SQL `NULL`로 입력합니다. - 잘못된 위치나 요소 변환은 문장 전체를 실패시킵니다. ### Direct sparse shorthand `ARRAY_SPARSE` 래퍼 없이 bracket 안에 위치와 value pair를 직접 쓸 수 있습니다. ```sql INSERT INTO ARRAY_APPEND_EXAMPLE (ID, A) VALUES (2, [0 => 10, 3 => 40]); SELECT [1 => 12, 33 => 23]; ``` 대상 ARRAY가 있으면 대상 타입과 요소 수를 사용합니다. standalone에서는 밀집 ARRAY와 같은 숫자 공통 타입을 추론하고 요소 수를 `가장 큰 position + 1`로 결정합니다. 따라서 두 번째 예제는 `INT32[34]`입니다. standalone all-NULL 희소는 요소 타입을 알 수 없어 오류입니다. 이 경우 `ARRAY_SPARSE(TYPE[N], ...)` 형식을 사용합니다. `[]`는 기존 밀집 empty constructor로 유지되며 `ARRAY[0 => 1]`은 지원하지 않습니다. ### INSERT target에 위치 지정 여러 행이 같은 위치를 입력하면 컬럼 목록에 요소 대상을 직접 지정합니다. ```sql INSERT INTO ARRAY_APPEND_EXAMPLE (ID, A[0], A[3]) VALUES (2, 10, 40); -- A는 존재하지만 모든 element가 NULL입니다. INSERT INTO ARRAY_APPEND_EXAMPLE (ID, A[0], A[3]) VALUES (3, NULL, NULL); -- A 자체가 NULL입니다. INSERT INTO ARRAY_APPEND_EXAMPLE (ID) VALUES (4); ``` 같은 문장에서 `A`와 `A[0]`을 함께 지정하거나 같은 요소를 두 번 지정할 수 없습니다. 스칼라 컬럼이나 범위 밖 위치를 요소 대상으로 사용하면 오류입니다. 요소 위치를 지정한 대상은 `INSERT ... VALUES`와 Append 선택 대상에서 지원합니다. `INSERT ... SELECT`와 `UPDATE ... SET A[0] = ...`에서는 지원하지 않습니다. ## Append 공통 규칙 ### 전체 행 입력과 선택 입력 일반 Open은 테이블의 입력 컬럼 순서를 사용합니다. 선택 Open은 지정한 대상 목록의 순서를 사용합니다. 아래 실습의 일반 Open에서는 `ID`, `A` 순서로 두 값을 전달하며, `_arrival_time` 값은 별도로 넣지 않습니다. 이 예제의 각 API가 LOG의 자동 시각을 처리합니다. 명시적인 수신 시각 입력은 해당 SDK의 시간 지정 API를 사용합니다. Node.js는 일반/선택 Open이 별도 메서드로 나뉘지 않습니다. 전체 입력도 `name`과 `type`을 가진 컬럼 정의가 필요하며, 이 실습에서는 `ID`와 `A`를 테이블 순서대로 나열합니다. `appendOpen(table)`만 호출하거나 빈 컬럼 목록을 넘기는 방식은 지원하지 않습니다. ### 실습 준비와 재실행 일반 예제와 선택 예제를 분리해 실행할 수 있도록 서로 다른 테이블을 사용합니다. 각 예제를 실행하기 전에 연결할 데이터베이스에서 아래 준비 SQL을 실행합니다. ```sql CREATE LOG TABLE ARRAY_APPEND_FULL_EXAMPLE (ID LONG, A INT32[4]); ``` 일반 예제의 파일명에는 `full`을 붙입니다. 선택 예제는 앞에서 만든 `ARRAY_APPEND_EXAMPLE`을 사용합니다. C 선택 예제는 이 테이블을 직접 다시 생성하므로 실습 전용 이름인지 확인하십시오. 다른 SDK의 선택 예제를 실행할 때도 같은 구조의 빈 `ARRAY_APPEND_EXAMPLE`을 준비합니다. 각 SDK 예제는 **독립 실행**합니다. 다른 SDK를 같은 테이블에 연달아 실행하면 ID가 중복됩니다. 재실행은 결과를 확인한 뒤 [실습 정리](#sparse-append-cleanup)를 수행하고 해당 테이블을 다시 생성합니다. 서버 주소·포트·계정은 실제 실습 환경에 맞춥니다. ### 공통 결과 두 입력 방식은 다음과 같은 네 행을 만듭니다. 일반 예제는 ID=1도 희소 객체로 입력하고, 선택 예제는 ID=1에 고정 요소 대상을 사용합니다. ```text ID=1 A=[10,null,null,40] 희소 객체 또는 고정 요소 대상 ID=2 A=[null,200,null,400] ARRAY 컬럼에 희소 객체 ID=3 A=[null,null,null,null] 빈 sparse 객체 ID=4 A=NULL whole NULL ``` 희소 객체에서 생략한 ARRAY 요소는 요소 NULL이 됩니다. `entry_count == 0`이나 빈 희소 객체는 길이가 0인 배열이 아니라, 선언한 길이만큼의 요소가 모두 NULL인 배열입니다. 전체 NULL은 배열 값 자체가 없다는 뜻이며 `ARRAY_LENGTH` 결과도 NULL입니다. ### 선택 Open의 규칙 다음 규칙은 **선택 Open의 대상 목록**에 적용됩니다. 일반 Open에서 컬럼 인자를 생략하는 것을 “빈 선택 목록”과 혼동하지 마십시오. 선택 목록에 없는 일반 컬럼은 기존 Append 규칙에 따라 처리됩니다. - nullable 컬럼은 NULL을 사용합니다. - DEFAULT가 있는 컬럼은 DEFAULT를 사용합니다. - 값을 반드시 요구하는 컬럼이 빠지면 Append Open 또는 행 입력이 실패합니다. 선택 대상 목록은 비어 있을 수 없으며 대소문자를 무시해 중복될 수 없습니다. whole ARRAY 대상과 같은 ARRAY의 요소 대상을 함께 열 수 없습니다. 한 번 Append Open한 뒤에는 각 행의 값 개수와 순서가 대상 목록과 정확히 같아야 합니다. 행 입력 중 오류가 발생해도 열린 Append 핸들은 닫아야 합니다. Append Open 자체가 실패하면 SDK가 내부 상태를 정리하므로 같은 연결을 다시 사용할 수 있습니다. ## C SQLCLI ARRAY 입력과 조회에는 다음 공개 타입을 사용합니다. | 타입 또는 상수 | 용도 | |---|---| | `SQL_MACHBASE_ARRAY` | SQL ARRAY 타입 식별 | | `SQL_C_MACHBASE_ARRAY` | 밀집 ARRAY 조회와 bind 디스크립터 | | `SQL_C_MACHBASE_SPARSE_ARRAY` | prepared 희소 ARRAY 입력 | | `SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH` | Append 희소 디스크립터 식별 | ### 일반 Open으로 희소 ARRAY 입력 `SQLAppendOpen()`으로 열고 `SQL_APPEND_PARAM` 배열에 `ID`와 `A`를 전달합니다. `A`의 `mVar.mData`에는 `SQL_MACHBASE_SPARSE_ARRAY_DESC` 주소를, `mVar.mLength`에는 `SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH`를 지정합니다. 컬럼 선택은 하지 않으며, 값 개수를 명시하는 `SQLAppendDataV3(..., row, 2)`를 사용합니다. 구형 `SQLAppendData(void *[])`에 이 디스크립터를 그대로 넘기는 예제는 아닙니다. 앞의 준비 SQL로 만든 빈 `ARRAY_APPEND_FULL_EXAMPLE`을 사용합니다. 디스크립터와 위치·값· indicator 버퍼는 Append 호출이 끝날 때까지 유효해야 합니다. 같은 열린 핸들에서 첫 행과 두 번째 행의 입력 위치를 바꾸고, 빈 희소 배열과 전체 NULL도 입력합니다. ```c /* sparse_append_full.c */ #include #include #include #include static int ok(SQLRETURN rc) { return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO; } static void fail(SQLHENV env, SQLHDBC dbc, SQLHSTMT stmt, const char *where) { SQLCHAR state[6] = {0}; SQLCHAR message[1024] = {0}; SQLINTEGER native = 0; SQLSMALLINT length = 0; SQLError(env, dbc, stmt, state, &native, message, (SQLSMALLINT)sizeof(message), &length); fprintf(stderr, "%s: %s %d %s\n", where, state, (int)native, message); exit(EXIT_FAILURE); } int main(void) { SQLHENV env = SQL_NULL_HENV; SQLHDBC dbc = SQL_NULL_HDBC; SQLHSTMT sql = SQL_NULL_HSTMT; SQLHSTMT append = SQL_NULL_HSTMT; SQLCHAR conn[] = "SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD=MANAGER;CONNTYPE=1"; SQL_APPEND_PARAM row[2]; SQLUSMALLINT positions[2] = {0, 3}; SQLINTEGER values[2] = {10, 40}; SQLLEN indicators[2] = {0, 0}; SQL_MACHBASE_SPARSE_ARRAY_DESC sparse; SQLBIGINT success = 0; SQLBIGINT failure = 0; SQLINTEGER id; SQLLEN idInd; SQLLEN textInd; SQLCHAR text[128]; if (!ok(SQLAllocEnv(&env)) || !ok(SQLAllocConnect(env, &dbc)) || !ok(SQLDriverConnect(dbc, NULL, conn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT)) || !ok(SQLAllocStmt(dbc, &sql)) || !ok(SQLAllocStmt(dbc, &append))) fail(env, dbc, SQL_NULL_HSTMT, "connect"); memset(&sparse, 0, sizeof(sparse)); sparse.struct_size = sizeof(sparse); sparse.element_c_type = SQL_C_SLONG; sparse.cardinality = 4; sparse.entry_count = 2; sparse.positions = positions; sparse.values = values; sparse.value_stride = sizeof(values[0]); sparse.element_indicators = indicators; memset(row, 0, sizeof(row)); if (!ok(SQLAppendOpen(append, (SQLCHAR*)"ARRAY_APPEND_FULL_EXAMPLE", 0))) fail(env, dbc, append, "sparse open"); row[0].mLong = 1; row[1].mVar.mData = &sparse; row[1].mVar.mLength = SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH; if (!ok(SQLAppendDataV3(append, row, 2))) { SQLAppendClose(append, &success, &failure); fail(env, dbc, append, "first sparse row"); } positions[0] = 1; values[0] = 200; values[1] = 400; row[0].mLong = 2; if (!ok(SQLAppendDataV3(append, row, 2))) { SQLAppendClose(append, &success, &failure); fail(env, dbc, append, "sparse row"); } row[0].mLong = 3; sparse.entry_count = 0; if (!ok(SQLAppendDataV3(append, row, 2))) { SQLAppendClose(append, &success, &failure); fail(env, dbc, append, "empty sparse row"); } row[0].mLong = 4; row[1].mVar.mData = NULL; row[1].mVar.mLength = 0; if (!ok(SQLAppendDataV3(append, row, 2))) { SQLAppendClose(append, &success, &failure); fail(env, dbc, append, "whole NULL row"); } success = 0; failure = 0; if (!ok(SQLAppendClose(append, &success, &failure)) || success != 4 || failure != 0) fail(env, dbc, append, "sparse close"); if (!ok(SQLExecDirect(sql, (SQLCHAR*)"SELECT ID,A FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID", SQL_NTS))) fail(env, dbc, sql, "select"); if (!ok(SQLBindCol(sql, 1, SQL_C_SLONG, &id, sizeof(id), &idInd)) || !ok(SQLBindCol(sql, 2, SQL_C_CHAR, text, sizeof(text), &textInd))) fail(env, dbc, sql, "bind verify"); for (;;) { SQLRETURN fetch = SQLFetch(sql); if (fetch == SQL_NO_DATA) break; if (!ok(fetch)) fail(env, dbc, sql, "fetch verify"); printf("%d %s\n", (int)id, textInd == SQL_NULL_DATA ? "NULL" : (char*)text); } SQLFreeStmt(append, SQL_DROP); SQLFreeStmt(sql, SQL_DROP); SQLDisconnect(dbc); SQLFreeConnect(dbc); SQLFreeEnv(env); return EXIT_SUCCESS; } ``` ```bash cc -I"$MACHBASE_HOME/include" sparse_append_full.c \ -L"$MACHBASE_HOME/lib" -lmachbasecli -lm -ldl -lrt -pthread \ -o sparse_append_full LD_LIBRARY_PATH="$MACHBASE_HOME/lib" ./sparse_append_full ``` 프로그램은 Close의 성공 4건·실패 0건을 확인하고, 조회한 ID와 배열을 출력합니다. 상세 기대값은 [결과 확인](#결과-확인)과 비교합니다. 입력 오류가 발생하면 Append를 닫은 뒤 프로세스를 종료합니다. 재시도 전에 테이블에 실제로 반영된 행을 확인합니다. ### 선택 컬럼 Open으로 입력 `SQLAppendOpenColumns()`와 와이드 문자 버전은 마지막 원소가 `NULL`인 컬럼명 포인터 배열을 받습니다. 별도의 컬럼 수 인자는 없습니다. ```c SQLRETURN SQL_API SQLAppendOpenColumns( SQLHSTMT aStmtHandle, SQLCHAR *aTableName, SQLCHAR **aColumnNames, SQLINTEGER aErrorCheckCount); SQLRETURN SQL_API SQLAppendOpenColumnsW( SQLHSTMT aStmtHandle, SQLWCHAR *aTableName, SQLWCHAR **aColumnNames, SQLINTEGER aErrorCheckCount); ``` `aColumnNames == NULL`이거나 첫 원소가 `NULL`이면 오류입니다. C 포인터에는 배열 길이 정보가 없으므로 호출자는 반드시 마지막 `NULL`까지 유효한 배열을 제공해야 합니다. 종단 `NULL`을 빠뜨리면 배열 경계를 벗어나 읽을 수 있으므로 안전하게 진단된다고 가정하면 안 됩니다. 다음 `sparse_append.c`는 테이블을 만들고 네 행을 Append한 뒤 결과를 출력합니다. ```c /* sparse_append.c */ #include #include #include #include static int ok(SQLRETURN rc) { return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO; } static void fail(SQLHENV env, SQLHDBC dbc, SQLHSTMT stmt, const char *where) { SQLCHAR state[6] = {0}; SQLCHAR message[1024] = {0}; SQLINTEGER native = 0; SQLSMALLINT length = 0; SQLError(env, dbc, stmt, state, &native, message, (SQLSMALLINT)sizeof(message), &length); fprintf(stderr, "%s: %s %d %s\n", where, state, (int)native, message); exit(EXIT_FAILURE); } int main(void) { SQLHENV env = SQL_NULL_HENV; SQLHDBC dbc = SQL_NULL_HDBC; SQLHSTMT sql = SQL_NULL_HSTMT; SQLHSTMT append = SQL_NULL_HSTMT; SQLCHAR conn[] = "SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD=MANAGER;CONNTYPE=1"; SQLCHAR *fixed[] = {(SQLCHAR*)"ID", (SQLCHAR*)"A[0]", (SQLCHAR*)"A[3]", NULL}; SQLCHAR *whole[] = {(SQLCHAR*)"ID", (SQLCHAR*)"A", NULL}; SQL_APPEND_PARAM row[3]; SQLUSMALLINT positions[2] = {1, 3}; SQLINTEGER values[2] = {200, 400}; SQLLEN indicators[2] = {0, 0}; SQL_MACHBASE_SPARSE_ARRAY_DESC sparse; SQLBIGINT success = 0; SQLBIGINT failure = 0; SQLINTEGER id; SQLLEN idInd; SQLLEN textInd; SQLCHAR text[128]; if (!ok(SQLAllocEnv(&env)) || !ok(SQLAllocConnect(env, &dbc)) || !ok(SQLDriverConnect(dbc, NULL, conn, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT)) || !ok(SQLAllocStmt(dbc, &sql)) || !ok(SQLAllocStmt(dbc, &append))) fail(env, dbc, SQL_NULL_HSTMT, "connect"); SQLExecDirect(sql, (SQLCHAR*)"DROP TABLE ARRAY_APPEND_EXAMPLE", SQL_NTS); if (!ok(SQLExecDirect(sql, (SQLCHAR*)"CREATE LOG TABLE ARRAY_APPEND_EXAMPLE(ID LONG,A INT32[4])", SQL_NTS))) fail(env, dbc, sql, "create"); memset(row, 0, sizeof(row)); row[0].mLong = 1; row[1].mInteger = 10; row[2].mInteger = 40; if (!ok(SQLAppendOpenColumns(append, (SQLCHAR*)"ARRAY_APPEND_EXAMPLE", fixed, 0))) fail(env, dbc, append, "fixed open"); if (!ok(SQLAppendDataV3(append, row, 3))) { SQLAppendClose(append, &success, &failure); fail(env, dbc, append, "fixed row"); } if (!ok(SQLAppendClose(append, &success, &failure)) || success != 1 || failure != 0) fail(env, dbc, append, "fixed close"); memset(&sparse, 0, sizeof(sparse)); sparse.struct_size = sizeof(sparse); sparse.element_c_type = SQL_C_SLONG; sparse.cardinality = 4; sparse.entry_count = 2; sparse.positions = positions; sparse.values = values; sparse.value_stride = sizeof(values[0]); sparse.element_indicators = indicators; memset(row, 0, sizeof(row)); if (!ok(SQLAppendOpenColumns(append, (SQLCHAR*)"ARRAY_APPEND_EXAMPLE", whole, 0))) fail(env, dbc, append, "sparse open"); row[0].mLong = 2; row[1].mVar.mData = &sparse; row[1].mVar.mLength = SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH; if (!ok(SQLAppendDataV3(append, row, 2))) { SQLAppendClose(append, &success, &failure); fail(env, dbc, append, "sparse row"); } row[0].mLong = 3; sparse.entry_count = 0; if (!ok(SQLAppendDataV3(append, row, 2))) { SQLAppendClose(append, &success, &failure); fail(env, dbc, append, "empty sparse row"); } row[0].mLong = 4; row[1].mVar.mData = NULL; row[1].mVar.mLength = 0; if (!ok(SQLAppendDataV3(append, row, 2))) { SQLAppendClose(append, &success, &failure); fail(env, dbc, append, "whole NULL row"); } success = 0; failure = 0; if (!ok(SQLAppendClose(append, &success, &failure)) || success != 3 || failure != 0) fail(env, dbc, append, "sparse close"); if (!ok(SQLExecDirect(sql, (SQLCHAR*)"SELECT ID,A FROM ARRAY_APPEND_EXAMPLE ORDER BY ID", SQL_NTS))) fail(env, dbc, sql, "select"); if (!ok(SQLBindCol(sql, 1, SQL_C_SLONG, &id, sizeof(id), &idInd)) || !ok(SQLBindCol(sql, 2, SQL_C_CHAR, text, sizeof(text), &textInd))) fail(env, dbc, sql, "bind verify"); for (;;) { SQLRETURN fetch = SQLFetch(sql); if (fetch == SQL_NO_DATA) break; if (!ok(fetch)) fail(env, dbc, sql, "fetch verify"); printf("%d %s\n", (int)id, textInd == SQL_NULL_DATA ? "NULL" : (char*)text); } SQLFreeStmt(append, SQL_DROP); SQLFreeStmt(sql, SQL_DROP); SQLDisconnect(dbc); SQLFreeConnect(dbc); SQLFreeEnv(env); return EXIT_SUCCESS; } ``` 다음과 같이 빌드하고 실행합니다. ```bash cc -I"$MACHBASE_HOME/include" sparse_append.c \ -L"$MACHBASE_HOME/lib" -lmachbasecli -lm -ldl -lrt -pthread \ -o sparse_append LD_LIBRARY_PATH="$MACHBASE_HOME/lib" ./sparse_append ``` 디스크립터 위치는 0부터 시작하는 인덱스입니다. 정렬하지 않아도 되지만 중복될 수 없습니다. entry indicator가 `SQL_NULL_DATA`이면 해당 위치는 요소 NULL입니다. `entry_count == 0`은 빈 희소 ARRAY이고, 배열 전체 NULL은 `mVar.mData = NULL`, `mVar.mLength = 0`으로 지정합니다. ## C++ SQLCLI ### 일반 Open으로 희소 ARRAY 입력 C와 같은 디스크립터와 `SQLAppendOpen()`을 사용합니다. 위치·값 버퍼는 `std::array`로 유지하고, 성공·예외 경로 모두에서 Append를 닫습니다. 일반 실습 테이블을 먼저 준비합니다. ```cpp /* sparse_append_full.cpp */ #include #include #include #include static bool ok(SQLRETURN rc) { return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO; } struct Handles { SQLHENV env{SQL_NULL_HENV}; SQLHDBC dbc{SQL_NULL_HDBC}; SQLHSTMT stmt{SQL_NULL_HSTMT}; ~Handles() { if (stmt != SQL_NULL_HSTMT) SQLFreeStmt(stmt, SQL_DROP); if (dbc != SQL_NULL_HDBC) { SQLDisconnect(dbc); SQLFreeConnect(dbc); } if (env != SQL_NULL_HENV) SQLFreeEnv(env); } }; int main() { Handles h; SQLCHAR conn[] = "SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD=MANAGER;CONNTYPE=1"; if (!ok(SQLAllocEnv(&h.env)) || !ok(SQLAllocConnect(h.env, &h.dbc)) || !ok(SQLDriverConnect(h.dbc, nullptr, conn, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_NOPROMPT)) || !ok(SQLAllocStmt(h.dbc, &h.stmt))) throw std::runtime_error("connect"); std::array positions{0, 3}; std::array values{10, 40}; std::array indicators{0, 0}; SQL_MACHBASE_SPARSE_ARRAY_DESC sparse{}; sparse.struct_size = sizeof(sparse); sparse.element_c_type = SQL_C_SLONG; sparse.cardinality = 4; sparse.entry_count = 2; sparse.positions = positions.data(); sparse.values = values.data(); sparse.value_stride = sizeof(values[0]); sparse.element_indicators = indicators.data(); std::array row{}; row[1].mVar.mData = &sparse; row[1].mVar.mLength = SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH; SQLBIGINT success = 0, failure = 0; if (!ok(SQLAppendOpen(h.stmt, (SQLCHAR*)"ARRAY_APPEND_FULL_EXAMPLE", 0))) throw std::runtime_error("SQLAppendOpen"); try { for (int id = 1; id <= 4; ++id) { row[0].mLong = id; if (id == 2) { positions[0] = 1; values[0] = 200; values[1] = 400; } else if (id == 3) { sparse.entry_count = 0; } else if (id == 4) { row[1].mVar.mData = nullptr; row[1].mVar.mLength = 0; } if (!ok(SQLAppendDataV3(h.stmt, row.data(), 2))) throw std::runtime_error("SQLAppendDataV3"); } } catch (...) { SQLAppendClose(h.stmt, &success, &failure); throw; } if (!ok(SQLAppendClose(h.stmt, &success, &failure)) || success != 4 || failure != 0) throw std::runtime_error("SQLAppendClose"); if (!ok(SQLExecDirect(h.stmt, (SQLCHAR*)"SELECT ID,A FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID", SQL_NTS))) throw std::runtime_error("verify query"); SQLINTEGER id{}; SQLLEN idInd{}, arrayInd{}; SQLCHAR value[128]{}; if (!ok(SQLBindCol(h.stmt, 1, SQL_C_SLONG, &id, sizeof(id), &idInd)) || !ok(SQLBindCol(h.stmt, 2, SQL_C_CHAR, value, sizeof(value), &arrayInd))) throw std::runtime_error("bind verify"); for (;;) { SQLRETURN fetch = SQLFetch(h.stmt); if (fetch == SQL_NO_DATA) break; if (!ok(fetch)) throw std::runtime_error("fetch verify"); std::cout << id << ' ' << (arrayInd == SQL_NULL_DATA ? "NULL" : (char*)value) << '\n'; } } ``` ```bash c++ -std=c++11 -I"$MACHBASE_HOME/include" sparse_append_full.cpp \ -L"$MACHBASE_HOME/lib" -lmachbasecli -lm -ldl -lrt -pthread \ -o sparse_append_full_cpp LD_LIBRARY_PATH="$MACHBASE_HOME/lib" ./sparse_append_full_cpp ``` ### 선택 컬럼 Open으로 입력 C++ 전용 전송 객체를 새로 만들지 않고 SQLCLI 디스크립터를 사용합니다. 다음 예제는 RAII 래퍼로 close를 보장하고 C++ 컨테이너가 살아 있는 동안 디스크립터를 전송합니다. ```cpp /* sparse_append.cpp */ #include #include #include #include static bool ok(SQLRETURN rc) { return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO; } struct Handles { SQLHENV env{SQL_NULL_HENV}; SQLHDBC dbc{SQL_NULL_HDBC}; SQLHSTMT stmt{SQL_NULL_HSTMT}; ~Handles() { if (stmt != SQL_NULL_HSTMT) SQLFreeStmt(stmt, SQL_DROP); if (dbc != SQL_NULL_HDBC) { SQLDisconnect(dbc); SQLFreeConnect(dbc); } if (env != SQL_NULL_HENV) SQLFreeEnv(env); } }; static void append(Handles& h, SQLCHAR **columns, SQL_APPEND_PARAM *row, SQLINTEGER count) { SQLBIGINT success = 0, failure = 0; if (!ok(SQLAppendOpenColumns(h.stmt, (SQLCHAR*)"ARRAY_APPEND_EXAMPLE", columns, 0))) throw std::runtime_error("SQLAppendOpenColumns"); try { if (!ok(SQLAppendDataV3(h.stmt, row, count))) throw std::runtime_error("SQLAppendDataV3"); } catch (...) { SQLAppendClose(h.stmt, &success, &failure); throw; } if (!ok(SQLAppendClose(h.stmt, &success, &failure)) || failure != 0) throw std::runtime_error("SQLAppendClose"); } int main() { Handles h; SQLCHAR conn[] = "SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD=MANAGER;CONNTYPE=1"; if (!ok(SQLAllocEnv(&h.env)) || !ok(SQLAllocConnect(h.env, &h.dbc)) || !ok(SQLDriverConnect(h.dbc, nullptr, conn, SQL_NTS, nullptr, 0, nullptr, SQL_DRIVER_NOPROMPT)) || !ok(SQLAllocStmt(h.dbc, &h.stmt))) throw std::runtime_error("connect"); SQLCHAR *fixed[] = {(SQLCHAR*)"ID", (SQLCHAR*)"A[0]", (SQLCHAR*)"A[3]", nullptr}; std::array row{}; row[0].mLong = 1; row[1].mInteger = 10; row[2].mInteger = 40; append(h, fixed, row.data(), 3); std::array pos{1, 3}; std::array val{200, 400}; std::array ind{0, 0}; SQL_MACHBASE_SPARSE_ARRAY_DESC sparse{}; sparse.struct_size = sizeof(sparse); sparse.element_c_type = SQL_C_SLONG; sparse.cardinality = 4; sparse.entry_count = 2; sparse.positions = pos.data(); sparse.values = val.data(); sparse.value_stride = sizeof(val[0]); sparse.element_indicators = ind.data(); SQLCHAR *whole[] = {(SQLCHAR*)"ID", (SQLCHAR*)"A", nullptr}; std::array sparseRow{}; sparseRow[0].mLong = 2; sparseRow[1].mVar.mData = &sparse; sparseRow[1].mVar.mLength = SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH; append(h, whole, sparseRow.data(), 2); sparse.entry_count = 0; sparseRow[0].mLong = 3; append(h, whole, sparseRow.data(), 2); sparseRow[0].mLong = 4; sparseRow[1].mVar.mData = nullptr; sparseRow[1].mVar.mLength = 0; append(h, whole, sparseRow.data(), 2); if (!ok(SQLExecDirect(h.stmt, (SQLCHAR*)"SELECT ID,A FROM ARRAY_APPEND_EXAMPLE ORDER BY ID", SQL_NTS))) throw std::runtime_error("verify query"); SQLINTEGER id{}; SQLLEN idInd{}, arrayInd{}; SQLCHAR value[128]{}; if (!ok(SQLBindCol(h.stmt, 1, SQL_C_SLONG, &id, sizeof(id), &idInd)) || !ok(SQLBindCol(h.stmt, 2, SQL_C_CHAR, value, sizeof(value), &arrayInd))) throw std::runtime_error("bind verify"); for (;;) { SQLRETURN fetch = SQLFetch(h.stmt); if (fetch == SQL_NO_DATA) break; if (!ok(fetch)) throw std::runtime_error("fetch verify"); std::cout << id << ' ' << (arrayInd == SQL_NULL_DATA ? "NULL" : (char*)value) << '\n'; } } ``` ```bash c++ -std=c++11 -I"$MACHBASE_HOME/include" sparse_append.cpp \ -L"$MACHBASE_HOME/lib" -lmachbasecli -lm -ldl -lrt -pthread \ -o sparse_append_cpp ``` ## Machbase ODBC extension ### 일반 Open으로 희소 ARRAY 입력 Machbase 드라이버를 직접 링크하는 C 프로그램은 [C 일반 Open 예제](#c-full-open)의 `sparse_append_full.c`를 그대로 사용합니다. 이 소스도 `SQLAppendOpen()` 뒤 `SQLAppendDataV3()`로 희소 디스크립터를 전달하며 `OpenColumns`는 호출하지 않습니다. 같은 버전의 헤더와 ODBC extension 라이브러리로 빌드합니다. ```bash cc -I"$MACHBASE_HOME/include" sparse_append_full.c \ -L"$MACHBASE_HOME/lib" -lmachbasecli_dll -lm -ldl -lrt -pthread \ -o sparse_append_full_odbc LD_LIBRARY_PATH="$MACHBASE_HOME/lib" ./sparse_append_full_odbc ``` 실행 전 일반 실습 테이블을 준비합니다. 이 예제의 핸들은 Machbase 드라이버가 직접 생성한 것이며, 범용 ODBC Driver Manager의 핸들과 섞지 않습니다. ### 선택 컬럼 Open으로 입력 Machbase 드라이버 라이브러리를 직접 링크하고 `machbase_sqlcli.h`를 사용하는 ODBC C 애플리케이션은 같은 extension 함수를 사용할 수 있습니다. 다음 예제는 direct Machbase 드라이버 API로 네 행을 입력합니다. 테이블은 앞 절의 DDL로 미리 만듭니다. ```c /* sparse_odbc.c */ #include #include #include static int ok(SQLRETURN rc) { return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO; } int main(void) { SQLHENV env = SQL_NULL_HENV; SQLHDBC dbc = SQL_NULL_HDBC; SQLHSTMT stmt = SQL_NULL_HSTMT; SQLBIGINT success = 0, failure = 0; SQLCHAR connection[] = "SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD=MANAGER;CONNTYPE=1"; if (!ok(SQLAllocEnv(&env)) || !ok(SQLAllocConnect(env, &dbc)) || !ok(SQLDriverConnect(dbc, NULL, connection, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT)) || !ok(SQLAllocStmt(dbc, &stmt))) return 1; SQLCHAR *fixed[] = {(SQLCHAR*)"ID", (SQLCHAR*)"A[0]", (SQLCHAR*)"A[3]", NULL}; SQL_APPEND_PARAM row[3] = {0}; row[0].mLong = 1; row[1].mInteger = 10; row[2].mInteger = 40; if (!ok(SQLAppendOpenColumns(stmt, (SQLCHAR*)"ARRAY_APPEND_EXAMPLE", fixed, 0))) return 2; if (!ok(SQLAppendDataV3(stmt, row, 3))) { SQLAppendClose(stmt, &success, &failure); return 2; } if (!ok(SQLAppendClose(stmt, &success, &failure)) || success != 1 || failure != 0) return 2; SQLUSMALLINT positions[2] = {1, 3}; SQLINTEGER values[2] = {200, 400}; SQLLEN indicators[2] = {0, 0}; SQL_MACHBASE_SPARSE_ARRAY_DESC sparse = {0}; sparse.struct_size = sizeof(sparse); sparse.element_c_type = SQL_C_SLONG; sparse.cardinality = 4; sparse.entry_count = 2; sparse.positions = positions; sparse.values = values; sparse.value_stride = sizeof(values[0]); sparse.element_indicators = indicators; SQLCHAR *whole[] = {(SQLCHAR*)"ID", (SQLCHAR*)"A", NULL}; memset(row, 0, sizeof(row)); row[0].mLong = 2; row[1].mVar.mData = &sparse; row[1].mVar.mLength = SQL_APPEND_SPARSE_ARRAY_DESC_LENGTH; if (!ok(SQLAppendOpenColumns(stmt, (SQLCHAR*)"ARRAY_APPEND_EXAMPLE", whole, 0))) return 3; if (!ok(SQLAppendDataV3(stmt, row, 2))) { SQLAppendClose(stmt, &success, &failure); return 3; } sparse.entry_count = 0; row[0].mLong = 3; if (!ok(SQLAppendDataV3(stmt, row, 2))) { SQLAppendClose(stmt, &success, &failure); return 3; } row[0].mLong = 4; row[1].mVar.mData = NULL; row[1].mVar.mLength = 0; if (!ok(SQLAppendDataV3(stmt, row, 2))) { SQLAppendClose(stmt, &success, &failure); return 3; } success = 0; failure = 0; if (!ok(SQLAppendClose(stmt, &success, &failure)) || success != 3 || failure != 0) return 3; SQLFreeStmt(stmt, SQL_DROP); SQLDisconnect(dbc); SQLFreeConnect(dbc); SQLFreeEnv(env); puts("ODBC sparse append OK"); return 0; } ``` ```bash cc sparse_odbc.c -I/opt/machbase/include -L/opt/machbase/lib \ -lmachbasecli_dll -lm -ldl -lrt -pthread -o sparse_odbc LD_LIBRARY_PATH=/opt/machbase/lib ./sparse_odbc ``` {{< callout type="warning" >}} 범용 ODBC Driver Manager가 만든 문장 핸들을 direct SQLCLI extension에 넘기면 핸들 ABI가 다르므로 혼용하지 마십시오. 선택 컬럼 Append는 Machbase 드라이버 extension과 direct 드라이버 핸들을 사용해야 합니다. 범용 ODBC API에는 Append Open 선택 대상이 없습니다. {{< /callout >}} ## JDBC ### 일반 Open으로 희소 ARRAY 입력 `executeAppendOpen(table, errorCheckCount)` 오버로드를 사용합니다. 반환된 메타데이터에 맞춰 `ID`와 `MachSparseArray`를 전달하고, 일반 실습 테이블의 자동 시각은 직접 넣지 않습니다. `null`은 전체 NULL, 빈 `MachSparseArray`는 모든 요소가 NULL인 배열입니다. `SparseAppendFull.java`로 저장하고 ARRAY 기능이 포함된 JDBC JAR로 실행합니다. ```java import com.machbase.jdbc.MachConnection; import com.machbase.jdbc.MachSparseArray; import com.machbase.jdbc.MachStatement; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class SparseAppendFull { public static void main(String[] args) throws Exception { try (MachConnection con = (MachConnection)DriverManager.getConnection( "jdbc:machbase://127.0.0.1:5656/machbasedb", "SYS", "MANAGER"); MachStatement st = (MachStatement)con.createStatement()) { Map entries = new HashMap(); entries.put(0, 10); entries.put(3, 40); MachSparseArray sparse = con.createSparseArrayOf("INT32", 4, entries); try (ResultSet opened = st.executeAppendOpen("ARRAY_APPEND_FULL_EXAMPLE", 0)) { try { ResultSetMetaData meta = opened.getMetaData(); for (int id = 1; id <= 4; id++) { if (id == 2) sparse.clear().set(1, 200).set(3, 400); if (id == 3) sparse.clear(); ArrayList row = new ArrayList(); row.add(Long.valueOf(id)); row.add(id == 4 ? null : sparse); st.executeAppendData(meta, row); } } finally { st.executeAppendClose(); } } try (ResultSet rs = st.executeQuery( "SELECT ID,A,ARRAY_LENGTH(A) " + "FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID")) { int count = 0; while (rs.next()) { System.out.println(rs.getLong(1) + " " + rs.getString(2)); count++; } if (count != 4) throw new IllegalStateException("Expected 4 rows"); } } } } ``` ```bash javac -cp "$MACHBASE_JDBC_JAR" SparseAppendFull.java java -cp ".:$MACHBASE_JDBC_JAR" SparseAppendFull ``` `MACHBASE_JDBC_JAR`에는 사용하는 JDBC JAR의 실제 경로를 설정합니다. 위 클래스 경로 구분자는 Linux 기준입니다. 오류 없이 네 행이 조회되는지 확인한 뒤 [공통 기대값](#결과-확인)과 비교합니다. ### 선택 컬럼 Open으로 입력 기존 `executeAppendOpen(String, int)`는 전체 행 API로 유지됩니다. 다음 오버로드에서 선택 대상을 지정합니다. ```java ResultSet executeAppendOpen(String tableName, String[] inputColumns, int errorCheckCount) ``` ```java import com.machbase.jdbc.MachConnection; import com.machbase.jdbc.MachSparseArray; import com.machbase.jdbc.MachStatement; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class SparseAppend { static void append(MachStatement st, String[] columns, Object[][] values) throws Exception { try (ResultSet metaResult = st.executeAppendOpen( "ARRAY_APPEND_EXAMPLE", columns, 0)) { ResultSetMetaData meta = metaResult.getMetaData(); try { for (Object[] value : values) { ArrayList row = new ArrayList(); for (Object item : value) row.add(item); st.executeAppendData(meta, row); } } finally { st.executeAppendClose(); } } } public static void main(String[] args) throws Exception { Class.forName("com.machbase.jdbc.MachDriver"); MachConnection con = (MachConnection)DriverManager.getConnection( "jdbc:machbase://127.0.0.1:5656/machbasedb", "SYS", "MANAGER"); try { try (MachStatement st = (MachStatement)con.createStatement()) { append(st, new String[] {"ID", "A[0]", "A[3]"}, new Object[][] {{1L, 10, 40}}); Map entries = new HashMap(); entries.put(1, 200); entries.put(3, 400); MachSparseArray sparse = con.createSparseArrayOf( "INT32", 4, entries); MachSparseArray empty = con.createSparseArrayOf( "INT32", 4, new HashMap()); append(st, new String[] {"ID", "A"}, new Object[][] { {2L, sparse}, {3L, empty}, {4L, null} }); try (ResultSet rs = st.executeQuery( "SELECT ID,A,ARRAY_LENGTH(A) " + "FROM ARRAY_APPEND_EXAMPLE ORDER BY ID")) { while (rs.next()) System.out.println( rs.getLong(1) + " " + rs.getString(2)); } } } finally { con.close(); } } } ``` `createSparseArrayOf()`에 전달하는 맵의 키는 0부터 시작하는 요소 위치입니다. `MachSparseArray.clear()`와 `set()`으로 같은 객체를 재사용할 수 있습니다. 빈 맵은 모든 요소가 NULL인 ARRAY이고 Java `null`은 배열 전체 NULL입니다. ## Python DB-API ### 컬럼 목록 없이 희소 ARRAY 입력 DB-API의 `append()`는 내부에서 Open·입력·Close를 처리합니다. `columns=`를 생략하고 각 행에 `ID`와 `SparseArray`를 전달합니다. 일반 실습 테이블을 먼저 준비하고 다음 코드를 `sparse_append_full.py`로 저장해 실행합니다. ```python from machbaseAPI import SparseArray, connect conn = connect(host="127.0.0.1", port=5656, user="SYS", password="MANAGER") try: first = SparseArray(4).set(0, 10).set(3, 40) second = SparseArray(4).set(1, 200).set(3, 400) empty = SparseArray(4) conn.append("ARRAY_APPEND_FULL_EXAMPLE", [ [1, first], [2, second], [3, empty], [4, None], ]) rows = conn.cursor(dictionary=False).execute( "SELECT ID,A,ARRAY_LENGTH(A) " "FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID" ).fetchall() expected = [ (1, [10, None, None, 40], 4), (2, [None, 200, None, 400], 4), (3, [None, None, None, None], 4), (4, None, None), ] assert rows == expected, rows print("Python full-row sparse append OK") finally: conn.close() ``` ```bash python3 sparse_append_full.py ``` 조회 결과가 기대값과 같으면 `Python full-row sparse append OK`를 출력합니다. ### 선택 컬럼을 지정해 입력 기존 `append(table, rows)`는 유지되며 `columns=` keyword로 선택 대상을 지정합니다. ```python from machbaseAPI import SparseArray, connect def main(): conn = connect(host="127.0.0.1", port=5656, user="SYS", password="MANAGER", database="MACHBASEDB") try: conn.append( "ARRAY_APPEND_EXAMPLE", [[1, 10, 40]], columns=["ID", "A[0]", "A[3]"], ) sparse = SparseArray(4).set(1, 200).set(3, 400) empty = SparseArray(4) conn.append( "ARRAY_APPEND_EXAMPLE", [[2, sparse], [3, empty], [4, None]], columns=["ID", "A"], ) rows = conn.cursor(dictionary=False).execute( "SELECT ID,A,ARRAY_LENGTH(A) " "FROM ARRAY_APPEND_EXAMPLE ORDER BY ID" ).fetchall() expected = [ (1, [10, None, None, 40], 4), (2, [None, 200, None, 400], 4), (3, [None, None, None, None], 4), (4, None, None), ] assert rows == expected, rows print("Python sparse append OK") finally: conn.close() if __name__ == "__main__": main() ``` `SparseArray.clear()`는 요소 수를 유지하면서 모든 요소를 NULL로 되돌립니다. ### Python legacy wrapper #### 일반 appendOpen으로 입력 `appendOpen(table)`로 연 뒤 `appendData()`를 사용합니다. 희소 배열을 만들기 위해 `appendOpenColumns()`를 호출할 필요는 없습니다. 일반 실습 테이블을 준비하고 `sparse_append_full_legacy.py`로 저장해 실행합니다. ```python from machbaseAPI import SparseArray, machbase db = machbase() if db.open("127.0.0.1", "SYS", "MANAGER", 5656) != 1: raise RuntimeError(db.result()) try: if db.appendOpen("ARRAY_APPEND_FULL_EXAMPLE") != 1: raise RuntimeError(db.result()) try: sparse = SparseArray(4).set(0, 10).set(3, 40) for row_id in range(1, 5): if row_id == 2: sparse.clear().set(1, 200).set(3, 400) if row_id == 3: sparse.clear() row = [row_id, None if row_id == 4 else sparse] if db.appendData("ARRAY_APPEND_FULL_EXAMPLE", None, row) != 1: raise RuntimeError(db.result()) finally: if db.appendClose() != 1: raise RuntimeError(db.result()) if db.select( "SELECT ID,A,ARRAY_LENGTH(A) " "FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID" ) != 1: raise RuntimeError(db.result()) print(db.result()) finally: db.close() ``` ```bash python3 sparse_append_full_legacy.py ``` `db.result()`의 조회 결과를 [공통 기대값](#결과-확인)과 비교합니다. #### 선택 컬럼 Open으로 입력 기존 `appendOpen(table, types=None)`는 전체 행 API로 유지됩니다. 선택 대상에는 `appendOpenColumns(table, columns, types=None)`를 사용합니다. ```python from machbaseAPI import SparseArray, machbase db = machbase() if db.open("127.0.0.1", "SYS", "MANAGER", 5656) != 1: raise RuntimeError(db.result()) try: if db.appendOpenColumns( "ARRAY_APPEND_EXAMPLE", ["ID", "A[0]", "A[3]"] ) != 1: raise RuntimeError(db.result()) try: if db.appendData( "ARRAY_APPEND_EXAMPLE", None, [1, 10, 40] ) != 1: raise RuntimeError(db.result()) finally: if db.appendClose() != 1: raise RuntimeError(db.result()) sparse = SparseArray(4).set(1, 200).set(3, 400) empty = SparseArray(4) if db.appendOpenColumns( "ARRAY_APPEND_EXAMPLE", ["ID", "A"] ) != 1: raise RuntimeError(db.result()) try: for row in ([2, sparse], [3, empty], [4, None]): if db.appendData("ARRAY_APPEND_EXAMPLE", None, row) != 1: raise RuntimeError(db.result()) finally: if db.appendClose() != 1: raise RuntimeError(db.result()) finally: db.close() ``` `appendData()`의 값 개수와 순서는 열린 대상 목록을 따릅니다. 새 코드에서는 더 간결한 DB-API `append(..., columns=...)` 사용을 권장합니다. ## Node.js ### 전체 컬럼 정의로 희소 ARRAY 입력 Node.js도 `appendOpen()`으로 희소 배열을 입력합니다. 다만 현재 `@machbase/ts-client`는 `appendOpen(table, columns, options?)`의 `columns`가 필수입니다. 별도의 `OpenColumns` 메서드가 없으며, 전체 입력과 선택 입력을 같은 메서드로 표현합니다. 다음 예제는 일반 실습 테이블의 두 입력 컬럼 `ID`, `A`를 순서대로 정의합니다. `A[0]` 같은 요소 대상을 Open에 지정하지 않고, 각 행의 `SparseArray`가 위치를 정합니다. `sparse_append_full.js`로 저장하고 ARRAY 기능이 포함된 패키지를 사용하는 프로젝트에서 실행합니다. ```javascript 'use strict'; const { createConnection, SparseArray } = require('@machbase/ts-client'); (async () => { const conn = createConnection({ host: '127.0.0.1', port: 5656, user: 'SYS', password: 'MANAGER', }); await conn.connect(); try { const stream = await conn.appendOpen('ARRAY_APPEND_FULL_EXAMPLE', [ { name: 'ID', type: 'int64' }, { name: 'A', type: 'int32-array' }, ]); try { await stream.append([ [1n, new SparseArray(4).set(0, 10).set(3, 40)], [2n, new SparseArray(4).set(1, 200).set(3, 400)], [3n, new SparseArray(4)], [4n, null], ]); } finally { await stream.close(); } const [rows] = await conn.query( 'SELECT ID,A,ARRAY_LENGTH(A) LEN ' + 'FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID', ); if (rows.length !== 4) throw new Error('Expected 4 rows'); console.log(rows); } finally { await conn.end(); } })().catch(error => { console.error(error); process.exitCode = 1; }); ``` ```bash node sparse_append_full.js ``` 네 행이 조회되는지 확인하고 [공통 기대값](#결과-확인)과 비교합니다. 컬럼 정의 없이 `appendOpen(table)`을 호출하거나 `[]`를 전달해 자동 추론시키는 방식은 지원하지 않습니다. ### 선택 컬럼·요소를 정의해 입력 `AppendColumnDefinition.name`에 컬럼 전체 또는 요소 위치를 지정한 대상을 지정합니다. ```javascript 'use strict'; const { createConnection, SparseArray } = require('@machbase/ts-client'); async function appendRows(connection, columns, rows) { const appender = await connection.appendOpen('ARRAY_APPEND_EXAMPLE', columns); try { await appender.append(rows); } finally { await appender.close(); } } (async () => { const connection = createConnection({ host: '127.0.0.1', port: 5656, user: 'SYS', password: 'MANAGER', }); await connection.connect(); try { await appendRows(connection, [ { name: 'ID', type: 'int64' }, { name: 'A[0]', type: 'int32' }, { name: 'A[3]', type: 'int32' }, ], [[1n, 10, 40]]); const sparse = new SparseArray(4).set(1, 200).set(3, 400); const empty = new SparseArray(4); await appendRows(connection, [ { name: 'ID', type: 'int64' }, { name: 'A', type: 'int32-array' }, ], [[2n, sparse], [3n, empty], [4n, null]]); const [rows] = await connection.query( 'SELECT ID,A,ARRAY_LENGTH(A) LEN ' + 'FROM ARRAY_APPEND_EXAMPLE ORDER BY ID', ); console.log(rows); } finally { await connection.end(); } })().catch((error) => { console.error(error.stack || error); process.exitCode = 1; }); ``` `MACHBASE_NATIVE_APPEND=0`으로 prepared 대체 경로를 선택해도 `SparseArray`를 ARRAY-compatible 값으로 처리합니다. ## .NET full/legacy provider ### 일반 AppendOpen으로 희소 ARRAY 입력 `AppendOpen(table)`로 열고 `AppendData()`에 `MachSparseArray`를 전달합니다. 일반 실습 테이블을 먼저 준비합니다. 다음 코드는 ARRAY 기능을 포함한 full/legacy provider를 참조하는 C# 프로젝트의 `Program.cs`로 사용합니다. ```csharp using System; using System.Collections.Generic; using Mach.Data.MachClient; public class SparseAppendFull { public static void Main() { using var conn = new MachConnection( "SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD=MANAGER"); conn.Open(); using var command = new MachCommand(conn); var writer = command.AppendOpen("ARRAY_APPEND_FULL_EXAMPLE"); try { var first = new MachSparseArray(MachDBType.INT32_ARRAY, 4) .Set(0, 10).Set(3, 40); var second = new MachSparseArray(MachDBType.INT32_ARRAY, 4) .Set(1, 200).Set(3, 400); var empty = new MachSparseArray(MachDBType.INT32_ARRAY, 4); var rows = new List> { new List { 1L, first }, new List { 2L, second }, new List { 3L, empty }, new List { 4L, DBNull.Value }, }; foreach (var row in rows) command.AppendData(writer, row); } finally { if (command.IsAppendOpened) command.AppendClose(writer); } if (writer.FailureCount != 0) throw new InvalidOperationException("APPEND row failure"); using var verify = new MachCommand( "SELECT ID,A,ARRAY_LENGTH(A) FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID", conn); using var reader = verify.ExecuteReader(); int count = 0; while (reader.Read()) { Console.WriteLine(reader.IsDBNull(1) ? $"{reader.GetInt64(0)} NULL" : $"{reader.GetInt64(0)} " + string.Join(",", (object[])reader.GetValue(1))); count++; } if (count != 4) throw new InvalidOperationException("Expected 4 rows"); } } ``` 다음 프로젝트 파일을 `Program.cs`와 같은 디렉터리에 `SparseAppendFull.csproj`로 저장합니다. 예시는 .NET 8용 provider를 사용합니다. ```xml Exe net8.0 false $(MachbaseProviderDll) ``` 아래 경로를 ARRAY 기능이 포함된 .NET 8 provider DLL의 실제 경로로 바꿉니다. ```bash dotnet build SparseAppendFull.csproj -p:MachbaseProviderDll=/absolute/path/to/provider.dll dotnet bin/Debug/net8.0/SparseAppendFull.dll ``` Close의 실패 건수가 0이고 조회 결과가 네 행인지 확인한 뒤 [공통 기대값](#결과-확인)과 비교합니다. ### 선택 컬럼 Open으로 입력 full API와 기존 호환 MachConnector40은 선택 대상을 받는 오버로드를 제공합니다. 기존 `AppendOpen(string)`과 error-check 오버로드는 유지됩니다. ```csharp MachAppendWriter AppendOpen(string tableName, IList inputColumns); MachAppendWriter AppendOpen(string tableName, IList inputColumns, int errorCheckCount, MachAppendOption option); ``` ```csharp using System; using System.Collections.Generic; using Mach.Data.MachClient; static void Append(MachConnection connection, IList columns, IList> rows) { using var command = new MachCommand(connection); var writer = command.AppendOpen("ARRAY_APPEND_EXAMPLE", columns); try { foreach (var row in rows) command.AppendData(writer, row); } finally { if (command.IsAppendOpened) command.AppendClose(writer); } if (writer.FailureCount != 0) throw new InvalidOperationException("APPEND row failure"); } using var connection = new MachConnection( "SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD=MANAGER"); connection.Open(); Append(connection, new List { "ID", "A[0]", "A[3]" }, new List> { new List { 1L, 10, 40 } }); var sparse = new MachSparseArray(MachDBType.INT32_ARRAY, 4) .Set(1, 200).Set(3, 400); var empty = new MachSparseArray(MachDBType.INT32_ARRAY, 4); Append(connection, new List { "ID", "A" }, new List> { new List { 2L, sparse }, new List { 3L, empty }, new List { 4L, DBNull.Value }, }); using var verify = new MachCommand( "SELECT ID,A,ARRAY_LENGTH(A) FROM ARRAY_APPEND_EXAMPLE ORDER BY ID", connection); using var reader = verify.ExecuteReader(); while (reader.Read()) Console.WriteLine(reader.IsDBNull(1) ? $"{reader.GetInt64(0)} NULL" : $"{reader.GetInt64(0)} " + string.Join(",", (object[])reader.GetValue(1))); ``` `MachSparseArray.Clear()`는 객체를 재사용 가능한 모든 요소가 NULL인 상태로 되돌립니다. 배열 전체 NULL은 `DBNull.Value`입니다. Append Open 성공 후 메타데이터 처리에 실패하면 프로바이더가 열린 핸들을 정리하고 연결은 재사용할 수 있습니다. ## Go neo-client ### 컬럼 인자 없이 Connect로 입력 `Appender.Connect(ctx, dsn, table)`에서 컬럼 인자를 생략하고 `Append(id, sparse)`로 입력합니다. 이 예제의 LOG 입력에서는 `_arrival_time`을 직접 전달하지 않습니다. `*api.Array`의 nil은 전체 NULL이며 빈 희소 객체와 구분합니다. 아래 코드는 요소 위치가 0부터 시작하는 ARRAY 기능을 포함한 `neo-client/v2` 소스가 필요합니다. 해당 소스를 `go.work` 또는 `replace`로 연결한 Go 모듈에서 `sparse_append_full.go`로 저장합니다. 연결된 소스는 아래 선택 예제와 동일한 버전 조건을 따릅니다. ```go package main import ( "context" "database/sql" "errors" "fmt" client "github.com/machbase/neo-client/v2" "github.com/machbase/neo-client/v2/api" ) func appendFull(ctx context.Context, dsn string) error { first, err := api.NewSparseArray(api.SqlTypeInt32, 4) if err != nil { return err } if err = first.Set(0, int32(10)); err != nil { return err } if err = first.Set(3, int32(40)); err != nil { return err } second, err := api.NewSparseArray(api.SqlTypeInt32, 4) if err != nil { return err } if err = second.Set(1, int32(200)); err != nil { return err } if err = second.Set(3, int32(400)); err != nil { return err } empty, err := api.NewSparseArray(api.SqlTypeInt32, 4) if err != nil { return err } var wholeNull *api.Array appender := &client.Appender{} if err = appender.Connect(ctx, dsn, "ARRAY_APPEND_FULL_EXAMPLE"); err != nil { return err } for _, row := range [][]any{ {int64(1), first}, {int64(2), second}, {int64(3), empty}, {int64(4), wholeNull}, } { if err = appender.Append(row...); err != nil { _, _, closeErr := appender.Close() return errors.Join(err, closeErr) } } success, failure, err := appender.Close() if err != nil { return err } if success != 4 || failure != 0 { return fmt.Errorf("success=%d failure=%d", success, failure) } return nil } func main() { ctx := context.Background() dsn := "server=tcp://sys:manager@127.0.0.1:5656" if err := appendFull(ctx, dsn); err != nil { panic(err) } db, err := sql.Open(client.DefaultDriverName, dsn) if err != nil { panic(err) } defer db.Close() rows, err := db.QueryContext(ctx, "SELECT ID,A,ARRAY_LENGTH(A) FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID") if err != nil { panic(err) } defer rows.Close() count := 0 for rows.Next() { var id int64 var value sql.NullString var length sql.NullInt64 if err = rows.Scan(&id, &value, &length); err != nil { panic(err) } if value.Valid { fmt.Println(id, value.String, length.Int64) } else { fmt.Println(id, "NULL") } count++ } if err = rows.Err(); err != nil { panic(err) } if count != 4 { panic("Expected 4 rows") } } ``` ```bash go run sparse_append_full.go ``` Close의 성공 4건·실패 0건과 조회 결과를 확인합니다. ### 선택 컬럼 Connect로 입력 이 예제는 Machbase Neo 서버가 아니라 `neo-client`가 Machbase DBMS에 직접 연결하는 경로입니다. 요소 위치가 0부터 시작하는 ARRAY와 선택 컬럼 Append API는 [`neo-client` PR #17](https://github.com/machbase/neo-client/pull/17) 이후의 v2 module 소스에 있습니다. 공개 v2 릴리스가 지정되기 전에는 공개 모듈 버전에 같은 기능이 포함되었다고 가정하지 마십시오. ```go package main import ( "context" "database/sql" "errors" "fmt" client "github.com/machbase/neo-client/v2" "github.com/machbase/neo-client/v2/api" ) func appendRows(ctx context.Context, dsn, table string, columns []string, rows [][]any) error { appender := &client.Appender{} if err := appender.Connect(ctx, dsn, table, columns...); err != nil { return err } for _, row := range rows { if err := appender.Append(row...); err != nil { _, _, closeErr := appender.Close() return errors.Join(err, closeErr) } } success, failure, err := appender.Close() if err != nil { return err } if failure != 0 { return fmt.Errorf("append success=%d failure=%d", success, failure) } return nil } func main() { ctx := context.Background() dsn := "server=tcp://sys:manager@127.0.0.1:5656" db, err := sql.Open(client.DefaultDriverName, dsn) if err != nil { panic(err) } defer db.Close() if err := db.PingContext(ctx); err != nil { panic(err) } if err := appendRows(ctx, dsn, "ARRAY_APPEND_EXAMPLE", []string{"ID", "A[0]", "A[3]"}, [][]any{{int64(1), int32(10), int32(40)}}); err != nil { panic(err) } sparse, err := api.NewSparseArray(api.SqlTypeInt32, 4) if err != nil { panic(err) } if err := sparse.Set(1, int32(200)); err != nil { panic(err) } if err := sparse.Set(3, int32(400)); err != nil { panic(err) } empty, err := api.NewSparseArray(api.SqlTypeInt32, 4) if err != nil { panic(err) } var wholeNull *api.Array if err := appendRows(ctx, dsn, "ARRAY_APPEND_EXAMPLE", []string{"ID", "A"}, [][]any{ {int64(2), sparse}, {int64(3), empty}, {int64(4), wholeNull}, }); err != nil { panic(err) } rows, err := db.QueryContext(ctx, "SELECT ID,A,ARRAY_LENGTH(A) FROM ARRAY_APPEND_EXAMPLE ORDER BY ID") if err != nil { panic(err) } defer rows.Close() for rows.Next() { var id int64 var value sql.NullString var length sql.NullInt64 if err := rows.Scan(&id, &value, &length); err != nil { panic(err) } if !value.Valid { fmt.Println(id, "NULL"); continue } fmt.Println(id, value.String, length.Int64) } if err := rows.Err(); err != nil { panic(err) } } ``` `Appender.Connect(ctx, dsn, table, columns...)`의 가변 인자가 선택 대상입니다. `WithInputColumns(columns...)`를 사용할 때는 `Connect()`보다 먼저 적용합니다. 하나의 `Appender`에서 `Append`, `Flush`, `Close`를 동시에 호출하지 마십시오. ## 결과 확인 일반 예제 실행 후 다음 쿼리로 값, 전체 NULL과 요소 NULL을 확인합니다. ```sql SELECT ID, A, ARRAY_LENGTH(A), A[0], A[1], A[2], A[3] FROM ARRAY_APPEND_FULL_EXAMPLE ORDER BY ID; ``` 선택 예제 실행 후에는 다음 쿼리를 사용합니다. 두 테이블의 기대값은 같습니다. ```sql SELECT ID, A, ARRAY_LENGTH(A), A[0], A[1], A[2], A[3] FROM ARRAY_APPEND_EXAMPLE ORDER BY ID; ``` | ID | A | `ARRAY_LENGTH(A)` | |---:|---|---:| | 1 | `[10,null,null,40]` | 4 | | 2 | `[null,200,null,400]` | 4 | | 3 | `[null,null,null,null]` | 4 | | 4 | `NULL` | `NULL` | 각 SDK 예제를 같은 테이블에 연속으로 실행하면 ID가 중복됩니다. 실제 검증에서는 예제마다 테이블을 비우거나 서로 다른 ID 범위를 사용합니다. ## 실습 정리 조회 결과를 확인한 뒤 이번에 만든 실습 테이블만 삭제합니다. 두 예제를 모두 실행한 경우 다음 두 문장을 사용하고, 한쪽만 실행했다면 해당 테이블만 삭제합니다. ```sql DROP TABLE ARRAY_APPEND_FULL_EXAMPLE; DROP TABLE ARRAY_APPEND_EXAMPLE; ``` 각 문장은 테이블과 데이터를 함께 삭제합니다. 같은 이름의 기존 업무 테이블에 적용하지 마십시오. 다시 실행할 때는 준비 SQL부터 진행합니다. ## 버전과 제한 사항 - `ARRAY`와 선택 컬럼 Append는 Machbase DBMS 8.7.0 기능입니다. - ARRAY의 공개 요소 위치는 0부터 시작합니다. 이전 개발 버전에서 위치를 1부터 지정했던 희소 ARRAY와 선택 대상 호출은 위치를 1씩 낮춰야 합니다. JDBC의 매개변수 순번처럼 별도로 1부터 시작하는 표준 API까지 변경하지 마십시오. - Machbase DBMS 8.7.0 서버와 ARRAY 기능이 포함된 SDK 빌드를 함께 사용합니다. - 기존 전체 행 Append Open 함수와 메서드의 시그니처와 의미는 유지됩니다. - C API의 컬럼명 목록은 NULL-terminated 배열이며 별도의 건수를 받지 않습니다. - 잘못된 요소 수, 중복 또는 범위 밖 위치, 중복 대상, whole/element 대상 충돌과 값 개수 불일치는 오류입니다. - Go ARRAY API는 정식 모듈 릴리스 전까지 기능이 포함된 개발 소스를 연결해야 합니다. - SDK는 실패한 행을 성공 건수에 포함해서는 안 됩니다. --- title: "12. 성능 튜닝" url: https://docs.machbase.com/kr/dbms/performance-tuning/ language: kr kind: section --- # 12. 성능 튜닝 성능 문제를 데이터 모델, 입력 경로, 쿼리 실행 계획, 메모리, 스토리지 순서로 진단하고 조정합니다. 설정값을 변경하기 전에 병목 지점과 재현 조건을 확인하고, 변경 전후를 같은 워크로드로 측정합니다. ## 권장 진단 순서 1. 대상 쿼리와 입력 작업의 지연, 처리량, 오류율을 기록합니다. 2. 데이터 특성과 테이블 타입이 일치하는지 확인합니다. 3. `EXPLAIN`, `V$STMT`, `V$SESSION`으로 실행 상태를 확인합니다. 4. 인덱스, 배치 크기, 캐시, 스토리지 설정을 한 항목씩 조정합니다. 5. 같은 데이터와 워크로드로 변경 효과를 다시 측정합니다. ## 이 장의 구성 | 순서 | 섹션 | 내용 | |-----:|------|------| | 12.1 | [성능 문제 접근 순서](./performance-approach/) | 기준값 수집, 병목 분류, 실행 계획과 시스템 뷰 | | 12.2 | [모델링 성능 튜닝](./performance-tuning-modeling/) | 테이블 타입과 스키마 설계 점검 | | 12.3 | [인덱스 튜닝](./index-tuning/) | 테이블 타입별 인덱스 선택과 쓰기 비용 | | 12.5 | [조회와 분석 성능 튜닝](./performance-query-tuning/) | 시간 조건, 실행 계획, ROLLUP, 윈도우 함수 | | 12.6 | [캐시와 메모리 튜닝(영문)](/dbms/performance-tuning/cache-tuning-memory/) | PVO Cache, Min-Max Cache, 메모리 사용량 | | 12.7 | [스토리지와 Cluster 튜닝](./tuning-storage-cluster/) | 디스크 I/O, 체크포인트, Cluster 구성 | 처리량과 응답 시간은 하드웨어, 데이터 분포, 스키마, 인덱스, 동시 사용자 수에 따라 달라집니다. 문서의 설정 예시는 시작점으로 사용하고 운영 워크로드에서 직접 검증합니다. --- title: "12.1 성능 문제 접근 순서" url: https://docs.machbase.com/kr/dbms/performance-tuning/performance-approach/ language: kr kind: page --- # 12.1 성능 문제 접근 순서 성능 문제는 재현 조건과 기준값을 확보한 뒤 병목을 좁혀야 합니다. 근거 없이 설정 속성이나 인덱스를 여러 개 동시에 바꾸면 원인과 효과를 구분할 수 없습니다. ## 1. 재현 조건 기록 - 느려진 SQL 또는 입력 경로 - 시작·종료 시각과 데이터베이스·사용자 - 대상 테이블, 시간 범위, 행 수와 결과 건수 - 동시 세션·쿼리·Appender 수 - 정상 시점과 문제 시점의 지연·처리량 - 직전 배포, 스키마, 설정 속성, 데이터 분포 변화 ## 2. 자원 병목 확인 ```bash iostat -x 1 5 top -b -n 1 free -h ``` CPU·I/O·메모리 수치는 고정 임계값보다 정상 시점 기준값과 비교합니다. 짧은 일시적 급증과 지속적인 포화를 구분하고, OS 지표 시각을 서버 trace·쿼리 시각과 맞춥니다. ## 3. 실행 중 작업 확인 ```sql SELECT sess_id, id AS stmt_id, state, record_size, query FROM V$STMT ORDER BY sess_id, id; SELECT id, user_name, user_ip, login_time, client_type FROM V$SESSION ORDER BY login_time DESC; ``` 장시간 문장, 비정상적으로 늘어난 세션, 같은 쿼리의 동시 실행을 찾습니다. system view 컬럼은 배포 버전에서 `DESC`로 확인합니다. ## 4. 실행 계획과 범위 확인 느린 SELECT는 `EXPLAIN`으로 테이블, 스캔 종류, 키 범위, 필터, 조인을 확인합니다. TAG·LOG 쿼리에 시간 범위가 있는지, 조건이 함수나 형변환 때문에 인덱스 범위에서 제외되는지 점검합니다. 상세 절차는 [조회와 분석 성능 튜닝](../performance-query-tuning/)을 참고합니다. ## 5. 한 가지 변경 후 재측정 변경 후보는 쿼리, 인덱스, 배치, 동시성, 캐시·설정 속성 순서로 좁힙니다. 한 번에 하나만 바꾸고 같은 재현 조건에서 다음을 비교합니다. | 항목 | 비교값 | |------|--------| | 쿼리 | 응답 시간 분포, 결과 행, 실행 계획 | | 입력 | rows/s, 서버 처리 응답 지연, 실패 건수 | | 서버 | CPU, I/O, 메모리, 세션 | | 부작용 | 다른 쿼리 지연, 입력 저하, 재시작 영향 | 효과가 없거나 부작용이 크면 기록한 이전 값으로 되돌립니다. 스키마나 스토리지 구조 변경은 마지막 수단으로 검토하고, 검증 환경과 복구 절차를 먼저 준비합니다. ## 최종 진단 체크리스트 - 현재 작업과 세션을 정상 기준값과 비교했는가 - 실제 SQL과 시간 범위로 실행 계획을 확인했는가 - 인덱스·ROLLUP 변경이 입력 비용에 미치는 영향을 확인했는가 - OS와 서버 지표의 시각을 같은 작업과 맞췄는가 - 설정 속성·스키마 변경을 한 번에 하나씩 적용했는가 - 원복 값과 재측정 결과를 기록했는가 --- title: "12.2 모델링 성능 튜닝" url: https://docs.machbase.com/kr/dbms/performance-tuning/performance-tuning-modeling/ language: kr kind: page --- # 12.2 모델링 성능 튜닝 모델링 단계에서는 데이터의 수명, 조회 키, 변경 방식에 맞는 테이블 타입과 스키마를 선택합니다. 잘못된 테이블 타입을 설정 속성이나 인덱스만으로 보완하려 하지 않습니다. ## 테이블 타입 선택 | 데이터 | 우선 검토 | |--------|-----------| | 이름·시간·숫자값 중심 시계열 | TAG | | append 중심 이벤트·로그 | LOG | | 관계형 변경과 트랜잭션 | TRANSACTION | | 작은 영속 참조 데이터 | LOOKUP | | 재생성 가능한 메모리 캐시 | VOLATILE | ## 스키마 설계 기준 - 쿼리와 입력에 실제 필요한 컬럼만 둡니다. - 문자열 길이는 관측한 최대값과 증가 가능성을 근거로 정합니다. - 시간, 숫자, IP를 문자열로 저장하지 않고 해당 SQL 타입을 사용합니다. - TAG 메타데이터에는 태그에 대해 비교적 안정적인 속성을 둡니다. - NULL 허용 여부와 기본값을 업무 의미에 맞춥니다. - 관계형 키는 자연 키와 대리 키의 수명·변경 가능성을 비교합니다. 문자열 길이에 일률적인 여유 비율을 적용하지 않습니다. 현재 분포와 상한, 잘렸을 때의 영향을 측정하고 스키마 변경 절차를 준비합니다. ## 인덱스와 집계 조회 조건식과 조인 키를 기준으로 인덱스 후보를 정합니다. 인덱스를 추가할 때는 읽기 개선뿐 아니라 입력 지연, 저장 공간, 메모리를 함께 측정합니다. 반복되는 TAG 시간 집계는 ROLLUP을 검토하고, 조회가 거의 없는 집계 단위를 무분별하게 만들지 않습니다. ## 검증 순서 1. 대표 데이터와 쿼리를 준비합니다. 2. 테이블 타입과 최소 스키마로 기준값을 측정합니다. 3. 인덱스 또는 ROLLUP을 하나 추가합니다. 4. 읽기·쓰기·메모리·스토리지를 다시 측정합니다. 5. 유지 가치가 없는 구조는 제거합니다. 테이블별 상세 설계는 [테이블 타입 개념과 선택](/dbms/data-modeling-table-design/)을 참고합니다. --- title: "12.3 인덱스 튜닝" url: https://docs.machbase.com/kr/dbms/performance-tuning/index-tuning/ language: kr kind: page --- # 12.3 인덱스 튜닝 인덱스는 실제 조건식과 조인 키의 읽기 비용을 줄일 때만 추가합니다. 생성 전후의 쿼리 지연, 입력 처리량, 메모리·스토리지를 함께 측정합니다. ## 테이블별 확인 | 테이블 타입 | 기본 키·접근 경로 | 추가 인덱스 | |------------|--------------------|------------| | TAG | 이름과 BASETIME 기반 접근 | 지원 값·메타데이터 인덱스 | | LOG | `_ARRIVAL_TIME` 범위 | LSM, BITMAP, KEYWORD | | LOOKUP | PRIMARY KEY | 지원 보조 인덱스 | | VOLATILE | 선택한 PRIMARY KEY | REDBLACK 보조 인덱스 | | TRANSACTION | PRIMARY KEY | 관계형 보조 인덱스 | 지원되는 인덱스 타입과 구문은 테이블별 장과 [인덱스 구문](/dbms/reference/sql/syntax/index-syntax/)을 확인합니다. ## 적용 순서 1. 느린 SQL의 `EXPLAIN`과 결과 건수를 기록합니다. 2. 조건식의 선택도와 값 분포를 확인합니다. 3. 이미 같은 선두 컬럼을 가진 인덱스가 있는지 확인합니다. 4. 후보 인덱스 하나를 생성하고 구축 완료를 확인합니다. 5. 같은 조건에서 쿼리와 입력을 다시 측정합니다. 6. 효과가 없거나 쓰기 비용이 큰 인덱스는 제거합니다. ```sql SHOW INDEXES; SHOW INDEXGAP; ``` 고정된 “인덱스 개수별 처리량 감소율”을 적용하지 않습니다. 행 크기, 키 분포, 동시성, 스토리지에 따라 결과가 달라지므로 운영과 유사한 데이터로 측정합니다. ## 주의사항 - 낮은 선택도의 컬럼에 인덱스를 추가하기 전에 스캔과 비교합니다. - 함수·형변환으로 인덱스 컬럼을 감싸 키 범위를 잃지 않는지 확인합니다. - 복합 인덱스는 자주 쓰는 조건식 조합과 선두 컬럼을 기준으로 설계합니다. - 인덱스 구축 중 입력·쿼리 부하와 `SHOW INDEXGAP`을 관찰합니다. - 미사용 인덱스를 제거하기 전 최대 부하·배치 업무에서도 쓰이지 않는지 확인합니다. 테이블별 상세 내용은 TAG, LOG, LOOKUP, VOLATILE, TRANSACTION 장의 “인덱스와 성능” 페이지를 참고합니다. --- title: "12.4 입력 성능 튜닝" url: https://docs.machbase.com/kr/dbms/performance-tuning/performance-tuning/ language: kr kind: page --- # 12.4 입력 성능 튜닝 입력 성능은 경로, 행 크기, 배치, 동시성, 인덱스, 스토리지의 영향을 함께 받습니다. 대표 데이터로 전체 경로 처리량과 서버 처리 응답 지연을 측정해 조정합니다. ## 입력 경로 선택 입력 경로와 SDK·테이블 타입별 지원 범위는 [데이터 입력과 반출](/dbms/development-tools-integration/data-input-load-export/)에서 선택하고, 이 페이지에서는 선택한 경로의 처리량과 지연만 조정합니다. ## 측정 순서 1. 실제와 비슷한 스키마, 행 크기, 인덱스를 준비합니다. 2. 연결 1개와 작은 배치로 기준값을 측정합니다. 3. 배치 크기를 한 단계씩 늘려 rows/s와 flush·서버 처리 응답 지연을 기록합니다. 4. 클라이언트 CPU·메모리와 서버 CPU·I/O·메모리를 함께 관찰합니다. 5. 연결 수를 늘리며 총처리량과 p95·p99 응답 시간을 비교합니다. p99는 전체 요청의 99%가 그 시간 안에 완료되는 지연 값으로, 느린 요청의 영향을 확인할 때 사용합니다. 6. 실패·재연결·중복 처리 시나리오를 실행합니다. 일률적인 권장 배치 크기나 스레드 수를 사용하지 않습니다. 너무 큰 배치는 메모리와 오류 재처리 범위를 늘리고, 너무 작은 배치는 네트워크 왕복 통신 비중을 키웁니다. ## Append 운영 Append 생명 주기·오류·중복 처리 계약은 [공통 연동 개념](/dbms/development-tools-integration/concepts-common/#append-api-batch)과 [SDK Append matrix](/dbms/development-tools-integration/sdk-support-scope/#append-table-type-matrix)를 따릅니다. 배치 크기와 연결 수를 바꾸면서 초당 입력 행 수(rows/s), p95·p99 지연, 서버 처리 응답과 실패 건수를 함께 기록합니다. ## 파일 적재 파일 형식 검증, 오류 행 파일, 종료 코드와 최종 행 확인은 [데이터 입력과 반출](/dbms/development-tools-integration/data-input-load-export/)을 참고합니다. 여기서는 같은 검증이 끝난 워크로드의 성능만 비교합니다. ## 병목 분류 | 관찰 | 다음 확인 | |------|-----------| | 클라이언트 CPU 포화 | 직렬화, 변환, 로그 기록 | | 네트워크 대기 증가 | 배치, 왕복 통신, 패킷 손실 | | 서버 CPU 포화 | 인덱스 수, SQL 파싱, 동시성 | | 스토리지 지연 시간 증가 | 체크포인트, 장치 대기열, 보존 작업 | | 메모리 증가 | 배치 버퍼, 연결 수, 캐시 | | 일부 노드만 느림 | 키 분포, 라우팅, 노드별 자원 | ## 변경 전후 처리량 개선은 성공 행 수와 실패 행 수가 검증된 상태에서 판단합니다. 입력 시작부터 서버 반영까지의 지연을 함께 측정하고, 변경이 조회 성능과 복구 시간에 미치는 영향도 확인합니다. --- title: "12.5 조회와 분석 성능 튜닝" url: https://docs.machbase.com/kr/dbms/performance-tuning/performance-query-tuning/ language: kr kind: page --- # 12.5 조회와 분석 성능 튜닝 조회 성능은 결과 정확성을 유지하면서 읽는 행과 파티션, 정렬·집계 작업량을 줄이는 방향으로 개선합니다. 변경 전후에는 같은 데이터·조건·동시성에서 실행 시간과 결과 건수를 비교합니다. ## 핵심 원칙 1. TAG·LOG 조회는 필요한 시간 범위를 먼저 제한합니다. 2. 반환할 컬럼만 선택하고 무제한 `SELECT *`를 피합니다. 3. 자주 사용하는 동등·범위 조건과 JOIN 키에 적합한 인덱스를 검토합니다. 4. 반복되는 장기 TAG 집계에는 ROLLUP을 사용합니다. 5. 힌트나 설정 속성 변경 전에 `EXPLAIN`으로 실행 계획을 확인합니다. 6. 평균값뿐 아니라 지연 분포, 읽은 행, CPU·I/O, 동시 쿼리 영향을 기록합니다. ## 검증 가능한 예제 다음 예제는 LOG 테이블과 인덱스를 만들고 실행 계획·결과를 확인한 뒤 정리합니다. ```sql CREATE LOG TABLE perf_event_demo ( device_id VARCHAR(32), level VARCHAR(16), code INTEGER, message VARCHAR(100) ); CREATE INDEX idx_perf_event_code ON perf_event_demo(code) INDEX_TYPE LSM; INSERT INTO perf_event_demo VALUES ('DEV-01', 'WARN', 1001, 'temperature high'); INSERT INTO perf_event_demo VALUES ('DEV-02', 'INFO', 1000, 'started'); EXEC TABLE_FLUSH(perf_event_demo); EXPLAIN SELECT device_id, level, message FROM perf_event_demo WHERE code = 1001 AND _ARRIVAL_TIME >= NOW - 60000000000; SELECT device_id, level, message FROM perf_event_demo WHERE code = 1001 AND _ARRIVAL_TIME >= NOW - 60000000000; DROP TABLE perf_event_demo; ``` 작은 표본에서 인덱스 스캔이 항상 더 빠르다고 결론 내리지 않습니다. 운영과 유사한 데이터 분포와 조건 선택도로 인덱스 생성 전후를 비교합니다. ## SELECT와 JOIN - 큰 원본 테이블은 시간·키 조건으로 먼저 범위를 줄입니다. - JOIN 조건의 양쪽 데이터 타입과 길이를 맞춥니다. - WHERE 조건을 함수로 감싸 인덱스 범위를 사용할 수 없게 만드는지 확인합니다. - outer JOIN을 inner JOIN으로 바꾸기 전에 NULL 공급 행이 사라지지 않는지 비교합니다. - 결과 순서가 필요하면 `ORDER BY`를 명시합니다. - 페이지 나누기는 큰 OFFSET보다 업무 키·시간을 이용한 이어 읽기를 검토합니다. 옵티마이저가 SQL에 적힌 테이블 순서를 그대로 따른다고 가정하지 않습니다. 조인 순서를 강제하는 힌트는 통계와 데이터 분포가 달라졌을 때 역효과가 날 수 있으므로 실행 계획과 결과를 함께 검증합니다. ## EXPLAIN 사용 `EXPLAIN`은 쿼리를 실행하지 않고 실행 계획을 확인합니다. `EXPLAIN FULL`은 실제 실행을 포함할 수 있으므로 운영 부하가 없는 제한된 환경에서 사용합니다. 실행 계획에서는 다음을 확인합니다. | 항목 | 질문 | |------|------| | 대상 테이블 | 의도한 테이블과 view가 선택됐는가 | | 스캔 종류 | 조건과 인덱스에 맞는 접근 경로인가 | | 시간 범위 | TAG·LOG 파티션 범위가 제한되는가 | | JOIN | 입력 행이 큰 상태에서 불필요한 조인이 수행되는가 | | 정렬·집계 | 큰 중간 결과를 정렬하거나 materialize하는가 | 버전마다 달라질 수 있는 내부 객체 ID나 실행 계획 전체 문자열을 자동 검사의 고정값으로 사용하지 않습니다. 테이블 이름, 스캔 종류, 주요 조건식처럼 의미가 안정적인 항목을 검사합니다. ## CTE CTE는 복잡한 쿼리를 읽기 쉽게 만들지만 자동으로 성능을 개선하지는 않습니다. 같은 CTE가 반복 평가되는지, 필터가 CTE 안쪽까지 적용되는지, 큰 중간 결과가 만들어지는지 실행 계획으로 확인합니다. Machbase 8.7.0은 Standard Edition에서 비재귀 SELECT CTE를 지원하며, 재귀 CTE는 지원하지 않습니다. 구문과 예제는 [CTE](/dbms/reference/sql/syntax/cte-syntax/)를 참고합니다. ## 검색 연산자 | 조건 | 점검 | |------|------| | 동등·범위 비교 | 컬럼 타입과 인덱스 종류가 맞는지 | | `LIKE 'prefix%'` | prefix search 지원과 문자열 인덱스를 사용하는지 | | leading wildcard | 전체 스캔 비용을 허용할 수 있는지 | | `SEARCH`·`ESEARCH` | KEYWORD 인덱스와 문법이 맞는지 | | `REGEXP` | 시간·다른 인덱스 조건으로 후보 행을 먼저 줄였는지 | | JSON 경로 | 지원 테이블 타입과 JSON 인덱스를 확인했는지 | | IP 범위 | IPV4·IPV6 타입과 인덱스 지원을 확인했는지 | 조건식의 정확한 의미와 인덱스 사용 조건은 [SEARCH·ESEARCH·REGEXP](/dbms/reference/sql/syntax/search-esearch-regexp-syntax/)와 [JSON 연산자](/dbms/reference/sql/functions/operators-json/)를 참고합니다. ## 윈도우 함수와 PIVOT 윈도우 파티션과 순서 키가 넓으면 정렬·메모리 비용이 커질 수 있습니다. 먼저 시간과 업무 키로 입력 범위를 줄이고, 같은 윈도우를 중복 계산하지 않는지 확인합니다. PIVOT은 출력 범주 수를 제한하고 예상 밖 범주의 처리 방식을 정합니다. 구문은 [윈도우 함수](/dbms/reference/sql/syntax/window-function-over-syntax/)과 [PIVOT](/dbms/reference/sql/syntax/pivot-syntax/)을 참고합니다. ## 변경 전후 체크리스트 - 같은 결과 행 수와 NULL 분포를 반환하는가 - 같은 시간 범위와 시간대를 사용하는가 - cold·warm 캐시를 구분했는가 - 단일 실행뿐 아니라 동시 쿼리에서 비교했는가 - 입력 처리량과 메모리 사용량에 부작용이 없는가 - 변경을 되돌릴 DDL·설정 속성 값과 기준 측정값을 기록했는가 ## 관련 SQL 문서 | 주제 | 상세 문서 | |---|---| | SELECT·시간 조건 | [SELECT 문법](/dbms/reference/sql/syntax/select-syntax/) | | VIEW·CTE·집합 연산 | [SQL 문법 사전](/dbms/reference/sql/syntax/) | | 힌트 | [SELECT 힌트](/dbms/reference/sql/syntax/select-hint-syntax/) | | 함수·집계 | [함수 사전](/dbms/reference/sql/functions/) | --- title: "12.7 스토리지와 Cluster 튜닝" url: https://docs.machbase.com/kr/dbms/performance-tuning/tuning-storage-cluster/ language: kr kind: page --- # 12.7 스토리지와 Cluster 튜닝 스토리지와 Cluster 튜닝은 워크로드 측정, 장애 복구 목표, 노드별 자원 사용량을 근거로 진행합니다. 고정 하드웨어 사양이나 임의의 설정 속성 값을 모든 환경에 적용하지 않습니다. ## 스토리지와 체크포인트 다음 항목을 같은 시각 범위에서 비교합니다. - 입력 rows/s와 서버 처리 응답 지연 - 쿼리 응답 시간과 읽기량 - 장치별 IOPS, 처리량, 대기열, 지연 시간 - 체크포인트 시작·종료와 소요 시간 - 메모리·swap 변화 - 장애 후 허용 가능한 복구 시간 ```bash iostat -x 1 5 df -h ``` 체크포인트 간격과 I/O 관련 설정 속성은 현재값을 [설정 레퍼런스](/dbms/reference/configuration/configuration/)에서 확인합니다. 한 번에 하나만 변경하고, 재시작 필요 여부와 롤백 값을 기록합니다. ## 경로와 용량 - 데이터, 백업, 내보내기 경로의 소유권과 여유 공간을 확인합니다. - 같은 물리 장치에 경로를 나눴다는 이유만으로 I/O가 분산된다고 가정하지 않습니다. - 운영 중 데이터 파일을 수동 이동하지 않습니다. - 보존 정책과 백업 공간 증가를 함께 계산합니다. - 파일 시스템·마운트 옵션 변경은 지원 범위와 복구 절차를 검증합니다. 오래된 데이터는 내부 파티션 테이블 이름을 직접 찾아 삭제하지 않습니다. 테이블 타입별 `DELETE ... BEFORE`, 보존 정책, 백업 정책 등 공개 SQL과 운영 기능을 사용합니다. ## Cluster 측정 클라이언트, Broker, Warehouse, Coordinator의 지표를 분리해 봅니다. | 구간 | 확인 | |------|------| | 클라이언트 → Broker | 연결, 왕복 통신, 배치 크기 | | Broker | 세션, 라우팅, CPU·네트워크 | | Warehouse | 노드별 입력·쿼리·디스크 편차 | | 노드 간 통신 | 대역폭, 패킷 손실, 지연 시간 | | Coordinator | 노드 상태와 disk-full 정책 | 특정 노드에 부하가 몰리면 tag·키 분포, 라우팅, warehouse 그룹, 노드별 스토리지와 네트워크를 함께 확인합니다. `TAG_PARTITION_COUNT`를 cluster 노드 분산 제어값으로 사용하지 않습니다. ## 설정 변경 원칙 - 이름, 허용 범위, 기본값은 설치된 버전의 설정 레퍼런스에서 확인합니다. - 임의의 추천 숫자보다 현재 기준값과 목표를 기록합니다. - 버퍼를 키울 때 처리량뿐 아니라 메모리와 상위 지연 시간을 측정합니다. - 복제 관련 값을 바꾸기 전에 정상·장애 복구 시간을 모두 비교합니다. - disk-full 상·하한에는 hysteresis를 두고 실제 증설·정리 절차와 연결합니다. - 에디션별 미지원 기능은 [지원 범위](/dbms/reference/support-scope-constraints/)에서 확인합니다. ## 변경 체크리스트 1. 노드와 구간별 병목 근거가 있는가 2. 설정 현재값과 출처를 기록했는가 3. 검증 환경에서 정상·장애 시나리오를 측정했는가 4. 노드별 배포 순서와 재시작 필요 여부를 확인했는가 5. 결과가 나쁘면 되돌릴 값과 절차가 있는가 6. 변경 후 백업·복구 검증까지 수행했는가 --- title: "13. 운영, 설정, 복구" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/ language: kr kind: section --- # 13. 운영, 설정, 복구 Machbase 서버의 시작과 종료, 설정 변경, 관측, 백업과 복원, Cluster 운영 절차를 다룹니다. 일상 운영 절차를 먼저 정립하고, 변경·복구 작업은 계획에 따라 수행합니다. ## 이 장의 구성 | 순서 | 섹션 | 내용 | |-----:|------|------| | 13.1 | [서버와 데이터베이스 운영](./server-database/) | 서버 시작·종료, 데이터베이스 생성·삭제, 라이선스 | | 13.2 | [다중 데이터베이스](./multi-database/) | 논리 데이터베이스, 권한, 백업·복원, 클라이언트 연동 | | 13.3 | [설정 운영](./configuration/) | 설정 파일, 메모리, 네트워크, 스토리지, 타임존 | | 13.4 | [ALTER SYSTEM 운영](./alter-system/) | 런타임 설정 변경과 시스템 제어 | | 13.5 | [데이터 보존 정책](./policy-data-retention/) | Retention 생성, 적용, 점검, 해제 | | 13.6 | [관측과 진단](./diagnosis-observability/) | 시스템 뷰, 로그, 세션, 용량과 장애 징후 | | 13.7 | [스키마 변경 체크리스트](./checklist-schema-alter/) | DDL 전후 영향 분석과 검증 절차 | | 13.8 | [백업, 복원, 마운트](./backup-restore-mount/) | 온라인 백업, 오프라인 복원, 읽기 전용 마운트 | | 13.9 | [Cluster 운영](./cluster/) | 토폴로지, 노드 추가·제거, 상태 관리 | 일상 점검에는 [서버와 데이터베이스 운영](./server-database/), [설정 운영](./configuration/), [관측과 진단](./diagnosis-observability/)을 사용합니다. 다중 데이터베이스를 도입할 때는 [다중 데이터베이스](./multi-database/)를 먼저 확인합니다. 스키마 또는 설정을 변경할 때는 [ALTER SYSTEM 운영](./alter-system/)과 [스키마 변경 체크리스트](./checklist-schema-alter/)를, 장애 복구와 백업 검증에는 [백업, 복원, 마운트](./backup-restore-mount/)를 확인합니다. --- title: "13.1 서버와 데이터베이스 운영" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/server-database/ language: kr kind: page --- # 13.1 서버와 데이터베이스 운영 `machadmin`은 서버 인스턴스 시작·종료, 물리 데이터베이스 생성·삭제, 라이선스 설치, 오프라인 복원을 수행합니다. SQL `CREATE DATABASE`로 만드는 논리 데이터베이스와 `machadmin -c`의 물리 인스턴스 데이터베이스를 구분합니다. ## 주요 옵션 확인 ```bash "$MACHBASE_HOME/bin/machadmin" -h ``` 설치된 릴리스의 도움말을 기준으로 옵션과 영향을 확인합니다. ## 서버 시작과 종료 상태 확인은 안전하게 실행할 수 있습니다. ```bash "$MACHBASE_HOME/bin/machadmin" -e ``` 시작·종료는 서비스 관리자와 운영 절차서 중 한 경로로 통일합니다. - 시작 전 설정, 라이선스, 데이터 경로, 여유 공간을 확인합니다. - 시작 후 `machadmin -e`, 5656 연결, 가벼운 SQL, 서버 로그를 확인합니다. - 정상 종료 전 새 연결·입력을 차단하고 진행 중 트랜잭션·백업·Appender를 확인합니다. - 강제 종료는 정상 종료가 반복 실패하고 복구 영향을 판단한 경우에만 사용합니다. - 복구 모드를 임의로 강제하지 말고 오류와 공식 복구 절차를 확인합니다. 명령 출력 예시는 릴리스마다 달라질 수 있으므로 성공 문구 전체를 자동화 조건으로 사용하지 않습니다. 프로세스 종료 코드와 실제 연결을 함께 확인합니다. ## 물리 인스턴스 데이터베이스 `machadmin -c`와 `machadmin -d`는 `DBS_PATH`의 물리 인스턴스 데이터를 생성·삭제합니다. 논리 데이터베이스 작업은 [다중 데이터베이스](../multi-database/)의 SQL을 사용합니다. 물리 데이터베이스 삭제·초기화는 인스턴스 전체 데이터를 잃을 수 있는 파괴적 작업입니다. 서버 시작 오류를 해결하려고 데이터베이스를 삭제한 뒤 다시 만드는 절차를 사용하지 마십시오. 먼저 오류를 진단하고 기존 데이터를 보존한 상태에서 복구 방법을 선택합니다. 실행 전 확인: 1. 대상 `MACHBASE_HOME`과 `DBS_PATH`의 절대 경로 2. 서비스·프로세스가 완전히 종료됐는지 3. 최신 백업과 격리 복원 검증 4. 보존해야 할 설정, 라이선스, 로그 5. 롤백 가능 여부와 예상 복구 시간 6. 두 명의 작업자가 인스턴스·경로를 교차 확인했는지 데이터 디렉터리 내부 파일과 메타데이터를 수동으로 생성·이동·삭제하지 않습니다. ## 라이선스 설치와 확인 라이선스 파일은 비밀정보로 취급하고 내용이나 실제 키를 문서·지원 요청·로그에 복사하지 않습니다. | 상태 | 방법 | |------|------| | 서버 종료 상태의 설치 | 현재 릴리스의 `machadmin` 라이선스 옵션 | | 서버 실행 중 설치 | `ALTER SYSTEM INSTALL LICENSE` | | 적용 확인 | `machadmin` 라이선스 정보와 `V$LICENSE_INFO` | ```sql SELECT * FROM V$LICENSE_INFO; ``` 설치 전 대상 인스턴스, 에디션, 유효 기간, 파일 권한을 확인합니다. 온라인 설치 경로는 서버 프로세스가 읽을 수 있어야 합니다. 갱신 후 새 연결과 필요한 에디션 기능을 검증하고 원본 라이선스 파일의 보관 정책을 적용합니다. ## 장애 원인 파악에 필요한 자료 - `machadmin -e` 결과와 종료 코드 - 릴리스·에디션과 `MACHBASE_HOME` - 실제 설정·데이터 경로 - 서버 시작·종료 시각 - 최초 오류 전후 서버 로그 - 파일 시스템 여유 공간과 권한 - 직전 설정·라이선스·스토리지 변경 서버가 시작되지 않는다는 이유로 물리 데이터베이스를 삭제하거나 초기화 복구를 먼저 실행하지 않습니다. 백업을 보존한 채 원인을 진단합니다. --- title: "13.2 다중 데이터베이스" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/multi-database/ language: kr kind: page --- # 13.2 다중 데이터베이스 Standard Edition에서는 한 서버 안에 여러 논리 데이터베이스를 만들고 객체와 접근 권한을 분리할 수 있습니다. 이 페이지는 도입과 운영 흐름만 설명합니다. SQL 문법, 권한, SDK 옵션과 백업 절차는 연결된 상세 문서를 참고합니다. ## 적용 범위 - 다중 데이터베이스는 Standard Edition 기능입니다. - 논리 데이터베이스는 별도 서버 프로세스나 자원 할당량을 만들지 않습니다. - 객체 이름은 `object`, `owner.object`, `database.owner.object`의 최대 세 부분입니다. - 다른 데이터베이스를 지정할 때 소유자를 생략하지 않습니다. - 마운트된 데이터베이스는 활성 데이터베이스와 다른 읽기 전용 백업 조회 경로입니다. ## 도입 전 결정 사항 1. 데이터베이스별 소유자와 애플리케이션 사용자를 정합니다. 2. `CONNECT`와 객체별 최소 권한을 정의합니다. 3. 연결 풀이 현재 데이터베이스를 어떻게 초기화·복원하는지 확인합니다. 4. 백업 단위, 복구 순서와 마운트된 데이터베이스 이름 규칙을 정합니다. 5. 데이터베이스별 사용량과 장애를 구분할 모니터링 기준을 마련합니다. ## 빠른 검증 다음 예제는 두 데이터베이스가 분리되는지 확인한 뒤 모두 정리합니다. ```sql CREATE DATABASE IF NOT EXISTS manual_multidb_a; CREATE DATABASE IF NOT EXISTS manual_multidb_b; USE manual_multidb_a; CREATE LOG TABLE sensor_event ( event_time DATETIME, message VARCHAR(100) ); INSERT INTO sensor_event VALUES (SYSDATE, 'from-a'); USE manual_multidb_b; CREATE LOG TABLE sensor_event ( event_time DATETIME, message VARCHAR(100) ); INSERT INTO sensor_event VALUES (SYSDATE, 'from-b'); SELECT message FROM manual_multidb_a.SYS.sensor_event; SELECT message FROM manual_multidb_b.SYS.sensor_event; USE MACHBASEDB; DROP DATABASE manual_multidb_a CASCADE FORCE; DROP DATABASE manual_multidb_b CASCADE FORCE; ``` `USE database_name`은 현재 연결의 데이터베이스를 변경합니다. 트랜잭션, 열린 커서, 준비된 문장(*prepared statement*)과 Appender가 있는 상태에서 전환하지 마십시오. 다른 데이터베이스의 객체는 `database.owner.object`로 명시합니다. ## 권한 경계 사용자는 대상 데이터베이스의 `CONNECT` 권한과 실제 객체 작업에 필요한 권한을 모두 가져야 합니다. 데이터베이스 생성·삭제·권한 SQL은 [계정과 권한](/dbms/security-access-control/privileges/)을 참고합니다. 운영 계정에 관리자 권한을 일괄 부여하지 않습니다. ## 애플리케이션 연결 SDK마다 초기 데이터베이스를 지정하는 옵션 이름과 연결 풀 초기화 동작이 다릅니다. 각 SDK의 연결 문서에서 지원 여부를 확인하고, 연결을 빌린 직후 다음 값을 검증합니다. ```sql SELECT CURRENT_DATABASE(); ``` 언어별 설정은 [개발 및 애플리케이션 연동](/dbms/development-tools-integration/)을, 서버·SDK 호환성은 [서버와 SDK 호환성](/dbms/reference/support-scope-constraints/compatibility-xma-protocol/)을 참고하십시오. ## 백업과 복구 백업 전에 포함할 활성 데이터베이스와 복구 순서를 기록합니다. 마운트된 데이터베이스는 `USE`할 수 있으나 읽기 전용(*read-only*)로 `SELECT`만 가능합니다. 정확한 명령과 검증 절차는 [백업, 복원, 마운트](../backup-restore-mount/)를 사용하십시오. ## 운영 체크리스트 - 연결 직후와 연결 풀 재사용 직후 `CURRENT_DATABASE()`가 기대값인가 - SQL과 모니터링이 동일 이름의 다른 데이터베이스 객체를 혼동하지 않는가 - 사용자에게 대상 데이터베이스와 객체의 최소 권한만 부여했는가 - 백업·복구 훈련에서 모든 대상 데이터베이스를 확인했는가 - 데이터베이스 삭제 전에 열린 연결, 객체와 백업 보존 조건을 확인했는가 정확한 `CREATE/DROP/USE DATABASE` 문법은 [DATABASE 문법](/dbms/reference/sql/syntax/database-syntax/)을 참고하십시오. --- title: "13.3 설정 운영" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/configuration/ language: kr kind: page --- # 13.3 설정 운영 설정 변경은 현재값, 변경 근거, 적용 방식, 검증값, 롤백을 기록하고 한 번에 하나씩 진행합니다. 설정 속성의 전체 목록과 기본값은 현재 릴리스의 [설정 레퍼런스](/dbms/reference/configuration/configuration/)를 참고합니다. ## 설정 파일 기본 설정 파일은 `$MACHBASE_HOME/conf/machbase.conf`입니다. 패키지·서비스 구성에 따라 다른 파일을 사용할 수 있으므로 시작 명령과 실제 환경을 확인합니다. 변경 전에는 다음을 기록합니다. - 파일 경로, 소유자, 권한 - 변경할 설정 속성의 현재 파일값과 `V$PROPERTY` 값 - 단위와 허용 범위 - 실행 시점 변경 가능 여부와 재시작 필요 여부 - 관련 노드·인스턴스 범위 - 롤백 값과 검증 SQL 비밀번호, AUTH KEY, 라이선스 내용을 일반 설정 백업이나 작업 기록에 포함하지 않습니다. ## 실행 중 변경과 재시작 `ALTER SYSTEM SET`은 지원되는 일부 설정 속성만 실행 시점에 변경합니다. 문서에 예시가 있다는 이유로 임의 설정 속성을 실행하지 않습니다. ```sql SELECT NAME, VALUE FROM V$PROPERTY ORDER BY NAME; ``` 1. 설정 레퍼런스에서 동적 변경 지원 여부를 확인합니다. 2. 유지보수 범위와 영향받는 연결·쿼리를 정합니다. 3. 현재값을 저장합니다. 4. 검증 환경 또는 제한된 워크로드에서 한 설정 속성만 변경합니다. 5. 응답 시간, 처리량, 메모리·I/O와 오류를 비교합니다. 6. 유지할 값은 설정 파일에도 반영해 재시작 후 되돌아가지 않게 합니다. 7. 재시작 후 `V$PROPERTY`와 기능 테스트로 적용을 확인합니다. ## 설정 속성 찾기 ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME LIKE 'PVO_CACHE%' ORDER BY NAME; ``` 정확한 이름을 알고 있을 때는 `NAME = '...'`로 조회합니다. 이름이 비슷한 설정 속성을 추정해 설정하지 않습니다. ## 메모리 설정 프로세스 상한, 테이블 space·캐시, 입력 버퍼, 쿼리·세션의 일시 메모리를 하나의 예산으로 봅니다. OS와 같은 호스트의 다른 프로세스에 필요한 메모리도 남깁니다. - 정상·최대 부하 워크로드의 resident 메모리와 available 메모리 - swap 발생 여부와 증가 시각 - 동시 쿼리·Appender·세션 수 - PVO, Min-Max, LOOKUP·VOLATILE 등 캐시 사용량 - 인덱스 생성, 정렬, 집계 같은 일시 작업 고정 비율이나 예시 바이트 값을 그대로 적용하지 않습니다. ## 네트워크와 세션 listener 주소·포트, 최대 세션, connect·쿼리 시간 초과는 애플리케이션 연결 수와 장애 격리 요구사항을 기준으로 정합니다. 방화벽과 바인딩을 별도 검증하고, 최대 세션을 늘리기 전에 연결 누수와 연결 풀 설정을 확인합니다. ```sql SELECT ID, USER_NAME, USER_IP, LOGIN_TIME, CLIENT_TYPE FROM V$SESSION ORDER BY LOGIN_TIME DESC; ``` ## 스토리지와 체크포인트 `DBS_PATH`, 체크포인트, direct I/O, I/O 스레드 관련 설정은 데이터 위치와 복구 시간에 직접 영향을 줍니다. 운영 데이터 파일을 수동 이동하거나 다른 경로를 추정하지 않습니다. - 실제 데이터·백업 경로와 파일 시스템을 확인합니다. - 체크포인트 시간과 장치 지연 시간을 같은 시각에서 비교합니다. - 재시작이 필요한 설정은 서비스 중단·복구 절차를 준비합니다. - 변경 후 정상 재시작과 백업·복원을 검증합니다. ## 시간대 시간 문자열을 입력·표시할 시간대를 서버, command-line tool, SDK에서 일관되게 설정합니다. epoch 단위와 `DATETIME` 정밀도를 별도로 확인하고, 같은 값의 입력·조회 왕복 테스트를 수행합니다. ## 서버와 세션의 시간대 서버의 기본 시간대와 클라이언트가 선택한 세션 시간대를 구분합니다. 애플리케이션의 문자열 입출력 기준은 지원되는 연결 옵션으로 명시하고, 새 연결에서 `SHOW TIMEZONE`과 표본 `DATETIME` 조회로 확인합니다. 설정 방법은 [시간대 설정 사전](/dbms/reference/configuration/configuration-timezone/)을 참고하십시오. 기존 연결의 세션 설정이 자동으로 변경되는 것은 아닙니다. ## machsql `-z` ```bash "$MACHBASE_HOME/bin/machsql" -s 127.0.0.1 -P 5656 -u APP_USER -p "$MACH_SAMPLE_PASSWORD" -z +0900 ``` 입력·출력 문자열이 해당 오프셋으로 해석·표시되는지 표본으로 확인합니다. ## machloader `-z` ```bash "$MACHBASE_HOME/bin/machloader" -s 127.0.0.1 -P 5656 -u APP_USER -p "$MACH_SAMPLE_PASSWORD" -z +0900 -i -t SENSOR_LOG -d /data/sensor.csv ``` CSV 원본의 시간대와 날짜 형식을 함께 문서화합니다. ## SDK 연결 시간대 지원 옵션 이름은 SDK마다 다릅니다. [11장 개발 및 애플리케이션 연동](/dbms/development-tools-integration/) 에서 해당 드라이버의 연결 옵션을 확인하고, 입력·조회·연결 풀 재사용 뒤에도 같은 시간대가 적용되는지 검증합니다. ## 변경 기록 | 항목 | 기록 | |------|------| | 대상 | 호스트, 인스턴스, 노드, 데이터베이스 | | 변경 | 설정 속성과 이전·새 값 | | 근거 | 기준값과 목표 | | 적용 | 실행 시점 또는 재시작 | | 검증 | SQL, 워크로드, OS 지표 | | 롤백 | 값, 실행 순서, 담당자 | 확인되지 않은 추천값이나 과거 릴리스의 기본값을 현재 설정으로 간주하지 않습니다. --- title: "13.4 ALTER SYSTEM 운영" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/alter-system/ language: kr kind: page --- # 13.4 ALTER SYSTEM 운영 `ALTER SYSTEM`은 인스턴스 전체에 영향을 줄 수 있는 관리자 명령입니다. 실행 전 대상 인스턴스, 권한, 진행 중 작업, 롤백 또는 해제 명령을 확인합니다. 전체 구문은 [system·세션 ALTER 구문](/dbms/reference/sql/syntax/system-session-alter-syntax/)을 참고합니다. ## 공통 절차 1. 현재 서버와 데이터베이스, 릴리스를 확인합니다. 2. 관련 세션·문장·백업·체크포인트 상태를 기록합니다. 3. 명령의 blocking·I/O·메모리 영향을 확인합니다. 4. 유지보수 창과 실패 시 조치를 정합니다. 5. 실행 직후 결과와 관련 가상 테이블·로그를 확인합니다. ## CHECKPOINT ```text ALTER SYSTEM CHECKPOINT; ``` 체크포인트는 스토리지 I/O를 증가시킬 수 있습니다. 백업·종료 전 필요성을 검토하고 동시 대량 입력과 쿼리 영향을 관찰합니다. 단순히 “느리다”는 이유로 반복 실행하지 않습니다. ## CHECK DISK_USAGE ```text ALTER SYSTEM CHECK DISK_USAGE; ``` 파일 시스템 상태와 데이터베이스 스토리지 메타데이터의 점검이 필요한 경우 사용합니다. 실행 전 여유 공간과 마운트 상태를 확인하고 결과 로그를 검토합니다. 내부 파일을 직접 수정해 수치를 맞추지 않습니다. ## INSTALL LICENSE ```text ALTER SYSTEM INSTALL LICENSE; ALTER SYSTEM INSTALL LICENSE = '/absolute/path/license.dat'; ``` 라이선스 파일의 출처, 대상 인스턴스, 에디션, 만료일을 확인합니다. 파일 내용은 문서·로그에 복사하지 않고 권한을 제한합니다. 설치 뒤 `V$LICENSE_INFO`와 새 연결로 적용을 확인합니다. ## KILL과 CANCEL SESSION ```text ALTER SYSTEM CANCEL SESSION session_id; ALTER SYSTEM KILL SESSION session_id; ``` 먼저 `V$SESSION`과 `V$STMT`에서 사용자, 클라이언트 IP, SQL, 상태를 확인합니다. `CANCEL`은 실행 중 문장 중단을 우선 시도할 때, `KILL`은 연결 자체를 종료해야 할 때 검토합니다. 트랜잭션·Appender·애플리케이션 재시도가 만드는 중복과 롤백 영향을 확인합니다. ## FREEZE와 UNFREEZE ```text ALTER SYSTEM FREEZE; ALTER SYSTEM UNFREEZE; ``` freeze는 공개 백업 기능으로 대체할 수 없는 파일 시스템 스냅샷 절차에서만 검토합니다. 실행 전 허용되는 읽기·쓰기 범위와 최대 freeze 시간을 정하고, 어떤 오류 경로에서도 `UNFREEZE`를 실행할 담당자와 확인 절차를 준비합니다. 세션을 freeze 상태로 방치하지 않습니다. ## FLUSH AGER ```text ALTER SYSTEM FLUSH AGER; ``` 삭제된 공간의 정리 지연을 조사할 때 사용 여부를 검토합니다. 보존 정책·DELETE 상태와 스토리지 여유를 먼저 확인하고, 정상 백그라운드 작업을 반복 강제하지 않습니다. ## FLUSH PVO_CACHE ```text ALTER SYSTEM FLUSH PVO_CACHE; ``` 캐시된 실행 계획을 비우면 이후 쿼리가 다시 파싱·최적화됩니다. 스키마·실행 계획 문제를 분리 진단할 때만 실행하고, 동시 쿼리의 지연 시간 일시적 급증을 관찰합니다. 캐시 flush를 지속적인 성능 문제의 해결책으로 사용하지 않습니다. ## FLUSH SYS_STAT ```text ALTER SYSTEM FLUSH SYS_STAT; ``` 누적 통계를 초기화하기 전에 필요한 기준값을 저장합니다. 초기화 시각을 모니터링에 기록해 변화율 계산과 장애 분석이 왜곡되지 않게 합니다. ## FLUSH PAGE_CACHE ```text ALTER SYSTEM FLUSH PAGE_CACHE; ``` 페이지 캐시 flush는 후속 쿼리의 I/O와 지연 시간을 크게 바꿀 수 있습니다. cold-cache 비교나 제한된 진단에서만 사용하고 운영 최대 부하에는 실행하지 않습니다. ## 권한과 감사 최소 관리자 계정으로 실행하고 명령, 대상, 시각, 실행자, 사유, 결과를 감사 기록에 남깁니다. 예제의 세션 ID, 경로, 설정 속성 값을 그대로 운영 명령으로 사용하지 않습니다. --- title: "13.5 데이터 보존 정책" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/policy-data-retention/ language: kr kind: page --- # 13.5 데이터 보존 정책 Retention Policy는 TAG, KV, LOG 테이블에서 기준 시각보다 오래된 데이터를 주기적으로 삭제합니다. `DURATION`은 보존 기간이고 `INTERVAL`은 삭제 작업의 실행 주기입니다. TRANSACTION, VOLATILE, LOOKUP 테이블에는 적용할 수 없습니다. ```text 정책 생성 → 테이블 적용 → 실행 상태 확인 → 테이블에서 해제 → 정책 삭제 ``` ## Retention Policy 운영 정책을 만들기 전에 법적 보관 의무, 복구 요구사항, 시간당 유입량과 삭제 부하를 함께 검토합니다. 고정 비율로 `INTERVAL`을 정하지 말고 운영 환경에서 측정한 삭제 시간보다 충분히 긴 주기를 사용하십시오. 정책과 적용 상태는 다음 뷰에서 확인합니다. ```sql SELECT * FROM M$RETENTION; SELECT USER_NAME, TABLE_NAME, POLICY_NAME, STATE, LAST_DELETED_TIME FROM V$RETENTION_JOB; ``` ### Retention Policy 생성 ```sql CREATE RETENTION policy_name DURATION duration_value {MONTH|DAY|HOUR|MIN|SEC} INTERVAL interval_value {DAY|HOUR|MIN|SEC}; ``` `DURATION`은 월부터 초까지, `INTERVAL`은 `DAY`부터 `SEC`까지 지정할 수 있습니다. `MONTH`는 달력 월이 아니라 고정 30일이므로 법적 보관처럼 달력 경계가 중요한 정책은 `DAY` 단위로 환산하고 실제 삭제 기준을 검증합니다. 정확한 파서 문법과 적용 가능한 테이블 타입은 [RETENTION 문법](/dbms/reference/sql/syntax/retention-syntax/)을 참고합니다. 예를 들어 30일을 보존하고 하루마다 삭제 대상을 처리하는 정책은 다음과 같습니다. ```sql CREATE RETENTION policy_30d DURATION 30 DAY INTERVAL 1 DAY; SELECT * FROM M$RETENTION WHERE POLICY_NAME = 'POLICY_30D'; ``` 정책 생성과 삭제에는 필요한 관리 권한이 있어야 합니다. 운영 계정으로 실행하기 전에 권한과 변경 승인 범위를 확인하십시오. ### 테이블에 적용 ```sql ALTER TABLE sensor_tag ADD RETENTION policy_30d; SELECT USER_NAME, TABLE_NAME, POLICY_NAME, STATE FROM V$RETENTION_JOB WHERE TABLE_NAME = 'SENSOR_TAG'; ``` 한 테이블에는 하나의 정책만 적용할 수 있습니다. TAG 테이블은 `BASETIME`, LOG 테이블은 `_ARRIVAL_TIME`을 기준으로 오래된 데이터를 판정합니다. 적용 직후 삭제되는 것이 아니라 설정한 주기에 따라 작업이 실행됩니다. ### 테이블에서 해제 ```sql ALTER TABLE sensor_tag DROP RETENTION; ``` 해제하면 자동 삭제가 중단되지만 이미 삭제된 데이터는 복구되지 않습니다. 해제 후 `V$RETENTION_JOB`에서 대상 테이블이 사라졌는지 확인합니다. ### Retention Policy 삭제 정책을 사용하는 모든 테이블에서 먼저 해제한 뒤 정책 객체를 삭제합니다. ```sql SELECT USER_NAME, TABLE_NAME FROM V$RETENTION_JOB WHERE POLICY_NAME = 'POLICY_30D'; -- 조회된 각 테이블에서 정책을 해제한 뒤 실행 DROP RETENTION policy_30d; ``` `ALTER TABLE ... DROP RETENTION`은 테이블과 정책의 연결을 해제하고, `DROP RETENTION`은 정책 객체를 삭제합니다. 적용 중인 정책은 삭제할 수 없습니다. ### 적용 범위와 권한 | 테이블 타입 | 적용 가능 | |---|---| | TAG, KV, LOG | 예 | | TRANSACTION, VOLATILE, LOOKUP | 아니요 | 정책을 만들거나 삭제할 계정과 테이블 소유 계정이 다르면 운영 전에 실제 권한 구성을 검증하십시오. 다른 소유자의 테이블을 대상으로 할 때는 명시적으로 필요한 권한만 부여합니다. ### 실행 상태 확인 ```sql SELECT USER_NAME, TABLE_NAME, POLICY_NAME, STATE, LAST_DELETED_TIME FROM V$RETENTION_JOB ORDER BY USER_NAME, TABLE_NAME; ``` `STATE`는 작업의 현재 상태이고, `LAST_DELETED_TIME`은 마지막 삭제 작업에 사용한 기준 시각입니다. 벽시계 기준의 작업 완료 시각으로 해석하지 마십시오. 실제 삭제 여부는 대상 테이블의 가장 오래된 시간과 행 수 추세를 함께 확인합니다. --- title: "13.6 관측과 진단" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/diagnosis-observability/ language: kr kind: page --- # 13.6 관측과 진단 운영 진단은 재현 시각과 증상을 기록한 뒤 서버 상태, 세션·문장, 스토리지·메모리, 관련 로그를 같은 시간축에서 확인합니다. `M$` 메타데이터 테이블은 스키마를, `V$` virtual 테이블은 현재 상태를 제공합니다. ## 진단과 로그 1. 문제 시작·종료 시각과 클라이언트 정보를 기록합니다. 2. `machadmin -e`로 서버 응답을 확인합니다. 3. `V$SESSION`과 `V$STMT`에서 관련 작업을 찾습니다. 4. 스토리지·메모리·ROLLUP 등 증상에 맞는 가상 테이블을 확인합니다. 5. 같은 시각의 서버·클라이언트·loader 로그를 비교합니다. 6. 변경 전 현재 설정 속성과 기준값을 저장합니다. ## 트레이스 로그 설정 trace level과 파일 크기·보존 설정은 장애 분석에 필요한 범위만 조정합니다. 상세 설정 속성은 [설정 레퍼런스](/dbms/reference/configuration/configuration/)에서 현재 릴리스의 이름, 허용값, 재시작 필요 여부를 확인합니다. 민감 SQL과 데이터가 기록될 수 있으므로 로그 접근 권한과 보존 기간을 설정합니다. ## 서버 로그 기본 trace 디렉터리는 `$MACHBASE_HOME/trc`입니다. 파일명이 고정돼 있다고 가정하지 말고 현재 디렉터리와 설정값을 확인합니다. ```bash ls -lh "$MACHBASE_HOME/trc" tail -n 200 "$MACHBASE_HOME/trc/machbase.trc" ``` 오류 문자열만 세는 대신 최초 오류, 직전 경고, 서버 시작·종료, 체크포인트·스토리지 사건을 시간 순서로 봅니다. 로그 파일을 수동 명령으로 일괄 삭제하지 않습니다. ## machsql 로그 `machsql.history`에는 자격 증명이나 민감 SQL이 남을 수 있습니다. 운영 계정의 history 권한을 제한하고, 장애 공유 전에 내용을 검토합니다. 재현 SQL은 대상 데이터베이스와 실행 시각, 결과·오류를 함께 보존합니다. ## machloader 로그 대량 적재에서는 프로세스 종료 코드, 요약, 로그, 오류 행 파일을 함께 확인합니다. - 스키마와 입력 컬럼 수·순서 - 구분자, 인용 문자, 인코딩 - NULL과 DATETIME 형식 - 최초 실패 행과 반복되는 오류 코드 - 최종 성공·실패 건수 오류 행 파일을 그대로 전체 재실행하지 말고 원인을 수정한 표본으로 검증한 뒤 실패 행만 재처리합니다. ## 메타데이터 테이블 `M$SYS_TABLES`, `M$SYS_COLUMNS`, `M$SYS_INDEXES` 등으로 현재 데이터베이스의 스키마를 확인합니다. 이름만으로 조인하지 말고 데이터베이스 ID, 소유자 ID, object ID를 포함합니다. reserved object 이름과 내부 테이블 구조에 애플리케이션이 의존하지 않도록 합니다. ## 가상 테이블 먼저 현재 릴리스에서 실제 컬럼을 확인합니다. ```sql SELECT * FROM V$SESSION LIMIT 1; SELECT * FROM V$STMT LIMIT 1; SELECT * FROM V$PROPERTY LIMIT 1; SELECT * FROM V$STORAGE_USAGE LIMIT 1; SELECT * FROM V$SYSMEM LIMIT 1; SELECT * FROM V$ROLLUP LIMIT 1; SELECT * FROM V$LICENSE_INFO LIMIT 1; ``` 전체 목록과 컬럼 의미는 [system 카탈로그](/dbms/reference/system-catalog/virtual-table-full/)을 참고합니다. ## 모니터링과 용량 관리 고정 임계값보다 정상 기준값, 증가율, 업무 최대 부하, 복구 여유를 기준으로 경보를 설정합니다. 파일 시스템 사용량과 데이터베이스 스토리지 사용량을 함께 보고 백업·내보내기가 사용하는 별도 공간도 포함합니다. ## 서버 상태 ```bash "$MACHBASE_HOME/bin/machadmin" -e ``` 프로세스 존재만으로 정상이라고 판단하지 않습니다. 네이티브 연결과 가벼운 SQL, 최근 서버 로그까지 확인합니다. ## 세션과 실행 SQL ```sql SELECT id, user_name, user_ip, login_time, client_type FROM V$SESSION ORDER BY login_time DESC; SELECT sess_id, id AS stmt_id, state, record_size, query FROM V$STMT ORDER BY sess_id, id; ``` 장시간 실행이 곧 오류는 아닙니다. 업무 종류, 처리 행, 클라이언트 시간 초과, I/O·CPU 상태를 함께 확인한 뒤 cancel·kill 여부를 결정합니다. ## 디스크 용량 ```bash df -h "$MACHBASE_HOME" du -sh "$MACHBASE_HOME/dbs" ``` 별도 `DBS_PATH`를 사용하면 실제 경로를 확인합니다. 내부 파티션 파일을 직접 수정하거나 삭제하지 않습니다. ## 메모리 OS available 메모리·swap과 `V$SYSMEM`, 캐시·쿼리 동시성을 함께 비교합니다. manager별 내부 이름을 자동화의 고정 기준으로 사용하지 않습니다. ## 백업 검증 백업 명령 성공만 확인하지 않습니다. 경로·크기·완료 상태를 기록하고 격리 환경에서 마운트 또는 복원 후 주요 테이블, 행 수, 시간 범위, 표본 쿼리를 검증합니다. ## 장애 자료 수집 - 릴리스와 에디션 - 문제 시각·시간대 - 재현 명령과 데이터베이스·사용자 - 서버·클라이언트·loader 로그 - 관련 가상 테이블 결과 - OS CPU·I/O·메모리·디스크 - 최근 스키마·설정 속성·배포 변경 - 이미 시도한 조치와 결과 자격 증명, AUTH KEY, 개인정보와 원문 민감 데이터는 지원 자료에서 제거합니다. --- title: "13.7 스키마 변경 체크리스트" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/checklist-schema-alter/ language: kr kind: page --- # 13.7 스키마 변경 체크리스트 운영 중 스키마 변경 전에 아래 항목을 순서대로 확인합니다. ## 변경 전 점검 ### 1. 테이블 타입 확인 ```sql SELECT NAME AS TABLE_NAME, TYPE AS TABLE_TYPE FROM M$SYS_TABLES WHERE NAME = 'TARGET_TABLE'; ``` 테이블 타입별 ALTER TABLE 지원 범위가 다릅니다. [테이블 타입별 관리 가능 범위](/dbms/reference/support-scope-constraints/table-types-type/)를 미리 확인하십시오. ### 2. 현재 스키마 확인 ```sql -- 컬럼 정보 확인 DESC target_table; -- 인덱스 확인 SELECT i.NAME AS INDEX_NAME, i.TYPE AS INDEX_TYPE FROM M$SYS_INDEXES i JOIN M$SYS_TABLES t ON i.DATABASE_ID = t.DATABASE_ID AND i.TABLE_ID = t.ID WHERE t.NAME = 'TARGET_TABLE'; ``` ### 3. 데이터 볼륨 확인 ```sql SELECT COUNT(*) FROM target_table; ``` 대용량 테이블의 스키마 변경은 시간이 걸릴 수 있습니다. 테스트 환경에서 소요 시간과 잠금 영향을 측정한 뒤 서비스의 유지보수 시간에 실행하십시오. ### 4. Retention Policy 적용 여부 ```sql SELECT * FROM V$RETENTION_JOB WHERE TABLE_NAME = 'TARGET_TABLE'; ``` 스키마 변경 전 Retention Policy가 실행 중이라면 완료 후 작업하십시오. ### 5. DDL 충돌 정책 설정 Standard Edition은 서로 다른 객체의 DDL을 동시에 수행할 수 있습니다. 같은 객체나 직접 관련된 객체의 DDL은 충돌하므로 운영 배포 세션에서 허용할 대기 시간을 먼저 설정합니다. ```sql -- 충돌한 DDL 잠금을 최대 10초 동안 대기 ALTER SESSION SET DDL_LOCK_TIMEOUT = 10; -- 세션별 설정값 확인 SELECT id, user_name, ddl_lock_timeout FROM v$session WHERE closed = 0 ORDER BY id; ``` | 동시 실행 대상 | 판단 | |----------------|------| | 이름이 서로 다른 독립 테이블 | 병렬 실행 가능 | | 동일 객체 또는 동일 이름 | 충돌 | | 테이블 변경·삭제 DDL과 해당 테이블의 인덱스 DDL | 충돌 | | 뷰 DDL과 원본 테이블의 변경·삭제 DDL | 충돌 | | TAG 테이블 변경·삭제 DDL과 해당 Rollup 또는 Retention DDL | 충돌 | Cluster Edition에는 `DDL_LOCK_TIMEOUT`이 없으며 기존 DDL 직렬화 정책을 사용합니다. 자세한 동작은 [DDL 동시성과 잠금](/dbms/reference/sql/syntax/ddl-syntax/#ddl-concurrency)을 참고하십시오. --- ## 컬럼 추가 체크리스트 - [ ] 추가할 컬럼의 데이터 타입이 해당 테이블 타입에서 지원되는가? - [ ] LOG/TRANSACTION 테이블 컬럼 추가 시 기존 데이터의 새 컬럼 값은 NULL로 채워짐을 인지하고 있는가? - [ ] 컬럼명 중복 여부 확인 ```sql ALTER TABLE sensor_log ADD COLUMN (new_col DOUBLE); ``` --- ## 컬럼 삭제 체크리스트 - [ ] 삭제할 컬럼이 인덱스에 포함되어 있는가? (인덱스 먼저 삭제 필요) - [ ] 애플리케이션에서 해당 컬럼을 참조하는 쿼리가 있는가? - [ ] 컬럼 삭제 후 데이터는 복구 불가 ```sql ALTER TABLE sensor_log DROP COLUMN (old_col); ``` --- ## 인덱스 변경 체크리스트 - [ ] 인덱스 생성/삭제는 쿼리 성능에 직접 영향 - [ ] 인덱스 생성 작업은 기존 데이터에 대한 인덱싱을 포함하므로, 대용량 테이블에서는 시간이 소요됨 - [ ] 사용하지 않는 인덱스는 INSERT 성능을 저하시키므로 삭제 고려 - [ ] `IF NOT EXISTS` 사용 시 같은 이름의 기존 인덱스 정의를 별도로 확인 ```sql -- 반복 배포에서 조건부 생성 CREATE INDEX IF NOT EXISTS idx_new ON sensor_log (sensor_id); -- name-only no-op일 수 있으므로 실제 mapping 확인 SHOW INDEX idx_new; -- 불필요한 인덱스 삭제 DROP INDEX idx_old; ``` `IF NOT EXISTS`는 같은 데이터베이스와 소유자의 인덱스 이름만 확인합니다. 기존 인덱스의 테이블, 컬럼, 타입과 설정 속성이 배포 의도와 일치하는지는 [INDEX 문법](/dbms/reference/sql/syntax/index-syntax/#create-index-if-not-exists)의 규칙에 따라 별도로 검증합니다. --- ## Retention Policy 변경 체크리스트 - [ ] 정책 변경 필요 시: 기존 정책 해제 → 새 정책 생성/적용 - [ ] 보존 기간 단축 시: 다음 삭제 작업에서 대상이 늘어날 수 있으므로 데이터 손실 가능성 검토 ```sql -- 기존 정책 해제 ALTER TABLE sensor_tag DROP RETENTION; -- 새 정책 적용 ALTER TABLE sensor_tag ADD RETENTION new_policy; ``` --- ## DDL 충돌 처리 기본 `DDL_LOCK_TIMEOUT=0`에서는 충돌 시 `ERR-02031: Resource busy ()`가 즉시 반환됩니다. 1. `ERR-02031`만 제한된 횟수와 대기 간격을 두고 재시도합니다. 2. 재시도하기 전에 대상 객체와 의존 객체의 현재 상태를 다시 조회합니다. 3. 대기 후에는 선행 DDL의 결과에 따라 `already exists`나 `table not found`가 반환될 수 있습니다. 4. 문법 오류, 권한 오류, `already exists`, `table not found`는 같은 SQL로 반복 재시도하지 않습니다. 5. `machsql`을 사용하는 자동화는 프로세스 종료 코드뿐 아니라 출력의 `ERR-`도 확인합니다. DDL 대기 시간이 끝나거나 작업이 취소된 뒤에는 다시 실행할 수 있지만, 선행 작업의 반영 여부를 확인한 다음 재시도해야 합니다. --- ## 변경 후 검증 ```sql -- 스키마 변경 확인 DESC target_table; -- 데이터 정합성 확인 SELECT COUNT(*) FROM target_table; -- 인덱스 상태 확인 SELECT i.NAME AS INDEX_NAME, i.TYPE AS INDEX_TYPE FROM M$SYS_INDEXES i JOIN M$SYS_TABLES t ON i.DATABASE_ID = t.DATABASE_ID AND i.TABLE_ID = t.ID WHERE t.NAME = 'TARGET_TABLE'; ``` --- **다음으로 읽을 내용:** - [데이터 보존 정책](/dbms/operations-configuration-recovery/policy-data-retention/) - [운영 및 구성](/dbms/operations-configuration-recovery/) --- title: "13.8 백업, 복원, 마운트" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/backup-restore-mount/ language: kr kind: page --- # 13.8 백업, 복원, 마운트 백업은 생성 성공뿐 아니라 격리 환경의 마운트·복원과 표본 쿼리까지 검증해야 합니다. 경로, 권한, 저장 공간, 보존 기간, 암호화·접근 통제를 함께 운영합니다. 전체 SQL과 옵션은 [백업·복원·마운트 구문](/dbms/reference/sql/syntax/backup-restore-mount-syntax/)을 참고합니다. ## 백업 선택 | 목적 | 방식 | |------|------| | 인스턴스의 전체 복구 기준점 | 데이터베이스 전체 백업 | | 특정 테이블 이동·보존 | 테이블 백업 | | 이전 백업 이후 변경분 | 증분 백업 | | 특정 시간 범위 보관 파일 | 기간 백업 | | 운영 중 백업 조회 | 읽기 전용 마운트 | | 인스턴스 데이터 교체 | 오프라인 복원 | 백업 종류를 선택하기 전에 에디션, 테이블 타입, 증분 체인, 마운트·복원 지원 범위를 현재 릴리스에서 확인합니다. ## 전체 백업 전체 백업은 독립 복구 기준점으로 보존합니다. - 백업 경로는 서버 프로세스가 접근합니다. - 대상 파일 시스템의 여유 공간과 할당량을 확인합니다. - 실행 시작·종료·오류와 결과 크기를 기록합니다. - 백업과 원본 데이터를 같은 장애 영역에만 두지 않습니다. - 정기적으로 격리 서버에 복원해 주요 테이블을 검증합니다. ## 테이블 백업 테이블 백업은 지원되는 테이블 타입과 인덱스·메타데이터 포함 범위를 확인합니다. 테이블 한 개를 복구할 때는 새 데이터베이스 또는 마운트에서 검증한 뒤 명시적인 INSERT·export/import 경로로 옮기는 방식을 우선합니다. 존재하는 운영 테이블을 즉시 교체하지 않습니다. ## 증분 백업과 AFTER 증분 백업은 이전 백업 이후의 변경분을 저장합니다. 복원에 필요한 기준 백업과 연결된 증분 백업을 하나의 체인으로 관리합니다. - 각 백업의 기준 백업과 생성 순서를 기록합니다. - 중간 파일 하나가 없을 때 복구 가능한지 점검합니다. - 마지막 증분만 따로 보관하지 않습니다. - 체인이 길어지면 새 전체 백업 기준점을 만듭니다. - 오프라인 복원은 최종 증분 백업 경로를 `machadmin -r`에 한 번 지정합니다. 전체 백업부터 각각 반복 적용하지 않습니다. 필요한 체인을 모두 보존한 상태에서 최종 시점의 데이터가 복원되는지 확인합니다. ## 기간 백업 기간 백업은 `BACKUP DATABASE FROM ... TO ...`로 시작 시각과 종료 시각을 지정합니다. 일반 조회의 `WHERE` 조건과 구문을 혼동하지 마십시오. 지정한 시간대와 경계 시각의 데이터를 포함해 백업 결과의 최소·최대 시각과 행 수를 원본과 비교합니다. ## SQL BACKUP BACKUP 실행 계정에는 필요한 데이터베이스·테이블 권한과 서버 경로 접근이 필요합니다. 운영 자동화에서는 비밀번호를 명령행에 고정하지 않고, 종료 코드와 작업 상태를 함께 확인합니다. 실행 전후 점검: 1. 대상 데이터베이스·테이블과 백업 종류 확인 2. 백업 대상 경로가 아직 존재하지 않는 고유한 경로인지 확인 3. 여유 공간과 예상 증가량 확인 4. 백업 작업 완료·오류 확인 5. 결과 목록·크기·체크섬 또는 스토리지 검증 6. 마운트·복원 표본 검증 ## 오프라인 복원 `machadmin -r`을 사용하는 오프라인 복원은 현재 인스턴스 데이터를 교체하는 작업입니다. 기존 데이터베이스가 있으면 복원이 거부되므로, 현재 데이터의 보존과 복구 대상 확인을 완료한 뒤 검증된 절차에 따라 서버 종료와 기존 데이터베이스 제거를 수행합니다. 8.7.0 Standard Edition의 논리 데이터베이스 온라인 복원은 별도 SQL인 `RESTORE DATABASE`를 사용합니다. 두 복원 방식의 대상과 사전 조건을 구분하십시오. - 서비스와 모든 클라이언트·Collector를 중지할 계획을 세웁니다. - 현재 데이터의 별도 백업과 롤백 경로를 확보합니다. - 복원할 정확한 백업과 체인을 검증합니다. - 동일 릴리스·에디션·설정 호환성을 확인합니다. - 복구 담당자 두 명이 대상 인스턴스와 경로를 교차 확인합니다. - 격리 복구 훈련을 통과한 운영 절차서만 운영에 적용합니다. - 복원 뒤 스키마, 행 수, 시간 범위, 애플리케이션 쿼리를 검증합니다. ## 데이터베이스 마운트 마운트는 백업을 읽기 전용 데이터베이스로 연결해 조사·선별 복구할 때 사용합니다. ```text MOUNT DATABASE '/absolute/backup/path' TO mount_name; UMOUNT DATABASE mount_name; ``` 운영 활성 데이터베이스와 겹치지 않는 마운트 이름을 사용합니다. 마운트 경로와 권한은 서버 프로세스 기준입니다. ## 마운트된 데이터베이스 조회 ```text SELECT * FROM mount_name.SYS.table_name WHERE _ARRIVAL_TIME >= TO_DATE('2026-01-01', 'YYYY-MM-DD'); ``` 먼저 테이블 목록과 스키마를 확인하고, 시간 범위·행 수·표본값을 검증합니다. 필요한 데이터는 현재 스키마와 중복 정책을 확인한 뒤 선별 이동합니다. ## 읽기 전용과 사용 중인 마운트 마운트된 데이터베이스에는 DDL·DML을 실행하지 않습니다. 열린 커서나 문장이 있으면 마운트 해제가 거부될 수 있으므로 모든 참조를 닫고 재시도합니다. 활성 데이터베이스나 다른 마운트와 이름이 충돌하지 않는지 먼저 확인합니다. ## 지원하지 않는 경로 일부 내부 문법이 성공처럼 보이더라도 공개 운영 API가 아닌 `MOUNT TABLE`·`UMOUNT TABLE`에 의존하지 않습니다. 공개 `MOUNT DATABASE`와 `UMOUNT DATABASE`를 사용합니다. ## 테이블 타입과 에디션별 범위 LOG, TAG, TRANSACTION, LOOKUP, VOLATILE의 백업·마운트 동작은 동일하지 않습니다. VOLATILE은 서버 재시작에 유지되지 않는 메모리 테이블입니다. 테이블·에디션별 범위는 [백업·마운트 지원 범위](/dbms/reference/support-scope-constraints/backup-mount/)를 확인합니다. ## 복구 검증표 - 데이터베이스·소유자·테이블 수 - 주요 테이블 스키마와 인덱스 - 행 수와 최소·최대 시간 - NULL·문자열·숫자 표본 - 사용자·권한과 애플리케이션 연결 - ROLLUP·보존 정책·작업 상태 - 백업 시점 이후 데이터의 처리 - 롤백 가능 여부와 실제 소요 시간 --- title: "13.9 Cluster 운영" url: https://docs.machbase.com/kr/dbms/operations-configuration-recovery/cluster/ language: kr kind: page --- # 13.9 Cluster 운영 Cluster 작업은 노드 구성, 노드 역할, 복제·데이터 상태를 확인한 뒤 검증된 운영 절차서로 수행합니다. 이 페이지의 점검 순서로 변경 영향과 복구 경로를 확인한 뒤, 설치된 버전의 관리 도구 명령을 운영 절차서에 반영합니다. ## 구성 요소 | 역할 | 확인 | |------|------| | Coordinator | 노드 구성과 노드 상태 | | Deployer | 패키지와 노드 배포 | | Broker | 클라이언트 연결과 쿼리 라우팅 | | Warehouse | 데이터 저장·쿼리 처리 | | Lookup | 참조 데이터 서비스 | 노드 수와 배치는 가용성·처리량·장애 영역 요구사항을 근거로 설계합니다. 고정 노드 수나 하드웨어 사양을 모든 환경에 적용하지 않습니다. ## 상태 확인 변경 전후에 Coordinator 관점의 전체 노드 구성과 각 노드의 프로세스·자원을 확인합니다. 상태 문자열과 명령 옵션은 설치된 도구의 도움말을 기준으로 합니다. ```bash machcoordinatoradmin --help machclusterctl --help ``` - 예상한 노드가 모두 등록돼 있는가 - 노드 역할, 호스트, 포트, 그룹이 배포 기록과 같은가 - 서비스·복제·scrap 상태가 정상인가 - 노드별 CPU·메모리·디스크·네트워크 편차가 있는가 - Broker를 통한 실제 연결·쿼리가 성공하는가 ## 접속과 설정 내보내기 `machclusterctl connect`로 접속할 때 대상 Broker와 네이티브 포트를 명시하고 실제 `CURRENT_DATABASE()`와 표본 쿼리를 확인합니다. 설정 내보내기에는 호스트·포트·경로와 운영 정보가 포함될 수 있으므로 접근 권한을 제한하고, 가져오기 전에 diff를 검토합니다. ## 노드 시작·종료 노드 제어 전 다음을 확인합니다. 1. 대상 노드 이름·별칭·호스트·역할 2. 클라이언트 연결과 진행 중 쿼리·Appender 3. Warehouse 그룹의 이중화와 데이터 상태 4. 노드 중지 시 남은 처리 용량 5. 시작·중지 순서와 롤백 6. 유지보수 뒤 정상 판정 기준 강제 종료는 정상 종료가 반복해서 실패하고 데이터·복구 영향을 판단한 경우에만 사용합니다. 단순 시간 초과에 바로 kill을 실행하지 않습니다. ## Cluster 전체 제어와 destroy 전체 시작·중지는 Coordinator, Deployer, Broker, Warehouse, Lookup 의존 순서를 현재 릴리스 운영 절차서에서 확인합니다. `destroy`는 노드 구성과 노드 데이터를 제거할 수 있는 파괴적 작업입니다. - 이름이 비슷한 다른 cluster가 아닌지 확인 - 최신 백업과 복원 검증 - 서비스 소유자 승인과 클라이언트 차단 - 외부 `DBS_PATH` 포함 삭제 범위 확인 - 롤백 불가능 영역 명시 - 실행 후 호스트별 잔여 프로세스·경로 확인 일반 상태 복구에 `destroy`를 사용하지 않습니다. ## 노드 추가·제거 추가 전 패키지·버전·포트·경로·파일 시스템·네트워크를 확인합니다. 제거 전에는 데이터 이중화와 이전 완료, 노드를 참조하는 별칭·그룹·모니터링을 확인합니다. 노드 remove가 home과 데이터 경로를 삭제할 수 있으므로 정확한 범위는 해당 명령 도움말과 검증 환경 검증으로 확인합니다. ## 상태 변경 Broker 비활성화, Warehouse 그룹 읽기 전용, 노드 scrap 같은 상태 변경은 목적이 다릅니다. 문제 노드를 숨기기 위해 상태를 임의 변경하지 않습니다. | 목적 | 먼저 확인 | |------|-----------| | 새 연결 차단 | Broker drain과 기존 연결 | | 쓰기 중단 | Warehouse 그룹과 진행 중 Append | | 노드 격리 | 복제·데이터 손상 근거 | | 서비스 복귀 | 정상 동작 여부, 데이터 동기화, 표본 쿼리 | ## Warehouse 복구 1. 장애 시각과 최초 오류를 보존합니다. 2. 프로세스, 디스크, 네트워크, 복제 상태를 확인합니다. 3. 남은 노드의 이중화와 서비스 영향을 판단합니다. 4. 단순 재시작, reattach, rebuild 중 지원되는 경로를 선택합니다. 5. 복구 진행률과 오류를 관찰합니다. 6. 완료 후 노드별 행·시간 범위와 쿼리 결과를 비교합니다. 데이터 손상 여부를 확인하지 않고 노드를 normal로 강제 전환하지 않습니다. ## 제약과 점검표 에디션별 SQL, ROLLUP, 백업, ALTER SYSTEM 지원 범위는 [지원 범위](/dbms/reference/support-scope-constraints/)를 확인합니다. - 모든 노드와 클라이언트 SDK 릴리스가 호환되는가 - 유지보수 중 허용되는 읽기·쓰기 범위가 정의됐는가 - 백업·복원과 노드 복구 훈련이 완료됐는가 - 호스트·포트·경로를 두 사람이 교차 확인했는가 - 모니터링과 알림이 새 노드 구성을 반영하는가 - 변경 후 Broker 연결, 쿼리, Append, 메타데이터를 검증했는가 --- title: "14. 계정, 권한, 접속 제어" url: https://docs.machbase.com/kr/dbms/security-access-control/ language: kr kind: section --- # 14. 계정, 권한, 접속 제어 운영 환경에서 데이터를 보호하려면 계정, 권한, 접속 제어를 체계적으로 구성해야 합니다. 이 장에서는 보안 모델 전반을 다루며, 실무에 맞는 설정 방법을 안내합니다. ## 이 장의 구성 | 순서 | 섹션 | 설명 | |-----:|------|------| | 14.1 | [보안 모델 개요](./security-model/) | Machbase 보안 구조, 기본 계정, 권한 종류, AUTH KEY 인증 방식 | | 14.2 | [계정 관리](./account/) | 사용자 생성·삭제·비밀번호 변경, 비밀번호 정책(NONE/LOW/HIGH) | | 14.3 | [권한 관리](./privileges/) | GRANT/REVOKE, 테이블 권한, 데이터베이스 권한 | | 14.4 | [AUTH KEY 인증](./authentication-auth-key/) | 공개키 기반 challenge 인증, 키 생성·등록·관리 | | 14.5 | [접속 제어](./access-control/) | 원격 접속 허용 여부와 바인드 IP 설정 | | 14.6 | [보안 설정 체크리스트](./checklist-configuration/) | 운영 환경 배포 전 점검 항목 | ## 보안의 4대 영역 **1. 보안 모델** -- 사용자 계정, 권한 체계, 인증 방식의 연동 구조를 파악합니다. SYS는 관리 작업으로 제한하고 각 사용자에게 최소 권한만 부여합니다. **2. 계정 관리** -- 데이터베이스에 접근하는 주체를 관리합니다. 사용자 계정을 생성·삭제하고 비밀번호 정책(NONE/LOW/HIGH)과 공개키 기반 AUTH KEY 인증을 적용합니다. **3. 권한 관리** -- 계정이 수행할 수 있는 작업을 제한합니다. 테이블 단위 DML 권한(SELECT, INSERT, DELETE, UPDATE)과 데이터베이스 단위 DDL·운영 권한(CREATE, DROP, ALTER, BACKUP, MOUNT)을 세분화해 최소 권한 원칙을 적용합니다. **4. 접속 제어** -- 어떤 네트워크 경로에서 연결을 허용할지 제어합니다. `GRANT_REMOTE_ACCESS`로 원격 접속 허용 여부를 결정하고, `BIND_IP_ADDRESS`로 리스너가 열리는 네트워크 인터페이스를 지정합니다. --- title: "14.1 보안 모델 개요" url: https://docs.machbase.com/kr/dbms/security-access-control/security-model/ language: kr kind: page --- # 14.1 보안 모델 개요 Machbase의 보안 모델은 **사용자 계정**, **권한(GRANT/REVOKE)**, **접속 제어(IP/인증)** 세 요소가 계층적으로 결합된 구조입니다. ## Machbase 보안 구조 ``` 클라이언트 연결 요청 │ ▼ ┌─────────────────────┐ │ 접속 제어 │ BIND_IP_ADDRESS, GRANT_REMOTE_ACCESS │ (네트워크 레이어) │ └────────┬────────────┘ │ 허용된 경우 ▼ ┌─────────────────────┐ │ 인증 │ 비밀번호 인증 또는 AUTH KEY (공개키) 인증 │ (사용자 식별) │ └────────┬────────────┘ │ 인증 성공 ▼ ┌─────────────────────┐ │ 권한 검사 │ GRANT/REVOKE로 부여된 권한 확인 │ (작업 허용 여부) │ 테이블 권한 + 데이터베이스 권한 └─────────────────────┘ ``` 연결 요청은 먼저 네트워크 접속 제어를 통과해야 하고, 이후 사용자 인증을 거쳐, 마지막으로 해당 작업에 필요한 권한이 부여되어 있는지 확인하는 순서로 진행됩니다. ## 기본 계정: SYS Machbase를 설치하면 `SYS` 계정이 자동으로 생성됩니다. SYS는 슈퍼유저로서 모든 데이터베이스 작업을 수행할 수 있고, 다른 사용자를 생성하고 권한을 부여합니다. | 항목 | 내용 | |------|------| | 계정명 | `SYS` | | 기본 비밀번호 | `MANAGER` | | 권한 | 모든 권한 보유 (슈퍼유저) | | 삭제 가능 여부 | 불가 | > **운영 환경 필수 조치**: SYS 계정의 기본 비밀번호(`MANAGER`)는 설치 후 즉시 변경해야 합니다. > > ```sql > ALTER USER SYS IDENTIFIED BY '새_비밀번호'; > ``` ## 관련 문서 - 사용자 생명 주기와 비밀번호 정책: [계정 관리](../account/) - 데이터베이스·테이블 권한과 GRANT/REVOKE: [권한 관리](../privileges/) - 공개키 등록·롤오버: [AUTH KEY 인증](../authentication-auth-key/) - 원격 접속과 listener: [접속 제어](../access-control/) 계정 생성, 권한 부여와 인증 설정은 각각 별도의 작업입니다. 아래 예제의 계정과 테이블을 준비한 뒤, 각 계정으로 다시 접속하여 허용한 작업과 제한한 작업을 확인하십시오. ## 최소 권한 원칙 운영 환경에서는 다음 원칙을 적용하십시오. - **SYS는 관리 작업 전용**: 일상적인 데이터 조회나 입력에는 SYS 계정을 사용하지 않습니다. - **용도별 계정 분리**: 읽기 전용 계정, 데이터 입력 계정, 배포(DDL) 계정, 백업 계정을 분리합니다. - **테이블 단위 권한 제한**: 계정이 접근해야 하는 테이블에만 필요한 DML 권한을 부여합니다. - **정기 점검**: 불필요한 계정을 제거하고 권한이 과도하게 부여된 계정을 확인합니다. ```sql -- 읽기 전용 계정 CREATE USER reader IDENTIFIED BY 'Reader#Strong123'; GRANT SELECT ON sys.sensor_log TO reader; -- 데이터 입력 전용 계정 CREATE USER writer IDENTIFIED BY 'Writer#Strong123'; GRANT SELECT, INSERT ON sys.sensor_log TO writer; -- DDL 전용 계정 (테이블 생성/삭제) CREATE USER deploy IDENTIFIED BY 'Deploy#Strong123'; GRANT CONNECT ON DATABASE factory_a TO deploy; GRANT DDL ON DATABASE factory_a TO deploy; ``` --- title: "14.2 계정 관리" url: https://docs.machbase.com/kr/dbms/security-access-control/account/ language: kr kind: page --- # 14.2 계정 관리 애플리케이션과 운영 작업에는 서로 다른 사용자 계정을 사용합니다. `SYS`는 사용자와 권한을 관리하는 계정으로 제한하고, 일상적인 조회·적재에는 필요한 권한만 가진 계정을 사용하십시오. ## 사용자 생성과 삭제 ```sql CREATE USER app_user IDENTIFIED BY 'App#Strong123' PASSWORD POLICY HIGH; SELECT USER_ID, NAME, PWD_POLICY_LEVEL, VALID_BEFORE FROM M$SYS_USERS WHERE NAME = 'APP_USER'; ``` 사용자명은 대문자로 저장됩니다. 논리 데이터베이스에 연결하려면 계정 생성 후 대상 데이터베이스의 `CONNECT` 권한을 별도로 부여합니다. ```sql GRANT CONNECT ON DATABASE factory_a TO app_user; GRANT SELECT, INSERT ON TABLE factory_a.sys.sensor_log TO app_user; ``` 비밀번호를 바꿀 때는 새 비밀번호와 정책을 함께 지정할 수 있습니다. ```sql ALTER USER app_user IDENTIFIED BY 'App#Changed456' PASSWORD POLICY HIGH; ``` 사용자 삭제 전에는 실행 중인 세션, 부여한 권한, 사용자가 소유한 객체를 확인합니다. 소유 객체가 있으면 계정을 삭제할 수 없습니다. ```sql DROP USER app_user; ``` `SYS` 계정과 현재 연결 중인 자기 계정은 삭제할 수 없습니다. `machsql`에서 다른 계정으로 작업을 계속하려면 `CONNECT user/password;`로 새 세션 인증을 수행하거나 클라이언트를 다시 연결합니다. ### 활성 세션이 있는 사용자 삭제 다른 관리자 세션이 사용자를 삭제해도 해당 사용자로 이미 인증한 세션은 즉시 종료되지 않습니다. 기존 세션은 로그인할 때 보존한 사용자명과 내부 ID를 유지하지만, 삭제된 사용자는 새로 접속할 수 없고 `M$SYS_USERS`에서도 조회되지 않습니다. 삭제 전 활성 세션을 확인하고 애플리케이션 연결을 먼저 종료합니다. Machbase 8.7.0부터 기존 세션의 사용자 컨텍스트는 [CURRENT_USER와 SESSION_USER 함수](../../reference/sql/functions/functions-full/#current-session-user)로 확인할 수 있습니다. ## 비밀번호 정책 | 정책 | 주요 동작 | |---|---| | `NONE` | 호환성을 위한 기본 정책. 강도·만료 제약 없음 | | `LOW` | 길이와 문자 조합을 검사 | | `HIGH` | LOW 검사, 최근 비밀번호 재사용 제한, 유효기간 적용 | LOW와 HIGH는 10자 이상의 비밀번호를 요구합니다. 대소문자 검사 방식은 `ENABLE_CASE_SENSITIVE_PASSWORD` 설정의 영향을 받습니다. HIGH는 설정 시점에서 90일 뒤를 `VALID_BEFORE`로 기록합니다. ```sql CREATE USER reader_user IDENTIFIED BY 'Reader#Strong123' PASSWORD POLICY HIGH; ALTER USER reader_user IDENTIFIED BY 'Reader#Changed456' PASSWORD POLICY LOW; ``` 정책만 단독으로 바꾸는 대신 새 비밀번호를 함께 지정합니다. 현재 정책과 만료일은 다음처럼 확인합니다. ```sql SELECT USER_ID, NAME, PWD_POLICY_LEVEL, VALID_BEFORE FROM M$SYS_USERS ORDER BY USER_ID; ``` `PWD_POLICY_LEVEL`은 `0=NONE`, `1=LOW`, `2=HIGH`입니다. 애플리케이션에 비밀번호를 직접 기록하지 말고 운영 환경의 비밀 관리 수단을 사용하십시오. 전체 문법은 [USER/AUTH 문법](/dbms/reference/sql/syntax/user-auth-syntax/#create-drop-alter-user)을 참고하십시오. --- title: "14.3 권한 관리" url: https://docs.machbase.com/kr/dbms/security-access-control/privileges/ language: kr kind: page --- # 14.3 권한 관리 Machbase 권한은 활성 데이터베이스 범위의 관리 권한과 특정 테이블의 DML 권한으로 나뉩니다. 사용자는 데이터베이스에 연결할 `CONNECT` 권한과, 실제 작업 대상에 필요한 권한을 모두 가져야 합니다. ```sql GRANT CONNECT ON DATABASE factory_a TO app_user; GRANT SELECT, INSERT ON TABLE factory_a.sys.sensor_log TO app_user; ``` ## 권한 모델 | 범위 | 권한 | 용도 | |---|---|---| | 활성 데이터베이스 | `CONNECT` | 연결 및 `USE` | | 활성 데이터베이스 | `CREATE`, `DROP`, `ALTER` | 객체 생성·삭제·변경 | | 활성 데이터베이스 | `BACKUP` | 데이터베이스 백업 | | 활성 데이터베이스 | `DDL` | `CREATE`와 `DROP` 묶음 | | 활성 데이터베이스 | `ALL` | `CONNECT`, `CREATE`, `DROP`, `ALTER`, `BACKUP` | | 마운트된 데이터베이스 | `USAGE` | 마운트된 데이터베이스 탐색 | | 관리 데이터베이스 | `MOUNT` | `MOUNT DATABASE`, `UMOUNT DATABASE` | | 테이블 | `SELECT`, `INSERT`, `DELETE`, `UPDATE` | 특정 테이블 DML | | 테이블 | `ALL` | 네 가지 테이블 DML 권한 | 데이터베이스의 `ALL`은 테이블 DML이나 `MOUNT`를 포함하지 않습니다. 테이블의 `ALL`도 데이터베이스 관리 권한을 포함하지 않습니다. 권한이 있어도 해당 테이블 타입이 지원하지 않는 DML은 실행할 수 없습니다. 예를 들어 LOG 테이블은 `UPDATE`를 지원하지 않습니다. ## GRANT / REVOKE ```sql GRANT privilege_list ON target TO user_name; REVOKE privilege_list ON target FROM user_name; ``` 다음 예제는 사용자에게 데이터베이스 연결과 한 테이블의 읽기·쓰기를 허용합니다. ```sql GRANT CONNECT ON DATABASE factory_a TO app_user; GRANT SELECT, INSERT ON TABLE factory_a.sys.sensor_log TO app_user; REVOKE INSERT ON TABLE factory_a.sys.sensor_log FROM app_user; REVOKE CONNECT ON DATABASE factory_a FROM app_user; ``` 테이블은 현재 데이터베이스의 `owner.table` 또는 `database.owner.table`로 지정할 수 있습니다. 데이터베이스 전체에 `SELECT` 같은 DML 권한을 주는 구문은 지원하지 않습니다. 현재 권한 기록은 `M$SYS_USER_ACCESS`에서 확인합니다. ```sql SELECT DB_NAME, USER_NAME, OWNER_NAME, TABLE_NAME, PRIV FROM M$SYS_USER_ACCESS WHERE USER_NAME = 'APP_USER' ORDER BY DB_NAME, OWNER_NAME, TABLE_NAME; ``` `OWNER_NAME`과 `TABLE_NAME`이 `NULL`이면 데이터베이스 범위, 값이 있으면 테이블 범위의 기록입니다. `PRIV`는 복수 권한을 표현하는 비트 마스크이므로 화면에 나온 숫자 하나를 권한명 하나로 해석하거나 운영 스크립트에 고정하지 마십시오. ## 데이터베이스 권한 논리 데이터베이스는 사용자를 만든 것만으로 연결할 수 없습니다. 대상 데이터베이스에 `CONNECT`를 명시적으로 부여하고 필요한 관리 권한을 최소 단위로 추가합니다. ```sql GRANT CONNECT ON DATABASE factory_a TO deploy_user; GRANT DDL ON DATABASE factory_a TO deploy_user; GRANT ALTER ON DATABASE factory_a TO deploy_user; ``` 새 사용자에게 기본으로 기록되는 호환 권한은 기본 데이터베이스인 `MACHBASEDB` 범위입니다. 다른 논리 데이터베이스까지 자동으로 확장되지 않습니다. ### SELECT / INSERT / DELETE / UPDATE DML 권한은 특정 테이블을 대상으로 부여합니다. ```sql GRANT SELECT ON sys.sensor_log TO reader_user; GRANT INSERT ON sys.sensor_log TO writer_user; GRANT DELETE ON sys.device_config TO maint_user; GRANT UPDATE ON sys.device_config TO maint_user; ``` `DELETE`와 `UPDATE`를 부여하기 전에는 대상 테이블 타입의 조건 제약을 함께 검토합니다. TAG 데이터 변경은 태그와 시간 조건이 필요하고, VOLATILE 변경은 기본 키 조건이 필요합니다. ### CREATE / DROP ```sql GRANT CREATE ON DATABASE factory_a TO deploy_user; GRANT DROP ON DATABASE factory_a TO deploy_user; ``` `DROP`은 복구하기 어려운 변경을 허용하므로, 단순 적재·조회 계정에는 부여하지 마십시오. 객체 소유권만으로 다른 데이터베이스에 접속할 수 있는 것은 아닙니다. ### ALTER ```sql GRANT ALTER ON DATABASE factory_a TO deploy_user; ``` `ALTER`는 테이블 구조와 운영 설정 변경에 영향을 줄 수 있습니다. 애플리케이션 계정과 분리한 배포·운영 계정에만 부여하고, 변경 후 현재 설정과 스키마를 다시 조회하십시오. ### BACKUP ```sql GRANT BACKUP ON DATABASE factory_a TO backup_user; ``` 백업 경로에 대한 Machbase 서버 프로세스 OS 계정의 쓰기 권한과 여유 공간은 SQL 권한과 별도로 필요합니다. 백업용 DB 사용자에는 DML이나 DDL 권한을 함께 주지 않는 구성을 권장합니다. ### MOUNT ```sql GRANT MOUNT ON DATABASE MACHBASEDB TO recovery_user; ``` MOUNT/UMOUNT는 관리 작업입니다. 마운트된 데이터베이스를 탐색할 `USAGE`와 그 안의 테이블을 읽을 `SELECT`는 별도 권한입니다. 실제 복구 절차는 [백업, 복구, 마운트](/dbms/operations-configuration-recovery/backup-restore-mount/)를 따르십시오. ### DDL / ALL 합성 권한 ```sql -- CREATE + DROP GRANT DDL ON DATABASE factory_a TO deploy_user; -- active database의 CONNECT, CREATE, DROP, ALTER, BACKUP GRANT ALL ON DATABASE factory_a TO database_admin; -- 한 테이블의 SELECT, INSERT, DELETE, UPDATE GRANT ALL ON TABLE factory_a.sys.sensor_log TO table_admin; ``` 합성 권한은 편리하지만 최소 권한 검토를 어렵게 할 수 있습니다. 자동화 계정에는 가능한 한 개별 권한을 부여하십시오. ## 기본 부여 권한과 제외 권한 `CREATE USER`가 만든 사용자는 `MACHBASEDB`에 대한 호환 기본 권한 기록을 가집니다. 논리 데이터베이스에서는 다음처럼 필요한 범위를 명시하는 구성을 기준으로 삼으십시오. ```sql GRANT CONNECT ON DATABASE factory_a TO app_user; GRANT SELECT, INSERT ON TABLE factory_a.sys.sensor_log TO app_user; ``` `ALTER`, `BACKUP`, `MOUNT`, `USAGE`와 다른 논리 데이터베이스의 권한은 업무 역할을 검토한 뒤 별도로 부여합니다. ## 테이블 권한 테이블 권한은 반드시 대상 객체와 함께 관리합니다. ```sql GRANT SELECT ON TABLE factory_a.sys.sensor_log TO reader_user; GRANT SELECT, INSERT ON TABLE factory_a.sys.sensor_log TO ingest_user; REVOKE INSERT ON TABLE factory_a.sys.sensor_log FROM ingest_user; ``` 테이블을 삭제할 때 기존 테이블 grant도 정리되며, 같은 이름으로 만든 새 객체에 승계되지 않습니다. 필요한 권한을 다시 부여한 뒤 `M$SYS_USER_ACCESS`에서 확인하십시오. ## 권한 진단 체크리스트 사용자와 권한 현황을 다음 순서로 검토합니다. ```sql SELECT USER_ID, NAME, PWD_POLICY_LEVEL, VALID_BEFORE FROM M$SYS_USERS ORDER BY USER_ID; SELECT DB_NAME, USER_NAME, OWNER_NAME, TABLE_NAME, PRIV FROM M$SYS_USER_ACCESS ORDER BY USER_NAME, DB_NAME, OWNER_NAME, TABLE_NAME; ``` - 사용하지 않는 계정이 남아 있지 않은지 확인합니다. - 읽기 계정에 쓰기·DDL·관리 권한이 없는지 확인합니다. - 임시 권한은 승인된 기간이 끝나면 `REVOKE`하고 결과를 다시 조회합니다. - 사용자를 삭제하기 전 소유 객체와 실행 중인 세션을 확인합니다. - 숫자 `PRIV`를 해석해야 하는 감사 도구는 사용 중인 버전의 권한 정의와 함께 검증합니다. --- title: "14.4 AUTH KEY 인증" url: https://docs.machbase.com/kr/dbms/security-access-control/authentication-auth-key/ language: kr kind: page --- # 14.4 AUTH KEY 인증 AUTH KEY 인증은 서버에 공개키를 등록하고 클라이언트가 보관한 개인키로 서버의 인증 요청값 (challenge)에 서명하는 방식입니다. 비밀번호 대신 개인키를 사용할 수 있지만, 개인키 보호와 교체 절차는 별도로 운영해야 합니다. 연결마다 `AUTH_MODE=PASSWORD` 또는 `AUTH_MODE=CHALLENGE`를 선택합니다. ## 준비 사항 1. 애플리케이션 전용 사용자를 만듭니다. 2. 클라이언트 호스트에서 키 쌍을 생성합니다. 3. 공개키만 서버에 등록합니다. 4. 개인키 파일을 클라이언트의 비밀 저장소에 보관합니다. 5. CHALLENGE 연결을 확인한 뒤 키 만료와 교체 일정을 기록합니다. 지원되는 키와 AUTH KEY SQL의 전체 문법은 [USER/AUTH 문법](/dbms/reference/sql/syntax/user-auth-syntax/#auth-key)을 참고하십시오. ## 사용자 AUTH KEY 관리 등록 상태는 `V$USER_AUTH_KEYS`에서 확인합니다. ```sql SELECT KEY_ID, USER_NAME, KEY_ALGO, KEY_PARAM, ACTIVATED, VALID_AFTER, VALID_BEFORE, COMMENT FROM V$USER_AUTH_KEYS WHERE USER_NAME = 'APP_USER' ORDER BY KEY_ID; ``` `PUBKEY`에는 공개키 본문이 있으므로 일반 운영 보고서에는 포함하지 않는 것이 좋습니다. ### CREATE USER ... WITH AUTH KEY 사용자를 만들면서 공개키를 등록할 수 있습니다. 아래 공개키 문자열은 실제 PEM의 줄바꿈을 `\n`으로 바꾼 값으로 교체합니다. ```sql CREATE USER app_user IDENTIFIED BY 'App#Strong123' WITH AUTH KEY ( key='-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n', valid_before='2047-12-31', comment='initial application key' ); ``` 비밀번호도 비상 복구 경로로 관리될 수 있으므로 별도의 강한 값과 정책을 사용합니다. ### ALTER USER ... ADD AUTH KEY 기존 사용자에는 다음처럼 새 공개키를 추가합니다. ```sql ALTER USER app_user ADD AUTH KEY ( key='-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n', valid_before='2047-12-31', comment='replacement key' ); ``` 등록 후 새 `KEY_ID`, 알고리즘, 활성 상태, 만료일을 조회합니다. ### AUTH KEY 활성화와 비활성화 ```sql ALTER USER app_user DEACTIVATE AUTH KEY ID 3; ALTER USER app_user ACTIVATE AUTH KEY ID 3; ``` 비활성화는 키 메타데이터를 보존하면서 인증을 차단합니다. 운영 키를 비활성화하기 전에는 다른 인증 경로가 실제로 동작하는지 확인하십시오. ### AUTH KEY 만료 변경 ```sql ALTER USER app_user ALTER AUTH KEY ID 3 VALID_BEFORE='2048-06-30'; ``` 만료일을 연장하기 전에 키 사용 주체와 보관 상태를 다시 확인합니다. 유효기간을 연장해도 키 자체가 바뀌지는 않으므로 교체 주기는 별도로 관리합니다. ### AUTH KEY 삭제 ```sql ALTER USER app_user DROP AUTH KEY ID 3; ``` 삭제는 되돌릴 수 없습니다. 새 키 접속 성공과 이전 키 비활성화 상태를 확인한 뒤 삭제합니다. 사용자를 삭제하면 그 사용자의 AUTH KEY도 함께 정리됩니다. ## 키 롤오버 1. 새 키 쌍을 생성합니다. 2. 새 공개키를 `ADD AUTH KEY`로 등록합니다. 3. 새 개인키로 CHALLENGE 연결을 검증합니다. 4. 이전 키를 비활성화하고 이전 키의 접속 실패를 검증합니다. 5. 관찰 기간이 끝나면 이전 키를 삭제합니다. 한 번에 기존 키를 덮어쓰지 않으면 서비스 중단 없이 교체 결과를 검증할 수 있습니다. ## AUTH KEY challenge 인증 클라이언트는 서버에 등록된 공개키와 짝을 이루는 개인키 파일을 읽을 수 있어야 합니다. 개인키 자체는 서버에 등록하지 않습니다. 키 파일이 없거나, 등록된 키와 일치하지 않거나, 비활성 또는 만료 상태이면 CHALLENGE 인증이 실패합니다. 자동으로 PASSWORD 인증으로 전환되지 않으므로 필요한 경우 별도의 PASSWORD 연결을 명시합니다. ## SYS AS USER 인증 제약 `SYS`도 CHALLENGE 인증을 사용하려면 `SYS` 사용자에 AUTH KEY가 등록되어 있어야 합니다. 그러나 애플리케이션 접속에는 `SYS`를 사용하지 말고, 전용 계정에 최소 권한과 키를 부여하십시오. `SYS` 키 변경은 다른 관리 경로가 검증된 유지보수 시간에 수행합니다. ## AUTH_MODE=CHALLENGE `machsql`에서는 연결 문자열과 개인키 옵션을 함께 지정합니다. ```bash machsql -s 127.0.0.1 -P 5656 -u app_user \ -c "AUTH_MODE=CHALLENGE" \ -K /secure/path/app_user.key ``` JDBC 연결 속성 예시는 다음과 같습니다. ```text jdbc:machbase://127.0.0.1:5656/machbasedb?AUTH_MODE=CHALLENGE&AUTH_KEY_FILE=/secure/path/app_user.key ``` 로그나 오류 보고서에 개인키 경로의 내용, 비밀번호, 연결 문자열의 비밀값을 남기지 마십시오. ## AUTH_KEY_FILE 개인키는 클라이언트 호스트에서 생성하고 공개키만 SQL 등록에 사용합니다. 예를 들어 ECDSA P-256 키 쌍은 다음처럼 만들 수 있습니다. ```bash openssl ecparam -name prime256v1 -genkey -noout -out app_user.key openssl ec -in app_user.key -pubout -out app_user.pub chmod 600 app_user.key ``` `chmod 600`은 개인키 노출을 줄이기 위한 운영 권장사항입니다. 실제 프로세스 계정이 파일과 상위 디렉터리를 읽을 수 있는지도 확인합니다. 공개키를 SQL 인라인 문자열로 만들 때는 다음과 같이 줄바꿈을 `\n`으로 표현합니다. ```bash awk '{printf "%s\\n", $0}' app_user.pub ``` PKCS#8을 포함한 실제 지원 개인키 형식은 사용 중인 클라이언트와 배포 버전에서 검증하십시오. ## AUTH_SIG_SCHEME | 공개키 | 사용 가능한 서명 스킴 | |---|---| | ECDSA P-256, P-384, P-521 | `ECDSA` | | RSA 2048, 3072, 4096 | `RSA_PKCS1_V15`, `RSA_PSS` | 키에 맞는 기본 스킴을 사용할 수 있습니다. RSA-PSS를 명시하려면 클라이언트 옵션을 함께 지정합니다. ```bash machsql -s 127.0.0.1 -P 5656 -u app_user \ -c "AUTH_MODE=CHALLENGE" \ -K /secure/path/app_user_rsa.key \ --auth-sig-scheme=RSA_PSS ``` 등록된 공개키 타입과 서명 스킴이 맞지 않으면 인증에 실패합니다. ## RSA / ECDSA / RSA_PSS 지원 범위 알고리즘은 보안 정책, 사용 중인 클라이언트 지원, 키 관리 시스템과의 호환성을 기준으로 선택합니다. 특정 알고리즘의 속도나 보안성을 모든 환경에 동일하게 단정하지 마십시오. 키를 등록하기 전에 실제 클라이언트로 생성·연결·롤오버·폐기 전 과정을 검증합니다. --- title: "14.5 접속 제어" url: https://docs.machbase.com/kr/dbms/security-access-control/access-control/ language: kr kind: page --- # 14.5 접속 제어 네트워크 접속 범위는 `GRANT_REMOTE_ACCESS`, `BIND_IP_ADDRESS`, 운영체제 또는 클라우드 방화벽을 함께 사용해 제한합니다. 현재 값은 다음처럼 확인합니다. ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME IN ('GRANT_REMOTE_ACCESS', 'BIND_IP_ADDRESS'); ``` 리스너 설정은 서버가 시작할 때 적용됩니다. 운영 중인 리스너가 자동으로 다시 바인드된다고 가정하지 말고 `machbase.conf`를 변경한 뒤 승인된 재시작 절차를 수행하십시오. ## 원격 접속 설정 `GRANT_REMOTE_ACCESS`는 원격 클라이언트 접속 허용 여부를 제어합니다. ```ini # 원격 접속 허용 GRANT_REMOTE_ACCESS = 1 # 원격 접속 차단 GRANT_REMOTE_ACCESS = 0 ``` 값을 바꾸기 전에 다음을 확인합니다. 1. 애플리케이션, 모니터링, 백업 클라이언트의 접속 위치 2. 로컬 관리 접속을 유지할 방법 3. 재시작 후 원격·비상 접속을 모두 시험할 점검 순서 `GRANT_REMOTE_ACCESS=1`만으로 모든 원격 주소를 허용하지 않도록 방화벽 허용 목록을 함께 구성하십시오. ## BIND_IP_ADDRESS와 네트워크 노출 제어 `BIND_IP_ADDRESS`는 IPv4 리스너가 바인드할 주소입니다. ```ini # 모든 IPv4 인터페이스 BIND_IP_ADDRESS = 0.0.0.0 # 로컬 IPv4 인터페이스 BIND_IP_ADDRESS = 127.0.0.1 # 지정한 내부 IPv4 인터페이스 BIND_IP_ADDRESS = 10.0.0.5 ``` 서버에 존재하지 않는 주소를 지정하면 시작에 실패할 수 있습니다. 설정 변경 전 현재 인터페이스 주소를 확인하고, 재시작 후 실제 수신 주소와 포트를 운영체제 도구로 검증하십시오. `0.0.0.0`이 필요하면 방화벽이나 보안 그룹에서 허용할 소스 주소와 포트를 제한합니다. 구체적인 방화벽 명령은 배포 운영체제와 네트워크 정책에 따라 다르므로 이 매뉴얼의 고정 명령을 그대로 적용하지 마십시오. --- title: "14.6 보안 설정 체크리스트" url: https://docs.machbase.com/kr/dbms/security-access-control/checklist-configuration/ language: kr kind: page --- # 14.6 보안 설정 체크리스트 운영 배포 전과 정기 감사 때 다음 항목을 점검합니다. ## 계정과 인증 - 설치 직후 `SYS`의 초기 비밀번호를 조직의 비밀 관리 절차에 따라 변경합니다. - 애플리케이션별 전용 계정을 만들고 `SYS`를 일반 접속에 사용하지 않습니다. - 비밀번호를 소스 코드, 문서, 명령 이력에 저장하지 않습니다. - 사용하지 않는 계정과 만료 예정 계정을 검토합니다. - AUTH KEY를 사용하면 개인키의 보관 위치, 교체, 폐기 담당자를 지정합니다. ```sql SELECT USER_ID, NAME, PWD_POLICY_LEVEL, VALID_BEFORE FROM M$SYS_USERS ORDER BY USER_ID; SELECT USER_NAME, KEY_ID, KEY_ALGO, KEY_PARAM, ACTIVATED, VALID_BEFORE FROM V$USER_AUTH_KEYS ORDER BY USER_NAME, KEY_ID; ``` ## 권한 - 각 논리 데이터베이스에 필요한 `CONNECT` 권한만 부여합니다. - 읽기 계정에 쓰기·DDL·백업 권한이 없는지 확인합니다. - 임시 권한의 만료와 회수 책임자를 기록합니다. - 사용자나 테이블을 다시 만든 뒤 권한을 재검증합니다. ```sql SELECT DB_NAME, USER_NAME, OWNER_NAME, TABLE_NAME, PRIV FROM M$SYS_USER_ACCESS ORDER BY USER_NAME, DB_NAME, OWNER_NAME, TABLE_NAME; ``` `PRIV`는 비트 마스크입니다. 숫자를 권한명처럼 표시하는 자체 도구는 사용 중인 배포 버전의 정의와 대조해 검증하십시오. 권한 부여·회수 절차는 [권한 관리](../privileges/)를 참고합니다. ## 네트워크 접속 - 원격 접속이 필요한지 먼저 결정합니다. - `BIND_IP_ADDRESS`를 필요한 IPv4 인터페이스로 제한합니다. - 방화벽 또는 보안 그룹의 소스 주소 허용 목록을 검토합니다. - 설정 변경은 재시작과 접속 검증을 포함한 유지보수 절차로 수행합니다. ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME IN ('GRANT_REMOTE_ACCESS', 'BIND_IP_ADDRESS'); ``` ## 변경 후 증거 보안 변경 후에는 다음 결과를 변경 기록에 남깁니다. 1. 사용자와 만료일 조회 결과 2. 대상 사용자와 데이터베이스·테이블 권한 조회 결과 3. 허용한 주소에서의 정상 접속과 허용하지 않은 주소에서의 차단 결과 4. AUTH KEY를 변경했다면 새 키 접속 성공과 이전 키 차단 결과 운영 중인 공유 서버에서 `SYS` 비밀번호, 리스너, 방화벽을 시험 목적으로 변경하지 마십시오. 별도 검증 환경에서 복구 경로까지 확인한 뒤 운영 변경을 승인합니다. --- title: "15. 문제 해결" url: https://docs.machbase.com/kr/dbms/troubleshooting/ language: kr kind: section --- # 15. 문제 해결 Machbase 운영 중 발생하는 문제를 증상 확인, 원인 진단, 해결, 재발 방지 순서로 다룹니다. {{< callout type="info" >}} 문제를 분류하기 전에 `machadmin -e`로 서버 상태를 확인하고, `$MACHBASE_HOME/trc/machbase.trc`의 최근 오류와 클라이언트의 `ERR-XXXXX` 코드를 기록합니다. {{< /callout >}} ## 이 장의 구성 | 순서 | 섹션 | 내용 | |-----:|------|------| | 15.1 | [문제 해결 접근법](./troubleshooting/) | 증상 수집, 진단 명령, 로그와 오류 코드 분석 | | 15.2 | [서버와 연결 문제](./server-connection/) | 서버 시작, 원격 접속, 인증 오류 | | 15.3 | [입력과 적재 문제](./item/) | Append와 CSV 가져오기 오류 | | 15.4 | [쿼리와 성능 문제](./performance/) | 느린 쿼리, 빈 결과, 메모리, 트랜잭션 충돌 | | 15.5 | [백업과 복구 문제](./recovery-backup/) | BACKUP, RESTORE, MOUNT, UMOUNT 오류 | | 15.6 | [Cluster 문제](./cluster/) | 노드 상태와 Cluster Edition 오류 | | 15.7 | [ROLLUP 문제](./rollup/) | 집계 지연, 결과 불일치와 재구성 판단 | TAG와 LOOKUP의 UPDATE·DELETE 조건 오류는 각 테이블 장의 제약·문제 해결 페이지에서 확인하십시오. 문제를 해결한 뒤에는 원인, 조치, 확인 쿼리와 재발 방지 항목을 운영 기록에 남깁니다. --- title: "15.1 문제 해결 접근법" url: https://docs.machbase.com/kr/dbms/troubleshooting/troubleshooting/ language: kr kind: page --- # 15.1 문제 해결 접근법 문제를 재현하기 전에 상태와 증거를 보존하고, 가장 작은 범위부터 원인을 좁힙니다. ## 5단계 문제 해결 절차 1. 실패 시각, 실행한 명령, 전체 오류 메시지와 `ERR-` 코드를 기록합니다. 2. `machadmin -e`와 연결 시험으로 서버·네트워크·인증 중 실패 단계를 구분합니다. 3. 같은 시각의 서버 로그와 세션·문장 상태를 확인합니다. 4. 한 번에 한 원인만 수정하고 같은 입력으로 재검증합니다. 5. 원인, 조치, 검증 결과와 재발 방지 항목을 남깁니다. ## 증상 확인 | 증상 | 첫 확인 | |---|---| | 서버 응답 없음 | `machadmin -e`, 프로세스와 포트, 서버 로그 | | 연결 거부 | 서버 상태, 수신 주소, 방화벽, 포트 | | 인증 실패 | 사용자, 인증 방식, 만료 상태, AUTH KEY 상태 | | SQL 실패 | 전체 SQL, 대상 데이터베이스와 객체, 정확한 오류 코드 | | 느린 쿼리 | 실행 계획, 시간 범위, 스캔 행 수, 동시 부하 | | 적재 중단 | 성공·실패 행 수, bad/log 파일, 마지막 성공 위치 | 문제를 해결하기 전에 서버 재시작이나 설정 변경부터 수행하면 최초 원인의 증거를 잃을 수 있습니다. ## 진단 명령어 모음 ```bash machadmin -e tail -100 "$MACHBASE_HOME/trc/machbase.trc" ``` ```sql SELECT * FROM V$VERSION; SELECT ID, USER_NAME, CLOSED FROM V$SESSION ORDER BY ID; SELECT ID, SESS_ID, STATE, QUERY FROM V$STMT ORDER BY ID; SELECT * FROM V$STORAGE_USAGE; SELECT NAME, VALUE FROM V$PROPERTY ORDER BY NAME; ``` 운영 환경에서는 결과에 SQL 본문, 사용자명, 경로 등 민감한 정보가 포함될 수 있으므로 공유 전에 검토하십시오. ## 로그 확인 기본 서버 로그 위치는 `$MACHBASE_HOME/trc/machbase.trc`입니다. 실제 경로와 순환 설정은 `V$PROPERTY`와 설치 설정을 기준으로 확인합니다. ```bash tail -100 "$MACHBASE_HOME/trc/machbase.trc" rg -n 'ERR-|ERROR|WARN' "$MACHBASE_HOME/trc/machbase.trc" ``` 로그 레벨이나 파일 수를 바꾸기 전에 현재 `TRACE_LOG_LEVEL`, `TRACE_LOGFILE_SIZE`, `TRACE_LOGFILE_COUNT`, `TRACE_LOGFILE_PATH`를 조회하십시오. 장애 중 과도한 상세 로그는 디스크와 성능에 영향을 줄 수 있습니다. ## 오류 코드로 원인 찾기 정확한 오류 코드를 기록한 뒤 [오류 코드 사전](/dbms/reference/error-codes/)에서 현재 정의를 확인하십시오. 코드가 사전에 없으면 전체 메시지, 서버 빌드, 재현 SQL과 로그 시각을 함께 수집합니다. --- title: "15.2 서버와 연결 문제" url: https://docs.machbase.com/kr/dbms/troubleshooting/server-connection/ language: kr kind: page --- # 15.2 서버와 연결 문제 연결 문제는 서버 시작, TCP 연결, 사용자 인증 순서로 분리해 확인합니다. ## 서버가 시작되지 않을 때 ```bash machadmin -e tail -100 "$MACHBASE_HOME/trc/machbase.trc" ``` 다음을 차례로 확인합니다. 1. 설정한 포트를 다른 프로세스가 사용 중인지 2. Machbase 서버 OS 계정이 설치·데이터·로그 경로를 읽고 쓸 수 있는지 3. 파일 시스템의 공간과 inode가 충분한지 4. 라이선스가 설치되어 있고 유효한지 (`machadmin -f`) 5. 이전 프로세스와 lock 파일이 남은 원인이 무엇인지 원인을 확인하지 않은 강제 종료나 lock 파일 삭제는 피하십시오. 정상 종료가 불가능하면 로그와 프로세스 상태를 보존한 뒤 승인된 복구 절차를 사용합니다. ## 연결할 수 없을 때 서버 호스트에서는 먼저 로컬 연결을 시험합니다. ```bash machadmin -e machsql -s 127.0.0.1 -P 5656 -u app_user ``` 로컬은 성공하고 원격만 실패하면 다음을 확인합니다. - 클라이언트가 사용하는 주소와 포트 - `GRANT_REMOTE_ACCESS`, `BIND_IP_ADDRESS`의 현재 값 - 운영체제·클라우드 방화벽과 중간 네트워크 경로 - `MAX_SESSION_COUNT` 도달 여부와 닫히지 않은 세션 ```sql SELECT NAME, VALUE FROM V$PROPERTY WHERE NAME IN ('GRANT_REMOTE_ACCESS', 'BIND_IP_ADDRESS', 'MAX_SESSION_COUNT'); SELECT ID, USER_NAME, CLOSED FROM V$SESSION ORDER BY ID; ``` 리스너 관련 설정은 서버 시작 시 적용되므로 설정 파일을 바꾼 뒤 유지보수 재시작과 원격·로컬 접속 검증을 함께 수행합니다. 방화벽 명령은 배포 운영체제의 공식 문서을 따릅니다. ## 인증이 실패할 때 먼저 연결에서 선택한 인증 방식을 확인합니다. `AUTH_MODE`는 서버 `V$PROPERTY`가 아니라 클라이언트 연결 옵션입니다. 비밀번호 인증은 사용자명, 비밀번호 정책과 만료일을 확인합니다. ```sql SELECT USER_ID, NAME, PWD_POLICY_LEVEL, VALID_BEFORE FROM M$SYS_USERS WHERE NAME = 'APP_USER'; ``` AUTH KEY 인증은 등록 키와 클라이언트 옵션을 확인합니다. ```sql SELECT KEY_ID, USER_NAME, KEY_ALGO, KEY_PARAM, ACTIVATED, VALID_BEFORE FROM V$USER_AUTH_KEYS WHERE USER_NAME = 'APP_USER' ORDER BY KEY_ID; ``` ```bash machsql -s 127.0.0.1 -P 5656 -u app_user \ -c "AUTH_MODE=CHALLENGE" \ -K /secure/path/app_user.key ``` 개인키 파일이 존재하고 클라이언트 프로세스가 읽을 수 있는지, 서버의 공개키와 짝이 맞는지, 키가 활성·유효 상태인지 확인합니다. 계정 잠금 해제나 `CREATE AUTH KEY` 같은 현행 문법에 없는 구문을 사용하지 마십시오. 등록과 교체는 [AUTH KEY 인증](/dbms/security-access-control/authentication-auth-key/)을 따릅니다. --- title: "15.3 입력과 적재 문제" url: https://docs.machbase.com/kr/dbms/troubleshooting/item/ language: kr kind: page --- # 15.3 입력과 적재 문제 ## 입력이 실패할 때 클라이언트가 보고한 전체 오류, 대상 데이터베이스·테이블, 입력 방식, 마지막 성공 행을 먼저 기록합니다. 오류 코드 의미는 [오류 코드 사전](/dbms/reference/error-codes/)에서 확인하고 이 페이지의 고정 코드 표에 의존하지 않습니다. ```sql DESC target_table; SELECT NAME, TYPE, COLCOUNT FROM M$SYS_TABLES WHERE NAME = 'TARGET_TABLE'; ``` 다음을 확인합니다. - 입력 열 수·순서·타입과 NULL 허용 여부 - 현재 데이터베이스와 테이블 소유자 - 사용자 `CONNECT`와 테이블 `INSERT` 권한 - 시간 문자열 형식과 연결 시간대 - 파일 시스템 공간과 `V$STORAGE_USAGE` - Append API의 반환값, 실패 행과 flush 결과 시간 역순 입력 가능 여부나 UPDATE/DELETE 조건은 테이블 타입에 따라 다르므로 해당 활용 장의 제약 문서를 확인합니다. ## CSV import가 실패할 때 현재 배포본의 옵션은 `machloader -h`로 확인합니다. ```bash machloader -h machloader -s 127.0.0.1 -P 5656 -u app_user \ -t target_table -i /data/input.csv \ -b /data/input.bad -l /data/input.log ``` 실패 시 다음 순서로 작은 파일부터 재현합니다. 1. 절대 경로와 서버가 아닌 loader 실행 호스트의 파일 권한을 확인합니다. 2. CSV 한 행의 열 수와 `DESC target_table` 결과를 비교합니다. 3. 인코딩 이름을 현재 도움말과 대조합니다. 배포본에서 확인되는 이름에는 `UTF8`, `MS949`, `KSC5601`, `EUCJP` 등이 있습니다. 4. 구분자와 인용 문자 옵션을 실제 파일과 맞춥니다. 5. 날짜 형식 옵션에는 대상 열 이름과 형식을 함께 지정합니다. 6. bad 파일의 첫 실패 행을 고친 뒤 별도 검증 테이블에 다시 적재합니다. `-F` 구문의 정확한 형식과 loader 옵션은 [machloader 명령/옵션 사전](/dbms/reference/command-line-tools/machloader/)을 사용하십시오. 일부 성공 뒤 재시도할 때는 이미 입력된 범위를 확인해 중복을 방지합니다. --- title: "15.4 쿼리와 성능 문제" url: https://docs.machbase.com/kr/dbms/troubleshooting/performance/ language: kr kind: page --- # 15.4 쿼리와 성능 문제 ## 쿼리가 느릴 때 문제가 발생한 SQL, 바인드 값의 범위, 시작·종료 시각, 기대 행 수와 실제 행 수를 기록합니다. ```sql SELECT ID, SESS_ID, STATE, QUERY FROM V$STMT ORDER BY ID; EXPLAIN SELECT ...; ``` 실행 계획에서 다음을 확인합니다. - 시간·태그 조건이 충분히 이른 단계에서 적용되는가 - 큰 테이블이 불필요하게 반복 스캔되는가 - 조인 키의 타입과 값 형태가 일치하는가 - 필요한 인덱스 또는 ROLLUP이 실제로 사용되는가 - 반환할 필요가 없는 열과 행을 읽고 있지 않은가 MINMAX 캐시를 검토할 때의 현재 속성명은 `DISK_COLUMNAR_TABLE_COLUMN_MINMAX_CACHE_SIZE`입니다. 값을 바꾸기 전에 현재 값과 실행 계획을 기록하고 격리 환경에서 비교하십시오. 상세 절차는 [성능 튜닝](/dbms/performance-tuning/)을 따릅니다. ## 검색 결과가 예상과 다를 때 ```sql SELECT COUNT(*), MIN(_ARRIVAL_TIME), MAX(_ARRIVAL_TIME) FROM target_log; ``` 1. 대상 데이터베이스, 소유자와 테이블 이름을 확인합니다. 2. 필터 없이 소량 조회해 데이터 존재 여부와 실제 시간 값을 봅니다. 3. 연결 시간대와 입력 문자열의 시간대 해석을 확인합니다. 4. 태그 이름, 대소문자, 경계 연산자(`>`, `>=`, `<`, `<=`)를 확인합니다. 5. ROLLUP 결과라면 gap과 갱신 상태를 확인합니다. 서버 속성 `DEFAULT_TIMEZONE`을 찾지 마십시오. 시간대는 클라이언트 연결과 세션에서 명시하고, 원본 데이터의 기준 시간대를 함께 기록합니다. ```sql SHOW ROLLUPGAP; SELECT * FROM V$ROLLUP; ``` ## 메모리 부족 ```sql SELECT * FROM V$SYSMEM; SELECT ID, SESS_ID, STATE, QUERY FROM V$STMT ORDER BY ID; ``` 운영체제 메모리, swap, OOM 기록과 같은 시각의 Machbase 로그를 함께 확인합니다. 대량 결과를 한 번에 가져오는 쿼리, 넓은 조인·정렬, 과도한 동시 실행, 클라이언트의 큰 fetch·Append 버퍼를 각각 분리해 재현합니다. 현재 설정값은 `V$PROPERTY`에서 조회합니다. 허용 범위와 변경 방법은 [설정 사전](/dbms/reference/configuration/configuration/)에서 확인하고, 변경은 하나씩 부하 시험한 뒤 적용합니다. --- title: "15.5 백업과 복구 문제" url: https://docs.machbase.com/kr/dbms/troubleshooting/recovery-backup/ language: kr kind: page --- # 15.5 백업과 복구 문제 ## 백업과 복원이 실패할 때 백업 실패 시 서버 프로세스 OS 계정의 경로 권한, 사용 가능 공간, 동일 경로의 기존 백업, 서버 로그를 확인합니다. ```sql SELECT * FROM V$STORAGE_USAGE; ``` ```bash machadmin -e tail -100 "$MACHBASE_HOME/trc/machbase.trc" ``` 복원은 기존 물리 데이터베이스를 교체하는 파괴적 작업입니다. 실행 중인 서버를 정지하는 것만으로 충분하지 않으며, 현재 데이터베이스가 존재하면 복원이 거부됩니다. 다음 순서는 개념적 점검표이며 운영 명령으로 그대로 복사하지 마십시오. ```text 1. 복구 대상·백업 경로·버전·체크섬을 확인한다. 2. 현재 database의 보존 방법과 되돌림 조건을 승인받는다. 3. 서버를 정상 종료한다. 4. 현재 물리 database를 제거하는 승인된 절차를 수행한다. 5. machadmin restore를 실행한다. 6. 서버를 시작하고 업무 검증 쿼리를 수행한다. ``` `machadmin -d`는 현재 데이터베이스를 파기하므로 백업과 명시적 승인 없이 실행해서는 안 됩니다. 정확한 복원 구문과 제약은 [BACKUP/RESTORE/MOUNT 문법](/dbms/reference/sql/syntax/backup-restore-mount-syntax/)을 참고하십시오. 백업 이미지 확인이나 MOUNT 성공은 1차 검증일 뿐 완전한 복구 가능성을 보장하지 않습니다. 별도 환경에서 복원과 애플리케이션 검증까지 정기적으로 수행합니다. ## 마운트가 실패할 때 현재 MOUNT 목록, 고유한 별칭, 백업 경로와 서버 프로세스의 읽기 권한을 확인합니다. ```sql SELECT * FROM V$STORAGE_MOUNT_DATABASES; ``` 현행 구문은 백업 경로 뒤에 별칭을 지정합니다. ```sql MOUNT DATABASE '/backup/sc15_snapshot' TO backup_check; SELECT COUNT(*) FROM backup_check.sys.target_table; UMOUNT DATABASE backup_check; ``` 동일 별칭 충돌, 지원하지 않는 Edition, 호환되지 않는 백업, 사용 중인 마운트된 데이터베이스를 구분해 처리합니다. 강제로 파일을 삭제하거나 서버 메타데이터를 수정하지 마십시오. --- title: "15.6 Cluster 문제" url: https://docs.machbase.com/kr/dbms/troubleshooting/cluster/ language: kr kind: page --- # 15.6 Cluster 문제 ## Cluster 노드 상태가 비정상일 때 토폴로지 변경이나 재시작 전에 전체 상태와 최초 오류를 수집합니다. ```bash machcoordinatoradmin --cluster-status machclusterctl status ``` 1. Coordinator, Broker, Warehouse 중 어느 역할이 처음 비정상이 되었는지 확인합니다. 2. 해당 노드와 선행 역할의 로그 시각을 맞춰 비교합니다. 3. 호스트, 프로세스, 디스크, 네트워크와 설정 파일 변경 이력을 확인합니다. 4. 복제·재배치 상태와 클라이언트 영향 범위를 기록합니다. 5. 13장의 승인된 복구 절차로 한 노드씩 조치하고 전체 상태를 다시 검증합니다. 노드 이름, 서비스 포트와 노드 간 통신 포트를 추측해 start/add/remove 명령을 실행하지 마십시오. 실제 `cluster.yaml`과 배포 도구 도움말을 기준으로 합니다. 자세한 안전 제한은 [Cluster 운영](/dbms/operations-configuration-recovery/cluster/)을 참고하십시오. ## Cluster Edition 제한 오류 ```sql SELECT * FROM V$VERSION; ``` 오류가 Edition 제한인지 판단할 때는 현재 Edition과 [Edition별 지원 범위](/dbms/reference/support-scope-constraints/)를 대조합니다. Standard 전용 기능을 우회하는 비공식 절차를 사용하지 말고, 같은 요구를 충족하는 Cluster 지원 기능 또는 별도 Standard 환경을 검토합니다. --- title: "15.7 ROLLUP 문제" url: https://docs.machbase.com/kr/dbms/troubleshooting/rollup/ language: kr kind: page --- # 15.7 ROLLUP 문제 ROLLUP 결과가 늦거나 원본과 다르면 처리 지연과 집계 의미의 차이를 먼저 구분합니다. 예제의 이름은 실제 진단할 테이블·작업으로 바꾸고 삭제·재생성을 첫 조치로 실행하지 않습니다. ## 1. 상태와 범위 확인 ```sql SELECT ROLLUP_NAME, ROLLUP_TABLE, ROOT_TABLE, EXT_TYPE, INTERVAL_TIME, WAKEUP_INTERVAL, ENABLED, RUN_STATE, LAST_ELAPSED_MSEC FROM V$ROLLUP ORDER BY ROLLUP_NAME; SHOW ROLLUPGAP; ``` SHOW ROLLUPGAP은 machsql 명령이며 SDK SQL API에는 보내지 않습니다. gap은 RID 처리 차이이며 시간 지연이나 원본 보정 완료를 직접 나타내지 않습니다. 여러 계층·Cluster 노드의 상태, 서버 빌드와 데이터베이스·소유자를 함께 기록합니다. ## 2. 같은 데이터 집합인지 비교 | 증상 | 확인 | |---|---| | 일부 표본이 없음 | 조건 ROLLUP 후보가 선택됐는지, 원본 필터와 같은지 | | FIRST/LAST 오류 | 실제 선택 후보가 EXTENSION인지 | | 월·일 조회에 후보 없음 | 저장 간격 선택 규칙과 조회 버킷을 혼동했는지 | | 평균 불일치 | NULL·유효 건수·부분 평균 재집계·태그 단위가 같은지 | | 원본 정정 후에도 값이 같음 | FORCE로 과거를 되감으려 하지 않았는지, REBUILD 대상인지 | | JSON 건수 불일치 | 원본 문서·SQL NULL·경로별 건수·문서 집계 건수를 구분했는지 | 원본은 DATE_TRUNC/DATE_BIN과 GROUP BY로, 저장 집계는 rollup()으로 조회해 비교합니다. 태그·시각·origin·종료 경계·집계 함수를 고정합니다. 적용 가능한 ROLLUP이 없으면 rollup()이 원본 스캔으로 자동 전환된다고 가정하지 않습니다. ## 3. 새 입력을 따라잡기 대상 작업이 활성화된 상태인지 확인하고 필요한 작업을 이름으로 지정합니다. ```sql ALTER ROLLUP rollup_name FORCE; SHOW ROLLUPGAP; ``` WAKEUP은 깨우기만 하고 FORCE는 처리 범위를 따라잡도록 기다립니다. 중지된 작업은 상태를 확인한 뒤 START하고, 여러 계층은 하위부터 처리합니다. `ALTER SYSTEM FLUSH ROLLUP`은 지원되는 명령이 아니므로 진단 예제로 사용하지 않습니다. ## 4. 과거 보정과 재구성 Standard Edition에서도 모든 생성 구성이 REBUILD 대상인 것은 아닙니다. 완전한 자동 계층인지, Custom 간격·버킷이 지원되는지, 원본이 남아 있는지 먼저 확인합니다. 시간 인수는 지원되는 상수 문자열/TO_DATE를 쓰며 해당 시각의 버킷 전체가 재계산됩니다. 관련 작업의 중지·재시작과 부분 실패 가능성을 고려합니다. 성공·실패 뒤에도 결과와 실제 활성 상태를 확인합니다. [REBUILD 실습](../../tag-rollup-usage/rollup-rebuild/)과 [인수 계약](../../reference/sql/syntax/rollup-rebuild-syntax/)을 따릅니다. ## 5. 지원 요청 자료 - 서버 빌드·Edition·클라이언트와 접속 대상 - TAG 스키마, ROLLUP 정의, 조건과 의존 관계 - 상태·gap과 관측 시각 - 비교한 원본/ROLLUP SQL, 시간대·origin과 예상/실제 결과 - 최초 오류와 최근 원본 보정·삭제·대량 입력·설정 변경 이력 --- title: "16. 레퍼런스" url: https://docs.machbase.com/kr/dbms/reference/ language: kr kind: section --- # 16. 레퍼런스 문법, 함수, 설정, 시스템 카탈로그의 정확한 정의를 빠르게 찾아보는 종합 레퍼런스입니다. SDK/API 문서는 11장 개발 및 애플리케이션 연동에서, 개념이나 사용 예시는 각 기능 장에서 확인하십시오. ## 구성 | 섹션 | 설명 | |------|------| | [SQL 레퍼런스](./sql/) | SQL 문법 사전, 함수 사전, 데이터 타입, 힌트, 상대 시간 표현 | | [설정 레퍼런스](./configuration/) | machbase.conf 속성, 동적 변경 가능 속성 목록 | | [명령줄 도구](./command-line-tools/) | machsql, machadmin, machloader 등 CLI 도구 옵션 | | [개발 도구 연동](../development-tools-integration/) | Go, Python, Java, C 클라이언트 SDK/API (11장) | | [시스템 카탈로그](./system-catalog/) | V$, M$SYS 뷰 목록 및 컬럼 설명 | | [에러 코드](./error-codes/) | 에러 코드 번호, 메시지, 원인 및 조치 방법 | | [지원 범위 및 제약](./support-scope-constraints/) | 테이블 유형별 기능 지원 여부, 알려진 제약 사항 | | [AI Agent Reference](./ai-agent-reference/) | AI·RAG용 탐색 가이드, 정본 맵과 LLM 출력 | ## 활용 방법 - **문법 확인** → [SQL 문법 사전](./sql/syntax/) - **함수 인자와 반환값** → [SQL 함수 사전](./sql/functions/) - **데이터 타입 범위와 기본값** → [데이터 타입 사전](./sql/types/) - **설정값 의미와 허용 범위** → [설정 레퍼런스](./configuration/) - **에러 원인 파악** → [에러 코드](./error-codes/) > 동작 원리, 선택 기준, 운영 가이드는 해당 기능을 다루는 장을 참고하십시오. --- title: "16.1 SQL 레퍼런스" url: https://docs.machbase.com/kr/dbms/reference/sql/ language: kr kind: section --- # 16.1 SQL 레퍼런스 SQL 문법, 함수, 데이터 타입, 쿼리 힌트, 상대 시간 표현의 정확한 정의를 제공합니다. ## 하위 섹션 | 섹션 | 설명 | |------|------| | [SQL 문법 사전](./syntax/) | CREATE, DROP, ALTER, SELECT, WITH/CTE, INSERT, DELETE, UPDATE, BACKUP, MOUNT 등 모든 SQL 구문의 BNF 문법과 예시 | | [함수 사전](./functions/) | 집계 함수, 수학 함수, 문자열 함수, 날짜/시간 함수, 타입 변환 함수, TAG 전용 함수 목록 및 설명 | | [데이터 타입 사전](./types/) | 지원 데이터 타입의 크기, 범위, 기본값, 테이블 유형별 사용 가능 여부 | | [SELECT hint syntax](./syntax/select-hint-syntax/) | SELECT 힌트 문법, 사용법, 적용 대상 | | [상대 시간 표현 사전](./relative-time/) | `now - 1h` 형태의 상대 시간 literal과 접미사 | | [ROWID](./rowid/) | 테이블별 ROWID 의미, 조회 조건과 INSERT 결과 | ## SQL 특징 표준 ANSI SQL을 기반으로 시계열 데이터 처리에 최적화된 확장 문법을 제공합니다. - **TAG 시계열 기능**: BASETIME, METADATA, `FIRST`/`LAST`, `SERIES BY`, ROLLUP - **시간 범위 조회**: `DURATION`, `BEFORE`, `AFTER`, `RANGE` 절 - **공통 테이블 표현식**: Standard Edition의 비재귀 `WITH`/CTE - **대량 입력 연동**: 클라이언트 SDK의 Append API (SQL 문장이 아닌 별도 입력 API) - **텍스트 검색**: `SEARCH`, `ESEARCH`, `REGEXP` 연산자 - **집합 연산**: `UNION ALL` (UNION, INTERSECT, EXCEPT 미지원) --- title: "16.1.1 SQL 문법 사전" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/ language: kr kind: section --- # 16.1.1 SQL 문법 사전 SQL 문법 사전은 Machbase에서 지원하는 모든 SQL 구문의 BNF 표기와 최소 예시를 제공합니다. ## 지원 SQL 구문 목록 | 구문 | 분류 | 설명 | |------|------|------| | [CREATE TABLE](./ddl-syntax/#create-table) | DDL | 로그/TAG/LOOKUP/VOLATILE/TRANSACTION 테이블 생성 | | [DROP TABLE](./ddl-syntax/#drop-table) | DDL | 테이블 삭제 | | [ALTER TABLE](./ddl-syntax/#alter-table) | DDL | 테이블 스키마 변경 (컬럼 추가/삭제/수정/이름 변경) | | [TRUNCATE TABLE](./ddl-syntax/#truncate-table) | DDL | 테이블 데이터 전체 삭제 | | [CREATE INDEX](./index-syntax/#create-index) | DDL | 조건부 생성과 테이블 타입별 인덱스 지원 | | [DROP INDEX](./index-syntax/#drop-index) | DDL | 인덱스 삭제 | | [CREATE ROLLUP](./rollup-syntax/#create-rollup) | DDL | TAG 테이블 롤업 정의 생성 | | [DROP ROLLUP / ALTER ROLLUP](./rollup-syntax/#drop-rollup) | DDL | 롤업 삭제 및 제어 | | [CREATE RETENTION](./retention-syntax/#create-retention) | DDL | 데이터 보존 정책 생성 | | [CREATE VIEW / DROP VIEW](./view-syntax/) | DDL | 저장 뷰 생성 및 삭제 | | [CREATE TABLESPACE](./ddl-syntax/#create-tablespace) | DDL | 테이블스페이스 생성 | | [INSERT INTO](./dml-syntax/#insert-into) | DML | 단건 및 다건 데이터 삽입 | | [INSERT SELECT](./dml-syntax/#insert-select) | DML | 조회 결과를 다른 테이블에 삽입 | | [UPDATE](./dml-syntax/#update) | DML | TRANSACTION/LOOKUP/VOLATILE 행 수정과 조건이 제한된 TAG 데이터 보정 | | [DELETE](./dml-syntax/#delete) | DML | 테이블 데이터 삭제 | | [LOAD DATA INFILE](./load-data-infile-syntax/) | DML | CSV 파일에서 직접 데이터 입력 | | [SELECT](./select-syntax/) | SELECT | 데이터 조회 (JOIN, GROUP BY, ORDER BY, LIMIT 포함) | | [WITH / CTE](./cte-syntax/) | SELECT | Standard Edition의 비재귀 공통 테이블 표현식 | | [Named Bind Parameter](./named-bind-parameter-syntax/) | SQL 공통 | `:name` 형식의 값 파라미터 | | [CAST](../functions/functions-full/#cast) | SQL 표현식 | 값을 지정한 데이터 타입으로 명시적으로 변환 | | [SAVE DATA INTO](./save-data-into-syntax/) | SELECT | 조회 결과를 CSV 파일로 저장 | | [BACKUP](./backup-restore-mount-syntax/#backup) | 운영 | 데이터베이스 또는 테이블 백업 | | [RESTORE](./backup-restore-mount-syntax/#restore) | 운영 | 논리 데이터베이스 복원과 `machadmin -r`을 사용하는 오프라인 복원 | | [MOUNT / UMOUNT DATABASE](./backup-restore-mount-syntax/#mount-database) | 운영 | 백업 데이터베이스 마운트/언마운트 | | [CREATE USER / DROP USER / ALTER USER](./user-auth-syntax/#create-drop-alter-user) | 사용자 | 사용자 생성, 삭제, 비밀번호 변경 | | [GRANT / REVOKE](./user-auth-syntax/#grant-revoke) | 사용자 | 권한 부여 및 회수 | | [AUTH KEY 관리](./user-auth-syntax/#auth-key) | 사용자 | 공개키 기반 인증 키 등록/관리 | | [ALTER SYSTEM](./system-session-alter-syntax/#alter-system) | 시스템 | 세션 제어, PVO Cache flush, 라이선스 설치 등 | | [ALTER SESSION](./system-session-alter-syntax/#alter-session) | 세션 | 세션별 파라미터 설정 | | [PIVOT](./pivot-syntax/) | 분석 | 행을 열로 변환하는 피벗 쿼리 | | [WINDOW FUNCTION (OVER)](./window-function-over-syntax/) | 분석 | 윈도우 함수와 OVER 절 | | [SERIES BY](./series-syntax/) | 분석 | 연속 조건 만족 레코드 그룹화 | | [SEARCH / ESEARCH / REGEXP](./search-esearch-regexp-syntax/) | 검색 | 키워드 인덱스 기반 텍스트 검색 | | [ROLLUP REBUILD](./rollup-rebuild-syntax/) | 운영 | 롤업 결과 재계산 | | [DATABASE](./database-syntax/) | DDL/세션 | 논리 데이터베이스 생성·선택·삭제와 상태 확인 | | [AUTO_INCREMENT](./auto-increment-syntax/) | DDL | 64비트 PRIMARY KEY 자동값 생성 | | [EXEC procedure / SHOW ROLLUPGAP](./execute-procedure-syntax/) | 제어 | table flush·refresh와 ROLLUP 제어·상태 확인 | ## BNF 표기 규칙 이 사전에서 사용하는 BNF(Backus-Naur Form) 표기는 다음 규칙을 따릅니다. | 표기 | 의미 | |------|------| | `'keyword'` | SQL 예약어 (대소문자 무관) | | `name` | 사용자 정의 이름 | | `( A \| B )` | A 또는 B 중 하나 | | `[ ... ]` | 선택적 요소 (생략 가능) | | `( ... )*` | 0회 이상 반복 | | `( ... )+` | 1회 이상 반복 | | `( ... )?` | 0회 또는 1회 | --- title: "SELECT" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/select-syntax/ language: kr kind: page --- # SELECT `SELECT`는 Machbase의 다양한 테이블에서 데이터를 조회·필터링·집계하는 구문입니다. ## SELECT 전체 문법 ```sql query_stmt ::= [ with_clause ] select_stmt select_stmt ::= 'SELECT' [ hint_clause ] target_list [ 'FROM' table_reference_list ] [ 'WHERE' condition_expr ] [ 'DURATION' duration_expr ] [ 'GROUP BY' expr_list [ 'HAVING' condition_expr ] ] [ 'ORDER BY' expr_list [ 'ASC' | 'DESC' ] ] [ 'SERIES BY' condition_expr ] [ 'LIMIT' [ offset ',' ] row_count ] -- 집합 연산자 select_stmt 'UNION ALL' select_stmt ``` `DURATION`은 `WHERE` 뒤, `GROUP BY`·`HAVING`·`ORDER BY`·`SERIES BY`·`LIMIT`보다 앞에 작성합니다. 위 문법에 나열된 순서는 SQL 절의 작성 순서이며, 내부 실행 순서를 의미하지 않습니다. `with_clause`는 Standard Edition에서 비재귀 CTE를 선언합니다. 전체 문법과 제한은 [WITH / CTE syntax](../cte-syntax/)를 참고하십시오. ### 대상 목록 (target_list) ```sql target_list ::= '*' | target_expr ( ',' target_expr )* target_expr ::= column_name [ 'AS' alias ] | expr [ 'AS' alias ] | '(' subquery ')' [ 'AS' alias ] ``` ### FROM 절 ```sql table_reference_list ::= table_reference ( ',' table_reference )* table_reference ::= table_name [ alias ] | '(' subquery ')' [ alias ] | table_name [ alias ] join_clause | view_name [ alias ] join_clause ::= [ 'INNER' | 'LEFT OUTER' | 'RIGHT OUTER' ] 'JOIN' table_reference 'ON' condition_expr | 'CROSS JOIN' table_reference ``` --- ## FROM 절 없는 SELECT 테이블 조회 없이 상수, 산술식, 단순 함수 결과를 1행으로 반환합니다. ```sql SELECT 1; SELECT 'alive'; SELECT 1 + 2; SELECT ABS(-7); SELECT SYSDATE; ``` --- ## WHERE 절 ```sql condition_expr ::= expr comparison_op expr | expr [ 'NOT' ] 'BETWEEN' expr 'AND' expr | column_name [ 'NOT' ] 'IN' '(' value_list | subquery ')' | column_name 'RANGE' duration_spec | column_name [ 'NOT' ] 'SEARCH' string_literal | column_name 'ESEARCH' pattern_literal | column_name [ 'NOT' ] 'REGEXP' pattern_literal | expr 'IS' [ 'NOT' ] 'NULL' | condition_expr ( 'AND' | 'OR' ) condition_expr | 'NOT' condition_expr | '(' condition_expr ')' | '(' subquery ')' ``` ### 주요 WHERE 연산자 | 연산자 | 설명 | |--------|------| | `=`, `<>`, `<`, `<=`, `>`, `>=` | 비교 연산자 | | `BETWEEN value1 AND value2` | 범위 조건 | | `IN (value_list)` | 값 목록 조건 | | `IN (subquery)` | 서브쿼리 IN | | `RANGE n unit` | 현재 시각 기준 시간 범위 조건 | | `SEARCH 'keyword'` | 키워드 인덱스 기반 텍스트 검색 | | `ESEARCH 'pattern%'` | 확장 텍스트 검색 (% 와일드카드) | | `REGEXP 'pattern'` | 정규 표현식 검색 (인덱스 미사용) | | `IS NULL` / `IS NOT NULL` | NULL 조건 | ```sql -- BETWEEN SELECT * FROM sensor_log WHERE value BETWEEN 10.0 AND 20.0; -- IN SELECT * FROM sensor_log WHERE status IN ('OK', 'WARN'); -- RANGE (현재 시각 기준 최근 1시간) SELECT * FROM sensor_log WHERE _arrival_time RANGE 1 HOUR; -- SEARCH (키워드 인덱스 사용) SELECT * FROM log_table WHERE message SEARCH 'error'; -- ESEARCH (와일드카드 패턴) SELECT * FROM log_table WHERE message ESEARCH 'timeout%'; -- REGEXP (정규식) SELECT * FROM log_table WHERE message REGEXP 'error[0-9]+'; ``` --- ## GROUP BY / HAVING ```sql 'GROUP BY' expr_list [ 'HAVING' condition_expr ] ``` ```sql SELECT name, AVG(value), MAX(value), COUNT(*) FROM sensor_log GROUP BY name HAVING AVG(value) > 50.0; ``` --- ## ORDER BY ```sql 'ORDER BY' expr_list [ 'ASC' | 'DESC' ] ``` ```sql SELECT name, value FROM sensor_log ORDER BY value DESC; SELECT name, value FROM sensor_log ORDER BY name ASC, value DESC; ``` --- ## LIMIT ```sql 'LIMIT' [ offset ',' ] row_count ``` ```sql -- 처음 10건만 조회 SELECT * FROM sensor_log LIMIT 10; -- 11번째부터 10건 조회 SELECT * FROM sensor_log LIMIT 10, 10; ``` --- ## DURATION `_arrival_time` 컬럼을 기준으로 조회 시간 범위를 지정합니다. ```sql duration_expr ::= number time_unit [ ( 'BEFORE' | 'AFTER' ) number time_unit ] | 'FROM' datetime_expr 'TO' datetime_expr time_unit ::= 'YEAR' | 'MONTH' | 'WEEK' | 'DAY' | 'HOUR' | 'MINUTE' | 'SECOND' ``` ```sql -- 최근 1시간 데이터 SELECT * FROM sensor_log DURATION 1 HOUR; -- 1일 전부터 1시간 범위 SELECT * FROM sensor_log DURATION 1 HOUR BEFORE 1 DAY; -- 명시적 범위 SELECT * FROM sensor_log DURATION FROM TO_DATE('2024-01-01','YYYY-MM-DD') TO TO_DATE('2024-01-31','YYYY-MM-DD'); ``` --- ## JOIN ### INNER JOIN (쉼표 방식) ```sql SELECT t1.id, t2.name FROM sensor_log t1, devices t2 WHERE t1.id = t2.device_id AND t1.value > 50; ``` ### ANSI JOIN ```sql -- INNER JOIN SELECT t1.id, t2.name FROM sensor_log t1 INNER JOIN devices t2 ON (t1.id = t2.device_id) WHERE t1.value > 50; -- LEFT OUTER JOIN SELECT t1.id, t2.location FROM sensor_log t1 LEFT OUTER JOIN devices t2 ON (t1.name = t2.name); -- RIGHT OUTER JOIN SELECT t1.value, t2.name FROM sensor_log t1 RIGHT OUTER JOIN devices t2 ON (t1.name = t2.name); ``` > FULL OUTER JOIN은 지원하지 않습니다. --- ## SERIES BY 정렬된 결과에서 조건을 연속으로 만족하는 레코드 그룹을 추출합니다. ```sql 'ORDER BY' expr 'SERIES BY' condition_expr ``` ```sql -- C2 > 1을 연속으로 만족하는 레코드 그룹 조회 SELECT c1, c2, SERIESNUM() AS grp FROM t1 ORDER BY c1 SERIES BY c2 > 1; ``` --- ## SUBQUERY ```sql -- FROM 절 서브쿼리 (인라인 뷰) SELECT a.name, a.avg_val FROM (SELECT name, AVG(value) AS avg_val FROM sensor_log GROUP BY name) a WHERE a.avg_val > 50; -- WHERE 절 서브쿼리 SELECT * FROM sensor_log WHERE value > (SELECT AVG(value) FROM sensor_log); -- IN 절 서브쿼리 SELECT * FROM sensor_log WHERE name IN (SELECT name FROM devices WHERE status = 'ACTIVE'); ``` > 상관 서브쿼리(외부 쿼리 컬럼을 참조하는 서브쿼리)는 지원하지 않습니다. --- ## CASE 문 ```sql -- simple CASE CASE expr WHEN value1 THEN result1 [ WHEN value2 THEN result2 ... ] [ ELSE default_result ] END -- searched CASE CASE WHEN condition1 THEN result1 [ WHEN condition2 THEN result2 ... ] [ ELSE default_result ] END ``` ```sql SELECT name, value, CASE WHEN value >= 80 THEN 'HIGH' WHEN value >= 40 THEN 'MID' ELSE 'LOW' END AS level FROM sensor_log; ``` --- ## PIVOT 인라인 뷰의 집계 결과를 행에서 열로 변환합니다. ```sql 'PIVOT' '(' aggregate_func '(' column ')' 'FOR' pivot_column 'IN' '(' value_list ')' ')' ``` ```sql SELECT * FROM (SELECT regtime, tagid, dvalue FROM result_d) PIVOT (SUM(dvalue) FOR tagid IN ('AXIS_X', 'AXIS_Y', 'AXIS_Z')); ``` --- ## UNION ALL ```sql select_stmt 'UNION ALL' select_stmt ``` 두 SELECT 결과를 합칩니다. 컬럼 수와 타입이 호환되어야 합니다. `UNION` (중복 제거), `INTERSECT`, `EXCEPT`는 지원하지 않습니다. ```sql SELECT id, name FROM table_a UNION ALL SELECT id, name FROM table_b; ``` --- ## SAVE DATA INTO SELECT 결과를 CSV 파일로 저장합니다. ```sql 'SAVE DATA INTO' 'file_path' [ 'HEADER' ( 'ON' | 'OFF' ) ] [ ( 'FIELDS' | 'COLUMNS' ) [ 'TERMINATED BY' char ] [ 'ENCLOSED BY' char ] ] [ 'ENCODED BY' encoding ] 'AS' select_stmt ``` ```sql SAVE DATA INTO '/tmp/sensor_data.csv' HEADER ON AS SELECT * FROM sensor_log; ``` --- ## 관련 문서 - [WITH / CTE syntax](../cte-syntax/) - 비재귀 공통 테이블 표현식 - [힌트 사전](../select-hint-syntax/) - SELECT 쿼리 성능 최적화 힌트 - [SERIES BY](../series-syntax/) - 연속 조건 그룹화 상세 설명 - [PIVOT](../pivot-syntax/) - 행-열 변환 상세 예시 - [SEARCH/ESEARCH/REGEXP](../search-esearch-regexp-syntax/) - 텍스트 검색 상세 - [DURATION 상대 시간 표현](../../relative-time/) - 시간 범위 표현 전체 목록 --- title: "WITH / CTE" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/cte-syntax/ language: kr kind: page --- # WITH / CTE 공통 테이블 표현식(Common Table Expression, CTE)은 한 SQL 문 안에서 `SELECT` 결과에 이름을 붙여 사용하는 기능입니다. 복잡한 인라인 뷰를 단계별로 분리하거나, 집계 결과를 다른 테이블과 조인할 때 사용합니다. Machbase 8.7.0 Standard Edition은 비재귀 SELECT CTE를 지원합니다. CTE는 현재 SQL 문에서만 유효하며 별도 데이터베이스 객체로 저장되지 않습니다. ## 지원 범위 | 기능 | 지원 여부 | 설명 | |---|:---:|---| | 단일 비재귀 CTE | O | CTE 본문과 주 쿼리는 `SELECT`입니다. | | 다중 CTE | O | 쉼표로 구분하며, 뒤 CTE가 앞 CTE를 참조할 수 있습니다. | | 명시적 결과 컬럼명 | O | CTE 이름 뒤에 결과 컬럼 목록을 지정합니다. | | 중첩 CTE | O | 바깥 CTE는 하위 `SELECT`에서 참조할 수 있습니다. | | `INSERT SELECT` | O | `INSERT INTO ... WITH ... SELECT` 순서를 사용합니다. | | VIEW 정의 | O | `CREATE VIEW ... AS WITH ... SELECT`를 사용합니다. | | prepared statement | O | CTE 본문과 주 `SELECT`에서 `?` 또는 `:name`을 사용할 수 있습니다. | | EXPLAIN | O | `EXPLAIN`, `EXPLAIN FULL`, `EXPLAIN TRACE`를 지원합니다. | | `UNION ALL`, PIVOT | O | 기존 `SELECT`의 지원 범위와 제약을 따릅니다. | | 테이블 유형 | O | LOG, TAG, LOOKUP, VOLATILE, TRANSACTION 테이블을 조회할 수 있습니다. | | 재귀 CTE | X | `WITH RECURSIVE`, 자기 참조와 상호 재귀를 지원하지 않습니다. | | 구체화 제어 | X | `MATERIALIZED`, `NOT MATERIALIZED`를 지원하지 않습니다. | | 데이터 변경 CTE | X | CTE 본문에 DML이나 DDL을 사용할 수 없습니다. | CTE 본문에서는 JOIN, 집계 함수, `GROUP BY`, `HAVING`, `ORDER BY`, `LIMIT`, `UNION ALL`, PIVOT을 기존 `SELECT` 규칙에 따라 사용할 수 있습니다. LOG 테이블의 `DURATION`, `SERIES BY`, 윈도우 함수와 TAG 테이블의 ROLLUP도 기존 규칙을 따릅니다. Named parameter의 이름 규칙과 SDK별 바인딩 방법은 [Named Bind Parameter syntax](../named-bind-parameter-syntax/)를 참고하십시오. ## 기본 문법 ### SELECT ```sql WITH cte_name [(column_name [, ...])] AS ( select_statement ) [, cte_name [(column_name [, ...])] AS (select_statement) ...] select_statement; ``` ### INSERT SELECT ```sql INSERT INTO target_table [(target_column [, ...])] WITH cte_name [(column_name [, ...])] AS ( select_statement ) [, cte_name [(column_name [, ...])] AS (select_statement) ...] select_statement; ``` `INSERT SELECT`에서는 `WITH` 절을 대상 테이블과 대상 컬럼 목록 뒤에 작성합니다. 다른 DBMS에서 사용하는 문장 선두의 `WITH ... INSERT INTO ...` 형식은 지원하지 않습니다. ### VIEW ```sql CREATE [OR REPLACE] VIEW view_name AS WITH cte_name [(column_name [, ...])] AS ( select_statement ) [, cte_name [(column_name [, ...])] AS (select_statement) ...] select_statement; ``` ### EXPLAIN ```sql EXPLAIN [FULL | TRACE] WITH cte_name [(column_name [, ...])] AS ( select_statement ) [, cte_name [(column_name [, ...])] AS (select_statement) ...] select_statement; ``` ## 기본 사용법 다음 예제는 현재 사용자가 아래 테이블을 소유한다고 가정합니다. | 테이블 | 종류 | 사용 컬럼 | |---|---|---| | `sensor_data` | LOG | `name`, `device_id`, `time`, `value` | | `device_info` | TRANSACTION | `device_id`, `device_name` | | `device_summary` | LOG | `device_id`, `sample_count`, `avg_value` | ### 조회 결과에 이름 지정 ```sql WITH recent_data AS ( SELECT name, time, value FROM sensor_data WHERE time >= NOW - 10m ) SELECT name, time, value FROM recent_data ORDER BY time DESC; ``` 최종 출력 순서를 보장하려면 주 `SELECT`에 `ORDER BY`를 지정합니다. CTE 본문의 `ORDER BY`만으로 바깥 결과의 순서는 보장되지 않습니다. ### 집계 결과 조인 ```sql WITH top_devices AS ( SELECT device_id, AVG(value) AS avg_value FROM sensor_data WHERE time >= NOW - 1h GROUP BY device_id ORDER BY avg_value DESC LIMIT 10 ) SELECT d.device_id, d.device_name, t.avg_value FROM device_info d JOIN top_devices t ON d.device_id = t.device_id ORDER BY t.avg_value DESC; ``` 대량 시계열 데이터를 먼저 집계하고 결과 건수를 제한한 뒤 기준정보와 조인할 때 사용할 수 있습니다. ### 여러 CTE 연결 ```sql WITH recent_data AS ( SELECT device_id, value FROM sensor_data WHERE time >= NOW - 30m ), device_avg AS ( SELECT device_id, AVG(value) AS avg_value FROM recent_data GROUP BY device_id ) SELECT device_id, avg_value FROM device_avg WHERE avg_value >= 80; ``` `device_avg`는 앞에서 선언한 `recent_data`를 참조할 수 있습니다. 앞 CTE가 뒤 CTE를 참조하는 전방 참조는 지원하지 않습니다. ### 결과 컬럼명 지정 ```sql WITH device_stat (id, sample_count, average_value) AS ( SELECT device_id, COUNT(*), AVG(value) FROM sensor_data GROUP BY device_id ) SELECT id, sample_count, average_value FROM device_stat; ``` 명시한 컬럼 수는 CTE 본문의 결과 컬럼 수와 같아야 하며 컬럼명을 중복해서 지정할 수 없습니다. 컬럼 목록을 생략하면 `SELECT` alias와 인라인 뷰의 컬럼 이름 결정 규칙을 따릅니다. ## 사용할 수 있는 SQL 문맥 ### 하위 SELECT ```sql WITH active_devices AS ( SELECT device_id FROM sensor_data WHERE time >= NOW - 1m ) SELECT d.device_id, d.device_name FROM device_info d WHERE d.device_id IN ( SELECT device_id FROM active_devices ); ``` 바깥 `SELECT`에 선언한 CTE는 스칼라 서브쿼리, `IN (subquery)`, 인라인 뷰 등의 하위 `SELECT`에서 참조할 수 있습니다. JOIN `ON` 조건의 스칼라 서브쿼리와 `IN (subquery)` 같은 기존 `SELECT` 제약은 CTE를 사용해도 변경되지 않습니다. ### INSERT SELECT ```sql INSERT INTO device_summary WITH hourly_summary AS ( SELECT device_id, COUNT(*) AS sample_count, AVG(value) AS avg_value FROM sensor_data WHERE time >= NOW - 1h GROUP BY device_id ) SELECT device_id, sample_count, avg_value FROM hourly_summary; ``` CTE는 결과 행을 만들며, 실제 입력 가능 여부와 중복 키 처리는 대상 테이블의 기존 `INSERT SELECT` 규칙을 따릅니다. CTE를 사용한다고 대상 테이블의 제약이나 원자성 범위가 변경되지는 않습니다. ### VIEW 정의 ```sql CREATE VIEW active_device_summary AS WITH recent_data AS ( SELECT device_id, value FROM sensor_data WHERE time >= NOW - 10m ) SELECT device_id, COUNT(*) AS sample_count, AVG(value) AS avg_value FROM recent_data GROUP BY device_id; ``` VIEW에는 CTE를 포함한 `SELECT` 정의가 저장되며, 조회 시 해당 정의가 다시 해석됩니다. `CREATE OR REPLACE VIEW`에도 같은 문법을 사용할 수 있습니다. VIEW 정의에는 바인드 매개변수(`?`)를 사용할 수 없습니다. 실행 시마다 달라지는 조건은 VIEW를 조회하는 `SELECT`에 작성합니다. ### EXPLAIN ```sql EXPLAIN FULL WITH recent_data AS ( SELECT name, time, value FROM sensor_data WHERE time >= NOW - 5m ) SELECT * FROM recent_data WHERE name = 'sensor-01'; ``` `EXPLAIN`, `EXPLAIN FULL`, `EXPLAIN TRACE`로 CTE가 전개된 뒤 실제 테이블에 적용되는 스캔, 필터와 JOIN 계획을 확인합니다. ### Prepared statement CTE 본문과 주 `SELECT`에서 바인드 매개변수(`?`)를 사용할 수 있습니다. ```sql WITH selected_data AS ( SELECT device_id, time, value FROM sensor_data WHERE device_id = ? ) SELECT device_id, time, value FROM selected_data WHERE value >= ?; ``` 참조하지 않는 CTE의 바인드 매개변수도 문장의 매개변수로 등록되므로 값을 바인드해야 합니다. 같은 CTE를 여러 번 참조하더라도 원래 CTE 본문의 매개변수 개수가 참조 횟수만큼 늘어나지는 않습니다. ## 이름과 유효 범위 ### 선언 순서 뒤 CTE는 앞 CTE를 참조할 수 있습니다. 전방 참조, 자기 참조와 CTE 간 상호 참조는 지원하지 않습니다. ### 실제 테이블과 이름이 같은 경우 한정하지 않은 이름이 CTE와 실제 TABLE 또는 VIEW에 모두 존재하면 현재 유효 범위의 CTE가 우선합니다. ```sql WITH device_info AS ( SELECT device_id FROM sensor_data ) SELECT * FROM device_info; ``` 실제 테이블을 선택하려면 `user_name.device_info`처럼 소유자를 명시합니다. 소유자를 명시한 이름은 CTE가 아니라 실제 TABLE 또는 VIEW를 찾습니다. ### 중첩 범위 바깥 CTE는 안쪽 `SELECT`에서 참조할 수 있습니다. 안쪽 `SELECT`에 선언한 CTE는 바깥에서 참조할 수 없으며, 안쪽 CTE가 바깥 CTE와 같은 이름이면 안쪽 CTE가 우선합니다. ## 실행 특성과 성능 Machbase는 CTE 참조를 기존 인라인 뷰 형태로 전개하여 계획합니다. CTE 결과가 임시 테이블에 구체화되거나 한 번만 평가된다고 보장하지 않습니다. 같은 CTE를 여러 번 참조하면 각 참조가 별도로 계획되고 실행될 수 있습니다. ```sql WITH recent_data AS ( SELECT device_id, time, value FROM sensor_data WHERE time >= NOW - 1d ) SELECT a.device_id, a.value, b.value FROM recent_data a JOIN recent_data b ON a.device_id = b.device_id AND a.time = b.time; ``` 성능을 관리할 때는 다음 기준을 적용합니다. - 대량 테이블을 읽는 CTE를 반복해서 참조하지 않습니다. - 시간, TAG 이름과 키 조건 등 선택도를 높이는 조건을 CTE 본문에 가능한 한 일찍 적용합니다. - 필터 pushdown이나 CTE 결과의 자동 재사용을 전제로 성능을 예측하지 않습니다. - 반복 참조가 필요하면 쿼리를 분리하거나 실제 저장 객체 사용을 검토합니다. - `EXPLAIN`으로 각 참조의 실제 실행 계획을 확인합니다. `MATERIALIZED`와 `NOT MATERIALIZED`를 지원하지 않으므로 CTE 평가 방식을 사용자가 강제할 수 없습니다. ### CTE 확장 한도 한 SQL 문에서 CTE 전개 과정으로 생성되는 `SELECT` 단위는 최대 1,024개입니다. 이는 선언할 수 있는 CTE 이름의 개수가 아니라, 다중 참조와 연쇄 참조를 모두 전개한 `SELECT` 단위의 합계입니다. 한도를 초과하면 다음 오류가 발생합니다. ```text CTE expansion limit exceeded ``` 오류가 발생하면 반복 다중 참조 체인을 줄이거나 중간 결과를 별도 테이블 또는 VIEW로 분리합니다. ## 제한사항 다음 기능은 지원하지 않습니다. - `WITH RECURSIVE`와 재귀 CTE - `RECURSIVE` 키워드를 생략한 자기 참조 및 CTE 간 상호 재귀 - 전방 참조 - `MATERIALIZED`, `NOT MATERIALIZED` - 재귀 CTE 문법의 `SEARCH DEPTH FIRST`, `SEARCH BREADTH FIRST`, `CYCLE` - 문장 선두의 `WITH ... INSERT`, `WITH ... UPDATE`, `WITH ... DELETE`, `WITH ... MERGE` - CTE 본문의 INSERT, UPDATE, DELETE, MERGE 또는 DDL - `UNION`, `INTERSECT`, `EXCEPT` - `FROM` 절 없는 리터럴 `SELECT`끼리의 `UNION ALL` - `EXISTS` 식 - CTE 본문의 `FREQUENCY` - 사용자 정의 `CREATE ROLLUP ... AS (...)` 쿼리의 CTE CTE는 테이블 유형별 DML 기능을 확장하지 않습니다. LOG, TAG, LOOKUP, VOLATILE과 TRANSACTION 테이블의 조회 및 입력은 각 테이블의 기존 규칙을 따릅니다. ## 오류 확인 | 상황 | 확인할 내용 | |---|---| | CTE 이름 중복 | 같은 `WITH` 절에서 이름을 한 번만 선언했는지 확인합니다. | | 컬럼 개수 불일치 | 명시한 CTE 컬럼 수와 `SELECT` 결과 컬럼 수를 맞춥니다. | | 컬럼 이름 중복 | 명시적 컬럼 목록에서 중복 이름을 제거합니다. | | 전방 참조 | 참조 대상 CTE를 먼저 선언합니다. | | 자기 참조 | 재귀 구조를 제거하거나 고정 깊이 SQL 또는 애플리케이션 반복으로 변경합니다. | | 테이블을 찾을 수 없음 | 한정 이름이 CTE가 아닌 실제 TABLE/VIEW를 찾는지 확인합니다. | | 확장 한도 초과 | 반복 참조 체인을 줄이거나 중간 결과를 별도 객체로 분리합니다. | | VIEW bind 오류 | VIEW 정의와 CTE에서 `?`를 제거하고 조회 시 조건으로 옮깁니다. | | 문법 오류 | 지원하지 않는 재귀, 구체화 또는 문장 선두 `WITH ... DML` 문법인지 확인합니다. | ## 다른 DBMS에서 이전 | 원본 DBMS 기능 | Machbase에서의 처리 | |---|---| | PostgreSQL/MySQL의 `WITH RECURSIVE` | 고정 깊이 SQL 또는 애플리케이션 반복으로 변경합니다. | | PostgreSQL/SQLite의 `MATERIALIZED` | 키워드를 제거하고 한 번 평가된다고 가정하지 않습니다. | | PostgreSQL/SQLite의 `NOT MATERIALIZED` | 키워드를 제거하고 실제 계획을 `EXPLAIN`으로 확인합니다. | | Oracle의 재귀 subquery factoring | 자기 참조를 제거한 비재귀 CTE만 이전합니다. | | SQL Server의 `WITH ... UPDATE/DELETE/MERGE` | CTE와 DML을 분리하고 테이블별 DML 규칙을 적용합니다. | | 재귀 CTE의 `SEARCH` 또는 `CYCLE` | 경로와 cycle 검사를 애플리케이션이나 저장 컬럼으로 처리합니다. | ## 관련 문서 - [SELECT syntax](../select-syntax/) - [DML syntax](../dml-syntax/) - [VIEW syntax](../view-syntax/) - [집합 연산자](../set-operator-syntax/) - [PIVOT syntax](../pivot-syntax/) - [윈도우 함수와 OVER](../window-function-over-syntax/) - [쿼리 분석과 EXPLAIN](/dbms/performance-tuning/performance-query-tuning/) --- title: "Named Bind Parameter" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/named-bind-parameter-syntax/ language: kr kind: page --- # Named Bind Parameter Named Bind Parameter는 SQL의 값 위치에 `:name` 형식의 이름을 지정하고 실행할 때 값을 바인딩하는 기능입니다. 반복되는 파라미터의 의미를 이름으로 표현할 수 있어 SQL과 애플리케이션 코드의 대응 관계를 명확하게 유지할 수 있습니다. ```sql SELECT ID, NAME FROM SENSOR_DATA WHERE ID = :id; ``` ## 이름 문법 Named marker는 다음 형식을 사용합니다. ```text :[A-Za-z_$][A-Za-z0-9_$]* ``` | 구분 | 예 | |---|---| | 유효한 이름 | `:id`, `:sensor_id`, `:value2`, `:_from_time`, `:select` | | 유효하지 않은 이름 | `:1id`, `:`, `::id` | 여러 SDK에서 같은 SQL을 공유하려면 `[A-Za-z][A-Za-z0-9_]*` 형식의 이름을 사용하는 것이 좋습니다. 파라미터 이름은 대소문자를 구분합니다. 따라서 `:VALUE`, `:value`, `:VaLuE`는 서로 다른 이름입니다. .NET의 `MachParameterCollection`은 기존 provider 호환을 위해 이름을 대소문자 구분 없이 찾습니다. ## 사용할 수 있는 위치 Named marker는 값이나 표현식이 들어가는 위치에 사용합니다. ```sql SELECT ID, NAME, VALUE FROM SENSOR_DATA WHERE CREATED_AT >= :from_time AND CREATED_AT < :to_time AND VALUE >= :minimum_value ORDER BY CREATED_AT LIMIT :row_count OFFSET :start_row; ``` 다음과 같은 식별자나 SQL 구조는 파라미터로 대체할 수 없습니다. ```sql SELECT * FROM :table_name; -- 사용할 수 없음 SELECT :column_name FROM SENSOR_DATA; -- 컬럼 식별자 대체가 아님 SELECT * FROM SENSOR_DATA ORDER BY ID :direction; -- 사용할 수 없음 ``` 동적 식별자가 필요하면 애플리케이션에서 허용 목록을 검사한 뒤 SQL을 구성합니다. 문자열과 SQL 주석 안의 콜론은 파라미터로 인식하지 않습니다. ```sql SELECT ':not_a_parameter' FROM SENSOR_DATA WHERE ID = :id /* :ignored */; ``` ## 파라미터 발생 순서 파라미터 개수는 고유 이름 수가 아니라 SQL에 나타난 횟수로 계산합니다. 다음 SQL에는 `target`이라는 이름이 두 번 나타나므로 파라미터가 두 개입니다. ```sql SELECT ID, NAME FROM SENSOR_DATA WHERE ID = :target OR PARENT_ID = :target; ``` - `SQLNumParams()`는 `2`를 반환합니다. - ordinal API는 첫 번째와 두 번째 위치를 각각 바인딩합니다. - 이름 기반 API는 `target` 값 하나를 이름이 같은 두 위치에 모두 적용합니다. - 파라미터 메타데이터에는 각 위치가 별도 항목으로 나타납니다. 한 SQL 문에 사용할 수 있는 파라미터 발생 횟수는 최대 256개입니다. ## Positional marker와의 관계 저수준 ordinal API는 `?`와 `:name`을 SQL에 나타난 순서대로 바인딩할 수 있습니다. 이름, 객체 또는 매핑 기반 API는 anonymous marker인 `?`와 named marker를 함께 사용하면 오류를 반환합니다. 한 SQL 문에서는 한 종류의 marker를 사용하십시오. | 방식 | SQL marker | 바인딩 | |---|---|---| | Positional | `?` | SQL 출현 순서의 1-based ordinal | | Named SQL과 ordinal API | `:name` | SQL 출현 순서의 1-based ordinal | | Named API | `:name` | 파라미터 이름 | ## DML 사용 예 Named Bind Parameter는 기존 Prepared Statement와 같은 타입 규칙을 사용합니다. ```sql INSERT INTO SENSOR_DATA (ID, PARENT_ID, NAME, VALUE, CREATED_AT) VALUES (:id, :parent_id, :name, :value, :created_at); ``` Named Bind Parameter는 테이블별 DML 정책이나 Edition 제약을 변경하지 않습니다. 지원되는 DML과 조건은 [DML syntax](../dml-syntax/) 및 [지원 범위와 제약](../../../support-scope-constraints/)을 참고하십시오. Standard Edition과 Cluster Edition은 같은 `:name` 문법과 ordinal 규칙을 사용합니다. 실제로 실행할 수 있는 SQL과 테이블 타입은 각 Edition의 기존 지원 범위를 따릅니다. ### TAG data UPDATE에서 사용 Machbase 8.7.0부터 Standard Edition의 TAG data UPDATE는 `WHERE` 절의 NAME과 BASETIME 조건 값에 named marker를 사용할 수 있습니다. ```sql UPDATE sensor_tag SET value = :value, status = :status, note = :note WHERE name = :name AND time = :time; ``` 같은 prepared statement를 다시 실행할 때 SET, NAME, TIME 값을 새로 바인딩할 수 있습니다. 일치하는 행이 없으면 affected rows `0`으로 성공합니다. bind 사용 여부와 관계없이 태그 선택 조건과 BASETIME 조건은 모두 필요하고, SET 대상 컬럼 제약도 그대로 적용됩니다. 지원되는 조건 형태와 parameter metadata는 [TAG data UPDATE](../dml-syntax/tag-data-update-syntax/#tag-data-update-predicate-bind)를 참고하십시오. ## CTE에서 사용 Standard Edition에서는 CTE 본문과 주 `SELECT`에서 Named Bind Parameter를 사용할 수 있습니다. ```sql WITH FILTERED AS ( SELECT ID, NAME, VALUE FROM SENSOR_DATA WHERE ID > :minimum_id AND NAME = :label ) SELECT ID, NAME, VALUE FROM FILTERED WHERE ID = :target_id ORDER BY ID; ``` 위 SQL의 파라미터 ordinal은 `minimum_id`, `label`, `target_id` 순서입니다. CTE의 지원 범위와 Standard Edition 제약은 [WITH / CTE syntax](../cte-syntax/)를 참고하십시오. ## NULL과 데이터 타입 NULL은 각 SDK의 표준 NULL 값 또는 indicator를 사용해 전달합니다. | SDK | NULL 값 | |---|---| | Machbase SQLCLI | indicator의 `SQL_NULL_DATA` | | ODBC | indicator의 `SQL_NULL_DATA` | | JDBC | `null` | | Node.js/TypeScript | `null` | | Python | `None` | | .NET | `DBNull.Value` | `column = :value`에 NULL을 바인딩해도 `column IS NULL`과 같은 조건이 되지 않습니다. NULL을 검색하려면 SQL의 NULL 비교 규칙에 따라 `IS NULL`을 사용합니다. `INTEGER`, `VARCHAR`, `DOUBLE`, `DECIMAL`, `NUMERIC`, `DATETIME` 등 기존 Prepared Statement 데이터 타입을 사용할 수 있습니다. `DECIMAL` 또는 `NUMERIC`의 정밀도를 보존하려면 SDK의 decimal 타입이나 문자열 표현을 사용하십시오. ## SDK별 바인딩 방식 | SDK 또는 도구 | 이름 기반 사용 방식 | |---|---| | Machbase SQLCLI | `SQLBindParameterByName()`, `SQLBindParameterByNameW()` | | ODBC | `:name` SQL을 `SQLBindParameter()` ordinal로 바인딩 | | JDBC | `MachPreparedStatement.setObject(String name, Object value)` | | Node.js/TypeScript | 배열은 positional, 객체는 named 입력 | | Python DB-API | mapping 전달. 2.4 prepared cursor는 `:name`과 `%(name)s`를 호출 간 재사용 | | .NET | `MachCommand.Parameters.AddWithValue(":name", value)` | | Go native | `api.Named("name", value)` | | Go `database/sql` | `sql.Named("name", value)` | | machsql | SQL은 `:name`, 값은 `$1`, `$2` 순서로 지정 | 자세한 API와 오류 처리는 [개발 도구 연동](../../../../development-tools-integration/)과 [machsql 명령/옵션 사전](../../../command-line-tools/machsql/)을 참고하십시오. ## 호환성과 오류 Machbase 8.7.0의 이름 기반 SDK API를 사용하려면 해당 기능을 지원하는 클라이언트와 서버가 모두 필요합니다. 이전 버전과 함께 사용해야 하면 `?`와 ordinal API를 사용하십시오. | 상황 | 대표 오류 | |---|---| | 필요한 이름이 누락됨 | missing parameter | | SQL에 없는 이름을 전달함 | unknown 또는 extra parameter | | named와 positional 방식을 혼용함 | sequence 또는 mixed error | | 값 타입이 SQL 타입과 맞지 않음 | type 또는 conversion error | | 이름 기반 API를 이전 서버에 사용함 | unsupported | 운영 코드에서는 오류 문자열보다 SQLSTATE, 오류 코드와 예외 타입을 우선 확인하십시오. 버전 조합별 동작과 SDK별 오류 코드는 [클라이언트/서버 프로토콜 호환성](../../../support-scope-constraints/compatibility-xma-protocol/)을 참고하십시오. --- title: "SELECT hint" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/select-hint-syntax/ language: kr kind: section --- # SELECT hint SELECT 힌트는 `/*+ ... */` 형식의 주석 블록으로 옵티마이저 동작을 제어하거나 샘플링 등의 데이터 처리 기능을 지정합니다. ## 힌트 문법 ```sql SELECT /*+ hint_clause */ ... SELECT /*+ hint1 hint2 */ ... ``` 힌트는 `SELECT` 키워드 바로 뒤의 `/*+ ... */` 블록에 작성합니다. ## 주요 힌트 목록 ### 실행 계획 제어 힌트 | 힌트 | 문법 | 설명 | |------|------|------| | `PARALLEL` | `/*+ PARALLEL(table, n) */` | 병렬 처리 계수 지정 | | `NOPARALLEL` | `/*+ NOPARALLEL(table) */` | 병렬 처리 비활성화 | | `FULL` | `/*+ FULL(table) */` | 인덱스 스캔 대신 풀 스캔 강제 | | `NO_INDEX` | `/*+ NO_INDEX(table, index) */` | 특정 인덱스 사용 안 함 | | `ROLLUP_TABLE` | `/*+ ROLLUP_TABLE(rollup_table) */` | 특정 ROLLUP 테이블 강제 선택 | | `RID_RANGE` | `/*+ RID_RANGE(table, start, end) */` | RID 범위 지정 | | `SCAN_FORWARD` | `/*+ SCAN_FORWARD(table) */` | 오래된 레코드부터 스캔 (LOG 테이블) | | `SCAN_BACKWARD` | `/*+ SCAN_BACKWARD(table) */` | 최신 레코드부터 스캔 (LOG 테이블) | ### 데이터 처리 힌트 | 힌트 | 문법 | 설명 | |------|------|------| | `SAMPLING` | `/*+ SAMPLING(SamplingRate) */` | 실수 값으로 지정한 비율에 따라 데이터 추출 | ## 예시 ```sql -- 병렬 처리 8개 스레드 SELECT /*+ PARALLEL(sensor_log, 8) */ sensor, AVG(value) FROM sensor_log WHERE ts BETWEEN TO_DATE('2024-01-01', 'YYYY-MM-DD') AND TO_DATE('2024-01-31', 'YYYY-MM-DD') GROUP BY sensor; -- 특정 인덱스 미사용 SELECT /*+ NO_INDEX(sensor_log, idx_ts) */ * FROM sensor_log WHERE ts > TO_DATE('2024-01-01', 'YYYY-MM-DD'); -- ROLLUP 테이블 강제 선택 SELECT /*+ ROLLUP_TABLE(_rollup_tag_value_min) */ name, rollup('min', 5, time) AS t, AVG(value) FROM tag WHERE name = 'TEMP-01' GROUP BY name, t; -- 조건에 맞는 행을 최대 100,000행으로 제한한 범위에서 1% 비율로 추출 SELECT /*+ SAMPLING(0.01) */ t_name, time, value FROM tag WHERE t_name = 'TAG_99' LIMIT 100000; ``` ## 하위 항목 - [SAMPLING hint](./sampling-hint/) — 비율 기반 샘플링 힌트 상세 ## 관련 문서 - [쿼리 성능 튜닝](/dbms/performance-tuning/performance-query-tuning/) — 실행 계획과 힌트 적용 기준 --- title: "SAMPLING hint" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/select-hint-syntax/sampling-hint/ language: kr kind: page --- # SAMPLING hint `SAMPLING` 힌트는 지정한 비율에 따라 데이터를 추출합니다. 추출 비율은 실수 값인 `SamplingRate`로 지정하며, `1`은 전체 데이터의 100%를 의미합니다. ## 문법 ```sql SELECT /*+ SAMPLING(SamplingRate) */ col1, col2, ... FROM table_name WHERE ...; ``` | 매개변수 | 설명 | |----------|------| | `SamplingRate` | 데이터를 추출할 비율을 나타내는 실수 값 | 추출 비율을 백분율로 나타내려면 `SamplingRate`에 100을 곱합니다. | SamplingRate | 추출 비율 | |--------------|-----------| | `1` | 100% (전체 데이터) | | `0.01` | 1% | | `0.0001` | 0.01% | | `0.00001` | 0.001% | ## 예시 ```sql -- 조건에 맞는 행을 최대 100,000행으로 제한한 범위에서 1% 비율로 추출 SELECT /*+ SAMPLING(0.01) */ t_name, time, value FROM tag WHERE t_name = 'TAG_99' LIMIT 100000; ``` ## 주의사항 - `SamplingRate`는 시간 간격이나 반환 행 수가 아니라 추출 비율입니다. - 반환 행 수는 실행마다 달라질 수 있으며, 지정 비율에 해당하는 정확한 행 수를 보장하지 않습니다. - 데이터가 적거나 추출 비율이 낮으면 결과가 0행일 수 있습니다. - 위 예제처럼 `LIMIT`를 함께 사용하면 `LIMIT`로 제한된 행 범위에서 샘플링합니다. 예를 들어 `SAMPLING(0.5)`와 `LIMIT 1000`을 함께 지정하면, 조건에 맞는 데이터가 충분한 경우 최대 1,000행의 범위에서 약 50%를 추출합니다. 샘플링 결과를 1,000행까지 채워 반환하는 의미는 아닙니다. ## 관련 문서 - [SELECT hint syntax](../) — 전체 힌트 목록 - [ROLLUP syntax](../../rollup-syntax/) — 정확한 시간 단위 집계 --- title: "SEARCH / ESEARCH / REGEXP" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/search-esearch-regexp-syntax/ language: kr kind: page --- # SEARCH / ESEARCH / REGEXP 검색 연산자는 비슷해 보여도 검사하는 대상이 다릅니다. SEARCH·ESEARCH는 KEYWORD 인덱스의 토큰을 사용하고, LIKE·REGEXP는 원문에 조건을 평가합니다. 성능 때문에 연산자를 바꾸기 전에 결과 의미가 같은지 확인하세요. ## SEARCH ```text column_name SEARCH 'search_term' column_name NOT SEARCH 'search_term' ``` LOG의 VARCHAR·TEXT 컬럼에 KEYWORD 인덱스가 필요합니다. 다중 단어는 AND 의미로 찾으며, 어순·인접성을 보장하는 구문 검색은 아닙니다. 기본 모드에서 일반 ASCII 단어는 소문자로 정규화되고 한글 등은 2-gram으로 분리됩니다. 모든 언어의 형태소 분석이나 Unicode 대소문자 처리를 보장하는 기능은 아닙니다. 다음 테이블은 이 페이지 전체에서 사용하는 독립 실습입니다. ```sql CREATE LOG TABLE ch7_ref_search ( event_id INTEGER, message VARCHAR(200), detail VARCHAR(200) ); CREATE INDEX ch7_ref_msg ON ch7_ref_search(message) INDEX_TYPE KEYWORD; CREATE INDEX ch7_ref_detail ON ch7_ref_search(detail) INDEX_TYPE KEYWORD; INSERT INTO ch7_ref_search VALUES (1, 'ERROR timeout occurred', 'connection reset'); INSERT INTO ch7_ref_search VALUES (2, 'pretimeout normal', 'port 8080'); INSERT INTO ch7_ref_search VALUES (3, NULL, NULL); EXEC TABLE_FLUSH(ch7_ref_search); EXEC INDEX_FLUSH(ch7_ref_search); SELECT event_id FROM ch7_ref_search WHERE message SEARCH 'timeout' ORDER BY event_id; SELECT event_id FROM ch7_ref_search WHERE message SEARCH 'error' AND detail SEARCH 'reset' ORDER BY event_id; SELECT event_id FROM ch7_ref_search WHERE message NOT SEARCH 'timeout' ORDER BY event_id; ``` 앞의 두 쿼리는 1번, 마지막 쿼리는 2번을 선택합니다. NOT SEARCH는 NULL 메시지인 3번을 포함하지 않습니다. 여러 컬럼을 검색하려면 각 대상 컬럼에 필요한 인덱스를 준비하세요. ## ESEARCH ```text column_name ESEARCH 'pattern%' column_name ESEARCH '%pattern%' ``` 색인된 단어에 패턴을 적용합니다. `pattern%`는 단어 접두사, `%pattern%`는 단어 내부의 부분 패턴입니다. `%`가 원문 전체의 단어 경계를 자유롭게 가로지르는 것으로 해석하지 마세요. 아래 예제는 ASCII 키워드 패턴을 기준으로 합니다. ```sql SELECT event_id FROM ch7_ref_search WHERE message ESEARCH 'time%' ORDER BY event_id; SELECT event_id FROM ch7_ref_search WHERE message ESEARCH '%time%' ORDER BY event_id; ``` 결과는 각각 1번과 1·2번입니다. 현재 ESEARCH의 ASCII 패턴 비교는 대소문자를 구분하지 않습니다. 원문 LIKE의 완전한 대체 기능은 아니며, 패턴에 매칭되는 토큰·행 수에 따라 비용이 달라집니다. `NOT ESEARCH` 구문은 지원하지 않습니다. NOT SEARCH·NOT LIKE로 바꾸면 제외할 행도 달라질 수 있으므로 결과와 NULL 처리를 확인하세요. ## LIKE와 비교 ```sql SELECT event_id FROM ch7_ref_search WHERE message LIKE '%TIMEOUT%' ORDER BY event_id; SELECT event_id FROM ch7_ref_search WHERE message NOT LIKE '%timeout%' ORDER BY event_id; ``` 첫 쿼리는 1·2번, 두 번째는 0건입니다. NULL 행은 NOT LIKE에도 포함되지 않습니다. LIKE는 현재 ASCII 비교에서 대소문자를 구분하지 않습니다. `%`는 0개 이상의 문자, `_`는 한 문자를 나타냅니다. LIKE 자체는 KEYWORD 인덱스를 사용하지 않습니다. 그렇다고 항상 테이블 전체 스캔이라고 단정할 수는 없습니다. 시간 조건이나 다른 인덱스 조건으로 먼저 대상 행이 제한될 수 있습니다. ## REGEXP ```text column_name REGEXP 'pattern' column_name NOT REGEXP 'pattern' ``` 정규식 패턴에 맞는 부분을 검사합니다. 기본 비교는 대소문자를 구분합니다. 시작·끝을 제한하려면 `^`·`$`를 사용하세요. ```sql SELECT event_id FROM ch7_ref_search WHERE message REGEXP '^ERROR.*timeout' ORDER BY event_id; SELECT event_id FROM ch7_ref_search WHERE message NOT REGEXP 'timeout' ORDER BY event_id; ``` 첫 쿼리는 1번, 두 번째는 0건입니다. REGEXP는 KEYWORD 인덱스를 직접 사용하지 않습니다. SEARCH로 먼저 대상을 줄이려면 그 조건이 원하는 결과를 누락시키지 않는지 확인해야 합니다. ### 함수 형태 REGEXP 스칼라 표현식에서도 REGEXP 결과를 사용할 수 있습니다. ```sql SELECT 'abcde' REGEXP 'a[bcd]{1,10}e' FROM dual; ``` 결과는 1입니다. 함수 형태의 REGEXP_LIKE는 비교 옵션도 받을 수 있습니다. 현재 함수 입력은 VARCHAR여야 하며 패턴과 옵션은 상수 VARCHAR여야 합니다. TEXT 컬럼을 받는 REGEXP 연산자와 입력 타입 제약이 같다고 가정하지 마세요. ```sql SELECT event_id, REGEXP_LIKE(message, 'error') AS case_sensitive, REGEXP_LIKE(message, 'error', 'i') AS case_insensitive FROM ch7_ref_search WHERE event_id IN (1, 3) ORDER BY event_id; ``` 1번의 결과는 0·1, 3번은 NULL·NULL입니다. `i`는 대소문자 비구분, `c`는 구분이며 옵션을 생략하면 구분 비교입니다. ## 성능 권장사항 | 목적 | 선택 기준 | |---|---| | 단어 존재 확인 | SEARCH | | 색인된 단어의 접두사·부분 패턴 | ESEARCH | | 원문 문자열의 부분 패턴 | LIKE | | 원문 형식·위치·복잡한 패턴 | REGEXP·REGEXP_LIKE | 인덱스 존재와 빌드 완료를 구분하고, 같은 데이터와 시간 범위에서 비교하세요. 실습을 마쳤으면 테이블을 정리합니다. ```sql DROP TABLE ch7_ref_search; ``` ## 관련 문서 - [텍스트 검색 실습](/dbms/log-table-usage/text-search-keyword-index/) — 다중 단어·한글·NULL·TEXT 제약 - [INDEX syntax](../index-syntax/) — KEYWORD 인덱스 생성 --- title: "set operator" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/set-operator-syntax/ language: kr kind: page --- # set operator 집합 연산자는 두 개 이상의 `SELECT` 쿼리 결과를 합치거나 교집합/차집합을 구하는 연산자입니다. > Machbase는 현재 `UNION ALL` 집합 연산자만 지원합니다. `UNION` (중복 제거), > `INTERSECT`, `EXCEPT`는 지원하지 않습니다. ## UNION ALL 두 쿼리의 결과를 중복 제거 없이 합칩니다. ```sql select_stmt UNION ALL select_stmt ``` ```sql SELECT i1, i2 FROM table_1 UNION ALL SELECT c1, c2 FROM table_2; ``` ## 사용 조건 두 `SELECT` 문은 다음 조건을 모두 만족해야 합니다. 1. **컬럼 수가 동일**해야 합니다. 2. **컬럼 타입이 같거나 호환 가능**해야 합니다. 조건 중 하나라도 불일치하면 오류가 반환됩니다. ### 타입 호환 규칙 | 조합 | 호환 여부 | 결과 타입 | |------|-----------|-----------| | 부호 있는 정수 ↔ 부호 없는 정수 | X | 오류 | | 정수 ↔ 실수 | O | 실수 타입 | | 문자 타입 (다른 길이) | O | 처리됨 | | IPv6 ↔ IPv4 | X | 오류 | - 결과 컬럼명은 좌측 쿼리의 컬럼명을 따릅니다. ## 예시 ```sql -- 두 테이블의 데이터를 합치기 SELECT id, name FROM active_devices UNION ALL SELECT id, name FROM inactive_devices; -- 서로 다른 기간의 통계를 합치기 SELECT 'Q1' AS quarter, SUM(value) AS total FROM sales WHERE month BETWEEN 1 AND 3 UNION ALL SELECT 'Q2' AS quarter, SUM(value) AS total FROM sales WHERE month BETWEEN 4 AND 6; -- 세 쿼리 결합 SELECT name, time, value FROM sensor_a WHERE time > TO_DATE('2024-01-01', 'YYYY-MM-DD') UNION ALL SELECT name, time, value FROM sensor_b WHERE time > TO_DATE('2024-01-01', 'YYYY-MM-DD') UNION ALL SELECT name, time, value FROM sensor_c WHERE time > TO_DATE('2024-01-01', 'YYYY-MM-DD'); ``` ## 주의사항 - `UNION ALL`은 중복 행을 제거하지 않습니다. 중복 제거가 필요하면 `UNION ALL` 결과를 서브쿼리로 감싸고 `DISTINCT`나 `GROUP BY`를 적용합니다. - `FROM` 절 없는 리터럴 `SELECT`끼리의 `UNION ALL`은 지원하지 않습니다. - 결과 행 순서는 보장되지 않습니다. 정렬이 필요하면 전체를 인라인 뷰로 감싸고 외부에서 `ORDER BY`를 적용합니다. ```sql -- 정렬이 필요한 경우 SELECT * FROM ( SELECT id, name, time FROM log_a UNION ALL SELECT id, name, time FROM log_b ) ORDER BY time DESC; ``` ## 관련 문서 - [SELECT syntax](../select-syntax/) — SELECT 기본 문법 - [WITH / CTE syntax](../cte-syntax/) — CTE에서 UNION ALL 사용 - [VIEW syntax](../view-syntax/) — UNION ALL을 포함한 VIEW 생성 --- title: "PIVOT" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/pivot-syntax/ language: kr kind: page --- # PIVOT `PIVOT`은 행(row) 방향 데이터를 열(column) 방향으로 변환하는 구문입니다. GROUP BY 집계 결과를 컬럼으로 재배열해 가독성 높은 리포트 형태로 표현할 때 사용합니다. > PIVOT 구문은 Machbase 5.6 버전부터 지원됩니다. ## 문법 ```sql SELECT * FROM (inline_view) PIVOT (aggregate_function(value_col) FOR category_col IN ('val1', 'val2', ...)) [WHERE ...] ``` - `inline_view`에서 PIVOT 절에 사용되지 않은 컬럼에 대해 GROUP BY를 수행합니다. - `FOR category_col IN (...)`: 피벗 기준 컬럼과 열로 변환할 값 목록을 지정합니다. - 결과 컬럼명은 IN 절에 지정한 문자열 값이 됩니다. ## 예시 ### 센서별 집계를 열로 변환 ```sql -- 인라인 뷰를 사용한 PIVOT SELECT * FROM ( SELECT regtime, tagid, dvalue FROM result_d WHERE regtime BETWEEN TO_DATE('2024-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND TO_DATE('2024-01-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS') ) PIVOT ( SUM(dvalue) FOR tagid IN ('FRONT_AXIS_TORQUE', 'REAR_AXIS_TORQUE', 'HOIST_AXIS_TORQUE', 'SLIDE_AXIS_TORQUE') ) WHERE FRONT_AXIS_TORQUE >= 40 AND REAR_AXIS_TORQUE >= 20; ``` ### CASE 문 대비 간결한 표현 ```sql -- PIVOT 없이 CASE 사용 SELECT regtime, SUM(CASE WHEN tagid = 'SENSOR_A' THEN dvalue ELSE 0 END) AS sensor_a, SUM(CASE WHEN tagid = 'SENSOR_B' THEN dvalue ELSE 0 END) AS sensor_b FROM result_d GROUP BY regtime; -- PIVOT으로 간결하게 표현 SELECT * FROM ( SELECT regtime, tagid, dvalue FROM result_d ) PIVOT (SUM(dvalue) FOR tagid IN ('SENSOR_A', 'SENSOR_B')); ``` ### TAG 테이블과 PIVOT 조합 ```sql SELECT * FROM ( SELECT name, time, value FROM sensor_tag WHERE time BETWEEN TO_DATE('2024-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND TO_DATE('2024-01-01 01:00:00', 'YYYY-MM-DD HH24:MI:SS') ) PIVOT ( AVG(value) FOR name IN ('sensor-01', 'sensor-02', 'sensor-03') ); ``` ## 제약사항 - PIVOT은 반드시 인라인 뷰(서브쿼리)와 함께 사용해야 합니다. - 인라인 뷰에서 PIVOT 집계 컬럼(`value_col`)과 기준 컬럼(`category_col`) 외의 모든 컬럼이 자동 GROUP BY 대상이 됩니다. - IN 절에 나열하는 값은 컴파일 시점에 확정된 리터럴이어야 합니다 (동적 컬럼 목록 불가). ## 관련 문서 - [SELECT hint syntax](../select-hint-syntax/) — SELECT 힌트 문법과 사용 예제 --- title: "window function / OVER" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/window-function-over-syntax/ language: kr kind: page --- # window function / OVER Machbase의 `LAG()`와 `LEAD()`는 조회 결과의 각 행에서 이전 또는 다음 행의 값을 참조합니다. 예를 들어 센서별 측정값을 시간순으로 비교해 직전 측정값과의 차이를 구할 수 있습니다. `GROUP BY` 집계와 달리 여러 행을 하나로 줄이지 않습니다. ## 문법 ```sql LAG(value_expression, offset) OVER ( [PARTITION BY partition_expression] [ORDER BY order_expression] ) LEAD(value_expression, offset) OVER ( [PARTITION BY partition_expression] [ORDER BY order_expression] ) ``` | 요소 | 설명 | |------|------| | `value_expression` | 이전 또는 다음 행에서 가져올 값 | | `offset` | 현재 행에서 떨어진 행 수. 1 이상의 정수를 지정 | | `PARTITION BY` | 비교할 행을 그룹으로 나누는 단일 식. 생략하면 전체 결과를 한 그룹으로 처리 | | `ORDER BY` | 그룹 안에서 비교 순서를 정하는 단일 식 | `OVER`는 필수이며, 괄호 안의 `PARTITION BY`와 `ORDER BY`는 생략할 수 있습니다. 시간순 비교에는 `ORDER BY time`처럼 기준을 명시하십시오. 정렬 기준 값이 같은 행 사이의 순서는 이 식만으로 구분할 수 없습니다. 최종 결과의 출력 순서가 필요하면 SELECT 문 끝에도 `ORDER BY`를 지정합니다. ## 지원 윈도우 함수 | 함수 | 설명 | |------|------| | `LAG(value, n)` | 같은 그룹에서 현재 행보다 n행 앞의 값 | | `LEAD(value, n)` | 같은 그룹에서 현재 행보다 n행 뒤의 값 | 참조할 행이 없으면 NULL을 반환합니다. `offset`은 시간 간격이 아니라 행 수입니다. 측정 간격이 불규칙하면 직전 행이 반드시 1초 전이나 1분 전 측정값인 것은 아닙니다. ### 다른 DBMS의 윈도우 문법과 구분 다음 문법을 Machbase의 `LAG`/`LEAD` 문법과 혼동하지 마십시오. - `ROW_NUMBER()`, `RANK()`, `DENSE_RANK()`, `FIRST_VALUE()`, `LAST_VALUE()`는 지원하지 않습니다. - `SUM(...) OVER (...)`, `AVG(...) OVER (...)` 등의 집계 윈도우 함수는 지원하지 않습니다. - `ROWS`/`RANGE` 프레임과 `UNBOUNDED PRECEDING`, `CURRENT ROW` 같은 프레임 경계를 지정할 수 없습니다. - `OVER` 안의 `PARTITION BY`와 `ORDER BY`에는 각각 하나의 식만 지정할 수 있습니다. `ORDER BY` 뒤에 `ASC`/`DESC`를 지정하는 문법도 지원하지 않습니다. 결과 행에 번호를 붙이는 `ROWNUM()`과 연속 구간 번호를 구하는 `SERIESNUM()`은 [윈도우/시리즈 함수](../../functions/series/)에서 설명합니다. ## 예시 ### LAG / LEAD: 이전/이후 값 비교 다음 예제는 `name`, `time`, `value` 컬럼이 있는 `sensor_tag` 테이블을 사용합니다. ```sql SELECT name, time, value, LAG(value, 1) OVER (PARTITION BY name ORDER BY time) AS prev_value, LEAD(value, 1) OVER (PARTITION BY name ORDER BY time) AS next_value, value - LAG(value, 1) OVER (PARTITION BY name ORDER BY time) AS delta FROM sensor_tag WHERE name = 'TEMP-01' AND time >= TO_DATE('2024-01-01', 'YYYY-MM-DD') ORDER BY name, time; ``` `prev_value`와 `next_value`는 조회 범위 안의 이전·다음 값을 나타냅니다. 첫 행의 `prev_value`와 마지막 행의 `next_value`는 NULL입니다. `WHERE`로 제외한 과거 행은 비교 대상에 포함되지 않으므로 첫 행의 차이까지 필요하면 조회 범위를 앞쪽으로 넓힙니다. ### 집계 결과의 이전 값 비교 태그별 시간 구간을 먼저 집계한 뒤 집계값 사이의 변화를 비교할 수 있습니다. 다음 예제는 시간 단위 ROLLUP을 조회할 수 있는 `sensor_tag` 테이블을 전제로 합니다. ```sql SELECT name, bucket, avg_val, LAG(avg_val, 1) OVER (PARTITION BY name ORDER BY bucket) AS prev_avg FROM ( SELECT name, rollup('hour', 1, time) AS bucket, AVG(value) AS avg_val FROM sensor_tag WHERE time BETWEEN TO_DATE('2024-01-01', 'YYYY-MM-DD') AND TO_DATE('2024-07-01', 'YYYY-MM-DD') GROUP BY name, bucket ) t ORDER BY name, bucket; ``` 이 쿼리는 구간별 평균과 직전 구간의 평균을 비교합니다. 이동 평균이나 누적 합계를 계산하는 쿼리는 아닙니다. 데이터가 없는 구간은 자동으로 채워지지 않습니다. ## 성능 주의사항 윈도우 계산에는 그룹 구분, 정렬과 이전·다음 행의 값을 유지하는 작업이 필요합니다. 시간 범위와 태그 조건으로 대상 행을 줄이고, 장기 추세 비교는 집계 결과에 적용하면 처리해야 할 행 수를 줄일 수 있습니다. `LAG`/`LEAD`는 SELECT 결과 식에서 사용합니다. `WHERE`, `HAVING`, `GROUP BY`, `ORDER BY`, JOIN의 `ON` 조건에 직접 호출하지 마십시오. 계산한 값으로 필터링하려면 인라인 뷰의 결과 컬럼을 바깥 SELECT에서 참조합니다. ## 관련 문서 - [윈도우/시리즈 함수](../../functions/series/) — `ROWNUM()`과 `SERIESNUM()` - [PIVOT syntax](../pivot-syntax/) — 행을 열로 변환 - [SERIES BY syntax](../series-syntax/) — 연속 구간 추출 --- title: "SERIES BY" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/series-syntax/ language: kr kind: page --- # SERIES BY `SERIES BY` 절은 정렬된 결과 집합에서 특정 조건을 만족하는 연속적인 행의 구간(series)을 추출합니다. 연속된 구간에서 시작/종료 시각과 패턴을 분석할 때 사용합니다. ## 문법 ```sql SELECT ... FROM table_name [WHERE ...] ORDER BY col [ASC | DESC] SERIES BY condition_expr ``` - `ORDER BY` 절이 없으면 `_ARRIVAL_TIME` 컬럼 기준으로 정렬됩니다. - `GROUP BY`를 사용하거나 `_ARRIVAL_TIME` 컬럼이 없는 VOLATILE / LOOKUP 테이블에서는 반드시 `ORDER BY`를 명시해야 합니다. - `SERIES BY` 조건을 연속으로 만족하는 같은 구간의 행들은 동일한 `SERIESNUM()` 반환값을 가집니다. ## 예시 ### 기본 사용 ```sql CREATE LOG TABLE t1 (c1 INTEGER, c2 INTEGER); INSERT INTO t1 VALUES (0, 1); INSERT INTO t1 VALUES (1, 2); INSERT INTO t1 VALUES (2, 3); INSERT INTO t1 VALUES (3, 2); INSERT INTO t1 VALUES (4, 1); INSERT INTO t1 VALUES (5, 2); INSERT INTO t1 VALUES (6, 3); INSERT INTO t1 VALUES (7, 1); SELECT c1, c2 FROM t1 ORDER BY c1 SERIES BY c2 > 1; ``` 결과: ``` C1 C2 --------------------------- 1 2 2 3 3 2 5 2 6 3 ``` ### SERIESNUM()으로 구간 번호 확인 ```sql SELECT c1, c2, SERIESNUM() AS grp FROM t1 ORDER BY c1 SERIES BY c2 > 1; ``` 결과: ``` C1 C2 GRP ----------------------------------- 1 2 1 2 3 1 3 2 1 5 2 2 6 3 2 ``` ### TAG 테이블에서 연속 구간 분석 내부 쿼리에서 연속 구간별 번호를 생성하고, 외부 쿼리에서 구간 번호를 기준으로 집계합니다. ```sql -- 100을 초과하는 연속 구간별 시작/끝 시각과 최댓값 조회 SELECT MIN(time) AS start_time, MAX(time) AS end_time, MAX(value) AS peak_value, series_id FROM ( SELECT time, value, SERIESNUM() AS series_id FROM tag WHERE name = 'PRESSURE-01' AND time >= TO_DATE('2024-01-01', 'YYYY-MM-DD') ORDER BY time SERIES BY value > 100.0 ) GROUP BY series_id ORDER BY series_id; ``` ## 관련 문서 - [SELECT hint syntax](../select-hint-syntax/) — SELECT 힌트 문법과 사용 예제 - [window function / OVER syntax](../window-function-over-syntax/) — 윈도우 기반 분석과의 차이 --- title: "SAVE DATA INTO" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/save-data-into-syntax/ language: kr kind: page --- # SAVE DATA INTO `SAVE DATA INTO`는 `SELECT` 쿼리 결과를 CSV 파일로 저장하는 구문입니다. ## 문법 ```sql SAVE DATA INTO 'file_path' [HEADER { ON | OFF }] [{ FIELDS | COLUMNS } [TERMINATED BY 'char'] [ENCLOSED BY 'char'] ] [ENCODED BY coding_name] AS select_query ``` ## 옵션 | 옵션 | 기본값 | 설명 | |------|--------|------| | `HEADER { ON \| OFF }` | OFF | 첫 줄에 컬럼명 출력 여부 | | `TERMINATED BY 'char'` | `,` | 필드 구분자 | | `ENCLOSED BY 'char'` | `"` | 필드 인용 문자 | | `ENCODED BY coding_name` | UTF8 | 출력 파일 인코딩 | 지원 인코딩: `UTF8`, `MS949`, `KSC5601`, `EUCJP`, `SHIFTJIS`, `BIG5`, `GB231280` ## 예시 ```sql -- 기본 CSV 저장 SAVE DATA INTO '/tmp/result.csv' AS SELECT * FROM sensor_log; -- 헤더 포함, 세미콜론 구분자 SAVE DATA INTO '/tmp/output.csv' HEADER ON FIELDS TERMINATED BY ';' AS SELECT name, time, value FROM sensor_log WHERE time > TO_DATE('2024-01-01', 'YYYY-MM-DD'); -- 특수 구분자와 인용 문자 지정 SAVE DATA INTO '/tmp/export.csv' HEADER ON FIELDS TERMINATED BY ';' ENCLOSED BY '\'' ENCODED BY MS949 AS SELECT * FROM t1 WHERE i1 > 100; -- TAG 테이블 데이터 내보내기 SAVE DATA INTO '/tmp/tag_export.csv' HEADER ON AS SELECT name, time, value FROM sensor_tag WHERE name = 'TEMP-01' AND time BETWEEN TO_DATE('2024-01-01', 'YYYY-MM-DD') AND TO_DATE('2024-01-02', 'YYYY-MM-DD') ORDER BY time; ``` ## 주의사항 - 파일 경로는 Machbase 서버 프로세스가 쓰기 가능한 경로여야 합니다. - 출력 경로에 파일이 이미 존재하면 오류가 발생하며, 기존 파일은 변경되지 않습니다. 다른 파일명을 지정하거나 기존 파일을 이동한 후 다시 실행하십시오. - SELECT 결과가 없는 경우 빈 파일(헤더만)이 생성될 수 있습니다. - 파일 경로에 접근 권한이 없으면 오류가 반환됩니다. ## 관련 문서 - [LOAD DATA INFILE syntax](../load-data-infile-syntax/) — 파일에서 테이블로 데이터 입력 --- title: "DDL" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/ddl-syntax/ language: kr kind: page --- # DDL DDL(Data Definition Language)은 테이블, 인덱스, 뷰, 롤업 등 데이터베이스 객체를 생성·수정·삭제하는 구문입니다. > **권한**: 일반 사용자가 active database에서 DDL을 실행하려면 `GRANT DDL ON DATABASE database_name TO user_name;` 또는 `GRANT CREATE ON DATABASE database_name TO user_name;`이 필요합니다. 자세한 내용은 [GRANT/REVOKE](../user-auth-syntax/#grant-revoke)를 참고하십시오. ## CREATE TABLE ```sql create_table_stmt ::= 'CREATE' table_type? 'TABLE' ['IF NOT EXISTS'] table_name '(' column_def ( ',' column_def )* ')' [ 'METADATA' '(' column_def ( ',' column_def )* ')' ] [ table_property_list ] [ 'TABLESPACE' tablespace_name ] [ 'WITH ROLLUP' rollup_interval_spec ] table_type ::= 'LOG' | 'TAG' | 'VOLATILE' | 'LOOKUP' | 'TRANSACTION' | 'TXN' -- table_type을 생략하면 TRANSACTION 테이블이 생성됩니다. column_def ::= column_name column_type [ 'PRIMARY KEY' ] [ 'NOT NULL' ] [ column_axis ] [ 'SUMMARIZED' ] [ 'DEFAULT' value ] [ 'PROPERTY' '(' column_property_list ')' ] decimal_type ::= ( 'DECIMAL' | 'NUMERIC' | 'DEC' | 'FIXED' | 'NUMBER' ) [ '(' precision [ ',' scale ] ')' ] array_type ::= ( 'SHORT' | 'INT16' | 'USHORT' | 'UINT16' | 'INTEGER' | 'INT' | 'INT32' | 'UINTEGER' | 'UINT32' | 'LONG' | 'INT64' | 'ULONG' | 'UINT64' | 'FLOAT' | 'DOUBLE' | decimal_type ) '[' cardinality ']' column_axis ::= 'BASETIME' | 'BASE TIME' | 'BASE DISTANCE' | 'BASEDISTANCE' column_property_list ::= ( 'MINMAX_CACHE_SIZE' '=' number | 'PART_PAGE_COUNT' '=' number | 'PAGE_VALUE_COUNT' '=' number | 'MAX_CACHE_PART_COUNT' '=' number | 'SEQUENCE' '=' number ) ( ',' column_property_list )* table_property_list ::= ( 'TAG_PARTITION_COUNT' '=' number | 'TAG_DATA_PART_SIZE' '=' number | 'TAG_STAT_ENABLE' '=' ( '0' | '1' ) | 'TAG_DUPLICATE_CHECK_DURATION' '=' number | 'VARCHAR_FIXED_LENGTH_MAX' '=' number ) ( ',' table_property_list )* ``` ### 테이블 유형 | 키워드 | 설명 | |--------|------| | (없음) | **TRANSACTION 테이블** - 관계형 데이터와 트랜잭션 지원 | | `LOG` | **LOG 테이블** - 시계열 로그 데이터. 추가(INSERT) 중심, 일반 UPDATE 불가 | | `TAG` | **TAG 테이블** - 태그 이름/시간/값 구조의 시계열 데이터. BASETIME 컬럼 필수 | | `LOOKUP` | **LOOKUP 테이블** - 메모리 상주. PRIMARY KEY 필수. DML 전체 지원 | | `VOLATILE` | **VOLATILE 테이블** - 메모리 상주. 서버 재시작 시 데이터 소멸. PRIMARY KEY 선택 | | `TRANSACTION`, `TXN` | **TRANSACTION 테이블** - 전체 이름과 축약형은 같은 테이블을 생성 | 무수식 `CREATE TABLE`, `CREATE TRANSACTION TABLE`, `CREATE TXN TABLE`은 모두 TRANSACTION 테이블을 생성합니다. LOG 테이블을 만들 때는 `CREATE LOG TABLE`을 사용합니다. 이전 공개 명칭인 `RDB`와 `TRX`는 테이블 유형 별칭으로 지원하지 않습니다. TRANSACTION 테이블은 Standard Edition 전용이므로 Cluster Edition에서는 세 TRANSACTION 생성 문법이 모두 거부됩니다. `DECIMAL`은 모든 테이블 유형에서 사용할 수 있습니다. precision은 `1~65`, scale은 `0~30`이며 scale은 precision보다 클 수 없습니다. 자세한 내용은 [DECIMAL과 NUMERIC 고정소수점 타입](/dbms/reference/sql/types/decimal-numeric-fixed-point/)을 참고하십시오. Machbase DBMS 8.7.0의 `ARRAY`는 숫자 요소 타입 뒤에 `1..1024` 범위의 cardinality를 지정합니다. 지원 타입과 테이블별 제약은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ### 예시 ```sql -- LOG 테이블 생성: LOG 키워드를 명시합니다. CREATE LOG TABLE sensor_log ( id INTEGER, name VARCHAR(64), value DOUBLE, status VARCHAR(20) ); -- TAG 테이블 생성 (BASETIME 필수, SUMMARIZED는 롤업 대상 컬럼에 지정) CREATE TAG TABLE tag ( name VARCHAR(40) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); -- TAG 테이블 + 메타데이터 + 프로퍼티 CREATE TAG TABLE sensors ( name VARCHAR(40) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ) METADATA ( location VARCHAR(100), unit VARCHAR(20) ) TAG_PARTITION_COUNT = 4; -- LOOKUP 테이블 (PRIMARY KEY 필수) CREATE LOOKUP TABLE devices ( device_id VARCHAR(40) PRIMARY KEY, ip IPV4, status VARCHAR(20) ); -- VOLATILE 테이블 CREATE VOLATILE TABLE cache_data ( id INTEGER PRIMARY KEY, value DOUBLE ); -- TRANSACTION 테이블의 exact fixed-point 컬럼 CREATE TRANSACTION TABLE invoice ( id LONG PRIMARY KEY, amount DECIMAL(18,2), tax NUMERIC(18,4) ); -- IF NOT EXISTS 사용 CREATE TAG TABLE IF NOT EXISTS tag ( name VARCHAR(40) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); -- NOT NULL 제약 조건 CREATE TABLE t1 ( c1 INTEGER NOT NULL, c2 VARCHAR(200) ); ``` ### 사전 정의 시스템 컬럼 시스템 컬럼은 다음과 같이 제공됩니다. | 컬럼 | 타입 | 설명 | |------|------|------| | `_ARRIVAL_TIME` | DATETIME | LOG 테이블에서만 제공. 레코드가 삽입된 시각이며 `DURATION` 조회 기준 | | `_RID` | LONG | LOG 테이블과 TAG 테이블의 내부 데이터 테이블에서 제공. 레코드 고유 식별자이며 사용자가 직접 지정 불가 | --- ## DROP TABLE ```sql drop_table_stmt ::= 'DROP TABLE' table_name ``` 지정한 테이블과 해당 테이블의 모든 데이터 및 인덱스를 삭제합니다. 다른 세션에서 해당 테이블을 조회 중이면 오류가 발생합니다. ```sql DROP TABLE sensor_log; ``` --- ## ALTER TABLE `ALTER TABLE`은 테이블의 스키마를 변경합니다. 사용할 수 있는 하위 구문은 테이블 타입에 따라 다릅니다. TRANSACTION 테이블은 `ADD COLUMN`, `DROP COLUMN`, `RENAME COLUMN`, `RENAME TO`를 지원합니다. TAG 메타데이터 컬럼은 `METADATA ADD COLUMN`과 `METADATA DROP COLUMN`을 사용합니다. ### ADD COLUMN ```sql alter_table_add_stmt ::= 'ALTER TABLE' table_name [ 'METADATA' ] 'ADD COLUMN' '(' column_name column_type [ 'DEFAULT' value ] ')' ``` ```sql -- 컬럼 추가 ALTER TABLE sensor_log ADD COLUMN (quality FLOAT); -- TRANSACTION 컬럼 추가 ALTER TABLE product_master ADD COLUMN (stock_qty INTEGER DEFAULT 0); -- 기본값과 함께 추가 ALTER TABLE sensor_log ADD COLUMN (flag INTEGER DEFAULT 0); ALTER TABLE sensor_log ADD COLUMN (tag_ip IPV4 DEFAULT '192.168.0.1'); -- ARRAY 컬럼과 DEFAULT 추가 ALTER TABLE sensor_log ADD COLUMN (channels INT32[3] DEFAULT [1, NULL, 3]); -- TAG METADATA ARRAY 컬럼 추가 ALTER TABLE sensor_tag METADATA ADD COLUMN (limits DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]); ``` `DECIMAL(p)[n]` ARRAY에서 scale을 생략하면 0으로 처리합니다. ARRAY DEFAULT의 요소 수는 선언 cardinality와 정확히 같아야 합니다. 잘못된 요소 타입, cardinality, precision, scale, 중첩 또는 다차원 선언과 길이가 다른 DEFAULT는 컬럼을 일부 생성하지 않고 문장 전체를 실패시킵니다. #### ARRAY ADD COLUMN 지원 범위 | Edition | 테이블 또는 컬럼 영역 | 지원 | 기존 row의 명시적 DEFAULT | |---|---|:---:|---| | Standard | LOG | O | 적용 | | Standard | VOLATILE | O | 적용하지 않고 whole NULL 유지 | | Standard | LOOKUP | O | 적용 | | Standard | TRANSACTION | O | 적용 | | Standard | TAG METADATA | O | 적용 | | Standard | TAG DATA 일반 컬럼 | X | - | | Cluster | LOG | O | 적용 | | Cluster | 그 외 테이블 또는 TAG METADATA | X | - | DEFAULT가 없으면 지원되는 모든 테이블에서 ALTER 전에 존재한 row의 새 ARRAY 컬럼은 whole NULL입니다. TAG DATA 일반 ARRAY 컬럼은 `CREATE TABLE`에서 선언할 수 있지만 ALTER로 추가할 수 없습니다. 타입과 NULL 계약은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ### DROP COLUMN ```sql alter_table_drop_stmt ::= 'ALTER TABLE' table_name [ 'METADATA' ] 'DROP COLUMN' '(' column_name ')' ``` ```sql ALTER TABLE sensor_log DROP COLUMN (quality); ALTER TABLE product_master DROP COLUMN (stock_qty); ALTER TABLE sensor_log DROP COLUMN (channels); ALTER TABLE sensor_tag METADATA DROP COLUMN (limits); ``` ### RENAME COLUMN ```sql alter_table_column_rename_stmt ::= 'ALTER TABLE' table_name 'RENAME COLUMN' old_column_name 'TO' new_column_name ``` ```sql ALTER TABLE sensor_log RENAME COLUMN status TO device_status; ALTER TABLE product_master RENAME COLUMN name TO product_name; ``` ### MODIFY COLUMN ```sql alter_table_modify_stmt ::= 'ALTER TABLE' table_name 'MODIFY COLUMN' ( '(' column_name 'VARCHAR' '(' new_size ')' ')' | column_name ( 'NOT NULL' [ 'NOCHECK' ] | 'NULL' | 'SET' 'MINMAX_CACHE_SIZE' '=' value ) ) ``` 아래 길이 확장과 MINMAX 예제는 LOG 테이블 대상입니다. 기존 VARCHAR의 길이를 늘릴 수 있지만 줄이거나 다른 타입을 VARCHAR로 바꿀 수는 없습니다. LOG의 새 길이는 최대 32,767바이트입니다. MINMAX_CACHE_SIZE는 LOG의 지원되는 고정 길이 컬럼에 적용하며, VARCHAR·TEXT 같은 가변 길이 컬럼에는 적용할 수 없습니다. LOG에서 옵션 없는 NOT NULL은 기존 행도 검사합니다. NOCHECK는 그 검사를 생략할 뿐, 기존 NULL을 채워 주는 옵션은 아닙니다. NULL은 해당 제약을 해제합니다. TAG에는 이 범위를 일괄 적용하지 말고 [TAG 컬럼 변경](/dbms/tag-table-usage/create-alter-drop/)의 별도 제약을 확인하세요. TRANSACTION 테이블은 `MODIFY COLUMN`을 지원하지 않습니다. ```sql -- VARCHAR 길이 확장 (줄이기 불가) ALTER TABLE sensor_log MODIFY COLUMN (name VARCHAR(128)); -- NOT NULL 추가 ALTER TABLE sensor_log MODIFY COLUMN id NOT NULL; -- NOT NULL 해제 ALTER TABLE sensor_log MODIFY COLUMN id NULL; -- MINMAX_CACHE_SIZE 변경 ALTER TABLE sensor_log MODIFY COLUMN id SET MINMAX_CACHE_SIZE = 10240; ``` ### RENAME TO ```sql alter_table_rename_stmt ::= 'ALTER TABLE' table_name 'RENAME TO' new_name ``` ```sql -- TRANSACTION 테이블에서 지원 ALTER TABLE product_master RENAME TO product_catalog; ``` ### ADD / DROP RETENTION Retention 연결과 해제 문법은 [RETENTION syntax](../retention-syntax/)를 참고하십시오. --- ## TRUNCATE TABLE ```sql truncate_table_stmt ::= 'TRUNCATE TABLE' table_name ``` 테이블의 모든 데이터를 삭제합니다. 다른 세션에서 해당 테이블을 조회 중이면 오류가 발생합니다. ```sql TRUNCATE TABLE sensor_log; ``` --- ## CREATE INDEX 인덱스 타입, 테이블별 지원 범위, JSON path와 속성은 [INDEX syntax](../index-syntax/)를 참고하십시오. --- ## DROP INDEX 삭제 문법과 제약은 [INDEX syntax](../index-syntax/#drop-index)를 참고하십시오. --- ## CREATE TABLESPACE ```sql create_tablespace_stmt ::= 'CREATE TABLESPACE' tablespace_name 'DATADISK' datadisk_list datadisk_list ::= data_disk ( ',' data_disk )* data_disk ::= disk_name '(' 'DISK_PATH' '=' '"' path '"' [ ',' 'PARALLEL_IO' '=' number ] ')' ``` ```sql -- 단일 디스크 테이블스페이스 CREATE TABLESPACE tbs1 DATADISK disk1 (DISK_PATH="tbs1_disk1"); -- 병렬 I/O 설정 CREATE TABLESPACE tbs2 DATADISK disk1 (DISK_PATH="tbs2_disk1", PARALLEL_IO = 5); -- 다중 디스크 CREATE TABLESPACE tbs3 DATADISK disk1 (DISK_PATH="tbs3_d1", PARALLEL_IO = 10), disk2 (DISK_PATH="tbs3_d2"), disk3 (DISK_PATH="tbs3_d3"); ``` --- ## DROP TABLESPACE ```sql drop_tablespace_stmt ::= 'DROP TABLESPACE' tablespace_name ``` ```sql DROP TABLESPACE tbs1; ``` 테이블스페이스에 생성된 객체가 있으면 삭제할 수 없습니다. --- ## CREATE ROLLUP 기본, 조건부, custom ROLLUP 문법은 [ROLLUP syntax](../rollup-syntax/)를 참고하십시오. --- ## DROP ROLLUP 삭제 문법은 [ROLLUP syntax](../rollup-syntax/#drop-rollup)를 참고하십시오. --- ## ALTER ROLLUP 시작, 중지, 강제 실행과 주기 변경은 [ROLLUP syntax](../rollup-syntax/#alter-rollup)를 참고하십시오. --- ## CREATE RETENTION 생성 문법과 테이블 연결은 [RETENTION syntax](../retention-syntax/)를 참고하십시오. --- ## DROP RETENTION 삭제 문법과 연결 해제 순서는 [RETENTION syntax](../retention-syntax/#drop-retention)를 참고하십시오. --- ## DDL 동시성과 잠금 {#ddl-concurrency} Machbase 8.7.0 Standard Edition은 서로 독립적인 객체의 DDL을 객체 단위로 조정합니다. 따라서 같은 데이터베이스에서 서로 다른 이름의 LOG, TAG, VOLATILE, LOOKUP, TRANSACTION 테이블을 생성하거나 변경하는 DDL은 동시에 진행될 수 있습니다. | Edition | 독립 객체의 DDL | 충돌 범위 | 충돌 시 대기 설정 | |---------|-----------------|-----------|-------------------| | Standard | 동시에 진행 가능 | 동일 객체와 직접 관련된 객체 | `DDL_LOCK_TIMEOUT` | | Cluster | 기존 정책에 따라 직렬화 | 카탈로그 범위 | `DDL_LOCK_TIMEOUT`을 제공하지 않음 | 독립 객체의 DDL이 동시에 시작되더라도 메타데이터 처리나 스토리지 I/O 같은 공통 작업을 공유할 수 있습니다. 따라서 클라이언트 수에 비례한 처리량 향상이나 모든 DDL의 동시 완료를 보장하지는 않습니다. ### 충돌하는 객체 | 동시 실행 상황 | 동작 | |----------------|------| | 이름이 서로 다른 독립 테이블 | 테이블 유형과 관계없이 동시에 진행할 수 있음 | | 동일 객체 또는 동일 이름의 객체 | 한 DDL만 진행하고 다른 DDL은 대기하거나 오류 반환 | | 테이블 변경·삭제 DDL과 해당 테이블의 인덱스 DDL | 서로 관련된 객체로 처리 | | 뷰 DDL과 뷰가 참조하는 테이블의 변경·삭제 DDL | 서로 관련된 객체로 처리 | | TAG 테이블 변경·삭제 DDL과 해당 Rollup 또는 Retention DDL | 서로 관련된 객체로 처리 | | `DROP VIEW`, `CREATE OR REPLACE VIEW`, 시스템 범위 DDL | 더 넓은 범위에서 직렬화될 수 있음 | 테이블 유형이 달라도 같은 테이블 이름은 하나의 이름 공간을 사용합니다. 예를 들어 같은 이름으로 LOG 테이블과 TAG 테이블을 동시에 생성하면 둘 중 하나만 생성됩니다. ### DDL 잠금 대기 시간 Standard Edition에서는 `DDL_LOCK_TIMEOUT`으로 충돌한 DDL 잠금을 기다릴 시간을 초 단위로 설정합니다. | 값 | 동작 | |---:|------| | `0` | 기다리지 않고 즉시 `ERR-02031: Resource busy ()` 반환 | | 양수 | 지정한 시간까지 기다린 후 잠금을 얻지 못하면 `ERR-02031` 반환 | 오류 메시지의 괄호에는 대표 충돌 객체가 표시됩니다. 넓은 범위에서 충돌한 DDL은 객체 이름 대신 `DDL`로 표시될 수 있습니다. 기본값은 `0`이고 설정 범위는 `0`~`1000000`입니다. 현재 세션의 값을 변경하려면 다음 문을 실행합니다. ```sql ALTER SESSION SET DDL_LOCK_TIMEOUT = 10; ``` 하나의 DDL이 여러 잠금 단계를 거치더라도 대기 시간은 단계마다 다시 시작되지 않습니다. 잠금을 얻은 뒤에는 객체와 의존 관계를 다시 확인하므로, 선행 DDL의 결과에 따라 `already exists`, `table not found` 같은 일반 SQL 오류가 반환될 수 있습니다. `DDL_LOCK_TIMEOUT`은 DDL 잠금을 기다리는 시간만 제한하며 SQL 전체 실행 시간을 제한하지 않습니다. 실행 중인 DDL은 시작 시점의 값을 계속 사용하고, `ALTER SESSION`으로 변경한 값은 다음 DDL부터 적용됩니다. DDL의 커밋과 복구 동작은 이전 버전과 동일하며 암시적 커밋을 새로 수행하지 않습니다. | 설정 | 단위 | 제한 대상 | |------|------|-----------| | `DDL_LOCK_TIMEOUT` | 초 | Standard Edition의 DDL 잠금 대기 | | `SESSION_QUERY_TIMEOUT_SEC` / `QUERY_TIMEOUT` | 초 | 쿼리 실행 및 응답 대기 | | `TRANSACTION_BUSY_TIMEOUT_MS` | 밀리초 | TRANSACTION 테이블의 동시 쓰기 충돌 대기 | --- ## 관련 문서 - [테이블 유형](/dbms/data-modeling-table-design/) - LOG, TAG, LOOKUP, VOLATILE, TRANSACTION 테이블 특성 및 사용 가이드 - [TAG 테이블 롤업](/dbms/tag-table-usage/create-alter-drop/#original-85-creating-tag-tables) - 롤업 생성 및 운영 가이드 - [GRANT/REVOKE](../user-auth-syntax/#grant-revoke) - DDL 실행에 필요한 권한 부여 - [ALTER SESSION](../system-session-alter-syntax/#alter-session) - 현재 세션의 DDL 잠금 대기 시간 설정 - [스키마 변경 체크리스트](/dbms/operations-configuration-recovery/checklist-schema-alter/) - 운영 중 DDL 실행과 충돌 대응 --- title: "DML" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/dml-syntax/ language: kr kind: section --- # DML DML(Data Manipulation Language)은 테이블에 데이터를 삽입·수정·삭제하는 구문입니다. ## 테이블 유형별 DML 지원 | 구문 | LOG | TAG | LOOKUP | VOLATILE | TRANSACTION | |------|:---:|:---:|:------:|:--------:|:---:| | INSERT | O | O | O | O | O | | INSERT SELECT | O | O | O | O | O | | UPDATE | - | O(태그/축 조건 또는 메타데이터) | O(일반 조건식) | O(PK 조건) | O | | DELETE | O(보존 조건/전체) | O(시간/이름 조건) | O(일반 조건식/전체) | O(PK 조건) | O | | DELETE WHERE | - | O(태그/축 조건) | O(일반 조건식) | O(PK equality) | O | | TRUNCATE | O | - | - | - | O | > LOG 테이블의 UPDATE는 지원하지 않습니다. 데이터 수정이 필요하면 LOOKUP 또는 VOLATILE 테이블을 사용하거나 TRANSACTION 테이블을 선택하십시오. --- ## INSERT INTO ```sql insert_stmt ::= 'INSERT INTO' table_name [ 'METADATA' ] [ '(' insert_column_list ')' ] 'VALUES' '(' value_list ')' [ 'ON DUPLICATE KEY UPDATE' [ 'SET' set_list ] ] insert_column_list ::= insert_target ( ',' insert_target )* insert_target ::= column_name | array_column_name '[' position ']' value_list ::= value ( ',' value )* set_list ::= column_name '=' value ( ',' column_name '=' value )* ``` 지정하지 않은 컬럼에는 NULL이 입력됩니다. `METADATA`는 TAG 테이블의 메타데이터 컬럼에 삽입할 때 사용합니다. ```sql -- 기본 삽입 INSERT INTO sensor_log VALUES (1, 'sensor-01', 23.5, 'OK'); -- 컬럼 지정 삽입 INSERT INTO sensor_log (name, value) VALUES ('sensor-01', 23.5); -- TAG 테이블 메타데이터 삽입 INSERT INTO sensors METADATA (name, location, unit) VALUES ('sensor-01', 'building-A', 'celsius'); ``` ### ARRAY element target Machbase DBMS 8.7.0에서는 `INSERT ... VALUES`의 컬럼 목록에 고정 길이 `ARRAY`의 위치를 지정할 수 있습니다. position은 0부터 시작합니다. 지정하지 않은 요소는 element NULL로 저장됩니다. ```sql CREATE LOG TABLE array_input ( id INTEGER, channels INT32[4] ); INSERT INTO array_input (id, channels[0], channels[3]) VALUES (1, 10, 40); ``` 같은 문장에서 whole ARRAY target과 element target을 함께 사용하거나 같은 위치를 두 번 지정할 수 없습니다. scalar 컬럼과 범위 밖 position도 element target으로 사용할 수 없습니다. indexed target은 `INSERT ... SELECT`와 `UPDATE SET`에서는 지원하지 않습니다. ARRAY 값 생성, sparse 입력과 Append 선택 target은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)과 [Sparse ARRAY와 선택 컬럼 Append API](/dbms/development-tools-integration/data-input-load-export/array-append/)를 참고하십시오. ### ON DUPLICATE KEY UPDATE `INSERT ... VALUES`에서 duplicate key가 발생하면 기존 행을 UPDATE합니다. `INSERT ... SELECT`와 결합할 수 없습니다. | 테이블 타입 | duplicate 판정 key | 지원 범위 | |---|---|---| | TRANSACTION | PRIMARY KEY, 단일·복합 UNIQUE INDEX | Standard Edition | | LOOKUP | PRIMARY KEY | 지원 | | VOLATILE | PRIMARY KEY | 지원 | | TAG METADATA | tag name PRIMARY KEY | 지원 | | TAG data, LOG | - | 미지원 | `SET`을 생략하면 INSERT 입력값으로 기존 non-key 컬럼을 갱신합니다. `SET`이 있으면 오른쪽 표현식은 duplicate 기존 행을 기준으로 평가합니다. LOOKUP·VOLATILE·TRANSACTION에서는 PRIMARY KEY 자체를 갱신할 수 없습니다. TAG METADATA의 tag name과 시스템 관리 컬럼은 [TAG 메타데이터](/dbms/tag-table-usage/tag-metadata/)의 변경 규칙을 따릅니다. ```sql -- 키 중복 시 value 컬럼만 업데이트 INSERT INTO devices (device_id, ip, status) VALUES ('dev-001', '192.168.1.1', 'ONLINE') ON DUPLICATE KEY UPDATE SET status = 'ONLINE'; -- SET 절 없이 사용하면 모든 컬럼을 삽입 값으로 업데이트 INSERT INTO devices (device_id, ip, status) VALUES ('dev-001', '192.168.1.2', 'ONLINE') ON DUPLICATE KEY UPDATE; ``` duplicate를 판정할 key가 없는 table에서의 UPSERT, key 변경, `VALUES(col)`·`EXCLUDED.col` 같은 다른 DBMS 전용 표현은 오류입니다. TRANSACTION의 UNIQUE 충돌·transaction 예제는 [TRANSACTION upsert](/dbms/rdb-table-usage/insert-on-duplicate-key-update/)를 참고하십시오. --- ## INSERT SELECT ```sql insert_select_stmt ::= 'INSERT INTO' table_name [ '(' insert_column_list ')' ] [ with_clause ] select_stmt ``` SELECT 결과를 테이블에 삽입합니다. Standard Edition에서는 대상 테이블과 컬럼 목록 뒤에 `WITH` 절을 둘 수 있습니다. 문장 선두의 `WITH ... INSERT INTO ...` 형식은 지원하지 않습니다. ```sql -- 조회 결과를 다른 테이블에 복사 INSERT INTO sensor_log_copy SELECT * FROM sensor_log; -- _arrival_time 명시 삽입 (시간 순서 보장 필요) INSERT INTO sensor_log_copy (_arrival_time, id, name, value) SELECT _arrival_time, id, name, value FROM sensor_log ORDER BY _arrival_time; -- CTE 결과 삽입 INSERT INTO sensor_log_copy (id, name, value) WITH filtered AS ( SELECT id, name, value FROM sensor_log WHERE value >= 80 ) SELECT id, name, value FROM filtered; ``` 주의사항: - `_ARRIVAL_TIME`을 명시하지 않으면 INSERT 실행 시점의 시간이 자동 입력됩니다. - LOG의 명시 시각 복사는 빈 대상에 오름차순으로 입력하고, 다른 입력 작업과 분리하세요. 대상에 더 최신 시각이 있으면 원본을 정렬했더라도 역전 입력이 됩니다. 기본 `DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE=1`에서는 역전된 값을 직전 저장 시각+1ns로 보정하고, 0에서는 거부합니다. 따라서 명시 입력이 원본 시각 보존을 무조건 보장하지는 않습니다. - VARCHAR 컬럼에서 삽입 값이 최대 길이를 초과하면 자동으로 잘라서 입력됩니다. - LOG/TAG 입력은 TRANSACTION 테이블 트랜잭션의 ROLLBACK 대상이 아닙니다. --- ## UPDATE ```sql update_stmt ::= 'UPDATE' table_name [ 'METADATA' ] 'SET' update_expr_list [ 'WHERE' predicate ] update_expr_list ::= column_name '=' value ( ',' column_name '=' value )* ``` TRANSACTION 테이블은 WHERE 절을 생략하면 모든 행을 수정합니다. LOOKUP 테이블은 기본 키 또는 일반 조건식을 사용하고, VOLATILE 테이블은 기본 키 일치 조건을 사용합니다. TAG data UPDATE는 태그 선택자와 시간축 조건을 함께 사용합니다. ```sql -- LOOKUP 테이블 레코드 수정 UPDATE devices SET status = 'OFFLINE' WHERE device_id = 'dev-001'; -- 여러 컬럼 동시 수정 UPDATE devices SET ip = '10.0.0.1', status = 'ONLINE' WHERE device_id = 'dev-002'; -- LOOKUP 일반 조건식으로 여러 행 수정 UPDATE devices SET status = 'OFFLINE' WHERE site = 'SEOUL' AND status = 'READY'; ``` ### TAG data UPDATE TAG 테이블의 실제 시계열 데이터는 태그 선택 조건과 BASETIME 조건을 함께 지정해 수정합니다. ```sql UPDATE sensors SET value = 101, status = 1 WHERE name = 'sensor-01' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); ``` `name`(PRIMARY KEY), `time`(BASETIME), 메타데이터 컬럼은 data UPDATE의 SET 대상이 아닙니다. ### UPDATE METADATA (TAG 테이블) TAG 테이블의 메타데이터 컬럼은 별도 `UPDATE ... METADATA` 구문으로 수정합니다. ```sql -- 메타데이터 조건으로 여러 행 수정 UPDATE sensors METADATA SET status = 'DONE' WHERE status = 'READY'; -- tag name 기준으로 수정 UPDATE sensors METADATA SET location = 'building-B' WHERE name = 'sensor-01'; ``` --- ## DELETE ```sql -- LOG 테이블용 삭제 (시간/행 수 기반) delete_stmt ::= 'DELETE FROM' table_name [ 'OLDEST' number 'ROWS' | 'EXCEPT' number ( 'ROWS' | time_unit ) | 'BEFORE' datetime_expression ] [ 'NO WAIT' ] time_unit ::= 'YEAR' | 'MONTH' | 'WEEK' | 'DAY' | 'HOUR' | 'MINUTE' | 'SECOND' ``` LOG 테이블에서는 임의 위치 삭제를 지원하지 않으며, 가장 오래된 데이터부터 연속으로만 삭제할 수 있습니다. 현재 LOG의 `BEFORE t`는 `_arrival_time <= t`인 행을 삭제합니다. 이름만 보고 경계를 제외한다고 생각하기 쉬우므로 사전 조회도 같은 비교 조건을 사용하세요. `EXCEPT n DAY` 등의 기간은 서버 현재 시각을 기준으로 계산하며 마지막 입력 시각을 기준으로 하지 않습니다. 일반 사용자 DATETIME 컬럼의 값은 이 삭제 기준이 아닙니다. 실제 전후 결과는 [LOG 보존형 삭제](/dbms/log-table-usage/operations-lifecycle/)에서 확인할 수 있습니다. ```sql -- 모든 데이터 삭제 DELETE FROM sensor_log; -- 가장 오래된 N건 삭제 DELETE FROM sensor_log OLDEST 1000 ROWS; -- 최근 N건을 제외하고 모두 삭제 DELETE FROM sensor_log EXCEPT 10000 ROWS; -- 최근 N일 데이터를 제외하고 모두 삭제 DELETE FROM sensor_log EXCEPT 7 DAY; -- 특정 시각까지의 데이터 삭제 (경계 시각 포함) DELETE FROM sensor_log BEFORE TO_DATE('2024-01-01', 'YYYY-MM-DD'); ``` ### DELETE WHERE (LOOKUP/VOLATILE 테이블) ```sql delete_where_stmt ::= 'DELETE FROM' table_name 'WHERE' predicate ``` LOOKUP 테이블은 기본 키 또는 일반 조건식을 사용합니다. VOLATILE 테이블은 기본 키 일치 조건을 사용합니다. LOOKUP 테이블은 WHERE 절을 생략하여 모든 행을 삭제할 수도 있습니다. ```sql DELETE FROM devices WHERE device_id = 'dev-001'; -- LOOKUP 일반 조건식으로 여러 행 삭제 DELETE FROM devices WHERE status = 'EXPIRED' OR site = 'RETIRED'; -- LOOKUP 전체 삭제 DELETE FROM devices; ``` ### DELETE (TAG 테이블) ```sql -- TAG 테이블: 이름 또는 시간 조건으로 삭제 delete_from_tag_where_stmt ::= 'DELETE FROM' table_name [ 'ROLLUP' ] 'WHERE' predicate -- predicate: tag_name 조건, tag_time 조건, 또는 두 조건의 AND 조합 ``` 시간 조건에는 `=`, `<`, `<=`, `BETWEEN`을 사용할 수 있습니다. ```sql -- TAG 이름 기준 삭제 DELETE FROM tag WHERE name = 'sensor-01'; -- TAG 이름 + 시간 기준 삭제 DELETE FROM tag WHERE name = 'sensor-01' AND time < TO_DATE('2024-01-01', 'YYYY-MM-DD'); -- 시간 조건만으로 삭제 DELETE FROM tag WHERE time <= TO_DATE('2024-01-01', 'YYYY-MM-DD'); -- ROLLUP 데이터 삭제 DELETE FROM tag ROLLUP WHERE name = 'sensor-01'; DELETE FROM tag ROLLUP WHERE time BETWEEN TO_DATE('2024-01-01','YYYY-MM-DD') AND TO_DATE('2024-02-01','YYYY-MM-DD'); ``` ### DELETE FROM TAG METADATA ```sql DELETE FROM table_name METADATA [ WHERE predicate ] ``` TAG 테이블의 메타데이터 행을 삭제합니다. WHERE를 생략하면 모든 메타데이터를 삭제합니다. 실제 데이터가 있는 태그의 메타데이터는 삭제할 수 없습니다. ```sql DELETE FROM sensors METADATA WHERE name = 'sensor-01'; DELETE FROM sensors METADATA WHERE status = 'STOP'; DELETE FROM sensors METADATA; -- 모든 메타데이터 삭제 (실제 데이터 없는 태그만) ``` --- ## UPDATE/DELETE 영향 행 수 `UPDATE`와 `DELETE`를 실행한 클라이언트는 해당 문장의 영향 행 수(affected rows)를 확인할 수 있습니다. Direct execution과 prepared statement 모두 같은 기준을 사용합니다. `UPDATE`는 실제로 값이 달라진 행 수가 아니라 `WHERE` 조건에 일치한 행 수를 반환합니다. 따라서 기존 값과 같은 값을 다시 설정해도 대상 행이 조건에 일치하면 영향 행 수에 포함됩니다. `WHERE` 절을 생략할 수 있는 테이블에서는 모든 대상 행이 일치한 것으로 계산합니다. `DELETE`는 조건에 일치해 실제로 삭제된 행 수를 반환합니다. 같은 `DELETE`를 반복하면 첫 실행에서 행이 제거되므로 다음 실행은 `0`을 반환합니다. ```sql CREATE LOOKUP TABLE device_state ( id INTEGER PRIMARY KEY, value INTEGER ); INSERT INTO device_state VALUES (1, 10); INSERT INTO device_state VALUES (2, 10); UPDATE device_state SET value = 20 WHERE id >= 1 AND id <= 2; -- 2 row(s) updated. UPDATE device_state SET value = 20 WHERE id >= 1 AND id <= 2; -- 2 row(s) updated. (동일 값 반복 UPDATE) UPDATE device_state SET value = 20 WHERE id = 999; -- No row updated. DELETE FROM device_state WHERE id = 1; -- 1 row(s) deleted. DELETE FROM device_state WHERE id = 1; -- No row deleted. ``` `No row updated.` 또는 영향 행 수 `0`은 설정한 값이 기존 값과 같다는 의미가 아니라, 조건에 일치한 행이 없다는 의미입니다. 트랜잭션에서 반환된 영향 행 수는 각 문장을 실행한 시점의 결과입니다. 이후 `ROLLBACK`하더라도 이미 반환된 영향 행 수의 의미는 바뀌지 않습니다. --- ## 관련 문서 - [DDL 문법 사전](../ddl-syntax/) - 테이블 생성 및 스키마 변경 - [SELECT 문법 사전](../select-syntax/) - 데이터 조회 - [WITH / CTE syntax](../cte-syntax/) - CTE를 사용한 INSERT SELECT - [LOOKUP predicate UPDATE](./lookup-predicate-update-syntax/) - 일반 조건식 갱신 - [LOOKUP predicate DELETE](./lookup-predicate-delete-syntax/) - 일반 조건식 삭제 - [LOAD DATA INFILE](../load-data-infile-syntax/) - CSV 파일 일괄 입력 --- title: "TAG data UPDATE" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/ language: kr kind: page --- # TAG data UPDATE TAG 테이블의 시계열 데이터는 일반 `UPDATE` 문으로 수정합니다. `UPDATE TAG TABLE`이라는 별도 키워드는 사용하지 않습니다. Machbase 8.7.0부터 지원되는 기능 TAG data UPDATE는 Standard Edition의 논리 TAG 테이블에서만 지원합니다. ## Syntax ```sql UPDATE table_name SET data_column = expression [, data_column = expression ...] WHERE tag_selector AND time_condition [AND data_predicate ...]; ``` `tag_selector`에는 `name = ...`, `name IN (...)`, `name LIKE ...` 조건을 사용할 수 있습니다. `time_condition`은 BASETIME 컬럼의 등치, `BETWEEN`, 양쪽 범위, 한쪽 범위 조건을 사용할 수 있습니다. ## Examples ### 단일 태그와 시간 범위 ```sql UPDATE sensor_tag SET value = 110, status = 1 WHERE name = 'TEMP-01' AND time >= TO_DATE('2026-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND time < TO_DATE('2026-07-02 00:00:00', 'YYYY-MM-DD HH24:MI:SS'); ``` ### 여러 태그 ```sql UPDATE sensor_tag SET note = 'corrected' WHERE name IN ('TEMP-01', 'TEMP-02') AND time BETWEEN TO_DATE('2026-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND TO_DATE('2026-07-01 23:59:59', 'YYYY-MM-DD HH24:MI:SS'); ``` ### LIKE와 데이터 컬럼 predicate ```sql UPDATE sensor_tag SET status = 7 WHERE name LIKE 'TEMP-%' AND time >= TO_DATE('2026-07-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND value > 100; ``` ### CASE 표현식 ```sql UPDATE sensor_tag SET grade = CASE WHEN 1 = 1 THEN 'HIGH' ELSE 'LOW' END WHERE name = 'TEMP-01' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); ``` ### NAME과 TIME 조건에 bind parameter 사용 `WHERE` 절의 태그 이름과 BASETIME 조건 값에 positional marker `?` 또는 named marker `:name`을 사용할 수 있습니다. 한 SQL 문에서는 marker 방식을 혼용하지 않습니다. Positional marker는 SQL에 나타난 순서대로 SET 값, 태그 이름, 기준 시간을 바인딩합니다. ```sql UPDATE sensor_tag SET value = ?, status = ?, note = ? WHERE name = ? AND time = ?; ``` Named marker는 SDK의 이름 기반 API로 SQL 출현 순서와 관계없이 값을 전달할 수 있습니다. ```sql UPDATE sensor_tag SET value = :value, status = :status, note = :note WHERE name = :name AND time = :time; ``` 같은 prepared statement를 다시 실행하면 새로 바인딩한 SET, NAME, TIME 값으로 대상 행을 선택합니다. NAME parameter는 `VARCHAR`, TIME parameter는 `DATETIME` 타입 정보를 유지하며, 조건에 일치하는 행이 없으면 오류 없이 affected rows `0`을 반환합니다. `? = name`, `:name = name`, `? = time`, `:time = time`처럼 컬럼이 오른쪽에 있는 등치 조건도 지원하지만, 가독성을 위해 컬럼을 왼쪽에 쓰는 형식을 권장합니다. 기존에 허용되는 BASETIME 범위 조건의 값 위치에도 marker를 사용할 수 있습니다. SDK별 이름 기반 API와 ordinal 규칙은 [Named Bind Parameter](../../named-bind-parameter-syntax/)를 참고하십시오. ## Metadata UPDATE TAG 메타데이터 컬럼은 TAG data UPDATE의 SET 대상이 아닙니다. 메타데이터는 `UPDATE ... METADATA` 구문으로 수정합니다. ```sql UPDATE sensor_tag METADATA SET location = 'zone-2', owner = 'ops' WHERE name = 'TEMP-01'; ``` ## Restrictions - WHERE 절에는 하나의 태그 선택 조건과 하나 이상의 BASETIME 조건이 필요합니다. `name =`, `name IN (...)`, `name LIKE ...`와 등치·BETWEEN·양쪽/한쪽 시간 범위를 사용할 수 있으며, 데이터 컬럼 조건은 `AND`로 추가할 수 있습니다. - `OR`, 서브쿼리, 집계식, non-bare tag/axis 표현식은 UPDATE 대상 조건으로 사용할 수 없습니다. - `name`(PRIMARY KEY), `time`(BASETIME), 메타데이터 컬럼, 숨김/시스템 컬럼은 data UPDATE의 SET 대상이 될 수 없습니다. - SET 우변은 상수, bind 변수, 기존 행 컬럼을 참조하지 않는 함수·연산식·`CASE`·NULL만 사용할 수 있습니다. `value = value + 1`처럼 기존 행 컬럼을 참조하는 식은 허용되지 않습니다. - bind parameter는 값만 대체하며 태그 선택 조건, BASETIME 조건, SET 대상 컬럼 제약을 변경하지 않습니다. - UPDATE는 이미 구체화된 롤업 row를 자동으로 보정하지 않습니다. 롤업 조회 전에 영향을 받은 구간을 `ROLLUP_REBUILD`로 재구성합니다. ## Related - [TAG data UPDATE WHERE/SET constraints](../tag-data-update-where-set-constraints/) - [Named Bind Parameter](../../named-bind-parameter-syntax/) - [ROLLUP_REBUILD syntax](../../rollup-rebuild-syntax/) --- title: "TAG data UPDATE WHERE/SET constraints" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/dml-syntax/tag-data-update-where-set-constraints/ language: kr kind: page --- # TAG data UPDATE WHERE/SET constraints TAG data UPDATE는 대상 범위가 명확해야 합니다. WHERE 절에는 태그 선택 조건과 BASETIME 조건이 모두 필요하고, SET 절은 실제 데이터 컬럼만 대상으로 합니다. Machbase 8.7.0부터 지원되는 기능 ## SET 절 제약 | 컬럼 역할 | SET 가능 여부 | 설명 | |-----------|:------------:|------| | 데이터 컬럼 | O | `value`, 보조 수치/문자열 컬럼 등 | | `SUMMARIZED` 데이터 컬럼 | O | 원본 TAG row 값이 변경됨 | | BASETIME 컬럼 | X | 시간 축 컬럼은 변경 불가 | | PRIMARY KEY 컬럼 (`name`) | X | 태그 이름 변경 불가 | | 메타데이터 컬럼 | X | `UPDATE ... METADATA`로 별도 처리 | | 숨김/시스템 컬럼 | X | 내부 컬럼은 SET 대상이 아님 | SET 표현식에는 상수, bind 변수, 기존 행 컬럼을 참조하지 않는 산술식·문자열식·`CASE`, 허용된 형변환 함수와 NULL을 사용할 수 있습니다. 기존 행 컬럼을 참조하는 식, 서브쿼리와 집계식은 SET RHS로 사용할 수 없습니다. ## WHERE 절 제약 ```sql UPDATE table_name SET col = expr WHERE name = 'tag-name' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); ``` | WHERE 조건 | 지원 여부 | |-----------|:---------:| | `name = '...'` | O | | `name = ?`, `name = :tag_name` | O | | `? = name`, `:tag_name = name` | O | | `name IN ('...', '...')` | O | | `name LIKE '...'` | O | | `time = t1` | O | | `time = ?`, `time = :base_time` | O | | `? = time`, `:base_time = time` | O | | `time BETWEEN t1 AND t2` | O | | `time >= t1 AND time < t2` | O | | `time >= ? AND time < ?` | O | | 한쪽 시간 조건 | O | | 데이터 컬럼 조건 | O | | 태그 선택 없는 조건 | X | | 시간 조건 없는 조건 | X | | `OR` 조건 | X | | `IN (SELECT ...)` | X | | 태그/축 컬럼을 함수·연산식으로 감싼 표현식 | X | Bind parameter는 조건의 값 위치에만 사용합니다. 태그 이름과 BASETIME 컬럼을 marker로 대체할 수 없으며, bind를 사용해도 태그 선택 조건과 시간 조건은 모두 필요합니다. 같은 prepared statement를 다시 실행하면 새로 바인딩한 값으로 대상을 선택하고, 일치하는 행이 없으면 affected rows `0`으로 성공합니다. NAME과 TIME parameter의 타입 정보와 SDK별 API는 [TAG data UPDATE의 bind parameter](../tag-data-update-syntax/#tag-data-update-predicate-bind) 및 [Named Bind Parameter](../../named-bind-parameter-syntax/)를 참고하십시오. ## 메타데이터 UPDATE ```sql UPDATE table_name METADATA SET meta_col = value WHERE condition; ``` 메타데이터 UPDATE는 태그 속성 영역을 수정합니다. 실제 시계열 row의 데이터 컬럼을 수정하는 TAG data UPDATE와 구문과 대상이 다릅니다. ## 컬럼 역할 확인 방법 `DESC` 명령으로 컬럼 속성을 확인합니다. ```sql DESC sensor_tag; ``` 또는 시스템 테이블에서 컬럼 FLAG를 조회합니다. ```sql SELECT NAME, TYPE, FLAG FROM M$SYS_COLUMNS WHERE TABLE_ID = ( SELECT ID FROM M$SYS_TABLES WHERE NAME = 'SENSOR_TAG' ); ``` | FLAG 값 | 의미 | |---------|------| | 134217728 | Tag Name | | 16777216 | Base Time / Base Distance | | 33554432 | Summarized | | 67108864 | Metadata | ## 오류 사례 ```sql -- 오류: BASETIME 컬럼을 SET 대상으로 지정 UPDATE sensor_tag SET time = TO_DATE('2026-07-01', 'YYYY-MM-DD') WHERE name = 'TEMP-01' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); -- 오류: 시간 조건이 없음 UPDATE sensor_tag SET value = 0.0 WHERE name = 'TEMP-01'; -- 오류: OR 조건 사용 UPDATE sensor_tag SET value = 0.0 WHERE name = 'TEMP-01' OR name = 'TEMP-02'; ``` ## 관련 문서 - [TAG data UPDATE syntax](../tag-data-update-syntax/) - [Named Bind Parameter](../../named-bind-parameter-syntax/) - [ROLLUP_REBUILD syntax](../../rollup-rebuild-syntax/) --- title: "LOOKUP predicate UPDATE" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-update-syntax/ language: kr kind: page --- # LOOKUP predicate UPDATE LOOKUP 테이블의 `UPDATE`는 primary key equality 조건뿐 아니라 일반 predicate를 `WHERE` 절에 사용할 수 있습니다. 조건에 맞는 모든 row가 갱신됩니다. ## Syntax ```sql UPDATE table_name SET column_name = expression [, column_name = expression ...] WHERE predicate; ``` ## 지원하는 조건 예 ```sql -- 일반 컬럼 조건 UPDATE device_lookup SET status = 'ACTIVE' WHERE site = 'SEOUL' AND status = 'READY'; -- 범위와 문자열 조건 UPDATE device_lookup SET score = score + 10 WHERE score BETWEEN 10 AND 80 AND note LIKE 'sensor-%'; -- JSON path 조건 UPDATE device_lookup SET meta = JSON_SET(meta, '$.state', 'active') WHERE meta->'$.region' = 'kr' AND JSON_EXTRACT_INTEGER(meta, '$.level') >= 3; ``` `SET` 절의 오른쪽 표현식은 현재 row의 값을 참조할 수 있습니다. ```sql UPDATE device_lookup SET score = score + 1 WHERE group_name IN ('A', 'B'); ``` ## 지원 조건 범위 | 조건 | 지원 | |------|:---:| | `pk_col = value` | O | | `non_pk_col = value` | O | | `<`, `<=`, `>`, `>=`, `<>` | O | | `BETWEEN` | O | | `IN`, `NOT IN` | O | | `LIKE`, `NOT LIKE` | O | | `AND`, `OR`, `NOT` | O | | `IS NULL`, `IS NOT NULL` | O | | `TO_DATE(...)` 날짜 조건 | O | | JSON `->`, `JSON_EXTRACT_*`, `JSON_IS_VALID` | O | ## 제약 - Primary key 컬럼 자체는 `SET` 절에서 변경할 수 없습니다. - 조건에 맞는 row가 여러 개이면 여러 row가 갱신됩니다. - JSON path 문자열은 작은따옴표(`'$.key'`)로 작성합니다. 큰따옴표는 SQL 식별자로 해석됩니다. - 숫자 JSON 값을 비교할 때는 `JSON_EXTRACT_INTEGER`, `JSON_EXTRACT_DOUBLE` 같은 타입별 함수를 권장합니다. ## 관련 문서 - [LOOKUP predicate DELETE syntax](../lookup-predicate-delete-syntax/) - [LOOKUP SQL/JSON 지원표](../../../../support-scope-constraints/lookup-sql-json/) --- title: "LOOKUP predicate DELETE" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/dml-syntax/lookup-predicate-delete-syntax/ language: kr kind: page --- # LOOKUP predicate DELETE LOOKUP 테이블의 `DELETE`는 primary key equality 조건뿐 아니라 일반 predicate를 `WHERE` 절에 사용할 수 있습니다. 조건에 맞는 모든 row가 삭제됩니다. ## Syntax ```sql DELETE FROM table_name WHERE predicate; ``` `WHERE` 절 없이 실행하면 LOOKUP 테이블의 모든 row가 삭제됩니다. ## 지원하는 조건 예 ```sql -- 일반 컬럼 조건 DELETE FROM device_lookup WHERE status = 'EXPIRED'; -- 날짜와 범위 조건 DELETE FROM device_lookup WHERE updated_at < TO_DATE('2026-01-01 00:00:00') OR score < 10; -- JSON path 조건 DELETE FROM device_lookup WHERE meta->'$.region' = 'kr' AND JSON_EXTRACT_INTEGER(meta, '$.level') < 2; ``` ## 지원 조건 범위 | 조건 | 지원 | |------|:---:| | `pk_col = value` | O | | `non_pk_col = value` | O | | `<`, `<=`, `>`, `>=`, `<>` | O | | `BETWEEN` | O | | `IN`, `NOT IN` | O | | `LIKE`, `NOT LIKE` | O | | `AND`, `OR`, `NOT` | O | | `IS NULL`, `IS NOT NULL` | O | | `TO_DATE(...)` 날짜 조건 | O | | JSON `->`, `JSON_EXTRACT_*`, `JSON_IS_VALID` | O | | WHERE 절 없는 전체 삭제 | O | ## 운영 주의사항 일반 predicate `DELETE`는 조건에 맞는 모든 row를 삭제합니다. 운영 데이터에서는 먼저 같은 조건으로 대상 범위를 확인한 뒤 실행합니다. ```sql SELECT COUNT(*) FROM device_lookup WHERE status = 'EXPIRED'; DELETE FROM device_lookup WHERE status = 'EXPIRED'; ``` ## 관련 문서 - [LOOKUP predicate UPDATE syntax](../lookup-predicate-update-syntax/) - [LOOKUP SQL/JSON 지원표](../../../../support-scope-constraints/lookup-sql-json/) --- title: "LOAD DATA INFILE" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/load-data-infile-syntax/ language: kr kind: page --- # LOAD DATA INFILE `LOAD DATA INFILE`은 CSV 포맷 데이터 파일을 서버에서 직접 읽어 테이블에 입력하는 구문입니다. > 대용량 데이터 입력에는 `machloader` 유틸리티 사용을 권장합니다. `machloader`는 병렬 처리와 다양한 옵션을 제공하여 더 빠른 입력 성능을 제공합니다. ## 문법 ```sql LOAD DATA INFILE 'file_path' INTO TABLE table_name [TABLESPACE tablespace_name] [AUTO { BULKLOAD | HEADUSE | HEADUSE_ESCAPE }] [{ FIELDS | COLUMNS } [TERMINATED BY 'char'] [ENCLOSED BY 'char']] [LINES TERMINATED BY 'char'] [TRIM { ON | OFF }] [IGNORE number LINES] [MAX_LINE_LENGTH number] [ENCODED BY coding_name] [ON ERROR { STOP | IGNORE }] ``` ## 옵션 | 옵션 | 설명 | |------|------| | `AUTO BULKLOAD` | 행 전체를 하나의 컬럼으로 입력 | | `AUTO HEADUSE` | 첫 번째 행의 컬럼명으로 테이블을 자동 생성 후 입력 | | `AUTO HEADUSE_ESCAPE` | `HEADUSE`와 동일하나 예약어, 특수문자를 `_`로 치환 | | `TERMINATED BY 'char'` | 필드 구분자 (기본값: `,`) | | `ENCLOSED BY 'char'` | 필드 인용 문자 (기본값: `"`) | | `LINES TERMINATED BY 'char'` | 레코드 구분자 | | `TRIM { ON \| OFF }` | 컬럼 앞뒤 공백 제거 여부 (기본값: ON) | | `IGNORE number LINES` | 첫 N줄 무시 (헤더 스킵 등) | | `MAX_LINE_LENGTH number` | 한 줄 최대 길이 (기본값: 512KB) | | `ENCODED BY coding_name` | 파일 인코딩 (기본값: UTF8) | | `ON ERROR STOP\|IGNORE` | 오류 발생 시 중단 또는 무시 (기본값: STOP) | 지원 인코딩: `UTF8`, `MS949`, `KSC5601`, `EUCJP`, `SHIFTJIS`, `BIG5`, `GB231280` ## 예시 ```sql -- 기본 CSV 파일 입력 (구분자: , 인용: ") LOAD DATA INFILE '/tmp/sensor_data.csv' INTO TABLE sensor_log; -- 헤더 1줄 무시하고 ;로 구분된 파일 입력 LOAD DATA INFILE '/tmp/data.csv' INTO TABLE sample_data FIELDS TERMINATED BY ';' ENCLOSED BY '\'' IGNORE 1 LINES ON ERROR IGNORE; -- AUTO BULKLOAD: 각 줄을 단일 컬럼으로 입력 (테이블 자동 생성) LOAD DATA INFILE '/tmp/raw.txt' INTO TABLE raw_table AUTO BULKLOAD; -- AUTO HEADUSE: 첫 줄을 컬럼명으로 사용해 테이블 자동 생성 후 입력 LOAD DATA INFILE '/tmp/data_with_header.csv' INTO TABLE auto_table AUTO HEADUSE; -- 인코딩 지정 LOAD DATA INFILE '/tmp/korean_data.csv' INTO TABLE Korean_table ENCODED BY MS949; ``` ## 주의사항 - `AUTO` 옵션을 사용하지 않는 경우, 대상 테이블의 모든 컬럼은 `VARCHAR` 또는 `TEXT` 타입이어야 합니다. - 파일 경로는 Machbase 서버 프로세스가 접근 가능한 경로여야 합니다. - 입력 도중 오류가 발생해도 이미 입력된 행은 롤백되지 않습니다. - 대용량 파일은 `machloader`를 사용하는 것이 성능 면에서 유리합니다. ## machloader와의 비교 | 항목 | LOAD DATA INFILE | machloader | |------|-----------------|------------| | 병렬 처리 | 미지원 | 지원 | | 사용 방법 | SQL 문 | CLI 유틸리티 | | 용도 | 소량 데이터, 스크립트 내 사용 | 대용량 일괄 입력 | ## 관련 문서 - [SAVE DATA INTO syntax](../save-data-into-syntax/) — SELECT 결과를 파일로 저장 --- title: "VIEW" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/view-syntax/ language: kr kind: page --- # VIEW VIEW는 `SELECT` 결과를 이름 있는 논리 객체로 저장해 재사용하는 기능입니다. 데이터를 별도로 저장하지 않으며, 조회 시 저장된 정의 SQL이 내부적으로 다시 전개되어 실행됩니다. ## CREATE VIEW ```sql CREATE VIEW view_name AS SELECT ... FROM ...; ``` ```sql CREATE VIEW view_name (col1, col2, ...) AS SELECT ... FROM ...; ``` ```sql CREATE OR REPLACE VIEW view_name AS SELECT ... FROM ...; ``` Standard Edition에서는 VIEW 정의의 `SELECT` 앞에 비재귀 CTE를 선언할 수 있습니다. ```sql CREATE VIEW view_name AS WITH cte_name AS ( SELECT ... FROM ... ) SELECT ... FROM cte_name; ``` - `CREATE OR REPLACE VIEW`는 기존 VIEW 정의를 교체합니다. 교체 대상이 VIEW가 아닌 객체이면 오류를 반환합니다. - 컬럼 리스트를 명시하면 해당 이름이 VIEW의 공식 컬럼명이 됩니다. 생략하면 alias 또는 원본 컬럼명이 사용됩니다. - `view_name`은 `db.user.view_name` 형태의 schema-qualified 이름도 사용할 수 있습니다. ### 기본 예시 ```sql CREATE LOOKUP TABLE customer ( id INTEGER PRIMARY KEY, name VARCHAR(20), city VARCHAR(20), amount INTEGER ); CREATE VIEW v_customer AS SELECT id, name, city, amount FROM customer; SELECT name, city FROM v_customer WHERE id = 100; ``` ### 컬럼 이름 명시 ```sql CREATE VIEW v_customer_short (cust_id, cust_name) AS SELECT id, name FROM customer; ``` ### 기존 VIEW 정의 교체 ```sql CREATE OR REPLACE VIEW v_customer_amount AS SELECT id, amount * 10 AS amount FROM customer WHERE id <= 10; ``` ### CTE를 포함한 VIEW ```sql CREATE VIEW v_customer_city_summary AS WITH city_summary AS ( SELECT city, COUNT(*) AS customer_count, SUM(amount) AS total_amount FROM customer GROUP BY city ) SELECT city, customer_count, total_amount FROM city_summary; ``` VIEW 정의에는 바인드 매개변수(`?`)를 사용할 수 없습니다. 실행할 때마다 달라지는 조건은 VIEW를 조회하는 `SELECT`에 작성합니다. ## VIEW 사용자 컨텍스트 Machbase 8.7.0부터 VIEW 내부에서 `CURRENT_*`와 `SESSION_*` 함수를 사용해 definer와 caller를 구분할 수 있습니다. 다음 예제에서 `VIEW_OWNER`는 VIEW를 만들고, `VIEW_CALLER`는 부여받은 권한으로 조회합니다. ```sql CONNECT sys/manager; CREATE USER view_owner IDENTIFIED BY 'VIEW_OWNER'; CREATE USER view_caller IDENTIFIED BY 'VIEW_CALLER'; CONNECT view_owner/VIEW_OWNER; CREATE LOOKUP TABLE user_context_source (id INTEGER PRIMARY KEY); INSERT INTO user_context_source VALUES (1); CREATE VIEW v_user_context AS SELECT CURRENT_USER() AS current_name, SESSION_USER() AS session_name, CURRENT_USER_ID() AS current_id, SESSION_USER_ID() AS session_id FROM user_context_source; CONNECT sys/manager; GRANT SELECT ON view_owner.v_user_context TO view_caller; CONNECT view_caller/VIEW_CALLER; SELECT current_name, session_name, CASE WHEN current_id <> session_id THEN 'DIFF' ELSE 'SAME' END AS id_context FROM view_owner.v_user_context; ``` ```text CURRENT_NAME SESSION_NAME ID_CONTEXT VIEW_OWNER VIEW_CALLER DIFF ``` VIEW 내부의 `CURRENT_*`는 VIEW owner를 반환하고, `SESSION_*`는 연결한 caller를 반환합니다. 일반 SQL에서는 두 계열이 같은 사용자를 반환합니다. 함수 계약은 [사용자 컨텍스트 함수](../../functions/functions-full/#current-session-user)를 참고합니다. ```sql CONNECT view_owner/VIEW_OWNER; DROP VIEW v_user_context; DROP TABLE user_context_source; CONNECT sys/manager; DROP USER view_caller; DROP USER view_owner; ``` ## DROP VIEW ```sql DROP VIEW view_name; DROP VIEW IF EXISTS view_name; ``` - `DROP VIEW IF EXISTS`는 대상이 없어도 오류 없이 통과합니다. - 다른 VIEW가 해당 VIEW를 참조하고 있으면 삭제가 차단됩니다. - `DROP TABLE view_name`으로 VIEW를 삭제할 수 없습니다. ## 메타 확인 ```sql SHOW VIEWS; DESC view_name; SELECT USER_NAME, DB_NAME, VIEW_NAME, VIEW_SQL FROM M$SYS_VIEWS WHERE VIEW_NAME = 'V_CUSTOMER'; ``` - `M$SYS_TABLES`에서 VIEW는 `TYPE = 7`로 확인할 수 있습니다. ## 지원되는 VIEW 형태 | 형태 | 지원 여부 | |------|-----------| | 단순 projection과 predicate | O | | expression, 함수, 상수, CASE | O | | JOIN | O | | subquery 포함 | O | | nested VIEW | O | | GROUP BY, HAVING | O | | DISTINCT | O | | UNION ALL | O | ## Tag / BINARY 컬럼 활용 예시 TAG 테이블의 `BINARY` 컬럼을 `extract_*()` 함수로 해석해 논리 컬럼처럼 노출하는 패턴입니다. ```sql CREATE TAG TABLE dam ( name VARCHAR(20) PRIMARY KEY, time DATETIME BASETIME, frame BINARY(16) ); CREATE VIEW damdata AS SELECT name, time, extract_bit(frame, 0) AS bit0, extract_ulong(frame, 0, 16) AS u16, extract_float(frame, 0) AS f32, extract_scaled_double(frame, 0, 12, 0, 0.5, 0.5) AS sd12 FROM dam; SELECT name, time, bit0, u16, f32, sd12 FROM damdata WHERE name = 'main' AND time >= TO_DATE('2024-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND time < TO_DATE('2024-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY time; ``` ## 제한사항 - VIEW 정의 SQL(`SELECT` 본문)은 최대 256KB까지 지원됩니다. - VIEW는 데이터를 별도로 저장하지 않으므로, 성능은 원본 조회식과 옵티마이저 판단에 의존합니다. - `DISTINCT`, 계산식 컬럼 기반 predicate는 full scan으로 처리될 수 있으므로 `EXPLAIN`으로 확인이 필요합니다. - 재귀 VIEW(자기 자신을 참조하는 VIEW)는 지원하지 않습니다. ## 관련 문서 - [SELECT syntax](../select-syntax/) — FROM 절에서 VIEW 사용 - [WITH / CTE syntax](../cte-syntax/) — CTE를 포함한 VIEW 정의 --- title: "INDEX" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/index-syntax/ language: kr kind: page --- # INDEX 인덱스 문법과 테이블 타입별 지원 범위를 설명합니다. 인덱스는 조회 비용을 줄이는 대신 입력과 변경 때 유지 비용이 발생하므로 실제 조건과 실행 계획을 확인한 뒤 추가하십시오. ## CREATE INDEX Machbase 8.7.0부터 지원되는 기능 ```sql create_index_stmt ::= 'CREATE' index_modifier? 'INDEX' [ 'IF NOT EXISTS' ] index_name 'ON' index_target '(' index_column_list ')' [ 'INDEX_TYPE' ( 'LSM' | 'KEYWORD' | 'BITMAP' | 'REDBLACK' | 'TAG' ) ] [ 'TABLESPACE' tablespace_name ] [ index_property_list ] index_modifier ::= 'UNIQUE' | 'PRIMARY KEY' index_target ::= table_name | table_name 'METADATA' index_column_list ::= column_name ( ',' column_name )* | column_name json_path index_property_list ::= ( 'MAX_LEVEL' '=' number | 'PAGE_SIZE' '=' number | 'BITMAP_ENCODE' '=' ( 'EQUAL' | 'RANGE' ) | 'PART_VALUE_COUNT' '=' number ) ( ',' index_property_list )* ``` ### IF NOT EXISTS `IF NOT EXISTS`를 지정하면 같은 database와 owner에 동일한 index name이 있을 때 오류 없이 성공하고 기존 index를 유지합니다. - 동일한 이름이 없으면 table, column, index type, property와 권한을 기존 CREATE INDEX와 동일하게 검증한 뒤 생성합니다. - 동일한 이름이 있으면 table, column, index type, JSON path와 property를 비교하거나 변경하지 않습니다. - 중복 판정 namespace는 `database + owner + index name`입니다. 다른 database나 owner의 같은 이름은 별도 index입니다. - 옵션을 생략한 CREATE INDEX는 기존 duplicate-name 오류를 그대로 반환합니다. {{< callout type="warning" >}} `IF NOT EXISTS`는 index 정의를 일치시키는 기능이 아닙니다. 같은 이름이 있으면 statement의 target table이나 column이 없거나 정의가 달라도 no-op으로 성공합니다. 반복 배포 뒤에는 `SHOW INDEX` 또는 system catalog에서 실제 table, column, type과 property를 확인하십시오. {{< /callout >}} ```sql CREATE LOG TABLE sensor_log_ifne ( sensor_id INTEGER, value DOUBLE ); CREATE INDEX IF NOT EXISTS sensor_log_ifne_idx ON sensor_log_ifne(sensor_id); -- 같은 이름이 있으므로 성공하고 기존 SENSOR_ID mapping을 유지합니다. CREATE INDEX IF NOT EXISTS sensor_log_ifne_idx ON sensor_log_ifne(value); SHOW INDEX sensor_log_ifne_idx; DROP TABLE sensor_log_ifne; ``` ### 조건부 생성 지원 형태 | 형식 | 지원 범위 | |---|---| | 일반 `CREATE INDEX IF NOT EXISTS` | 해당 table type에서 지원하는 일반 index | | `CREATE UNIQUE INDEX IF NOT EXISTS` | Standard Edition TRANSACTION table | | `CREATE PRIMARY KEY INDEX IF NOT EXISTS` | Standard Edition TRANSACTION table | | TAG data JSON path / TAG METADATA index | Standard Edition, Cluster Edition | | 일반 문법의 `INDEX_TYPE` 절 | 해당 table type과 index type의 기존 지원 범위 | deprecated `CREATE BITMAP INDEX`, `CREATE KEYWORD INDEX`, `CREATE REDBLACK INDEX` 전용 문법에는 `IF NOT EXISTS`를 사용할 수 없습니다. 일반 문법을 사용합니다. ```sql CREATE INDEX IF NOT EXISTS idx_message ON app_log(message) INDEX_TYPE KEYWORD; ``` ## 테이블 타입별 지원 범위 | 테이블 타입 | 인덱스 | 주요 용도 | |------------|--------|-----------| | LOG | LSM, KEYWORD, BITMAP | 범위 조회, 텍스트 검색, 분석 조건 | | TAG | TAG/KV secondary, JSON path | 값 컬럼과 JSON member 조건 | | TAG METADATA | 자동 컬럼 인덱스, JSON path | 태그 속성 조건 | | TRANSACTION | PRIMARY KEY, UNIQUE, 일반 BTREE | 관계형 키와 복합 조건 | | VOLATILE | REDBLACK | 메모리 테이블의 키·조건 조회 | | LOOKUP | REDBLACK | 메모리 테이블의 키·조건 조회 | 타입을 생략했을 때 적용되는 내부 인덱스는 테이블 타입에 따라 다릅니다. 다른 테이블 타입의 인덱스 이름을 지정해도 같은 구조가 생성된다고 가정하지 마십시오. ## LOG 인덱스 ```sql CREATE INDEX idx_ts ON sensor_log (ts); CREATE INDEX idx_msg ON app_log (message) INDEX_TYPE KEYWORD; CREATE INDEX idx_status ON sensor_log (status) INDEX_TYPE BITMAP BITMAP_ENCODE = RANGE; ``` | 타입 | 대상과 특징 | |------|-------------| | LSM | LOG의 기본 범위 인덱스 | | KEYWORD | VARCHAR/TEXT의 `SEARCH`, `ESEARCH` | | BITMAP | 반복 값 분석. VARCHAR, TEXT, BINARY에는 사용하지 않음 | LSM의 `MAX_LEVEL`, `PAGE_SIZE`, BITMAP의 `BITMAP_ENCODE` 같은 속성은 데이터 분포와 조회 조건으로 측정해 결정하십시오. ## TAG 인덱스 TAG 이름과 시간 축의 기본 접근 구조는 자동으로 관리됩니다. 값 컬럼을 단독 조건으로 자주 사용할 때 TAG/KV secondary index를 검토합니다. ```sql CREATE INDEX idx_value ON sensor_tag (value) INDEX_TYPE TAG; ``` JSON 값 컬럼은 path별 인덱스를 만들 수 있습니다. ```sql CREATE INDEX idx_sensor ON tag_json (value.sensor.name); CREATE INDEX idx_metric ON tag_json (value->'$.metric'); CREATE INDEX idx_item ON tag_json (value.items[0]."product-id"); ``` TAG METADATA 일반 컬럼에는 인덱스가 자동 생성됩니다. METADATA JSON 컬럼의 path를 추가할 때는 다음 형식을 사용합니다. ```sql CREATE INDEX idx_ship_owner ON ships METADATA (info->'$.owner'); ``` 지원 범위와 실행 계획 예시는 [TAG 인덱스와 성능](/dbms/tag-table-usage/index-performance/)을 참고하십시오. ## TRANSACTION 인덱스 TRANSACTION은 PRIMARY KEY, UNIQUE INDEX와 일반 단일·복합 인덱스를 지원합니다. ```sql CREATE PRIMARY KEY INDEX idx_pk_order ON orders (order_id); CREATE UNIQUE INDEX uidx_account_email ON account (email); CREATE UNIQUE INDEX uidx_tenant_login ON account (tenant_id, login_name); CREATE INDEX idx_category_name ON product (category, product_name); ``` PRIMARY KEY는 테이블당 하나이며 단일 컬럼입니다. `CREATE UNIQUE INDEX`는 복합 컬럼을 지원하고, NULL이 포함된 키는 다른 NULL 포함 키와 중복으로 판정하지 않습니다. 상세 동작은 [TRANSACTION 인덱스와 성능](/dbms/rdb-table-usage/index-performance/)을 참고하십시오. ## VOLATILE과 LOOKUP 인덱스 VOLATILE과 LOOKUP은 REDBLACK 메모리 인덱스를 사용합니다. ```sql CREATE INDEX idx_status ON device_status (status) INDEX_TYPE REDBLACK; ``` 타입별 기본 키와 추가 인덱스 설계는 다음 문서를 참고하십시오. - [VOLATILE 인덱스와 성능](/dbms/volatile-table-usage/index-performance/) - [LOOKUP 인덱스와 성능](/dbms/lookup-table-usage/index-performance/) ## DROP INDEX ```sql drop_index_stmt ::= 'DROP INDEX' index_name ``` ```sql DROP INDEX idx_status; ``` 대상 인덱스를 사용하는 세션이 있으면 삭제가 실패할 수 있습니다. 삭제 전 실행 계획과 해당 인덱스를 사용하는 운영 쿼리를 확인하십시오. ## 관련 문서 - [SEARCH / ESEARCH / REGEXP](../search-esearch-regexp-syntax/) - [쿼리 성능 튜닝](/dbms/performance-tuning/performance-query-tuning/) --- title: "RETENTION" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/retention-syntax/ language: kr kind: page --- # RETENTION RETENTION 정책은 TAG, KV, LOG 테이블에서 보존 기간을 지난 데이터를 주기적으로 삭제합니다. TRANSACTION, LOOKUP, VOLATILE에는 적용할 수 없습니다. ## RETENTION 정책 생성 ```sql create_retention_stmt ::= 'CREATE RETENTION' policy_name 'DURATION' positive_integer ( 'MONTH' | 'DAY' | 'HOUR' | 'MIN' | 'SEC' ) 'INTERVAL' positive_integer ( 'DAY' | 'HOUR' | 'MIN' | 'SEC' ) ``` | 매개변수 | 설명 | |----------|------| | `policy_name` | 정책 이름 | | `DURATION duration MONTH\|DAY\|HOUR\|MIN\|SEC` | 데이터 보존 기간 (`MONTH`는 고정 30일) | | `INTERVAL interval DAY\|HOUR\|MIN\|SEC` | 삭제 실행 주기 | ```sql -- 1일 보존, 1시간마다 삭제 실행 CREATE RETENTION policy_1d_1h DURATION 1 DAY INTERVAL 1 HOUR; -- 30일 보존, 1일마다 삭제 실행 CREATE RETENTION policy_30d_1d DURATION 30 DAY INTERVAL 1 DAY; -- 3개월 보존, 1일마다 삭제 실행 CREATE RETENTION policy_3m_1d DURATION 3 MONTH INTERVAL 1 DAY; ``` ## RETENTION 정책 삭제 ```sql drop_retention_stmt ::= 'DROP RETENTION' policy_name ``` ```sql DROP RETENTION policy_1d_1h; ``` ## 테이블에 RETENTION 정책 적용 ```sql alter_table_add_retention_stmt ::= 'ALTER TABLE' table_name 'ADD RETENTION' policy_name ``` ```sql ALTER TABLE sensor_tag ADD RETENTION policy_1d_1h; ``` ## 테이블에서 RETENTION 정책 해제 ```sql alter_table_drop_retention_stmt ::= 'ALTER TABLE' table_name 'DROP RETENTION' ``` ```sql ALTER TABLE sensor_tag DROP RETENTION; ``` ## RETENTION 정책 목록 조회 시스템 테이블에서 등록된 RETENTION 정책과 적용 현황을 조회합니다. ```sql -- 모든 RETENTION 정책 조회 SELECT * FROM M$RETENTION; -- 테이블별 적용 작업과 마지막 삭제 기준 확인 SELECT USER_NAME, TABLE_NAME, POLICY_NAME, STATE, LAST_DELETED_TIME FROM V$RETENTION_JOB ORDER BY USER_NAME, TABLE_NAME; ``` ## 전체 예시 ```sql -- 1. RETENTION 정책 생성 (1일 보존, 1시간마다 삭제) CREATE RETENTION ret_1d DURATION 1 DAY INTERVAL 1 HOUR; -- 2. TAG 테이블 생성 CREATE TAG TABLE sensor_tag ( name VARCHAR(40) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ); -- 3. 테이블에 RETENTION 정책 적용 ALTER TABLE sensor_tag ADD RETENTION ret_1d; -- 4. 정책 적용 확인 SELECT * FROM M$RETENTION; -- 5. 정책 해제 ALTER TABLE sensor_tag DROP RETENTION; -- 6. 정책 삭제 DROP RETENTION ret_1d; ``` ## 주의사항 - RETENTION 정책은 LOG 테이블과 TAG 테이블에 적용할 수 있습니다. - KV 테이블에도 적용할 수 있습니다. - 한 테이블에는 하나의 RETENTION 정책만 적용할 수 있습니다. - `MONTH`는 달력 월이 아니라 고정 30일로 계산됩니다. 달력 경계가 중요한 정책은 `DAY` 단위로 환산하고 실제 삭제 기준을 검증합니다. - RETENTION 작업이 삭제한 row는 되돌릴 수 없으므로 보존 기간과 실행 주기를 신중하게 설정합니다. `DROP RETENTION`은 table에서 정책을 모두 해제한 뒤 정책 객체만 삭제합니다. - `INTERVAL`은 삭제 작업 실행 주기이며, 실제 삭제 시각은 약간 지연될 수 있습니다. - 존재하지 않는 정책, 지원하지 않는 테이블 타입, 한 테이블의 중복 정책 적용은 오류입니다. - 적용 중인 정책 객체는 모든 테이블에서 해제한 뒤 삭제합니다. ## 관련 문서 - [Retention Policy 역할](/dbms/core-concepts/features-concepts/#role-retention-policy) — 자동 데이터 삭제 정책 개념 --- title: "BACKUP / RESTORE / MOUNT" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/backup-restore-mount-syntax/ language: kr kind: page --- # BACKUP / RESTORE / MOUNT Machbase의 백업·복원·마운트 구문은 데이터를 안전하게 보호하고 필요 시 복구하거나 과거 데이터를 조회할 때 사용합니다. > **권한**: 일반 사용자가 백업·마운트를 실행하려면 별도 권한이 필요합니다. > ```sql > GRANT BACKUP ON DATABASE database_name TO user_name; > GRANT MOUNT ON DATABASE MACHBASEDB TO user_name; > ``` --- ## BACKUP ### 논리 데이터베이스 백업 8.7.0 Standard Edition에서는 대상 catalog를 명시하는 logical backup을 사용할 수 있습니다. ```sql backup_logical_database_stmt ::= 'BACKUP DATABASE' database_name [ 'AFTER' 'backup_path_or_lsn' ] 'INTO DISK' '=' 'backup_path' ``` ```sql BACKUP DATABASE factory_a INTO DISK = '/backup/factory_a_20260806'; BACKUP DATABASE factory_a AFTER '/backup/factory_a_20260806' INTO DISK = '/backup/factory_a_inc'; ``` 논리 backup은 하나의 active database catalog를 대상으로 합니다. 여러 active database가 포함된 전체 인스턴스 image는 logical `MOUNT` 또는 `RESTORE` 입력으로 사용할 수 없습니다. ### 전체 백업 ```sql backup_database_stmt ::= 'BACKUP DATABASE INTO DISK' '=' 'backup_path' [ 'IMPORT MODE' ] ``` 현재 데이터베이스 전체를 지정한 경로에 저장합니다. 서버를 중단하지 않고 실행하는 온라인 백업입니다. ```sql -- 절대 경로로 전체 백업 BACKUP DATABASE INTO DISK = '/backup/machbase_20240101'; -- 상대 경로 ($MACHBASE_HOME/dbs 기준) BACKUP DATABASE INTO DISK = 'backup_20240101'; ``` - `backup_path`가 이미 존재하면 오류가 발생합니다. 날짜 등을 포함한 고유한 이름을 사용하십시오. - 백업이 완료될 때까지 명령이 블로킹됩니다. ### 증분 백업 ```sql backup_incremental_stmt ::= 'BACKUP DATABASE AFTER' 'backup_path_or_lsn' 'INTO DISK' '=' 'backup_path' ``` 마지막 전체 또는 증분 백업을 기준으로 백업합니다. 테이블 타입별 저장 방식은 구분해야 합니다. TRANSACTION 저장소는 증분 이미지에도 해당 백업 시점의 전체 스냅샷으로 포함되므로, 변경 행만의 델타나 변경량만큼의 공간으로 계산하면 안 됩니다. [TRANSACTION 백업 검증](/dbms/rdb-table-usage/backup-restore-mount/)에서 백업과 현재 데이터의 조회를 비교할 수 있습니다. ```sql -- 전체 백업 이후 변경분 증분 백업 BACKUP DATABASE AFTER '/backup/machbase_20240101' INTO DISK = '/backup/incr_20240102'; ``` ### 기간 백업 ```sql backup_period_stmt ::= 'BACKUP DATABASE' 'FROM' datetime_expr 'TO' datetime_expr 'INTO DISK' '=' 'backup_path' ``` 지정한 시간 범위에 해당하는 데이터만 백업합니다. ```sql BACKUP DATABASE FROM TO_DATE('2024-01-01','YYYY-MM-DD') TO TO_DATE('2024-02-01','YYYY-MM-DD') INTO DISK = '/backup/period_jan'; ``` ### 테이블 백업 ```sql backup_table_stmt ::= 'BACKUP TABLE' table_name 'INTO DISK' '=' 'backup_path' ``` 전체 데이터베이스가 아닌 특정 테이블만 선택적으로 백업합니다. ```sql BACKUP TABLE sensor_log INTO DISK = '/backup/sensor_log_20240101'; ``` --- ## RESTORE 기존 `machadmin -r` 복원은 서버를 중단한 오프라인 인스턴스 복원입니다. 8.7.0 Standard Edition에서는 논리 database를 새 catalog로 복원하거나 READ ONLY target을 교체하는 online `RESTORE DATABASE`도 지원합니다. ```sql restore_database_stmt ::= 'RESTORE DATABASE' database_name 'FROM DISK' '=' 'backup_path' [ 'REMAP OWNER' old_owner 'TO' new_owner ] [ 'REPLACE' ] ``` ```sql RESTORE DATABASE factory_a_copy FROM DISK = '/backup/factory_a_20260806' REMAP OWNER APP_A TO APP_ARCHIVE; RESTORE DATABASE factory_a FROM DISK = '/backup/factory_a_20260806' REPLACE; ``` `RESTORE DATABASE`는 SYS 전용입니다. `REPLACE` target은 READ ONLY이며 참조가 없어야 하고, restore 후 database/table 권한은 자동 승계되지 않으므로 다시 부여해야 합니다. backup image의 unsupported object나 owner 충돌은 restore 전체를 실패시킬 수 있습니다. ### 기존 인스턴스 오프라인 복원 (`machadmin -r`) 복원 전 백업 SQL 파일을 준비해 검증한 뒤 실행합니다. ```sql -- /secure/path/pre_restore_backup.sql BACKUP DATABASE INTO DISK = '/backup/before_restore'; ``` ```bash # 1. 복원 전 현재 데이터 백업 machsql -s 127.0.0.1 -P 5656 -u SYS \ -f /secure/path/pre_restore_backup.sql # 2. 서버 종료 machadmin -s # 3. 현재 데이터베이스 삭제 machadmin -d # 4. 백업 데이터로 복원 machadmin -r /backup/machbase_20240101 # 5. 서버 시작 machadmin -u ``` `machadmin -d`는 현재 database를 파기합니다. 복구 대상, 백업과 되돌림 계획을 확인하고 명시적으로 승인받은 뒤에만 실행하십시오. 복원을 실행하면 현재 database가 백업 시점으로 완전히 교체됩니다. ### 증분 백업 복원 복원할 최종 증분 백업 경로를 한 번 지정합니다. 증분 백업은 체인 정보를 포함하므로 전체 백업부터 반복 적용하지 않아도 됩니다. ```bash machadmin -s machadmin -d machadmin -r /backup/incr_20240103 machadmin -u ``` ### machadmin 주요 옵션 | 옵션 | 설명 | |------|------| | `-s` (`--shutdown`) | 서버 정상 종료 | | `-k` (`--kill`) | 서버 강제 종료 | | `-u` (`--startup`) | 서버 시작 | | `-d` (`--destroydb`) | 현재 데이터베이스 삭제 | | `-r path` (`--restore`) | 지정한 백업 경로로 복원 | --- ## MOUNT DATABASE ```sql mount_database_stmt ::= 'MOUNT DATABASE' 'backup_database_path' 'TO' mount_name ``` 서버를 중단하거나 active database를 교체하지 않고, 단일 catalog backup image를 현재 서버에 mounted database로 연결합니다. mounted database는 항상 READ ONLY이며 `USE`로 current database가 될 수 없습니다. - `backup_database_path`: DISK 방식으로 생성된 백업 디렉터리 경로 - `mount_name`: mounted database에 접근할 때 사용할 database alias ```sql -- 절대 경로로 마운트 MOUNT DATABASE '/backup/machbase_20240101' TO backup_db; -- 상대 경로 ($MACHBASE_HOME/dbs 기준) MOUNT DATABASE 'machbase_20240101' TO backup_db; ``` ### 마운트된 DB 조회 마운트된 데이터베이스의 테이블은 `mount_name.user_name.table_name` 형식으로 접근합니다. 조회하려면 mounted database의 `USAGE`와 대상 table의 `SELECT`가 모두 필요합니다. ```sql -- 마운트 DB의 테이블 조회 SELECT * FROM backup_db.sys.sensor_log WHERE _arrival_time > TO_DATE('2024-01-01','YYYY-MM-DD'); -- 현재 DB와 마운트 DB를 함께 조회 (JOIN) SELECT a.name, a.value AS current_val, b.value AS backup_val FROM sensor_log a JOIN backup_db.sys.sensor_log b ON a.name = b.name; ``` --- ## UMOUNT DATABASE ```sql umount_database_stmt ::= 'UMOUNT DATABASE' mount_name ``` 마운트된 데이터베이스를 해제합니다. ```sql UMOUNT DATABASE backup_db; ``` 마운트 DB를 참조 중인 열린 커서나 실행 중인 쿼리가 있으면 언마운트가 실패합니다. 해당 세션을 종료한 뒤 다시 실행하십시오. --- ## 제약 및 주의 사항 | 항목 | 설명 | |------|------| | 마운트 DB 쓰기 | 불가 (읽기 전용) | | IBFILE 방식 백업 마운트 | 불가 (DISK 방식만 마운트 가능) | | 버전 호환성 | 백업 DB와 현재 서버의 메타 버전이 호환되어야 함 | | TAG 테이블 기간 복원 | 미지원 (전체 백업 또는 증분 백업으로만 복원 가능) | | Cluster Edition | 다중 database와 MOUNT/UMOUNT 미지원 | --- ## 관련 문서 - [백업, 복원, 마운트 운영 가이드](/dbms/operations-configuration-recovery/backup-restore-mount/) - 상세 운영 절차 및 자동화 예시 - [GRANT/REVOKE](../user-auth-syntax/#grant-revoke) - 백업·마운트 권한 부여 --- title: "ROLLUP" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/rollup-syntax/ language: kr kind: page --- # ROLLUP ROLLUP은 시간축 TAG의 반복 집계를 위한 저장·조회 기능입니다. 일반·조건·확장 ROLLUP은 공개 `rollup()` 조회로, Custom은 사용자 대상 TAG의 재집계로 읽습니다. ## 생성 다음은 문법 형식이며 대괄호·중괄호를 그대로 실행하지 않습니다. ```text CREATE ROLLUP [IF NOT EXISTS] name ON source_tag [(column_name | json_path_expression)] INTERVAL n { SEC | MIN | HOUR } [WAKEUP INTERVAL m { SEC | MIN | HOUR }] [EXTENSION] [WHERE predicate]; CREATE ROLLUP [IF NOT EXISTS] name FROM source_rollup INTERVAL n { SEC | MIN | HOUR } [WAKEUP INTERVAL m { SEC | MIN | HOUR }] [EXTENSION] [WHERE predicate]; CREATE ROLLUP [IF NOT EXISTS] name INTO (destination_tag) AS (SELECT ...) INTERVAL n { SEC | MIN | HOUR } [WAKEUP INTERVAL m { SEC | MIN | HOUR }]; ``` - EXTENSION은 키워드만 쓰며 별도 extension_name을 붙이지 않습니다. - CREATE의 시간 단위는 SEC/MIN/HOUR입니다. DAY 등은 조회 단위와 구분합니다. - 일반 숫자 컬럼은 SUMMARIZED 없이 명시할 수 있습니다. JSON 경로 집계와 JSON 문서 전체 집계를 구분하며, 문서 전체와 WITH ROLLUP 자동 생성에는 SUMMARIZED 조건이 있습니다. - FROM 간격은 소스보다 큰 정수배이며 확장 속성·집계 모드가 맞아야 합니다. - WAKEUP은 양수, 집계 간격 이하이며 그 간격을 나누어떨어지게 해야 합니다. - Custom은 Standard 전용이고 소스 TAG 하나와 미리 만든 대상 TAG가 필요합니다. WHERE는 SELECT 내부에 쓰고 BASETIME 직접 조건·JOIN·FROM 서브쿼리는 허용하지 않습니다. - IF NOT EXISTS는 기존 이름에 대해 생성하지 않는 동작이지 정의를 수정하거나 비교해 일치시키는 기능이 아닙니다. 문법과 소스 검증을 모두 무시하는 옵션도 아닙니다. ## 삭제 ```sql DROP ROLLUP rollup_name; ``` 참조하는 상위 ROLLUP부터 삭제합니다. Custom 대상 TAG는 관련 작업이 남아 있으면 DROP이 거부됩니다. 원본 TAG의 CASCADE는 관련 ROLLUP 제거 범위까지 확인한 뒤 사용하며, 사용자 Custom 대상 테이블은 별도 수명주기로 관리합니다. ## 제어 ```sql ALTER ROLLUP rollup_name STOP; ALTER ROLLUP rollup_name START; ALTER ROLLUP rollup_name WAKEUP; ALTER ROLLUP rollup_name FORCE; ALTER ROLLUP rollup_name SET WAKEUP INTERVAL 10 SEC; ``` 이 명령은 이미 존재하는 작업과 간격 조건을 전제로 합니다. 생성 시 자동 시작되며 이미 시작·중지된 상태를 반복 지정하면 오류가 날 수 있습니다. WAKEUP은 완료를 기다리지 않고 FORCE는 대상의 소스 처리 범위를 따라잡도록 기다립니다. 과거 원본 보정에는 [REBUILD의 별도 지원 범위](../rollup-rebuild-syntax/)를 확인합니다. ## 조회와 후보 선택 ```text rollup(time_unit, period, basetime_column [, origin]) ``` 반환 타입은 DATETIME입니다. period는 양의 정수 리터럴입니다. 일반 DATE_TRUNC + GROUP BY 쿼리가 ROLLUP의 존재만으로 자동 전환된다고 안내하지 않습니다. ROLLUP 조회에는 `rollup()`을 명시하고, 적용 가능한 후보가 없으면 별도의 원본 쿼리를 사용합니다. 자동 선택은 같은 컬럼·경로·모드에서 조건 없는 후보를 먼저 찾고, 가능한 가장 큰 간격을 선택합니다. 같은 간격은 등록 순서의 영향을 받습니다. 일반/확장만으로 우선순위를 단정하지 않습니다. 특정 데이터 집합을 고정하려면 ROLLUP_TABLE 힌트를 사용합니다. SEC/MIN의 후보 간격은 period초/분을 기준으로 하며 HOUR·DAY·WEEK·MONTH·YEAR는 후보 선택 단계에서 period시간을 기준으로 검사합니다. 결과 버킷의 달력 계산과 별개의 규칙입니다. 월·년 origin은 월의 1일 조건을 확인합니다. SELECT에 name을 반환하는 태그별 집계는 GROUP BY에도 name을 포함합니다. 시간 범위·origin·NULL 처리와 후보 조건을 맞춘 뒤 원본 결과와 비교합니다. 일반 숫자 ROLLUP은 MIN/MAX/SUM/COUNT/AVG/SUMSQ, 확장은 FIRST/LAST를 추가로 지원합니다. 원본 FIRST/LAST 사용과 저장 ROLLUP의 확장 필요조건을 구분합니다. JSON의 문서 전체 COUNT와 경로별 건수는 별도 계약입니다. 실행 가능한 생성·조회·오류 예제는 [6장 ROLLUP 활용](/dbms/tag-rollup-usage/)과 [조회 문법](/dbms/tag-rollup-usage/query-syntax-rollup/)을 참고하십시오. --- title: "ROLLUP_REBUILD" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/rollup-rebuild-syntax/ language: kr kind: page --- # ROLLUP_REBUILD ROLLUP_REBUILD는 지원되는 TAG 집계의 과거 버킷을 다시 계산하는 Standard Edition 프로시저입니다. Cluster Edition에서는 지원하지 않습니다. ## 문법과 인수 ```text EXEC ROLLUP_REBUILD(source_tag, tag_name, begin_time, end_time); ``` | 인수 | 현행 입력 | |---|---| | source_tag | 원본 TAG 식별자; 필요한 경우 소유자로 한정 | | tag_name | 재구성할 한 태그 이름 문자열 | | begin_time | 시각 문자열 또는 상수 문자열 인수의 TO_DATE | | end_time | 같은 형식의 종료 시각; begin_time 이상 | 현재 시간 인수 처리는 일반 DATETIME 식의 평가가 아닙니다. NOW, NOW-1h, 컬럼·바인딩 매개변수 등을 예제로 쓰지 않습니다. 상대 범위가 필요하면 운영 도구에서 시각을 확정해 지원되는 상수 형식으로 전달합니다. 날짜 문자열과 형식·시간대를 명확히 합니다. ## 시간 범위 시작·종료가 속한 버킷을 포함하고 버킷 전체를 재계산합니다. 1분 단계의 00:00:30~00:01:00은 [00:00:00, 00:02:00) 범위입니다. 시작=종료도 그 시각의 버킷을 처리하며 시작>종료는 오류입니다. HOUR 단계는 시간 버킷 전체로 더 넓어질 수 있습니다. 단순히 원본 WHERE의 반개구간 끝을 그대로 전달하면 다음 버킷도 포함될 수 있습니다. 보정한 실제 시각과 각 집계의 버킷 경계를 기준으로 영향 범위를 정합니다. ## 대상과 제한 - 완전한 자동 SEC→MIN→HOUR 계층을 기본 실습 대상으로 합니다. - 임의 이름의 수동 일반 ROLLUP, SEC가 없는 자동 계층까지 같은 경로로 처리한다고 가정하지 않습니다. - Custom 트리는 별도 경로이며 현재 시간 경계 생성은 1 SEC·1 MIN·1 HOUR 간격을 대상으로 합니다. 10 MIN 등 생성 가능한 다른 간격과 재구성 가능 범위를 혼동하지 않습니다. - Custom SELECT의 시간 버킷과 origin은 재구성 경계와 일치해야 합니다. - 유효한 대상이 있는 상황에서 존재하지 않는 태그는 no-op일 수 있습니다. - 원본 데이터가 없어졌다면 삭제 전의 통계 값을 복원할 수 없습니다. ## 실행 예 다음은 ch6_rebuild 실습 객체가 준비된 경우의 호출입니다. ```sql EXEC ROLLUP_REBUILD(ch6_rebuild, 'S1', TO_DATE('2026-01-01 00:00:30', 'YYYY-MM-DD HH24:MI:SS'), TO_DATE('2026-01-01 00:01:00', 'YYYY-MM-DD HH24:MI:SS')); ``` 준비 SQL, 원본 정정, 기본·Custom 결과 비교와 정리는 [6.10 완결 실습](/dbms/tag-rollup-usage/rollup-rebuild/)에 있습니다. ## 작업 상태와 실패 관련 집계 작업은 중지·재계산·재시작 단계를 거칩니다. 재구성 중 정상 집계가 그대로 계속 진행된다는 보장은 없습니다. 원본의 안정적인 조회를 위한 처리와 데이터 재생성의 영향을 격리 환경에서 확인합니다. 실패하면 일부 결과가 이미 변경됐을 수 있습니다. 전체 롤백이나 원래 중지 상태의 자동 복원을 전제로 하지 말고 원본·대상 버킷·V$ROLLUP·gap·최초 오류를 확인합니다. 지원되지 않는 작업을 제외하거나 절차를 보완하기 전에 같은 명령을 반복하지 않습니다. [생성·조회 문법](../rollup-syntax/), [지원 범위](/dbms/reference/support-scope-constraints/rollup/)와 [문제 해결](/dbms/troubleshooting/rollup/)을 함께 확인합니다. --- title: "USER/AUTH" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/user-auth-syntax/ language: kr kind: page --- # USER/AUTH 사용자 생성·삭제·비밀번호 변경, 권한 부여·회수, 공개키 기반 AUTH KEY 관리 구문입니다. --- ## CREATE USER {#create-drop-alter-user} ```sql create_user_stmt ::= 'CREATE USER' user_name 'IDENTIFIED BY' password [ 'PASSWORD POLICY' ( 'NONE' | 'LOW' | 'HIGH' ) ] [ 'WITH AUTH KEY' '(' auth_key_spec ')' ] auth_key_spec ::= "key='" pem_public_key "'," "valid_before='" YYYY-MM-DD "'," "comment='" text "'" ``` 사용자명은 저장 시 대문자로 변환됩니다. ```sql -- 기본 사용자 생성 CREATE USER app_user IDENTIFIED BY 'App#1234'; -- 비밀번호 정책 지정 CREATE USER ops_user IDENTIFIED BY 'Ops@Strong1' PASSWORD POLICY HIGH; -- AUTH KEY와 함께 생성 (공개키 기반 인증) CREATE USER app_user IDENTIFIED BY 'App#1234' WITH AUTH KEY ( key='-----BEGIN PUBLIC KEY-----\nMFkw...(생략)...==\n-----END PUBLIC KEY-----\n', valid_before='2047-12-31', comment='initial key' ); ``` ### 비밀번호 정책 | 정책 | 설명 | |------|------| | `NONE` | 강도 제약 없음. 만료일 없음 | | `LOW` | 최소 10자, 대/소문자/특수문자 포함, 연속 숫자·키보드 패턴 금지 | | `HIGH` | LOW 규칙 + 최근 24개 비밀번호 재사용 금지 + 90일 자동 만료 | --- ## DROP USER ```sql drop_user_stmt ::= 'DROP USER' user_name ``` `SYS` 사용자는 삭제할 수 없습니다. 해당 사용자가 생성한 테이블이 남아 있으면 오류가 발생합니다. 다른 관리자 세션이 사용자를 삭제해도 기존 활성 세션은 즉시 종료되지 않습니다. 새 접속은 실패하며 기존 세션은 로그인 시점의 사용자명과 ID를 유지합니다. 운영 절차는 [계정 관리](../../../../security-access-control/account/#drop-user-active-session)를, 확인 함수는 [사용자 컨텍스트 함수](../../functions/functions-full/#current-session-user)를 참고합니다. ```sql DROP USER old_user; ``` --- ## ALTER USER ```sql -- 비밀번호 변경 alter_user_pwd_stmt ::= 'ALTER USER' user_name 'IDENTIFIED BY' new_password [ 'PASSWORD POLICY' ( 'NONE' | 'LOW' | 'HIGH' ) ] ``` 정책만 단독으로 변경하는 구문은 허용되지 않습니다. 정책을 변경할 때는 반드시 새 비밀번호를 함께 지정해야 합니다. ```sql -- 비밀번호 변경 ALTER USER app_user IDENTIFIED BY 'NewPass#456'; -- 비밀번호와 정책 동시 변경 ALTER USER app_user IDENTIFIED BY 'NewPass#456' PASSWORD POLICY HIGH; ``` --- ## CONNECT ```sql user_connect_stmt ::= 'CONNECT' user_name '/' password ``` 애플리케이션을 종료하지 않고 다른 사용자로 재연결합니다. ```sql CONNECT app_user/App#1234; ``` --- ## GRANT / REVOKE {#grant-revoke} ```sql grant_stmt ::= 'GRANT' priv_list 'ON' object_ref 'TO' user_name revoke_stmt ::= 'REVOKE' priv_list 'ON' object_ref 'FROM' user_name priv_list ::= priv_value ( ',' priv_value )* object_ref ::= 'DATABASE' database_name | 'TABLE' ['database_name.'] owner_name '.' table_name | ['database_name.'] owner_name '.' table_name ``` ### 테이블 권한 ```sql -- 테이블에 대한 DML 권한 GRANT SELECT ON sensor_log TO reader; GRANT SELECT, INSERT ON sys.sensor_log TO writer; GRANT ALL ON sys.sensor_log TO app_user; -- 권한 회수 REVOKE INSERT ON sys.sensor_log FROM writer; REVOKE ALL ON sys.sensor_log FROM app_user; ``` 테이블 권한 종류: `SELECT`, `INSERT`, `DELETE`, `UPDATE`, `ALL` ### 데이터베이스 권한 (Machbase 8.5 이상) ```sql -- DDL 권한 (CREATE + DROP) GRANT DDL ON DATABASE factory_a TO deploy_user; -- 개별 DDL 권한 GRANT CONNECT ON DATABASE factory_a TO app_user; GRANT CREATE ON DATABASE factory_a TO create_user; GRANT DROP ON DATABASE factory_a TO drop_user; GRANT ALTER ON DATABASE factory_a TO ops_user; -- 운영 권한 GRANT BACKUP ON DATABASE factory_a TO backup_user; GRANT MOUNT ON DATABASE MACHBASEDB TO mount_user; GRANT USAGE ON DATABASE factory_a_backup TO report_user; -- 모든 데이터베이스 권한 GRANT ALL ON DATABASE factory_a TO admin_user; -- 권한 회수 REVOKE BACKUP ON DATABASE factory_a FROM backup_user; ``` 데이터베이스 권한 종류: `CONNECT`, `CREATE`, `DROP`, `ALTER`, `BACKUP`, `MOUNT`, `USAGE`, `DDL`(`CREATE+DROP`), `ALL`(`CONNECT+CREATE+DROP+ALTER+BACKUP`) ### 데이터베이스 권한이 필요한 작업 | 작업 | 필요한 권한 | |------|------------| | CREATE/DROP TABLE, VIEW, INDEX, ROLLUP, TABLESPACE, RETENTION | `CREATE`, `DROP`, 또는 `DDL` | | ALTER SYSTEM | `ALTER` | | BACKUP DATABASE | `BACKUP` | | MOUNT/UMOUNT DATABASE | `MOUNT` | ### 사용자 생성 시 기본 권한 신규 생성 사용자는 `SELECT`, `INSERT`, `DELETE`, `UPDATE`, `CREATE`, `DROP` 권한을 기본 보유합니다. `ALTER`, `MOUNT`, `BACKUP`은 명시적으로 부여해야 합니다. --- ## AUTH KEY 관리 {#auth-key} AUTH KEY는 비밀번호 대신 공개키 기반 challenge 인증을 사용할 때 Machbase에 등록하는 공개키입니다. ### 지원 알고리즘 | 알고리즘 | 지원 파라미터 | 서명 스킴 | |---------|-------------|----------| | ECDSA | P-256, P-384, P-521 | ECDSA | | RSA | 2048, 3072, 4096 bits | RSA_PKCS1_V15, RSA_PSS | ### 키 파일 생성 (openssl) ```bash # ECDSA P-256 키 생성 openssl ecparam -name prime256v1 -genkey -noout -out app_user.key openssl ec -in app_user.key -pubout -out app_user.pub chmod 600 app_user.key # RSA 2048-bit 키 생성 openssl genrsa -out app_user_rsa.key 2048 openssl rsa -in app_user_rsa.key -pubout -out app_user_rsa.pub chmod 600 app_user_rsa.key # PEM을 SQL 인라인 형식으로 변환 (줄바꿈을 \n으로) awk '{printf "%s\\n", $0}' app_user.pub ``` ### AUTH KEY 추가 ```sql alter_user_add_auth_key_stmt ::= 'ALTER USER' user_name 'ADD AUTH KEY' '(' auth_key_spec ')' auth_key_spec ::= "key='" pem_public_key "'," "valid_before='" YYYY-MM-DD "'," "comment='" text "'" ``` ```sql ALTER USER app_user ADD AUTH KEY ( key='-----BEGIN PUBLIC KEY-----\nMFkw...(생략)...==\n-----END PUBLIC KEY-----\n', valid_before='2047-12-31', comment='primary key' ); ``` 추가된 키는 즉시 활성 상태(`ACTIVATED=1`)로 등록됩니다. ### AUTH KEY 활성화 / 비활성화 ```sql alter_user_activate_key_stmt ::= 'ALTER USER' user_name 'ACTIVATE AUTH KEY ID' key_id alter_user_deactivate_key_stmt ::= 'ALTER USER' user_name 'DEACTIVATE AUTH KEY ID' key_id ``` ```sql ALTER USER app_user DEACTIVATE AUTH KEY ID 3; ALTER USER app_user ACTIVATE AUTH KEY ID 3; ``` ### AUTH KEY 유효기간 변경 ```sql alter_user_alter_key_stmt ::= 'ALTER USER' user_name 'ALTER AUTH KEY ID' key_id "VALID_BEFORE='" YYYY-MM-DD "'" ``` ```sql ALTER USER app_user ALTER AUTH KEY ID 3 VALID_BEFORE='2048-06-30'; ``` ### AUTH KEY 삭제 ```sql alter_user_drop_key_stmt ::= 'ALTER USER' user_name 'DROP AUTH KEY ID' key_id ``` ```sql ALTER USER app_user DROP AUTH KEY ID 3; ``` ### AUTH KEY 조회 ```sql SELECT key_id, user_name, key_algo, key_param, activated, valid_before, comment FROM V$USER_AUTH_KEYS WHERE user_name = 'APP_USER' ORDER BY key_id; ``` `V$USER_AUTH_KEYS` 주요 컬럼: `KEY_ID`, `USER_NAME`, `KEY_ALGO`, `KEY_PARAM`, `ACTIVATED`, `VALID_AFTER`, `VALID_BEFORE`, `COMMENT`, `PUBKEY` --- ## 관련 문서 - [사용자 관리 가이드](../../../../operations-configuration-recovery/) - 사용자 운영 절차 및 예시 - [시스템/세션 관리 문법](../system-session-alter-syntax/) - ALTER SYSTEM, ALTER SESSION --- title: "SYSTEM/SESSION/ALTER SYSTEM" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/system-session-alter-syntax/ language: kr kind: page --- # SYSTEM/SESSION/ALTER SYSTEM `ALTER SYSTEM`은 서버 전역 자원을 관리하는 구문입니다. `ALTER SESSION`은 현재 세션에만 적용되는 파라미터를 설정합니다. > **권한**: `ALTER SYSTEM` 명령은 `SYS` 계정 또는 `GRANT ALTER ON DATABASE database_name TO user_name;`으로 권한을 부여받은 사용자만 실행할 수 있습니다. --- ## ALTER SYSTEM {#alter-system} ### 명령어 목록 | 명령어 | 설명 | |--------|------| | `KILL SESSION n` | 지정한 세션 강제 종료 | | `CANCEL SESSION n` | 세션은 유지하고 실행 중인 쿼리만 취소 | | `CHECKPOINT` | 메모리 버퍼를 디스크에 즉시 동기화 | | `FREEZE` | 모든 DML 일시 중단 (백업 준비용) | | `UNFREEZE` | FREEZE로 중단된 DML 재개 | | `FLUSH AGER` | Ager 스레드를 즉시 실행하여 만료 데이터 정리 | | `FLUSH SYS_STAT` | 쿼리 최적화기용 시스템 통계 정보 갱신 | | `FLUSH PVO_CACHE` | PVO Statement 캐시 초기화 | | `FLUSH PAGE_CACHE` | OS 페이지 캐시 강제 해제 | | `FLUSH TAG_CACHE` | TAG 테이블 메타데이터 캐시 초기화 | | `INSTALL LICENSE` | 기본 경로에 라이선스 파일 설치 | | `INSTALL LICENSE = 'path'` | 지정 경로에 라이선스 파일 설치 | | `CHECK DISK_USAGE` | 로그 테이블 디스크 사용량 재계산 | | `SET property = value` | 시스템 속성 동적 변경 | --- ### KILL SESSION / CANCEL SESSION ```sql alter_system_kill_session_stmt ::= 'ALTER SYSTEM KILL SESSION' session_id alter_system_cancel_session_stmt ::= 'ALTER SYSTEM CANCEL SESSION' session_id ``` ```sql -- 현재 세션 목록 확인 SELECT id, user_id, client_type FROM v$session; -- 세션 강제 종료 (접속 해제, 트랜잭션 롤백) ALTER SYSTEM KILL SESSION 12; -- 실행 중인 쿼리만 취소 (접속 유지) ALTER SYSTEM CANCEL SESSION 6; ``` - `KILL SESSION`: SYS 사용자만 실행 가능. 대상 세션 즉시 종료. - `CANCEL SESSION`: 같은 사용자 또는 SYS만 실행 가능. 세션은 유지되고 현재 실행 중인 SQL만 중단. --- ### CHECKPOINT ```sql alter_system_checkpoint_stmt ::= 'ALTER SYSTEM CHECKPOINT' ``` 메모리에 버퍼링된 데이터를 디스크에 즉시 동기화합니다. ```sql ALTER SYSTEM CHECKPOINT; ``` --- ### FREEZE / UNFREEZE ```sql alter_system_freeze_stmt ::= 'ALTER SYSTEM FREEZE' alter_system_unfreeze_stmt ::= 'ALTER SYSTEM UNFREEZE' ``` 백업 준비 등 일관성이 필요할 때 모든 DML을 일시 중단합니다. ```sql ALTER SYSTEM FREEZE; -- (백업 또는 점검 수행) ALTER SYSTEM UNFREEZE; ``` --- ### FLUSH ```sql alter_system_flush_stmt ::= 'ALTER SYSTEM FLUSH' ( 'AGER' | 'SYS_STAT' | 'PVO_CACHE' | 'PAGE_CACHE' | 'TAG_CACHE' ) ``` ```sql -- Ager 즉시 실행 (만료 데이터 정리) ALTER SYSTEM FLUSH AGER; -- 쿼리 최적화기 통계 갱신 ALTER SYSTEM FLUSH SYS_STAT; -- PVO Statement 캐시 초기화 ALTER SYSTEM FLUSH PVO_CACHE; -- OS 페이지 캐시 강제 해제 ALTER SYSTEM FLUSH PAGE_CACHE; -- TAG 메타데이터 캐시 초기화 ALTER SYSTEM FLUSH TAG_CACHE; ``` --- ### INSTALL LICENSE ```sql -- 기본 경로 ($MACHBASE_HOME/conf/license.dat) alter_system_install_license_stmt ::= 'ALTER SYSTEM INSTALL LICENSE' -- 지정 경로 alter_system_install_license_path_stmt ::= 'ALTER SYSTEM INSTALL LICENSE' '=' "'" path "'" ``` ```sql -- 기본 경로에서 설치 ALTER SYSTEM INSTALL LICENSE; -- 지정 경로에서 설치 ALTER SYSTEM INSTALL LICENSE = '/tmp/new_license.dat'; ``` --- ### CHECK DISK_USAGE ```sql alter_system_check_disk_stmt ::= 'ALTER SYSTEM CHECK DISK_USAGE' ``` `V$STORAGE`의 `DC_TABLE_FILE_SIZE` 값을 파일 시스템에서 재계산합니다. 프로세스 장애나 정전 후 사용량이 부정확할 때 사용합니다. ```sql ALTER SYSTEM CHECK DISK_USAGE; ``` --- ### SET (시스템 속성 동적 변경) ```sql alter_system_set_stmt ::= 'ALTER SYSTEM SET' property_name '=' value_expr value_expr ::= value | property_name '|' number -- 비트 OR (플래그 추가) | property_name '&' '~' number -- 비트 AND NOT (플래그 제거) ``` 변경 가능한 속성 목록: | 속성 | 설명 | |------|------| | `QUERY_PARALLEL_FACTOR` | 쿼리 병렬 처리 스레드 수 | | `DEFAULT_DATE_FORMAT` | 기본 날짜 형식 (예: `'YYYY-MM-DD HH24:MI:SS'`) | | `TRACE_LOG_LEVEL` | 트레이스 로그 레벨 (비트 플래그) | | `DISK_COLUMNAR_PAGE_CACHE_MAX_SIZE` | 디스크 컬럼형 페이지 캐시 최대 크기 | | `MAX_SESSION_COUNT` | 최대 세션 수 | | `SESSION_IDLE_TIMEOUT_SEC` | 세션 유휴 타임아웃 (초) | | `PROCESS_MAX_SIZE` | 프로세스 최대 메모리 크기 | | `TAG_CACHE_MAX_MEMORY_SIZE` | TAG 캐시 최대 메모리 크기 | | `PVO_CACHE_ENABLE` | PVO 캐시 활성화 (0/1) | | `PVO_CACHE_MAX_MEMORY_SIZE` | PVO 캐시 최대 메모리 크기 | ```sql -- 직접 값 설정 ALTER SYSTEM SET TRACE_LOG_LEVEL = 3; ALTER SYSTEM SET DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'; -- 변경 전 현재값 확인 SELECT NAME, VALUE, MIN, MAX FROM V$PROPERTY WHERE NAME = 'MAX_SESSION_COUNT'; -- 비트 플래그 추가 (OR) ALTER SYSTEM SET TRACE_LOG_LEVEL = TRACE_LOG_LEVEL | 0x00000004; -- 비트 플래그 제거 (AND NOT) ALTER SYSTEM SET TRACE_LOG_LEVEL = TRACE_LOG_LEVEL & ~0x00000001; -- 16진수로 설정 ALTER SYSTEM SET TRACE_LOG_LEVEL = 0x00000003; ``` --- ## ALTER SESSION {#alter-session} 세션 단위 파라미터를 변경합니다. ```sql alter_session_stmt ::= 'ALTER SESSION SET' session_property_name '=' value ``` ### SET SQL_LOGGING ```sql ALTER SESSION SET SQL_LOGGING = flag -- flag: 비트 OR 조합 -- 0x1: 파싱·검증·최적화 단계 로그 -- 0x2: DDL 수행 결과 로그 ``` ```sql ALTER SESSION SET SQL_LOGGING = 3; -- 파싱 로그 + DDL 로그 ALTER SESSION SET SQL_LOGGING = 0; -- 로깅 비활성화 ``` ### SET DEFAULT_DATE_FORMAT ```sql ALTER SESSION SET DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'; ALTER SESSION SET DEFAULT_DATE_FORMAT = 'YYYYMMDD'; ``` ### SET SHOW_HIDDEN_COLS `SELECT *` 시 숨김 컬럼(`_arrival_time`)을 함께 출력할지 설정합니다. ```sql ALTER SESSION SET SHOW_HIDDEN_COLS = 1; -- 숨김 컬럼 표시 ALTER SESSION SET SHOW_HIDDEN_COLS = 0; -- 숨김 컬럼 숨김 (기본값) ``` ### SET FEEDBACK_APPEND_ERROR Append API에서 발생한 에러 메시지를 클라이언트로 전달할지 설정합니다. ```sql ALTER SESSION SET FEEDBACK_APPEND_ERROR = 1; -- 에러 메시지 전송 ALTER SESSION SET FEEDBACK_APPEND_ERROR = 0; -- 에러 메시지 미전송 (기본값) ``` ### SET MAX_QPX_MEM 단일 SQL이 GROUP BY, DISTINCT, ORDER BY 연산 시 사용할 수 있는 최대 메모리 (바이트 단위)입니다. ```sql ALTER SESSION SET MAX_QPX_MEM = 1073741824; -- 1GB ``` ### SET DDL_LOCK_TIMEOUT Standard Edition에서 충돌한 DDL 잠금을 기다릴 시간을 초 단위로 지정합니다. 기본값은 `0`, 설정 범위는 `0`~`1000000`입니다. `0`이면 기다리지 않고 즉시 `ERR-02031: Resource busy ()`를 반환합니다. ```sql ALTER SESSION SET DDL_LOCK_TIMEOUT = 10; -- 최대 10초 대기 ``` 실행 중인 DDL의 대기 시간은 변경되지 않으며 새 값은 다음 DDL부터 적용됩니다. 현재 세션별 설정값은 `V$SESSION.DDL_LOCK_TIMEOUT`에서 확인합니다. ```sql SELECT id, user_name, ddl_lock_timeout FROM v$session WHERE closed = 0 ORDER BY id; ``` 충돌 범위와 오류 처리 방법은 [DDL 동시성과 잠금](../ddl-syntax/#ddl-concurrency)을 참고하십시오. ### SET SESSION_IDLE_TIMEOUT_SEC 세션 유휴 상태 연결 유지 최대 시간 (초 단위)입니다. ```sql ALTER SESSION SET SESSION_IDLE_TIMEOUT_SEC = 300; -- 5분 ``` ### SET QUERY_TIMEOUT 쿼리 실행 대기 최대 시간 (초 단위)입니다. 초과 시 쿼리가 자동 중단됩니다. ```sql ALTER SESSION SET QUERY_TIMEOUT = 60; -- 60초 ``` --- ## 관련 뷰 | 뷰 | 설명 | |----|------| | `v$session` | 현재 접속 세션 목록 및 세션별 파라미터 | | `v$storage` | 디스크 사용량 정보 (`DC_TABLE_FILE_SIZE` 등) | | `v$license_info` | 설치된 라이선스 정보 | | `v$property` | 시스템 속성 및 현재 값 | ```sql -- 세션 목록 조회 SELECT id, user_id, client_type, login_time FROM v$session; -- 시스템 속성 확인 SELECT name, value FROM v$property WHERE name = 'TRACE_LOG_LEVEL'; ``` --- ## 관련 문서 - [ALTER SYSTEM 운영 가이드](../../../../operations-configuration-recovery/alter-system/) - 상세 운영 절차 및 각 명령별 동작 설명 - [GRANT/REVOKE](../user-auth-syntax/#grant-revoke) - ALTER SYSTEM 권한 부여 --- title: "DATABASE" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/database-syntax/ language: kr kind: page --- # DATABASE Machbase 8.7.0 Standard Edition의 논리 데이터베이스 lifecycle과 session 선택 구문입니다. 데이터베이스 이름은 catalog 이름이며, 서버 인스턴스의 물리 저장소를 관리하는 `machadmin -c`, `machadmin -d`와는 구분합니다. ## CREATE DATABASE ```sql create_database_stmt ::= 'CREATE DATABASE' ['IF NOT EXISTS'] database_name ``` `CREATE DATABASE`는 현재 Machbase 인스턴스에 active logical database를 만듭니다. 새 데이터베이스의 기본 access mode는 `READ WRITE`입니다. 사용자와 인증 정보는 인스턴스 전체에서 공유되며, 테이블·view·index와 객체 권한은 데이터베이스별로 관리됩니다. ```sql CREATE DATABASE factory_a; CREATE DATABASE IF NOT EXISTS factory_b; ``` ## ALTER DATABASE ```sql alter_database_stmt ::= 'ALTER DATABASE' database_name ( 'READ ONLY' | 'READ WRITE' ) ``` `READ ONLY` 데이터베이스는 조회할 수 있지만 쓰기 DML, append와 변경 DDL을 실행할 수 없습니다. 실행 중인 쓰기 작업을 정리한 뒤 mode를 변경하십시오. ```sql ALTER DATABASE factory_a READ ONLY; ALTER DATABASE factory_a READ WRITE; ``` ## DROP DATABASE ```sql drop_database_stmt ::= 'DROP DATABASE' ['IF EXISTS'] database_name [ 'RESTRICT' | 'CASCADE' | 'FORCE' | 'CASCADE FORCE' | 'FORCE CASCADE' ] ``` - `RESTRICT`는 객체나 사용 중인 참조가 있으면 삭제하지 않습니다. - `CASCADE`는 대상 데이터베이스의 객체, metadata와 database-local grant를 정리합니다. - `FORCE`는 종료 가능한 session, statement, cursor와 job 참조를 정리한 뒤 삭제를 진행합니다. - 객체와 참조를 함께 정리해야 하면 `CASCADE FORCE`를 사용합니다. 기본 데이터베이스 `MACHBASEDB`는 삭제할 수 없습니다. 현재 session의 database도 삭제할 수 없으므로 먼저 `USE MACHBASEDB` 또는 다른 active database를 실행해야 합니다. ## USE ```sql use_database_stmt ::= 'USE' ['DATABASE'] database_name ``` `USE`와 `USE DATABASE`는 같은 동작을 합니다. 현재 session의 database만 변경하며 다른 연결에는 영향을 주지 않습니다. transaction이 진행 중이거나 대상 database가 mounted database인 경우에는 실패합니다. ```sql USE factory_a; USE DATABASE factory_b; ``` ## 현재 데이터베이스 확인 ```sql SELECT CURRENT_DATABASE(); SELECT DATABASE(); SELECT CURRENT_CATALOG; SHOW CURRENT DATABASE; SHOW DATABASES; ``` 권장 확인 방법은 `CURRENT_DATABASE()`입니다. client 연결 옵션으로 초기 database를 지정했더라도 연결 직후 이 값을 확인하여 실제 server catalog를 검증하십시오. ## 객체 이름 테이블·view와 DML 대상은 다음 형식으로 지정할 수 있습니다. ```text table_name -- 현재 DB, 현재 사용자 owner.table_name -- 현재 DB, 지정 owner database_name.owner.table_name -- 지정 DB, 지정 owner ``` 두 부분 이름은 항상 `owner.table`입니다. 따라서 `factory_a.sensor_log`를 database와 table의 두 부분 이름으로 해석하지 않으며, 명시적으로 다른 database를 지정하려면 `factory_a.sys.sensor_log`처럼 세 부분을 사용합니다. ```sql SELECT * FROM factory_a.sys.sensor_log; INSERT INTO factory_b.app.orders VALUES (1, 'ready'); ``` 다른 database를 직접 참조하려면 대상 database의 `CONNECT`와 대상 table의 필요한 DML 권한이 모두 필요합니다. index 이름, `LOAD DATA` 대상 등은 각 구문의 별도 qualifier 제약을 따릅니다. ## 권한 구문과 database 범위 database 권한과 table 권한은 서로 별개입니다. 기본 형태는 다음과 같습니다. ```sql GRANT CONNECT ON DATABASE factory_a TO app_a; GRANT CREATE, ALTER ON DATABASE factory_a TO deployer; GRANT SELECT, INSERT ON TABLE factory_a.sys.sensor_log TO app_a; REVOKE CONNECT ON DATABASE factory_a FROM app_a; ``` mounted database를 조회할 때는 대상 database의 `USAGE`와 table `SELECT`가 모두 필요합니다. `MOUNT DATABASE`와 `UMOUNT DATABASE`를 실행하는 운영 권한은 [USER/AUTH 문법](../user-auth-syntax/#grant-revoke)과 [다중 데이터베이스 운영 가이드](/dbms/operations-configuration-recovery/multi-database/)를 참조하십시오. ## BACKUP/RESTORE와의 관계 논리 database 백업·복원 구문은 [BACKUP / RESTORE / MOUNT](../backup-restore-mount-syntax/)에 정리되어 있습니다. 단일 active catalog를 대상으로 한 named backup만 logical MOUNT 또는 RESTORE 입력으로 사용할 수 있으며, 여러 active database가 포함된 full-instance image는 logical catalog로 mount/restore할 수 없습니다. --- title: "AUTO_INCREMENT" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/auto-increment-syntax/ language: kr kind: page --- # AUTO_INCREMENT `AUTO_INCREMENT`는 단일 64비트 정수 PRIMARY KEY 값을 서버가 자동으로 생성하도록 하는 컬럼 속성입니다. Machbase 8.7.0부터 LOOKUP과 VOLATILE에서도 지원 ## 지원 범위 | 항목 | 지원 범위 | |---|---| | Edition | Standard Edition | | 테이블 | TRANSACTION, LOOKUP, VOLATILE | | 컬럼 타입 | `LONG`, `INT64` | | 키 | 컬럼 단위 단일 `PRIMARY KEY` | 테이블 단위·복합 PRIMARY KEY에는 사용할 수 없습니다. LOOKUP의 같은 컬럼에 `PROPERTY(SEQUENCE)`를 함께 지정하거나 `NEXTVAL()`을 사용하지 않습니다. ```sql CREATE TRANSACTION TABLE device_master ( id LONG PRIMARY KEY AUTO_INCREMENT, device_name VARCHAR(80), site_code VARCHAR(32) ); CREATE LOOKUP TABLE lookup_order ( id INT64 PRIMARY KEY AUTO_INCREMENT, item VARCHAR(100) ); CREATE VOLATILE TABLE volatile_order ( id LONG PRIMARY KEY AUTO_INCREMENT, item VARCHAR(100) ); ``` 명시적 transaction이 진행 중이면 TRANSACTION 테이블 DDL을 실행할 수 없습니다. 먼저 `COMMIT` 또는 `ROLLBACK`한 뒤 테이블을 생성합니다. ## 자동값 생성 자동 생성 컬럼을 생략하거나 `NULL`로 입력하면 서버가 값을 생성합니다. ```sql INSERT INTO device_master(device_name, site_code) VALUES ('compressor-01', 'SEOUL-A'); INSERT INTO device_master(id, device_name, site_code) VALUES (NULL, 'pump-02', 'SEOUL-A'); ``` `NULL`이 아닌 값을 직접 지정할 수도 있습니다. 지정한 값이 현재 다음 값 이상이면 다음 자동값은 그보다 큰 값부터 진행하고, 작은 값을 지정해도 순번은 되감기지 않습니다. `0`은 유효합니다. `INT64_MAX` 뒤에는 더 생성할 값이 없어 자동 INSERT가 실패합니다. 중복 키와 실패한 INSERT 뒤의 번호 재사용 여부에 의존하지 마십시오. 이 값은 행 식별자이며 빠짐없는 업무 순번이 아닙니다. ## 테이블별 차이 | 동작 | TRANSACTION | LOOKUP | VOLATILE | |---|:---:|:---:|:---:| | 행과 다음 자동값의 재시작 후 유지 | O | O | X | | 명시적 transaction | O | X | X | | `INSERT ... SELECT` 자동값 생성 | O | X | X | | 단일 INSERT 결과 ROWID | O | O | O | VOLATILE 테이블은 서버 재시작 시 테이블과 값이 사라집니다. TRANSACTION의 데이터 이관에서만 자동 컬럼을 생략한 `INSERT ... SELECT`를 사용할 수 있습니다. ```sql INSERT INTO device_master(device_name, site_code) SELECT device_name, site_code FROM staging_device ORDER BY device_name; ``` ## INSERT 결과 확인 지원 SDK는 성공한 단일 `INSERT ... VALUES`의 실행 결과에서 생성된 식별자를 제공할 수 있습니다. batch, Append, loader, `INSERT ... SELECT`와 UPSERT에서는 단일 값을 반환하지 않습니다. 자세한 조건은 [ROWID](../../rowid/)를, 언어별 API는 [SDK 기능 지원 범위](/dbms/development-tools-integration/sdk-support-scope/)를 참고하십시오. ## 관련 문서 - [TRANSACTION 테이블 구조](/dbms/rdb-table-usage/table-structure-schema/) - [LOOKUP 테이블 구조](/dbms/lookup-table-usage/table-structure-schema/) - [VOLATILE 테이블 구조](/dbms/volatile-table-usage/table-structure-schema/) - [LOOKUP SEQUENCE](/dbms/lookup-table-usage/sequence-column/) --- title: "EXEC procedure와 ROLLUPGAP" url: https://docs.machbase.com/kr/dbms/reference/sql/syntax/execute-procedure-syntax/ language: kr kind: page --- # EXEC procedure와 ROLLUPGAP Machbase가 공개하는 table·ROLLUP 제어 procedure와 machsql 상태 명령을 설명합니다. ## 공통 EXEC 형식 ```text execute_procedure_stmt ::= 'EXEC' procedure_name [ '(' argument_list ')' ] ``` procedure별 인자 수는 고정됩니다. 이름이 없거나, 인자 수·타입이 다르거나, 대상 객체가 존재하지 않으면 오류를 반환합니다. ## TABLE_FLUSH ```sql EXEC TABLE_FLUSH(table_name); ``` | 항목 | 계약 | |---|---| | 인자 | table 이름 1개 | | Edition | Standard, Cluster | | 동작 | table의 pending storage/input buffer를 명시적으로 flush | | 반환 | ResultSet 없이 statement 성공 또는 오류 반환 | | 오류 | table 없음, 접근 불가, flush 처리 실패 | 검증이나 운영상 명시적인 storage flush가 필요할 때 사용합니다. transaction commit 또는 조회 가시성을 보장하는 수단은 아닙니다. 입력 row마다 호출하면 flush 비용이 증가하므로 반복 호출하지 않습니다. ## INDEX_FLUSH ```sql EXEC INDEX_FLUSH(table_name); EXEC INDEX_FLUSH(table_name, index_name); ``` table 이름만 지정하면 그 table의 모든 index build가 끝날 때까지 기다립니다. index 이름도 지정하면 해당 index만 대상으로 하며, 지정한 index가 해당 table에 속하지 않으면 오류입니다. ResultSet은 반환하지 않습니다. ## TABLE_REFRESH ```sql EXEC TABLE_REFRESH(lookup_table_name); ``` | 항목 | 계약 | |---|---| | 인자 | LOOKUP table 이름 1개 | | Edition | Standard, Cluster | | 동작 | 영속 LOOKUP 내용을 runtime memory table에 다시 반영 | | 이름 범위 | 현재 database의 table, `owner.table` 허용 | | 권한 | table owner 또는 허용된 관리 사용자 | | 쓰기 제한 | READ ONLY database에서는 실행할 수 없음 | | 반환 | ResultSet 없이 statement 성공 또는 오류 반환 | | 오류 | LOOKUP 이외의 table, table 없음, write admission 실패 | 실행 전 진행 중인 LOOKUP 변경과 조회 영향을 확인하고, 완료 후 row 수와 대표 key를 다시 조회합니다. ## FREEZE_TAG_INDEX와 UNFREEZE_TAG_INDEX ```sql EXEC FREEZE_TAG_INDEX(tag_table_name); EXEC UNFREEZE_TAG_INDEX(tag_table_name); ``` TAG table의 tag index를 freeze하거나 다시 해제하는 1인자 procedure입니다. TAG 이외의 table에는 사용할 수 없습니다. index 유지보수 경계를 직접 제어하는 운영 명령이므로 일반 입력 경로에서 상시 사용하지 말고, 실패 시 반드시 `UNFREEZE_TAG_INDEX` 실행 여부를 확인합니다. 두 명령 모두 ResultSet 없이 statement 성공 또는 오류를 반환합니다. ## ROLLUP_START와 ROLLUP_STOP ```sql EXEC ROLLUP_START; EXEC ROLLUP_START(rollup_name); EXEC ROLLUP_STOP; EXEC ROLLUP_STOP(rollup_name); ``` 두 procedure 모두 0인자와 rollup 이름 1인자 형식을 지원합니다. 이름을 지정하면 해당 ROLLUP을, 생략하면 현재 사용자 범위의 ROLLUP을 제어합니다. SYS의 무인자 실행은 전체 사용자 범위에 적용될 수 있으므로 대상 확인과 변경 승인이 필요합니다. 존재하지 않는 ROLLUP, 이미 시작한 대상의 START, 이미 중지한 대상의 STOP은 오류입니다. `V$ROLLUP.RUN_STATE`로 변경 결과를 확인합니다. ## ROLLUP_FORCE ```sql EXEC ROLLUP_FORCE; EXEC ROLLUP_FORCE(rollup_name); ``` 0인자 형식은 현재 사용자 범위의 기본 SEC→MIN→HOUR 계층을 처리합니다. 이름을 지정하면 해당 ROLLUP source의 현재 END_RID까지 따라잡도록 기다리는 동기 경로입니다. 중지된 ROLLUP은 먼저 START 상태인지 확인합니다. 완료 뒤 `V$ROLLUP`과 `SHOW ROLLUPGAP`으로 모든 관련 source 단계의 gap을 확인합니다. ## ROLLUP_REBUILD 태그와 시간 범위를 받는 4인자 Standard 전용 procedure입니다. 자세한 계약은 [ROLLUP_REBUILD](../rollup-rebuild-syntax/)를 정본으로 사용합니다. ## SHOW ROLLUPGAP ```sql SHOW ROLLUPGAP; ``` `SHOW ROLLUPGAP`은 서버 SQL이 아니라 **machsql 전용 client 명령**입니다. JDBC, ODBC와 SDK의 일반 SQL 실행 API에 같은 문자열을 보내지 마십시오. Standard 출력에는 source·ROLLUP table, source END_RID, ROLLUP END_RID, `GAP`, 상태와 wakeup 시간이 포함됩니다. Cluster 출력에는 `HOSTNAME`이 추가되어 노드별 상태를 표시합니다. `GAP = SRC_END_RID - ROLLUP_END_RID`이며, 계층의 모든 source→ROLLUP 행이 0인지 확인해야 전체 계층이 따라잡았다고 판단할 수 있습니다. ## 관련 문서 - [ROLLUP 운영과 상태](/dbms/tag-rollup-usage/ingestion-control-rollup/) - [V$ROLLUP 사전](/dbms/reference/system-catalog/vrollup/) - [machsql 명령](/dbms/reference/command-line-tools/machsql/) --- title: "16.1.2 데이터 타입 사전" url: https://docs.machbase.com/kr/dbms/reference/sql/types/ language: kr kind: section --- # 16.1.2 데이터 타입 사전 Machbase에서 지원하는 SQL 데이터 타입을 설명합니다. 타입은 저장할 값의 범위와 정밀도에 맞춰 선택합니다. 정수의 최솟값 또는 최댓값처럼 NULL 표현에 예약된 값은 일반 데이터로 사용할 수 없습니다. 아래 표의 `NULL 값`은 내부 표현이며, SQL에서는 `NULL`을 입력하고 `IS NULL`로 검사합니다. ## 데이터 타입 요약 | 타입 | 크기 | 값 범위 | NULL 값 | |------|------|---------|---------| | `SHORT` | 2 bytes | -32,767 ~ 32,767 | -32,768 | | `USHORT` | 2 bytes | 0 ~ 65,534 | 65,535 | | `INTEGER` | 4 bytes | -2,147,483,647 ~ 2,147,483,647 | -2,147,483,648 | | `UINTEGER` | 4 bytes | 0 ~ 4,294,967,294 | 4,294,967,295 | | `LONG` | 8 bytes | -9,223,372,036,854,775,807 ~ 9,223,372,036,854,775,807 | -9,223,372,036,854,775,808 | | `ULONG` | 8 bytes | 0 ~ 18,446,744,073,709,551,614 | 18,446,744,073,709,551,615 | | `FLOAT` | 4 bytes | 32비트 단정밀도 부동소수점 | 양수 최대값 | | `DOUBLE` | 8 bytes | 64비트 배정밀도 부동소수점 | 양수 최대값 | | `DECIMAL(M,D)` | precision에 따라 가변 | exact fixed-point, M: 1~65, D: 0~30 | - | | `ARRAY` | 요소 타입과 cardinality에 따라 가변 | 고정 길이 1차원 숫자 배열, cardinality 1~1024 | whole NULL과 element NULL 구분 | | `DATETIME` | 8 bytes | 1970-01-01 ~ 2262-04-11 (나노초 정밀도) | - | | `VARCHAR(n)` | 가변 | 최대 n 바이트 (LOG 선언 범위: 1~32,767) | - | | `IPV4` | 4 bytes | 0.0.0.0 ~ 255.255.255.255 | - | | `IPV6` | 16 bytes | 0000:...:0000 ~ FFFF:...:FFFF | - | | `TEXT` | 가변 | 0 ~ 64MB (전문 검색 인덱스 지원) | - | | `BINARY` | 가변 | LOG: 0~64MB / TAG: 1~32,767 bytes (고정 길이) | - | | `JSON` | 가변 | JSON 문서: 1~32,768 bytes / path: 1~512 bytes | - | --- ## 정수 타입 ### SHORT 16비트 부호 있는 정수 타입입니다. C의 `int16_t`와 저장 크기는 같지만 최소값 (-32,768)은 NULL 표현으로 예약됩니다. SQL에서는 `INT16` 별칭도 사용할 수 있습니다. ```sql CREATE LOG TABLE t (c1 SHORT); INSERT INTO t VALUES (-32767); -- 유효한 최솟값 INSERT INTO t VALUES (-32768); -- NULL로 처리됨 ``` ### USHORT 16비트 부호 없는 정수(`uint16_t`). 최대값(65,535)은 NULL로 인식됩니다. ### INTEGER 32비트 부호 있는 정수 타입입니다. C의 `int32_t`와 저장 크기는 같지만 최소값은 NULL 표현으로 예약됩니다. SQL에서는 `INT32` 또는 `INT` 별칭도 사용할 수 있습니다. ### UINTEGER 32비트 부호 없는 정수(`uint32_t`). ### LONG 64비트 부호 있는 정수 타입입니다. C의 `int64_t`와 저장 크기는 같지만 최소값은 NULL 표현으로 예약됩니다. SQL에서는 `INT64` 별칭도 사용할 수 있습니다. ### ULONG 64비트 부호 없는 정수(`uint64_t`). --- ## 부동소수점 타입 ### FLOAT C 언어의 32비트 부동소수점 타입 `float`와 동일합니다. 양수 최대값은 NULL로 인식됩니다. ### DOUBLE C 언어의 64비트 부동소수점 타입 `double`과 동일합니다. 양수 최대값은 NULL로 인식됩니다. --- ## 고정소수점 타입 ### DECIMAL / NUMERIC 선언한 전체 자릿수와 소수 자릿수 범위에서 10진수를 정확하게 저장하는 고정소수점 타입입니다. 입력값의 소수 자릿수가 선언한 범위를 넘으면 반올림이 발생할 수 있으므로 금액이나 비율을 저장할 때 필요한 자릿수를 먼저 정합니다. `NUMERIC`, `DEC`, `FIXED`, `NUMBER`는 `DECIMAL`의 별칭입니다. ```sql CREATE TRANSACTION TABLE invoice ( id LONG PRIMARY KEY, amount DECIMAL(18,2), rate NUMERIC(7,4) ); ``` `DECIMAL`은 `DECIMAL(10,0)`으로, `DECIMAL(M)`은 `DECIMAL(M,0)`으로 해석합니다. 선언 규칙, 반올림, 인덱스, 집계 및 클라이언트 매핑은 [DECIMAL과 NUMERIC 고정소수점 타입](decimal-numeric-fixed-point/)을 참고하십시오. --- ## ARRAY 타입 Machbase DBMS 8.7.0은 숫자 요소를 정해진 개수만큼 저장하는 고정 길이 1차원 `ARRAY` 타입을 지원합니다. 요소 타입 뒤에 cardinality를 지정합니다. ```sql CREATE LOG TABLE sensor_array ( id INTEGER, location DOUBLE[2], acceleration FLOAT[3] ); ``` 지원 요소 타입, NULL 구분, 입력·조회 문법과 SDK별 표현은 [숫자 ARRAY 타입](array/)을 참고하십시오. --- ## 날짜/시간 타입 ### DATETIME 1970년 1월 1일 자정 이후 경과된 시간의 나노초 값을 내부적으로 저장합니다. 표현 범위는 1970-01-01 00:00:00 000:000:000 ~ 2262-04-11 23:47:16.854:775:807입니다. - 나노초 단위까지 처리 가능 - 내부 표현: 8바이트 정수 (nanoseconds since epoch) - 문자열 표현: `YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn` ```sql -- 문자열에서 DATETIME으로 변환 SELECT TO_DATE('2024-01-15 10:30:00 000:000:000'); -- DATETIME에서 문자열로 변환 SELECT TO_CHAR(ts, 'YYYY-MM-DD HH24:MI:SS') FROM t; ``` --- ## 문자열 타입 ### VARCHAR(n) 가변 길이 문자열 타입입니다. `n`은 문자 수가 아니라 저장할 수 있는 바이트 수이며, LOG의 선언 범위는 1~32,767입니다. UTF-8 문자열은 문자에 따라 필요한 바이트 수가 다르므로 한글과 이모지 등을 저장할 때는 실제 인코딩 크기를 고려해 길이를 지정합니다. ```sql CREATE LOG TABLE t (name VARCHAR(100), description VARCHAR(1000)); ``` ### TEXT VARCHAR 크기를 초과하는 대용량 텍스트를 저장하기 위한 타입입니다. 최대 64MB를 저장할 수 있습니다. 텍스트 저장 지원과 KEYWORD 인덱스 지원은 구분하며, 인덱스 사용 가능 여부는 테이블 유형에 따라 다릅니다. - LOG와 Standard Edition의 TRANSACTION 테이블에서 지원 - LOG 테이블은 KEYWORD 인덱스와 `SEARCH` 연산자로 키워드 검색 가능 - TAG, LOOKUP, VOLATILE 테이블에서는 지원하지 않음 LOG의 TEXT 컬럼 자체에는 ORDER BY·GROUP BY를 적용할 수 없습니다. 단순 성능 권장사항이 아니라 쿼리 검증에서 거부되는 제약입니다. 정렬·집계에 사용할 장치, 오류 코드, 등급 등은 별도 VARCHAR·숫자 컬럼으로 두세요. TEXT를 VARCHAR로 변경하려고 `MODIFY COLUMN`을 사용하는 것도 지원하지 않습니다. ```sql CREATE LOG TABLE log_table (ts DATETIME, message TEXT); -- 키워드 인덱스 생성 CREATE INDEX idx_msg ON log_table (message) INDEX_TYPE KEYWORD; ``` --- ## 바이너리 타입 ### BINARY 이미지, 문서 등 비정형 바이너리 데이터를 저장하는 타입입니다. - **LOG 테이블**: 가변 길이, 최대 64MB - **TRANSACTION 테이블**: 가변 길이 바이너리 값 지원 (Standard Edition) - **TAG 테이블**: `BINARY(n)` 형식의 고정 길이 변형, 1 ~ 32,767 bytes - LOOKUP, VOLATILE 테이블에서는 지원하지 않음 TAG 테이블의 `BINARY(n)`: - `X'...'`, `B'...'`, `O'...'` 리터럴 지원 (소문자 prefix도 지원) - 기존 호환성을 위해 `'0x...'` 형식도 지원 - 선언된 길이를 초과하면 `ERR-02233` 오류 발생 --- ## 네트워크 주소 타입 ### IPV4 IPv4 주소를 저장하는 타입입니다. 내부적으로 4바이트를 사용하며 `"0.0.0.0"` ~ `"255.255.255.255"` 범위를 표현합니다. ```sql CREATE LOG TABLE access_log (ts DATETIME, src_ip IPV4, dst_ip IPV4); INSERT INTO access_log VALUES (NOW, '192.168.0.1', '10.0.0.1'); SELECT * FROM access_log WHERE src_ip = TO_IPV4('192.168.0.1'); ``` ### IPV6 IPv6 주소를 저장하는 타입입니다. 내부적으로 16바이트를 사용합니다. 축약 표기도 지원합니다. - `"::FFFF:1232"` — 선행 0 생략 - `"::FFFF:192.168.0.3"` — IPv4 호환 표기 - `"::192.168.3.1"` — IPv4 호환 표기 (deprecated) ```sql CREATE LOG TABLE v6_log (ts DATETIME, src_ip IPV6); INSERT INTO v6_log VALUES (NOW, '21DA:D3:0:2F3B:2AA:FF:FE28:9C5A'); ``` --- ## JSON 타입 JSON 문서를 저장하는 타입입니다. "Key-Value" 쌍으로 구성된 JSON 데이터를 텍스트 형식으로 저장합니다. - 데이터 최대 크기: 32,768 bytes - JSON path 최대 길이: 512 bytes - TAG, LOG, LOOKUP, TRANSACTION 테이블에서 지원 - VOLATILE 테이블에서는 JSON 컬럼 생성 불가 - LOOKUP 테이블의 JSON 컬럼은 primary key로 사용할 수 없음 ```sql CREATE LOG TABLE sensor_data ( ts DATETIME, data JSON ); INSERT INTO sensor_data VALUES (NOW, '{"temp":23.5,"hum":60}'); SELECT data -> 'temp' AS temperature FROM sensor_data; ``` 자세한 테이블 타입별 JSON 지원 범위는 [JSON 타입의 테이블 타입별 지원 범위](table-types-type-support-scope-json/)를 참고하십시오. --- ## SQL 데이터 타입 매핑 Machbase 데이터 타입과 SQL 표준 타입 및 C 타입의 대응 관계입니다. | Machbase 타입 | Machbase CLI 타입 | SQL 타입 | C 타입 | C 기본 타입 | |--------------|------------------|----------|--------|------------| | `short` | SQL_SMALLINT | SQL_SMALLINT | SQL_C_SSHORT | `int16_t` | | `ushort` | SQL_USMALLINT | SQL_SMALLINT | SQL_C_USHORT | `uint16_t` | | `integer` | SQL_INTEGER | SQL_INTEGER | SQL_C_SLONG | `int32_t` | | `uinteger` | SQL_UINTEGER | SQL_INTEGER | SQL_C_ULONG | `uint32_t` | | `long` | SQL_BIGINT | SQL_BIGINT | SQL_C_SBIGINT | `int64_t` | | `ulong` | SQL_UBIGINT | SQL_BIGINT | SQL_C_UBIGINT | `uint64_t` | | `float` | SQL_FLOAT | SQL_REAL | SQL_C_FLOAT | `float` | | `double` | SQL_DOUBLE | SQL_FLOAT, SQL_DOUBLE | SQL_C_DOUBLE | `double` | | `decimal` | SQL_DECIMAL | SQL_DECIMAL, SQL_NUMERIC | SQL_C_NUMERIC | decimal-preserving value | | `datetime` | SQL_TIMESTAMP | SQL_TYPE_TIMESTAMP | SQL_C_TYPE_TIMESTAMP | `char *` (YYYY-MM-DD ...) | | `varchar` | SQL_VARCHAR | SQL_VARCHAR | SQL_C_CHAR | `char *` | | `ipv4` | SQL_IPV4 | SQL_VARCHAR | SQL_C_CHAR | `char *` (IP 문자열) | | `ipv6` | SQL_IPV6 | SQL_VARCHAR | SQL_C_CHAR | `char *` (IP 문자열) | | `text` | SQL_TEXT | SQL_LONGVARCHAR | SQL_C_CHAR | `char *` | | `binary` | SQL_BINARY | SQL_BINARY | SQL_C_BINARY | `char *` | | `json` | SQL_JSON | SQL_JSON | SQL_C_CHAR | `json_t` | --- ## 테이블 타입별 지원 데이터 타입 | 타입 | TAG | LOG | LOOKUP | VOLATILE | TRANSACTION | |------|:---:|:---:|:------:|:--------:|:---:| | SHORT | O | O | O | O | O | | USHORT | O | O | O | O | O | | INTEGER | O | O | O | O | O | | UINTEGER | O | O | O | O | O | | LONG | O | O | O | O | O | | ULONG | O | O | O | O | O | | FLOAT | O | O | O | O | O | | DOUBLE | O | O | O | O | O | | DECIMAL / NUMERIC | O | O | O | O | O | | DATETIME | O | O | O | O | O | | VARCHAR | O | O | O | O | O | | IPV4 | O | O | O | O | O | | IPV6 | O | O | O | O | O | | TEXT | X | O | X | X | O | | JSON | O | O | O | X | O | | BINARY | O (고정 길이) | O | X | X | O | DECIMAL은 모든 public 테이블 타입에서 지원합니다. TRANSACTION 테이블 자체는 Standard Edition에서 사용하며, Cluster Edition에서는 LOG/TAG 테이블의 DECIMAL 컬럼과 DDL 전파를 지원합니다. --- title: "JSON 타입의 테이블 타입별 지원 범위" url: https://docs.machbase.com/kr/dbms/reference/sql/types/table-types-type-support-scope-json/ language: kr kind: page --- # JSON 타입의 테이블 타입별 지원 범위 JSON 타입 컬럼을 각 테이블 타입에서 사용할 때의 지원 범위를 정리합니다. ## 지원 범위 요약 | 테이블 타입 | JSON 컬럼 생성 | JSON path query | JSON PK | 비고 | |------------|:-------------:|:---------------:|:-------:|------| | TAG | O | O | X | JSON 컬럼과 JSON 함수 지원, PK는 미지원 | | LOG | O | O | X | JSON 컬럼과 JSON 함수 지원 | | LOOKUP | O | O | X | 일반 컬럼으로 지원, JSON path index는 미지원 | | VOLATILE | X | X | X | JSON 컬럼 생성 불가 | | TRANSACTION | O | O | X | JSON 컬럼과 JSON 함수 지원 | ## LOOKUP 테이블 LOOKUP 테이블은 JSON 타입 컬럼을 일반 컬럼으로 지원합니다. ```sql CREATE LOOKUP TABLE config_lookup ( key VARCHAR(64) PRIMARY KEY, site VARCHAR(32), config JSON ); INSERT INTO config_lookup VALUES ( 'device-001', 'SEOUL', '{"region":"kr","level":3,"state":"ready"}' ); SELECT key FROM config_lookup WHERE config->'$.region' = 'kr' AND JSON_EXTRACT_INTEGER(config, '$.level') >= 3; ``` JSON 컬럼은 `JSON_SET`, `JSON_SET_JSON`, `JSON_REMOVE` 등 JSON 함수로 갱신할 수 있습니다. ```sql UPDATE config_lookup SET config = JSON_SET(config, '$.state', 'active') WHERE site = 'SEOUL'; ``` 단, JSON 컬럼은 primary key로 선언할 수 없습니다. ```sql -- 오류 CREATE LOOKUP TABLE invalid_lookup ( config JSON PRIMARY KEY ); ``` ## VOLATILE 테이블 VOLATILE 테이블은 JSON 타입 컬럼 생성을 지원하지 않습니다. ```sql CREATE VOLATILE TABLE session_data ( session_id VARCHAR(64) PRIMARY KEY, payload JSON ); ``` ## JSON 관련 함수 테이블 타입별 지원 | 함수/연산자 | TAG | LOG | LOOKUP | VOLATILE | TRANSACTION | |-------------|:---:|:---:|:------:|:--------:|:---:| | `->` 연산자 | O | O | O | X | O | | `JSON_EXTRACT*` | O | O | O | X | O | | `JSON_TYPEOF` | O | O | O | X | O | | `JSON_IS_VALID` | O | O | O | O | O | | `JSON_SET` | O | O | O | X | O | | `JSON_SET_JSON` | O | O | O | X | O | | `JSON_REMOVE` | O | O | O | X | O | ## 사용 주의사항 - JSON path 문자열은 작은따옴표(`'$.key'`)로 작성합니다. - 숫자 비교에는 `JSON_EXTRACT_INTEGER`, `JSON_EXTRACT_DOUBLE` 같은 타입별 함수를 사용합니다. - LOOKUP 테이블은 JSON path별 전용 인덱스를 지원하지 않으므로 고빈도 검색 값은 별도 컬럼으로 분리합니다. --- title: "DECIMAL과 NUMERIC 고정소수점" url: https://docs.machbase.com/kr/dbms/reference/sql/types/decimal-numeric-fixed-point/ language: kr kind: page --- # DECIMAL과 NUMERIC 고정소수점 `DECIMAL`은 10진수 값을 오차 없이 저장하는 exact fixed-point 타입입니다. `NUMERIC`, `DEC`, `FIXED`, `NUMBER`는 `DECIMAL`의 alias이며 `DESC`, `SHOW`와 결과 메타데이터에서는 canonical 이름인 `DECIMAL`로 표시됩니다. `NUMBER`는 MySQL alias가 아닌 Machbase 호환 확장 alias입니다. ## 선언 문법 ```sql DECIMAL DECIMAL(precision) DECIMAL(precision, scale) NUMERIC NUMERIC(precision) NUMERIC(precision, scale) ``` | 선언 | 해석 | |------|------| | `DECIMAL` | `DECIMAL(10,0)` | | `DECIMAL(M)` | `DECIMAL(M,0)` | | `DECIMAL(M,D)` | precision `M`, scale `D` | - precision은 전체 유효 숫자 수이며 `1`부터 `65`까지 지정합니다. - scale은 소수점 이하 숫자 수이며 `0`부터 `30`까지 지정합니다. - scale은 precision보다 클 수 없습니다. - `UNSIGNED`와 `ZEROFILL`은 지원하지 않습니다. ```sql CREATE TRANSACTION TABLE invoice ( invoice_id LONG PRIMARY KEY, amount DECIMAL(18,2), tax_rate NUMERIC(7,4) ); ``` ## 반올림과 범위 초과 입력 값의 소수 자릿수가 scale을 초과하면 0에서 멀어지는 방향의 절반 올림 (round-half-away-from-zero)을 적용합니다. ```sql CREATE TRANSACTION TABLE decimal_rounding ( id INTEGER PRIMARY KEY, amount DECIMAL(5,2) ); INSERT INTO decimal_rounding VALUES (1, 1.235); -- 1.24 INSERT INTO decimal_rounding VALUES (2, -1.235); -- -1.24 ``` precision을 초과하는 값은 잘라내거나 부동소수점으로 변환하지 않고 오류로 처리합니다. `DECIMAL`의 NULL은 특정 숫자 값을 sentinel로 사용하지 않고 값과 별도로 관리합니다. ## 테이블 타입별 지원 | 테이블 타입 | DECIMAL 컬럼 | 주요 사용 위치 | |------------|:------------:|----------------| | LOG | O | 금액·정산 이벤트, exact 집계 | | TAG | O | exact 계측값과 집계 대상 데이터 컬럼 | | VOLATILE | O | 상태·캐시 값, primary key | | LOOKUP | O | 기준 금액·비율, primary key와 보조 인덱스 | | TRANSACTION | O | 관계형 업무 데이터, PK/UNIQUE/일반 인덱스 | ```sql CREATE LOG TABLE payment_log ( occurred_at DATETIME, amount DECIMAL(18,2) ); CREATE TAG TABLE meter_value ( name VARCHAR(80) PRIMARY KEY, time DATETIME BASETIME, value DECIMAL(24,6) ); CREATE VOLATILE TABLE exchange_cache ( rate_key DECIMAL(12,6) PRIMARY KEY, label VARCHAR(32) ); CREATE LOOKUP TABLE price_rule ( rule_id LONG PRIMARY KEY, amount DECIMAL(18,2) ); ``` Cluster Edition에서는 LOG/TAG 테이블의 DECIMAL 컬럼과 DDL 전파를 지원합니다. TRANSACTION 테이블은 DECIMAL 타입과 무관하게 Standard Edition에서 사용합니다. ## 비교와 인덱스 모든 테이블 엔진은 동일한 DECIMAL 비교 규칙을 사용합니다. 표현 scale이 달라도 수치가 같으면 동일한 값으로 비교합니다. ```sql -- 1, 1.0, 1.00은 equality, PK, UNIQUE 비교에서 같은 값입니다. SELECT * FROM price_rule WHERE amount = 1.00; ``` VOLATILE과 LOOKUP의 primary key 메모리 인덱스, TRANSACTION 테이블의 일반·UNIQUE·PRIMARY KEY 인덱스에서 DECIMAL을 사용할 수 있습니다. TRANSACTION 인덱스는 equality, range, ordering에 같은 수치 순서를 적용합니다. VIEW의 derived column도 DECIMAL precision과 scale을 유지합니다. `DESC`, `SHOW`, `M$SYS_COLUMNS`와 클라이언트 result metadata에서 precision과 scale을 각각 확인할 수 있습니다. ## 식과 집계 함수 `+`, `-`, `*`, `/`, `ROUND`, `TRUNC`, [CAST](../../functions/functions-full/#cast)와 다음 집계·정렬 연산에서 DECIMAL 값을 사용할 수 있습니다. - `SUM`, `AVG`, `MIN`, `MAX` - `GROUP BY`, `ORDER BY`, `DISTINCT` exact DECIMAL 경로가 없는 고급 통계 함수, percentile, `TOP_K` 등은 DECIMAL 값을 DOUBLE로 변환해 계산하므로 결과가 근삿값일 수 있습니다. ## 입출력과 클라이언트 매핑 machloader의 `.fmt`, CSV import/export와 Append 경로는 부호, NULL, precision과 scale을 보존합니다. 부동소수점 타입을 경유하지 않고 문자열 또는 각 언어의 decimal 타입으로 전달하십시오. | 인터페이스 | 권장 매핑 | |-----------|-----------| | ODBC | `SQL_DECIMAL` / `SQL_NUMERIC`, `SQL_C_NUMERIC` | | JDBC | `java.math.BigDecimal` | | Python | `decimal.Decimal` | | Node.js | decimal-compatible 문자열 또는 connector의 decimal 표현 | | .NET | `decimal`, `DbType.Decimal` | Go에서 NUMERIC 값을 처리할 때도 `float64`로 변환하지 말고 connector가 제공하는 decimal-preserving 값 또는 문자열 표현을 사용합니다. ## 타입 선택 - 통화, 세율, 정산값처럼 10진수 정확성이 필요하면 `DECIMAL`을 사용합니다. - 센서 실수처럼 근삿값과 넓은 지수 범위가 중요하면 `FLOAT` 또는 `DOUBLE`을 사용합니다. - 저장·비교·연산 중 DECIMAL 값을 DOUBLE로 변환하면 exact fixed-point 의미가 사라집니다. --- title: "숫자 ARRAY" url: https://docs.machbase.com/kr/dbms/reference/sql/types/array/ language: kr kind: page --- # 숫자 ARRAY Machbase DBMS 8.7.0은 같은 숫자 타입의 값을 정해진 개수만큼 저장하는 고정 길이 1차원 `ARRAY` 타입을 지원합니다. 센서의 좌표, 축별 측정값처럼 하나의 행에 여러 숫자 값을 함께 저장하고 요소별로 조회할 때 사용합니다. 일부 위치만 입력하는 방법과 선택 컬럼 Append API는 [Sparse ARRAY와 선택 컬럼 Append API](../../../../development-tools-integration/data-input-load-export/array-append/)를 참고하십시오. ## 지원 타입과 선언 범위 컬럼을 선언할 때 숫자 요소 타입 뒤에 `[cardinality]`를 붙입니다. cardinality는 배열에 저장할 요소 개수이며, 각 행에서 NULL이 아닌 요소의 개수가 아니라 컬럼에 선언한 고정 길이를 뜻합니다. 배열 전체가 없는 상태(whole NULL)와 배열 안의 특정 값만 없는 상태 (element NULL)는 서로 구분합니다. | 요소 타입 | DDL 예 | 설명 | |---|---|---| | `INT16` | `INT16[4]` | signed 16-bit 정수 | | `UINT16` | `UINT16[4]` | unsigned 16-bit 정수 | | `INT32` | `INT32[4]` | signed 32-bit 정수 | | `UINT32` | `UINT32[4]` | unsigned 32-bit 정수 | | `INT64` | `INT64[4]` | signed 64-bit 정수 | | `UINT64` | `UINT64[4]` | unsigned 64-bit 정수 | | `FLOAT` | `FLOAT[4]` | 단정밀도 부동소수점 | | `DOUBLE` | `DOUBLE[4]` | 배정밀도 부동소수점 | | `DECIMAL(p,s)` | `DECIMAL(12,4)[4]` | 고정소수점 숫자 | - cardinality 범위는 `1..1024`입니다. - `DECIMAL` precision 범위는 `1..65`입니다. - `DECIMAL` scale 범위는 `0..30`이며 precision보다 클 수 없습니다. 다음 별칭은 해당 대표 요소 타입으로 처리됩니다. | 별칭 | 대표 타입 | |---|---| | `SHORT` | `INT16` | | `USHORT` | `UINT16` | | `INT`, `INTEGER` | `INT32` | | `UINTEGER` | `UINT32` | | `LONG` | `INT64` | | `ULONG` | `UINT64` | | `NUMERIC`, `DEC`, `FIXED`, `NUMBER` | `DECIMAL` | ## 테이블 생성과 컬럼 추가 다음 예제는 네 개의 채널 값, 세 개의 누적값, 두 개의 고정소수점 값을 저장합니다. ```sql CREATE LOG TABLE SENSOR_ARRAY ( ID INTEGER, CHANNELS DOUBLE[4], COUNTERS UINT64[3], AMOUNTS DECIMAL(12,4)[2] ); ``` 기존에 `ADD COLUMN`을 지원하는 테이블에는 같은 ARRAY 선언을 사용해 컬럼을 추가하고 기존 `DROP COLUMN` 문법으로 제거할 수 있습니다. ```sql ALTER TABLE SENSOR_ARRAY ADD COLUMN (STATUS_VALUES INT32[3]); ALTER TABLE SENSOR_ARRAY ADD COLUMN (LIMITS DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]); ALTER TABLE SENSOR_ARRAY DROP COLUMN (STATUS_VALUES); ALTER TABLE SENSOR_ARRAY DROP COLUMN (LIMITS); ``` `DECIMAL(12)[2]`처럼 scale을 생략하면 `DECIMAL(12,0)[2]`로 처리합니다. TAG METADATA ARRAY는 `METADATA ADD COLUMN`과 `METADATA DROP COLUMN`을 사용합니다. ```sql ALTER TABLE SENSOR_TAG METADATA ADD COLUMN (LIMITS DECIMAL(12,4)[2] DEFAULT [0.0000, NULL]); ALTER TABLE SENSOR_TAG METADATA DROP COLUMN (LIMITS); ``` `ARRAY`는 다음 위치의 일반 데이터 컬럼에 사용할 수 있습니다. - LOG 테이블 - TAG DATA의 일반 DATA 컬럼 - TAG METADATA의 일반 metadata 컬럼 - VOLATILE 테이블 - LOOKUP 테이블 - Standard Edition의 TRANSACTION 테이블 `ARRAY` 추가가 각 테이블의 기존 DML 범위를 넓히지는 않습니다. 예를 들어 LOG 테이블의 `UPDATE`는 계속 지원하지 않으며 TAG 테이블의 `UPDATE`도 기존에 허용된 DATA 또는 METADATA 경로만 사용할 수 있습니다. ### ADD COLUMN 지원 범위 | Edition | 테이블 또는 컬럼 영역 | ARRAY ADD/DROP | |---|---|:---:| | Standard | LOG | O | | Standard | VOLATILE | O | | Standard | LOOKUP | O | | Standard | TRANSACTION | O | | Standard | TAG METADATA | O | | Standard | TAG DATA 일반 컬럼 | X | | Cluster | LOG | O | | Cluster | 그 외 테이블 또는 TAG METADATA | X | TAG DATA 일반 ARRAY 컬럼은 `CREATE TABLE`에서 선언할 수 있지만 ALTER로 추가할 수 없습니다. ### DEFAULT와 기존 행 - DEFAULT가 없으면 ALTER 전에 존재한 행의 새 ARRAY 컬럼은 전체 NULL입니다. - LOG, LOOKUP, TRANSACTION과 TAG METADATA에서는 명시한 ARRAY DEFAULT를 기존 행에 적용합니다. - VOLATILE은 단일 값 컬럼의 `ADD COLUMN`과 마찬가지로 기존 행을 DEFAULT로 다시 쓰지 않으므로 새 ARRAY 컬럼은 전체 NULL입니다. - Cluster LOG는 명시한 ARRAY DEFAULT를 기존 행에 적용합니다. - DEFAULT 배열 생성식의 요소 수는 선언한 cardinality와 정확히 같아야 합니다. ALTER 이후에 TAG DATA의 INSERT 또는 Append로 새 태그가 자동 등록되면, 새 메타데이터 행에는 ADD COLUMN의 DEFAULT를 적용하지 않습니다. 추가한 ARRAY 메타데이터 컬럼은 전체 NULL로 생성됩니다. 이 DEFAULT는 ALTER 전에 존재한 메타데이터 행에만 적용됩니다. 다음 역할에는 `ARRAY`를 사용할 수 없습니다. - PRIMARY KEY, UNIQUE 또는 일반 인덱스 키 - `AUTO_INCREMENT`, `SEQUENCE` - TAG 테이블의 NAME, BASETIME, BASE DISTANCE, SUMMARIZED 컬럼 TAG METADATA ARRAY 컬럼에는 자동 index를 생성하지 않으며 명시적 index도 지원하지 않습니다. 다음 선언은 지원하지 않습니다. ```sql INT32[] INT32[0] INT32[1025] VARCHAR[4] INT32[2][3] DECIMAL[4](12,4) ``` ## ARRAY 값 입력 `ARRAY[...]`와 축약형 `[...]`를 모두 사용할 수 있습니다. ```sql INSERT INTO SENSOR_ARRAY VALUES (1, ARRAY[1.5, NULL, 3.5, 4.5], [1, NULL, 3], [12.3400, NULL]); INSERT INTO SENSOR_ARRAY VALUES (2, [10.0, 20.0, 30.0, 40.0], [4, 5, 6], [1.2500, 2.5000]); INSERT INTO SENSOR_ARRAY VALUES (3, NULL, NULL, NULL); ``` constructor의 요소 수는 대상 컬럼의 cardinality와 정확히 같아야 합니다. 길이가 다르면 padding하거나 자르지 않고 문장을 실패시킵니다. 빈 `[]`와 `ARRAY[]`도 cardinality가 0인 저장값으로 사용할 수 없습니다. 요소마다 대상 숫자 타입의 변환, 부호, 범위, `DECIMAL` precision과 scale 규칙을 적용합니다. 한 요소라도 변환할 수 없으면 문장 전체가 실패하고 부분 `ARRAY`를 저장하지 않습니다. ### 숫자 범위 정수 타입은 내부 NULL sentinel을 실제 값으로 저장할 수 없습니다. | 타입 | 저장 가능한 범위 | |---|---| | `INT16` | `-32767..32767` | | `UINT16` | `0..65534` | | `INT32` | `-2147483647..2147483647` | | `UINT32` | `0..4294967294` | | `INT64` | `-9223372036854775807..9223372036854775807` | | `UINT64` | `0..18446744073709551614` | `FLOAT`와 `DOUBLE`의 예약된 최대 finite NULL sentinel 값도 실제 요소로 저장할 수 없습니다. 그보다 큰 입력의 Infinity 처리는 대응 scalar 타입과 동일합니다. ### 대상이 없는 ARRAY 타입 추론 `SELECT [1,2,3]`처럼 대상 컬럼이 없는 문맥에서는 전체 요소에서 공통 타입을 추론합니다. - 모든 non-NULL 요소가 같은 타입이면 그 타입을 유지합니다. - signed와 unsigned 정수는 모든 값을 담을 수 있는 가장 작은 정수 타입으로 승격합니다. - signed 정수와 `UINT64`가 함께 있으면 `DECIMAL(20,0)`을 사용합니다. - `DECIMAL`끼리는 필요한 정수 자릿수와 scale을 합칩니다. - `FLOAT`만 있으면 `FLOAT`를 유지하고 다른 숫자 타입과 혼합하면 `DOUBLE`로 승격합니다. - 빈 배열, 모든 요소가 NULL인 배열, 숫자가 아닌 요소 또는 중첩 배열은 추론 오류입니다. INSERT, UPDATE 또는 prepared parameter처럼 대상 컬럼이 있으면 대상 컬럼의 요소 타입, cardinality와 `DECIMAL` 메타데이터로 각 요소를 검증합니다. ### whole NULL과 element NULL `ARRAY` 자체의 NULL과 NULL 요소를 가진 `ARRAY`는 서로 다른 값입니다. ```sql -- ARRAY 자체가 NULL입니다. INSERT INTO SENSOR_ARRAY (ID, CHANNELS) VALUES (10, NULL); -- ARRAY는 존재하며 네 요소가 모두 NULL입니다. INSERT INTO SENSOR_ARRAY (ID, CHANNELS) VALUES (11, [NULL, NULL, NULL, NULL]); ``` `NOT NULL`은 `ARRAY` 전체의 NULL만 제한합니다. 따라서 모든 요소가 NULL인 `ARRAY`는 `NOT NULL` 컬럼에도 입력할 수 있습니다. ## 요소 조회 요소 위치는 0부터 시작합니다. cardinality가 4이면 유효한 위치는 `0..3`입니다. ```sql SELECT CHANNELS, CHANNELS[0] AS FIRST_CHANNEL, CHANNELS[3] AS LAST_CHANNEL, CHANNELS[4] AS OUT_OF_RANGE FROM SENSOR_ARRAY; ``` 다음 경우에는 오류 대신 SQL `NULL`을 반환합니다. - 인덱스가 음수 또는 cardinality 이상인 경우 - 인덱스 표현식이 SQL NULL인 경우 - `ARRAY` 전체가 NULL인 경우 - 해당 요소가 NULL인 경우 {{< callout type="warning" >}} 요소 위치를 나타내는 `[위치]`는 따옴표로 감싸지 않은 단순 컬럼명 뒤에만 붙일 수 있습니다. `A[0]`은 가능하지만 `T.A[0]`과 `"A"[0]`은 지원하지 않습니다. {{< /callout >}} ## ARRAY_LENGTH `ARRAY_LENGTH()`는 배열 전체가 NULL이 아니면 선언한 요소 개수(cardinality)를 반환합니다. ```sql SELECT ID, ARRAY_LENGTH(CHANNELS) FROM SENSOR_ARRAY; ``` 모든 요소가 NULL이어도 cardinality를 반환합니다. whole NULL은 NULL을 반환합니다. 타입 정보가 없는 `ARRAY_LENGTH(NULL)`은 인자 타입을 결정할 수 없으므로 오류입니다. ## ARRAY 전체 CAST 같은 cardinality의 숫자 `ARRAY`는 `CAST(array_expression AS TYPE[N])`로 요소 타입을 전체 변환할 수 있습니다. ```sql SELECT CAST(CHANNELS AS INT32[4]) FROM SENSOR_ARRAY; SELECT CAST(AMOUNTS AS DECIMAL(10,2)[2]) FROM SENSOR_ARRAY; ``` - 입력은 숫자 `ARRAY` 또는 SQL `NULL`이어야 합니다. - 대상에는 이 문서의 숫자 요소 타입과 별칭을 사용할 수 있습니다. - 입력과 대상 cardinality는 정확히 같아야 합니다. - whole NULL과 각 element NULL은 변환 뒤에도 유지됩니다. - 각 non-NULL 요소에는 대응하는 scalar CAST의 숫자 변환 규칙을 적용합니다. - `DECIMAL[N]`은 `DECIMAL(10,0)[N]`, `DECIMAL(p)[N]`은 `DECIMAL(p,0)[N]`으로 처리합니다. - 한 요소라도 범위나 변환 규칙을 위반하면 CAST와 이를 포함한 문장 전체가 실패합니다. 준비된 문장(prepared statement)에서는 CAST 대상이 매개변수와 결과의 요소 타입, cardinality, DECIMAL의 전체·소수 자릿수를 결정합니다. 같은 문장에 ARRAY 값, 전체 NULL, 일부 위치만 지정한 sparse ARRAY를 다시 바인딩할 수 있습니다. ```sql SELECT CAST(? AS INT32[3]); SELECT CAST(? AS DECIMAL(12,4)[3]); ``` `CASE`와 `UNION ALL`에서 ARRAY 결과를 결합하려면 요소 타입, cardinality와 DECIMAL precision/scale이 모두 같아야 합니다. 서로 다르면 명시적으로 같은 ARRAY 타입으로 CAST한 뒤 결합합니다. 다음 변환은 지원하지 않습니다. - scalar 값을 ARRAY로 확장 - ARRAY를 scalar로 축소 - 서로 다른 cardinality 사이의 padding 또는 truncation - 문자열, 날짜, IP, BINARY, JSON ARRAY 대상 전체 문법, 숫자 변환과 오류 규칙은 [CAST 함수](../../functions/functions-full/#cast)를 참고하십시오. ## 비교와 표현식 `ARRAY` 전체에 `=`, `<>`, `IS NULL`, `IS NOT NULL`을 사용할 수 있습니다. 같은 위치의 NULL 요소끼리는 전체 `ARRAY` 동등 비교에서 일치합니다. whole NULL 비교는 일반 SQL NULL 규칙을 따릅니다. ```sql SELECT ID FROM SENSOR_ARRAY WHERE CHANNELS = [1.5, NULL, 3.5, 4.5] OR CHANNELS[1] IS NULL; ``` 요소 표현식은 해당 숫자 타입의 일반 표현식과 조건식에 사용할 수 있습니다. 반면 전체 `ARRAY`를 다음 위치에 사용하는 기능은 지원하지 않습니다. - `DISTINCT` - `GROUP BY` - `ORDER BY` - 집계 함수의 DISTINCT 인자 ## VIEW, INSERT SELECT, CASE와 upsert VIEW와 `INSERT ... SELECT`는 요소 타입, cardinality, `DECIMAL` precision과 scale을 보존합니다. 서로 다른 숫자 `ARRAY` 타입으로 입력하면 요소마다 대상 타입으로 변환하고, 한 요소라도 변환할 수 없으면 문장 전체가 실패합니다. INSERT와 UPDATE의 `CASE` 결과에도 대상 `ARRAY` 계약을 적용합니다. ```sql UPDATE SENSOR_LOOKUP SET AMOUNTS = CASE WHEN ID = 1 THEN [12345678.1234, NULL] ELSE AMOUNTS END WHERE ID = 1; ``` LOOKUP 또는 VOLATILE 테이블의 duplicate-key upsert에서도 direct `ARRAY`, 상수 `CASE`, prepared whole-ARRAY bind에 같은 변환 규칙을 적용합니다. upsert의 오른쪽 식에서 기존 행 컬럼을 참조할 수 있는지는 각 테이블의 기존 정책을 따릅니다. ## 메타데이터와 표시 형식 `DESC`와 SQL export에는 canonical 선언을 표시합니다. ```sql DESC SENSOR_ARRAY; ``` 시스템 카탈로그는 `ARRAY` type code, cardinality, precision과 scale을 개별 필드로 보존합니다. SQLCLI 또는 ODBC의 `SQLColumns()`는 다음 정보를 반환합니다. - `DATA_TYPE`: `SQL_MACHBASE_ARRAY` - `TYPE_NAME`: `INT32[3]`, `DECIMAL(12,4)[2]` 같은 canonical 선언 - `COLUMN_SIZE`: cardinality - `DECIMAL_DIGITS`: `DECIMAL` 요소의 scale `machsql`, 범용 ODBC text 조회와 Go `database/sql`처럼 문자열 결과가 필요한 경로는 `[value,null,value]` 형식을 사용합니다. 소문자 `null`은 element NULL이며 컬럼 결과의 SQL `NULL`은 whole NULL입니다. ## SDK에서 ARRAY 읽기와 쓰기 다음 예제는 공통 테이블과 데이터를 사용합니다. ```sql CREATE LOG TABLE SDK_ARRAY_SAMPLE ( ID INTEGER, A_I32 INT32[3], A_U64 UINT64[3], A_DEC DECIMAL(12,4)[3] ); INSERT INTO SDK_ARRAY_SAMPLE VALUES (1, [1,NULL,-3], [1,NULL,18446744073709551614], [1.2500,NULL,-3.7500]); INSERT INTO SDK_ARRAY_SAMPLE (ID) VALUES (2); ``` whole NULL은 각 SDK의 NULL 값으로 표현하고 element NULL은 collection 내부의 NULL 값으로 표현합니다. `UINT64`와 `DECIMAL`은 SDK가 제공하는 정밀도 보존 타입을 사용해야 합니다. ### C SQLCLI typed fetch에는 `SQL_C_MACHBASE_ARRAY`와 `SQL_MACHBASE_ARRAY_DESC`를 사용합니다. ```c SQLINTEGER values[3] = {0}; SQLLEN elements[3] = {0}; SQLLEN outer = 0; SQL_MACHBASE_ARRAY_DESC array = {0}; array.struct_size = sizeof(array); array.element_c_type = SQL_C_SLONG; array.capacity = 3; array.values = values; array.element_indicators = elements; SQLExecDirect(stmt, (SQLCHAR*)"SELECT A_I32 FROM SDK_ARRAY_SAMPLE WHERE ID=1", SQL_NTS); SQLBindCol(stmt, 1, SQL_C_MACHBASE_ARRAY, &array, sizeof(array), &outer); SQLFetch(stmt); /* outer != SQL_NULL_DATA, array.count == 3, * values[0] == 1, elements[1] == SQL_NULL_DATA, values[2] == -3 */ ``` whole NULL이면 `outer == SQL_NULL_DATA`이고 `array.count == 0`입니다. NULL 요소를 구분하려면 `element_indicators`를 제공해야 합니다. `DECIMAL`을 문자열로 받을 때는 `element_c_type = SQL_C_CHAR`와 요소 버퍼 간 `value_stride`를 설정합니다. prepared INSERT는 `capacity`, `count`, `ColumnSize`를 대상 cardinality로 설정합니다. ```c array.count = 3; SQLPrepare(stmt, (SQLCHAR*)"INSERT INTO SDK_ARRAY_SAMPLE(ID,A_I32) VALUES(3,?)", SQL_NTS); SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_MACHBASE_ARRAY, SQL_MACHBASE_ARRAY, 3, 0, &array, sizeof(array), &outer); SQLExecute(stmt); ``` 전체 NULL 입력은 `outer = SQL_NULL_DATA`로 지정합니다. ARRAY parameter-set execute는 현재 지원하지 않으며 `HYC00`을 반환합니다. 기존 `SQLAppendBatch`도 ARRAY type code가 없어 ARRAY를 지원하지 않지만 동일한 SQLSTATE를 계약하지는 않습니다. ### C++ C++은 SQLCLI descriptor ABI를 그대로 사용합니다. bind부터 fetch가 끝날 때까지 `vector`의 주소가 바뀌지 않도록 크기를 고정합니다. ```cpp std::vector values(3); std::vector indicators(3); SQLLEN outer = 0; SQL_MACHBASE_ARRAY_DESC array{}; array.struct_size = sizeof(array); array.element_c_type = SQL_C_SLONG; array.capacity = values.size(); array.values = values.data(); array.element_indicators = indicators.data(); SQLBindCol(stmt, 1, SQL_C_MACHBASE_ARRAY, &array, sizeof(array), &outer); ``` 애플리케이션 모델에서는 whole NULL을 `std::optional>>`의 바깥 `optional`, element NULL을 안쪽 `optional`로 표현할 수 있습니다. ### Machbase ODBC와 범용 ODBC Machbase 전용 header를 사용하는 ODBC C 프로그램은 C SQLCLI와 같은 ARRAY descriptor를 사용합니다. 전용 타입을 해석하지 않는 범용 도구는 canonical text로 조회하거나 요소를 각각 projection합니다. ```sql SELECT ID, A_I32, A_I32[1], A_I32[2], A_I32[3] FROM SDK_ARRAY_SAMPLE ORDER BY ID; ``` ### JDBC JDBC는 `java.sql.Array`를 반환합니다. `UINT64`는 `BigInteger`, `DECIMAL`은 `BigDecimal`로 보존합니다. ```java try (Connection con = DriverManager.getConnection( "jdbc:machbase://127.0.0.1:5656/machbasedb", "SYS", "MANAGER"); Statement st = con.createStatement(); ResultSet rs = st.executeQuery( "SELECT A_I32 FROM SDK_ARRAY_SAMPLE ORDER BY ID")) { rs.next(); java.sql.Array sqlArray = rs.getArray(1); Object[] values = (Object[])sqlArray.getArray(); // [Integer(1), null, Integer(-3)] rs.next(); assert rs.getArray(1) == null && rs.wasNull(); } ``` metadata의 JDBC type은 `Types.ARRAY`, precision은 cardinality, `DECIMAL` scale은 요소 scale입니다. `Connection.createArrayOf()`로 만든 값을 `PreparedStatement.setArray()`에 전달할 수 있습니다. ### Python Python은 ARRAY를 `list`, element NULL과 whole NULL을 각각 내부 `None`과 컬럼 자체의 `None`으로 반환합니다. `UINT64`는 arbitrary precision `int`, `DECIMAL`은 `Decimal`입니다. ```python from decimal import Decimal from machbaseAPI import connect conn = connect(host="127.0.0.1", port=5656, user="SYS", password="MANAGER") try: rows = conn.cursor(dictionary=True).execute( "SELECT A_I32,A_U64,A_DEC FROM SDK_ARRAY_SAMPLE ORDER BY ID" ).fetchall() assert rows[0]["A_I32"] == [1, None, -3] assert rows[0]["A_U64"][2] == 18446744073709551614 assert rows[0]["A_DEC"][0] == Decimal("1.2500") assert rows[1]["A_I32"] is None finally: conn.close() ``` prepared `execute()`와 `executemany()`는 `list` 또는 `tuple`을 ARRAY로 encode합니다. `cursor.column_metadata`에서 ARRAY type code, cardinality와 `DECIMAL` 요소 metadata를 확인할 수 있습니다. ### Node.js Node.js는 ARRAY를 JavaScript `Array`로 반환합니다. `INT64`와 `UINT64`는 `bigint`, `DECIMAL`은 정밀도 보존을 위해 문자열로 반환합니다. ```javascript const { createConnection } = require('@machbase/ts-client'); const conn = createConnection({ host: '127.0.0.1', port: 5656, user: 'SYS', password: 'MANAGER', }); await conn.connect(); try { const [rows] = await conn.query( 'SELECT A_I32,A_U64,A_DEC FROM SDK_ARRAY_SAMPLE ORDER BY ID', ); console.log(rows[0].A_I32); // [1, null, -3] console.log(rows[0].A_U64); // [1n, null, 18446744073709551614n] console.log(rows[1].A_I32); // null: whole NULL } finally { await conn.end(); } ``` `JSON.stringify()` 전에 `bigint`를 문자열로 바꾸고 `DECIMAL` 문자열을 `Number`로 강제 변환하지 마십시오. prepared statement의 `getColumns()`에서 ARRAY cardinality와 요소 precision/scale metadata를 확인할 수 있습니다. ### .NET full/legacy provider MachConnector40 full/legacy provider는 ARRAY를 `object[]`로 반환합니다. element NULL은 배열 안의 `null`, whole NULL은 `IsDBNull()`로 구분합니다. ```csharp using Mach.Data.MachClient; using var conn = new MachConnection( "SERVER=127.0.0.1;PORT_NO=5656;UID=SYS;PWD=MANAGER"); conn.Open(); using var cmd = new MachCommand( "SELECT A_I32 FROM SDK_ARRAY_SAMPLE ORDER BY ID", conn); using var reader = cmd.ExecuteReader(); reader.Read(); var values = (object[])reader.GetValue(0); Console.WriteLine((int)values[0]); Console.WriteLine(values[1] is null); reader.Read(); Console.WriteLine(reader.IsDBNull(0)); ``` 각 요소는 `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, `decimal`입니다. CLR `decimal` 범위를 벗어나는 값은 invariant 문자열로 반환합니다. `GetSchemaTable()`은 provider type, cardinality, element scale과 `object[]` field type을 제공합니다. ### Go neo-client 이 항목은 Machbase Neo 서버가 아니라 `neo-client`가 Machbase DBMS에 직접 연결하는 SDK 경로입니다. 0-based ARRAY API는 [`neo-client` PR #17](https://github.com/machbase/neo-client/pull/17) 이후의 v2 module 소스에 있습니다. 공개 v2 릴리스가 지정되기 전에는 해당 소스 checkout과 `go.work` 또는 `replace` 등 명시적 로컬 module 연결이 필요합니다. 공개 v1 릴리스에 기능이 있다고 가정하지 마십시오. ```go import ( "context" "database/sql" "fmt" client "github.com/machbase/neo-client/v2" "github.com/machbase/neo-client/v2/api" ) db, err := sql.Open(client.DefaultDriverName, dsn) if err != nil { return err } defer db.Close() dense, err := api.NewArray(api.SqlTypeInt32, int32(10), nil, int32(30)) if err != nil { return err } if _, err = db.ExecContext(context.Background(), "INSERT INTO SDK_ARRAY_SAMPLE(ID,A_I32) VALUES(3,?)", dense); err != nil { return err } rows, err := db.QueryContext(context.Background(), "SELECT A_I32 FROM SDK_ARRAY_SAMPLE WHERE ID=3") if err != nil { return err } defer rows.Close() for rows.Next() { var raw sql.NullString if err := rows.Scan(&raw); err != nil { return err } fmt.Println(raw.String) // [10,null,30] } return rows.Err() ``` `database/sql` 결과 경계는 canonical 문자열을 제공합니다. `sql.NullString`으로 whole NULL을 확인하고 유효한 값이면 `array.Scan(raw.String)`, whole NULL이면 `array.Scan(nil)`로 해석합니다. 원래의 narrow integer와 `FLOAT` 타입까지 보존하려면 요소 metadata로 receiver를 먼저 만듭니다. `DECIMAL` precision/scale은 `NewSparseArrayWithMeta()`로 지정합니다. `ColumnTypes()`의 `DatabaseTypeName()`은 ARRAY 타입 이름을, `DecimalSize()`는 `DECIMAL` 요소 precision/scale을 제공합니다. 현재 `Length()`는 cardinality가 아니라 encoded payload byte length이므로 cardinality로 사용하면 안 됩니다. 표준 `database/sql` metadata만으로 cardinality를 직접 얻을 수 없습니다. ## 명령행 도구와 데이터 이동 ### machsql `machsql`은 canonical `ARRAY` 문자열을 출력합니다. ```sql SELECT ID, CHANNELS, ARRAY_LENGTH(CHANNELS), CHANNELS[1] FROM SENSOR_ARRAY ORDER BY ID; ``` SQL 파일로 저장한 뒤 다음과 같이 실행할 수 있습니다. ```bash machsql -s 127.0.0.1 -P 5656 -u SYS -p MANAGER -f array_query.sql ``` ### machloader machloader의 text 입력과 출력은 canonical `[value,null,value]` 형식을 사용합니다. delimiter나 quote가 ARRAY 내부에 포함되므로 CSV에서는 ARRAY 필드를 enclosure로 감쌉니다. ```csv 1,"[1.5,null,3.5,4.5]" ``` whole NULL과 all-element-NULL `ARRAY`가 서로 바뀌지 않는지 round trip으로 확인합니다. CSV 자동 테이블 생성에서는 `ARRAY`를 자동 추론하지 않으므로 테이블을 먼저 명시적으로 생성합니다. ### backup, restore와 mount backup과 restore는 요소 타입, cardinality, `DECIMAL` precision/scale과 NULL 정보를 보존합니다. mount 조회도 같은 결과와 메타데이터를 제공합니다. ARRAY를 포함하는 데이터는 Machbase DBMS 8.7.0 환경에서 backup, restore와 mount를 수행합니다. ## 버전과 오류 처리 - `ARRAY` 타입은 Machbase DBMS 8.7.0에서 지원합니다. - ARRAY의 SQL 요소 위치와 Machbase 전용 SDK position은 0-based입니다. 기존 1-based SQL과 SDK 호출은 위치를 1씩 낮춰야 합니다. - Machbase DBMS 8.7.0 서버와 ARRAY 기능이 포함된 SDK 빌드를 함께 사용합니다. - 지원하지 않는 서버 또는 SDK에서는 ARRAY를 다른 타입으로 자동 변환하지 않고 오류를 반환합니다. - cardinality, position 또는 요소 변환 오류는 문장 전체를 실패시키며 부분 ARRAY를 저장하지 않습니다. - 애플리케이션은 whole NULL과 all-element-NULL `ARRAY`를 별개의 값으로 처리해야 합니다. - 이 문서는 Machbase DBMS의 SQL과 SDK 기능을 다루며 Machbase Neo, HTTP, TQL과 ILP는 범위에 포함하지 않습니다. --- title: "16.1.3 함수 사전" url: https://docs.machbase.com/kr/dbms/reference/sql/functions/ language: kr kind: section --- # 16.1.3 함수 사전 내장 함수를 카테고리별로 정리합니다. | 카테고리 | 설명 | |----------|------| | [집계 함수](aggregation/) | COUNT, SUM, AVG, MIN, MAX, STDDEV, FIRST, LAST 등 그룹 집계 함수 | | [윈도우/시리즈 함수](series/) | ROWNUM, SERIESNUM 등 윈도우·시리즈 분석 함수 | | [날짜/시간 함수](datetime/) | TO_DATE, TO_CHAR, DATE_TRUNC, ADD_TIME 등 날짜·시간 처리 함수 | | [JSON 함수와 JSON dot 표기법](operators-json/) | JSON 데이터 추출·조작 함수 및 멤버 접근 문법 | | [정규식 함수](regex/) | REGEXP_LIKE, REGEXP_SUBSTR 등 정규식 기반 검색·변환 함수 | | [NEXTVAL 함수](nextval/) | Lookup 테이블 Sequence 컬럼용 자동 증가값 생성 함수 | | [사용자 컨텍스트 함수](functions-full/#current-session-user) | CURRENT_USER, SESSION_USER와 내부 사용자 ID 조회 | | [전체 함수 레퍼런스](functions-full/) | 기존 함수 항목과 CAST를 포함한 전체 함수 레퍼런스 | ## 공통 규칙 - 입력 값이 `NULL`이면 결과도 `NULL`입니다 (별도 명시가 없는 한). - 인자 타입 불일치 시 `ERR-02036` 또는 `ERR-02037` 오류가 발생합니다. --- title: "집계 함수" url: https://docs.machbase.com/kr/dbms/reference/sql/functions/aggregation/ language: kr kind: page --- # 집계 함수 집계 함수는 여러 행의 값을 하나의 결과로 계산합니다. `GROUP BY` 절과 함께 사용하면 그룹별 집계 결과를 얻을 수 있습니다. `NULL` 값은 집계에서 무시됩니다 (COUNT(*) 제외). ## 빠른 참조 | 함수 | 문법 | 설명 | |------|------|------| | COUNT | `COUNT(*) / COUNT(col)` | 전체 행 수 또는 NULL이 아닌 행 수 | | SUM | `SUM(col)` | 합계 | | AVG | `AVG(col)` | 평균 | | MIN | `MIN(col)` | 최솟값 | | MAX | `MAX(col)` | 최댓값 | | STDDEV | `STDDEV(col)` | 표본 표준편차 | | STDDEV_POP | `STDDEV_POP(col)` | 모표준편차 | | VARIANCE | `VARIANCE(col)` | 표본 분산 | | VAR_POP | `VAR_POP(col)` | 모분산 | | FIRST | `FIRST(sort_expr, return_expr)` | 정렬 기준 첫 번째 레코드 값 | | LAST | `LAST(sort_expr, return_expr)` | 정렬 기준 마지막 레코드 값 | | SUMSQ | `SUMSQ(col)` | 제곱합 | | MEDIAN | `MEDIAN(col)` | 중앙값 | | MODE | `MODE(col)` | 최빈값 | | AREA | `AREA(y, x)` | 곡선 아래 면적 (사다리꼴 적분) | | SLOPE | `SLOPE(y, x)` | 선형 회귀 기울기 | | GROUP_CONCAT | `GROUP_CONCAT(col ...)` | 그룹 내 값을 문자열로 연결 | | TS_CHANGE_COUNT | `TS_CHANGE_COUNT(col)` | 값 변경 횟수 | | TOP_K | `TOP_K(col, k)` | 상위 k개 빈도 값 | | PERCENTILE_CONT | `PERCENTILE_CONT(col, ratio)` | 연속 분위수 | | PERCENTILE_DISC | `PERCENTILE_DISC(col, ratio)` | 이산 분위수 | | APPROX_PERCENTILE | `APPROX_PERCENTILE(col, ratio)` | 근사 분위수 | | CUME_DIST | `CUME_DIST(value, threshold)` | 누적 분포 비율 | --- ## COUNT 주어진 컬럼의 레코드 개수를 구합니다. `COUNT(*)`는 NULL을 포함한 전체 행 수를, `COUNT(col)`은 NULL이 아닌 행 수를 반환합니다. ```sql COUNT(*) COUNT(column_name) ``` ```sql Mach> CREATE LOG TABLE count_table (id1 INTEGER, id2 INTEGER); Mach> INSERT INTO count_table VALUES(1, 1); Mach> INSERT INTO count_table VALUES(2, 2); Mach> INSERT INTO count_table VALUES(null, 4); Mach> SELECT COUNT(*) FROM count_table; COUNT(*) --------- 3 Mach> SELECT COUNT(id1) FROM count_table; COUNT(id1) ----------- 2 ``` --- ## SUM 숫자 컬럼의 합계를 반환합니다. ```sql SUM(column_name) ``` ```sql Mach> SELECT c1, SUM(c2) FROM sum_table GROUP BY c1; c1 SUM(c2) -------------------- 1 6 2 6 3 4 ``` --- ## AVG 숫자형 컬럼의 평균값을 반환합니다. ```sql AVG(column_name) ``` ```sql Mach> SELECT id1, AVG(id2) FROM avg_table GROUP BY id1; id1 AVG(id2) --------------------- 1 2 2 2 NULL 4 ``` --- ## MIN 지정한 숫자 컬럼의 최솟값을 반환합니다. ```sql MIN(column_name) ``` ```sql Mach> SELECT MIN(c1) FROM min_table; MIN(c1) -------- 1 ``` --- ## MAX 지정한 숫자 컬럼의 최댓값을 반환합니다. ```sql MAX(column_name) ``` ```sql Mach> SELECT MAX(c) FROM max_table; MAX(c) ------- 30 ``` --- ## STDDEV / STDDEV_POP 입력 컬럼의 표본 표준편차(STDDEV)와 모표준편차(STDDEV_POP)를 반환합니다. ```sql STDDEV(column) STDDEV_POP(column) ``` ```sql Mach> SELECT c2, STDDEV(c1) FROM stddev_table GROUP BY c2; c2 STDDEV(c1) ----------------------- 1 0.707107 2 0.707107 Mach> SELECT c2, STDDEV_POP(c1) FROM stddev_table GROUP BY c2; c2 STDDEV_POP(c1) --------------------------- 1 0.5 2 0.5 ``` --- ## VARIANCE / VAR_POP 표본 분산(VARIANCE)과 모분산(VAR_POP)을 반환합니다. ```sql VARIANCE(column_name) VAR_POP(column_name) ``` ```sql Mach> SELECT VARIANCE(c1) FROM var_table; VARIANCE(c1) -------------- 0.333333 Mach> SELECT VAR_POP(c1) FROM var_table; VAR_POP(c1) ------------- 0.25 ``` --- ## FIRST / LAST 각 그룹에서 `sort_expr` 기준으로 정렬했을 때 가장 앞(FIRST) 또는 마지막(LAST) 레코드의 `return_expr` 값을 반환합니다. 시계열 데이터에서 특정 시점의 값을 가져올 때 유용합니다. ```sql FIRST(sort_expr, return_expr) LAST(sort_expr, return_expr) ``` ```sql Mach> SELECT group_no, FIRST(id, name) FROM firstlast_table GROUP BY group_no; group_no first(id, name) ---------------------------- 0 John 1 Grey Mach> SELECT group_no, LAST(id, name) FROM firstlast_table GROUP BY group_no; group_no last(id, name) --------------------------- 0 Ryan 1 Kyle ``` --- ## SUMSQ 숫자 값들의 제곱합을 반환합니다. ```sql SUMSQ(value) ``` ```sql Mach> SELECT c1, SUMSQ(c2) FROM sumsq_table GROUP BY c1; c1 SUMSQ(c2) ---------------------- 1 14 2 41 ``` --- ## MEDIAN 숫자식의 정확한 중앙값을 반환합니다. ```sql MEDIAN(value) ``` ```sql SELECT MEDIAN(temp_c) FROM sensor_log; ``` --- ## MODE 입력 집합에서 가장 자주 나타나는 숫자 값(최빈값)을 반환합니다. 최빈값이 여러 개면 더 작은 값을 반환합니다. ```sql MODE(value) ``` ```sql SELECT MODE(alarm_code) FROM event_log; ``` --- ## AREA 숫자형 `(x, y)` 점들로 이루어진 곡선 아래 면적을 사다리꼴 적분으로 계산합니다. 유효한 점이 2개 미만이면 NULL을 반환합니다. ```sql AREA(y, x) ``` ```sql SELECT AREA(power_kw, sample_sec) FROM power_log; ``` --- ## SLOPE 숫자형 `(x, y)` 점들에 대한 선형 회귀 직선의 기울기를 계산합니다. `x` 분산이 0이거나 유효 데이터가 부족하면 NULL을 반환합니다. ```sql SLOPE(y, x) ``` ```sql SELECT SLOPE(temp_c, sample_sec) FROM sensor_log; ``` --- ## GROUP_CONCAT 그룹 내 컬럼 값들을 문자열로 이어 붙여 반환합니다. {{< callout type="warning" >}} Cluster Edition에서는 사용할 수 없습니다. {{< /callout >}} ```sql GROUP_CONCAT( [DISTINCT] column [ORDER BY column [ASC | DESC] [, ...]] [SEPARATOR str_val] ) ``` ```sql Mach> SELECT GROUP_CONCAT(name) FROM concat_table GROUP BY id2; G_NAMES --------- Jack,Jack,Ram Jill,Zara,John Mach> SELECT GROUP_CONCAT(DISTINCT name SEPARATOR '.') FROM concat_table GROUP BY id2; G_NAMES --------- Jack.Ram Jill.Zara.John ``` --- ## TS_CHANGE_COUNT 시간순으로 입력된 컬럼 값의 변경 횟수를 반환합니다. VARCHAR 타입은 지원하지 않습니다. {{< callout type="warning" >}} Cluster Edition에서는 사용할 수 없습니다. {{< /callout >}} ```sql TS_CHANGE_COUNT(column) ``` ```sql Mach> SELECT id, TS_CHANGE_COUNT(ip) FROM ipcount_table GROUP BY id; id TS_CHANGE_COUNT(ip) -------------------------------- 1 4 2 2 ``` --- ## TOP_K 가장 자주 등장한 `k`개의 숫자 값을 `value:count` 형식의 문자열로 반환합니다. 빈도 내림차순, 동일 빈도 시 값 오름차순으로 정렬됩니다. ```sql TOP_K(value, k) ``` ```sql SELECT TOP_K(alarm_code, 3) FROM event_log; -- 결과 예: 101:532,205:317,301:90 ``` --- ## PERCENTILE_CONT / PERCENTILE_DISC 정확한 분위값을 계산하는 집계 함수입니다. `ratio`는 0.0 이상 1.0 이하의 상수여야 합니다. - `PERCENTILE_CONT`: 인접한 정렬 값 사이를 보간합니다. - `PERCENTILE_DISC`: 실제 관측값 중 하나를 선택합니다. ```sql PERCENTILE_CONT(value, ratio) PERCENTILE_DISC(value, ratio) ``` 축약형으로 `P05`, `P10`, `P90`, `P95` 함수도 제공합니다. ```sql SELECT PERCENTILE_CONT(latency_ms, 0.95) AS p95, PERCENTILE_DISC(latency_ms, 0.50) AS p50 FROM api_log; -- 축약형 SELECT P05(response_ms), P95(response_ms) FROM web_log; ``` --- ## APPROX_PERCENTILE 근사 분위수 함수입니다. 데이터가 매우 크고 작은 오차를 허용할 때 유용합니다. `APPROX_MEDIAN`, `APPROX_P05`, `APPROX_P10`, `APPROX_P90`, `APPROX_P95` 축약형도 있습니다. ```sql APPROX_PERCENTILE(value, ratio) APPROX_MEDIAN(value) APPROX_P95(value) ``` ```sql SELECT APPROX_PERCENTILE(latency_ms, 0.95) AS ap95, APPROX_MEDIAN(latency_ms) AS amedian FROM api_log; ``` --- ## CUME_DIST `value`가 `threshold` 이하인 행의 누적 비율(0.0 ~ 1.0)을 반환합니다. 윈도우 함수가 아닌 집계 함수입니다. ```sql CUME_DIST(value, threshold) ``` ```sql SELECT CUME_DIST(latency_ms, 100) FROM api_log; ``` --- title: "윈도우/시리즈 함수" url: https://docs.machbase.com/kr/dbms/reference/sql/functions/series/ language: kr kind: page --- # 윈도우/시리즈 함수 이 페이지는 결과 행에 번호를 붙이는 `ROWNUM()`과 연속 구간을 구분하는 `SERIESNUM()`을 설명합니다. `SERIES BY`는 정렬된 데이터에서 조건을 연속으로 만족하는 구간을 분석할 때 사용합니다. `LAG()`와 `LEAD()`처럼 `OVER` 절을 사용하는 함수는 [윈도우 함수 문법](../../syntax/window-function-over-syntax/)을 참고하십시오. ## 빠른 참조 | 함수 | 문법 | 설명 | |------|------|------| | ROWNUM | `ROWNUM()` | SELECT 결과 행 번호 부여 | | SERIESNUM | `SERIESNUM()` | 행이 속한 연속 구간의 번호 (같은 구간의 행은 같은 번호) | --- ## ROWNUM `SELECT` 결과 행에 순서 번호를 부여합니다. 서브쿼리나 인라인 뷰 내부에서도 사용할 수 있습니다. 인라인 뷰의 Target List에서 사용할 경우 외부에서 참조할 수 있도록 Alias를 지정해야 합니다. ```sql ROWNUM() ``` ### 사용 가능 절 | 사용 가능 | 사용 불가 | |-----------|----------| | SELECT Target List, GROUP BY, ORDER BY | WHERE, HAVING | `WHERE` / `HAVING`에서 행 번호로 필터링하려면 인라인 뷰에서 `ROWNUM()`을 계산한 뒤 외부 쿼리에서 참조합니다. ```sql -- 상위 2개 행만 선택 Mach> SELECT INNER_RANK, c3 AS NAME FROM (SELECT ROWNUM() AS INNER_RANK, * FROM rownum_table) WHERE INNER_RANK < 3; INNER_RANK NAME -------------------------- 1 Fourth Row 2 Third Row ``` ### ORDER BY와 함께 사용 `ORDER BY`를 포함한 쿼리를 인라인 뷰로 만들고 외부 `SELECT`에서 `ROWNUM()`을 호출하면 정렬된 순서로 번호가 부여됩니다. ```sql Mach> SELECT ROWNUM(), c2 AS SORT, c3 AS NAME FROM (SELECT * FROM rownum_table ORDER BY c3); ROWNUM() SORT NAME -------------------------- 1 1 NULL 2 2 John 3 4.3 Micheal 4 3.3 Sarah ``` --- ## SERIESNUM `SERIES BY` 절로 그룹화된 시리즈에서 각 레코드가 몇 번째 시리즈에 속하는지 나타내는 번호를 반환합니다. `SERIES BY` 절을 사용하지 않으면 항상 1을 반환합니다. 반환 타입은 `BIGINT`입니다. ```sql SERIESNUM() ``` ```sql Mach> CREATE LOG TABLE T1 (C1 INTEGER, C2 INTEGER); Mach> INSERT INTO T1 VALUES (0, 1); Mach> INSERT INTO T1 VALUES (1, 2); Mach> INSERT INTO T1 VALUES (2, 3); Mach> INSERT INTO T1 VALUES (3, 2); Mach> INSERT INTO T1 VALUES (4, 1); Mach> INSERT INTO T1 VALUES (5, 2); Mach> INSERT INTO T1 VALUES (6, 3); Mach> INSERT INTO T1 VALUES (7, 1); -- C2 > 1 조건을 만족하는 연속 구간을 시리즈로 분리 Mach> SELECT SERIESNUM(), C1, C2 FROM T1 ORDER BY C1 SERIES BY C2 > 1; SERIESNUM() C1 C2 -------------------- 1 1 2 1 2 3 1 3 2 2 5 2 2 6 3 [5] row(s) selected. ``` - `C1=1,2,3` (C2>1 조건 만족 연속 구간) → 시리즈 1 - `C1=4` (C2=1, 조건 불만족) → 시리즈 구분 - `C1=5,6` (C2>1 조건 만족 연속 구간) → 시리즈 2 --- ## SERIES BY 절 개요 `SERIES BY` 절은 `ORDER BY`와 함께 사용하며, 지정한 조건을 연속으로 만족하는 행들을 하나의 시리즈로 묶습니다. `SERIESNUM()`으로 각 시리즈를 구분하고, 집계 함수와 조합하면 연속 구간별 통계를 계산할 수 있습니다. 구간별 통계는 내부 쿼리에서 구간 번호를 생성한 뒤 외부 쿼리에서 집계합니다. 아래 예제의 `threshold`는 비교할 임계값으로 바꾸어 사용합니다. ```sql SELECT series_id, COUNT(*), AVG(value) FROM ( SELECT value, SERIESNUM() AS series_id FROM sensor_log ORDER BY ts SERIES BY value > threshold ) GROUP BY series_id ORDER BY series_id; ``` --- title: "정규식 함수" url: https://docs.machbase.com/kr/dbms/reference/sql/functions/regex/ language: kr kind: page --- # 정규식 함수 Machbase는 PCRE(Perl Compatible Regular Expressions) 기반의 정규식 함수를 제공합니다. 모든 정규식 함수는 `VARCHAR` 타입 컬럼에서만 동작합니다. ## 빠른 참조 | 함수 | 문법 | 설명 | |------|------|------| | REGEXP_LIKE | `REGEXP_LIKE(src, pat [, flag])` | 패턴 일치 여부 확인 | | REGEXP_INSTR | `REGEXP_INSTR(src, pat [, pos [, occ [, ret [, flag]]]])` | 패턴 일치 위치 반환 | | REGEXP_SUBSTR | `REGEXP_SUBSTR(src, pat [, pos [, occ [, flag]]])` | 패턴 일치 부분 문자열 추출 | | REGEXP_REPLACE | `REGEXP_REPLACE(src, pat [, repl [, pos [, occ [, flag]]]])` | 패턴 일치 문자열 치환 | ### match_param (공통 파라미터) | 값 | 설명 | |----|------| | `'c'` | 대소문자 구분 (기본값) | | `'i'` | 대소문자 무시 | --- ## REGEXP_LIKE 문자열이 정규식 패턴과 일치하는지 검사합니다. `WHERE` 절에서 주로 사용하며 Boolean(1/0)을 반환합니다. ```sql REGEXP_LIKE(source, pattern) REGEXP_LIKE(source, pattern, match_param) ``` - `source`: 검사할 `VARCHAR` 컬럼 또는 식 - `pattern`: 상수 `VARCHAR` 정규식 - `match_param`: `'c'`(대소문자 구분, 기본) 또는 `'i'`(대소문자 무시) ```sql -- 'error' 또는 'warn'을 포함하는 메시지 조회 (대소문자 무시) SELECT * FROM sensor_text WHERE REGEXP_LIKE(message, 'error|warn', 'i'); -- 숫자로 시작하는 코드 조회 SELECT * FROM event_log WHERE REGEXP_LIKE(code, '^[0-9]+'); -- 이메일 형식 검증 SELECT name FROM users WHERE REGEXP_LIKE(email, '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'); ``` --- ## REGEXP_INSTR 정규식과 일치하는 위치를 반환합니다. 일치하는 값이 없으면 `0`을 반환합니다. 위치는 1부터 시작합니다. ```sql REGEXP_INSTR(source, pattern) REGEXP_INSTR(source, pattern, position) REGEXP_INSTR(source, pattern, position, occurrence) REGEXP_INSTR(source, pattern, position, occurrence, return_pos) REGEXP_INSTR(source, pattern, position, occurrence, return_pos, match_param) ``` | 파라미터 | 설명 | |---------|------| | `source` | 검사할 `VARCHAR` | | `pattern` | 상수 `VARCHAR` 정규식 | | `position` | 검색 시작 위치 (1 이상, 기본값: 1) | | `occurrence` | 찾을 몇 번째 일치 (1 이상, 기본값: 1) | | `return_pos` | `0`: 시작 위치, `1`: 일치 문자열 다음 위치 | | `match_param` | `'c'` 또는 `'i'` | ```sql -- 'The' 패턴의 위치 반환 (대소문자 무시, 첫 번째 발견, 다음 위치) SELECT REGEXP_INSTR('TechOnTheNet', 'The', 1, 1, 1, 'i'); -- 결과: 10 (일치 문자열 'The' 다음 위치) ``` --- ## REGEXP_SUBSTR 정규식과 일치하는 부분 문자열을 반환합니다. 일치하는 값이 없으면 NULL을 반환합니다. ```sql REGEXP_SUBSTR(source, pattern) REGEXP_SUBSTR(source, pattern, position) REGEXP_SUBSTR(source, pattern, position, occurrence) REGEXP_SUBSTR(source, pattern, position, occurrence, match_param) ``` | 파라미터 | 설명 | |---------|------| | `source` | 검사할 `VARCHAR` | | `pattern` | 상수 `VARCHAR` 정규식 | | `position` | 검색 시작 위치 (1 이상, 기본값: 1) | | `occurrence` | 찾을 몇 번째 일치 (1 이상, 기본값: 1) | | `match_param` | `'c'` 또는 `'i'` | ```sql -- 두 번째 모음 추출 (대소문자 무시) SELECT REGEXP_SUBSTR('TechOnTheNet', 'a|e|i|o|u', 1, 2, 'i'); -- 결과: 'O' -- IP 주소에서 첫 번째 옥텟 추출 SELECT REGEXP_SUBSTR(ip_str, '[0-9]+', 1, 1) FROM log_table; -- 로그에서 오류 코드 추출 SELECT REGEXP_SUBSTR(message, 'ERR-[0-9]+') FROM event_log; ``` --- ## REGEXP_REPLACE 정규식과 일치하는 문자열을 지정한 문자열로 치환합니다. ```sql REGEXP_REPLACE(source, pattern) REGEXP_REPLACE(source, pattern, replacement) REGEXP_REPLACE(source, pattern, replacement, position) REGEXP_REPLACE(source, pattern, replacement, position, occurrence) REGEXP_REPLACE(source, pattern, replacement, position, occurrence, match_param) ``` | 파라미터 | 설명 | |---------|------| | `source` | 대상 `VARCHAR` | | `pattern` | 상수 `VARCHAR` 정규식 | | `replacement` | 치환 문자열 (생략 시 일치 문자열 제거) | | `position` | 검색 시작 위치 (1 이상, 기본값: 1) | | `occurrence` | `0`: 모든 일치 치환, 양수 n: n번째 일치만 치환 (기본값: 0) | | `match_param` | `'c'` 또는 `'i'` | ```sql -- 두 번째 모음을 'Z'로 치환 (대소문자 무시) SELECT REGEXP_REPLACE('TechOnTheNet', 'a|e|i|o|u', 'Z', 1, 2, 'i'); -- 결과: 'TechZnTheNet' -- 모든 숫자 제거 SELECT REGEXP_REPLACE(code, '[0-9]', '') FROM log_table; -- 공백 정규화 (연속 공백을 단일 공백으로) SELECT REGEXP_REPLACE(message, '\s+', ' ') FROM event_log; ``` --- ## PCRE 정규식 기초 | 패턴 | 설명 | 예시 | |------|------|------| | `.` | 임의의 문자 1개 | `a.c` → abc, aXc | | `*` | 0회 이상 반복 | `ab*c` → ac, abc, abbc | | `+` | 1회 이상 반복 | `ab+c` → abc, abbc | | `?` | 0 또는 1회 | `colou?r` → color, colour | | `^` | 문자열 시작 | `^error` | | `$` | 문자열 끝 | `\.log$` | | `[abc]` | 문자 클래스 | `[aeiou]` | | `[^abc]` | 부정 문자 클래스 | `[^0-9]` | | `\d` | 숫자 (`[0-9]`) | `\d+` | | `\w` | 단어 문자 | `\w+` | | `\s` | 공백 문자 | `\s+` | | `a\|b` | a 또는 b | `error\|warn` | | `(abc)` | 그룹 | `(foo)+` | | `{n,m}` | n~m회 반복 | `\d{3,5}` | --- ## SEARCH / ESEARCH와의 차이 | 기능 | REGEXP_LIKE | SEARCH / ESEARCH | |------|:-----------:|:----------------:| | 적용 타입 | `VARCHAR` | `TEXT` (전문 검색 인덱스) | | 정규식 지원 | O (PCRE) | X (키워드 검색) | | 인덱스 활용 | X | O | | 대용량 텍스트 | 제한적 | 권장 | 대용량 텍스트에서 키워드 검색이 필요하면 `TEXT` 타입과 `SEARCH` 절을 사용하는 것이 성능 면에서 유리합니다. 정규식 패턴 매칭이 필요한 경우 `VARCHAR` 컬럼과 `REGEXP_LIKE`를 사용합니다. --- title: "JSON 함수와 JSON dot 표기법" url: https://docs.machbase.com/kr/dbms/reference/sql/functions/operators-json/ language: kr kind: page --- # JSON 함수와 JSON dot 표기법 Machbase는 `JSON` 타입 컬럼에 저장된 데이터를 조작·조회하기 위한 함수와 JSON dot 표기법을 제공합니다. ## 빠른 참조 | 함수/표기법 | 문법 | 설명 | |-------------|------|------| | JSON dot 표기법 | `col.key` | JSON 객체에서 키 값 추출 | | `JSON_EXTRACT` | `JSON_EXTRACT(doc, path)` | JSON 경로의 값을 JSON 문자열로 추출 | | `JSON_EXTRACT_STRING` | `JSON_EXTRACT_STRING(doc, path)` | JSON 경로의 값을 문자열로 추출 | | `JSON_EXTRACT_INTEGER` | `JSON_EXTRACT_INTEGER(doc, path)` | JSON 경로의 값을 정수로 추출 | | `JSON_EXTRACT_DOUBLE` | `JSON_EXTRACT_DOUBLE(doc, path)` | JSON 경로의 값을 실수로 추출 | | `JSON_TYPEOF` | `JSON_TYPEOF(doc, path)` | 지정한 JSON 경로에 있는 값의 타입 확인 | | `JSON_IS_VALID` | `JSON_IS_VALID(json_text)` | JSON 문자열 유효성 확인 | | `JSON_SET` | `JSON_SET(doc, path, scalar)` | JSON 경로에 스칼라 값 설정 | | `JSON_SET_JSON` | `JSON_SET_JSON(doc, path, json_text)` | JSON 경로에 JSON 서브트리 설정 | | `JSON_REMOVE` | `JSON_REMOVE(doc, path)` | JSON 경로의 멤버 제거 | `JSON_TYPEOF`의 `path`는 필수 인자입니다. JSON 문서 전체의 타입은 `JSON_TYPEOF(doc, '$')`로 확인합니다. --- ## JSON dot 표기법 JSON 컬럼 뒤에 점(`.`)과 키 이름을 붙여 해당 키의 값을 조회합니다. JSONPath 문자열을 작성하지 않고 JSON 컬럼의 멤버에 접근할 때 사용합니다. ```sql json_column.key ``` ```sql -- JSON 컬럼에서 특정 키 값 추출 SELECT data.temperature AS temp FROM sensor_log; -- WHERE 절에서 사용 SELECT * FROM sensor_log WHERE data.status = 'active'; ``` JSONPath 문자열을 직접 지정할 때는 `data -> '$.temperature'`처럼 `->` 연산자를 사용합니다. 위의 JSON dot 표기법과 구분하여 작성합니다. --- ## JSON_SET JSON 문서의 특정 경로에 SQL 스칼라 값을 JSON 스칼라로 저장합니다. ```sql JSON_SET(json_doc, path, scalar) ``` - `path`는 full JSONPath(`$.key.subkey` 형식)를 사용해야 합니다. - `JSON_SET(..., path, NULL)`은 JSON `null`을 저장합니다. - JSON 문서 인자가 SQL `NULL`이면 결과는 SQL `NULL`입니다. - 배열 요소 갱신(`$.items[0]`)은 지원하지 않습니다. ```sql Mach> SELECT JSON_SET('{"ship":{"status":"READY"}}', '$.ship.status', 'DONE') FROM dual; {"ship":{"status":"DONE"}} Mach> SELECT JSON_SET('{"count":0}', '$.count', 42) FROM dual; {"count":42} ``` --- ## JSON_SET_JSON 세 번째 인자를 JSON 문자열로 해석하여 object 또는 array 서브트리를 저장합니다. ```sql JSON_SET_JSON(json_doc, path, json_text) ``` - 세 번째 인자가 SQL `NULL`이면 결과는 SQL `NULL`입니다. - 유효하지 않은 JSON 문자열은 오류가 발생합니다. - 배열 요소 갱신은 지원하지 않습니다. ```sql Mach> SELECT JSON_SET_JSON('{"ship":{}}', '$.ship.owner', '{"name":"machbase"}') FROM dual; {"ship":{"owner":{"name":"machbase"}}} Mach> SELECT JSON_SET_JSON('{"tags":{}}', '$.tags.sensors', '[1,2,3]') FROM dual; {"tags":{"sensors":[1,2,3]}} ``` --- ## JSON_REMOVE JSON 문서에서 특정 멤버 또는 하위 경로를 제거합니다. ```sql JSON_REMOVE(json_doc, path) ``` - `path`는 full JSONPath를 사용해야 합니다. - 존재하지 않는 경로는 no-op으로 처리됩니다. - `JSON_REMOVE(..., '$')`는 허용되지 않습니다. - JSON 문서 인자가 SQL `NULL`이면 결과는 SQL `NULL`입니다. ```sql Mach> SELECT JSON_REMOVE('{"owner":{"name":"machbase","team":"db"}}', '$.owner.team') FROM dual; {"owner":{"name":"machbase"}} Mach> SELECT JSON_REMOVE('{"a":1,"b":2}', '$.a') FROM dual; {"b":2} ``` --- ## JSON 데이터 삽입 예시 ```sql -- JSON 타입 컬럼을 포함한 LOG 테이블 CREATE LOG TABLE device_log ( ts DATETIME, data JSON ); -- JSON 데이터 삽입 INSERT INTO device_log VALUES (NOW, '{"temperature":23.5,"humidity":60,"status":"active"}'); -- JSON dot 표기법으로 값 추출 SELECT ts, data.temperature AS temp FROM device_log WHERE data.status = 'active'; ``` --- ## 테이블 타입별 JSON 지원 현황 | 테이블 타입 | JSON 컬럼 | JSON path query | 비고 | |------------|:---------:|:---------------:|------| | TAG | O | O | JSON 컬럼과 JSON 함수 지원, JSON PK는 미지원 | | LOG | O | O | 완전 지원 | | LOOKUP | O | O | 일반 컬럼으로 지원, JSON path index는 미지원 | | VOLATILE | X | X | JSON 컬럼 생성 불가 | | TRANSACTION | O | O | 완전 지원 | 자세한 내용은 [JSON 타입의 테이블 타입별 지원 범위](/dbms/lookup-table-usage/json-column-query/)를 참고하십시오. --- title: "날짜/시간 함수" url: https://docs.machbase.com/kr/dbms/reference/sql/functions/datetime/ language: kr kind: page --- # 날짜/시간 함수 Machbase의 `DATETIME` 타입은 1970-01-01 00:00:00 UTC 이후 경과한 나노초 값을 내부적으로 저장합니다. 날짜/시간 함수는 이 값을 인간이 읽을 수 있는 형식으로 변환하거나 산술 연산을 수행합니다. ## 빠른 참조 | 함수 | 문법 | 설명 | |------|------|------| | SYSDATE / NOW | `SYSDATE`, `NOW` | 현재 시스템 시간 반환 | | TO_DATE | `TO_DATE(str [, fmt])` | 문자열을 DATETIME으로 변환 | | TO_DATE_SAFE | `TO_DATE_SAFE(str [, fmt])` | 변환 실패 시 NULL 반환 | | TO_CHAR | `TO_CHAR(col [, fmt])` | DATETIME을 문자열로 변환 | | ADD_TIME | `ADD_TIME(col, diff)` | 날짜/시간 증감 | | DATE_TRUNC | `DATE_TRUNC(unit, col [, count])` | 지정 단위로 시간 절사 | | DATE_BIN | `DATE_BIN(unit, count, col [, origin])` | 지정 기준으로 시간 버킷 처리 | | DAYOFWEEK | `DAYOFWEEK(col)` | 요일 번호 반환 (0=일요일) | | YEAR / MONTH / DAY | `YEAR(col)`, `MONTH(col)`, `DAY(col)` | 연, 월, 일 추출 | | FROM_UNIXTIME | `FROM_UNIXTIME(unix_ts)` | Unix 타임스탬프(32비트)를 DATETIME으로 변환 | | UNIX_TIMESTAMP | `UNIX_TIMESTAMP(col)` | DATETIME을 Unix 타임스탬프(32비트)로 변환 | | FROM_TIMESTAMP | `FROM_TIMESTAMP(ns)` | 나노초 정수를 DATETIME으로 변환 | | TO_TIMESTAMP | `TO_TIMESTAMP(col)` | DATETIME을 나노초 정수로 변환 | --- ## SYSDATE / NOW 현재 시스템 시간을 반환하는 의사 컬럼입니다. `SYSDATE`와 `NOW`는 동일한 값을 반환합니다. ```sql SYSDATE NOW ``` ```sql Mach> SELECT SYSDATE, NOW FROM t1; SYSDATE NOW ------------------------------------------------------------------- 2017-01-16 14:14:53 310:973:000 2017-01-16 14:14:53 310:973:000 ``` --- ## TO_DATE 지정한 포맷 문자열에 따라 문자열을 `DATETIME` 타입으로 변환합니다. 포맷을 생략하면 기본값 `YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn`을 사용합니다. ```sql TO_DATE(date_string [, format_string]) ``` ```sql Mach> SELECT TO_DATE('2014-12-30 11:22:33 444:555:666'); 2014-12-30 11:22:33 444:555:666 Mach> SELECT TO_DATE('1999-12-31 13:12:32', 'YYYY-MM-DD HH24:MI:SS'); 1999-12-31 13:12:32 000:000:000 Mach> SELECT TO_DATE('1999', 'YYYY'); 1999-01-01 00:00:00 000:000:000 ``` 변환 실패 시 오류 없이 NULL을 반환하는 `TO_DATE_SAFE()`도 제공합니다. ```sql Mach> SELECT TO_DATE_SAFE('2016-12-32', 'YYYY-MM-DD'); NULL ``` --- ## TO_CHAR (DATETIME) `DATETIME` 컬럼 값을 임의의 문자열로 변환합니다. 포맷을 생략하면 기본값 `YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn`을 사용합니다. ```sql TO_CHAR(datetime_col [, format_string]) ``` ### 포맷 문자열 | 포맷 표현식 | 설명 | |------------|------| | `YYYY` | 연도 4자리 | | `YY` | 연도 2자리 | | `MM` | 월 2자리 (`01~12`) | | `MON` | 월 3자리 영문 약어 (JAN, FEB, ...) | | `DD` | 일 2자리 | | `DAY` | 요일 3자리 영문 약어 (SUN, MON, ...) | | `IW` | ISO 8601 주차 (`1~53`, 월요일 기준) | | `WW` | 연간 주차 (`1~53`, 요일 무관) | | `W` | 월간 주차 (`1~5`, 요일 무관) | | `HH` | 시간 2자리 | | `HH12` | 시간 12시간제 (`1~12`) | | `HH24` | 시간 24시간제 (`0~23`) | | `HH2`, `HH3`, `HH6` | 지정 단위로 시간 절사 | | `MI` | 분 2자리 | | `MI2`, `MI5`, `MI10`, `MI20`, `MI30` | 지정 단위로 분 절사 | | `SS` | 초 2자리 | | `SS2`, `SS5`, `SS10`, `SS20`, `SS30` | 지정 단위로 초 절사 | | `AM` | AM/PM | | `mmm` | 밀리초 3자리 (`0~999`) | | `uuu` | 마이크로초 3자리 (`0~999`) | | `nnn` | 나노초 3자리 (`0~999`) | ```sql Mach> SELECT TO_CHAR(dt, 'YYYY-MM-DD HH24:MI:SS') FROM datetime_table; 2014-12-30 11:22:33 2013-11-11 01:02:03 Mach> SELECT TO_CHAR(dt, 'YYYY-MM-DD HH24:MI:SS mmm.uuu.nnn') FROM datetime_table; 2014-12-30 11:22:33 444.555.666 ``` --- ## ADD_TIME `DATETIME` 컬럼에 년/월/일/시/분/초 단위의 증감 연산을 수행합니다. 밀리초·마이크로초·나노초 단위는 지원하지 않습니다. ```sql ADD_TIME(column, time_diff_format) ``` `time_diff_format` 형식: `"Year/Month/Day Hour:Minute:Second"` (각 항목은 양수 또는 음수) ```sql -- 1년 후 Mach> SELECT ADD_TIME(dt, '1/0/0 0:0:0') FROM t; -- 1시간 1분 1초 후 Mach> SELECT ADD_TIME(dt, '0/0/0 1:1:1') FROM t; -- 1년 1개월 1일 전 Mach> SELECT ADD_TIME(dt, '-1/-1/-1 0:0:0') FROM t; ``` --- ## DATE_TRUNC 주어진 `DATETIME` 값을 지정 시간 단위로 절사하여 반환합니다. `count`를 지정하면 해당 배수 단위로 절사합니다. ```sql DATE_TRUNC(field, date_val [, count]) ``` ### 지원 시간 단위 및 최대 범위 | 시간 단위 | 최대 범위 | |-----------|----------| | `nanosecond` (`nsec`) | 1,000,000,000 (1초) | | `microsecond` (`usec`) | 60,000,000 (60초) | | `millisecond` (`msec`) | 60,000 (60초) | | `second` (`sec`) | 86,400 (1일) | | `minute` (`min`) | 1,440 (1일) | | `hour` | 24 (1일) | | `day` | 1 | | `week` | 1 (일요일 시작) | | `month` | 1 | | `year` | 1 | ```sql -- 초 단위 절사 Mach> SELECT COUNT(*), DATE_TRUNC('second', i2) tm FROM t GROUP BY tm ORDER BY 2; -- 2초 단위 절사 Mach> SELECT COUNT(*), DATE_TRUNC('second', i2, 2) tm FROM t GROUP BY tm ORDER BY 2; -- 2분 단위 절사 (DATE_TRUNC('second', time, 120)과 동일) Mach> SELECT COUNT(*), DATE_TRUNC('minute', ts, 2) tm FROM t GROUP BY tm; ``` --- ## DATE_BIN 지정한 기준 시각(`origin`)을 기준으로 `DATETIME` 값을 시간 단위와 범위로 버킷 처리합니다. `origin`을 생략하면 로컬 타임존 기준 `1970-01-01 00:00:00`을 사용합니다. ```sql DATE_BIN(field, count, source [, origin]) ``` ```sql -- 2시간 버킷 (특정 origin 기준) SELECT DATE_BIN('hour', 2, time, TO_DATE('2020-01-01 00:00:00')) FROM log ORDER BY time; -- 3시간 버킷 (로컬 타임존 경계 기준) SELECT DATE_BIN('hour', 3, ts) FROM t ORDER BY ts; ``` --- ## DAYOFWEEK `DATETIME` 값의 요일을 정수로 반환합니다. ```sql DAYOFWEEK(date_val) ``` | 반환값 | 요일 | |--------|------| | 0 | 일요일 | | 1 | 월요일 | | 2 | 화요일 | | 3 | 수요일 | | 4 | 목요일 | | 5 | 금요일 | | 6 | 토요일 | ```sql SELECT DAYOFWEEK(dt) FROM log_table; ``` --- ## YEAR / MONTH / DAY 입력 `DATETIME` 값에서 연, 월, 일을 추출해 정수로 반환합니다. ```sql YEAR(datetime_col) MONTH(datetime_col) DAY(datetime_col) ``` ```sql Mach> SELECT YEAR(c1), MONTH(c1), DAY(c1) FROM extract_table; year(c1) month(c1) day(c1) --------------------------------- 2001 1 1 ``` --- ## FROM_UNIXTIME / UNIX_TIMESTAMP `FROM_UNIXTIME`은 32비트 Unix 타임스탬프 정수를 `DATETIME`으로 변환합니다. `UNIX_TIMESTAMP`는 반대로 `DATETIME`을 32비트 Unix 타임스탬프로 변환합니다. ```sql FROM_UNIXTIME(unix_timestamp_value) UNIX_TIMESTAMP(datetime_value) ``` ```sql Mach> SELECT FROM_UNIXTIME(315540671); 1980-01-01 11:11:11 000:000:000 Mach> INSERT INTO unix_table VALUES (UNIX_TIMESTAMP('2001-01-01')); Mach> SELECT * FROM unix_table; C1 ----------- 978274800 ``` --- ## FROM_TIMESTAMP / TO_TIMESTAMP `FROM_TIMESTAMP`는 UTC 기준 1970-01-01 00:00:00부터 경과한 나노초 정수를 `DATETIME`으로 변환합니다. `TO_TIMESTAMP`는 반대로 `DATETIME`을 같은 기준 시점부터 경과한 나노초 정수로 변환합니다. 기준 시점은 UTC+09:00에서 1970-01-01 09:00:00으로 표시됩니다. 아래 예제의 날짜와 시각은 UTC+09:00 기준입니다. ```sql FROM_TIMESTAMP(nanosecond_time_value) TO_TIMESTAMP(datetime_value) ``` ```sql Mach> SELECT FROM_TIMESTAMP(1562302560007248869); 2019-07-05 13:56:00 007:248:869 Mach> SELECT TO_TIMESTAMP(c1) FROM datetime_tbl; to_timestamp(c1) ----------------------- 1262308210000000000 ``` 나노초 단위 산술 연산 예시: ```sql -- 현재 시각에서 1ms (1,000,000 ns) 전 SELECT FROM_TIMESTAMP(SYSDATE - 1000000) FROM t; ``` --- title: "NEXTVAL 함수" url: https://docs.machbase.com/kr/dbms/reference/sql/functions/nextval/ language: kr kind: page --- # NEXTVAL 함수 `NEXTVAL`은 LOOKUP 테이블의 SEQUENCE 컬럼에 대해 다음 자동 증가값을 `INT64`로 반환합니다. `INSERT`의 값 식에서만 사용할 수 있습니다. ## 문법 ```sql NEXTVAL(sequence_column) ``` - `sequence_column`은 `PROPERTY(SEQUENCE=...)` 속성으로 생성된 컬럼이어야 합니다. - `INSERT` 문 이외의 컨텍스트(SELECT, WHERE 등)에서는 사용할 수 없습니다. - 인자는 정확히 하나이며 같은 INSERT 대상 테이블의 SEQUENCE 컬럼을 지정합니다. --- ## Sequence 컬럼 생성 SEQUENCE 컬럼은 LOOKUP 테이블의 `LONG` 또는 `INT64` 컬럼에서 지원합니다. `PROPERTY(SEQUENCE=1)`은 시작값을 1로 지정합니다. ```sql CREATE LOOKUP TABLE seq_lookup ( id LONG PROPERTY(SEQUENCE=1) PRIMARY KEY, name VARCHAR(64) ); ``` --- ## NEXTVAL 사용 ```sql -- NEXTVAL로 자동 증가 ID 삽입 INSERT INTO seq_lookup (id, name) VALUES (NEXTVAL(id), 'sensor-a'); INSERT INTO seq_lookup (id, name) VALUES (NEXTVAL(id), 'sensor-b'); INSERT INTO seq_lookup (id, name) VALUES (NEXTVAL(id), 'sensor-c'); -- 결과 확인 SELECT * FROM seq_lookup; id name ---------- 1 sensor-a 2 sensor-b 3 sensor-c DROP TABLE seq_lookup; ``` --- ## 주의사항 - `NEXTVAL`은 `INSERT` 문에서만 사용할 수 있습니다. - SEQUENCE 컬럼은 **LOOKUP 테이블**에서만 지원됩니다. TAG, LOG, VOLATILE, TRANSACTION 테이블에서는 사용할 수 없습니다. - `LONG`·`INT64` 이외의 타입, 일반 컬럼, `SELECT`·`WHERE` 호출은 오류입니다. - Sequence 번호는 트랜잭션 롤백이나 오류 발생 시에도 재사용되지 않을 수 있습니다 (gap이 발생할 수 있음). - 자세한 DDL 설명은 [DDL - Sequence Column](../../syntax/) 문서를 참고하십시오. --- title: "전체 함수 레퍼런스" url: https://docs.machbase.com/kr/dbms/reference/sql/functions/functions-full/ language: kr kind: page --- # 전체 함수 레퍼런스 ## 오류 처리 |오류 유형|코드|발생 조건| |---|---|---| |인자 타입 오류|`ERR-02036`, `ERR-02037`|숫자 타입이 아닌 값을 넣었거나 `PI`에 인자를 전달한 경우| |실행 오류|`ERR-02317`|`SQRT`의 음수 입력, `MOD`의 0 나누기, `LOG`의 잘못된 밑/값, `EXP`/`POWER`의 범위 초과 등| 입력이 `NULL`이면 결과도 `NULL`입니다. ## ABS 숫자형 컬럼의 절댓값을 실수로 반환합니다. ```sql ABS(column_expr) ``` ```sql Mach> CREATE LOG TABLE abs_table (c1 INTEGER, c2 DOUBLE, c3 VARCHAR(10)); Created successfully. Mach> INSERT INTO abs_table VALUES(1, 1.0, ''); 1 row(s) inserted. Mach> INSERT INTO abs_table VALUES(2, 2.0, 'sqltest'); 1 row(s) inserted. Mach> INSERT INTO abs_table VALUES(3, 3.0, 'sqltest'); 1 row(s) inserted. Mach> SELECT ABS(c1), ABS(c2) FROM abs_table; SELECT ABS(c1), ABS(c2) from abs_table; ABS(c1) ABS(c2) ----------------------------------------------------------- 3 3 2 2 1 1 [3] row(s) selected. ``` ## ADD_TIME DATETIME 컬럼에 년/월/일/시/분/초 단위의 증감 연산을 수행합니다. 밀리초, 마이크로초, 나노초 단위는 지원하지 않습니다. Diff 형식은 `"Year/Month/Day Hour:Minute:Second"`이며, 각 항목은 양수 또는 음수를 사용할 수 있습니다. ```sql ADD_TIME(column,time_diff_format) ``` ```sql Mach> CREATE LOG TABLE add_time_table (id INTEGER, dt DATETIME); Created successfully. Mach> INSERT INTO add_time_table VALUES(1, TO_DATE('1999-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO add_time_table VALUES(2, TO_DATE('2000-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO add_time_table VALUES(3, TO_DATE('2012-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO add_time_table VALUES(4, TO_DATE('2013-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO add_time_table VALUES(5, TO_DATE('2014-12-30 11:22:33 444:555:666')); 1 row(s) inserted. Mach> INSERT INTO add_time_table VALUES(6, TO_DATE('2014-12-30 23:22:33 444:555:666')); 1 row(s) inserted. Mach> SELECT ADD_TIME(dt, '1/0/0 0:0:0') FROM add_time_table; ADD_TIME(dt, '1/0/0 0:0:0') ---------------------------------- 2015-12-30 23:22:33 444:555:666 2015-12-30 11:22:33 444:555:666 2014-11-11 01:02:03 004:005:006 2013-11-11 01:02:03 004:005:006 2001-11-11 01:02:03 004:005:006 2000-11-11 01:02:03 004:005:006 [6] row(s) selected. Mach> SELECT ADD_TIME(dt, '0/0/0 1:1:1') FROM add_time_table; ADD_TIME(dt, '0/0/0 1:1:1') ---------------------------------- 2014-12-31 00:23:34 444:555:666 2014-12-30 12:23:34 444:555:666 2013-11-11 02:03:04 004:005:006 2012-11-11 02:03:04 004:005:006 2000-11-11 02:03:04 004:005:006 1999-11-11 02:03:04 004:005:006 [6] row(s) selected. Mach> SELECT ADD_TIME(dt, '1/1/1 0:0:0') FROM add_time_table; ADD_TIME(dt, '1/1/1 0:0:0') ---------------------------------- 2016-01-31 23:22:33 444:555:666 2016-01-31 11:22:33 444:555:666 2014-12-12 01:02:03 004:005:006 2013-12-12 01:02:03 004:005:006 2001-12-12 01:02:03 004:005:006 2000-12-12 01:02:03 004:005:006 [6] row(s) selected. Mach> SELECT ADD_TIME(dt, '-1/0/0 0:0:0') FROM add_time_table; ADD_TIME(dt, '-1/0/0 0:0:0') ---------------------------------- 2013-12-30 23:22:33 444:555:666 2013-12-30 11:22:33 444:555:666 2012-11-11 01:02:03 004:005:006 2011-11-11 01:02:03 004:005:006 1999-11-11 01:02:03 004:005:006 1998-11-11 01:02:03 004:005:006 [6] row(s) selected. Mach> SELECT ADD_TIME(dt, '0/0/0 -1:-1:-1') FROM add_time_table; ADD_TIME(dt, '0/0/0 -1:-1:-1') ---------------------------------- 2014-12-30 22:21:32 444:555:666 2014-12-30 10:21:32 444:555:666 2013-11-11 00:01:02 004:005:006 2012-11-11 00:01:02 004:005:006 2000-11-11 00:01:02 004:005:006 1999-11-11 00:01:02 004:005:006 [6] row(s) selected. Mach> SELECT ADD_TIME(dt, '-1/-1/-1 0:0:0') FROM add_time_table; ADD_TIME(dt, '-1/-1/-1 0:0:0') ---------------------------------- 2013-11-29 23:22:33 444:555:666 2013-11-29 11:22:33 444:555:666 2012-10-10 01:02:03 004:005:006 2011-10-10 01:02:03 004:005:006 1999-10-10 01:02:03 004:005:006 1998-10-10 01:02:03 004:005:006 [6] row(s) selected. Mach> SELECT * FROM add_time_table WHERE dt > ADD_TIME(TO_DATE('2014-12-30 11:22:33 444:555:666'), '-1/-1/-1 0:0:0'); ID DT ----------------------------------------------- 6 2014-12-30 23:22:33 444:555:666 5 2014-12-30 11:22:33 444:555:666 [2] row(s) selected. Mach> SELECT * FROM add_time_table WHERE dt > ADD_TIME(TO_DATE('2014-12-30 11:22:33 444:555:666'), '-1/-2/-1 0:0:0'); ID DT ----------------------------------------------- 6 2014-12-30 23:22:33 444:555:666 5 2014-12-30 11:22:33 444:555:666 4 2013-11-11 01:02:03 004:005:006 [3] row(s) selected. Mach> SELECT ADD_TIME(TO_DATE('2000-12-01 00:00:00 000:000:001'), '-1/0/0 0:0:-1') FROM add_time_table; ADD_TIME(TO_DATE('2000-12-01 00:00:00 000:000:001'), '-1/0/0 0:0:-1') ------------------------------------------ 1999-11-30 23:59:59 000:000:001 1999-11-30 23:59:59 000:000:001 1999-11-30 23:59:59 000:000:001 1999-11-30 23:59:59 000:000:001 1999-11-30 23:59:59 000:000:001 1999-11-30 23:59:59 000:000:001 [6] row(s) selected. Mach> SELECT * FROM add_time_table WHERE dt > ADD_TIME(TO_DATE('2014-12-30 11:22:33 444:555:666'), '-1/-2/-1 0:0:0'); ID DT ----------------------------------------------- 6 2014-12-30 23:22:33 444:555:666 5 2014-12-30 11:22:33 444:555:666 4 2013-11-11 01:02:03 004:005:006 [3] row(s) selected. ``` ## APPROX_PERCENTILE {#approx_percentile-family} ``` APPROX_PERCENTILE APPROX_MEDIAN APPROX_P05 APPROX_P10 APPROX_P90 APPROX_P95 ``` 이 함수들은 원시 값을 모두 정렬하지 않고 제한된 크기의 summary를 유지해 분위값을 근사합니다. 입력 데이터가 매우 크고, 작은 오차를 허용할 수 있을 때 유용합니다. ```sql APPROX_PERCENTILE(value, ratio) APPROX_MEDIAN(value) APPROX_P05(value) APPROX_P10(value) APPROX_P90(value) APPROX_P95(value) ``` - `value`는 숫자형이어야 합니다. - `ratio`는 `0.0` 이상 `1.0` 이하의 상수여야 합니다. - 반환 타입은 `DOUBLE`입니다. - `NULL` 값은 무시합니다. `APPROX_MEDIAN(value)`는 근사 중앙값이며, `APPROX_P05`, `APPROX_P10`, `APPROX_P90`, `APPROX_P95`는 자주 쓰는 분위값을 위한 축약형입니다. ```sql SELECT APPROX_PERCENTILE(latency_ms, 0.95) AS ap95, APPROX_MEDIAN(latency_ms) AS amedian, APPROX_P05(latency_ms) AS ap05 FROM api_log; ``` ## ARRAY_LENGTH `ARRAY_LENGTH(array_value)`는 non-NULL `ARRAY`의 선언 cardinality를 반환합니다. ```sql SELECT ARRAY_LENGTH(ARRAY[10, NULL, 30]); -- 3 ``` 모든 요소가 NULL이어도 cardinality를 반환합니다. whole NULL은 NULL을 반환하며, 타입 정보가 없는 `ARRAY_LENGTH(NULL)`은 오류입니다. 자세한 ARRAY 문법과 제약은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ## ARRAY_SPARSE `ARRAY_SPARSE`는 고정 길이 `ARRAY`에서 값이 있는 위치만 지정합니다. 위치는 0부터 시작하며, 생략한 위치는 element NULL입니다. ```sql -- 대상 컬럼에서 타입과 cardinality를 결정합니다. INSERT INTO sensor_array (id, channels) VALUES (1, ARRAY_SPARSE(0 => 10, 3 => 40)); -- 대상이 없는 표현식은 타입과 cardinality를 명시합니다. SELECT ARRAY_SPARSE(INT32[4], 0 => 10, 3 => 40); -- bracket 축약형은 가장 큰 position + 1로 cardinality를 추론합니다. SELECT [1 => 12, 33 => 23]; ``` bracket 축약형은 대상 ARRAY가 있으면 대상 타입과 cardinality를 사용합니다. standalone 표현식이면 dense ARRAY와 같은 숫자 공통 타입을 사용하고 가장 큰 position에 1을 더해 cardinality를 정합니다. target 없는 all-NULL sparse, 중복 또는 범위 밖 position은 오류입니다. 입력 방식과 SDK sparse 객체는 [Sparse ARRAY와 선택 컬럼 Append API](/dbms/development-tools-integration/data-input-load-export/array-append/)를 참고하십시오. ## AREA {#area} `AREA(y, x)`는 숫자형 `(x, y)` 점들로 이루어진 곡선 아래 면적을 정확하게 계산하는 집계 함수입니다. ```sql AREA(y, x) ``` - 두 인자는 모두 숫자형이어야 합니다. - 둘 중 하나라도 `NULL`인 행은 무시합니다. - 유효한 점이 2개 미만이면 결과는 `NULL`입니다. - 반환 타입은 `DOUBLE`입니다. ```sql SELECT AREA(power_kw, sample_sec) FROM power_log; ``` ## AVG 숫자형 컬럼의 평균값을 반환하는 집계 함수입니다. ```sql AVG(column_name) ``` ```sql Mach> CREATE LOG TABLE avg_table (id1 INTEGER, id2 INTEGER); Created successfully. Mach> INSERT INTO avg_table VALUES(1, 1); 1 row(s) inserted. Mach> INSERT INTO avg_table VALUES(1, 2); 1 row(s) inserted. Mach> INSERT INTO avg_table VALUES(1, 3); 1 row(s) inserted. Mach> INSERT INTO avg_table VALUES(2, 1); 1 row(s) inserted. Mach> INSERT INTO avg_table VALUES(2, 2); 1 row(s) inserted. Mach> INSERT INTO avg_table VALUES(2, 3); 1 row(s) inserted. Mach> INSERT INTO avg_table VALUES(null, 4); 1 row(s) inserted. Mach> SELECT id1, AVG(id2) FROM avg_table GROUP BY id1; id1 AVG(id2) ------------------------------------------- 2 2 NULL 4 1 2 ``` ## BITAND / BITOR 두 정수 값을 64비트 부호 있는 정수로 변환한 뒤 비트 단위 AND/OR 연산 결과를 반환합니다. 입력은 정수형이어야 하며, 출력도 64비트 부호 있는 정수입니다. 0보다 작은 정수 값의 경우, 플랫폼에 따라 다른 결과가 나올 수 있으므로 uinteger 및 ushort 타입만 사용하는 것을 권장합니다. ```sql BITAND (, ) BITOR (, ) ``` ```sql Mach> CREATE LOG TABLE bit_table (i1 INTEGER, i2 UINTEGER, i3 FLOAT, i4 DOUBLE, i5 SHORT, i6 VARCHAR(10)); Created successfully. Mach> INSERT INTO bit_table VALUES (-1, 1, 1, 1, 2, 'aaa'); 1 row(s) inserted. Mach> INSERT INTO bit_table VALUES (-2, 2, 2, 2, 3, 'bbb'); 1 row(s) inserted. Mach> SELECT BITAND(i1, i2) FROM bit_table; BITAND(i1, i2) ----------------------- 2 1 [2] row(s) selected. Mach> SELECT * FROM bit_table WHERE BITAND(i2, 1) = 1; I1 I2 I3 I4 I5 I6 --------------------------------------------------------------------------------------------------------------- -1 1 1 1 2 aaa [1] row(s) selected. Mach> SELECT BITOR(i5, 1) FROM bit_table WHERE BITOR(i5, 1) = 3; BITOR(i5, 1) ----------------------- 3 3 [2] row(s) selected. Mach> SELECT * FROM bit_table WHERE BITOR(i2, 1) = 1; I1 I2 I3 I4 I5 I6 --------------------------------------------------------------------------------------------------------------- -1 1 1 1 2 aaa [1] row(s) selected. Mach> SELECT * FROM bit_table WHERE BITAND(i3, 1) = 1; I1 I2 I3 I4 I5 I6 --------------------------------------------------------------------------------------------------------------- [ERR-02037 : Function [BITAND] argument data type is mismatched.] [0] row(s) selected. Mach> SELECT * FROM bit_table WHERE BITAND(i4, 1) = 1; I1 I2 I3 I4 I5 I6 --------------------------------------------------------------------------------------------------------------- [ERR-02037 : Function [BITAND] argument data type is mismatched.] [0] row(s) selected. Mach> SELECT BITAND(i5, 1) FROM bit_table WHERE BITAND(i5, 1) = 1; BITAND(i5, 1) ----------------------- 1 [1] row(s) selected. Mach> SELECT * FROM bit_table WHERE BITOR(i6, 1) = 1; I1 I2 I3 I4 I5 I6 --------------------------------------------------------------------------------------------------------------- [ERR-02037 : Function [BITOR] argument data type is mismatched.] [0] row(s) selected. Mach> SELECT BITOR(i1, i2) FROM bit_table; BITOR(i1, i2) ----------------------- -2 -1 [2] row(s) selected. Mach> SELECT BITAND(i1, i3) FROM bit_table; BITAND(i1, i3) ----------------------- [ERR-02037 : Function [BITAND] argument data type is mismatched.] [0] row(s) selected. Mach> SELECT BITOR(i1, i6) FROM bit_table; BITOR(i1, i6) ----------------------- [ERR-02037 : Function [BITOR] argument data type is mismatched.] [0] row(s) selected. ``` ## CAST Machbase 8.7.0부터 지원되는 기능 `CAST`는 값, 컬럼 또는 표현식을 지정한 데이터 타입으로 명시적으로 변환합니다. `SELECT`, 조건식, `CASE`, `UNION ALL`, VIEW 정의와 prepared statement에서 사용할 수 있습니다. ### 문법 ```sql CAST(expression AS data_type) CAST(expression AS data_type(length)) CAST(expression AS DECIMAL(precision[, scale])) CAST(array_expression AS numeric_type[cardinality]) CAST(array_expression AS DECIMAL(precision[, scale])[cardinality]) ``` - `expression`은 변환할 값, 컬럼 또는 SQL 표현식입니다. - `array_expression`은 숫자 `ARRAY` 또는 SQL `NULL`입니다. - `data_type`은 아래 표의 대상 타입 또는 별칭입니다. - 타입 이름은 대소문자를 구분하지 않습니다. - `length`, `precision`, `scale`은 대상 타입에서 허용할 때만 지정할 수 있습니다. - ARRAY 입력과 대상의 `cardinality`는 정확히 같아야 합니다. ### 지원 타입과 별칭 | 분류 | 대상 타입 | 사용할 수 있는 이름 | |------|-----------|---------------------| | 부호 있는 정수 | 16비트 | `INT16`, `SHORT` | | | 32비트 | `INT32`, `INT`, `INTEGER` | | | 64비트 | `INT64`, `LONG` | | 부호 없는 정수 | 16비트 | `UINT16`, `USHORT` | | | 32비트 | `UINT32`, `UINTEGER` | | | 64비트 | `UINT64`, `ULONG` | | 실수 | 단정밀도·배정밀도 | `FLOAT`, `DOUBLE` | | 고정소수 | DECIMAL | `DECIMAL`, `NUMERIC`, `DEC`, `FIXED`, `NUMBER` | | 문자 | 고정 길이·가변 길이 | `CHAR`, `VARCHAR` | | 문자 LOB | 텍스트 | `TEXT`, `CLOB` | | 날짜와 시간 | 나노초 정밀도 | `DATETIME` | | 네트워크 주소 | IP 주소 | `IPV4`, `IPV6` | | 바이너리 | 바이너리·바이너리 LOB | `BINARY`, `BLOB` | | 문서 | JSON | `JSON` | 같은 행의 이름은 같은 타입으로 동작합니다. 예를 들어 `INTEGER`, `INT`, `INT32`는 모두 32비트 부호 있는 정수입니다. 결과 컬럼의 타입 메타데이터에는 표준 타입 이름이 표시될 수 있습니다. ### 길이와 정밀도 #### CHAR, VARCHAR, BINARY | 타입 | 길이 생략 시 기본값 | 허용 길이 | 길이 초과 처리 | |------|-------------------:|-----------|----------------| | `CHAR(n)` | 1 byte | 1~32,767 byte | 앞에서부터 `n` byte 보존 | | `VARCHAR(n)` | 32,767 byte | 1~32,767 byte | 앞에서부터 `n` byte 보존 | | `BINARY(n)` | 1 byte | 1~67,108,864 byte | 앞에서부터 `n` byte 보존 | 길이는 문자 수가 아니라 byte 수입니다. UTF-8 문자열을 변환할 때는 다중 byte 문자가 중간에서 잘릴 수 있으므로 충분한 길이를 지정하십시오. `CHAR`는 남는 공간을 공백으로 채우지 않습니다. `CAST(... AS CHAR(n))`의 결과 메타데이터는 현재 `VARCHAR(n)`으로 표시됩니다. ```sql SELECT '[' || CAST('abc' AS CHAR) || ']' AS char_default; -- [a] SELECT '[' || CAST('abc' AS CHAR(5)) || ']' AS char_value; -- [abc] (공백을 추가하지 않음) SELECT CAST('abcdef' AS VARCHAR(3)) AS varchar_value; -- abc SELECT CAST('414243' AS BINARY(2)) AS binary_value; -- 4142 ``` `TEXT`, `CLOB`, `BLOB`, `JSON`에는 `length`를 지정할 수 없습니다. 이 타입들의 CAST 결과는 현재 최대 32,767 byte를 지원하며, 허용된 길이 제한을 넘어 결과 의미가 손상되는 경우에는 자동으로 자르지 않고 오류를 반환합니다. #### DECIMAL | 구문 | 해석 | |------|------| | `DECIMAL` | `DECIMAL(10,0)` | | `DECIMAL(p)` | `DECIMAL(p,0)` | | `DECIMAL(p,s)` | precision `p`, scale `s` | - precision `p`는 `1~65`입니다. - scale `s`는 `0~30`이며 precision보다 클 수 없습니다. - 입력 값의 소수 자릿수가 scale보다 많으면 0에서 멀어지는 방향의 절반 올림을 적용합니다. - DECIMAL 계열 이외의 타입에는 precision 또는 scale을 지정할 수 없습니다. ```sql SELECT CAST('12.34' AS DECIMAL(5,2)); -- 12.34 SELECT CAST(123.456 AS NUMERIC(6,2)); -- 123.46 ``` ### NULL과 빈 문자열 - 입력이 `NULL`이면 대상 타입의 `NULL`을 반환합니다. - Machbase에서는 길이가 0인 문자열 리터럴 `''`을 SQL `NULL`로 처리합니다. - `''''`는 작은따옴표 한 글자를 나타내는 문자열이므로 빈 문자열이 아닙니다. ```sql SELECT CAST(NULL AS INTEGER) AS null_integer; SELECT CAST('' AS VARCHAR(10)) AS empty_value; SELECT CAST('''' AS VARCHAR(10)) AS quote_value; ``` ### 숫자 변환 숫자 타입끼리 변환하거나 숫자로 해석할 수 있는 문자열을 숫자 타입으로 변환할 수 있습니다. ```sql SELECT CAST('123' AS INTEGER); SELECT CAST('1.25' AS DOUBLE); SELECT CAST(12.9 AS SHORT); -- 12 SELECT CAST(-12.9 AS INTEGER); -- -12 SELECT CAST('9223372036854775806e0' AS LONG); ``` - 실수를 정수로 변환할 때 소수부는 반올림하지 않고 0 방향으로 버립니다. - 정수 문자열의 지수 표기도 정수 정밀도를 유지해 해석합니다. - 대상 타입의 범위를 벗어나는 값은 오류입니다. - 부호 없는 정수로 변환할 때 음수 결과는 허용하지 않습니다. 소수부를 버린 결과가 0인 값은 0으로 변환할 수 있습니다. - `NaN`, 양의 무한대, 음의 무한대는 정수 변환에 사용할 수 없습니다. CAST로 만들 수 있는 정수 범위는 다음과 같습니다. 각 타입의 NULL 예약값은 유효한 결과 범위에 포함되지 않습니다. | 대상 타입 | CAST 결과 범위 | |-----------|----------------| | `INT16`, `SHORT` | -32,767~32,767 | | `UINT16`, `USHORT` | 0~65,534 | | `INT32`, `INT`, `INTEGER` | -2,147,483,647~2,147,483,647 | | `UINT32`, `UINTEGER` | 0~4,294,967,294 | | `INT64`, `LONG` | -9,223,372,036,854,775,807~9,223,372,036,854,775,807 | | `UINT64`, `ULONG` | 0~18,446,744,073,709,551,614 | ### 숫자 ARRAY 전체 변환 같은 cardinality의 숫자 `ARRAY`는 요소 타입을 전체 변환할 수 있습니다. 대상에는 `INT16`, `UINT16`, `INT32`, `UINT32`, `INT64`, `UINT64`, `FLOAT`, `DOUBLE`, `DECIMAL`과 지원 타입 표의 숫자 별칭을 사용합니다. ```sql SELECT CAST([1.9, NULL, -3.9] AS INT32[3]); SELECT CAST([1.235, NULL, -2.345] AS DECIMAL(6,2)[3]); ``` - whole NULL은 변환 뒤에도 whole NULL입니다. - element NULL은 같은 위치의 element NULL로 유지됩니다. - 각 non-NULL 요소에는 대응하는 scalar 숫자 CAST의 절삭, 반올림과 범위 규칙을 적용합니다. - 한 요소라도 변환할 수 없으면 CAST와 이를 포함한 문장 전체가 실패합니다. 변환된 일부 요소나 행을 결과로 남기지 않습니다. - `DECIMAL[N]`은 `DECIMAL(10,0)[N]`, `DECIMAL(p)[N]`은 `DECIMAL(p,0)[N]`으로 처리합니다. prepared statement에서도 CAST 대상이 parameter의 요소 타입, cardinality와 DECIMAL precision/scale을 결정합니다. 같은 statement에 dense ARRAY, sparse ARRAY와 whole NULL을 다시 bind할 수 있습니다. ```sql SELECT CAST(? AS INT32[3]); SELECT CAST(? AS DECIMAL(12,4)[3]); ``` scalar를 ARRAY로 확장하거나 ARRAY를 scalar로 축소할 수 없습니다. 서로 다른 cardinality 사이에 padding 또는 truncation하지 않으며 문자열, 날짜, IP, BINARY, JSON ARRAY를 대상으로 지정할 수 없습니다. ### 문자열 및 LOB 변환 숫자, 날짜와 시간, IP 주소, 바이너리, JSON을 문자 타입으로 변환할 수 있습니다. - 정수와 DECIMAL은 값의 10진수 표현을 반환합니다. - `FLOAT`는 최대 9자리, `DOUBLE`은 최대 17자리의 유효 숫자를 사용해 표현합니다. - `DATETIME`은 세션의 날짜 형식과 시간대에 따라 문자열로 표시됩니다. - `IPV4`와 `IPV6`은 표준화된 주소 문자열로 표시됩니다. - `BINARY`와 `BLOB`은 접두사 없는 대문자 16진수 문자열로 표시됩니다. - JSON은 원문의 JSON 표현을 유지합니다. ```sql SELECT CAST(123456 AS VARCHAR(8)); -- 123456 SELECT CAST(CAST('2001:db8::1' AS IPV6) AS VARCHAR(64)); SELECT CAST(CAST('0x00ff10' AS BLOB) AS VARCHAR(8)); -- 00FF10 ``` 문자열을 `BINARY` 또는 `BLOB`으로 변환할 때는 접두사 없는 짝수 길이 16진수 또는 `0x`/`0X` 접두사가 있는 짝수 길이 16진수를 사용합니다. ```sql SELECT CAST('414243' AS BINARY(3)); SELECT CAST('0x00ff10' AS BLOB); SELECT CAST(X'414243' AS VARCHAR(6)); ``` `BINARY(n)`은 앞에서부터 `n` byte만 보존합니다. 16진수가 아닌 문자나 홀수 길이 16진수는 오류입니다. ### DATETIME 변환 문자열 또는 숫자를 `DATETIME`으로 변환할 수 있습니다. - 문자열은 session의 기본 날짜 형식과 시간대를 사용해 해석합니다. - 숫자는 Unix epoch 기준 nanosecond로 해석합니다. - 숫자 `-1`은 DATETIME의 NULL 표시용 예약값이므로 변환할 수 없습니다. - `DATETIME`을 숫자로 변환하면 Unix epoch 기준 nanosecond 값을 반환합니다. ```sql SELECT CAST('2026-08-15 12:34:56' AS DATETIME); SELECT CAST(1000000000 AS DATETIME); SELECT CAST(CAST(1000000000 AS DATETIME) AS VARCHAR(40)); ``` 같은 epoch 값도 session timezone이 다르면 문자열로 표시되는 날짜와 시간이 달라질 수 있습니다. ### IPV4와 IPV6 변환 문자열을 `IPV4` 또는 `IPV6`으로 변환할 수 있습니다. 주소 전체가 올바른 형식이어야 합니다. ```sql SELECT CAST('127.0.0.1' AS IPV4); SELECT CAST('2001:db8::1' AS IPV6); ``` 잘못된 주소나 대상 타입과 맞지 않는 주소 형식은 오류입니다. ### JSON 변환 문자열을 `JSON`으로 변환할 때는 입력 전체가 유효한 JSON이어야 합니다. 객체와 배열뿐 아니라 JSON 문자열, 숫자, `true`, `false`, `null`도 사용할 수 있습니다. ```sql SELECT CAST('{"ok":true}' AS JSON); SELECT CAST('[1,2,3]' AS JSON); SELECT CAST('"abc"' AS JSON); SELECT CAST(CAST('"abc"' AS JSON) AS VARCHAR(16)); -- "abc" ``` 일부만 유효한 JSON이거나 JSON이 아닌 문자가 뒤에 남아 있으면 변환할 수 없습니다. ### 표현식과 결과 메타데이터 CAST는 일반 SQL 표현식이므로 WHERE 조건, `CASE`, `UNION ALL`, VIEW 정의에서도 사용할 수 있습니다. ```sql SELECT CASE WHEN reading >= 0 THEN CAST(reading AS VARCHAR(32)) ELSE 'invalid' END AS reading_text FROM sensor_log; CREATE VIEW sensor_cast_view AS SELECT CAST(sensor_id AS VARCHAR(100)) AS sensor_id_text, CAST(value AS DECIMAL(12,3)) AS value_decimal FROM sensor_log; ``` prepared statement에서도 CAST 구문은 동일합니다. 입력값은 `?` 또는 SDK가 제공하는 named marker로 전달하고, 대상 타입과 precision/scale은 SQL에 선언합니다. ```sql SELECT CAST(? AS DECIMAL(12,2)) AS amount; ``` CAST 결과의 타입, byte 길이, DECIMAL precision과 scale은 결과 메타데이터와 VIEW 컬럼 정보에 반영됩니다. 결과의 NULL 가능 여부는 입력 표현식의 NULL 가능 여부를 따릅니다. `CASE` 또는 `UNION ALL`에서 ARRAY 결과를 결합하려면 요소 타입, cardinality와 DECIMAL precision/scale이 모두 같아야 합니다. 서로 다르면 각 결과를 명시적으로 같은 ARRAY 타입으로 CAST한 뒤 결합합니다. 각 SDK는 기존 결과 메타데이터 API로 CAST 결과를 확인합니다. CAST 전용 SDK API는 제공하지 않습니다. | SDK | CAST 결과 metadata API | |-----|------------------------| | Machbase SQLCLI | `SQLDescribeCol()`, `SQLColAttribute()` | | ODBC | `SQLDescribeCol()`, `SQLColAttribute()` | | JDBC | `ResultSetMetaData` | | Python | `cursor.description` | | Node.js | `ColumnMeta` | | .NET | `GetSchemaTable()` | | Go (native) | native column metadata | | Go (`database/sql`) | `ColumnTypeNullable()` 및 `ColumnType` API | ### 오류가 발생하는 경우 | 원인 | 예 | |------|-----| | 지원하지 않는 대상 타입 | `CAST('1' AS UNKNOWN_TYPE)` | | 허용되지 않은 length 또는 precision/scale | `CAST('1' AS INTEGER(2))`, `CAST('1' AS DECIMAL(2,3))` | | 숫자 범위 초과 또는 NULL 예약값 | `CAST('65535' AS USHORT)` | | 부호 없는 정수로 변환되는 음수 | `CAST('-1' AS UINTEGER)` | | 숫자로 변환할 수 없는 문자열 | `CAST('12x' AS INTEGER)` | | scalar와 ARRAY 사이의 변환 | `CAST(1 AS INT32[1])`, `CAST([1] AS INT32)` | | ARRAY cardinality 불일치 | `CAST([1, 2] AS INT32[3])` | | 지원하지 않는 ARRAY 대상 타입 | `CAST([1] AS VARCHAR[1])` | | 잘못된 IP 주소 | `CAST('999.1.1.1' AS IPV4)` | | 홀수 길이 또는 비16진수 바이너리 문자열 | `CAST('123' AS BINARY(4))`, `CAST('GG' AS BLOB)` | | 유효하지 않은 JSON | `CAST('{bad}' AS JSON)` | | 허용 크기를 초과하는 LOB 또는 JSON 결과 | 32,767 byte를 초과하는 `TEXT`, `CLOB`, `BLOB`, `JSON` 결과 | ### 호환성 CAST 함수와 숫자 ARRAY 전체 CAST는 Machbase 8.7.0에서 지원됩니다. Standard Edition과 Cluster Edition에서 사용할 수 있으며, Cluster Edition에서는 모든 cluster node가 CAST를 지원하는 동일 버전이어야 합니다. CAST를 지원하지 않는 구버전 node와의 혼합 실행은 지원하지 않습니다. ### 관련 문서 - [SQL 문법 사전](../../syntax/) - [데이터 타입 사전](../../types/) - [숫자 ARRAY 타입](../../types/array/) - [DECIMAL과 NUMERIC 고정소수점 타입](../../types/decimal-numeric-fixed-point/) ## COUNT 컬럼의 레코드 개수를 구하는 집계 함수입니다. ```sql COUNT(column_name) ``` ```sql Mach> CREATE LOG TABLE count_table (id1 INTEGER, id2 INTEGER); Created successfully. Mach> INSERT INTO count_table VALUES(1, 1); 1 row(s) inserted. Mach> INSERT INTO count_table VALUES(1, 2); 1 row(s) inserted. Mach> INSERT INTO count_table VALUES(1, 3); 1 row(s) inserted. Mach> INSERT INTO count_table VALUES(2, 1); 1 row(s) inserted. Mach> INSERT INTO count_table VALUES(2, 2); 1 row(s) inserted. Mach> INSERT INTO count_table VALUES(2, 3); 1 row(s) inserted. Mach> INSERT INTO count_table VALUES(null, 4); 1 row(s) inserted. Mach> SELECT COUNT(*) FROM count_table; COUNT(*) ----------------------- 7 [1] row(s) selected. Mach> SELECT COUNT(id1) FROM count_table; COUNT(id1) ----------------------- 6 [1] row(s) selected. ``` ## CUME_DIST {#cume_dist} `CUME_DIST(value, threshold)`는 `value`가 `threshold` 이하인 행의 누적 비율을 반환합니다. ```sql CUME_DIST(value, threshold) ``` - 윈도우 함수가 아닌 집계 함수입니다. - 두 인자는 모두 숫자형이어야 합니다. - `threshold`는 상수여야 합니다. - 반환 값은 `0.0` 이상 `1.0` 이하의 `DOUBLE`입니다. ```sql SELECT CUME_DIST(latency_ms, 100) FROM api_log; ``` ## CURRENT_USER / SESSION_USER / CURRENT_USER_ID / SESSION_USER_ID Machbase 8.7.0부터 지원되는 기능 현재 SQL 실행의 유효 권한 사용자와 접속 세션 사용자를 이름 또는 내부 ID로 조회합니다. Standard Edition과 Cluster Edition에서 모두 지원합니다. | 함수 | 반환형 | 설명 | |---|---|---| | `CURRENT_USER()` | `VARCHAR` | 현재 SQL 실행에 적용되는 유효 권한 사용자명 | | `SESSION_USER()` | `VARCHAR` | 현재 접속 세션의 인증 사용자명 | | `CURRENT_USER_ID()` | `INTEGER` | 유효 권한 사용자의 내부 ID | | `SESSION_USER_ID()` | `INTEGER` | 인증 세션 사용자의 내부 ID | 네 함수는 인자를 받지 않으며 괄호를 포함해 호출합니다. 괄호 없는 `CURRENT_USER` keyword나 `USER`, `SYSTEM_USER`, `CURRENT_SCHEMA` alias는 지원하지 않습니다. ```sql SELECT CURRENT_USER() AS current_name, SESSION_USER() AS session_name, CURRENT_USER_ID() AS current_id, SESSION_USER_ID() AS session_id; ``` 일반 SQL에서는 current user와 session user가 같습니다. ```text CURRENT_NAME SESSION_NAME CURRENT_ID SESSION_ID SYS SYS 1 1 ``` ### VIEW에서의 사용자 컨텍스트 다른 사용자가 소유한 definer VIEW를 조회하면 VIEW 내부 SQL은 소유자 권한으로 실행됩니다. - `CURRENT_USER()`와 `CURRENT_USER_ID()`는 VIEW owner를 반환합니다. - `SESSION_USER()`와 `SESSION_USER_ID()`는 VIEW를 호출한 접속 세션 사용자를 반환합니다. 재현 가능한 owner/caller 예제는 [VIEW 문법](../../syntax/view-syntax/#view-user-context)을 참고합니다. ### 사용자가 삭제된 활성 세션 다른 관리자 세션이 현재 접속 중인 사용자를 `DROP USER`해도 기존 접속은 즉시 종료되지 않습니다. 기존 세션의 네 함수는 로그인할 때 보존한 사용자명과 ID를 계속 반환합니다. 삭제된 사용자는 새로 접속할 수 없으며 `M$SYS_USERS`에서도 조회되지 않습니다. 사용자 ID는 Machbase metadata의 내부 식별자입니다. 장기간 보존하는 업무용 사용자 key로 사용하지 말고 현재 metadata를 비교하거나 join할 때만 사용합니다. ```sql SELECT COUNT(*) FROM M$SYS_USERS WHERE NAME = SESSION_USER() AND USER_ID = SESSION_USER_ID(); ``` ### 오류 함수에 인자를 전달하면 `ERR-02036`을 반환합니다. 나머지 세 함수도 같은 규칙을 적용합니다. ```sql SELECT CURRENT_USER(1); -- ERR-02036: Function [CURRENT_USER] has an invalid argument. ``` 관련 계정 lifecycle은 [계정 관리](../../../../security-access-control/account/)를 참고합니다. ## DATE_TRUNC DATETIME 값을 지정한 시간 단위로 절사하여 반환합니다. ```sql DATE_TRUNC (field, date_val [, count]) ``` ```sql Mach> CREATE LOG TABLE trunc_table (i1 INTEGER, i2 DATETIME); Created successfully. Mach> INSERT INTO trunc_table VALUES (1, TO_DATE('1999-11-11 1:2:0 4:5:1')); 1 row(s) inserted. Mach> INSERT INTO trunc_table VALUES (2, TO_DATE('1999-11-11 1:2:0 5:5:2')); 1 row(s) inserted. Mach> INSERT INTO trunc_table VALUES (3, TO_DATE('1999-11-11 1:2:1 6:5:3')); 1 row(s) inserted. Mach> INSERT INTO trunc_table VALUES (4, TO_DATE('1999-11-11 1:2:1 7:5:4')); 1 row(s) inserted. Mach> INSERT INTO trunc_table VALUES (5, TO_DATE('1999-11-11 1:2:2 8:5:5')); 1 row(s) inserted. Mach> INSERT INTO trunc_table VALUES (6, TO_DATE('1999-11-11 1:2:2 9:5:6')); 1 row(s) inserted. Mach> INSERT INTO trunc_table VALUES (7, TO_DATE('1999-11-11 1:2:3 10:5:7')); 1 row(s) inserted. Mach> INSERT INTO trunc_table VALUES (8, TO_DATE('1999-11-11 1:2:3 11:5:8')); 1 row(s) inserted. Mach> SELECT COUNT(*), DATE_TRUNC('second', i2) tm FROM trunc_table group by tm ORDER BY 2; COUNT(*) tm -------------------------------------------------------- 2 1999-11-11 01:02:00 000:000:000 2 1999-11-11 01:02:01 000:000:000 2 1999-11-11 01:02:02 000:000:000 2 1999-11-11 01:02:03 000:000:000 [4] row(s) selected. Mach> SELECT COUNT(*), DATE_TRUNC('second', i2, 2) tm FROM trunc_table group by tm ORDER BY 2; COUNT(*) tm -------------------------------------------------------- 4 1999-11-11 01:02:00 000:000:000 4 1999-11-11 01:02:02 000:000:000 [2] row(s) selected. Mach> SELECT COUNT(*), DATE_TRUNC('nanosecond', i2, 2) tm FROM trunc_table group by tm ORDER BY 2; COUNT(*) tm -------------------------------------------------------- 1 1999-11-11 01:02:00 004:005:000 1 1999-11-11 01:02:00 005:005:002 1 1999-11-11 01:02:01 006:005:002 1 1999-11-11 01:02:01 007:005:004 1 1999-11-11 01:02:02 008:005:004 1 1999-11-11 01:02:02 009:005:006 1 1999-11-11 01:02:03 010:005:006 1 1999-11-11 01:02:03 011:005:008 [8] row(s) selected. Mach> SELECT COUNT(*), DATE_TRUNC('nsec', i2, 1000000000) tm FROM trunc_table group by tm ORDER BY 2; //Same as DATE_TRUNC('sec', i2, 1) COUNT(*) tm -------------------------------------------------------- 2 1999-11-11 01:02:00 000:000:000 2 1999-11-11 01:02:01 000:000:000 2 1999-11-11 01:02:02 000:000:000 2 1999-11-11 01:02:03 000:000:000 [4] row(s) selected. ``` 시간 단위별로 허용되는 시간 범위는 다음과 같습니다. * nanosecond, microsecond, millisecond 단위 및 약어는 5.5.6부터 사용 가능합니다. * week는 일요일부터 시작합니다. |시간 단위|시간 범위| |--|--| |nanosecond (nsec)|1000000000 (1 second)| |microsecond (usec)|60000000 (60 seconds)| |millisecond (msec)|60000 (60 seconds)| |second (sec)|86400 (1 day)| |minute (min)|1440 (1 day)| |hour|24 (1 day)| |day|1| |week|1| |month|1| |year|1| 예를 들어, DATE_TRUNC('second', time, 120)을 입력하면 반환되는 값은 **2분마다** 표시되며, 이는 DATE_TRUNC('minute', time, 2)와 동일합니다. ## DATE_BIN 지정한 기준 시각(`origin`)을 기준으로 DATETIME 값을 `time unit`과 `time range`로 구간(bin) 처리합니다. ```sql DATE_BIN(field, count, source [, origin]) ``` - `origin`을 지정하면 해당 시각을 기준으로 버킷을 계산합니다. - `origin`을 생략하면 서버 로컬 타임존의 `1970-01-01 00:00:00`을 기준으로 버킷을 계산합니다. - `count`는 1 이상의 정수여야 합니다. `DATE_TRUNC()` 또는 `ROLLUP()`과 같은 로컬 타임존 경계로 버킷을 맞추고 싶으면 `origin`을 생략한 3-인자 형식을 사용하면 됩니다. 반대로 서버 타임존과 무관하게 항상 동일한 경계를 사용해야 하면 4-인자 형식으로 `origin`을 명시해야 합니다. 예를 들어, 서버 타임존이 `UTC+09:00`일 때 과거에는 `DATE_BIN(..., 0)` 대신 타임존 보정이 적용된 `origin` 값을 직접 넣어야 로컬 시간 경계에 맞출 수 있었지만, 이제는 `DATE_BIN(field, count, source)`만으로 같은 효과를 얻을 수 있습니다. ```sql Mach> CREATE LOG TABLE log (time DATETIME); Created successfully. Mach> INSERT INTO log VALUES (TO_DATE('2000-01-01 00:00:00')); 1 row(s) inserted. Mach> INSERT INTO log VALUES (TO_DATE('2000-01-01 01:00:00')); 1 row(s) inserted. Mach> INSERT INTO log VALUES (TO_DATE('2000-01-01 02:00:00')); 1 row(s) inserted. Mach> INSERT INTO log VALUES (TO_DATE('2000-01-01 03:00:00')); 1 row(s) inserted. Mach> INSERT INTO log VALUES (TO_DATE('2000-01-01 04:00:00')); 1 row(s) inserted. Mach> SELECT TIME, DATE_BIN('hour', 2, time, TO_DATE('2020-01-01 00:00:00')) FROM log ORDER BY time; TIME DATE_BIN('hour', 2, time, TO_DATE('2020-01-01 00:00:00')) --------------------------------------------------------------------------------------------- 2000-01-01 00:00:00 000:000:000 2000-01-01 00:00:00 000:000:000 2000-01-01 01:00:00 000:000:000 2000-01-01 00:00:00 000:000:000 2000-01-01 02:00:00 000:000:000 2000-01-01 02:00:00 000:000:000 2000-01-01 03:00:00 000:000:000 2000-01-01 02:00:00 000:000:000 2000-01-01 04:00:00 000:000:000 2000-01-01 04:00:00 000:000:000 [5] row(s) selected. ``` 로컬 타임존 경계를 기준으로 버킷을 계산하는 예는 다음과 같습니다. ```sql Mach> CREATE LOG TABLE t3521 (ts DATETIME); Created successfully. Mach> INSERT INTO t3521 VALUES (TO_DATE('2000-01-01 00:30:00')); 1 row(s) inserted. Mach> INSERT INTO t3521 VALUES (TO_DATE('2000-01-01 02:59:59')); 1 row(s) inserted. Mach> INSERT INTO t3521 VALUES (TO_DATE('2000-01-01 03:00:00')); 1 row(s) inserted. Mach> INSERT INTO t3521 VALUES (TO_DATE('2000-01-01 08:00:00')); 1 row(s) inserted. Mach> SELECT ts, DATE_BIN('hour', 3, ts) AS date_bin_3arg, DATE_TRUNC('hour', ts, 3) AS date_trunc_3arg FROM t3521 ORDER BY ts; ts date_bin_3arg date_trunc_3arg ---------------------------------------------------------------------------------------------------- 2000-01-01 00:30:00 000:000:000 2000-01-01 00:00:00 000:000:000 2000-01-01 00:00:00 000:000:000 2000-01-01 02:59:59 000:000:000 2000-01-01 00:00:00 000:000:000 2000-01-01 00:00:00 000:000:000 2000-01-01 03:00:00 000:000:000 2000-01-01 03:00:00 000:000:000 2000-01-01 03:00:00 000:000:000 2000-01-01 08:00:00 000:000:000 2000-01-01 06:00:00 000:000:000 2000-01-01 06:00:00 000:000:000 [4] row(s) selected. ``` 시간 단위별 허용 범위는 다음과 같습니다. * nanosecond, microsecond, millisecond 단위 및 약어는 5.5.6부터 사용할 수 있습니다. * week는 7일과 같습니다. |시간 단위| |----:| |nanosecond (nsec)| |microsecond (usec)| |millisecond (msec)| |second (sec)| |minute (min)| |hour| |day| |week| |month| |year| ## DAYOFWEEK DATETIME 값의 요일을 정수로 반환합니다. [TO_CHAR (time, 'DAY')](#to_char)와 의미적으로 동일하지만, 여기서는 정수를 반환합니다. ```sql DAYOFWEEK(date_val) ``` 반환되는 자연수는 아래 표와 같이 요일을 나타냅니다. |반환값|요일| |--|--| |0|일요일| |1|월요일| |2|화요일| |3|수요일| |4|목요일| |5|금요일| |6|토요일| ## DECODE 컬럼 값을 search 값들과 비교하여 일치하면 대응하는 return 값을 반환합니다. 일치하는 search 값이 없으면 default 값을, default를 생략하면 NULL을 반환합니다. ```sql DECODE(column, [search, return],.. default) ``` ```sql Mach> CREATE LOG TABLE decode_table (id1 VARCHAR(11)); Created successfully. Mach> INSERT INTO decode_table VALUES('decodetest1'); 1 row(s) inserted. Mach> INSERT INTO decode_table VALUES('decodetest2'); 1 row(s) inserted. Mach> SELECT id1, DECODE(id1, 'decodetest1', 'result1', 'decodetest2', 'result2', 'DEFAULT') FROM decode_table; id1 DECODE(id1, 'decodetest1', 'result1', 'decodetest2', 'result2', 'DEFAULT') --------------------------------------------------------- decodetest2 result2 decodetest1 result1 [2] row(s) selected. Mach> SELECT id1, DECODE(id1, 'codetest', 2, 99) FROM decode_table; id1 DECODE(id1, 'codetest', 2, 99) ----------------------------------------------- decodetest2 99 decodetest1 99 [2] row(s) selected. Mach> SELECT DECODE(id1, 'decodetest1', 2) FROM decode_table; DECODE(id1, 'decodetest1', 2) -------------------------------- NULL 2 [2] row(s) selected. Mach> SELECT DECODE(id1, 'codetest', 2) FROM decode_table; DECODE(id1, 'codetest', 2) ----------------------------- NULL NULL [2] row(s) selected. ``` ## EXTRACT_* 바이너리 프레임에서 비트를 추출하는 함수 모음입니다. `EXTRACT_*`는 빅엔디안, `EXTRACT_LE_*`는 리틀엔디안 모델을 사용합니다. 모든 함수는 `BINARY/VARBINARY`를 입력으로 받으며 frame이 NULL이면 결과도 NULL입니다. **엔디안 모델** - `EXTRACT_*`: MSB 우선 (`bit 0`은 `byte[0]`의 MSB) - `EXTRACT_LE_*`: LSB 우선 (`bit 0`은 `byte[0]`의 LSB) - 비트 인덱스는 프레임 전체 기준으로 0부터 시작합니다. **공통 규칙** - 단일 비트: `0 <= bit_pos < frame_bits` - 범위 추출: `start_bit >= 0`, `1 <= bit_count <= 64`, `start_bit + bit_count <= frame_bits` - `EXTRACT_FLOAT*`는 32비트, `EXTRACT_DOUBLE*`는 64비트를 읽습니다. - signed 추출은 2의 보수(two's complement)로 해석하고 64비트로 부호 확장합니다. - 범위 오류: `ERR_QP_INVALID_ARG_VALUE` (`ERR-02229` 계열) - 인자 타입 오류: `ERR_QP_FUNCTION_ARG_TYPE` ### EXTRACT_BIT ``` EXTRACT_BIT(frame, bit_pos) / EXTRACT_LE_BIT(frame, bit_pos) → TINYINT ``` 단일 비트를 0 또는 1로 반환합니다. ```sql -- frame = 0x80 (1000 0000) SELECT EXTRACT_BIT(frame, 0) AS be_bit0, EXTRACT_LE_BIT(frame, 0) AS le_bit0 FROM t; ``` ### EXTRACT_LONG, EXTRACT_ULONG ``` EXTRACT_ULONG(frame, start_bit, bit_count) → BIGINT UNSIGNED EXTRACT_LE_ULONG(frame, start_bit, bit_count) → BIGINT UNSIGNED EXTRACT_LONG(frame, start_bit, bit_count) → BIGINT EXTRACT_LE_LONG(frame, start_bit, bit_count) → BIGINT ``` 1~64비트를 부호 없는/2의 보수 정수로 읽습니다. ```sql -- frame = 0x12 34 SELECT EXTRACT_ULONG(frame, 0, 16) AS be_u16, -- 0x1234 EXTRACT_LE_ULONG(frame, 0, 16) AS le_u16 -- 0x3412 FROM t; ``` ### EXTRACT_FLOAT,EXTRACT_DOUBLE ``` EXTRACT_FLOAT(frame, start_bit) → FLOAT EXTRACT_LE_FLOAT(frame, start_bit) → FLOAT EXTRACT_DOUBLE(frame, start_bit) → DOUBLE EXTRACT_LE_DOUBLE(frame, start_bit) → DOUBLE ``` 32/64비트를 IEEE754 float/double로 재해석하며, 지정한 비트 구간이 frame 안에 들어와야 합니다. ```sql SELECT EXTRACT_FLOAT(frame, 0) AS be_f32, EXTRACT_LE_FLOAT(frame, 0) AS le_f32, EXTRACT_DOUBLE(frame, 64) AS be_f64, EXTRACT_LE_DOUBLE(frame, 64) AS le_f64 FROM sensor_bin; ``` ### EXTRACT_SCALED_DOUBLE ``` EXTRACT_SCALED_DOUBLE(frame, start_bit, bit_count, signed, scale, offset) → DOUBLE EXTRACT_LE_SCALED_DOUBLE(frame, start_bit, bit_count, signed, scale, offset) → DOUBLE ``` 1~64비트를 `signed=0`이면 부호 없는 값, `signed=1`이면 2의 보수 signed 값으로 읽고 `raw * scale + offset`을 반환합니다. ```sql -- 20비트 센서값, scale 0.01, offset -40.0 SELECT EXTRACT_SCALED_DOUBLE(frame, 0, 20, 0, 0.01, -40.0) AS be_value, EXTRACT_LE_SCALED_DOUBLE(frame, 0, 20, 0, 0.01, -40.0) AS le_value FROM t_bin; ``` ## FIRST / LAST 각 그룹에서 '기준 값'으로 정렬한 순서 기준으로 가장 앞(또는 마지막) 레코드의 특정 값을 반환하는 집계 함수입니다. * FIRST: 정렬 순서에서 가장 앞 레코드의 값을 반환합니다. * LAST: 정렬 순서에서 마지막 레코드의 값을 반환합니다. ```sql FIRST(sort_expr, return_expr) LAST(sort_expr, return_expr) ``` ```sql Mach> create table firstlast_table (id integer, name varchar(20), group_no integer); Created successfully. Mach> insert into firstlast_table values (1, 'John', 0); 1 row(s) inserted. Mach> insert into firstlast_table values (2, 'Grey', 1); 1 row(s) inserted. Mach> insert into firstlast_table values (5, 'Ryan', 0); 1 row(s) inserted. Mach> insert into firstlast_table values (4, 'Andrew', 0); 1 row(s) inserted. Mach> insert into firstlast_table values (7, 'Kyle', 1); 1 row(s) inserted. Mach> insert into firstlast_table values (6, 'Ross', 1); 1 row(s) inserted. Mach> select group_no, first(id, name) from firstlast_table group by group_no; group_no first(id, name) ------------------------------------- 1 Grey 0 John [2] row(s) selected. Mach> select group_no, last(id, name) from firstlast_table group by group_no; group_no last(id, name) ------------------------------------- 1 Kyle 0 Ryan ``` ## FROM_TIMESTAMP UTC 기준 1970-01-01 00:00:00부터 경과한 나노초 값을 datetime 타입으로 변환합니다. (TO_TIMESTAMP()는 datetime 타입을 같은 기준 시점부터 경과한 나노초 값으로 변환합니다.) 기준 시점은 UTC+09:00에서 1970-01-01 09:00:00으로 표시됩니다. 아래 예제의 날짜와 시각은 UTC+09:00 기준입니다. ```sql FROM_TIMESTAMP(nanosecond_time_value) ``` ```sql Mach> SELECT FROM_TIMESTAMP(1562302560007248869); FROM_TIMESTAMP(1562302560007248869) -------------------------------------- 2019-07-05 13:56:00 007:248:869 ``` `SYSDATE`와 `NOW`는 현재 시각을 나타내는 DATETIME 값입니다. 아래 예제는 현재 시각을 그대로 변환하는 경우와 1밀리초(1,000,000나노초)를 빼는 경우를 보여줍니다. ```sql Mach> select sysdate, from_timestamp(sysdate) from test_tbl; sysdate from_timestamp(sysdate) ------------------------------------------------------------------- 2019-07-05 14:00:59 722:822:443 2019-07-05 14:00:59 722:822:443 [1] row(s) selected. Mach> select sysdate, from_timestamp(sysdate-1000000) from test_tbl; sysdate from_timestamp(sysdate-1000000) ------------------------------------------------------------------- 2019-07-05 14:01:05 130:939:525 2019-07-05 14:01:05 129:939:525 -- 1 ms (1,000,000 ns) 차이가 발생함 [1] row(s) selected. ``` ## FROM_UNIXTIME 정수로 입력된 32비트 UNIXTIME 값을 datetime 타입으로 변환합니다. (UNIX_TIMESTAMP는 datetime 데이터를 32비트 UNIXTIME 정수로 변환합니다.) 아래 예제의 날짜와 시각은 UTC+09:00 기준입니다. ```sql FROM_UNIXTIME(unix_timestamp_value) ``` ```sql Mach> SELECT FROM_UNIXTIME(315540671) FROM TEST; FROM_UNIXTIME(315540671) ---------------------------------- 1980-01-01 11:11:11 000:000:000 Mach> SELECT FROM_UNIXTIME(UNIX_TIMESTAMP('2001-01-01')) FROM unix_table; FROM_UNIXTIME(UNIX_TIMESTAMP('2001-01-01')) ------------------------------------------ 2001-01-01 00:00:00 000:000:000 ``` ## GROUP_CONCAT 그룹 내 컬럼 값들을 문자열로 이어 붙여 반환하는 집계 함수입니다. {{< callout type="warning" >}} Cluster Edition에서는 사용할 수 없습니다. {{< /callout >}} ```sql GROUP_CONCAT( [DISTINCT] column [ORDER BY { unsigned_integer | column } [ASC | DESC] [, column ...]] [SEPARATOR str_val] ) ``` * DISTINCT: 중복 값은 한 번만 연결합니다. * ORDER BY: 지정한 컬럼 값을 기준으로 연결 순서를 정렬합니다. * SEPARATOR: 컬럼 값을 연결할 때 사용할 구분자 문자열입니다. 기본값은 쉼표(,)입니다. 구문 관련 주의사항은 다음과 같습니다. * 하나의 컬럼만 지정할 수 있으며, 여러 컬럼을 붙이려면 TO_CHAR()와 CONCAT 연산자(||)로 하나의 표현식으로 만들어야 합니다. * ORDER BY에는 연결 대상 컬럼 외의 컬럼도 지정할 수 있으며, 여러 컬럼을 지정할 수 있습니다. * SEPARATOR에는 문자열 상수만 지정할 수 있으며, 문자열 컬럼은 지정할 수 없습니다. ```sql Mach> CREATE LOG TABLE concat_table(id1 INTEGER, id2 DOUBLE, name VARCHAR(10)); Created successfully. Mach> INSERT INTO concat_table VALUES (1, 2, 'John'); 1 row(s) inserted. Mach> INSERT INTO concat_table VALUES (2, 1, 'Ram'); 1 row(s) inserted. Mach> INSERT INTO concat_table VALUES (3, 2, 'Zara'); 1 row(s) inserted. Mach> INSERT INTO concat_table VALUES (4, 2, 'Jill'); 1 row(s) inserted. Mach> INSERT INTO concat_table VALUES (5, 1, 'Jack'); 1 row(s) inserted. Mach> INSERT INTO concat_table VALUES (6, 1, 'Jack'); 1 row(s) inserted. Mach> SELECT GROUP_CONCAT(name) AS G_NAMES FROM concat_table GROUP BY id2; G_NAMES ------------------------------------------------------------------------------------ Jack,Jack,Ram Jill,Zara,John [2] row(s) selected. Mach> SELECT GROUP_CONCAT(DISTINCT name) AS G_NAMES FROM concat_table GROUP BY Id2; G_NAMES ------------------------------------------------------------------------------------ Jack,Ram Jill,Zara,John [2] row(s) selected. Mach> SELECT GROUP_CONCAT(name SEPARATOR '.') G_NAMES FROM concat_table GROUP BY Id2; G_NAMES ------------------------------------------------------------------------------------ Jack.Jack.Ram Jill.Zara.John [2] row(s) selected. Mach> SELECT GROUP_CONCAT(name ORDER BY id1) G_NAMES, GROUP_CONCAT(id1 ORDER BY id1) G_SORTID FROM concat_table GROUP BY id2; G_NAMES ------------------------------------------------------------------------------------ G_SORTID ------------------------------------------------------------------------------------ Ram,Jack,Jack 2,5,6 John,Zara,Jill 1,3,4 [2] row(s) selected. ``` ## INSTR 대상 문자열에서 패턴 문자열이 시작하는 위치를 반환합니다. 위치는 1부터 시작합니다. * 패턴이 없으면 0을 반환합니다. * 찾을 패턴의 길이가 0이거나 NULL이면 NULL을 반환합니다. ```sql INSTR(target_string, pattern_string) ``` ```sql Mach> CREATE LOG TABLE string_table(c1 VARCHAR(20)); Created successfully. Mach> INSERT INTO string_table VALUES ('abstract'); 1 row(s) inserted. Mach> INSERT INTO string_table VALUES ('override'); 1 row(s) inserted. Mach> SELECT c1, INSTR(c1, 'act') FROM string_table; c1 INSTR(c1, 'act') ------------------------------------------ override 0 abstract 6 [2] row(s) selected. ``` ## LEAST / GREATEST 여러 컬럼/값을 입력하면 LEAST는 최소값, GREATEST는 최대값을 반환합니다. 입력 값이 1개이거나 없으면 오류로 처리됩니다. 입력 값이 NULL이면 NULL을 반환합니다. 따라서 입력이 컬럼인 경우 함수로 미리 변환해야 합니다. 비교할 수 없는 컬럼(BLOB, TEXT 등)이 포함되거나 비교를 위한 타입 변환이 불가능하면 오류로 처리됩니다. ```sql LEAST(value_list, value_list,...) GREATEST(value_list, value_list,...) ``` ```sql Mach> CREATE LOG TABLE lgtest_table(c1 INTEGER, c2 LONG, c3 VARCHAR(10), c4 VARCHAR(5)); Created successfully. Mach> INSERT INTO lgtest_table VALUES (1, 2, 'abstract', 'ace'); 1 row(s) inserted. Mach> INSERT INTO lgtest_table VALUES (null, 100, null, 'bag'); 1 row(s) inserted. Mach> SELECT LEAST (c1, c2) FROM lgtest_table; LEAST (c1, c2) ----------------------- NULL 1 [2] row(s) selected. Mach> SELECT LEAST (c1, c2, -1) FROM lgtest_table; LEAST (c1, c2, -1) ----------------------- NULL -1 [2] row(s) selected. Mach> SELECT GREATEST(c3, c4) FROM lgtest_table; GREATEST(c3, c4) -------------------- NULL ace [2] row(s) selected. Mach> SELECT LEAST(c3, c4) FROM lgtest_table; LEAST(c3, c4) ----------------- NULL abstract [2] row(s) selected. Mach> SELECT LEAST(NVL(c3, 'aa'), c4) FROM lgtest_table; LEAST(NVL(c3, 'aa'), c4) ---------------------------- aa abstract [2] row(s) selected. ``` ## LENGTH 문자열 컬럼의 길이를 반환합니다. 반환 값은 영문(ASCII) 기준 바이트 수입니다. ```sql LENGTH(column_name) ``` ```sql Mach> CREATE LOG TABLE length_table (id1 INTEGER, id2 DOUBLE, name VARCHAR(15)); Created successfully. Mach> INSERT INTO length_table VALUES(1, 10, 'Around the Horn'); 1 row(s) inserted. Mach> INSERT INTO length_table VALUES(NULL, 20, 'Alfreds Futterkiste'); 1 row(s) inserted. Mach> INSERT INTO length_table VALUES(3, NULL, 'Antonio Moreno'); 1 row(s) inserted. Mach> INSERT INTO length_table VALUES(4, 40, NULL); 1 row(s) inserted. Mach> select * FROM length_table; ID1 ID2 NAME ------------------------------------------------------------- 4 40 NULL 3 NULL Antonio Moreno NULL 20 Alfreds Futterk 1 10 Around the Horn [4] row(s) selected. Mach> select id1 * 10 FROM length_table; id1 * 10 ----------------------- 40 30 NULL 10 [4] row(s) selected. Mach> select * FROM length_table Where id1 > 1 and id2 < 50; ID1 ID2 NAME ------------------------------------------------------------- 4 40 NULL [1] row(s) selected. Mach> select name || ' with null concat' FROM length_table; name || ' with null concat' ------------------------------------ NULL Antonio Moreno with null concat Alfreds Futterk with null concat Around the Horn with null concat [4] row(s) selected. Mach> select LENGTH(name) FROM length_table; LENGTH(name) --------------- NULL 14 15 15 [4] row(s) selected. ``` ## LOWER 영문 문자열을 소문자로 변환합니다. ```sql LOWER(column_name) ``` ```sql Mach> CREATE LOG TABLE lower_table (name VARCHAR(20)); Created successfully. Mach> INSERT INTO lower_table VALUES(''); 1 row(s) inserted. Mach> INSERT INTO lower_table VALUES('James Backley'); 1 row(s) inserted. Mach> INSERT INTO lower_table VALUES('Alfreds Futterkiste'); 1 row(s) inserted. Mach> INSERT INTO lower_table VALUES('Antonio MORENO'); 1 row(s) inserted. Mach> INSERT INTO lower_table VALUES (NULL); 1 row(s) inserted. Mach> SELECT LOWER(name) FROM lower_table; LOWER(name) ------------------------ NULL antonio moreno alfreds futterkiste james backley NULL [5] row(s) selected. ``` ## LPAD / RPAD 입력 문자열이 지정 길이가 될 때까지 왼쪽(LPAD) 또는 오른쪽(RPAD)에 문자를 채웁니다. 마지막 파라미터 char는 생략할 수 있으며, 생략 시 공백(' ')으로 채웁니다. 입력 값이 지정 길이보다 길면 문자를 덧붙이지 않고 앞에서부터 지정 길이만큼만 반환합니다. ```sql LPAD(str, len, padstr) RPAD(str, len, padstr) ``` ```sql Mach> CREATE LOG TABLE pad_table (c1 integer, c2 varchar(15)); Created successfully. Mach> INSERT INTO pad_table VALUES (1, 'Antonio'); 1 row(s) inserted. Mach> INSERT INTO pad_table VALUES (25, 'Johnathan'); 1 row(s) inserted. Mach> INSERT INTO pad_table VALUES (30, 'M'); 1 row(s) inserted. Mach> SELECT LPAD(to_char(c1), 5, '0') FROM pad_table; LPAD(to_char(c1), 5, '0') ----------------------------- 00030 00025 00001 [3] row(s) selected. Mach> SELECT RPAD(to_char(c1), 5, '0') FROM pad_table; RPAD(to_char(c1), 5, '0') ----------------------------- 30000 25000 10000 [3] row(s) selected. Mach> SELECT LPAD(c2, 5) FROM pad_table; LPAD(c2, 5) --------------- M Johna Anton [3] row(s) selected. Mach> SELECT RPAD(c2, 5) FROM pad_table; RPAD(c2, 5) --------------- M Johna Anton [3] row(s) selected. Mach> SELECT RPAD(c2, 10, '***') FROM pad_table; RPAD(c2, 10, '***') ----------------------- M********* Johnathan* Antonio*** [3] row(s) selected. ``` ## LTRIM / RTRIM 첫 번째 인자에서 패턴 문자열에 포함된 문자를 제거합니다. LTRIM은 왼쪽부터, RTRIM은 오른쪽부터 패턴에 포함된 문자를 검사하며, 패턴에 없는 문자를 만나면 멈춥니다. 모든 문자가 패턴에 포함되어 있으면 NULL을 반환합니다. 패턴을 지정하지 않으면 공백(' ')을 기준으로 공백을 제거합니다. ```sql LTRIM(column_name, pattern) RTRIM(column_name, pattern) ``` ```sql Mach> CREATE LOG TABLE trim_table1(name VARCHAR(10)); Created successfully. Mach> INSERT INTO trim_table1 VALUES (' smith '); 1 row(s) inserted. Mach> SELECT ltrim(name) FROM trim_table1; ltrim(name) --------------- smith [1] row(s) selected. Mach> SELECT rtrim(name) FROM trim_table1; rtrim(name) --------------- smith [1] row(s) selected. Mach> SELECT ltrim(name, ' s') FROM trim_table1; ltrim(name, ' s') --------------------- mith [1] row(s) selected. Mach> SELECT rtrim(name, 'h ') FROM trim_table1; rtrim(name, 'h ') --------------------- smit [1] row(s) selected. Mach> CREATE LOG TABLE trim_table2 (name VARCHAR(10)); Created successfully. Mach> INSERT INTO trim_table2 VALUES ('ddckaaadkk'); 1 row(s) inserted. Mach> SELECT ltrim(name, 'dc') FROM trim_table2; ltrim(name, 'dc') --------------------- kaaadkk [1] row(s) selected. Mach> SELECT rtrim(name, 'dk') FROM trim_table2; rtrim(name, 'dk') --------------------- ddckaaa [1] row(s) selected. Mach> SELECT ltrim(name, 'dckak') FROM trim_table2; ltrim(name, 'dckak') ------------------------ NULL [1] row(s) selected. Mach> SELECT rtrim(name, 'dckak') FROM trim_table2; rtrim(name, 'dckak') ------------------------ NULL [1] row(s) selected. ``` ## MAX 지정한 숫자 컬럼의 최대값을 반환하는 집계 함수입니다. ```sql MAX(column_name) ``` ```sql Mach> CREATE LOG TABLE max_table (c INTEGER); Created successfully. Mach> INSERT INTO max_table VALUES(10); 1 row(s) inserted. Mach> INSERT INTO max_table VALUES(20); 1 row(s) inserted. Mach> INSERT INTO max_table VALUES(30); 1 row(s) inserted. Mach> SELECT MAX(c) FROM max_table; MAX(c) -------------- 30 [1] row(s) selected. ``` ## MEDIAN {#median} `MEDIAN(value)`는 숫자식의 정확한 중앙값을 반환하며 `PERCENTILE_CONT(value, 0.5)`와 같은 방식으로 동작합니다. ```sql MEDIAN(value) ``` - `value`는 숫자형이어야 합니다. - `NULL` 값은 무시합니다. - 반환 타입은 `DOUBLE`입니다. ```sql SELECT MEDIAN(temp_c) FROM sensor_log; ``` ## MIN 지정한 숫자 컬럼의 최소값을 반환하는 집계 함수입니다. ```sql MIN(column_name) ``` ```sql Mach> CREATE LOG TABLE min_table(c1 INTEGER); Created successfully. Mach> INSERT INTO min_table VALUES(1); 1 row(s) inserted. Mach> INSERT INTO min_table VALUES(22); 1 row(s) inserted. Mach> INSERT INTO min_table VALUES(33); 1 row(s) inserted. Mach> SELECT MIN(c1) FROM min_table; MIN(c1) -------------- 1 [1] row(s) selected. ``` ## NVL 컬럼 값이 NULL이면 지정한 값으로 대체하고, NULL이 아니면 원래 값을 반환합니다. ```sql NVL(string1, replace_with) ``` ```sql Mach> CREATE LOG TABLE nvl_table (c1 varchar(10)); Created successfully. Mach> INSERT INTO nvl_table VALUES ('Johnathan'); 1 row(s) inserted. Mach> INSERT INTO nvl_table VALUES (NULL); 1 row(s) inserted. Mach> SELECT NVL(c1, 'Thomas') FROM nvl_table; NVL(c1, 'Thomas') --------------------- Thomas Johnathan ``` ## NEXTVAL `NEXTVAL(sequence_column)`은 Lookup 테이블의 Sequence 컬럼에 대해 다음 값을 반환합니다. ```sql NEXTVAL(sequence_column) ``` - `NEXTVAL`은 `INSERT` 문에서만 사용할 수 있습니다. - 인자는 `PROPERTY(SEQUENCE=...)`로 설정된 컬럼이어야 합니다. - Sequence 컬럼 생성과 예제는 [Sequence Column](/dbms/lookup-table-usage/sequence-column/)을 참고하십시오. ```sql INSERT INTO seq_lookup (id, name) VALUES (NEXTVAL(id), 'sensor-a'); ``` ## ROUND 입력 값의 지정한 자릿수(입력 자릿수 + 1)를 반올림한 결과를 반환합니다. 자릿수를 생략하면 소수점 0자리에서 반올림합니다. 음수를 지정해 정수부 자리에서 반올림할 수 있습니다. ```sql ROUND(column_name, [decimals]) ``` ```sql Mach> CREATE LOG TABLE round_table (c1 DOUBLE); Created successfully. Mach> INSERT INTO round_table VALUES (1.994); 1 row(s) inserted. Mach> INSERT INTO round_table VALUES (1.995); 1 row(s) inserted. Mach> SELECT c1, ROUND(c1, 2) FROM round_table; c1 ROUND(c1, 2) ----------------------------------------------------------- 1.995 2 1.994 1.99 ``` ## ROWNUM SELECT 결과 행에 번호를 부여합니다. SELECT에서 사용하는 서브쿼리나 인라인 뷰 내부에서 사용할 수 있습니다. 인라인 뷰의 Target List에서 ROWNUM()을 사용한 경우 외부에서 참조할 수 있도록 Alias를 지정해야 합니다. ```sql ROWNUM() ``` **사용 가능한 절** SELECT Target List, GROUP BY, ORDER BY 절에서 사용할 수 있습니다. WHERE와 HAVING 절에서는 사용할 수 없습니다. 결과 번호로 WHERE/HAVING을 제어하려면 인라인 뷰에서 ROWNUM()을 계산한 뒤 외부 쿼리에서 참조합니다. |사용 가능 절|사용 불가 절| |--|--| |Target List / GROUP BY / ORDER BY|WHERE / HAVING| ```sql Mach> CREATE LOG TABLE rownum_table(c1 INTEGER, c2 DOUBLE, c3 VARCHAR(10)); Created successfully. Mach> INSERT INTO rownum_table VALUES(1, 1.0, ''); 1 row(s) inserted. Mach> INSERT INTO rownum_table VALUES(2, 2.0, 'Second Row'); 1 row(s) inserted. Mach> INSERT INTO rownum_table VALUES(3, 3.3, 'Third Row'); 1 row(s) inserted. Mach> INSERT INTO rownum_table VALUES(4, 4.3, 'Fourth Row'); 1 row(s) inserted. Mach> SELECT INNER_RANK, c3 AS NAME 2 FROM (SELECT ROWNUM() AS INNER_RANK, * FROM rownum_table) 3 WHERE INNER_RANK < 3; INNER_RANK NAME ------------------------------------ 1 Fourth Row 2 Third Row [2] row(s) selected. ``` **정렬로 인한 결과 번호 변화** SELECT에 ORDER BY 절이 있으면 Target List의 ROWNUM() 결과가 순차적으로 부여되지 않을 수 있습니다. 이는 ROWNUM()이 ORDER BY보다 먼저 처리되기 때문입니다. 순차 번호가 필요하면 ORDER BY를 포함한 쿼리를 인라인 뷰로 만든 뒤, 외부 SELECT에서 ROWNUM()을 호출하십시오. ```sql Mach> CREATE LOG TABLE rownum_table(c1 INTEGER, c2 DOUBLE, c3 VARCHAR(10)); Created successfully. Mach> INSERT INTO rownum_table VALUES(1, 1.0, ''); 1 row(s) inserted. Mach> INSERT INTO rownum_table VALUES(2, 2.0, 'John'); 1 row(s) inserted. Mach> INSERT INTO rownum_table VALUES(3, 3.3, 'Sarah'); 1 row(s) inserted. Mach> INSERT INTO rownum_table VALUES(4, 4.3, 'Micheal'); 1 row(s) inserted. Mach> SELECT ROWNUM(), c2 AS SORT, c3 AS NAME 2 FROM ( SELECT * FROM rownum_table ORDER BY c3 ); ROWNUM() SORT NAME ----------------------------------------------------------------- 1 1 NULL 2 2 John 3 4.3 Micheal 4 3.3 Sarah [4] row(s) selected. ``` ## SERIESNUM `SERIES BY`로 구분한 연속 구간 중 각 행이 속한 구간의 번호를 반환합니다. 같은 구간의 행에는 같은 번호를 부여하므로 구간 안의 행 순번과는 다릅니다. 반환 타입은 BIGINT이며, `SERIES BY` 절을 사용하지 않으면 항상 1을 반환합니다. ```sql SERIESNUM() ``` ```sql Mach> CREATE LOG TABLE T1 (C1 INTEGER, C2 INTEGER); Created successfully. Mach> INSERT INTO T1 VALUES (0, 1); 1 row(s) inserted. Mach> INSERT INTO T1 VALUES (1, 2); 1 row(s) inserted. Mach> INSERT INTO T1 VALUES (2, 3); 1 row(s) inserted. Mach> INSERT INTO T1 VALUES (3, 2); 1 row(s) inserted. Mach> INSERT INTO T1 VALUES (4, 1); 1 row(s) inserted. Mach> INSERT INTO T1 VALUES (5, 2); 1 row(s) inserted. Mach> INSERT INTO T1 VALUES (6, 3); 1 row(s) inserted. Mach> INSERT INTO T1 VALUES (7, 1); 1 row(s) inserted. Mach> SELECT SERIESNUM(), C1, C2 FROM T1 ORDER BY C1 SERIES BY C2 > 1; SERIESNUM() C1 C2 ------------------------------------------------- 1 1 2 1 2 3 1 3 2 2 5 2 2 6 3 [5] row(s) selected. ``` ## STDDEV / STDDEV_POP 입력 컬럼의 표본 표준편차(STDDEV)와 모표준편차(STDDEV_POP)를 반환하는 집계 함수입니다. 각각 VARIANCE, VAR_POP의 제곱근입니다. ```sql STDDEV(column) STDDEV_POP(column) ``` ```sql Mach> CREATE LOG TABLE stddev_table(c1 INTEGER, C2 DOUBLE); Mach> INSERT INTO stddev_table VALUES (1, 1); 1 row(s) inserted. Mach> INSERT INTO stddev_table VALUES (2, 1); 1 row(s) inserted. Mach> INSERT INTO stddev_table VALUES (3, 2); 1 row(s) inserted. Mach> INSERT INTO stddev_table VALUES (4, 2); 1 row(s) inserted. Mach> SELECT c2, STDDEV(c1) FROM stddev_table GROUP BY c2; c2 STDDEV(c1) ----------------------------------------------------------- 1 0.707107 2 0.707107 [2] row(s) selected. Mach> SELECT c2, STDDEV_POP(c1) FROM stddev_table GROUP BY c2; c2 STDDEV_POP(c1) ----------------------------------------------------------- 1 0.5 2 0.5 [2] row(s) selected. ``` ## SUBSTR 문자열 컬럼에서 START 위치부터 SIZE 길이만큼 잘라 반환합니다. * START는 1부터 시작하며 0이면 NULL을 반환합니다. * SIZE가 START 위치부터 남은 문자열 길이보다 크면 START 위치부터 문자열 끝까지 반환합니다. SIZE는 선택 사항이며 생략하면 문자열 길이가 사용됩니다. ```sql SUBSTRING(column_name, start, [length]) ``` ```sql Mach> CREATE LOG TABLE substr_table (c1 VARCHAR(10)); Created successfully. Mach> INSERT INTO substr_table values('ABCDEFG'); 1 row(s) inserted. Mach> INSERT INTO substr_table values('abstract'); 1 row(s) inserted. Mach> SELECT SUBSTR(c1, 1, 1) FROM substr_table; SUBSTR(c1, 1, 1) -------------------- a A [2] row(s) selected. Mach> SELECT SUBSTR(c1, 3, 3) FROM substr_table; SUBSTR(c1, 3, 3) -------------------- str CDE [2] row(s) selected. Mach> SELECT SUBSTR(c1, 2) FROM substr_table; SUBSTR(c1, 2) ----------------- bstract BCDEFG [2] row(s) selected. Mach> drop table substr_table; Dropped successfully. Mach> CREATE LOG TABLE substr_table (c1 VARCHAR(10)); Created successfully. Mach> INSERT INTO substr_table values('ABCDEFG'); 1 row(s) inserted. Mach> SELECT SUBSTR(c1, 1, 1) FROM substr_table; SUBSTR(c1, 1, 1) -------------------- A [1] row(s) selected. Mach> SELECT SUBSTR(c1, 3, 3) FROM substr_table; SUBSTR(c1, 3, 3) -------------------- CDE [1] row(s) selected. Mach> SELECT SUBSTR(c1, 2) FROM substr_table; SUBSTR(c1, 2) ----------------- BCDEFG [1] row(s) selected. ``` ## SUBSTRING_INDEX 입력된 count만큼 구분자(delim)를 찾을 때까지의 부분 문자열을 반환합니다. count가 음수이면 문자열 끝에서부터 구분자를 찾고, 구분자를 찾은 위치부터 문자열 끝까지 반환합니다. count가 0이면 NULL을 반환합니다. count가 0이 아니고 문자열에 구분자가 없으면 입력 문자열 전체를 반환합니다. ```sql SUBSTRING_INDEX(expression, delim, count) ``` ```sql Mach> CREATE LOG TABLE substring_table (url VARCHAR(30)); Created successfully. Mach> INSERT INTO substring_table VALUES('www.machbase.com'); 1 row(s) inserted. Mach> SELECT SUBSTRING_INDEX(url, '.', 1) FROM substring_table; SUBSTRING_INDEX(url, '.', 1) ---------------------------------- www [1] row(s) selected. Mach> SELECT SUBSTRING_INDEX(url, '.', 2) FROM substring_table; SUBSTRING_INDEX(url, '.', 2) ---------------------------------- www.machbase [1] row(s) selected. Mach> SELECT SUBSTRING_INDEX(url, '.', -1) FROM substring_table; SUBSTRING_INDEX(url, '.', -1) ---------------------------------- com [1] row(s) selected. Mach> SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(url, '.', 2), '.', -1) FROM substring_table; SUBSTRING_INDEX(SUBSTRING_INDEX(url, '.', 2), '.', -1) ------------------------------------------- machbase [1] row(s) selected. Mach> SELECT SUBSTRING_INDEX(url, '.', 0) FROM substring_table; SUBSTRING_INDEX(url, '.', 0) ---------------------------------- NULL [1] row(s) selected. ``` ## SUM 숫자 컬럼의 합계를 반환하는 집계 함수입니다. ```sql SUM(column_name) ``` ```sql Mach> CREATE LOG TABLE sum_table (c1 INTEGER, c2 INTEGER); Created successfully. Mach> INSERT INTO sum_table VALUES(1, 1); 1 row(s) inserted. Mach> INSERT INTO sum_table VALUES(1, 2); 1 row(s) inserted. Mach> INSERT INTO sum_table VALUES(1, 3); 1 row(s) inserted. Mach> INSERT INTO sum_table VALUES(2, 1); 1 row(s) inserted. Mach> INSERT INTO sum_table VALUES(2, 2); 1 row(s) inserted. Mach> INSERT INTO sum_table VALUES(2, 3); 1 row(s) inserted. Mach> INSERT INTO sum_table VALUES(3, 4); 1 row(s) inserted. Mach> SELECT c1, SUM(c1) from sum_table group by c1; c1 SUM(c1) ------------------------------------ 2 6 3 3 1 3 [3] row(s) selected. Mach> SELECT c1, SUM(c2) from sum_table group by c1; c1 SUM(c2) ------------------------------------ 2 6 3 4 1 6 [3] row(s) selected. ``` ## SUMSQ SUMSQ는 숫자 값들의 제곱합을 반환합니다. ```sql SUMSQ(value) ``` ```sql Mach> CREATE LOG TABLE sumsq_table (c1 INTEGER, c2 INTEGER); Created successfully. Mach> INSERT INTO sumsq_table VALUES (1, 1); 1 row(s) inserted. Mach> INSERT INTO sumsq_table VALUES (1, 2); 1 row(s) inserted. Mach> INSERT INTO sumsq_table VALUES (1, 3); 1 row(s) inserted. Mach> INSERT INTO sumsq_table VALUES (2, 4); 1 row(s) inserted. Mach> INSERT INTO sumsq_table VALUES (2, 5); 1 row(s) inserted. Mach> SELECT c1, SUMSQ(c2) FROM sumsq_table GROUP BY c1; c1 SUMSQ(c2) ------------------------------------ 2 41 1 14 [2] row(s) selected. ``` ## SYSDATE / NOW SYSDATE는 함수가 아닌 의사 컬럼으로, 시스템 현재 시간을 반환합니다. NOW는 SYSDATE와 동일한 기능이며, 사용자 편의를 위해 제공합니다. ```sql SYSDATE NOW ``` ```sql Mach> SELECT SYSDATE, NOW FROM t1; SYSDATE NOW ------------------------------------------------------------------- 2017-01-16 14:14:53 310:973:000 2017-01-16 14:14:53 310:973:000 ``` ## TO_CHAR 주어진 데이터 타입을 문자열 타입으로 변환합니다. 타입에 따라 format_string을 지정할 수 있지만, 바이너리 타입에는 사용할 수 없습니다. ```sql TO_CHAR(column) ``` **TO_CHAR: 기본 데이터 타입** 기본 데이터 타입은 아래와 같이 문자열 형태로 변환됩니다. ```sql Mach> CREATE LOG TABLE fixed_table (id1 SHORT, id2 INTEGER, id3 LONG, id4 FLOAT, id5 DOUBLE, id6 IPV4, id7 IPV6, id8 VARCHAR (128)); Created successfully. Mach> INSERT INTO fixed_table values(200, 19234, 1234123412, 3.14, 7.8338, '192.168.0.1', '::127.0.0.1', 'log varchar'); 1 row(s) inserted. Mach> SELECT '[ ' || TO_CHAR(id1) || ' ]' FROM fixed_table; '[ ' || TO_CHAR(id1) || ' ]' ------------------------------------------------------------------------------------ [ 200 ] [1] row(s) selected. Mach> SELECT '[ ' || TO_CHAR(id2) || ' ]' FROM fixed_table; '[ ' || TO_CHAR(id2) || ' ]' ------------------------------------------------------------------------------------ [ 19234 ] [1] row(s) selected. Mach> SELECT '[ ' || TO_CHAR(id3) || ' ]' FROM fixed_table; '[ ' || TO_CHAR(id3) || ' ]' ------------------------------------------------------------------------------------ [ 1234123412 ] [1] row(s) selected. Mach> SELECT '[ ' || TO_CHAR(id4) || ' ]' FROM fixed_table; '[ ' || TO_CHAR(id4) || ' ]' ------------------------------------------------------------------------------------ [ 3.140000 ] [1] row(s) selected. Mach> SELECT '[ ' || TO_CHAR(id5) || ' ]' FROM fixed_table; '[ ' || TO_CHAR(id5) || ' ]' ------------------------------------------------------------------------------------ [ 7.833800 ] [1] row(s) selected. Mach> SELECT '[ ' || TO_CHAR(id6) || ' ]' FROM fixed_table; '[ ' || TO_CHAR(id6) || ' ]' ------------------------------------------------------------------------------------ [ 192.168.0.1 ] [1] row(s) selected. Mach> SELECT '[ ' || TO_CHAR(id7) || ' ]' FROM fixed_table; '[ ' || TO_CHAR(id7) || ' ]' ------------------------------------------------------------------------------------ [ 0000:0000:0000:0000:0000:0000:7F00:0001 ] [1] row(s) selected. Mach> SELECT '[ ' || TO_CHAR(id8) || ' ]' FROM fixed_table; '[ ' || TO_CHAR(id8) || ' ]' ------------------------------------------------------------------------------------ [ log varchar ] [1] row(s) selected. ``` **TO_CHAR: 부동소수점 숫자** * 5.5.6 버전부터 지원 float와 double 값을 문자열로 변환합니다. 포맷 표현식은 반복해서 사용할 수 없으며 '[letter][number]' 형태로 입력해야 합니다. |포맷 표현식|설명| |--|--| |F / f|컬럼 값의 소수점 자릿수를 지정합니다. 입력 가능한 최대 값은 30입니다.| |N / n|소수점 자릿수를 지정하고 정수부 3자리마다 콤마(,)를 삽입합니다. 입력 가능한 최대 값은 30입니다.| ```sql Mach> create table float_table (i1 float, i2 double); Created successfully. Mach> insert into float_table values (1.23456789, 1234.5678901234567890); 1 row(s) inserted. Mach> select TO_CHAR(i1, 'f8'), TO_CHAR(i2, 'N9') from float_table; TO_CHAR(i1, 'f8') TO_CHAR(i2, 'N9') -------------------------------------------------------------- 1.23456788 1,234.567890123 [1] row(s) selected. ``` **TO_CHAR: DATETIME 타입** datetime 컬럼 값을 임의의 문자열로 변환하는 함수입니다. 이를 이용해 다양한 문자열을 생성하고 조합할 수 있습니다. format_string을 생략하면 기본값은 "YYYY-MM-DD HH24: MI: SS mmm: uuu: nnn"입니다. |포맷 표현식|설명| |--|--| |YYYY|연도를 4자리 숫자로 변환합니다.| |YY|연도를 2자리 숫자로 변환합니다.| |MM|월을 2자리 숫자로 변환합니다.| |MON|월을 3자리 영문 약어로 변환합니다. (예: JAN, FEB, MAY, ...)| |DD|일을 2자리 숫자로 변환합니다.| |DAY|요일을 3자리 영문 약어로 변환합니다. (예: SUN, MON, ...)| |IW|ISO 8601 규칙에 따라 특정 연도의 주차를 `1~53`으로 변환합니다(요일 고려).
- 한 주의 시작은 월요일입니다.
- 첫 주는 전년도 마지막 주로 간주될 수 있습니다. 마찬가지로 마지막 주는 다음 해의 첫 주로 간주될 수 있습니다.
자세한 내용은 ISO 8601을 참고하십시오.| |WW|요일을 고려하지 않고 특정 연도의 주차를 `1~53`으로 변환합니다.
즉, `1월 1일~1월 7일`은 1로 변환됩니다.| |W|요일을 고려하지 않고 특정 월의 주차를 `1~5`로 변환합니다.
즉, `3월 1일~3월 7일`은 1로 변환됩니다.| |HH|시간을 2자리 숫자로 변환합니다.| |HH12|시간을 `1~12` 범위의 2자리 숫자로 변환합니다.| |HH24|시간을 `00~23` 범위의 2자리 숫자로 변환합니다.| |HH2, HH3, HH6|HH 뒤 숫자 단위로 시간을 절단합니다.

예를 들어 HH6을 사용하면 `0~5`는 0, `6~11`은 6으로 표시합니다.
이 표현은 시간열 통계 계산에 유용합니다.
이 값은 24시간 기준으로 표시됩니다.| |MI|분을 2자리 숫자로 표시합니다.| |MI2, MI5, MI10, MI20, MI30|MI 뒤 숫자 단위로 분을 절단합니다.

예를 들어 MI30은 `0~29`분은 0, `30~59`분은 30으로 표시합니다.
이 표현은 시간열 통계 계산에 유용합니다.| |SS|초를 2자리 숫자로 표시합니다.| |SS2, SS5, SS10, SS20, SS30|SS 뒤 숫자 단위로 초를 절단합니다.

예를 들어 SS30은 `0~29`초는 0, `30~59`초는 30으로 표시합니다.
이 표현은 시간열 통계 계산에 유용합니다.| |AM|시간을 AM/PM으로 표시합니다.| |mmm|밀리초를 3자리 숫자로 표시합니다.

값 범위는 `0~999`입니다.| |uuu|마이크로초를 3자리 숫자로 표시합니다.

값 범위는 `0~999`입니다.| |nnn|나노초를 3자리 숫자로 표시합니다.

값 범위는 `0~999`입니다.| ```sql Mach> CREATE LOG TABLE datetime_table (id integer, dt datetime); Created successfully. Mach> INSERT INTO datetime_table values(1, TO_DATE('1999-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO datetime_table values(2, TO_DATE('2012-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO datetime_table values(3, TO_DATE('2013-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO datetime_table values(4, TO_DATE('2014-12-30 11:22:33 444:555:666')); 1 row(s) inserted. Mach> SELECT id, dt FROM datetime_table WHERE dt > TO_DATE('2000-11-11 1:2:3 4:5:0'); id dt ----------------------------------------------- 4 2014-12-30 11:22:33 444:555:666 3 2013-11-11 01:02:03 004:005:006 2 2012-11-11 01:02:03 004:005:006 [3] row(s) selected. Mach> SELECT id, dt FROM datetime_table WHERE dt > TO_DATE('2013-11-11 1:2:3') and dt < TO_DATE('2014-11-11 1:2:3'); id dt ----------------------------------------------- 3 2013-11-11 01:02:03 004:005:006 [1] row(s) selected. Mach> SELECT id, TO_CHAR(dt) FROM datetime_table; id TO_CHAR(dt) ------------------------------------------------------------------------------------------------- 4 2014-12-30 11:22:33 444:555:666 3 2013-11-11 01:02:03 004:005:006 2 2012-11-11 01:02:03 004:005:006 1 1999-11-11 01:02:03 004:005:006 [4] row(s) selected. Mach> SELECT id, TO_CHAR(dt, 'YYYY') FROM datetime_table; id TO_CHAR(dt, 'YYYY') ------------------------------------------------------------------------------------------------- 4 2014 3 2013 2 2012 1 1999 [4] row(s) selected. Mach> SELECT id, TO_CHAR(dt, 'YYYY-MM') FROM datetime_table; id TO_CHAR(dt, 'YYYY-MM') ------------------------------------------------------------------------------------------------- 4 2014-12 3 2013-11 2 2012-11 1 1999-11 [4] row(s) selected. Mach> SELECT id, TO_CHAR(dt, 'YYYY-MM-DD') FROM datetime_table; id TO_CHAR(dt, 'YYYY-MM-DD') ------------------------------------------------------------------------------------------------- 4 2014-12-30 3 2013-11-11 2 2012-11-11 1 1999-11-11 [4] row(s) selected. Mach> SELECT id, TO_CHAR(dt, 'YYYY-MM-DD TO_CHAR') FROM datetime_table; id TO_CHAR(dt, 'YYYY-MM-DD TO_CHAR') ------------------------------------------------------------------------------------------------- 4 2014-12-30 TO_CHAR 3 2013-11-11 TO_CHAR 2 2012-11-11 TO_CHAR 1 1999-11-11 TO_CHAR [4] row(s) selected. Mach> SELECT id, TO_CHAR(dt, 'YYYY-MM-DD HH24:MI:SS') FROM datetime_table; id TO_CHAR(dt, 'YYYY-MM-DD HH24:MI:SS') ------------------------------------------------------------------------------------------------- 4 2014-12-30 11:22:33 3 2013-11-11 01:02:03 2 2012-11-11 01:02:03 1 1999-11-11 01:02:03 [4] row(s) selected. Mach> SELECT id, TO_CHAR(dt, 'YYYY-MM-DD HH24:MI:SS mmm.uuu.nnn') FROM datetime_table; id TO_CHAR(dt, 'YYYY-MM-DD HH24:MI:SS mmm. ------------------------------------------------------------------------------------------------- 4 2014-12-30 11:22:33 444.555.666 3 2013-11-11 01:02:03 004.005.006 2 2012-11-11 01:02:03 004.005.006 1 1999-11-11 01:02:03 004.005.006 [4] row(s) selected. ``` **TO_CHAR: 지원하지 않는 타입** 현재 TO_CHAR는 바이너리 타입을 지원하지 않습니다. 일반 문자열로 변환할 수 없기 때문입니다. 화면에 출력하려면 TO_HEX() 함수로 16진 값을 출력해 확인할 수 있습니다. ## TO_DATE 지정한 포맷 문자열에 따라 문자열을 datetime 타입으로 변환합니다. format_string을 생략하면 기본값은 "YYYY-MM-DD HH24: MI: SS mmm: uuu: nnn"입니다. ```sql -- default format is "YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn" if no format exists. TO_DATE(date_string [, format_string]) ``` ```sql Mach> CREATE LOG TABLE to_date_table (id INTEGER, dt datetime); Created successfully. Mach> INSERT INTO to_date_table VALUES(1, TO_DATE('1999-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO to_date_table VALUES(2, TO_DATE('2012-11-11 1:2:3 4:5:6')); 1 row(s) inserted. Mach> INSERT INTO to_date_table VALUES(3, TO_DATE('2014-12-30 11:22:33 444:555:666')); 1 row(s) inserted. Mach> INSERT INTO to_date_table VALUES(4, TO_DATE('2014-12-30 23:22:34 777:888:999', 'YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn')); 1 row(s) inserted. Mach> SELECT id, dt FROM to_date_table WHERE dt > TO_DATE('1999-11-11 1:2:3 4:5:0'); id dt ----------------------------------------------- 4 2014-12-30 23:22:34 777:888:999 3 2014-12-30 11:22:33 444:555:666 2 2012-11-11 01:02:03 004:005:006 1 1999-11-11 01:02:03 004:005:006 [4] row(s) selected. Mach> SELECT id, dt FROM to_date_table WHERE dt > TO_DATE('2000-11-11 1:2:3 4:5:0'); id dt ----------------------------------------------- 4 2014-12-30 23:22:34 777:888:999 3 2014-12-30 11:22:33 444:555:666 2 2012-11-11 01:02:03 004:005:006 [3] row(s) selected. Mach> SELECT id, dt FROM to_date_table WHERE dt > TO_DATE('2012-11-11 1:2:3','YYYY-MM-DD HH24:MI:SS') and dt < TO_DATE('2014-11-11 1:2:3','YYYY-MM-DD HH24:MI:SS'); id dt ----------------------------------------------- 2 2012-11-11 01:02:03 004:005:006 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999', 'YYYY') FROM to_date_table LIMIT 1; id TO_DATE('1999', 'YYYY') ----------------------------------------------- 4 1999-01-01 00:00:00 000:000:000 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999-12', 'YYYY-MM') FROM to_date_table LIMIT 1; id TO_DATE('1999-12', 'YYYY-MM') ----------------------------------------------- 4 1999.12.01 00:00:00 000:000:000 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999', 'YYYY') FROM to_date_table LIMIT 1; id TO_DATE('1999', 'YYYY') ----------------------------------------------- 4 1999-01-01 00:00:00 000:000:000 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999-12', 'YYYY-MM') FROM to_date_table LIMIT 1; id TO_DATE('1999-12', 'YYYY-MM') ----------------------------------------------- 4 1999-12-01 00:00:00 000:000:000 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999-12-31 13:12', 'YYYY-MM-DD HH24:MI') FROM to_date_table LIMIT 1; id TO_DATE('1999-12-31 13:12', 'YYYY-MM-DD HH24:MI') ------------------------------------------------------- 4 1999-12-31 13:12:00 000:000:000 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999-12-31 13:12:32', 'YYYY-MM-DD HH24:MI:SS') FROM to_date_table LIMIT 1; id TO_DATE('1999-12-31 13:12:32', 'YYYY-MM-DD HH24:MI:SS') ------------------------------------------------------- 4 1999-12-31 13:12:32 000:000:000 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999-12-31 13:12:32 123', 'YYYY-MM-DD HH24:MI:SS mmm') FROM to_date_table LIMIT 1; id TO_DATE('1999-12-31 13:12:32 123', 'YYYY-MM-DD HH24:MI:SS mmm') ------------------------------------------------------- 4 1999-12-31 13:12:32 123:000:000 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999-12-31 13:12:32 123:456', 'YYYY-MM-DD HH24:MI:SS mmm:uuu') FROM to_date_table LIMIT 1; id TO_DATE('1999-12-31 13:12:32 123:456', 'YYYY-MM-DD HH24:MI:SS mmm:uuu') ------------------------------------------------------- 4 1999-12-31 13:12:32 123:456:000 [1] row(s) selected. Mach> SELECT id, TO_DATE('1999-12-31 13:12:32 123:456:789', 'YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn') FROM to_date_table LIMIT 1; id TO_DATE('1999-12-31 13:12:32 123:456:789', 'YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn') ------------------------------------------------------- 4 1999-12-31 13:12:32 123:456:789 [1] row(s) selected. ``` ## TO_DATE_SAFE TO_DATE()와 유사하지만 변환에 실패하면 오류 없이 NULL을 반환합니다. ```sql TO_DATE_SAFE(date_string [, format_string]) ``` ```sql Mach> CREATE LOG TABLE date_table (ts DATETIME); Created successfully. Mach> INSERT INTO date_table VALUES (TO_DATE_SAFE('2016-01-01', 'YYYY-MM-DD')); 1 row(s) inserted. Mach> INSERT INTO date_table VALUES (TO_DATE_SAFE('2016-01-02', 'YYYY')); 1 row(s) inserted. Mach> INSERT INTO date_table VALUES (TO_DATE_SAFE('2016-12-32', 'YYYY-MM-DD')); 1 row(s) inserted. Mach> SELECT ts FROM date_table; ts ---------------------------------- NULL NULL 2016-01-01 00:00:00 000:000:000 [3] row(s) selected. ``` ## TO_HEX 컬럼 값이 NULL이면 NULL을 반환하고, NULL이 아니면 원래 값을 16진 문자열로 반환합니다. 출력 일관성을 위해 short, int, long 타입은 BIG ENDIAN으로 변환합니다. ```sql TO_HEX(column) ``` ```sql Mach> CREATE LOG TABLE hex_table (id1 SHORT, id2 INTEGER, id3 VARCHAR(10), id4 FLOAT, id5 DOUBLE, id6 LONG, id7 IPV4, id8 IPV6, id9 TEXT, id10 BINARY, id11 DATETIME); Created successfully. Mach> INSERT INTO hex_table VALUES(256, 65535, '0123456789', 3.141592, 1024 * 1024 * 1024 * 3.14, 13513135446, '192.168.0.1', '::192.168.0.1', 'textext', 'binary', TO_DATE('1999', 'YYYY')); 1 row(s) inserted. Mach> SELECT TO_HEX(id1), TO_HEX(id2), TO_HEX(id3), TO_HEX(id4), TO_HEX(id5), TO_HEX(id6), TO_HEX(id7), TO_HEX(id8), TO_HEX(id9), TO_HEX(id10), TO_HEX(id11) FROM hex_table; TO_HEX(id1) TO_HEX(id2) TO_HEX(id3) TO_HEX(id4) TO_HEX(id5) TO_HEX(id6) TO_HEX(id7) ------------------------------------------------------------------------------------------------------------------------- TO_HEX(id8) TO_HEX(id9) -------------------------------------------------------------------------------------------------------------------------- TO_HEX(id10) TO_HEX(id11) -------------------------------------------------------------------------------------------------------- 0100 0000FFFF 30313233343536373839 D80F4940 1F85EB51B81EE941 0000000325721556 04C0A80001 06000000000000000000000000C0A80001 74657874657874 62696E617279 0CB325846E226000 [1] row(s) selected. ``` ## TO_INET_STR `TO_INET_STR(ipv4_value)`는 `IPV4` 값을 점으로 구분된 십진 문자열로 변환합니다. ```sql TO_INET_STR(ipv4_value) ``` ```sql SELECT TO_INET_STR(TO_IPV4('192.168.0.1')); ``` ## TO_IPV4 / TO_IPV4_SAFE 주어진 문자열을 IPv4 타입으로 변환합니다. 문자열을 숫자 값으로 변환할 수 없으면 TO_IPV4()는 오류를 반환하고 작업을 중단합니다. 반면 TO_IPV4_SAFE()는 오류 발생 시 NULL을 반환하므로 작업을 계속할 수 있습니다. ```sql TO_IPV4(string_value) TO_IPV4_SAFE(string_value) ``` ```sql Mach> CREATE LOG TABLE ipv4_table (c1 varchar(100)); Created successfully. Mach> INSERT INTO ipv4_table VALUES('192.168.0.1'); 1 row(s) inserted. Mach> INSERT INTO ipv4_table VALUES(' 192.168.0.2 '); 1 row(s) inserted. Mach> INSERT INTO ipv4_table VALUES(NULL); 1 row(s) inserted. Mach> SELECT c1 FROM ipv4_table; c1 ------------------------------------------------------------------------------------ NULL 192.168.0.2 192.168.0.1 [3] row(s) selected. Mach> SELECT TO_IPV4(c1) FROM ipv4_table; TO_IPV4(c1) ------------------ NULL 192.168.0.2 192.168.0.1 [3] row(s) selected. Mach> INSERT INTO ipv4_table VALUES('192.168.0.1.1'); 1 row(s) inserted. Mach> SELECT TO_IPV4(c1) FROM ipv4_table limit 1; TO_IPV4(c1) ------------------ [ERR-02068 : Invalid IPv4 address format (192.168.0.1.1).] [0] row(s) selected. Mach> SELECT TO_IPV4_SAFE(c1) FROM ipv4_table; TO_IPV4_SAFE(c1) ------------------- NULL NULL 192.168.0.2 192.168.0.1 [4] row(s) selected. ``` ## TO_IPV6 / TO_IPV6_SAFE 주어진 문자열을 IPv6 타입으로 변환합니다. 문자열을 숫자 타입으로 변환할 수 없으면 TO_IPV6()는 오류를 반환하고 작업을 중단합니다. 반면 TO_IPV6_SAFE()는 오류 발생 시 NULL을 반환하므로 작업을 계속할 수 있습니다. ```sql TO_IPV6(string_value) TO_IPV6_SAFE(string_value) ``` ```sql Mach> CREATE LOG TABLE ipv6_table (id varchar(100)); Created successfully. Mach> INSERT INTO ipv6_table VALUES('::0.0.0.0'); 1 row(s) inserted. Mach> INSERT INTO ipv6_table VALUES('::127.0.0.1'); 1 row(s) inserted. Mach> INSERT INTO ipv6_table VALUES('::127.0' || '.0.2'); 1 row(s) inserted. Mach> INSERT INTO ipv6_table VALUES(' ::127.0.0.3'); 1 row(s) inserted. Mach> INSERT INTO ipv6_table VALUES('::127.0.0.4 '); 1 row(s) inserted. Mach> INSERT INTO ipv6_table VALUES(' ::FFFF:255.255.255.255 '); 1 row(s) inserted. Mach> INSERT INTO ipv6_table VALUES('21DA:D3:0:2F3B:2AA:FF:FE28:9C5A'); 1 row(s) inserted. Mach> SELECT TO_IPV6(id) FROM ipv6_table; TO_IPV6(id) --------------------------------------------------------------- 21da:d3::2f3b:2aa:ff:fe28:9c5a ::ffff:255.255.255.255 ::127.0.0.4 ::127.0.0.3 ::127.0.0.2 ::127.0.0.1 :: [7] row(s) selected. Mach> INSERT INTO ipv6_table VALUES('127.0.0.10.10'); 1 row(s) inserted. Mach> SELECT TO_IPV6(id) FROM ipv6_table limit 1; TO_IPV6(id) --------------------------------------------------------------- [ERR-02148 : Invalid IPv6 address format.(127.0.0.10.10)] [0] row(s) selected. Mach> SELECT TO_IPV6_SAFE(id) FROM ipv6_table; TO_IPV6_SAFE(id) --------------------------------------------------------------- NULL 21da:d3::2f3b:2aa:ff:fe28:9c5a ::ffff:255.255.255.255 ::127.0.0.4 ::127.0.0.3 ::127.0.0.2 ::127.0.0.1 :: [8] row(s) selected. ``` ## TO_NUMBER / TO_NUMBER_SAFE 주어진 문자열을 숫자(double)로 변환합니다. 문자열을 숫자 값으로 변환할 수 없으면 TO_NUMBER()는 오류를 반환하고 작업을 중단합니다. 반면 TO_NUMBER_SAFE()는 오류 발생 시 NULL을 반환하므로 작업을 계속할 수 있습니다. ```sql TO_NUMBER(string_value) TO_NUMBER_SAFE(string_value) ``` ```sql Mach> CREATE LOG TABLE number_table (id varchar(100)); Created successfully. Mach> INSERT INTO number_table VALUES('10'); 1 row(s) inserted. Mach> INSERT INTO number_table VALUES('20'); 1 row(s) inserted. Mach> INSERT INTO number_table VALUES('30'); 1 row(s) inserted. Mach> SELECT TO_NUMBER(id) from number_table; TO_NUMBER(id) ------------------------------ 30 20 10 [3] row(s) selected. Mach> CREATE LOG TABLE safe_table (id varchar(100)); Created successfully. Mach> INSERT INTO safe_table VALUES('invalidnumber'); 1 row(s) inserted. Mach> SELECT TO_NUMBER(id) from safe_table; TO_NUMBER(id) ------------------------------ [ERR-02145 : The string cannot be converted to number value.(invalidnumber)] [0] row(s) selected. Mach> SELECT TO_NUMBER_SAFE(id) from safe_table; TO_NUMBER_SAFE(id) ------------------------------ NULL [1] row(s) selected. ``` ## TOP_K {#top_k} `TOP_K(value, k)`는 가장 자주 등장한 `k`개의 숫자 값을 `value:count` 형식의 문자열로 반환합니다. ```sql TOP_K(value, k) ``` - `value`는 숫자형이어야 합니다. - `k`는 양의 정수 상수여야 합니다. - `NULL` 값은 무시합니다. - 반환 타입은 `VARCHAR`입니다. - 정렬 기준은 빈도 내림차순이며, 빈도가 같으면 값 오름차순입니다. ```sql SELECT TOP_K(alarm_code, 3) FROM event_log; ``` 예시 결과: ```text 101:532,205:317,301:90 ``` ## TO_TIMESTAMP datetime 타입을 UTC 기준 1970-01-01 00:00:00부터 경과한 나노초 값으로 변환합니다. 아래 예제의 날짜와 시각은 UTC+09:00 기준입니다. ```sql TO_TIMESTAMP(datetime_value) ``` ```sql Mach> create table datetime_tbl (c1 datetime); Created successfully. Mach> insert into datetime_tbl values ('2010-01-01 10:10:10'); 1 row(s) inserted. Mach> select to_timestamp(c1) from datetime_tbl; to_timestamp(c1) ----------------------- 1262308210000000000 [1] row(s) selected. ``` ## TRUNC TRUNC 함수는 소수점 이하 n자리에서 잘라낸 값을 반환합니다. n을 생략하면 0으로 간주하여 소수점을 모두 제거합니다. n이 음수이면 소수점 앞 n자리에서 잘라낸 값을 반환합니다. ```sql TRUNC(number [, n]) ``` ```sql Mach> CREATE LOG TABLE trunc_table (i1 DOUBLE); Created successfully. Mach> INSERT INTO trunc_table VALUES (158.799); 1 row(s) inserted. Mach> SELECT TRUNC(i1, 1), TRUNC(i1, -1) FROM trunc_table; TRUNC(i1, 1) TRUNC(i1, -1) ----------------------------------------------------------- 158.7 150 [1] row(s) selected. Mach> SELECT TRUNC(i1, 2), TRUNC(i1, -2) FROM trunc_table; TRUNC(i1, 2) TRUNC(i1, -2) ----------------------------------------------------------- 158.79 100 [1] row(s) selected. ``` ## TS_CHANGE_COUNT 특정 컬럼 값의 변경 횟수를 구하는 집계 함수입니다. 입력 데이터가 시간순으로 입력된다는 것을 보장할 수 없으므로 1) Join 또는 2) Inline view와 함께 사용할 수 없습니다. VARCHAR 타입은 지원하지 않습니다. * **Cluster Edition에서는 사용할 수 없습니다.** ```sql TS_CHANGE_COUNT(column) ``` ```sql Mach> CREATE LOG TABLE ipcount_table (id INTEGER, ip IPV4); Created successfully. Mach> INSERT INTO ipcount_table VALUES (1, '192.168.0.1'); 1 row(s) inserted. Mach> INSERT INTO ipcount_table VALUES (1, '192.168.0.2'); 1 row(s) inserted. Mach> INSERT INTO ipcount_table VALUES (1, '192.168.0.1'); 1 row(s) inserted. Mach> INSERT INTO ipcount_table VALUES (1, '192.168.0.2'); 1 row(s) inserted. Mach> INSERT INTO ipcount_table VALUES (2, '192.168.0.3'); 1 row(s) inserted. Mach> INSERT INTO ipcount_table VALUES (2, '192.168.0.3'); 1 row(s) inserted. Mach> INSERT INTO ipcount_table VALUES (2, '192.168.0.4'); 1 row(s) inserted. Mach> INSERT INTO ipcount_table VALUES (2, '192.168.0.4'); 1 row(s) inserted. Mach> SELECT id, TS_CHANGE_COUNT(ip) from ipcount_table GROUP BY id; id TS_CHANGE_COUNT(ip) ------------------------------------ 2 2 1 4 [2] row(s) selected. ``` ## UNIX_TIMESTAMP UNIX_TIMESTAMP는 유닉스 time() 시스템 콜 기준으로 date 타입 값을 32비트 정수로 변환하는 함수입니다. (FROM_UNIXTIME은 반대로 정수 값을 date 타입으로 변환합니다.) ```sql UNIX_TIMESTAMP(datetime_value) ``` ```sql Mach> CREATE table unix_table (c1 int); Created successfully. Mach> INSERT INTO unix_table VALUES (UNIX_TIMESTAMP('2001-01-01')); 1 row(s) inserted. Mach> SELECT * FROM unix_table; C1 -------------- 978274800 [1] row(s) selected. ``` ## UPPER 영문 문자열을 대문자로 변환합니다. ```sql UPPER(string_value) ``` ```sql Mach> CREATE LOG TABLE upper_table(id INTEGER,name VARCHAR(10)); Created successfully. Mach> INSERT INTO upper_table VALUES(1, ''); 1 row(s) inserted. Mach> INSERT INTO upper_table VALUES(2, 'James'); 1 row(s) inserted. Mach> INSERT INTO upper_table VALUES(3, 'sarah'); 1 row(s) inserted. Mach> INSERT INTO upper_table VALUES(4, 'THOMAS'); 1 row(s) inserted. Mach> SELECT id, UPPER(name) FROM upper_table; id UPPER(name) ---------------------------- 4 THOMAS 3 SARAH 2 JAMES 1 NULL [4] row(s) selected. ``` ## VARIANCE / VAR_POP 지정한 숫자 컬럼의 분산을 반환하는 집계 함수입니다. VARIANCE는 표본 분산, VAR_POP은 모분산을 반환합니다. ```sql VARIANCE(column_name) VAR_POP(column_name) ``` ```sql Mach> CREATE LOG TABLE var_table(c1 INTEGER, c2 DOUBLE); Created successfully. Mach> INSERT INTO var_table VALUES (1, 1); 1 row(s) inserted. Mach> INSERT INTO var_table VALUES (2, 1); 1 row(s) inserted. Mach> INSERT INTO var_table VALUES (1, 2); 1 row(s) inserted. Mach> INSERT INTO var_table VALUES (2, 2); 1 row(s) inserted. Mach> SELECT VARIANCE(c1) FROM var_table; VARIANCE(c1) ------------------------------ 0.333333 [1] row(s) selected. Mach> SELECT VAR_POP(c1) FROM var_table; VAR_POP(c1) ------------------------------ 0.25 [1] row(s) selected. ``` ## YEAR / MONTH / DAY 입력 datetime 컬럼 값에서 각각 연, 월, 일을 추출해 정수로 반환합니다. ```sql YEAR(datetime_col) MONTH(datetime_col) DAY(datetime_col) ``` ```sql Mach> CREATE LOG TABLE extract_table(c1 DATETIME, c2 INTEGER); Created successfully. Mach> INSERT INTO extract_table VALUES (to_date('2001-01-01 12:30:00 000:000:000'), 1); 1 row(s) inserted. Mach> SELECT YEAR(c1), MONTH(c1), DAY(c1) FROM extract_table; year(c1) month(c1) day(c1) ---------------------------------------- 2001 1 1 ``` ## ISNAN / ISINF 인자로 받은 숫자 값이 NaN 또는 Inf인지 판별합니다. NaN 또는 Inf이면 1, 그렇지 않으면 0을 반환합니다. ```sql ISNAN(number) ISINF(number) ``` 다음 예제는 테이블에 이미 `NaN` 및 `Inf` 값이 들어 있는 경우를 가정합니다. SQL `INSERT` 문에서 `nan` 또는 `inf` 토큰을 값으로 직접 입력할 수는 없습니다. ```sql Mach> SELECT * FROM test; I1 I2 I3 ------------------------------------------------------------------------ 1 1 1 nan inf 0 NULL NULL NULL [3] row(s) selected. Mach> SELECT ISNAN(i1), ISNAN(i2), ISNAN(i3), i3 FROM test ; ISNAN(i1) ISNAN(i2) ISNAN(i3) i3 ----------------------------------------------------- 0 0 0 1 1 0 0 0 NULL NULL NULL NULL [3] row(s) selected. Mach> SELECT * FROM test WHERE ISNAN(i1) = 1; I1 I2 I3 ------------------------------------------------------------------------ nan inf 0 [1] row(s) selected. ``` ## JSON_SET JSON 문서의 특정 경로에 SQL scalar 값을 JSON scalar로 저장합니다. ```sql JSON_SET(json_doc, path, scalar) ``` ```sql Mach> SELECT JSON_SET('{"ship":{"status":"READY"}}', '$.ship.status', 'DONE') FROM dual; JSON_SET('{"ship":{"status":"READY"}}', '$.ship.status', 'DONE') -------------------------------------------------------------------------------- {"ship":{"status":"DONE"}} [1] row(s) selected. ``` 주의사항: - `path` 는 full JSONPath를 사용해야 합니다. - `JSON_SET(..., path, NULL)` 은 JSON `null` 을 저장합니다. - JSON 문서 인자가 `NULL` 이면 결과는 SQL `NULL` 입니다. - `path` 가 `NULL` 이거나 빈 문자열이면 오류가 발생합니다. - object 경로 중심으로 지원합니다. - array element 갱신 예: `$.items[0]` 는 지원하지 않습니다. ## JSON_SET_JSON 세 번째 인자를 JSON 문자열로 해석하여 object 또는 array subtree를 저장합니다. ```sql JSON_SET_JSON(json_doc, path, json_text) ``` ```sql Mach> SELECT JSON_SET_JSON('{"ship":{}}', '$.ship.owner', '{"name":"machbase"}') FROM dual; JSON_SET_JSON('{"ship":{}}', '$.ship.owner', '{"name":"machbase"}') ---------------------------------------------------------------------------- {"ship":{"owner":{"name":"machbase"}}} [1] row(s) selected. ``` 주의사항: - `path` 는 full JSONPath를 사용해야 합니다. - 세 번째 인자가 SQL `NULL` 이면 결과는 SQL `NULL` 입니다. - 유효하지 않은 JSON 문자열은 오류가 발생합니다. - object 경로 중심으로 지원합니다. - array element 갱신은 지원하지 않습니다. ## JSON_REMOVE JSON 문서에서 특정 멤버 또는 하위 경로를 제거합니다. ```sql JSON_REMOVE(json_doc, path) ``` ```sql Mach> SELECT JSON_REMOVE('{"owner":{"name":"machbase","team":"db"}}', '$.owner.team') FROM dual; JSON_REMOVE('{"owner":{"name":"machbase","team":"db"}}', '$.owner.team') -------------------------------------------------------------------------- {"owner":{"name":"machbase"}} [1] row(s) selected. ``` 주의사항: - `path` 는 full JSONPath를 사용해야 합니다. - 존재하지 않는 경로는 no-op 으로 처리됩니다. - `JSON_REMOVE(..., '$')` 는 허용되지 않습니다. - JSON 문서 인자가 `NULL` 이면 결과는 SQL `NULL` 입니다. ## PI() {#pi} `DOUBLE` 타입의 π 상수를 반환합니다. ```sql SELECT PI(); ``` ```sql Mach> SELECT PI(); PI() ------------------------------ 3.141592653589793 [1] row(s) selected. ``` ## SQRT() {#sqrt} 제곱근을 반환합니다. ```sql SELECT SQRT(9), SQRT(2.25), SQRT(16.0); ``` ```sql Mach> SELECT SQRT(9), SQRT(2.25), SQRT(16.0); SQRT(9) SQRT(2.25) SQRT(16.0) ----------------------------------------------- 3 1.5000000000000000 4 [1] row(s) selected. ``` ## POWER() {#power} `base`의 `exponent` 거듭제곱을 반환합니다. ```sql SELECT POWER(2, 3), POWER(9, 0.5), POWER(4, -1); ``` ```sql Mach> SELECT POWER(2, 3), POWER(9, 0.5), POWER(4, -1); POWER(2, 3) POWER(9, 0.5) POWER(4, -1) ------------------------------------------------ 8 3.0000000000000000 0.2500000000000000 [1] row(s) selected. ``` ## POW() {#pow} `POWER()`의 별칭입니다. ```sql SELECT POW(2, 3), POW(2, -1), POW(10, 0); ``` ```sql Mach> SELECT POW(2, 3), POW(2, -1), POW(10, 0); POW(2, 3) POW(2, -1) POW(10, 0) ----------------------------------------- 8 0.5 1 [1] row(s) selected. ``` ## LOG() {#log} `LOG(n)`은 자연로그, `LOG(base, n)`은 지정한 밑의 로그를 계산합니다. ```sql SELECT LOG(2, 8), LOG(100), LOG(10, 1000); ``` ```sql Mach> SELECT LOG(2, 8), LOG(100), LOG(10, 1000); LOG(2, 8) LOG(100) LOG(10, 1000) ------------------------------------------------ 3 4.605170185988092 3 [1] row(s) selected. ``` ## LN() {#ln} 자연로그 `ln(n)`을 반환합니다. ```sql SELECT LN(1), LN(10), LN(1000); ``` ```sql Mach> SELECT LN(1), LN(10), LN(1000); LN(1) LN(10) LN(1000) ----------------------------------- 0 2.302585092994046 6.907755278982137 [1] row(s) selected. ``` ## EXP() {#exp} `e^n`을 반환합니다. ```sql SELECT EXP(0), EXP(1), EXP(-1); ``` ```sql Mach> SELECT EXP(0), EXP(1), EXP(-1); EXP(0) EXP(1) EXP(-1) ----------------------------------- 1 2.718281828459045 0.36787944117144233 [1] row(s) selected. ``` ## FLOOR() {#floor} 음의 무한대 방향으로 내림합니다. ```sql SELECT FLOOR(-1.2), FLOOR(3.9), FLOOR(-3.0); ``` ```sql Mach> SELECT FLOOR(-1.2), FLOOR(3.9), FLOOR(-3.0); FLOOR(-1.2) FLOOR(3.9) FLOOR(-3.0) ----------------------------------------- -2 3 -3 [1] row(s) selected. ``` ## CEIL() {#ceil} 양의 무한대 방향으로 올림합니다. ```sql SELECT CEIL(-1.2), CEIL(3.2), CEIL(-3.0); ``` ```sql Mach> SELECT CEIL(-1.2), CEIL(3.2), CEIL(-3.0); CEIL(-1.2) CEIL(3.2) CEIL(-3.0) ------------------------------------- -1 4 -3 [1] row(s) selected. ``` ## SIN() {#sin} 라디안 입력, 사인값 반환. ```sql SELECT SIN(0), SIN(PI()/2), SIN(PI()); ``` ```sql Mach> SELECT SIN(0), SIN(PI()/2), SIN(PI()); SIN(0) SIN(PI()/2) SIN(PI()) ------------------------------------ 0 1 0 [1] row(s) selected. ``` ## SLOPE {#slope} `SLOPE(y, x)`는 숫자형 `(x, y)` 점들에 대한 선형 회귀 직선의 기울기를 계산합니다. ```sql SLOPE(y, x) ``` - 두 인자는 모두 숫자형이어야 합니다. - `NULL` 값은 무시합니다. - 유효한 데이터가 부족하거나 `x` 분산이 0이면 결과는 `NULL`입니다. - 반환 타입은 `DOUBLE`입니다. ```sql SELECT SLOPE(temp_c, sample_sec) FROM sensor_log; ``` ## COS() {#cos} 라디안 입력, 코사인값 반환. ```sql SELECT COS(0), COS(PI()), COS(PI()/2); ``` ```sql Mach> SELECT COS(0), COS(PI()), COS(PI()/2); COS(0) COS(PI()) COS(PI()/2) ------------------------------------- 1 -1 0 [1] row(s) selected. ``` ## TAN() {#tan} 라디안 입력, 탄젠트값 반환. ```sql SELECT TAN(0), TAN(PI()/4), TAN(PI()); ``` ```sql Mach> SELECT TAN(0), TAN(PI()/4), TAN(PI()); TAN(0) TAN(PI()/4) TAN(PI()) ----------------------------------- 0 1 0 [1] row(s) selected. ``` ## MOD() {#mod} 몫을 0으로 절사한 기준으로 나머지를 계산합니다. ```sql SELECT MOD(10, 3), MOD(11, 4), MOD(-10, 3), MOD(3.5, 0.5); ``` ```sql Mach> SELECT MOD(10, 3), MOD(11, 4), MOD(-10, 3), MOD(3.5, 0.5); MOD(10, 3) MOD(11, 4) MOD(-10, 3) MOD(3.5, 0.5) ------------------------------------------------------- 1 3 -1 0 [1] row(s) selected. ``` ## MODE {#mode} `MODE(value)`는 입력 집합에서 가장 자주 나타나는 숫자 값을 반환합니다. ```sql MODE(value) ``` - `value`는 숫자형이어야 합니다. - `NULL` 값은 무시합니다. - 최빈값이 여러 개면 더 작은 값을 반환합니다. - 반환 타입은 `DOUBLE`입니다. ```sql SELECT MODE(alarm_code) FROM event_log; ``` ## P05 / P10 / P90 / P95 {#p05-p10-p90-p95} 자주 쓰는 분위값을 빠르게 표현할 수 있도록 준비된 정확 분위수 축약 함수입니다. ```sql P05(value) P10(value) P90(value) P95(value) ``` - `value`는 숫자형이어야 합니다. - `NULL` 값은 무시합니다. - 반환 타입은 `DOUBLE`입니다. `P05`, `P10`, `P90`, `P95`는 각각 `PERCENTILE_CONT(value, 0.05)`, `0.10`, `0.90`, `0.95`와 같은 의미입니다. ```sql SELECT P05(response_ms), P10(response_ms), P90(response_ms), P95(response_ms) FROM web_log; ``` ## PERCENTILE_CONT / PERCENTILE_DISC {#percentile_cont-percentile_disc} 이 함수들은 숫자형 입력에 대해 정확한 분위값을 계산하는 집계 함수입니다. ```sql PERCENTILE_CONT(value, ratio) PERCENTILE_DISC(value, ratio) ``` - `value`는 숫자형이어야 합니다. - `ratio`는 `0.0` 이상 `1.0` 이하의 상수여야 합니다. - `PERCENTILE_CONT`는 필요하면 인접한 정렬 값 사이를 보간합니다. - `PERCENTILE_DISC`는 목표 순위에 해당하는 실제 관측값 중 하나를 선택합니다. - 두 함수 모두 반환 타입은 `DOUBLE`입니다. ```sql SELECT PERCENTILE_CONT(latency_ms, 0.95) AS pcont95, PERCENTILE_DISC(latency_ms, 0.95) AS pdisc95 FROM api_log; ``` ## QUANTILE {#quantile} `QUANTILE(value, ratio)`는 숫자형 입력에 대해 정확한 연속 분위값을 계산합니다. ```sql QUANTILE(value, ratio) ``` - `value`는 숫자형이어야 합니다. - `ratio`는 `0.0` 이상 `1.0` 이하의 상수여야 합니다. - 반환 타입은 `DOUBLE`입니다. - `PERCENTILE_CONT`와 같은 연속 분위수 의미를 사용합니다. ```sql SELECT QUANTILE(cpu_usage, 0.75) FROM host_metric; ``` ## RAND() {#rand} 난수 값을 생성합니다. ```sql SELECT RAND(5) = RAND(5) AS same_seed, RAND(7) = RAND(8) AS diff_seed, RAND() = RAND() AS diff_default; ``` ```sql Mach> SELECT RAND(5) = RAND(5) AS same_seed, RAND(7) = RAND(8) AS diff_seed, RAND() = RAND() AS diff_default FROM m$sys_users WHERE name = 'SYS'; same_seed diff_seed diff_default ------------------------------------ 1 0 0 [1] row(s) selected. ``` `RAND(seed)`는 같은 시드면 동일한 값이 나오며, `RAND()`는 세션 내부 상태를 기반으로 `[0,1)` 범위의 값을 생성합니다. ## REGEXP_LIKE `REGEXP_LIKE`는 문자열이 정규식 패턴과 일치하는지 검사합니다. Boolean 값을 반환하며 주로 `WHERE` 절에서 사용합니다. ```sql REGEXP_LIKE(source, pattern) REGEXP_LIKE(source, pattern, match_param) ``` - `source`는 `VARCHAR`여야 합니다. - `pattern`은 상수 `VARCHAR` 정규식이어야 합니다. - `match_param`은 선택 항목이며 상수 `VARCHAR`여야 합니다. `c`는 대소문자를 구분하고, `i`는 대소문자를 구분하지 않습니다. 기본값은 `c`입니다. ```sql SELECT * FROM sensor_text WHERE REGEXP_LIKE(message, 'error|warn', 'i'); ``` ## REGEXP_INSTR `REGEXP_INSTR`는 정규식과 일치하는 위치를 1부터 시작하는 값으로 반환합니다. 일치하는 값이 없으면 `0`을 반환합니다. ```sql REGEXP_INSTR(source, pattern[, position[, occurrence[, return_pos[, match_param]]]]) ``` - `source`는 `VARCHAR`여야 합니다. - `pattern`은 상수 `VARCHAR` 정규식이어야 합니다. - `position`과 `occurrence`는 `1` 이상의 상수 정수입니다. - `return_pos`는 상수 정수입니다. `0`은 시작 위치를 반환하고, `1`은 일치한 문자열 다음 위치를 반환합니다. - `match_param`은 `c` 또는 `i`를 사용할 수 있습니다. 기본값은 `c`입니다. ```sql SELECT REGEXP_INSTR('TechOnTheNet', 'The', 1, 1, 1, 'i'); ``` ## REGEXP_SUBSTR `REGEXP_SUBSTR`는 정규식과 일치하는 부분 문자열을 반환합니다. ```sql REGEXP_SUBSTR(source, pattern[, position[, occurrence[, match_param]]]) ``` - `source`는 `VARCHAR`여야 합니다. - `pattern`은 상수 `VARCHAR` 정규식이어야 합니다. - `position`과 `occurrence`는 `1` 이상의 상수 정수입니다. - `match_param`은 `c` 또는 `i`를 사용할 수 있습니다. 기본값은 `c`입니다. ```sql SELECT REGEXP_SUBSTR('TechOnTheNet', 'a|e|i|o|u', 1, 2, 'i'); ``` ## REGEXP_REPLACE `REGEXP_REPLACE`는 정규식과 일치하는 문자열을 치환합니다. ```sql REGEXP_REPLACE(source, pattern[, replacement[, position[, occurrence[, match_param]]]]) ``` - `source`는 `VARCHAR`여야 합니다. - `pattern`과 `replacement`는 상수 `VARCHAR` 값이어야 합니다. - `replacement`를 생략하면 일치하는 문자열을 제거합니다. - `position`은 `1` 이상의 상수 정수입니다. - `occurrence`는 상수 정수입니다. `0`은 모든 일치 항목을 치환하고, `0`보다 큰 값은 해당 번째 일치 항목만 치환합니다. - `match_param`은 `c` 또는 `i`를 사용할 수 있습니다. 기본값은 `c`입니다. ```sql SELECT REGEXP_REPLACE('TechOnTheNet', 'a|e|i|o|u', 'Z', 1, 2, 'i'); ``` ## 내장 함수 지원 타입 | |Short|Integer|Long|Float|Double|Varchar|Text|Ipv4|Ipv6|Datetime|Binary| |--|--|--|--|--|--|--|--|--|--|--|--| |ABS|o|o|o|o|o|x|x|x|x|x|x| |ADD_TIME|x|x|x|x|x|x|x|x|x|o|x| |APPROX_PERCENTILE / APPROX_MEDIAN / APPROX_P05 / APPROX_P10 / APPROX_P90 / APPROX_P95|o|o|o|o|o|x|x|x|x|x|x| |AREA|o|o|o|o|o|x|x|x|x|x|x| |AVG|o|o|o|o|o|x|x|x|x|x|x| |BITAND / BITOR|o|o|o|x|x|x|x|x|x|x|x| |COUNT|o|o|o|o|o|o|x|o|o|o|x| |CUME_DIST|o|o|o|o|o|x|x|x|x|x|x| |DATE_TRUNC|x|x|x|x|x|x|x|x|x|o|x| |DECODE|o|o|o|o|o|o|x|o|x|o|x| |FIRST / LAST|o|o|o|o|o|o|x|o|o|o|x| |FROM_TIMESTAMP|o|o|o|o|o|x|x|x|x|x|x| |FROM_UNIXTIME|o|o|o|o|o|x|x|x|x|x|x| |GROUP_CONCAT|o|o|o|o|o|o|x|o|o|o|x| |INSTR|x|x|x|x|x|o|o|x|x|x|x| |LEAST / GREATEST|o|o|o|o|o|o|x|x|x|x|x| |LENGTH|x|x|x|x|x|o|o|x|x|x|o| |LOWER|x|x|x|x|x|o|x|x|x|x|x| |LPAD / RPAD|x|x|x|x|x|o|x|x|x|x|x| |LTRIM / RTRIM|x|x|x|x|x|o|x|x|x|x|x| |MAX|o|o|o|o|o|o|x|o|o|o|x| |MEDIAN|o|o|o|o|o|x|x|x|x|x|x| |MIN|o|o|o|o|o|o|x|o|o|o|x| |MODE|o|o|o|o|o|x|x|x|x|x|x| |NVL|x|x|x|x|x|o|x|o|x|x|x| |P05 / P10 / P90 / P95|o|o|o|o|o|x|x|x|x|x|x| |PERCENTILE_CONT / PERCENTILE_DISC|o|o|o|o|o|x|x|x|x|x|x| |QUANTILE|o|o|o|o|o|x|x|x|x|x|x| |REGEXP_LIKE|x|x|x|x|x|o|x|x|x|x|x| |REGEXP_INSTR|x|x|x|x|x|o|x|x|x|x|x| |REGEXP_SUBSTR|x|x|x|x|x|o|x|x|x|x|x| |REGEXP_REPLACE|x|x|x|x|x|o|x|x|x|x|x| |SLOPE|o|o|o|o|o|x|x|x|x|x|x| |TOP_K|o|o|o|o|o|x|x|x|x|x|x| |ROUND|o|o|o|o|o|x|x|x|x|x|x| |ROWNUM|o|o|o|o|o|o|o|o|o|o|o| |SERIESNUM|o|o|o|o|o|o|o|o|o|o|o| |STDDEV / STDDEV_POP|o|o|o|o|o|x|x|x|x|x|x| |SUBSTR|x|x|x|x|x|o|x|x|x|x|x| |SUBSTRING_INDEX|x|x|x|x|x|o|o|x|x|x|x| |SUM|o|o|o|o|o|x|x|x|x|x|x| |SYSDATE / NOW|x|x|x|x|x|x|x|x|x|x|x| |TO_CHAR|o|o|o|o|o|o|x|o|o|o|x| |TO_DATE / TO_DATE_SAFE|x|x|x|x|x|o|x|x|x|x|x| |TO_HEX|o|o|o|o|o|o|o|o|o|o|o| |TO_INET_STR|x|x|x|x|x|x|x|o|x|x|x| |TO_IPV4 / TO_IPV4_SAFE|x|x|x|x|x|o|x|x|x|x|x| |TO_IPV6 / TO_IPV6_SAFE|x|x|x|x|x|o|x|x|x|x|x| |TO_NUMBER / TO_NUMBER_SAFE|x|x|x|x|x|o|x|x|x|x|x| |TO_TIMESTAMP|x|x|x|x|x|x|x|x|x|o|x| |TRUNC|o|o|o|o|o|x|x|x|x|x|x| |TS_CHANGE_COUNT|o|o|o|o|o|x|x|o|o|o|x| |UNIX_TIMESTAMP|x|x|x|x|x|x|x|x|x|o|x| |UPPER|x|x|x|x|x|o|x|x|x|x|x| |VARIANCE / VAR_POP|o|o|o|o|o|x|x|x|x|x|x| |YEAR / MONTH / DAY|x|x|x|x|x|x|x|x|x|o|x| |ISNAN / ISINF|o|o|o|o|o|x|x|x|x|x|x| ## JSON 관련 함수 이 함수들은 JSON 데이터 타입을 인자로 사용합니다. |함수명|설명|비고| |--|--|--| |JSON_EXTRACT(JSON column name, 'json path')|값을 문자열 타입으로 반환합니다.
(값이 없으면 ERROR를 반환합니다.)| - JSON object or array : 모든 객체를 문자열로 변환해 반환합니다.
- String type : 그대로 반환합니다.
- Numeric type : 문자열로 변환해 반환합니다.
- boolean type : \"True\" 또는 \"False\"를 반환합니다.| |JSON_EXTRACT_DOUBLE(JSON column name, 'json path')|값을 64비트 double 타입으로 반환합니다.
(값이 없으면 NULL을 반환합니다.)| - JSON object or array : NULL을 반환합니다.
- String type : 변환 가능하면 변환해 반환하고, 불가능하면 NULL을 반환합니다.
- Numeric type : 64비트 실수로 반환합니다.
- boolean type : \"True\"는 1.0, \"False\"는 0.0으로 반환합니다.| |JSON_EXTRACT_INTEGER(JSON column name, 'json path')|값을 64비트 정수 타입으로 반환합니다.
(값이 없으면 NULL을 반환합니다.)| - JSON object or array : NULL을 반환합니다.
- String type : 변환 가능하면 변환해 반환하고, 불가능하면 NULL을 반환합니다.
- Numeric type : 64비트 정수로 반환합니다.
- boolean type : \"True\"는 1, \"False\"는 0으로 반환합니다.| |JSON_EXTRACT_STRING(JSON column name, 'json path')|값을 문자열 타입으로 반환합니다.
(값이 없으면 NULL을 반환합니다.)
연산자(→)와 동일한 결과를 반환합니다.| - JSON object or array : 모든 객체를 문자열로 변환해 반환합니다.
- String type : 그대로 반환합니다.
- Numeric type : 문자열로 변환해 반환합니다.
- boolean type : \"True\" 또는 \"False\"를 반환합니다.| |JSON_SET(json_doc, path, scalar)|지정한 경로에 SQL scalar 값을 JSON scalar로 저장한 새 JSON 문서를 반환합니다.| - `path` 는 full JSONPath를 사용합니다.
- `NULL` 값은 JSON `null` 로 저장됩니다.
- object 경로만 지원합니다.| |JSON_SET_JSON(json_doc, path, json_text)|지정한 경로에 JSON 문자열을 object 또는 array subtree로 저장한 새 JSON 문서를 반환합니다.| - `path` 는 full JSONPath를 사용합니다.
- 세 번째 인자가 SQL `NULL` 이면 결과는 SQL `NULL` 입니다.
- 유효하지 않은 JSON 문자열은 오류가 발생합니다.| |JSON_REMOVE(json_doc, path)|지정한 경로의 멤버 또는 subtree를 제거한 새 JSON 문서를 반환합니다.| - `path` 는 full JSONPath를 사용합니다.
- 존재하지 않는 경로는 no-op 입니다.
- `JSON_REMOVE(..., '$')` 는 허용되지 않습니다.| |JSON_IS_VALID('json string')|json 문자열이 형식에 맞는지 확인합니다.| - 0 : False
- 1 : True| |JSON_TYPEOF(JSON column name, 'json path')|값의 타입을 반환합니다.| - None : 키가 존재하지 않음
- Object : Object 타입
- Integer : 정수 타입
- Real : 실수 타입
- String : 문자열 타입
- True/False : Boolean
- Array : Array 타입
- Null : NULL| ```sql Mach> CREATE LOG TABLE jsontbl (name VARCHAR(20), jval JSON); Created successfully. Mach> INSERT INTO jsontbl VALUES("name1", '{"name":"test1"}'); 1 row(s) inserted. Mach> INSERT INTO jsontbl VALUES("name2", '{"name":"test2", "value":123}'); 1 row(s) inserted. Mach> INSERT INTO jsontbl VALUES("name3", '{"name":{"class1": "test3"}}'); 1 row(s) inserted. Mach> INSERT INTO jsontbl VALUES("name4", '{"myarray": [1, 2, 3, 4]}'); 1 row(s) inserted. Mach> INSERT INTO jsontbl VALUES("name5", '{"name":"error"'); [ERR-02233: Error occurred at column (2): (Error in json load.)] Mach> SELECT name, JSON_EXTRACT_STRING(jval, '$.name') FROM jsontbl; name JSON_EXTRACT_STRING(jval, '$.name') ----------------------------------------------------------------------------------------------------------- name4 NULL name3 {"class1": "test3"} name2 test2 name1 test1 [4] row(s) selected. Mach> SELECT name, JSON_EXTRACT_INTEGER(jval, '$.myarray[1]') FROM jsontbl; name JSON_EXTRACT_INTEGER(jval, '$.myarray[1]') -------------------------------------------------------------------- name4 2 name3 NULL name2 NULL name1 NULL [4] row(s) selected. Mach> SELECT name, JSON_TYPEOF(jval, '$.name') FROM jsontbl; name JSON_TYPEOF(jval, '$.name') ----------------------------------------------------------------------------------------------------------- name4 None name3 Object name2 String name1 String [4] row(s) selected. ``` ## JSON 연산자 `->` 연산자는 JSON 데이터의 객체에 접근할 때 사용합니다. JSON_EXTRACT_STRING 함수와 동일한 결과를 반환합니다. ```sql json_col -> 'json path' ``` JSON 컬럼의 멤버 값은 JSONPath를 사용하는 `->` 연산자와 dot 축약 문법으로 접근할 수 있습니다. ```sql -- JSONPath arrow 문법 jval->'$.sensor.temperature' -- JSON dot 축약 문법 jval.sensor.temperature ``` 두 표현식은 같은 JSON 값을 조회합니다. 기존 `->` 연산자는 계속 사용할 수 있으며, dot 문법은 같은 값을 더 짧게 표현하기 위한 추가 문법입니다. ```sql Mach> SELECT name, jval->'$.name' FROM jsontbl; name JSON_EXTRACT_STRING(jval, '$.name') ----------------------------------------------------------------------------------------------------------- name4 NULL name3 {"class1": "test3"} name2 test2 name1 test1 [4] row(s) selected. Mach> SELECT name, jval->'$.myarray[1]' FROM jsontbl; name JSON_EXTRACT_INTEGER(jval, '$.myarray[1]') -------------------------------------------------------------------- name4 2 name3 NULL name2 NULL name1 NULL [4] row(s) selected. Mach> SELECT name, jval->'$.name.class1' FROM jsontbl; name jval->'$.name.class1' ----------------------------------------------------------------------------------------------------------- name4 NULL name3 test3 name2 NULL name1 NULL [4] row(s) selected ``` ### JSONPath arrow 문법 arrow 문법은 JSONPath 문자열을 사용합니다. ```sql jval->'$.name' jval->'$.sensor.temperature' jval->'$.items[0].name' ``` 대괄호를 사용해 JSON key를 직접 지정할 수도 있습니다. key 이름에 점(`.`)이 포함된 경우에는 대괄호 문법을 사용합니다. ```sql -- key 이름이 a.b인 경우 jval->'$["a.b"]' jval->'$[a.b]' -- 여러 단계 key를 대괄호로 지정 jval->'$[Plant1][Line1][Temperature]' -- 점이 포함된 하나의 key 이름 jval->'$[Plant1.Line1.Temperature]' ``` `$[Plant1.Line1.Temperature]`는 `Plant1.Line1.Temperature`라는 하나의 key를 찾습니다. `Plant1`, `Line1`, `Temperature`를 단계별 key로 찾으려면 `$[Plant1][Line1][Temperature]` 또는 `$.Plant1.Line1.Temperature`를 사용합니다. key 이름에 특수 문자나 점이 포함된 경우에는 다음처럼 따옴표가 있는 bracket 문법을 권장합니다. ```sql jval->'$["a.b"]["c.d"]["e.f"]' ``` 다음 문법은 지원하지 않습니다. ```sql jval->'$."a.b"' ``` ### JSON dot 축약 문법 JSON 컬럼 뒤에 멤버 이름을 붙여 JSON 값을 조회할 수 있습니다. ```sql -- 단일 멤버 jval.name -- 중첩 멤버 jval.sensor.temperature -- 배열 index jval.items[0].name -- 특수 문자가 포함된 key jval.items[0]."product-id" ``` dot 문법에서 double quote로 감싼 key는 대소문자와 특수 문자를 그대로 사용합니다. ```sql SELECT name, jval."Camel-Key", jval.items[0]."product-id" FROM jsontbl ORDER BY name; ``` ### WHERE 절 타입 비교 JSON 멤버 접근 결과는 조회할 때 문자열처럼 표시됩니다. 그러나 `WHERE` 절에서 숫자 타입 값과 비교하면 JSON 값을 숫자로 파싱해 숫자 비교를 수행합니다. ```sql SELECT name FROM jsontbl WHERE jval->'$.value' > 100 ORDER BY name; SELECT name FROM jsontbl WHERE jval.value BETWEEN 10 AND 30 ORDER BY name; SELECT name FROM jsontbl WHERE jval.value IN (10, 20, 30) ORDER BY name; ``` 지원되는 비교는 다음과 같습니다. - JSON integer 값과 SQL integer 값 비교 - JSON real/double 값과 SQL numeric 값 비교 - JSON 숫자 문자열과 SQL numeric 값 비교 - JSON boolean 값과 문자열 `'true'`, `'false'` 비교 - `=`, `<>`, `<`, `<=`, `>`, `>=`, `BETWEEN`, literal `IN (...)` SQL integer 값과 비교하는 경우 JSON integer는 정수로 비교하므로 `9007199254740992`와 `9007199254740993`처럼 double 정밀도 범위를 넘는 값도 서로 다른 값으로 비교할 수 있습니다. 문자 타입 값과 비교하면 기존처럼 문자열 비교를 수행합니다. ```sql SELECT name FROM jsontbl WHERE jval->'$.name' = 'test1' ORDER BY name; ``` 숫자 비교에서 JSON 값이 숫자로 해석될 수 없으면 조건에 매칭되지 않습니다. 오류로 처리하지 않습니다. 일반 `VARCHAR` 컬럼과 숫자 값의 비교 정책은 변경되지 않으며, 숫자 자동 비교는 JSON 멤버 접근식에만 적용됩니다. ### 이름 해석 규칙 일반 SQL의 컬럼 이름 해석이 JSON dot 해석보다 우선합니다. ```sql SELECT t.jval.name FROM jsontbl t; ``` 위 표현식은 먼저 일반 컬럼 이름으로 해석을 시도합니다. 일반 컬럼으로 해석되지 않고 `jval`이 JSON 컬럼이면 `jval.name`을 JSON 멤버 접근으로 처리합니다. JSON dot 접근은 JSON 컬럼을 기준으로만 사용할 수 있습니다. ```sql -- 지원하지 않음 (jval->'$.sensor').temperature name.member ``` ### 제한 사항 다음 문법은 지원하지 않습니다. - wildcard: `jval.items[*].name` - recursive descent: `jval..name` - filter expression: `jval.items[?(@.price > 10)]` - negative array index: `jval.items[-1]` - single quoted key: `jval.'product-id'` - dot 문법과 arrow 문법 혼합: `jval.items->'$.name'` - JSON 컬럼이 아닌 컬럼의 dot 접근: `name.member` - 임의 expression 뒤의 dot 접근: `(jval->'$.sensor').temperature` - quoted member arrow path: `jval->'$."a.b"'` `IN (SELECT ...)` 형태의 subquery `IN`에서는 JSON 멤버 값의 숫자 자동 비교를 지원하지 않습니다. literal `IN (...)`을 사용합니다. ## 윈도우 함수 윈도우 함수는 행 간 비교, 연산, 정의를 위한 함수이며 분석 함수 또는 랭킹 함수라고도 합니다. SELECT 문에서만 사용할 수 있습니다. ### 윈도우 함수 구문 윈도우 함수는 반드시 OVER 절을 포함합니다. ``` WINDOW_FUNCTION (ARGUMENTS) OVER ([PARTITION BY column_name] [ORDER BY column_name]) ``` * WINDOW_FUNCTION: 윈도우 함수 이름 * ARGUMENTS: 함수에 따라 0~N개의 인자를 지정할 수 있습니다. * PARTITION BY clause: 전체 집합을 기준에 따라 작은 그룹으로 나눕니다. (생략 가능) * ORDER BY clause: 정렬 기준이 되는 ORDER BY 절을 지정합니다. (생략 가능) ### 윈도우 함수 목록 #### LAG 파티션별 윈도우에서 이전 N번째 행의 값을 가져옵니다. 가져올 행이 없으면 NULL을 반환합니다. ``` LAG(column_name, N) OVER ([PARTITION BY column_name] [ORDER BY column_name]) ``` ``` Mach> CREATE LOG TABLE lag_table (name varchar(10), dt datetime, value INTEGER); Created successfully. Mach> INSERT INTO lag_table VALUES('name1', TO_DATE('2024-01-01'), 1); 1 row(s) inserted. Mach> INSERT INTO lag_table VALUES('name1', TO_DATE('2024-01-02'), 2); 1 row(s) inserted. Mach> INSERT INTO lag_table VALUES('name1', TO_DATE('2024-01-03'), 3); 1 row(s) inserted. -- Divide the set by name, sort by dt, and retrieve the first previous value. Mach> SELECT name, dt, value, LAG(value, 1) OVER(PARTITION BY name ORDER BY dt) FROM lag_table; name dt value LAG(value, 1) --------------------------------------------------------------------------- name1 2024-01-01 00:00:00 000:000:000 1 NULL name1 2024-01-02 00:00:00 000:000:000 2 1 name1 2024-01-03 00:00:00 000:000:000 3 2 [3] row(s) selected. ``` #### LEAD 파티션별 윈도우에서 N번째 다음 행의 값을 가져옵니다. 가져올 행이 없으면 NULL을 반환합니다. ``` LEAD(column_name, N) OVER ([PARTITION BY column_name] [ORDER BY column_name]) ``` ``` Mach> CREATE LOG TABLE lead_table (name varchar(10), dt datetime, value INTEGER); Created successfully. Mach> INSERT INTO lead_table VALUES('name1', TO_DATE('2024-01-01'), 1); 1 row(s) inserted. Mach> INSERT INTO lead_table VALUES('name1', TO_DATE('2024-01-02'), 2); 1 row(s) inserted. Mach> INSERT INTO lead_table VALUES('name1', TO_DATE('2024-01-03'), 3); 1 row(s) inserted. -- Divide the set by name, sort by dt, and retrieve the first and subsequent values. Mach> SELECT name, dt, value, LEAD(value, 1) OVER(PARTITION BY name ORDER BY dt) FROM lead_table; name dt value LEAD(value, 1) ---------------------------------------------------------------------------- name1 2024-01-01 00:00:00 000:000:000 1 2 name1 2024-01-02 00:00:00 000:000:000 2 3 name1 2024-01-03 00:00:00 000:000:000 3 NULL [3] row(s) selected. ``` #### NTILE `NTILE(n)`은 정렬된 행을 가능한 균등하게 `n`개의 버킷으로 나누고, 각 행이 속한 버킷 번호를 반환합니다. ``` NTILE(n) OVER ([PARTITION BY column_name] ORDER BY column_name) ``` - `n`은 양의 상수여야 합니다. - `OVER (...)` 안의 `ORDER BY`는 필수입니다. - 행 수가 균등하게 나누어지지 않으면 앞쪽 버킷이 한 행씩 더 가집니다. ``` Mach> SELECT user_id, score, NTILE(4) OVER (ORDER BY score) AS score_band FROM exam_result; ``` --- title: "16.1.4 상대 시간 표현" url: https://docs.machbase.com/kr/dbms/reference/sql/relative-time/ language: kr kind: page --- # 16.1.4 상대 시간 표현 상대 시간 표현을 사용하면 `NOW`, `SYSDATE`와 같은 기준 시점으로부터의 차이를 SQL 문 안에서 직접 기술할 수 있습니다. 별도 함수 호출 없이 시계열 윈도우를 간결하게 표현할 때 유용합니다. > 상대 시간 리터럴(`now - 1h` 형태)은 Machbase 8.0.50 이상에서 지원됩니다. 월/연 단위 보정이 필요하면 `ADD_TIME`, 문자열 변환이 필요하면 `TO_DATE`를 사용합니다. ## 빠른 참조표 | 표현 | 예시 | 설명 | |------|------|------| | `NOW` / `now` | `now` | 현재 시각 (나노초 정밀도) | | `SYSDATE` / `sysdate` | `sysdate` | 현재 시각 (`NOW`와 동일) | | `now - offset` | `now - 1h` | 현재 시각에서 오프셋 뺄셈 | | `now + offset` | `now + 30m` | 현재 시각에서 오프셋 덧셈 | | 나노초 정수 직접 사용 | `value + 1000000000` | 나노초 단위 정수를 DATETIME에 더함 | ## 상대 시간 단위 (리터럴 접미사) | 접미사 | 의미 | 예시 | |--------|------|------| | `ns` | 나노초 | `500ns` | | `us` | 마이크로초 | `20us` | | `ms` | 밀리초 | `15ms` | | `s` | 초 | `45s` | | `m` | 분 | `30m` | | `h` | 시간 | `12h` | | `d` | 일 | `7d` | | `w` | 주 | `2w` (= 14일) | > 월(`month`, `mo`)과 연(`year`, `y`) 접미사는 지원하지 않습니다. 달력 기준으로 한 달이나 > 한 해를 이동하려면 `ADD_TIME()`을 사용합니다. `30d`와 `365d`는 각각 고정된 일수이므로 > 달력상의 한 달·한 해와 항상 같지는 않습니다. ## ADD_TIME 함수 월·연처럼 상대 시간 리터럴에 없는 달력 보정에는 `ADD_TIME()`을 사용합니다. 인자, 형식과 오류 조건은 [SQL 함수 사전](../functions/functions-full/#add_time)을 참고하십시오. ## TO_DATE 함수 조회 구간의 시작과 끝을 날짜 문자열로 지정할 때는 `TO_DATE()`로 DATETIME 값을 만듭니다. 날짜 형식과 변환 오류는 [SQL 함수 사전](../functions/functions-full/#to_date)을 참고하십시오. ## 활용 패턴 ### 시간 구간 필터링 ```sql -- 최근 1시간 데이터 (상대 시간 리터럴) SELECT * FROM sensor_tag WHERE time > now - 1h; -- 최근 1시간 데이터 (ADD_TIME 함수) SELECT * FROM sensor_tag WHERE time > ADD_TIME(now, '0/0/0 -1:0:0'); -- 최근 24시간 기록 SELECT * FROM app_log WHERE _arrival_time BETWEEN now - 1d AND now; -- 최근 10분 이내 알람 SELECT alert_id, level, occurred_at FROM alert_log WHERE occurred_at >= sysdate - 10m; ``` ### 복합 시간 표현 ```sql -- 2일 6시간 15분 후 SELECT * FROM maintenance_plan WHERE planned_at < now + 2d6h15m; -- 하위 초 단위 조합 SELECT TO_CHAR(now + 3s125ms10us4ns, 'YYYY-MM-DD HH24:MI:SS mmm:uuu:nnn'); ``` ### TO_DATE 결과에 오프셋 적용 ```sql -- 문자열 날짜에 3일 추가 SELECT TO_CHAR(TO_DATE('2024-05-01', 'YYYY-MM-DD') + 3d, 'YYYY-MM-DD'); -- 결과: 2024-05-04 -- 문자열 날짜에서 4시간 15분 빼기 SELECT TO_CHAR( TO_DATE('2024-05-01 08:00:00', 'YYYY-MM-DD HH24:MI:SS') - 4h15m, 'YYYY-MM-DD HH24:MI:SS' ); -- 결과: 2024-05-01 03:45:00 ``` ### 나노초 정수 직접 사용 숫자 리터럴은 나노초로 해석됩니다. ```sql -- 1초 = 1,000,000,000 나노초 SELECT event_time + 1000000000 AS event_time_plus_1s FROM events; -- 250나노초 빼기 SELECT event_time - 250 AS event_time_minus_250ns FROM events; ``` ## 제한사항 - 상대 시간 리터럴(`1h`, `30m` 등)은 Machbase 8.0.50 이상에서만 지원됩니다. - 월(`mo`)과 연(`y`) 단위는 리터럴로 지원하지 않습니다. `ADD_TIME()`의 년/월 위치를 사용합니다. - 문자열 리터럴은 interval 산술에서 DATETIME으로 암시적 변환되지 않으므로 `TO_DATE()`로 먼저 변환해야 합니다. - 인터벌 자체는 `ORDER BY` 절에서 사용할 수 없습니다. ## 오류 처리 | 상황 | 오류 | 해결 방법 | |------|------|-----------| | 지원하지 않는 접미사(`1y`, `5mo`) | `ERR-02034` invalid time expression | 달력 단위는 `ADD_TIME()`, 고정 기간은 `d` 등 지원 접미사 사용 | | 단위 누락(`now + 10`) | 나노초로 해석됨 | 의도한 단위 접미사 명시 | | 너무 큰 값(`1000000d`) | `ERR_OVERFLOW_INTERVAL` | 값 범위 축소 | ## LOG의 DURATION 상대 시간 리터럴과 DURATION은 역할이 다릅니다. `event_time >= now - 1h`는 선택한 컬럼의 WHERE 조건이고, DURATION은 LOG의 `_arrival_time` 범위를 지정합니다. TAG의 시간 컬럼이나 사용자 DATETIME 컬럼에는 WHERE 조건을 사용하세요. ```text SELECT ... FROM log_table [WHERE ...] DURATION n unit [BEFORE base_time | AFTER base_time] [GROUP BY ...] [HAVING ...] [ORDER BY ...] [LIMIT ...] SELECT ... FROM log_table [WHERE ...] DURATION FROM from_time TO to_time [GROUP BY ...] [HAVING ...] [ORDER BY ...] [LIMIT ...] ``` 위는 기본 형태입니다. 기간에는 `HOUR`, `MINUTE`, `DAY` 같은 단위를 사용합니다. 전체 범위를 표현하는 `ALL`도 사용할 수 있습니다. DURATION은 WHERE 다음, GROUP BY·ORDER BY 앞에 둡니다. | 형태 | 범위 | 지정되는 스캔 방향 | |---|---|---| | DURATION 1 HOUR | 현재 시각 기준 최근 1시간 | 최신 쪽부터 | | DURATION 1 HOUR BEFORE t | t−1시간부터 t까지 | 최신 쪽부터 | | DURATION 1 HOUR AFTER t | t부터 t+1시간까지 | 오래된 쪽부터 | | DURATION FROM a TO b, a < b | a부터 b까지 | 오래된 쪽부터 | | DURATION FROM a TO b, a > b | b부터 a까지 | 최신 쪽부터 | 범위의 양 끝을 포함합니다. FROM과 TO가 같은 시각이면 그 시각의 행이 대상입니다. 스캔 방향과 조인·집계 이후의 최종 출력 순서는 구분하세요. 결과 순서가 필요하면 ORDER BY를 명시하고, 같은 시각의 여러 행에는 추가 정렬 기준을 둡니다. 다음 예제에서는 끝 시각에 있는 2번 행도 선택됩니다. ```sql CREATE LOG TABLE ch7_ref_duration (event_id INTEGER); INSERT INTO ch7_ref_duration(_arrival_time, event_id) VALUES (TO_DATE('2026-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1); INSERT INTO ch7_ref_duration(_arrival_time, event_id) VALUES (TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS'), 2); SELECT event_id FROM ch7_ref_duration DURATION 1 HOUR BEFORE TO_DATE('2026-01-01 11:00:00', 'YYYY-MM-DD HH24:MI:SS') ORDER BY event_id; DROP TABLE ch7_ref_duration; ``` 결과는 1·2번입니다. 일별로 연속 구간을 나눌 때는 `WHERE _arrival_time >= 시작 AND _arrival_time < 끝`처럼 반개구간을 사용하면 경계 행을 중복 집계하지 않을 수 있습니다. LOG와 LOOKUP이 함께 있는 쿼리는 DURATION 대신 LOG 컬럼의 WHERE 범위를 사용하세요. ## 관련 문서 - [LOG 시간 범위 실습](/dbms/log-table-usage/query-analysis/) — 경계·정렬·조인 결과 비교 - [상대 시간 표현](/dbms-8.5/sql-reference/time-expressions/) — 8.5 레퍼런스 상세 --- title: "16.1.6 ROWID" url: https://docs.machbase.com/kr/dbms/reference/sql/rowid/ language: kr kind: page --- # 16.1.6 ROWID Machbase 8.7.0부터 지원되는 기능 `ROWID`는 테이블 안의 한 행을 다시 찾기 위한 64비트 식별자입니다. 이 페이지는 SQL에서의 의미, 테이블별 조회 조건, INSERT 실행 결과와 유효 기간을 정의합니다. SDK별 접근 API와 코드는 [SDK 기능 지원 범위](/dbms/development-tools-integration/sdk-support-scope/)와 각 언어 페이지를 참고하십시오. 이 기능은 Standard Edition에서 지원합니다. 서버와 SDK를 ROWID를 지원하는 버전으로 함께 업데이트해야 합니다. Cluster Edition에서는 사용할 수 없습니다. ## ROWID와 업무 키 구분 ROWID는 현재 테이블의 저장 행 위치를 가리키는 식별자이며, 주문 번호나 장비 ID 같은 영구 업무 키가 아닙니다. - 일반 컬럼이 아니므로 `SELECT *`에는 포함되지 않습니다. 필요한 경우 명시적으로 조회합니다. - 다른 테이블의 ROWID와 비교하거나 다른 테이블 조회에 사용하지 않습니다. - ROWID 값을 분해하거나 산술 연산에 사용하지 않습니다. - 숫자의 크기가 전체 테이블의 입력 순서를 의미하지는 않습니다. - 신규 테이블에는 `ROWID`라는 실제 컬럼을 정의할 수 없습니다. - 이전 버전에서 실제 `ROWID` 컬럼을 만든 테이블은 그 컬럼을 우선합니다. ROWID 의사 컬럼을 사용하려면 기존 컬럼의 이름을 변경합니다. ```sql SELECT ROWID, name, time, value FROM sensor_tag WHERE name = 'TAG-01'; ``` ## 테이블별 지원 범위 | 테이블 | ROWID의 의미 | 조회 조건 | 단일 INSERT 결과 | |--------|--------------|-----------|------------------| | LOG | 저장된 로그 행의 식별자 | `=`, `<`, `<=`, `>`, `>=`, `BETWEEN`, `ORDER BY` | 생성된 ROWID 반환 | | TAG | 저장된 원본 TAG 행의 식별자 | 최상위 `AND`에 포함된 단일 `ROWID = 값` | 생성된 ROWID 반환 | | TRANSACTION | 단일 `LONG`/`INT64` PRIMARY KEY 값 | 기존 PK가 지원하는 조건 | PK 값을 ROWID로 반환 | | LOOKUP | 단일 `LONG`/`INT64` PRIMARY KEY 값 | 기존 PK가 지원하는 조건 | PK 값을 ROWID로 반환 | | VOLATILE | 단일 `LONG`/`INT64` PRIMARY KEY 값 | 기존 PK가 지원하는 조건 | PK 값을 ROWID로 반환 | TRANSACTION, LOOKUP, VOLATILE 테이블에서는 `AUTO_INCREMENT` 사용 여부와 관계없이 값이 `0` 이상인 단일 `LONG`/`INT64` PRIMARY KEY를 ROWID로 사용합니다. 애플리케이션이 PK 값을 지정한 경우에는 그 값이 반환되고, `AUTO_INCREMENT` PK를 생략하거나 NULL로 지정한 경우에는 서버가 생성한 값이 반환됩니다. 음수 PK는 ROWID로 사용할 수 없습니다. LOG와 TAG ROWID는 `0..UINT64_MAX-1`, 세 PRIMARY KEY 기반 테이블은 `0..INT64_MAX` 범위입니다. `0`은 유효한 값이며 `UINT64_MAX`는 ROWID로 사용할 수 없습니다. ### LOG 조회 LOG ROWID는 범위 조회와 정렬에 사용할 수 있습니다. `_ARRIVAL_TIME`은 여러 행에서 같을 수 있으므로 특정 행을 다시 찾는 용도로는 ROWID를 사용합니다. ```sql SELECT ROWID, message FROM app_log WHERE ROWID >= ? AND ROWID < ? ORDER BY ROWID; ``` `ROWID IN (...)`은 지원하지 않습니다. ### TAG 조회 TAG는 단일 ROWID 일치 조회만 지원합니다. 태그 이름, 시간, 값 조건을 `AND`로 함께 지정할 수 있으며 모든 조건을 만족해야 행을 반환합니다. ```sql SELECT ROWID, name, time, value FROM sensor_tag WHERE ROWID = ? AND name = 'TAG-01' AND time >= TO_DATE('2026-08-10 00:00:00', 'YYYY-MM-DD HH24:MI:SS'); ``` TAG ROWID에는 다음 조건을 사용할 수 없습니다. | 사용법 | 지원 여부 | |--------|:---------:| | `ROWID = ?` | O | | `ROWID > ?`, `BETWEEN` 등 범위 조건 | X | | `ROWID IN (...)` | X | | `ROWID = ? OR ...` | X | | `ORDER BY ROWID` | X | | `DELETE ... WHERE ROWID = ?` | X | | rollup, custom rollup, stat 결과와 결합 | X | ### TRANSACTION, LOOKUP, VOLATILE 비교 다음 세 테이블은 같은 `AUTO_INCREMENT` 선언을 사용할 수 있지만 재시작과 입력 기능이 다릅니다. ```sql CREATE TRANSACTION TABLE orders ( id LONG PRIMARY KEY AUTO_INCREMENT, item VARCHAR(100) ); CREATE LOOKUP TABLE lookup_orders ( id LONG PRIMARY KEY AUTO_INCREMENT, item VARCHAR(100) ); CREATE VOLATILE TABLE volatile_orders ( id LONG PRIMARY KEY AUTO_INCREMENT, item VARCHAR(100) ); ``` | 항목 | TRANSACTION | LOOKUP | VOLATILE | |------|-------------|--------|----------| | 행과 다음 자동값의 재시작 후 유지 | O | O | X | | 명시적 트랜잭션 | O | X | X | | `INSERT ... SELECT`로 자동값 생성 | O | X | X | | AUTO_INCREMENT 테이블 UPSERT | O (ROWID 반환 X) | X | X | | 단일 `INSERT ... VALUES` 결과 ROWID | O | O | O | LOOKUP의 `PROPERTY(SEQUENCE)`와 `NEXTVAL()`은 `AUTO_INCREMENT`와 별개의 기능입니다. 두 방식을 같은 컬럼에 함께 지정하지 않습니다. 명시적 트랜잭션이 진행 중일 때는 TRANSACTION 테이블 DDL을 실행할 수 없습니다. `CREATE TRANSACTION TABLE`이 `ERR-02362`로 실패하면 먼저 `COMMIT` 또는 `ROLLBACK`한 뒤 다시 실행합니다. ### JOIN, 집계와 View JOIN 결과에는 전체 결과를 대표하는 ROWID가 없습니다. 필요한 원본 테이블 별칭의 ROWID를 각각 조회합니다. ```sql SELECT a.ROWID AS order_rowid, b.ROWID AS item_rowid, a.customer, b.item FROM orders a JOIN order_items b ON a.id = b.order_id; ``` | 조회 형태 | ROWID 처리 | |-----------|------------| | JOIN | 필요한 원본 테이블 별칭마다 `alias.ROWID` 지정 | | 집계, `GROUP BY`, `DISTINCT`, 집합 연산 | 결과 행에 새 ROWID를 만들지 않음 | | View, CTE, inline view | 내부 SELECT에서 ROWID를 명시적으로 선택한 경우에만 전달 | ## INSERT 결과로 ROWID를 받는 조건 `INSERT ... RETURNING ROWID` 문법은 사용하지 않습니다. 지원되는 SDK는 성공한 단일 `INSERT ... VALUES`의 실행 결과에 ROWID를 함께 제공합니다. | 입력 방식 | generated ROWID | 설명 | |-----------|:---------------:|------| | 단일 direct `INSERT ... VALUES` | O | 행 한 개가 성공적으로 생성된 경우 | | 단일 prepared INSERT | O | 실행할 때마다 현재 결과를 반환 | | `INSERT ... SELECT` | X | 여러 행을 만들 수 있으므로 단일 값을 반환하지 않음 | | execute-array, batch, `executemany()` | X | 마지막 내부 행을 대표값으로 노출하지 않음 | | Append API, append batch | X | 고속 입력 경로에서는 반환하지 않음 | | loader | X | 파일 입력 경로에서는 반환하지 않음 | | UPSERT | X | INSERT 또는 UPDATE 중 하나를 단일 ROWID로 대표하지 않음 | | 실패한 INSERT | X | 이전 실행의 ROWID도 지워짐 | generated ROWID는 statement별 결과입니다. 연결 전체에서 최근 값을 조회하는 SQL 함수는 제공하지 않습니다. ## 조회 결과와 오류 구분 형식이 올바른 ROWID가 현재 테이블에 없으면 오류가 아니라 0행을 반환합니다. 삭제된 행, 추가 조건과 일치하지 않는 행도 같습니다. 반면 NULL, 음수 PK, `UINT64_MAX`, 숫자로 변환할 수 없는 값, TAG에서 지원하지 않는 범위·IN·OR·정렬 조건은 오류입니다. ## 유효 기간과 재시도 ROWID를 장기간 보관하는 업무 키로 사용하지 않습니다. | 상황 | 기존 ROWID | |------|------------| | 정상 재시작 | 보존된 행은 유지 | | ROWID 보존을 지원하는 제품 backup/restore | 보존된 행은 유지 | | 행 DELETE | 무효 | | transaction ROLLBACK | 해당 INSERT의 ROWID 무효 | | snapshot recovery로 폐기된 행 | 무효 | | LOG TRUNCATE | 과거 값이 재사용될 수 있음 | | 테이블 DROP 후 재생성 | 과거 값이 다른 행을 가리킬 수 있음 | | export/import 또는 행 재삽입 | 보존되지 않음 | INSERT는 성공했지만 네트워크 응답이 끊기면 애플리케이션이 ROWID를 받지 못할 수 있습니다. 이때 같은 INSERT를 자동 재시도하면 중복 행이 생길 수 있으므로 업무 키나 별도의 idempotency 정책으로 실제 반영 여부를 먼저 확인합니다. ## 관련 문서 - [SDK 기능 지원 범위](/dbms/development-tools-integration/sdk-support-scope/) - [AUTO_INCREMENT](/dbms/reference/sql/syntax/auto-increment-syntax/) - [LOG 데이터 입력](/dbms/log-table-usage/data-input-mutation/) - [TAG 데이터 입력](/dbms/tag-table-usage/data-input-mutation/) --- title: "16.2 설정 레퍼런스" url: https://docs.machbase.com/kr/dbms/reference/configuration/ language: kr kind: section --- # 16.2 설정 레퍼런스 Machbase 서버는 `$MACHBASE_HOME/conf/machbase.conf` 파일에 정의된 프로퍼티를 통해 동작을 제어합니다. 이 섹션은 각 프로퍼티의 허용 범위와 기본값을 빠르게 찾아볼 수 있는 레퍼런스입니다. ## 하위 섹션 | 섹션 | 설명 | |------|------| | [설정 프로퍼티 사전](./configuration/) | 서버 기본 설정, 성능, 보안, 로그 등 Standard Edition 전체 프로퍼티 목록 | | [클러스터 설정 프로퍼티 사전](./configuration-2/) | Cluster Edition 전용 Coordinator, Broker, Warehouse 설정 | | [PVO Cache 프로퍼티 사전](./pvo-cache/) | SQL 실행 계획 캐시(PVO Statement Cache) 관련 프로퍼티 | | [Timezone 설정 사전](./configuration-timezone/) | 타임존 프로퍼티 및 클라이언트별 타임존 설정 방법 | ## 프로퍼티 확인 방법 서버 실행 중에 현재 프로퍼티 값을 확인하려면 `v$property` 시스템 뷰를 조회합니다. ```sql -- 전체 프로퍼티 조회 SELECT name, value, type FROM v$property ORDER BY name; -- 특정 프로퍼티 조회 SELECT name, value, min, max FROM v$property WHERE name = 'PORT_NO'; ``` ## 동적 변경 가능 프로퍼티 서버 재시작 없이 `ALTER SYSTEM SET` 명령으로 변경할 수 있는 프로퍼티도 있습니다. ```sql ALTER SYSTEM SET TRACE_LOG_LEVEL = 3; ALTER SYSTEM SET PVO_CACHE_MAX_MEMORY_SIZE = 536870912; ``` 변경 후 `v$property`를 조회하면 적용 여부를 확인할 수 있습니다. 재시작이 필요한 프로퍼티를 동적으로 변경하면 오류가 반환됩니다. --- title: "16.2.1 설정 프로퍼티 사전" url: https://docs.machbase.com/kr/dbms/reference/configuration/configuration/ language: kr kind: page --- # 16.2.1 설정 프로퍼티 사전 `$MACHBASE_HOME/conf/machbase.conf` 파일에서 설정하는 Standard Edition 주요 프로퍼티 사전입니다. 별도 표시가 없는 한 서버 재시작이 필요합니다. ## 서버 기본 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `PORT_NO` | 5656 | 1024~65535 | 클라이언트 TCP/IP 연결 포트 | | `BIND_IP_ADDRESS` | 0.0.0.0 | - | 클라이언트 리스너 바인드 IP. `0.0.0.0`은 모든 인터페이스 | | `GRANT_REMOTE_ACCESS` | 1 | 0~1 | 원격 접속 허용 여부. 0이면 로컬만 허용 | | `MAX_SESSION_COUNT` | 4096 | 64~2^64-1 | 동시 세션 최대 개수 | | `MAX_STMT_COUNT_PER_SESSION` | 1024 | 512~2^32-1 | 세션당 최대 statement 수 | | `SESSION_IDLE_TIMEOUT_SEC` | 0 | 0~2^64-1 | 세션 유휴 타임아웃(초). 0이면 비활성 | | `SESSION_QUERY_TIMEOUT_SEC` | 0 | 0~2^64-1 | 쿼리 실행 타임아웃(초). 0이면 비활성 | | `UNIX_PATH` | machbase-unix | - | Unix domain socket 파일 이름 | | `DBS_PATH` | ?/dbs | - | 데이터베이스 파일 저장 경로(`?`는 `$MACHBASE_HOME`) | | `PID_PATH` | ?/conf | - | PID 파일 저장 경로 | ## CPU / 스레드 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `CPU_COUNT` | 1 | 0~2^32-1 | 사용할 CPU 수. 0이면 전체 사용 | | `CPU_PARALLEL` | 1 | 1~2^32-1 | CPU당 병렬 스레드 수 | | `CPU_AFFINITY_BEGIN_ID` | 0 | 0~2^32-1 | CPU 친화도 시작 번호 | | `CPU_AFFINITY_COUNT` | 0 | 0~2^32-1 | CPU 친화도 사용 수. 0이면 전체 | | `DISK_IO_THREAD_COUNT` | 3 | 1~2^32-1 | 디스크 I/O 스레드 수 | | `INDEX_BUILD_THREAD_COUNT` | 3 | 0~2^32-1 | 인덱스 빌드 스레드 수. 0이면 인덱스 생성 안 함 | | `INDEX_LEVEL_PARTITION_BUILD_THREAD_COUNT` | 3 | 1~1024 | LSM 인덱스 병합 스레드 수 | | `INDEX_LEVEL_PARTITION_AGER_THREAD_COUNT` | 1 | 1~1024 | LSM 인덱스 불필요 파일 삭제 스레드 수 | | `QUERY_PARALLEL_FACTOR` | 0 | 0~100 | 병렬 질의 실행 스레드 수. Standard 기본값 0, Cluster 기본값 4 | ## 메모리 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `PROCESS_MAX_SIZE` | 8GB | 1GB~2^64-1 | 서버 프로세스 최대 메모리(바이트). 배포 샘플은 `16GB`로 설정되어 있을 수 있음 | | `DISK_COLUMNAR_TABLESPACE_MEMORY_MAX_SIZE` | 8GB | 256MB~2^64-1 | 로그 테이블 입력 버퍼 상한. 전체 메모리 예산 안에서 조정 | | `DISK_COLUMNAR_TABLESPACE_MEMORY_MIN_SIZE` | 100MB | 1MB~2^64-1 | 서버 시작 시 사전 확보 메모리 | | `DISK_COLUMNAR_TABLESPACE_MEMORY_EXT_SIZE` | 2MB | 1MB~2^64-1 | 컬럼 파티션 메모리 블록 크기 | | `DISK_COLUMNAR_TABLESPACE_DWFILE_INT_SIZE` | 2MB | 1MB~2^32-1 | 데이터 일관성/복구용 double write 파일 초기 크기 | | `DISK_COLUMNAR_TABLESPACE_DWFILE_EXT_SIZE` | 1MB | 1MB~2^32-1 | double write 파일 확장 크기 | | `DISK_COLUMNAR_PAGE_CACHE_MAX_SIZE` | 2GB | 0~2^64-1 | 페이지 캐시 최대 크기(바이트) | | `VOLATILE_TABLESPACE_MEMORY_MAX_SIZE` | 2GB | 0~2^64-1 | Volatile/Lookup 테이블 전체 메모리 한도 | | `MAX_QPX_MEM` | 1GB | 1MB~2^64-1 | GROUP BY/ORDER BY 등 쿼리 처리기 최대 메모리 | | `MEMORY_ROW_TEMP_TABLE_PAGESIZE` | 32768 | 8KB~2^32-1 | Volatile/Lookup 임시 테이블 페이지 크기(바이트) | ## 디스크 I/O 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `DISK_BUFFER_COUNT` | 16 | 1~2^32-1 | 디스크 I/O 버퍼 수 | | `DISK_TABLESPACE_DIRECT_IO_WRITE` | 1 | 0~1 | 쓰기 Direct I/O 사용 여부. ZFS 등 미지원 파일시스템은 0으로 설정 | | `DISK_TABLESPACE_DIRECT_IO_READ` | 0 | 0~1 | 읽기 Direct I/O 사용 여부 | | `DISK_TABLESPACE_DIRECT_IO_FSYNC` | 0 | 0~1 | Direct I/O 시 fsync 사용 여부 | | `DISK_TABLESPACE_SYNCHRONOUS` | 1 | 0~3 | 동기화 정책. 0=OFF, 1=NORMAL, 2=FULL, 3=EXTRA | | `DISK_COLUMNAR_TABLE_COLUMN_PART_IO_INTERVAL_MIN_SEC` | 3 | 0~2^32-1 | 파티션 파일 디스크 반영 주기(초) | | `DISK_COLUMNAR_TABLE_COLUMN_PART_FLUSH_MODE` | 0 | 0~1 | 컬럼 파티션이 가득 찼을 때만 flush할지 여부 | | `DISK_COLUMNAR_TABLE_CHECKPOINT_INTERVAL_SEC` | 120 | 1~2^32-1 | 테이블 체크포인트 주기(초) | | `DISK_COLUMNAR_INDEX_CHECKPOINT_INTERVAL_SEC` | 120 | 1~2^32-1 | 인덱스 체크포인트 주기(초) | | `DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE` | 1 | 0~1 | LOG 시각 역전 시 1=직전 시각+1ns로 보정, 0=입력 거부 | | `DISK_COLUMNAR_TABLESPACE_MEMORY_SLOWDOWN_HIGH_LIMIT_PCT` | 80 | 0~100 | 메모리 사용량 임계값(%). 초과 시 입력 속도 저하 | | `DISK_COLUMNAR_TABLESPACE_MEMORY_SLOWDOWN_MSEC` | 1 | 0~2^32-1 | 임계값 초과 시 레코드당 대기 시간(ms) | LOG의 `DISK_COLUMNAR_TABLE_TIME_INVERSION_MODE=1`은 명시한 과거 시각을 그대로 저장한다는 뜻이 아닙니다. 직전 `_ARRIVAL_TIME`보다 작은 값은 보정됩니다. 같은 시각은 이 역전 조건에 해당하지 않으므로 모든 행의 시각이 고유해지는 것도 아닙니다. 이관 시에는 [시간 모델](/dbms/log-table-usage/arrival-time-model/)의 정렬·대상 상태를 함께 확인하세요. ## 인덱스 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `DEFAULT_LSM_MAX_LEVEL` | 2 | 0~3 | LSM 인덱스 기본 최대 레벨 | | `INDEX_BUILD_MAX_ROW_COUNT_PER_THREAD` | 100000 | 1~2^32-1 | 인덱스 빌드 시작 기준 미인덱싱 레코드 수 | | `INDEX_FLUSH_MAX_REQUEST_COUNT_PER_INDEX` | 3 | 1~2^32-1 | 인덱스당 최대 flush 요청 수 | | `INDEX_LEVEL_PARTITION_BUILD_MEMORY_HIGH_LIMIT_PCT` | 70 | 0~100 | LSM 인덱스 생성 최대 메모리 사용 비율(%) | | `DISK_COLUMNAR_INDEX_SHUTDOWN_BUILD_FINISH` | 0 | 0~1 | 종료 시 인덱스를 디스크에 모두 반영할지 여부 | | `DISK_COLUMNAR_INDEX_FDCACHE_COUNT` | 0 | 0~2^32-1 | 오픈 인덱스 파티션 파일 디스크립터 수 | | `DISK_COLUMNAR_TABLE_COLUMN_FDCACHE_COUNT` | 0 | 0~2^32-1 | 오픈 컬럼 파일 디스크립터 수 | | `DISK_COLUMNAR_TABLE_COLUMN_MINMAX_CACHE_SIZE` | 100MB | 0~2^64-1 | `_ARRIVAL_TIME` 컬럼 MINMAX 캐시 크기(바이트) | ## TAG 테이블 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `TAG_CACHE_ENABLE` | 31 | 0~31 | TAG 캐시 사용 범위(비트 OR). 0=사용 안 함, 1=map, 2=row, 4=data file, 8=varchar file, 16=delete vector | | `TAG_CACHE_MAX_MEMORY_SIZE` | 512MB | 32KB~2^64-1 | TAG 캐시 풀 1개당 최대 메모리(바이트) | | `TAG_CACHE_POOL_COUNT` | 1 | 1~128 | TAG 캐시 풀 개수. 전체 한도 = `TAG_CACHE_MAX_MEMORY_SIZE × TAG_CACHE_POOL_COUNT` | | `TAG_MEMORY_INDEX_TYPE` | 1 | 0~1 | 메모리 인덱스 유형. 0=RBTree, 1=BTree | | `TAG_MEMORY_INDEX_PANOUT` | 255 | 127~65536 | B-Tree 인덱스 차수. `TAG_MEMORY_INDEX_TYPE=1`일 때 적용 | | `TAGDATA_AUTO_META_INSERT` | 2 | 0~2 | TAG_NAME 없을 때 처리. 0=실패, 1=이름만 삽입, 2=메타데이터 포함 삽입 | | `TAG_TABLE_META_MAX_SIZE` | 524288000 | 1MB~2^32-1 | TAGDATA 테이블 메타데이터 최대 메모리(바이트) | | `TAG_PARTITION_COUNT` | 4 | 1~1024 | Tag 테이블 Key Value 파티션 수 | | `TAG_DATA_PART_SIZE` | 16MB | 1MB~1GB | Tag 데이터 파티션 크기(바이트) | | `ROLLUP_FETCH_COUNT_LIMIT` | 3000000 | 0~2^32-1 | 롤업 스레드 1회 패치 데이터 수. 0이면 무제한 | ## 보안 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `ENABLE_CASE_SENSITIVE_PASSWORD` | 0 | 0~1 | 비밀번호 대소문자 구분 여부. 0이면 대문자로 변환 | ## 세션 / 쿼리 설정 `TABLE_SCAN_DIRECTION`은 TAG 전용 설정이 아닙니다. LOG 등 스캔 방향을 사용하는 쿼리에도 영향을 줄 수 있으며, 최종 출력 정렬을 보장하는 설정도 아닙니다. 출력 순서는 ORDER BY로 지정하고 실제 접근 경로는 EXPLAIN으로 확인하세요. | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `TABLE_SCAN_DIRECTION` | 0 | -1~1 | -1=역방향, 0=테이블 유형 기본값, 1=정방향 | | `DDL_LOCK_TIMEOUT` | 0 | 0~1000000 | Standard Edition DDL 잠금 대기 시간(초). 0이면 즉시 오류 반환 | | `SHOW_HIDDEN_COLS` | 0 | 0~1 | `SELECT *`에서 `_ARRIVAL_TIME` 컬럼 표시 여부 | | `DURATION_BEGIN` | 0 | 0~2^32-1 | `DURATION` 미지정 SELECT의 기본 시작 오프셋(초) | | `DURATION_GAP` | 0 | 0~2^31-1 | `DURATION` 미지정 SELECT의 기본 기간(초) | | `LOOKUP_APPEND_UPDATE_ON_DUPKEY` | 0 | 0~1 | Lookup 테이블 Append 시 중복 키 처리. 0=실패, 1=UPDATE | | `LIN_HASH_BIT_SIZE` | 7 | 1~31 | 내부 선형 해시 초기 버킷 비트 수 | `machbase.conf`의 `DDL_LOCK_TIMEOUT`은 서버를 재시작한 뒤 새 세션에 복사됩니다. 현재 세션의 값은 `ALTER SESSION SET DDL_LOCK_TIMEOUT = seconds`로 변경하고 `V$SESSION`에서 확인합니다. Cluster Edition은 이 프로퍼티를 제공하지 않습니다. ## TRANSACTION 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `TRANSACTION_BUSY_TIMEOUT_MS` | 30000 | -1~2147483647 | 재시도 가능한 TRANSACTION 잠금 충돌의 대기 시간(ms). -1은 취소·해제까지 대기, 0은 즉시 반환 | | `TRANSACTION_SYNCHRONOUS` | 2 | 1~2 | TRANSACTION 테이블 트랜잭션 내구성 수준. 1=NORMAL, 2=FULL | | `TRANSACTION_JOURNAL_MODE` | 4 | 0~4 | TRANSACTION 저널 모드. 0=DELETE, 4=WAL | 새 세션은 서버의 TRANSACTION_BUSY_TIMEOUT_MS를 복사하며 현재 연결은 ALTER SESSION으로 변경할 수 있습니다. 이 값은 모든 busy 오류에 대한 최소 대기 시간을 보장하지 않습니다. WAL에서 오래된 읽기 스냅샷을 쓰기로 전환할 때의 충돌은 -1이어도 즉시 반환될 수 있습니다. 이 경우 같은 문장을 반복하지 말고 ROLLBACK 후 새 트랜잭션에서 읽기와 판단부터 다시 수행하세요. [두 연결 실습](/dbms/rdb-table-usage/locking-conflict-timeout/)에서 일시적인 쓰기 잠금과 스냅샷 충돌을 비교할 수 있습니다. ## 로그 / 진단 설정 | 프로퍼티 | 기본값 | 범위 | 설명 | |----------|--------|------|------| | `TRACE_LOG_LEVEL` | 277 | 0~2^32-1 | 트레이스 로그 상세 수준. 값이 높을수록 상세 | | `TRACE_LOGFILE_PATH` | ?/trc | - | 트레이스 로그 파일 저장 경로 | | `TRACE_LOGFILE_SIZE` | 10MB | 1MB~2^32-1 | 트레이스 로그 파일 최대 크기(바이트) | | `TRACE_LOGFILE_COUNT` | 1000 | 1~2^32-1 | 트레이스 로그 파일 최대 개수 | | `DUMP_TRACE_INFO` | 300 | 0~2^32-1 | DBMS 상태를 trc에 기록하는 주기(초). 0이면 비활성 | | `DUMP_APPEND_ERROR` | 0 | 0~1 | Append API 오류 시 trc에 기록 여부. 테스트 목적으로만 사용 권장 | | `FEEDBACK_APPEND_ERROR` | 1 | 0~1 | Append 오류 데이터를 클라이언트에 전송할지 여부 | | `GEN_CORE_FILE` | 1 | 0~1 | 비정상 종료 시 core 파일 생성 여부 | | `GEN_CALLSTACK_FOR_ABORT_ERROR` | 0 | 0~1 | 비정상 종료 시 call stack 기록 여부 | ## 프로퍼티 조회 예시 ```sql -- 현재 적용된 프로퍼티 값 전체 조회 SELECT name, value, type FROM v$property ORDER BY name; -- 특정 프로퍼티 상세 조회 SELECT name, value, min, max FROM v$property WHERE name = 'MAX_SESSION_COUNT'; ``` 동적 변경 가능한 프로퍼티는 서버 재시작 없이 `ALTER SYSTEM SET`으로 변경할 수 있습니다. ```sql ALTER SYSTEM SET TRACE_LOG_LEVEL = 3; ALTER SYSTEM SET SESSION_QUERY_TIMEOUT_SEC = 30; ``` --- title: "16.2.2 클러스터 설정 프로퍼티 사전" url: https://docs.machbase.com/kr/dbms/reference/configuration/configuration-2/ language: kr kind: page --- # 16.2.2 클러스터 설정 프로퍼티 사전 Cluster Edition에서는 `$MACHBASE_COORDINATOR_HOME/conf/`, `$MACHBASE_BROKER_HOME/conf/`, `$MACHBASE_WAREHOUSE_HOME/conf/` 각 노드의 설정 파일을 통해 클러스터 동작을 제어합니다. 이 페이지는 운영 시 자주 확인하는 주요 클러스터 프로퍼티를 정리합니다. ## Coordinator 설정 Coordinator는 클러스터 전체 메타데이터와 노드 상태를 관리합니다. | 프로퍼티 | 기본값 | 설명 | |----------|--------|------| | `CLUSTER_LINK_HOST` | - | Coordinator가 바인드할 IP 주소 | | `CLUSTER_LINK_PORT_NO` | 3868 | 클러스터 내부 통신 포트 | | `CLUSTER_LINK_THREAD_COUNT` | 16 | 클러스터 링크 처리 스레드 수 | | `CLUSTER_LINK_MAX_LISTEN` | 512 | 클러스터 링크 최대 listen 연결 수 | | `CLUSTER_LINK_MAX_POLL` | 4096 | 클러스터 링크 최대 poll 이벤트 수 | | `CLUSTER_LINK_BUFFER_SIZE` | 33554432 | 클러스터 링크 버퍼 크기(바이트). 기본 32MB | | `HTTP_ADMIN_PORT` | 5779 | Coordinator/Deployer 관리 REST 포트 | | `HTTP_THREAD_COUNT` | 2 | 관리 REST 요청 처리 스레드 수 | `HTTP_ADMIN_PORT`는 환경 변수 `MACHBASE_HTTP_ADMIN_PORT`로도 지정할 수 있습니다. 이 포트는 SQL 조회나 데이터 입력을 위한 포트가 아니라 클러스터 관리 요청에만 사용합니다. ## 클러스터 링크 타임아웃 설정 모든 값의 단위는 마이크로초(μs)입니다. | 프로퍼티 | 기본값(μs) | 설명 | |----------|-----------|------| | `CLUSTER_LINK_ACCEPT_TIMEOUT` | 5000000 | accept 타임아웃 (5초) | | `CLUSTER_LINK_CHECK_INTERVAL` | 1000000 | 연결 상태 확인 주기 (1초) | | `CLUSTER_LINK_CONNECT_RETRY_TIMEOUT` | 60000000 | 연결 재시도 최대 시간 (60초) | | `CLUSTER_LINK_CONNECT_TIMEOUT` | 5000000 | 연결 타임아웃 (5초) | | `CLUSTER_LINK_HANDSHAKE_TIMEOUT` | 5000000 | 핸드셰이크 타임아웃 (5초) | | `CLUSTER_LINK_RECEIVE_TIMEOUT` | 30000000 | 수신 타임아웃 (30초) | | `CLUSTER_LINK_SEND_TIMEOUT` | 30000000 | 송신 타임아웃 (30초) | | `CLUSTER_LINK_REQUEST_TIMEOUT` | 60000000 | 요청 타임아웃 (60초) | | `CLUSTER_LINK_SESSION_TIMEOUT` | 3600000000 | 세션 타임아웃 (1시간) | | `CLUSTER_LINK_LONG_WAIT_INTERVAL` | 1000000 | 장시간 대기 간격 (1초) | | `CLUSTER_LINK_LONG_TERM_CALLBACK_INTERVAL` | 1000000 | 장기 콜백 주기 (1초) | ## Broker 설정 Broker는 클라이언트의 쿼리를 받아 Warehouse로 분산 처리합니다. Broker의 `machbase.conf`에는 서버 공통 설정과 클러스터 설정을 지정합니다. 지원 프로퍼티와 기본값은 Edition과 노드 역할에 따라 다르므로 Standard Edition의 설정 파일을 그대로 적용하지 마십시오. | 프로퍼티 | 기본값 | 설명 | |----------|--------|------| | `PORT_NO` | 5656 | 클라이언트 연결 포트 | | `QUERY_PARALLEL_FACTOR` | 4 | 병렬 쿼리 처리 스레드 수 (Cluster 기본값) | | `CLUSTER_LINK_HOST` | - | Broker가 클러스터 통신에 바인드할 IP | | `CLUSTER_LINK_PORT_NO` | - | Broker 클러스터 통신 포트 | ## Warehouse 설정 Warehouse는 실제 데이터를 저장하고 처리하는 노드입니다. 저장 관련 설정과 함께 다음 클러스터 설정을 사용합니다. `DDL_LOCK_TIMEOUT`처럼 Standard Edition 전용인 프로퍼티는 Cluster Edition에서 지원하지 않습니다. | 프로퍼티 | 기본값 | 설명 | |----------|--------|------| | `PORT_NO` | 5656 | Warehouse 서비스 포트 | | `CLUSTER_LINK_HOST` | - | Warehouse가 클러스터 통신에 바인드할 IP | | `CLUSTER_LINK_PORT_NO` | - | Warehouse 클러스터 통신 포트 | | `DBS_PATH` | ?/dbs | Warehouse 데이터 파일 저장 경로 | ## 클러스터 상태 확인 클러스터 설정값은 `machcoordinatoradmin --configure` 명령으로 출력할 수 있습니다. ```bash machcoordinatoradmin --configure ``` 특정 설정값만 확인하려면 `--configuration=name` 옵션을 사용합니다. ```bash machcoordinatoradmin --configuration=decision ``` ## 클러스터 포트 구성 예시 단일 호스트에 클러스터를 구성할 때의 포트 할당 예시입니다. | 노드 | 서비스 포트 | HTTP 포트 | 클러스터 링크 포트 | |------|------------|-----------|------------------| | Coordinator | - | 5102 | 5101 | | Deployer | - | - | 5201 | | Broker | 5757 | 5302 | 5301 | | Warehouse-A1 | 5400 | 5402 | 5401 | | Warehouse-A2 | 5500 | 5502 | 5501 | --- title: "16.2.3 PVO Cache 프로퍼티 사전" url: https://docs.machbase.com/kr/dbms/reference/configuration/pvo-cache/ language: kr kind: page --- # 16.2.3 PVO Cache 프로퍼티 사전 PVO Statement Cache는 SQL 파싱·검증·최적화 결과와 실행 계획을 재사용하여 반복 SQL의 처리 비용을 줄입니다. 공개 근거가 없는 약어 확장을 문서에서 정의하지 않습니다. Standard Edition에서만 동작합니다. ## 프로퍼티 목록 | 프로퍼티 | 기본값 | 범위 | 동적 변경 | 설명 | |----------|--------|------|----------|------| | `PVO_CACHE_ENABLE` | 1 | 0~1 | 가능 | PVO Cache 활성화 여부. 0=비활성, 1=활성 | | `PVO_CACHE_MAX_MEMORY_SIZE` | 268435456 | 32768~2^64-1 | 가능 | PVO Cache 전체 최대 메모리(바이트). 기본 256MB | | `PVO_CACHE_SHARD_COUNT` | 16 | 1~256 | 불가 | Cache 샤드 수. 변경 시 서버 재시작 필요 | | `PVO_CACHE_MAX_SQL_ENTRIES` | 0 | 0~2^64-1 | 가능 | Cache에 보관할 최대 SQL 엔트리 수. 0=무제한 | | `PVO_CACHE_MAX_PLANS_PER_SQL` | 512 | 1~512 | 가능 | SQL 1개당 최대 플랜(핸들) 수 | ## 프로퍼티 상세 ### PVO_CACHE_ENABLE PVO Statement Cache 사용 여부를 설정합니다. ``` PVO_CACHE_ENABLE = 1 ``` ### PVO_CACHE_MAX_MEMORY_SIZE PVO Cache 전체가 사용할 최대 메모리 크기(바이트)입니다. 설정된 값은 `PVO_CACHE_SHARD_COUNT`에 따라 균등 분배됩니다. ``` PVO_CACHE_MAX_MEMORY_SIZE = 536870912 # 512MB ``` ### PVO_CACHE_SHARD_COUNT Cache 내부 샤드 수입니다. 초기화 시점에만 적용되므로 변경 시 서버 재시작이 필요합니다. 동시 접속이 많은 환경에서 샤드 수를 늘리면 잠금 경합을 줄일 수 있습니다. ``` PVO_CACHE_SHARD_COUNT = 32 ``` ### PVO_CACHE_MAX_SQL_ENTRIES PVO Cache에 보관할 수 있는 SQL 엔트리의 최대 개수입니다. 0은 무제한을 의미합니다. 값이 설정된 경우 샤드 수에 따라 분배되어 적용됩니다. ``` PVO_CACHE_MAX_SQL_ENTRIES = 10000 ``` ### PVO_CACHE_MAX_PLANS_PER_SQL 하나의 SQL에 대해 보관할 수 있는 최대 플랜 수입니다. 동일 SQL이라도 바인드 파라미터 타입에 따라 다른 플랜이 생성될 수 있습니다. ``` PVO_CACHE_MAX_PLANS_PER_SQL = 256 ``` ## 동적 변경 서버 재시작 없이 변경 가능한 프로퍼티는 `ALTER SYSTEM SET`으로 적용합니다. ```sql -- PVO Cache 활성화 ALTER SYSTEM SET PVO_CACHE_ENABLE = 1; -- 최대 메모리 512MB로 변경 ALTER SYSTEM SET PVO_CACHE_MAX_MEMORY_SIZE = 536870912; -- SQL 엔트리 수 제한 ALTER SYSTEM SET PVO_CACHE_MAX_SQL_ENTRIES = 5000; ``` ## 캐시 초기화 PVO Cache를 강제로 초기화하려면 다음 명령을 사용합니다. ```sql ALTER SYSTEM FLUSH PVO_CACHE; ``` ## 캐시 상태 확인 ```sql SELECT name, value FROM v$property WHERE name LIKE 'PVO_CACHE%' ORDER BY name; ``` --- title: "16.2.4 Timezone 설정 사전" url: https://docs.machbase.com/kr/dbms/reference/configuration/configuration-timezone/ language: kr kind: page --- # 16.2.4 Timezone 설정 사전 Machbase는 클라이언트 접속 옵션으로 타임존을 지정할 수 있습니다. datetime 값은 내부적으로 나노초 값으로 처리되며, 타임존 옵션은 문자열 입출력 변환에 영향을 줍니다. ## 지원 타임존 표현 형식 | 형식 | 예시 | 설명 | |------|------|------| | UTC 오프셋 | `+0900`, `-0530` | UTC 기준 시/분 오프셋 | 8.5 원본 매뉴얼과 현재 `machsql`, `machloader` 도움말 기준으로 문서화된 형식은 `+-HHMM` 오프셋입니다. `Asia/Seoul` 같은 IANA 지역명 또는 `DEFAULT_TIMEZONE` 서버 프로퍼티는 현재 배포 샘플에서 확인되지 않으므로 이 장의 지원 형식으로 다루지 않습니다. ## 클라이언트별 타임존 설정 ### machsql `-z` 옵션으로 세션 타임존을 지정합니다. ```bash machsql -s 127.0.0.1 -u SYS -p MANAGER -z +0900 ``` ### machloader `-z` 옵션으로 가져오기/내보내기 시 datetime 변환 타임존을 지정합니다. ```bash machloader -i -d data.csv -t table_name -z +0900 machloader -o -d data.csv -t table_name -z +0900 ``` ### JDBC JDBC에서 타임존을 지정해야 하는 경우 드라이버 문서의 연결 옵션을 확인합니다. 이 페이지에서는 `machsql`/`machloader`의 `+-HHMM` 오프셋 형식을 기준으로 설명합니다. ## 타임존 우선순위 클라이언트에서 `-z +0900`처럼 타임존을 명시하면 해당 세션의 입출력 변환에 적용됩니다. ## 타임존 변환 예시 `+0900` 타임존을 사용하는 경우 다음과 같이 접속합니다. ```bash machsql -s 127.0.0.1 -u SYS -p MANAGER -z +0900 ``` --- title: "16.3 시스템 카탈로그 레퍼런스" url: https://docs.machbase.com/kr/dbms/reference/system-catalog/ language: kr kind: section --- # 16.3 시스템 카탈로그 레퍼런스 시스템 카탈로그는 Machbase 서버의 내부 메타데이터와 실시간 운영 상태를 SQL로 조회할 수 있는 읽기 전용 테이블 집합입니다. 두 가지 유형으로 구성됩니다. | 유형 | 접두사 | 설명 | |------|--------|------| | 메타 테이블 | `M$` | 테이블 정의, 컬럼, 인덱스, 사용자 등 스키마 정보 | | 가상 테이블(동적 뷰) | `V$` | 세션, 실행 쿼리, 메모리, 스토리지 등 실시간 운영 상태 | ## 공통 사항 - 모든 시스템 카탈로그 테이블은 **읽기 전용**입니다. `INSERT`, `UPDATE`, `DELETE`는 오류를 반환합니다. - `M$` 테이블은 DDL 명령(`CREATE`, `ALTER`, `DROP`) 실행 결과를 자동으로 반영합니다. - `V$` 테이블은 서버 상태를 실시간으로 반영하며 쿼리할 때마다 최신 값을 반환합니다. - 전체 목록은 다음 쿼리로 확인합니다. ```sql -- 메타 테이블 전체 목록 SELECT name FROM m$tables ORDER BY name; -- 가상 테이블 전체 목록 SELECT name FROM v$tables WHERE name LIKE 'V$%' ORDER BY name; ``` ## 하위 섹션 | 섹션 | 설명 | |------|------| | [메타 테이블 사전](./meta/) | M$SYS_TABLES, M$SYS_COLUMNS 등 스키마 메타 테이블 상세 | | [가상 테이블 사전](./virtual/) | V$SESSION, V$STMT, V$PROPERTY 등 동적 뷰 상세 | | [TAG별 통계 뷰](/dbms/tag-table-usage/query-analysis/#tag-stat-axis-schema) | `V$
_STAT`의 시간·거리축별 스키마와 조회 방법 | | [V$ROLLUP 사전](./vrollup/) | Rollup 작업 상태 뷰 컬럼 상세 | | [V$STORAGE_MOUNT_* 사전](./vstorage-mount/) | 마운트된 백업 데이터베이스 뷰 컬럼 상세 | | [전체 가상 테이블 레퍼런스](./virtual-table-full/) | 8.5 원본 가상 테이블 레퍼런스의 전체 항목 | --- title: "16.3.1 메타 테이블 사전" url: https://docs.machbase.com/kr/dbms/reference/system-catalog/meta/ language: kr kind: page --- # 16.3.1 메타 테이블 사전 메타 테이블은 `M$` 접두사를 가지며 Machbase 스키마 정보(테이블 정의, 컬럼, 인덱스, 사용자 등)를 조회합니다. DDL 명령 실행 결과가 자동으로 반영되며 읽기 전용입니다. 8.7.0 Standard Edition의 다중 데이터베이스에서는 catalog-local metadata를 조인할 때 `DATABASE_ID`, `TABLESPACE_ID`와 parent object ID를 함께 사용해야 합니다. 논리 `DATABASE_ID`와 물리 `TABLESPACE_ID`는 서로 대체할 수 없습니다. database 운영 경계는 [다중 데이터베이스 운영 가이드](/dbms/operations-configuration-recovery/multi-database/)를 참고하십시오. ## 메타 테이블 목록 | 테이블 이름 | 설명 | |------------|------| | `M$SYS_TABLES` | 사용자가 생성한 테이블 목록과 타입 | | `M$SYS_TABLE_PROPERTY` | 테이블에 적용된 속성 정보 | | `M$SYS_COLUMNS` | 테이블 컬럼 정의 (타입, 길이 등) | | `M$SYS_INDEXES` | 인덱스 정의 | | `M$SYS_INDEX_COLUMNS` | 인덱스를 구성하는 컬럼 정보 | | `M$SYS_TABLESPACES` | 테이블스페이스 목록 | | `M$SYS_TABLESPACE_DISKS` | 테이블스페이스가 사용하는 디스크 경로 | | `M$SYS_USERS` | 등록된 사용자 목록 | | `M$SYS_VIEWS` | 뷰 정의 SQL 텍스트 | | `M$SYS_USER_ACCESS` | 테이블별 사용자 권한 | | `M$RETENTION` | Retention Policy 정보 | | `M$TABLES` | M$ 메타 테이블 자체 목록 | | `M$COLUMNS` | M$ 메타 테이블의 컬럼 목록 | ## M$SYS_TABLES 사용자가 생성한 테이블의 목록과 타입을 조회합니다. | 컬럼명 | 타입 | 설명 | |--------|------|------| | `NAME` | VARCHAR | 테이블 이름 | | `TYPE` | INTEGER | 테이블 타입 | | `ID` | LONG | 테이블 식별자 | | `DATABASE_ID` | LONG | 논리 데이터베이스 식별자 | | `TABLESPACE_ID` | LONG | 물리 tablespace 식별자 | | `USER_ID` | INTEGER | 테이블 생성 사용자 식별자 | | `COLCOUNT` | INTEGER | 컬럼 수 | | `FLAG` | INTEGER | 서브 타입 (1: Tag Data, 2: Rollup, 4: Tag Meta, 8: Tag Stat) | **TYPE 값 의미:** | 값 | 테이블 타입 | |----|------------| | `0` | Log 테이블 | | `1` | Fixed 테이블 | | `3` | Volatile 테이블 | | `4` | Lookup 테이블 | | `5` | Key Value 테이블 | | `6` | Tag 테이블 | | `7` | View | | `8` | TRANSACTION 테이블 | ## M$SYS_COLUMNS 테이블 컬럼의 정의를 조회합니다. | 컬럼명 | 타입 | 설명 | |--------|------|------| | `NAME` | VARCHAR | 컬럼명 | | `TYPE` | INTEGER | 컬럼 데이터 타입 | | `TABLE_ID` | LONG | 소속 테이블 식별자 | | `DATABASE_ID` | LONG | 논리 데이터베이스 식별자 | | `TABLESPACE_ID` | LONG | 물리 tablespace 식별자 | | `LENGTH` | INTEGER | 컬럼 최대 길이 | | `PART_PAGE_COUNT` | INTEGER | 파티션당 페이지 수 | | `MINMAX_CACHE_SIZE` | LONG | MIN-MAX 캐시 크기 | ## M$SYS_INDEXES 인덱스 정의를 조회합니다. | 컬럼명 | 타입 | 설명 | |--------|------|------| | `NAME` | VARCHAR | 인덱스 이름 | | `TYPE` | INTEGER | 인덱스 타입 | | `TABLE_ID` | LONG | 소속 테이블 식별자 | | `DATABASE_ID` | LONG | 논리 데이터베이스 식별자 | | `TABLESPACE_ID` | LONG | 물리 tablespace 식별자 | | `COLCOUNT` | INTEGER | 인덱스 컬럼 수 | | `MAX_LEVEL` | INTEGER | 최대 LSM 레벨 | ## M$SYS_USERS 등록된 사용자 목록을 조회합니다. | 컬럼명 | 타입 | 설명 | |--------|------|------| | `USER_ID` | INTEGER | 사용자 식별자 | | `NAME` | VARCHAR | 사용자 이름 | | `PWD_POLICY_LEVEL` | INTEGER | 비밀번호 정책 수준 | | `VALID_BEFORE` | VARCHAR | 계정 유효 기간 | ## M$RETENTION Retention Policy 정보를 조회합니다. | 컬럼명 | 타입 | 설명 | |--------|------|------| | `POLICY_NAME` | VARCHAR | 정책 이름 | | `DURATION` | LONG | 보존 기간 (초) | | `INTERVAL` | LONG | 삭제 실행 주기 (초) | ## SQL 예제 ```sql -- 전체 테이블 목록 (타입 포함) SELECT name, type, colcount FROM m$sys_tables ORDER BY name; -- Tag 테이블만 조회 (type = 6) SELECT name FROM m$sys_tables WHERE type = 6; -- 특정 테이블의 컬럼 목록 SELECT c.name AS col_name, c.type AS col_type, c.length FROM m$sys_columns c JOIN m$sys_tables t ON c.database_id = t.database_id AND c.tablespace_id = t.tablespace_id AND c.table_id = t.id WHERE t.name = 'SENSOR_TAG' ORDER BY c.id; -- 특정 테이블의 인덱스 목록 SELECT i.name AS idx_name, i.type AS idx_type, i.colcount FROM m$sys_indexes i JOIN m$sys_tables t ON i.database_id = t.database_id AND i.tablespace_id = t.tablespace_id AND i.table_id = t.id WHERE t.name = 'SENSOR_TAG'; -- 인덱스를 구성하는 컬럼 확인 SELECT ic.name AS col_name, ic.index_type FROM m$sys_index_columns ic JOIN m$sys_indexes i ON ic.index_id = i.id JOIN m$sys_tables t ON i.table_id = t.id WHERE t.name = 'SENSOR_TAG'; -- 테이블스페이스 디스크 경로 확인 SELECT ts.name AS tbs_name, d.path, d.io_thread_count FROM m$sys_tablespace_disks d JOIN m$sys_tablespaces ts ON d.tablespace_id = ts.id; -- 사용자 목록 조회 SELECT user_id, name, pwd_policy_level, valid_before FROM m$sys_users; -- Retention Policy 목록 SELECT * FROM m$retention; ``` > 메타 테이블은 읽기 전용입니다. `INSERT`, `UPDATE`, `DELETE` 명령은 오류를 반환합니다. 스키마 변경은 `CREATE TABLE`, `ALTER TABLE`, `DROP TABLE` 등의 DDL 명령을 사용합니다. --- title: "16.3.2 가상 테이블 사전" url: https://docs.machbase.com/kr/dbms/reference/system-catalog/virtual/ language: kr kind: page --- # 16.3.2 가상 테이블 사전 가상 테이블(동적 뷰)은 `V$` 접두사를 가지며 Machbase 서버의 실시간 운영 상태를 테이블 형태로 표현합니다. 읽기 전용이며 쿼리할 때마다 최신 상태를 반환합니다. ## 가상 테이블 목록 | 카테고리 | 테이블 이름 | 설명 | |---------|------------|------| | 세션/시스템 | `V$VERSION` | 서버 버전 정보 | | 세션/시스템 | `V$SESSION` | 현재 접속 세션 목록 | | 데이터베이스 | `V$DATABASES` | active/mounted 데이터베이스 상태 | | 데이터베이스 | `V$DATABASE_OPERATIONS` | database lifecycle 작업 이력 | | 세션/시스템 | `V$STMT` | 실행 중인 SQL 문장 | | 세션/시스템 | `V$PROPERTY` | 현재 서버 설정값 | | 세션/시스템 | `V$SYSMEM` | 시스템 메모리 사용량 | | 세션/시스템 | `V$SYSSTAT` | 시스템 통계 정보 | | 세션/시스템 | `V$SYSTIME` | 시스템 시간 통계 | | 스토리지 | `V$STORAGE` | 스토리지 파일 크기 요약 | | 스토리지 | `V$STORAGE_USAGE` | 디스크 사용량과 한계 비율 | | 스토리지 | `V$STORAGE_TABLES` | 테이블별 스토리지 사용량 | | 스토리지 | `V$STORAGE_MOUNT_DATABASES` | 마운트된 백업 데이터베이스 | | 태그 Rollup | `V$ROLLUP` | Rollup 작업 상태 | | TAG 테이블 | `V$
_STAT` | 테이블별 tag·축 통계. 실제 이름은 TAG 테이블 이름에 따라 생성 | | 라이선스 | `V$LICENSE_INFO` | 라이선스 정보 | | 잠금 | `V$MUTEX` | 잠금 현황 | `V$
_STAT`은 TAG 테이블마다 동적으로 생성되므로 고정된 전역 가상 테이블 목록과 구분합니다. 시간축·거리축별 컬럼 이름과 타입은 [TAG별 통계 뷰](/dbms/tag-table-usage/query-analysis/#tag-stat-axis-schema)를 참고하십시오. ## V$VERSION 서버 버전 정보를 조회합니다. | 컬럼 이름 | 설명 | |----------|------| | `BINARY_SIGNATURE` | 서버 버전 문자열 | ```sql SELECT binary_signature FROM v$version; ``` ## V$DATABASES 논리 데이터베이스와 mounted database의 상태를 조회합니다. `DATABASE_ID`는 논리 catalog 식별자이며 `TABLESPACE_ID`와 다릅니다. | 컬럼 | 설명 | |------|------| | `DATABASE_ID` | 논리 데이터베이스 식별자 | | `SOURCE_DATABASE_ID` | mounted backup의 원본 database 식별자 | | `NAME` | database 이름 또는 mounted alias | | `KIND` | `ACTIVE` 또는 `MOUNTED` | | `ACCESS_MODE` | `READ_WRITE` 또는 `READ_ONLY` | | `CAN_USE` | `USE`로 선택할 수 있는지 여부 | | `STATE` | lifecycle 상태 | | `IS_DEFAULT` | 기본 `MACHBASEDB` 여부 | ```sql SELECT database_id, name, kind, access_mode, can_use, state, is_default FROM v$databases ORDER BY database_id; ``` ## V$DATABASE_OPERATIONS `CREATE`, `ALTER`, `DROP`, `BACKUP`, `RESTORE`, `MOUNT`, `UMOUNT` 작업의 상태와 오류를 조회합니다. `FAILED_NEEDS_ACTION` 상태는 실제 `V$DATABASES` 상태와 server log를 함께 확인해야 합니다. | 컬럼 | 설명 | |------|------| | `OPERATION_ID` | operation 식별자 | | `DATABASE_ID` | 대상 logical database 식별자 | | `DATABASE_NAME` | 대상 database 이름 | | `STATE` | operation 상태 | | `LAST_ERROR` | 실패 원인 | | `CREATED_AT` | 생성 시각 | | `UPDATED_AT` | 마지막 변경 시각 | ```sql SELECT operation_id, database_name, state, last_error FROM v$database_operations ORDER BY operation_id DESC; ``` ## V$SESSION 현재 접속 세션의 목록과 상태를 표시합니다. | 컬럼 이름 | 설명 | |----------|------| | `ID` | 세션 식별자 | | `CLOSED` | 연결이 닫혀있는지 여부 (0: 활성) | | `USER_ID` | 사용자 식별자 | | `LOGIN_TIME` | 접속 시각 | | `CLIENT_TYPE` | 접속 클라이언트 타입 | | `USER_NAME` | 사용자 이름 | | `USER_IP` | 사용자 IP 주소 | | `SQL_LOGGING` | 해당 세션의 Trace Log 기록 여부 | | `IDLE_TIMEOUT` | 유휴 상태 세션 종료 시간 (초) | | `QUERY_TIMEOUT` | 쿼리 응답 대기 시간 | ```sql -- 현재 활성 세션 목록 SELECT id, user_name, user_ip, client_type, login_time FROM v$session WHERE closed = 0 ORDER BY login_time; ``` ## V$STMT 현재 실행 중이거나 대기 중인 SQL 문의 정보를 표시합니다. | 컬럼 이름 | 설명 | |----------|------| | `ID` | 쿼리 식별자 | | `SESS_ID` | 쿼리를 실행한 세션 식별자 | | `STATE` | 쿼리 상태 | | `RECORD_SIZE` | SELECT 수행 시 결과 레코드 크기 | | `QUERY` | 쿼리 구문 | ```sql -- 실행 중인 쿼리 확인 SELECT id, sess_id, state, query FROM v$stmt WHERE state LIKE 'Execute in progress%' OR state LIKE 'Fetch in progress%' OR state LIKE 'Append in progress%'; ``` ## V$PROPERTY 서버에 설정된 모든 프로퍼티 값을 조회합니다. | 컬럼 이름 | 설명 | |----------|------| | `NAME` | 프로퍼티 이름 | | `VALUE` | 현재 설정값 | | `TYPE` | 데이터 타입 | | `DEFLT` | 기본값 | | `MIN` | 최솟값 | | `MAX` | 최댓값 | ```sql -- 특정 설정값 확인 SELECT name, value, deflt FROM v$property WHERE name IN ('PORT_NO', 'TRACE_LOG_LEVEL', 'MAX_SESSION_COUNT'); -- 기본값과 다른 설정만 조회 SELECT name, value, deflt FROM v$property WHERE value != deflt ORDER BY name; ``` ## V$STORAGE_USAGE 저장 시스템의 디스크 사용 현황을 표시합니다. | 컬럼 이름 | 설명 | |----------|------| | `TOTAL_SPACE` | 데이터 디렉터리 스토리지의 총 용량 | | `USED_SPACE` | 사용 중인 용량 | | `USED_RATIO` | 사용량 비율 (%) | | `RATIO_CAP` | 사용량 한계 (초과 시 데이터 입력 중단) | ```sql SELECT total_space, used_space, used_ratio, ratio_cap FROM v$storage_usage; ``` ## V$SYSMEM 시스템 메모리 사용량을 조회합니다. | 컬럼 이름 | 설명 | |----------|------| | `ID` | 메모리 매니저 식별자 | | `NAME` | 메모리 매니저 이름 | | `USAGE` | 현재 사용량 | | `MAX_USAGE` | 기록된 최대 사용량 | ```sql SELECT name, usage, max_usage FROM v$sysmem ORDER BY usage DESC; ``` ## V$LICENSE_INFO 서버 라이선스 정보를 조회합니다. | 컬럼 이름 | 설명 | |----------|------| | `ID` | 라이선스 ID | | `ISSUE_DATE` | 발행일 | | `TYPE` | 라이선스 유형 | | `CUSTOMER` | 고객사 이름 | | `PROJECT` | 프로젝트 이름 | | `INSTALL_DATE` | 설치일 | | `VIOLATE_STATUS` | 라이선스 위반 상태 | | `VIOLATE_MSG` | 라이선스 위반 메시지 | ```sql SELECT id, type, customer, issue_date, install_date, violate_status, violate_msg FROM v$license_info; ``` ## 전체 가상 테이블 목록 확인 ```sql -- 현재 서버에서 조회 가능한 V$ 가상 테이블 전체 목록 SELECT name FROM v$tables WHERE name LIKE 'V$%' ORDER BY name; ``` > 가상 테이블은 읽기 전용입니다. 클러스터 에디션에서만 제공되는 테이블(V$NODE_STATUS, V$REPLICATION 등)은 Standard 에디션에서 조회되지 않습니다. --- title: "16.3.3 V$ROLLUP 사전" url: https://docs.machbase.com/kr/dbms/reference/system-catalog/vrollup/ language: kr kind: page --- # 16.3.3 V$ROLLUP 사전 `V$ROLLUP`은 Tag 데이터의 Rollup 작업 상태를 실시간으로 표시하는 가상 테이블입니다. Rollup이 정상 작동하는지 확인하거나 실행 주기와 소요 시간을 모니터링할 때 사용합니다. ## 컬럼 상세 | 컬럼 이름 | 타입 | 설명 | |----------|------|------| | `ID` | INTEGER | Rollup 작업 ID | | `ROLLUP_NAME` | VARCHAR | Rollup 작업 이름 | | `ROLLUP_TABLE` | VARCHAR | Rollup 결과가 저장되는 테이블 이름 | | `SOURCE_TABLE` | VARCHAR | 집계 대상 원본 TAG 테이블 이름 | | `COLUMN_NAME` | VARCHAR | 집계 대상 컬럼 이름 | | `INTERVAL_TIME` | ULONG | 데이터 집계 간격 (밀리초) | | `WAKEUP_INTERVAL` | ULONG | Rollup 작업의 실행 주기 (밀리초) | | `LAST_WAKEUP_TIME` | DATETIME | 최근 실행 시각 | | `ENABLED` | INTEGER | 활성화 여부 (1: 활성, 0: 비활성) | | `LAST_ELAPSED_MSEC` | DOUBLE | 직전 실행에 걸린 시간 (밀리초) | | `RUN_STATE` | VARCHAR | 스레드 상태 (I: 초기화, S: 대기, R: 실행중) | ## RUN_STATE 값 | 값 | 설명 | |----|------| | `I` | 초기화(Initializing) 중 | | `S` | 다음 실행을 대기(Sleeping) 중 | | `R` | 현재 실행(Running) 중 | ## SQL 예제 ```sql -- Rollup 작업 전체 상태 확인 SELECT rollup_name, rollup_table, source_table, column_name, interval_time, wakeup_interval, enabled, last_elapsed_msec, run_state FROM v$rollup ORDER BY rollup_table; -- 마지막 실행 시각 확인 SELECT rollup_name, rollup_table, last_wakeup_time, last_elapsed_msec, run_state FROM v$rollup; -- 비활성화된 Rollup 확인 SELECT rollup_name, rollup_table, source_table, enabled FROM v$rollup WHERE enabled = 0; -- 실행 시간이 오래 걸리는 Rollup 확인 SELECT rollup_name, rollup_table, wakeup_interval, last_elapsed_msec, last_elapsed_msec * 100.0 / wakeup_interval AS usage_ratio FROM v$rollup WHERE last_elapsed_msec > 0 AND wakeup_interval > 0 ORDER BY last_elapsed_msec DESC; ``` ## 주의 사항 - `INTERVAL_TIME`은 데이터 집계 간격이며, `WAKEUP_INTERVAL`은 Rollup 작업의 실행 주기입니다. - `LAST_ELAPSED_MSEC`가 `WAKEUP_INTERVAL`보다 크면 직전 실행에 걸린 시간이 설정된 실행 주기를 초과한 것입니다. 집계 대상 데이터 양과 실행 주기를 점검하십시오. 예제의 `usage_ratio`는 실행 주기 대비 직전 실행 소요시간의 비율(%)입니다. - `ENABLED = 0`이면 Rollup이 비활성화된 상태입니다. `ALTER ROLLUP rollup_name START` 명령으로 다시 활성화합니다. `rollup_name`에는 조회한 `ROLLUP_NAME` 값을 지정합니다. - Rollup 생성과 관리는 [TAG 테이블과 Rollup](/dbms/tag-rollup-usage/overview-use-criteria/#rollup) 섹션을 참고하십시오. --- title: "16.3.4 V$STORAGE_MOUNT_DATABASES 사전" url: https://docs.machbase.com/kr/dbms/reference/system-catalog/vstorage-mount/ language: kr kind: page --- # 16.3.4 V$STORAGE_MOUNT_DATABASES 사전 `V$STORAGE_MOUNT_DATABASES`는 현재 인스턴스에 읽기 전용으로 연결된 backup database를 표시합니다. ## 컬럼 | 컬럼 | 타입 | 설명 | |---|---|---| | `NAME` | VARCHAR | backup database 이름 | | `PATH` | VARCHAR | backup 이미지 원본 경로 | | `BACKUP_TBSID` | LONG | backup의 tablespace 식별자 | | `BACKUP_SCN` | LONG | backup SCN | | `MOUNTDB` | VARCHAR | MOUNT할 때 지정한 database 별칭 | | `DB_BEGIN_TIME` | VARCHAR | backup 데이터의 시작 시각 | | `DB_END_TIME` | VARCHAR | backup 데이터의 종료 시각 | | `BACKUP_BEGIN_TIME` | VARCHAR | backup 작업 시작 시각 | | `BACKUP_END_TIME` | VARCHAR | backup 작업 종료 시각 | | `FLAG` | INTEGER | 내부 상태 플래그. 값의 의미를 임의로 해석하지 않음 | ## 조회 ```sql SELECT NAME, PATH, MOUNTDB, DB_BEGIN_TIME, DB_END_TIME, BACKUP_BEGIN_TIME, BACKUP_END_TIME FROM V$STORAGE_MOUNT_DATABASES ORDER BY MOUNTDB; ``` ## MOUNT와 조회 예제 ```sql MOUNT DATABASE '/data/backup/sc15_snapshot' TO backup_check; SELECT * FROM backup_check.sys.target_table LIMIT 10; UMOUNT DATABASE backup_check; ``` 피연산자는 backup 경로, `TO`, 별칭 순서입니다. mounted database의 객체는 `mount_alias.owner.table` 세 부분 이름으로 조회합니다. 전체 권한과 안전 제한은 [BACKUP/RESTORE/MOUNT 문법](/dbms/reference/sql/syntax/backup-restore-mount-syntax/)을 참고하십시오. --- title: "16.3.5 전체 가상 테이블 레퍼런스" url: https://docs.machbase.com/kr/dbms/reference/system-catalog/virtual-table-full/ language: kr kind: page --- # 16.3.5 전체 가상 테이블 레퍼런스 Virtual Table은 Machbase 서버의 운영 정보를 테이블 형태로 제공하는 읽기 전용 가상 테이블이며, 이름이 `V$`로 시작합니다. 서버 상태를 조회하거나 다른 테이블과 JOIN하여 운영 데이터를 분석하는 데 활용합니다. INSERT, UPDATE, DELETE는 지원하지 않습니다. ## 목차 * [Session/System](#sessionsystem) * [V$PROPERTY](#vproperty) * [V$SESSION](#vsession) * [V$SESMEM](#vsesmem) * [V$SESSTAT](#vsesstat) * [V$SESTIME](#vsestime) * [V$SYSMEM](#vsysmem) * [V$SYSSTAT](#vsysstat) * [V$SYSTIME](#vsystime) * [V$STMT](#vstmt) * [V$VERSION](#vversion) * [V$DATABASES](#vdatabases) * [V$DATABASE_OPERATIONS](#vdatabase_operations) * [V$NEO\_SESSION](#vneo_session) * [V$NEO\_STMT](#vneo_stmt) * [PVO Statement Cache](#pvo-statement-cache) * [V$PVO\_CACHE\_STAT](#vpvo_cache_stat) * [V$PVO\_CACHE\_LIST](#vpvo_cache_list) * [Storage](#storage) * [V$STORAGE](#vstorage) * [V$STORAGE\_MOUNT\_DATABASES](#vstorage_mount_databases) * [V$CACHE](#vcache) * [V$CACHE\_OBJECTS](#vcache_objects) * [V$STORAGE\_DC\_TABLESPACES](#vstorage_dc_tablespaces) * [V$STORAGE\_DC\_TABLESPACE\_DISKS](#vstorage_dc_tablespace_disks) * [V$STORAGE\_DC\_DWFILES](#vstorage_dc_dwfiles) * [V$STORAGE\_DC\_PAGECACHE](#vstorage_dc_pagecache) * [V$STORAGE\_DC\_PAGECACHE\_LRU\_LST](#vstorage_dc_pagecache_lru_lst) * [V$STORAGE\_USAGE](#vstorage_usage) * [V$STORAGE\_TABLES](#vstorage_tables) * [Log Table](#log-table) * [V$STORAGE\_DC\_TABLES](#vstorage_dc_tables) * [V$STORAGE\_DC\_TABLES\_STAT](#vstorage_dc_tables_stat) * [V$STORAGE\_DC\_TABLE\_COLUMNS](#vstorage_dc_table_columns) * [V$STORAGE\_DC\_TABLE\_COLUMN\_PARTS](#vstorage_dc_table_column_parts) * [V$STORAGE\_DC\_TABLE\_INDEXES](#vstorage_dc_table_indexes) * [LSM(Log Structured Merge) Index](#lsmlog-structured-merge-index) * [V$STORAGE\_DC\_LSMINDEX\_LEVEL\_PARTS](#vstorage_dc_lsmindex_level_parts) * [V$STORAGE\_DC\_LSMINDEX\_LEVEL\_PARTS\_CACHE](#vstorage_dc_lsmindex_level_parts_cache) * [V$STORAGE\_DC\_LSMINDEX\_LEVELS](#vstorage_dc_lsmindex_levels) * [V$STORAGE\_DC\_LSMINDEX\_FILES](#vstorage_dc_lsmindex_files) * [V$STORAGE\_DC\_LSMINDEX\_AGER\_JOBS](#vstorage_dc_lsmindex_ager_jobs) * [Volatile Table](#volatile-table) * [V$STORAGE\_DC\_VOLATILE\_TABLE](#vstorage_dc_volatile_table) * [Tag Table](#tag-table) * [V$STORAGE\_TAG\_TABLES](#vstorage_tag_tables) * [V$STORAGE\_TAG\_CACHE](#vstorage_tag_cache) * [V$STORAGE\_TAG\_CACHE\_BASE](#vstorage_tag_cache_base) * [V$STORAGE\_TAG\_CACHE\_OBJECTS](#vstorage_tag_cache_objects) * [V$STORAGE\_TAG\_TABLE\_FILES](#vstorage_tag_table_files) * [V$STORAGE\_TAG\_INDEX](#vstorage_tag_index) * [Tag Rollup](#tag-rollup) * [V$ROLLUP](#vrollup) * [License](#license) * [V$LICENSE\_INFO](#vlicense_info) * [Mutex](#mutex) * [V$MUTEX](#vmutex) * [V$MUTEX\_WAIT\_STAT](#vmutex_wait_stat) * [Cluster](#cluster) * [V$NODE\_STATUS](#vnode_status) * [V$DDL\_INFO](#vddl_info) * [V$REPLICATION](#vreplication) * [V$REPL\_SENDER](#vrepl_sender) * [V$REPL\_SENDER\_META](#vrepl_sender_meta) * [V$REPL\_RECEIVER](#vrepl_receiver) * [V$REPL\_RECEIVER\_META](#vrepl_receiver_meta) * [V$REPL\_READER](#vrepl_reader) * [V$REPL\_READER\_META](#vrepl_reader_meta) * [V$REPL\_WRITER](#vrepl_writer) * [V$REPL\_WRITER\_META](#vrepl_writer_meta) * [Others](#others) * [V$TABLES](#vtables) * [V$COLUMNS](#vcolumns) * [V$RETENTION\_JOB](#vretention_job) * [V$USER\_AUTH\_KEYS](#vuser_auth_keys) ## Session/System ### V$PROPERTY --- 서버에 설정된 프로퍼티 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----- | ------------ | | NAME | 프로퍼티명 | | VALUE | 프로퍼티 값 | | TYPE | 데이터 타입 | | DEFLT | 기본 값 | | MIN | 설정할 수 있는 최소값 | | MAX | 설정할 수 있는 최대값 | ### V$SESSION --- MACHBASE 서버에 접속된 세션 정보를 표시합니다. | 컬럼 이름 | 설명 | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | HOSTNAME (Cluster Only) | 세션 연결된 HOST 이름 | | ID | 세션 식별자 | | CLOSED | 연결이 닫혀있는지 여부 | | USER_ID | 사용자 식별자 | | LOGIN_TIME | 접속 시각 | | CLIENT_TYPE | 접속 Client 타입 | | USER_NAME | 사용자 이름 | | CURRENT_DB_ID | 세션의 현재 논리 데이터베이스 식별자 | | CURRENT_DB_NAME | 세션의 현재 데이터베이스 이름 | | USER_IP | 사용자 IP | | SQL_LOGGING | 해당 세션의 Trace Log 에 메시지를 남길지 여부
Parsing, Validation, Optimization 단계에서 발생하는 에러를 남깁니다.
DDL을 수행한 결과를 남깁니다.
(위의 두 케이스 모두 남깁니다) | | SHOW_HIDDEN_COLS | SELECT 시, 숨겨진 컬럼을 나타낼 것인지 여부 | | FEEDBACK_APPEND_ERROR | APPEND 시 에러를 찾으면 곧바로 실패할 것인지 여부 | | DEFAULT_DATE_FORMAT | Datetime 입력 시 기본 입력 포맷 | | MAX_QPX_MEM | 쿼리 수행 시 가용할 최대 메모리 크기 | | IDLE_TIMEOUT | 세션 연결 후 해당 시간 동안 Client 가 아무일도 하지 않을 시 세션 종료 | | QUERY_TIMEOUT | 쿼리 수행 시 응답 대기 시간 | | DDL_LOCK_TIMEOUT (Standard Only) | 충돌한 DDL 잠금을 기다릴 시간(초). `0`이면 즉시 오류를 반환합니다. | | TRANSACTION_BUSY_TIMEOUT_MS | TRANSACTION 쓰기 충돌 시 대기할 시간(밀리초). `-1`은 계속 대기하고 `0`은 즉시 오류를 반환합니다. | ### V$SESMEM --- 세션 메모리 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----- | ----------- | | SID | 세션 식별자 | | ID | 메모리 매니저 식별자 | | USAGE | 사용 크기 | ### V$SESSTAT --- 세션의 통계 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----- | --------- | | SID | 세션 식별자 | | ID | 통계 정보 식별자 | | VALUE | 통계 정보 값 | ### V$SESTIME --- 세션의 시간 정보를 표시합니다. `ACCUM_MSEC`와 `MAX_MSEC`는 밀리초 단위의 `DOUBLE` 값입니다. | 컬럼 이름 | 설명 | | ---------- | -------------- | | SID | 세션 식별자 | | ID | 수행 단위 식별자 | | ACCUM_MSEC | 누적 시간 | | MAX_MSEC | (각 수행 중) 최대 시간 | ### V$SYSMEM --- 시스템의 메모리 정보를 표시합니다. | 컬럼 이름 | 설명 | | --------- | ------------ | | ID | 메모리 매니저 식별자 | | NAME | 메모리 매니저 이름 | | USAGE | 현재 사용량 | | MAX_USAGE | (기록된) 최대 사용량 | ### V$SYSSTAT --- 시스템의 통계 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----- | --------- | | ID | 통계 정보 식별자 | | NAME | 통계 정보 이름 | | VALUE | 통계 정보 값 | ### V$SYSTIME --- 시스템의 시간 정보를 표시합니다. `ACCUM_MSEC`, `AVG_MSEC`, `MIN_MSEC`, `MAX_MSEC`는 밀리초 단위의 `DOUBLE` 값입니다. | 컬럼 이름 | 설명 | | ---------- | -------------- | | ID | 수행 단위 식별자 | | NAME | 수행 단위 이름 | | ACCUM_MSEC | 누적 시간 | | AVG_MSEC | (각 수행 중) 평균 시간 | | MIN_MSEC | (각 수행 중) 최소 시간 | | MAX_MSEC | (각 수행 중) 최대 시간 | | COUNT | 수행 횟수 | ### V$STMT --- 사용자가 현재 실행중인 쿼리문에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----------- | ----------------------------- | | ID | 쿼리 식별자 | | SESS_ID | 쿼리를 수행한 세션 식별자 | | STATE | 쿼리 상태 | | RECORD_SIZE | SELECT 구문 수행 중인 경우, 결과 레코드 크기 | | QUERY | 쿼리 구문 | ### V$VERSION --- MACHBASE 의 버전에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------------------- | ---------------------------------------- | | BINARY_DB_MAJOR_VERSION | DB 메이저 버전 | | BINARY_DB_MINOR_VERSION | DB 마이너 버전 | | BINARY_META_MAJOR_VERSION | META 메이저 버전 | | BINARY_META_MINOR_VERSION | META 마이너 버전 | | BINARY_CM_MAJOR_VERSION | Client (Communication Level) 메이저 버전 | | BINARY_CM_MINOR_VERSION | Client (Communication Level) 마이너 버전 | | BINARY_SIGNATURE | DB서버 파일의 버전 명 | | FILE_DB_MAJOR_VERSION | File DB 메이저 버전 | | FILE_DB_MINOR_VERSION | File DB 메이저 버전 | | FILE_META_MAJOR_VERSION | File META 메이저 버전 | | FILE_META_MINOR_VERSION | File META 마이너 버전 | | FILE_CM_MAJOR_VERSION | File Client (Communication Level) 메이저 버전 | | FILE_CM_MINOR_VERSION | File Client (Communication Level) 마이너 버전 | | FILE_CREATE_TIME | 파일 생성 시각 | | EDITION | MACHBASE 유형 | ### V$DATABASES --- 논리 active database와 mounted database의 상태를 표시합니다. `DATABASE_ID`는 논리 catalog 식별자이며 물리 `TABLESPACE_ID`와 동일하지 않습니다. | 컬럼 이름 | 설명 | |-----------|------| | DATABASE_ID | 논리 데이터베이스 식별자 | | SOURCE_DATABASE_ID | mounted backup 원본 데이터베이스 식별자 | | NAME | 데이터베이스 이름 또는 mounted alias | | KIND | `ACTIVE` 또는 `MOUNTED` | | ACCESS_MODE | `READ_WRITE` 또는 `READ_ONLY` | | CAN_USE | `USE`로 선택할 수 있는지 여부 | | STATE | lifecycle 상태 | | IS_DEFAULT | 기본 `MACHBASEDB` 여부 | ```sql SELECT database_id, name, kind, access_mode, can_use, state, is_default FROM v$databases ORDER BY database_id; ``` ### V$DATABASE_OPERATIONS --- database lifecycle operation의 상태와 오류를 표시합니다. | 컬럼 이름 | 설명 | |-----------|------| | OPERATION_ID | operation 식별자 | | DATABASE_ID | 대상 논리 데이터베이스 식별자 | | DATABASE_NAME | 대상 데이터베이스 이름 | | STATE | operation 상태 | | LAST_ERROR | 실패 원인 | | CREATED_AT | 생성 시각 | | UPDATED_AT | 마지막 변경 시각 | ```sql SELECT operation_id, database_name, state, last_error FROM v$database_operations ORDER BY operation_id DESC; ``` ### V$NEO_SESSION --- Neo 프로토콜 클라이언트의 세션 상태를 표시합니다. | 컬럼 이름 | 설명 | | -- | -- | | ID | 세션 식별자 | | USER_ID | 사용자 식별자 | | USER_NAME | 사용자 이름 | | STMT_COUNT | 세션의 statement 수 | | DISCONN_FLAG | 연결 해제 플래그 | ### V$NEO_STMT --- Neo 프로토콜 클라이언트의 statement 상태를 표시합니다. | 컬럼 이름 | 설명 | | -- | -- | | ID | statement 식별자 | | SESS_ID | 세션 식별자 | | STATE | statement 상태 | | QUERY | statement 텍스트 | | APPEND_SUCCESS_CNT | append 성공 건수 | | APPEND_FAILURE_CNT | append 실패 건수 | ## PVO Statement Cache Standard 에디션에서만 제공되는 글로벌 PVO Statement Cache 상태를 조회합니다. ### V$PVO_CACHE_STAT --- PVO Statement Cache의 전체 통계를 보여줍니다. | 컬럼 이름 | 설명 | | -- | -- | | CACHE_ENTRY_COUNT | 캐시에 적재된 SQL 엔트리 수 | | CACHE_HANDLE_COUNT | 모든 SQL에 대한 캐시된 플랜(핸들) 수 | | CACHE_MEMORY_USAGE | 사용 중인 캐시 메모리 크기 | | CACHE_MAX_MEMORY_SIZE | 설정된 캐시 메모리 한도 | | CACHE_MAX_PLANS_PER_SQL | SQL당 허용되는 최대 플랜 수 | | CACHE_MAX_SQL_ENTRIES | 허용되는 최대 SQL 엔트리 수 (0은 무제한) | | CACHE_SHARD_COUNT | 캐시 샤드 개수 | | CACHE_HIT | 캐시 히트 횟수 | | CACHE_MISS | 캐시 미스 횟수 | | SINGLEFLIGHT_WAIT | 동일 SQL 병행 빌드 대기 횟수 | | BUILD_COUNT | 플랜 빌드 시도 횟수 | | BUILD_FAIL | 빌드 실패 횟수 | | INVALIDATE_COUNT | 무효화된 플랜 수 | | EVICT_COUNT | 메모리 한도 등으로 인한 캐시 축출 횟수 | | FLUSH_COUNT | 명시적/내부 플러시 횟수 | ### V$PVO_CACHE_LIST --- PVO Statement Cache에 저장된 SQL별 상세 정보를 보여줍니다. | 컬럼 이름 | 설명 | | -- | -- | | TOUCH_TIME | 마지막 터치 시각 | | USER_ID | SQL을 소유한 사용자 ID | | QUERY | 원본 SQL 텍스트 | | DEFAULT_DATE_FORMAT | 실행 당시의 날짜 포맷 | | TIMEZONE_OFFSET | 실행 당시 타임존 오프셋 | | SHOW_HIDDEN_COLS | 숨김 컬럼 표시 여부 | | QUERY_PARALLEL_FACTOR | 병렬 실행 계수 | | HANDLE_COUNT | 보유한 플랜(핸들) 수 | | BUSY_COUNT | 동시에 사용 중인 핸들 수 | | HIT_COUNT | 캐시 히트 횟수 | | BUILD_IN_PROGRESS | 빌드 진행 중 여부 | ## Storage ### V$STORAGE --- 저장 시스템의 내부 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------------------- | ------------------------------------ | | DC_TABLE_FILE_SIZE | 디스크 컬럼 데이터의 총 용량 | | DC_INDEX_FILE_SIZE | 인덱스 파일 데이터의 총 용량 | | DC_TABLESPACE_DWFILE_SIZE | 모든 컬럼데이터를 위한 DWFILE의 총 용량 | | DC_KV_TABLE_FILE_SIZE | TAGDATA 테이블의 파티션 테이블이 가지는 데이터파일 총 용량 | ### V$STORAGE_MOUNT_DATABASES --- 마운트 기능을 이용하여 마운트한 백업 데이터베이스의 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----------------- | ---------------------- | | NAME | 마운트된 데이터베이스의 이름 | | PATH | 백업 파일의 위치 | | BACKUP_TBSID | 백업 데이터베이스의 테이블스페이스 식별자 | | BACKUP_SCN | 백업 데이터베이스의 식별자 | | MOUNTDB | MOUNT할 때 지정한 데이터베이스 별칭 | | DB_BEGIN_TIME | 백업 데이터베이스의 최초입력 시간 | | DB_END_TIME | 백업 데이터베이스의 최종 입력 시간 | | BACKUP_BEGIN_TIME | 백업 실행시 시작 시간 | | BACKUP_END_TIME | 백업 실행시 종료 시간 | | FLAG | 프로퍼티 플래그 | ### V$CACHE --- Storage Manager 에서 읽은 결과를 캐싱한, 캐시 객체에 대한 종합 정보를 표시합니다. | 컬럼 이름 | 설명 | | --------- | ---------------- | | OBJ_COUNT | 결과집합 캐시 객체의 현재 수 | ### V$CACHE_OBJECTS --- 저장 시스템에서 읽은 결과를 캐싱한, 각 캐시 객체에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | --------- | -------------- | | OID | 객체식별자 | | REF_COUNT | 참조 카운트 | | FLAG | (서버 내부 사용 플래그) | ### V$STORAGE_DC_TABLESPACES --- 저장 시스템의 테이블스페이스 정보를 표시합니다. | 컬럼 이름 | 설명 | | ---------- | ---------------------------- | | NAME | 테이블스페이스 이름 | | ID | 테이블스페이스 식별자 | | FLAG | 테이블스페이스 Property 를 나타내는 Flag | | REF_COUNT | 테이블스페이스 참조 횟수 | | DISK_COUNT | 테이블스페이스에 속한 디스크 개수 | ### V$STORAGE_DC_TABLESPACE_DISKS --- 저장 시스템의 테이블스페이스 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------------ | ------------------- | | NAME | 디스크 이름 | | ID | 디스크 식별자 | | TABLESPACE_ID | 디스크가 속한 테이블스페이스 식별자 | | PATH | 디스크의 경로 | | IO_THREAD_COUNT | I/O Thread 개수 | | IO_JOB_COUNT | I/O Job 개수 | | VIRTUAL_DISK_COUNT | 가상 디스크 개수 | ### V$STORAGE_DC_DWFILES --- 저장 시스템에서 운용하는 Double-write 파일 (DW File) 의 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------------- | ----------------------- | | TBS_ID | 테이블스페이스 식별자 | | DISK_ID | 디스크 식별자 | | FILE | 파일의 경로 | | TABLE_ID | 테이블 식별자 | | COLUMN_ID | 컬럼 식별자 | | PARTITION_ID | 파티션 식별자 | | PAGE_ID | 페이지 식별자 | | DISK_OFFSET | 디스크 오프셋 | | DISK_IMAGE_SIZE | 디스크 이미지 크기 | | HEAD_CRC32CODE_IMAGE | CRC32 Code 의 Head Image | | TAIL_CRC32CODE_IMAGE | CRC32 Code 의 Tail Image | | CRC32CODE_PAGE | CRC32 Code 의 Page | | HEAD_TIMESTAMP_PAGE | Timestamp 의 Head Page | | TAIL_TIMESTAMP_PAGE | Timestamp 의 TailPage | ### V$STORAGE_DC_PAGECACHE --- 저장 시스템에서 운용하는 Page Cache 에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------ | ---------------------- | | MAX_MEM_SIZE | Page Cache 의 최대 메모리 크기 | | CUR_MEM_SIZE | Page Cache 의 현재 메모리 크기 | | PAGE_CNT | 캐싱된 페이지 개수 | | CHECK_TIME | 검사 시간 | ### V$STORAGE_DC_PAGECACHE_LRU_LST --- 저장 시스템에서 운용하는 Page Cache 의 LRU List 에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------ | ------------------- | | SIZE | 페이지 크기 | | REF_CNT | 참조 횟수 | | PARTITION_ID | 파티션 식별자 | | OFFSET | Page Cache 의 Offset | | OBJECT_ID | 객체 식별자 | | LEVEL | 파티션 레벨 | ### V$STORAGE_USAGE --- 저장 시스템에서 사용 중인 스토리지의 사용량을 표시합니다. | 컬럼 이름 | 설명 | | ----------- | ------------------------------------------------------ | | TOTAL_SPACE | $MACHBASE_HOME/dbs 디렉터리가 위치한 스토리지의 총 용량 | | USED_SPACE | $MACHBASE_HOME/dbs 디렉터리가 위치한 스토리지의 사용량 | | USED_RATIO | 사용량 비율(%) | | RATIO_CAP | 스토리지 사용량 한계. USED_RATIO이 이 한계에 도달하면 데이터 입력/인덱스 구축이 멈춤. | ### V$STORAGE_TABLES --- 테이블의 상세 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ID | 테이블의 ID | | TYPE | 테이블 형태
Persistent: LOG 테이블과 TAG 테이블
Volatile: 휘발성(Volatile) 테이블
Key-Value: TAG 테이블의 부속 테이블 | | STATUS | 현재 상태
Creating...: CREATE TABLE로 테이블 생성 진행중
Normal: 정상
Predrop: DROP TABLE 명령 접수 상태
Dropping...: DROP TABLE 명령 수행 상태
Dropped: DROP TABLE 명령 완료 상태
Mounted: 백업된 데이터베이스를 mount 명령으로 불러온 상태 | | STORAGE_USAGE | 해당 테이블이 스토리지에서 점유한 용량 | ## Log Table ### V$STORAGE_DC_TABLES --- Log Table 에 대한 내부 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------------- | ------------------------------------------ | | ID | 테이블의 식별자 | | TABLESPACE_ID | 테이블스페이스 식별자 | | CREATE_SCN | 생성 당시의 시스템 변경 번호 (System Change Number) | | UPDATE_SCN | 최근 변경 당시의 시스템 변경 번호 (System Change Number) | | DDL_REF_COUNT | DDL 구문 수행으로, 해당 테이블을 참조하고 있는 세션의 개수. | | BEGIN_RID | 테이블의 최소 RID | | END_RID | 테이블의 마지막 Row ID + 1 | | BEGIN_META_RID | 메타 정보를 기록하기 시작한 시점의 ID | | END_META_RID | 메타 정보의 기록이 종료한 시점의 ID | | END_SYNC_RID | 디스크에 기록된 마지막 Row ID + 1 | | FLAG | Table Property 를 나타내는 Flag | | COLUMN_COUNT | 테이블의 컬럼 수 | | INDEX_COUNT | 테이블의 인덱스 수 | | INDEX_MIN_END_RID | 인덱스에 기록된 마지막 RID + 1 | | LAST_ARRIVAL_TIME | 마지막으로 기록된 \_arrival_time 값 | | LAST_CHECKPOINT_TIME | 마지막으로 Checkpoint 를 지난 시점 | | TYPE | 테이블 유형 | ### V$STORAGE_DC_TABLES_STAT --- Log Table 에 대한 내부 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------- | ----------- | | TABLESPACE_ID | 테이블스페이스 식별자 | | TABLE_ID | 테이블 식별자 | | COUNT | 레코드 개수 | | COLUMN_ID | 컬럼 식별자 | ### V$STORAGE_DC_TABLE_COLUMNS --- Log Table 의 컬럼에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------------------- | ------------------------------- | | TABLE_ID | 테이블 식별자 | | TABLESPACE_ID | 테이블스페이스 식별자 | | ID | 컬럼 식별자 | | FLAG | 프로퍼티 플래그 | | SIZE | 컬럼의 데이터 크기 | | PARTITION_VALUE_COUNT | 파티션에 저장되는 최대 데이터 수 | | PAGE_VALUE_COUNT | 페이지에 저장되는 최대 데이터 수 | | CACHE_VALUE_COUNT | 캐시 값의 최대 수 | | MINMAX_CACHE_SIZE | 컬럼 파티션에 대한 MIN/MAX 캐시의 최대 크기 | | CUR_APPEND_PARTITION_ID | 현재 입력을 진행중인 파티션의 식별자 | | CUR_CACHE_PARTITION_COUNT | 현재 캐시에 데이터를 읽어들인 파티션의 수 | | CUR_MINMAX_CACHE_SIZE | 현재 MIN/MAX캐시의 크기 | | END_RID_FOR_DEFAULT_VALUE | 이 값보다 작은 RID를 갖는 컬럼은 디폴트값으로 지정됨 | | DISK_FILE_SIZE | 해당 컬럼에 대한 컬럼 파티션 데이터 파일의 전체 크기 | | MEMORY_TOTAL_SIZE | 테이블이 사용 중인 메모리 크기 | | MEMORY_ALLOC_SIZE | 테이블이 할당받은 메모리 크기 | ### V$STORAGE_DC_TABLE_COLUMN_PARTS --- Log Table 의 컬럼 파티션 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----------------------------- | ---------------------------------------------------------------------------------- | | TABLE_ID | 테이블 식별자 | | TABLESPACE_ID | 테이블스페이스 식별자 | | COLUMN_ID | 컬럼 식별자 | | ID | 파티션 식별자 | | FLAG | 컬럼 Property 를 나타내는 Flag | | BEGIN_RID | 파티션에 저장된 최소 RID | | END_RID | 파티션에 저장된 최종 RID | | END_SYNC_RID | SYNC가 끝난 최종 RID.
시작 RID 보다 크고 마지막 SYNC RID 보다 작은 RID 를 갖는 데이터는 파티션 파일에 기록되어 있습니다. | | MIN_TIME | 컬럼 파티션에 최초로 데이터를 입력한 시간 | | MAX_TIME | 컬럼 파티션에 마지막으로 데이터를 입력한 시간 | | MAX_VALUE_COUNT_PER_PARTITION | 파티션의 최대 데이터 수 | | MAX_VALUE_COUNT_PER_PAGE | 페이지당 최대 데이터 수 | | MAX_PAGE_COUNT | 파티션당 최대 페이지의 수 | | PAGE_SIZE | 컬럼 파티션에 저장된 페이지의 크기 | | PAGE_COUNT | 현재 컬럼 파티션에 생성된 페이지의 수 | | COMPRESS_RATIO | 컬럼 파티션의 압축률. 0이면 아직 데이터 압축이 실행되지 않은 경우입니다. | | DISK_FILENAME | 파티션 파일의 이름 | | EXTERNAL_PART_SIZE | 데이터의 양이 큰 값은 외부 파티션 파일에 기록하는데, 그 파일의 크기를 표시 | | MIN_VALUE | 컬럼 파티션의 최소값 | | MAX_VALUE | 컬럼 파티션의 최대값 | ### V$STORAGE_DC_TABLE_INDEXES --- Log Table 에 생성된 인덱스 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------------- | ---------------------------- | | TABLE_ID | 테이블 식별자 | | TABLESPACE_ID | 테이블스페이스 식별자 | | ID | 인덱스 식별자 | | FLAG | 인덱스 Property 를 나타내는 Flag | | TABLE_BEGIN_RID | 테이블의 입력된 최소 RID | | TABLE_END_RID | 테이블의 마지막 RID | | BEGIN_RID | 인덱스의 최소 RID | | END_RID | 인덱스의 최대 RID | | END_SYNC_RID | 파일에 기록된 최대 RID+1 | | COLUMN_COUNT | 인덱스 컬럼 수 | | BEGIN_PART_ID | 인덱스의 최초 파티션 식별자 | | END_PART_ID | 인덱스의 최종 파티션 식별자 | | FLUSH_REQUEST_COUNT | 디스크에 반영요청된 인덱스 파티션의 수 | | MAX_KEY_SIZE | 최대 키 크기 | | INDEX_TYPE | 인덱스 유형 | | DISK_FILE_SIZE | 해당 인덱스에 대한 인덱스 파티션 파일의 전체 크기 | | LAST_CHECKPOINT_TIME | 마지막으로 Checkpoint 를 지난 시점 | ## LSM(Log Structured Merge) Index ### V$STORAGE_DC_LSMINDEX_LEVEL_PARTS --- LSM Index 파티션에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------------------- | --------------------------------------- | | TABLE ID | 인덱스가 생성된 테이블의 식별자 | | TABLESPACE_ID | 테이블스페이스 식별자 | | INDEX_ID | 인덱스 식별자 | | LEVEL | 인데스 파티션의 LSM 레벨 | | PARTITION_ID | 파티션 식별자 | | BEGIN_RID | 파티션에 입력된 최소 RID | | END_RID | 파티션에 입력된 최대 RID+1 | | KEY_VALUE_COUNT | 파티션에 입력된 키값의 수 | | KEY_VALUE_TABLE_SIZE | 키값을 저장하는 페이지 크기 | | KEY_VALUE_TABLE_PAGE_COUNT | 키값을 저장하는 페이지의 수 | | MIN_KEY_VALUE | 최소 키 값 | | MAX_KEY_VALUE | 최대 키 값 | | BITMAP_TABLE_SIZE | 비트맵 값을 저장하는 페이지의 합계 | | BITMAP_TABLE_PAGE_COUNT | 비트맵 값을 저장하는 페이지의 수 | | META_SIZE | 메타 정보를 저장하는 페이지의 합계 | | META_PAGE_COUNT | 메타 정보를 저장하는 페이지의 수 | | TOTAL_BUILD_MSEC | 해당 파티션을 완성하기 까지의 총 시간 | | KEYVAL_BUILD_MSEC | KeyValue Mode 에서, 해당 파티션을 완성하기 까지의 총 시간 | | BITMAP_BUILD_MSEC | Bitmap Mode 에서, 해당 파티션을 완성하기 까지의 총 시간 | ### V$STORAGE_DC_LSMINDEX_LEVEL_PARTS_CACHE --- LSM Index 파티션 캐시에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------------------- | --------------------------- | | BEGIN_RID | 파티션에 입력된 최소 RID | | BITMAP_TABLE_PAGE_COUNT | 비트맵 값을 저장하는 페이지의 수 | | BITMAP_TABLE_SIZE | 비트맵 값을 저장하는 페이지의 합계 | | END_RID | 파티션에 입력된 최대 RID+1 | | INDEX_ID | 인덱스 식별자 | | KEY_VALUE_COUNT | 파티션에 입력된 키값의 수 | | KEY_VALUE_TABLE_PAGE_COUNT | 키값을 저장하는 페이지의 수 | | KEY_VALUE_TABLE_SIZE | 키값을 저장하는 페이지의 크기 | | LEVEL | 인데스 파티션의 LSM 레벨 | | MEMORY_SIZE | 메모리 사용량 | | MEMORY_SIZE_RBTREE | Redblack Tree 가 사용한 메모리 사용량 | | META_PAGE_COUNT | 메타 정보를 저장하는 페이지의 수 | | META_SIZE | 메타 정보를 저장하는 페이지의 합계 | | PARTITION_ID | 파티션 식별자 | | TABLE_ID | 인덱스가 생성된 테이블의 식별자 | | TABLESPACE_ID | 테이블스페이스 식별자 | ### V$STORAGE_DC_LSMINDEX_LEVELS --- LSM 인덱스의 레벨에 관한 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------- | ---------------------- | | TABLE_ID | 테이블 식별자 | | TABLESPACE_ID | 테이블스페이스 식별자 | | INDEX_ID | 인덱스 식별자 | | LEVEL | 레벨 | | BEGIN_RID | 파티션의 첫번째 RID | | END_RID | 파티션의 마지막 RID+1 | | META_BEGIN_RID | 메타정보를 기록하기 시작한 시점의 RID | | META_END_RID | 메타정보의 기록이 끝난 시점의 RID | | DELETE_END_RID | 삭제된 RID 최대값 +1 | ### V$STORAGE_DC_LSMINDEX_FILES --- LSM Index 를 구성하는 파일에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------ | --------------- | | TABLE_ID | 테이블 식별자 | | TABLESPACE_ID | 테이블스페이스 식별자 | | INDEX_ID | 인덱스 식별자 | | LEVEL | 인데스 파티션의 LSM 레벨 | | PARTITION_ID | 파티션 식별자 | | BEGIN_RID | 파티션의 첫번째 RID | | END_RID | 파티션의 마지막 RID+1 | | PATH | 인덱스 파일의 위치 | ### V$STORAGE_DC_LSMINDEX_AGER_JOBS --- LSM Index 의 삭제를 담당하는 Ager 의 작업 상태를 표시합니다. | 컬럼 이름 | 설명 | | --------- | ------------------ | | TABLE_ID | 테이블 식별자 | | INDEX_ID | 인덱스 식별자 | | LEVEL | 인데스 파티션의 LSM 레벨 | | BEGIN_RID | 파티션의 첫번째 RID | | END_RID | 파티션의 마지막 RID+1 | | STATE | Index Ager 의 작업 상태 | ## Volatile Table ### V$STORAGE_DC_VOLATILE_TABLE --- Volatile Table 에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------ | --------------------------- | | MAX_MEM_SIZE | Volatile Tablespace 의 최대 크기 | | CUR_MEM_SIZE | Volatile Tablespace 의 현재 크기 | ## Tag Table ### V$STORAGE_TAG_TABLES --- Tagdata Table 의 파티션 테이블에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ID | 테이블 식별자 | | TABLE_BEGIN_RID | 테이블 시작 RID | | TABLE_END_RID | 테이블 끝 RID | | WRITE_END_RID | 데이터 파일에 기록된 마지막 RID | | EXT_ROW_COUNT | VARCHAR 레코드 중 외부 파티션에 입력된 개수 | | EXT_WRITE_COUNT | VARCHAR 레코드 중 데이터파일에 기록된 개수 | | DISK_INDEX_END_RID | 스토리지에 저장된 인덱스의 끝 RID | | MEMORY_INDEX_END_RID | 메모리 인덱스에 상주한 테이블 끝 RID | | DELETE_MIN_DATE | DELETE ... BETWEEN ... 수행시 삭제 대상의 최소 시각 | | DELETE_MAX_DATE | DELETE ... BETWEEN ..., 혹은 DELETE ... BEFORE ... 수행시 삭제 대상의 최대 시각 | | INDEX_STATE | 현재 인덱스 구축 상태
IDLE: 구축 완료, 대기중.
PROGRESS: 구축 진행중
IOWAIT: 스토리지에 입출력 연산 대기.
PENDING: 테이블에 읽기 잠금 대기중
SHUTDOWN: 정지됩니다. DELETE 연산, 혹은 DROP 연산 진행중.
ABNORMAL: 비정상 종료 | | DELETE_STATE | 현재 DELETE 연산의 상태. DELETE 명령이 들어올 때에만 수행되므로 IDLE이 없습니다.
PROGRESS: 삭제 진행중
IOWAIT: 스토리지에 입출력 연산 대기.
PENDING: 테이블에 읽기/쓰기 잠금 대기중
SHUTDOWN: 정지됩니다. DELETE 연산이 진행되지 않습니다.
ABNORMAL: 비정상 종료 | | SAVE_STATE | 현재 테이블 저장 연산의 상태.
IDLE: 저장 완료, 대기중.
PROGRESS: 저장 진행중
IOWAIT: 스토리지에 입출력 연산 대기.
PENDING: 테이블에 읽기 잠금 대기중
SHUTDOWN: 정지됩니다. DELETE 연산, 혹은 DROP 연산 진행중.
ABNORMAL: 비정상 종료 | | VINDEX_STATE | 현재 VARCHAR 인덱스 구축 상태
IDLE: 구축 완료, 대기중.
PROGRESS: 구축 진행중
IOWAIT: 스토리지에 입출력 연산 대기.
PENDING: 테이블에 읽기 잠금 대기중
SHUTDOWN: 정지됩니다. DELETE 연산, 혹은 DROP 연산 진행중.
ABNORMAL: 비정상 종료 | ### V$STORAGE_TAG_CACHE --- Tagdata Table 의 파티션 테이블에서 사용하는 캐시 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----------- | ------------------------ | | POOL_ID | 캐시 풀 식별자 | | CATEGORY | 캐쉬되고 있는 객체 분류 | | USED_MEMORY | 사용중인 메모리 크기 | | BLOCK_COUNT | 데이터 캐시 개수 | | CACHE_HIT | 데이터 캐시 히트 횟수 | | CACHE_MISS | 데이터 캐시 미스 횟수 | | FLUSHOUT | 데이터 캐시 충돌로 페이지를 비운 횟수 | | COLD_READ | 스토리지에서 직접 읽어온 데이터 페이지 개수 | | MEMORY_WAIT | 데이터 메모리가 캐시 충돌로 대기한 횟수 | | IO_WAIT | 데이터 읽기 연산 대기 횟수 | ### V$STORAGE_TAG_CACHE_BASE --- 태그 캐시 풀의 집계 정보를 표시합니다. | 컬럼 이름 | 설명 | | -- | -- | | POOL_ID | 캐시 풀 식별자 | | TOTAL_CACHE_MEMORY | 전체 캐시 메모리 | | TOTAL_OBJECT_COUNT | 전체 캐시 객체 수 | | TOTAL_LRU_LOOP_COUNT | 전체 LRU 루프 수 | ### V$STORAGE_TAG_CACHE_OBJECTS --- Tagdata Table의 파티션 테이블에서 사용하는 각각의 캐시 블럭에 대한 상세정보를 표시합니다. | 컬럼 이름 | 설명 | | ---------- | -------------------------------------------------------------------------------------------------------------------- | | CATEGORY | 캐쉬되고 있는 객체 분류 | | LATEST_HIT | 마지막 접근 시각 | | STATUS | 캐시 상태
None: 메모리 할당을 마친 상태
Resides: 캐시에 보존된 상태
Loading: 스토리지에서 테이블 데이터를 불러 오는 중
ERROR!: 데이터를 불러오는 중 오류 발생 | | WAIT_COUNT | Loading 상태에서 해당 캐시를 읽지 못해 대기한 회수 | | REF_COUNT | 현재 캐시 블럭을 참조 중인 세션 수 | | HIT_COUNT | 캐시 블럭을 참조한 회수 | | TABLE_ID | 테이블 식별자 | | FILE_ID | 파일 식별자 | | PART_ID | 데이터파일 내부의 파티션 식별자 | | SAVE_SCN | 테이블 저장 SCN | | VSAVE_SCN | 테이블 저장 SCN | | DELETE_SCN | DELETE 연산 SCN | | OFFSET | 데이터파일 오프셋 | | DATA_SIZE | 압축 이전 데이터 크기, 혹은 0 | ### V$STORAGE_TAG_TABLE_FILES --- Tagdata Table 의 파티션 테이블의 파일 정보를 표시합니다. | 컬럼 이름 | 설명 | | --------- | ---------------------------------------------------------------------------------------------------------------------------------- | | TABLE_ID | 테이블 식별자 | | FILE_ID | 파일 식별자 | | STATE | 인덱싱 상태
COMPLETE: 데이터 저장, 인덱싱 완료
INDEXING: 인덱스 구축 중.
FILLED: 데이터가 꽉 찬 상태, 인덱싱 대기 중
PARTIAL: 아직 데이터가 꽉 차지 않았음. 인덱싱 대기 중. | | REF_COUNT | 현재 파일을 참조 중인 세션 수 | | ROW_COUNT | 삭제됐던 레코드를 포함하여 파일에 저장된 레코드 개수 | | DEL_COUNT | 파일에서 삭제된 레코드 개수 | | MIN_DATE | 해당 파일에 기록된 데이터의 최소 일자 | | MAX_DATE | 해당 파일에 기록된 데이터의 최대 일자 | ### V$STORAGE_TAG_INDEX --- Tagdata Table 에 생성된 인덱스 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------------- | ----------------------------------------------------------------------------------------------------- | | TABLE_ID | 테이블 식별자 | | INDEX_ID | 인덱스 식별자(INDEX_ID가 4294967295인 경우 tag테이블 생성시 자동으로 생성되는 기본 인덱스를 의미함) | | INDEX_STATE | 인덱싱 상태
IDLE: 인덱싱이 완료되어 대기중인 상태
INDEXING: 인덱싱이 진행중인 상태
STORAGE FULL: Disk full상태로 인덱싱이 중단된 상태 | | DISK_INDEX_END_RID | 마지막으로 disk에 반영된 인덱스의 EndRID | | MEMORY_INDEX_END_RID | 마지막으로 memory에 반영된 인덱스의 EndRID | | TABLE_END_RID | 테이블에 마지막으로 반영된 데이터의 EndRID | ## Tag Rollup ### V$ROLLUP --- Tagdata 테이블의 Rollup 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------- | ------------------------------------------------------- | | DATABASE_ID | 논리 데이터베이스 식별자 | | ID | Rollup 작업 ID | | ROLLUP_TABLE | Rollup 테이블 이름 | | SOURCE_TABLE | 집계 대상 테이블 이름(TAG/ROLLUP) | | COLUMN_NAME | 집계 대상 값 컬럼 | | ROOT_TABLE | 최상위 소스 태그 테이블 이름 | | USER_ID | 소유자 User ID | | INTERVAL_TIME | 데이터 집계 간격(밀리초) | | WAKEUP_INTERVAL| Rollup 작업의 실행 주기(밀리초) | | LAST_WAKEUP_TIME| 최근 wakeup 시각 | | NEXT_WAKEUP_TIME| 다음 wakeup 예정 시각 | | ENABLED | Rollup 활성화 여부(1/0) | | END_RID | 이 Rollup이 처리한 Source Table의 마지막 RID | | LAST_ELAPSED_MSEC | 직전 Rollup 실행에 걸린 시간(밀리초) | | EXT_TYPE | 확장(EXTENSION) 여부 플래그 | | PREDICATE | 조건 롤업의 필터 식(NULL이면 조건 없음) | | RUN_STATE | 스레드 상태: I=INIT, S=SLEEPING, R=RUNNING | ## License ### V$LICENSE_INFO --- 라이선스 정보를 표시합니다. | 컬럼 이름 | 설명 | | ---------------- | ---------------------- | | ID | 라이선스 ID | | ISSUE_DATE | 발행일 | | TYPE | 라이선스 유형 | | CUSTOMER | 고객사 이름 | | PROJECT | 프로젝트 이름 | | COUNTRY_CODE | 국가 코드 | | INSTALL_DATE | 설치일 | | VIOLATE_STATUS | 라이선스 위반 상태 | | VIOLATE_MSG | 라이선스 위반 메시지 | `V$LICENSE_STATUS`는 Standard 8.5.4 서버에서 노출되지 않습니다. Standard 에디션에서 조회 가능한 라이선스 필드는 `V$LICENSE_INFO`를 사용하십시오. ## Mutex ### V$MUTEX --- 현재 뮤텍스 상태를 보여줍니다. `WAIT_MSEC`, `WAIT_AVG_MSEC`, `HELD_MSEC`, `HELD_AVG_MSEC`는 밀리초 단위의 `DOUBLE` 값입니다. | 필드명 | 설명 | 비고 | | -------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------- | | OBJECT | 뮤텍스 객체의 주소 | | | NAME | 뮤텍스 생성시 부여한 이름 | | | TYPE | 뮤텍스 타입 | Mutex: pmuMutex
RW Mutex: pmuRWMutex | | OWNER | 뮤텍스를 획득한 스레드의 ID | Mutex: 뮤텍스를 획득한 스레드가 없으면 0.
RW Mutex w/ Read-Lock: 0
RW Mutex w/ Write-Lock: Write Lock을 획득한 스레드의 ID | | LOCK_COUNT | 뮤텍스를 획득한 스레드 개수 | RW Mutex는 2 이상이 될 수 있습니다. | | PEND_COUNT | 뮤텍스를 획득하려고 대기 중인 스레드 개수 | TRACE_MUTEX_WAIT_STATUS=1일 때에만 수집 | | TRY_COUNT | 뮤텍스를 획득하려고 시도한 회수 | TRACE_MUTEX_WAIT_STATUS=1일 때에만 수집 | | CONFLICT_COUNT | 뮤텍스 획득에 실패한 회수 | TRACE_MUTEX_WAIT_STATUS=1일 때에만 수집 | | WAIT_MSEC | 뮤텍스 획득 대기 시간의 총합 | TRACE_MUTEX_WAIT_STATUS=1일 때에만 수집
RW Mutex에는 기록하지 않음 | | WAIT_AVG_MSEC | 뮤텍스 획득 시도 후 성공까지의 평균 시간 | TRACE_MUTEX_WAIT_STATUS=1일 때에만 수집
RW Mutex에는 기록하지 않음 | | HELD_MSEC | 뮤텍스를 획득한 이후 해제할 때까지의 시간 총합 | TRACE_MUTEX_WAIT_STATUS=1일 때에만 수집
RW Mutex에는 기록하지 않음 | | HELD_AVG_MSEC | 뮤텍스 획득 이후 해제까지의 시간 평균 | TRACE_MUTEX_WAIT_STATUS=1일 때에만 수집
RW Mutex에는 기록하지 않음 | ### V$MUTEX_WAIT_STAT --- 현재 대기중인 뮤텍스의 콜스택을 보여줍니다. | 필드 | 설명 | 비고 | | --------- | ------------------- | -------------------------------- | | THREAD_ID | 뮤텍스 획득 대기 중인 스레드 ID | | | OBJECT | 획득 시도 중인 뮤텍스의 주소 | V$MUTEX의 OBJECT와 동일 | | DEPTH | 호출 깊이 | TRACE_MUTEX_WAIT_STACK=1일 때에만 수집 | | SYMBOL | 뮤텍스 획득을 호출한 함수의 심볼 | TRACE_MUTEX_WAIT_STACK=1일 때에만 수집 | ## Cluster 다음 가상 테이블은 클러스터 에디션용이며 Standard 서버에서는 노출되지 않습니다. 실행 중인 에디션에서 조회 가능한지 `V$TABLES`로 확인한 뒤 사용하십시오. ### V$NODE_STATUS --- Cluster 각 Node 의 상태를 표시합니다. 1건만 표시됩니다. | 컬럼 이름 | 설명 | | -------- | ------------------------------------------------------------- | | NODETYPE | Node 의 유형. 쿼리로 조회 가능한 Type 은 두 가지 뿐입니다.
Broker
Warehouse | | STATE | Node 의 상태 | ### V$DDL_INFO --- Cluster 에서 수행한 DDL 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------- | ----------------------- | | SEQUENCENUMBER | DDL 순서 번호 | | TIME | DDL 수행 시간 | | VALUE | DDL 쿼리 결과 값 (서버 내부 사용) | | CLIENT | 클라이언트 이름 | | BROKER | Leader Broker 의 Node 이름 | | USER | 사용자 이름 | | SQL | DDL 쿼리 값 | ### V$REPLICATION --- Replication 작동에 대한 정보를 표시합니다. | 컬럼 이름 | 설명 | | ---------------- | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | MODE | (서버 내부 사용) | | STATE | Node 의 상태 | | ADDR | Replication Manager 의 주소 | | PORT_NO | Replication Manager 의 포트번호 | | MAX_SENDER_COUNT | 생성 가능한 Sender 최대 개수 | | RUN_SENDER_COUNT | 작동중인 Sender 최대 개수 | ### V$REPL_SENDER --- Replication 작동 시, Sender 의 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------------ | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | ID | Sender 식별자 | | STATUS | Sender Thread 의 작동상태 | | PAYLOAD_RECV_COUNT | Sender 로부터 받은 페이로드 개수 | | PAYLOAD_RECV_BYTES | Sender 로부터 받은 페이로드 크기 총합 | | QUEUE_REMAIN_COUNT | Receive Queue 에 남은 버퍼의 개수 | | NET_SEND_COUNT | 전체 전송 횟수 | | NET_SEND_SIZE | 전체 전송 크기 총합 | | NET_RECV_COUNT | 전체 수신 횟수 | | NET_RECV_SIZE | 전체 수신 크기 총합 | ### V$REPL_SENDER_META --- Replication 작동 시, Sender 의 메타데이터를 표시합니다. | 컬럼 이름 | 설명 | | ---------- | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | SENDER_ID | Sender 식별자 | | TABLE_ID | 대상 테이블 식별자 | | TABLE_TYPE | 대상 테이블 유형 | | BEGIN_RID | 대상 레코드의 시작 RID | | END_RID | 대상 레코드의 끝 RID | ### V$REPL_RECEIVER --- Replication 작동 시, Receiver 의 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------------ | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | STATUS | Receiver Thread 의 작동상태 | | PAYLOAD_RECV_COUNT | Sender 로부터 받은 페이로드 개수 | | PAYLOAD_RECV_BYTES | Sender 로부터 받은 페이로드 크기 총합 | | QUEUE_REMAIN_COUNT | Receive Queue 에 남은 버퍼의 개수 | | NET_SEND_COUNT | 전체 전송 횟수 | | NET_SEND_SIZE | 전체 전송 크기 총합 | | NET_RECV_COUNT | 전체 수신 횟수 | | NET_RECV_SIZE | 전체 수신 크기 총합 | ### V$REPL_RECEIVER_META --- Replication 작동 시, Receiver 의 메타데이터를 표시합니다. | 컬럼 이름 | 설명 | | ---------- | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | TABLE_ID | 대상 테이블 식별자 | | TABLE_TYPE | 대상 테이블 유형 | | BEGIN_RID | 대상 레코드의 시작 RID | | END_RID | 대상 레코드의 끝 RID | ### V$REPL_READER --- Replication 작동 시, Reader 의 정보를 표시합니다. | 컬럼 이름 | 설명 | | ----------- | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | SENDER_ID | Sender 식별자 | | ID | Reader 식별자 | | STATUS | Reader Thread의 작동상태 | | FETCH_COUNT | FETCH 수행 횟수 | ### V$REPL_READER_META --- Replication 작동 시, Reader 의 메타데이터를 표시합니다. | 컬럼 이름 | 설명 | | ---------- | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | SENDER_ID | Sender 식별자 | | ID | Reader 식별자 | | TABLE_ID | 대상 테이블 식별자 | | TABLE_TYPE | 대상 테이블 유형 | | BEGIN_RID | 대상 레코드의 시작 RID | | END_RID | 대상 레코드의 끝 RID | ### V$REPL_WRITER --- Replication 작동 시, Writer 의 정보를 표시합니다. | 컬럼 이름 | 설명 | | ------------ | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | ID | Writer 식별자 | | STATUS | Writer Thread 의 작동상태 | | APPEND_COUNT | APPEND 수행 횟수 | ### V$REPL_WRITER_META --- Replication 작동 시, Writer 의 메타데이터를 표시합니다. | 컬럼 이름 | 설명 | | ---------- | ---------------------------------- | | HOSTNAME | Replication 이 작동되는 Node 의 Hostname | | ID | Writer 식별자 | | TABLE_ID | 대상 테이블 식별자 | | TABLE_TYPE | 대상 테이블 유형 | | BEGIN_RID | 대상 레코드의 시작 RID | | END_RID | 대상 레코드의 끝 RID | ## Others ### V$TABLES --- V$로 시작하는 모든 Virtual Table 을 표시합니다. | 컬럼 이름 | 설명 | | ----------- | ------------ | | NAME | 테이블 이름 | | TYPE | 테이블 유형 | | DATABASE_ID | 데이터베이스 식별자 | | ID | 테이블 식별자 | | USER_ID | 테이블을 생성한 사용자 | | COLCOUNT | 컬럼의 갯수 | ### V$COLUMNS --- Virtual Table 의 컬럼 정보를 표시합니다. | 컬럼 이름 | 설명 | | -------------------- | ---------- | | NAME | 컬럼명 | | TYPE | 컬럼의 데이터 타입 | | DATABASE_ID | 데이터베이스 식별자 | | ID | 컬럼의 식별자 | | LENGTH | 컬럼의 크기 | | TABLE_ID | 테이블 식별자 | | FLAG | 비공개 데이터 | | PART_PAGE_COUNT | (사용되지 않음) | | PAGE_VALUE_COUNT | (사용되지 않음) | | MINMAX_CACHE_SIZE | (사용되지 않음) | | MAX_CACHE_PART_COUNT | (사용되지 않음) | ### V$RETENTION_JOB --- RETENTION POLICY가 적용된 테이블 정보를 표시합니다. | 컬럼 이름 | 설명 | |-------------------|------------------------------------------| | USER_NAME | 사용자 이름 | | TABLE_NAME | 대상 TAG TABLE 이름 | | POLICY_NAME | 적용되어 있는 POLICY 이름 | | STATE | RETENTION 상태 (RUNNING/WAITING/STOPPED) | | LAST_DELETED_TIME | 마지막으로 삭제된 시간 | ### V$USER_AUTH_KEYS --- Challenge 인증에 등록된 공개 키 정보를 표시합니다. | 컬럼 이름 | 설명 | | -- | -- | | KEY_ID | 키 식별자 | | USER_ID | 사용자 식별자 | | USER_NAME | 사용자 이름 | | KEY_ALGO | 키 알고리즘 | | KEY_PARAM | 키 파라미터 | | PUBKEY | 공개 키 텍스트 | | ACTIVATED | 키 활성화 여부 | | VALID_AFTER | 키 유효 시작일 | | VALID_BEFORE | 키 유효 종료일 | | COMMENT | 키 설명 | --- title: "16.4 명령행 도구 레퍼런스" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/ language: kr kind: section --- # 16.4 명령행 도구 레퍼런스 Machbase는 서버 관리, 데이터 가져오기/내보내기, 쿼리 실행을 위한 다양한 명령행 도구를 제공합니다. 이 섹션은 각 도구의 옵션과 사용법을 빠르게 찾아볼 수 있는 레퍼런스입니다. ## 도구 목록 | 도구 | 에디션 | 설명 | |------|--------|------| | [machadmin](./machadmin/) | Standard / Cluster | 서버 시작/종료, 데이터베이스 생성/삭제, 라이선스 관리 | | [machsql](./machsql/) | Standard / Cluster | 대화형 SQL 터미널 도구 | | [machloader](./machloader/) | Standard / Cluster | CSV 등 텍스트 파일 가져오기/내보내기 | | [csvimport / csvexport](./csvimport-csvexport/) | Standard / Cluster | CSV 파일 전용 간편 가져오기/내보내기 래퍼 | | [tagmetaimport](./tagmetaimport/) | Standard / Cluster | TAG 테이블 메타데이터 일괄 가져오기 | | [machclusterctl](./machclusterctl/) | Cluster | 클러스터 전체 시작/종료/관리 도구 | | [machcoordinatoradmin](./machcoordinatoradmin/) | Cluster | Coordinator 노드 관리 및 클러스터 구성 도구 | | [machdeployeradmin](./machdeployeradmin/) | Cluster | Deployer 노드 관리 도구 | ## 공통 접속 옵션 다음은 `machsql`의 접속 옵션입니다. 도구마다 옵션 이름과 기본값이 다르므로 다른 도구를 사용할 때는 해당 도구의 옵션 사전이나 `--help` 출력을 확인하십시오. 특히 서버를 직접 관리하는 `machadmin`의 옵션을 SQL 클라이언트의 접속 옵션과 혼동하지 마십시오. | 옵션 | 기본값 | 설명 | |------|--------|------| | `-s`, `--server` | 127.0.0.1 | 서버 IP 주소 | | `-P`, `--port` | 5656 | 서버 포트 번호 | | `-u`, `--user` | SYS | 사용자 이름 | | `-p`, `--password` | MANAGER | 사용자 비밀번호 | ## 도구 위치 설치 패키지에 포함된 도구는 `$MACHBASE_HOME/bin/` 디렉터리에서 확인합니다. 사용 가능한 도구는 설치한 Edition과 패키지에 따라 다릅니다. ```bash ls $MACHBASE_HOME/bin/ # machadmin machsql machloader csvimport csvexport tagmetaimport ... ``` PATH에 `$MACHBASE_HOME/bin`이 등록되어 있으면 도구 이름만으로 실행할 수 있습니다. ```bash export PATH=$MACHBASE_HOME/bin:$PATH machadmin -e ``` --- title: "16.4.1 machadmin" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/machadmin/ language: kr kind: page --- # 16.4.1 machadmin `machadmin`은 Machbase 서버를 시작하거나 종료하고 데이터베이스 생성, 삭제 및 실행 상태를 확인하는 관리 도구입니다. ## 옵션 목록 ```bash machadmin -h ``` | 옵션 | 설명 | |------|------| | `-u`, `--startup` | Machbase 서버 시작 | | `--recovery[=simple,complex,reset]` | 시작 시 복구 모드 지정 (기본값: simple) | | `-s`, `--shutdown` | Machbase 서버 정상 종료 (graceful) | | `-k`, `--kill` | Machbase 서버 강제 종료 | | `-c`, `--createdb` | Machbase 데이터베이스 생성 | | `-d`, `--destroydb` | Machbase 데이터베이스 삭제 | | `-e`, `--check` | 서버 실행 상태 확인 | | `-i`, `--silent` | 배너 출력 없이 실행 | | `-r`, `--restore` | 백업에서 데이터베이스 복구 | | `-x`, `--extract` | 백업 파일을 백업 디렉토리로 변환 | | `-w`, `--viewimage` | 백업 이미지 파일 정보 출력 | | `-t`, `--licinstall` | 라이선스 파일 설치 | | `-f`, `--licinfo` | 설치된 라이선스 정보 출력 | | `--home-path=path` | Machbase 홈 경로 지정 | ## 서버 시작 ```bash machadmin -u ``` ### 복구 모드 지정 ```bash machadmin -u --recovery=simple # 기본 복구 (전원 정상 종료 후) machadmin -u --recovery=complex # 전원 손실 후 재시작 시 자동 적용 machadmin -u --recovery=reset # simple/complex 복구 실패 시 전체 검사 ``` | 복구 모드 | 설명 | |----------|------| | `simple` | 정상 종료 후 재시작 시 기본 복구. 실행 시간이 짧음 | | `complex` | 전원 손실 등 비정상 종료 후 재시작 시 자동 적용. `simple`보다 오래 걸림 | | `reset` | 모든 테이블 데이터를 전체 검사하여 복구. 일부 데이터 손실 가능 | ## 서버 종료 정상 종료 (진행 중인 작업 완료 후 종료): ```bash machadmin -s ``` 강제 종료 (즉시 프로세스 종료): ```bash machadmin -k ``` ## 데이터베이스 생성 및 삭제 ```bash # 데이터베이스 생성 machadmin -c # 데이터베이스 삭제 (확인 프롬프트 표시) machadmin -d ``` ## 서버 실행 상태 확인 ```bash machadmin -e ``` 서버가 실행 중이면 PID를 출력합니다. ``` Machbase server is already running with PID (14098). ``` 서버가 실행 중이 아니면 오류를 출력합니다. ``` [ERR] Server is not running. ``` ## 데이터베이스 복구 백업 디렉토리에서 데이터베이스를 복구합니다. ```bash machadmin -r /path/to/backup ``` 예시: ```bash machadmin -r /home/mach/backup/machbase_backup_20240101 ``` ## 라이선스 관리 라이선스 파일 설치: ```bash machadmin -t /path/to/license.dat ``` 설치된 라이선스 정보 확인: ```bash machadmin -f ``` ## 무음 모드 배너와 상태 메시지 없이 실행합니다. 스크립트에서 활용하기 좋습니다. ```bash machadmin -i -u # 서버 시작 (배너 없이) machadmin -i -s # 서버 종료 (배너 없이) machadmin -i -e # 상태 확인 (배너 없이) ``` ## 사용 예시 ```bash # 데이터베이스 초기 설정 및 서버 시작 machadmin -c machadmin -u # 서버 상태 확인 후 종료 machadmin -e machadmin -s # 라이선스 갱신 machadmin -s machadmin -t new_license.dat machadmin -u ``` --- title: "16.4.2 machsql" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/machsql/ language: kr kind: page --- # 16.4.2 machsql `machsql`은 터미널에서 SQL 쿼리를 대화형으로 실행하는 클라이언트 도구입니다. SQL 스크립트 파일 실행, 결과 파일 저장, 공개키 인증도 지원합니다. ## 옵션 목록 ```bash machsql -h ``` | 짧은 옵션 | 긴 옵션 | 기본값 | 설명 | |----------|---------|--------|------| | `-s` | `--server` | 127.0.0.1 | 접속할 서버 IP 주소 | | `-P` | `--port` | 5656 | 서버 포트 번호 | | `-u` | `--user` | SYS | 사용자 이름 | | `-p` | `--password` | MANAGER | 사용자 비밀번호 | | `-K` | `--auth-key-file` | - | 공개키 인증용 개인키 파일 경로 (8.5 이상) | | | `--auth-sig-scheme` | - | 인증 서명 스킴. `ECDSA`, `RSA_PKCS1_V15`, `RSA_PSS` (8.5 이상) | | `-f` | `--script` | - | 실행할 SQL 스크립트 파일 | | `-o` | `--output` | - | 쿼리 결과를 저장할 파일 이름 | | `-r` | `--format` | csv | 출력 파일 포맷 (`csv`, `json` 등) | | `-z` | `--timezone` | - | 타임존 설정. 예: `+0900`, `-1230` | | `-n` | `--nls` | - | NLS 설정 | | `-c` | `--connstr` | - | 추가 연결 매개변수 문자열 (6.1 이상) | | `-D` | `--database` | `MACHBASEDB` | 연결 직후 사용할 논리 데이터베이스 (8.7.0 Standard) | | `-i` | `--silent` | - | 저작권 배너 없이 실행 | | `-v` | `--verbose` | - | 상세 출력 | | `-x` | `--testing` | - | 테스트 모드로 실행 | | `-h` | `--help` | - | 옵션 목록 출력 | ## 접속 예시 기본 접속: ```bash machsql -s 127.0.0.1 -u SYS -p MANAGER machsql --server=localhost --user=SYS --password=MANAGER ``` 포트 지정: ```bash machsql -s 192.168.1.10 -P 5656 -u SYS -p MANAGER ``` SQL 스크립트 실행: ```bash machsql -s 127.0.0.1 -u SYS -p MANAGER -f create_tables.sql ``` 타임존 지정: ```bash machsql -s 127.0.0.1 -u SYS -p MANAGER -z +0900 machsql -s 127.0.0.1 -u SYS -p MANAGER -z -1230 ``` 결과를 파일로 저장: ```bash machsql -s 127.0.0.1 -u SYS -p MANAGER -o result.csv -f query.sql ``` ## 공개키 인증 (Machbase 8.5 이상) 비밀번호 대신 공개키 기반 challenge 인증을 사용할 수 있습니다. ECDSA 키로 접속: ```bash machsql -s 127.0.0.1 -u app_user \ -K /opt/machbase/keys/app_user_ecdsa.pem \ --auth-sig-scheme=ECDSA ``` RSA-PSS 키로 접속: ```bash machsql -s 127.0.0.1 -u app_user \ -K /opt/machbase/keys/app_user_rsa.pem \ --auth-sig-scheme=RSA_PSS ``` 지원 키 알고리즘: | 알고리즘 | 키 파라미터 | 기본 서명 스킴 | |---------|-----------|--------------| | ECDSA | P-256, P-384, P-521 | `ECDSA` | | RSA | 2048, 3072, 4096 bits | `RSA_PKCS1_V15` | ## 추가 연결 매개변수 (6.1 이상) `-c` 옵션으로 추가 연결 파라미터를 지정합니다. ```bash machsql -s 127.0.0.1 -u SYS -p MANAGER -P 5656 \ -c 'ALTERNATIVE_SERVERS=192.168.0.147:9209;CONNECTION_TIMEOUT=10' ``` 환경변수로도 설정할 수 있습니다. ```bash export MACHBASE_CONNECTION_STRING="ALTERNATIVE_SERVERS=192.168.0.148:8888;CONNECTION_TIMEOUT=3" machsql -s 127.0.0.1 -u SYS -p MANAGER ``` `-c` 옵션이 환경변수보다 우선 적용됩니다. ## 논리 데이터베이스 선택 Machbase 8.7.0 Standard Edition에서는 `-D` 또는 `--database`로 연결 직후 사용할 논리 데이터베이스를 지정할 수 있습니다. ```bash machsql -s 127.0.0.1 -u app_a -p 'AppA#1234' -D factory_a machsql -s 127.0.0.1 -u app_a -p 'AppA#1234' --database=factory_a ``` `-c` 연결 문자열을 사용할 때는 `DATABASE=factory_a` 또는 호환 별칭인 `DBNAME=factory_a`를 지정할 수 있습니다. `-D`와 연결 문자열의 database 값이 다르면 연결을 거부하므로 하나만 지정하거나 같은 값을 사용하십시오. 연결 후 다음 SQL로 실제 server catalog를 확인합니다. ```sql SELECT CURRENT_DATABASE(); SHOW CURRENT DATABASE; ``` ## machsql 내장 명령 machsql 프롬프트(`Mach>`)에서 사용할 수 있는 내장 명령입니다. | 명령 | 설명 | |------|------| | `SHOW TABLES` | 전체 테이블 목록 출력 | | `SHOW TABLE table_name` | 특정 테이블의 컬럼 및 인덱스 정보 출력 | | `SHOW INDEXES` | 전체 인덱스 목록 출력 | | `SHOW INDEX index_name` | 특정 인덱스 정보 출력 | | `SHOW INDEXGAP` | 인덱스 생성 GAP 정보 출력 | | `SHOW LSM` | LSM 인덱스 생성 정보 출력 | | `SHOW TABLESPACES` | 전체 테이블스페이스 목록 출력 | | `SHOW TABLESPACE name` | 특정 테이블스페이스 정보 출력 | | `SHOW STORAGE` | 테이블별 디스크 사용량 출력 | | `SHOW STATEMENTS` | 서버에 등록된 쿼리 목록 출력 | | `SHOW USERS` | 사용자 목록 출력 | | `SHOW LICENSE` | 라이선스 정보 출력 | | `SHOW DATABASES` | active/mounted 데이터베이스 목록 출력 | | `SHOW CURRENT DATABASE` | 현재 session의 데이터베이스 출력 | | `SHOW LAST ROWID` | 가장 최근에 성공한 단일 INSERT의 ROWID 출력 | | `SHOW LASTID` | `SHOW LAST ROWID`와 같은 명령 | ### 마지막 INSERT의 ROWID 확인 Machbase 8.7.0 Standard Edition에서는 단일 `INSERT ... VALUES`를 실행한 직후 입력된 행의 ROWID를 확인할 수 있습니다. ```sql INSERT INTO orders(item) VALUES('pump'); SHOW LAST ROWID; ``` ```text Last ROWID : 2048 ``` `SHOW LASTID`도 같은 값을 출력합니다. 반환할 ROWID가 없으면 `0`이 아니라 `NULL`을 출력합니다. INSERT 실패, batch·Append·loader, `INSERT ... SELECT`, UPSERT 또는 재접속 뒤에는 이전 값을 사용하지 않습니다. SELECT와 COMMIT 같은 비 INSERT 명령은 마지막 값을 유지합니다. 테이블별 ROWID 조건과 SDK에서 확인하는 방법은 [ROWID와 INSERT 결과 ID](/dbms/reference/sql/rowid/)를 참고하십시오. ## DESC와 PRIMARY KEY 메타데이터 `DESC table_name`은 컬럼과 인덱스 정보에 이어 `[ PRIMARY KEY ]` 섹션을 표시합니다. 이 섹션에서 PRIMARY KEY 이름, 컬럼 이름, key sequence를 확인할 수 있습니다. TRANSACTION· LOOKUP·VOLATILE 테이블의 선언된 PK와 TAG 테이블의 `NAME`이 대상이며, 일반 LOG 테이블에는 PK 행이 표시되지 않습니다. ```sql DESC ACCOUNT; ``` 이 출력은 SELECT 결과 컬럼 메타데이터와 별개입니다. SDK에서 SELECT 결과의 PK 여부를 확인하려면 [PRIMARY KEY 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-primary-key-metadata)를 참고하십시오. ## ARRAY 표시와 DESC Machbase DBMS 8.7.0의 `DESC`는 ARRAY 컬럼을 `INT32[3]`, `DECIMAL(12,4)[2]`와 같은 canonical 선언으로 표시합니다. 조회 결과는 `[value,null,value]` 형식이며 소문자 `null`은 element NULL입니다. 컬럼 전체가 NULL이면 일반 SQL `NULL`로 표시합니다. ```sql SELECT ID, CHANNELS, ARRAY_LENGTH(CHANNELS), CHANNELS[1] FROM SENSOR_ARRAY ORDER BY ID; ``` ARRAY 선언, NULL 구분과 표현식은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ## Named Bind Parameter `machsql`의 `PREPARE` SQL에는 `:name` marker를 사용할 수 있습니다. 값은 이름이 아니라 SQL에 나타난 순서대로 `$1`, `$2`, ... 변수에 지정합니다. ```sql PREPARE INSERT INTO SENSOR_DATA (ID, NAME, VALUE) VALUES (:id, :name, :value); $1 := 900; $2 := 'machsql-client'; $3 := 72.125000; EXECUTE; PREPARE CLEAN; ``` 같은 이름이 반복되어도 각 발생 위치에 값을 지정합니다. ```sql PREPARE SELECT ID, NAME FROM SENSOR_DATA WHERE ID = :id OR PARENT_ID = :id; $1 := 900; $2 := 900; EXECUTE; PREPARE CLEAN; ``` 이름 문법과 발생 순서 규칙은 [Named Bind Parameter syntax](../../sql/syntax/named-bind-parameter-syntax/)를 참고하십시오. ## 사용 예시 ```bash # 대화형 모드로 접속 machsql -s 127.0.0.1 -u SYS -p MANAGER # 접속 후 테이블 확인 Mach> SHOW TABLES; # 테이블 구조 확인 Mach> SHOW TABLE sensor_data; # SQL 스크립트를 실행하고 결과를 CSV로 저장 machsql -s 127.0.0.1 -u SYS -p MANAGER \ -f report.sql -o report_output.csv -i ``` --- title: "16.4.3 machloader" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/machloader/ language: kr kind: page --- # 16.4.3 machloader `machloader`는 텍스트 파일(CSV 등)과 Machbase 서버 간에 데이터를 가져오거나 내보내는 범용 데이터 로딩 도구입니다. APPEND 모드를 기본으로 지원하며 스키마 파일을 통해 복잡한 변환도 처리할 수 있습니다. ## 옵션 목록 ```bash machloader -h ``` | 옵션 | 설명 | |------|------| | `-s`, `--server=SERVER` | 서버 IP 주소 (기본값: 127.0.0.1) | | `-P`, `--port=PORT` | 서버 포트 번호 (기본값: 5656) | | `-u`, `--user=USER` | 사용자 이름 (기본값: SYS) | | `-p`, `--password=PASSWORD` | 사용자 비밀번호 (기본값: MANAGER) | | `-i`, `--import` | 가져오기 모드 | | `-o`, `--export` | 내보내기 모드 | | `-c`, `--schema` | 스키마 파일 생성 모드 | | `-t`, `--table=TABLE_NAME` | 대상 테이블 이름 | | `-f`, `--form=SCHEMA_FILE` | 스키마 파일 이름 | | `-d`, `--data=DATA_FILE` | 데이터 파일 이름 | | `-m`, `--mode=MODE` | 가져오기 모드. `append`(기본값) 또는 `replace` | | `-H`, `--header` | 헤더 행 존재 여부. 가져오기 시 첫 행을 헤더로 인식, 내보내기 시 컬럼명을 헤더로 생성 | | `-D`, `--delimiter=DELIMITER` | 필드 구분자 (기본값: `,`) | | `-n`, `--newline=NEWLINE` | 레코드 구분자 (기본값: `\n`) | | `-e`, `--enclosure=ENCLOSURE` | 필드 인클로저 문자 | | `-r`, `--format=FORMAT` | 파일 포맷 (기본값: csv) | | `-E`, `--encoding=CHARSET` | 파일 인코딩. UTF8(기본값), ASCII, MS949, KSC5601, EUCJP, SHIFTJIS, BIG5, GB231280, UTF16 | | `-F`, `--dateformat=DATEFORMAT` | datetime 컬럼 날짜 형식. `unixtimestamp` 또는 `nanotimestamp` 지정 가능 | | `-z`, `--timezone` | 타임존 설정. 예: `+0900`, `-1230` | | `-a`, `--atime` | `_ARRIVAL_TIME` 컬럼 포함 여부 (기본값: 미포함) | | `-C`, `--create` | 가져오기 시 테이블이 없으면 자동 생성 | | `-l`, `--log=LOG_FILE` | 실행 로그 파일 | | `-b`, `--bad=BAD_FILE` | 가져오기 실패 행을 기록하는 bad 파일 | | `--first=FIRST_ROW` | 처리를 시작할 첫 번째 행 번호 | | `-I`, `--silent` | 배너 및 진행 상태 출력 없이 실행 | | `-S`, `--slash` | 백슬래시 구분자 지정 | | `--summary` | 선택된 옵션 값을 출력하고 종료 (실제 처리 안 함) | | `-h`, `--help` | 옵션 목록 출력 | ## CSV 파일 가져오기 기본 가져오기: ```bash machloader -i -d data.csv -t sensor_data ``` 서버 접속 정보 지정: ```bash machloader -i -s 192.168.0.10 -P 5656 -u SYS -p MANAGER \ -d data.csv -t sensor_data ``` 헤더 행이 있는 CSV 가져오기: ```bash machloader -i -d data.csv -t sensor_data -H ``` 기존 데이터 삭제 후 가져오기 (replace 모드): ```bash machloader -i -d data.csv -t sensor_data -m replace ``` 특정 행부터 시작: ```bash machloader -i -d data.csv -t sensor_data --first=10 ``` ## CSV 파일 내보내기 ```bash machloader -o -d output.csv -t sensor_data machloader -o -d output.csv -t sensor_data -H ``` `_ARRIVAL_TIME` 컬럼 포함 내보내기: ```bash machloader -o -d output.csv -t sensor_data -a ``` ### ARRAY 컬럼 Machbase DBMS 8.7.0의 ARRAY 컬럼은 `[value,null,value]` 형식으로 가져오고 내보냅니다. 쉼표가 ARRAY 내부에 포함되므로 CSV 필드를 enclosure로 감쌉니다. ```csv 1,"[1.5,null,3.5,4.5]" ``` NULL field는 whole NULL이고 `"[null,null]"`은 모든 요소가 NULL인 non-NULL ARRAY입니다. `-C` 자동 테이블 생성은 ARRAY 타입을 추론하지 않으므로 ARRAY 컬럼이 필요한 테이블은 먼저 명시적으로 생성합니다. 자세한 타입과 NULL 규칙은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)을 참고하십시오. ## 인코딩 및 구분자 설정 EUC-KR 인코딩, 탭 구분자: ```bash machloader -i -d data.txt -t table_name -E MS949 -D '\t' ``` 파이프(`|`) 구분자: ```bash machloader -i -d data.txt -t table_name -D '|' machloader -o -d data.txt -t table_name -D '|' ``` ## 타임존 지정 ```bash machloader -i -d data.csv -t sensor_data -z +0900 machloader -i -d data.csv -t sensor_data -z -1230 ``` ## datetime 형식 지정 커맨드라인에서 직접 지정: ```bash machloader -i -d data.csv -t sensor_data \ -F "_arrival_time YYYY-MM-DD HH24:MI:SS" ``` Unix 타임스탬프로 입력: ```bash machloader -i -d data.csv -t sensor_data \ -F "time_column unixtimestamp" ``` 나노초 타임스탬프로 입력: ```bash machloader -i -d data.csv -t sensor_data \ -F "time_column nanotimestamp" ``` ## 스키마 파일 사용 스키마 파일을 생성합니다: ```bash machloader -c -t sensor_data -f sensor_data.fmt ``` 스키마 파일로 가져오기/내보내기: ```bash machloader -i -f sensor_data.fmt -d data.csv machloader -o -f sensor_data.fmt -d output.csv ``` 스키마 파일 형식 예시 (`sensor_data.fmt`): ``` table sensor_data { name varchar(64); time datetime; value double; } DATEFORMAT time "YYYY-MM-DD HH24:MI:SS" ``` 특정 컬럼 무시: ``` table sensor_data { id integer; name varchar(64); extra varchar(32) IGNORE; } ``` ## 로그 및 bad 파일 ```bash machloader -i -d data.csv -t sensor_data \ -l import.log -b import.bad ``` - `-l`: 가져오기 실행 로그(성공/실패 통계) - `-b`: 실패한 행 데이터를 원본 형식으로 기록 ## 자동 테이블 생성 테이블이 없을 때 자동으로 생성합니다. 컬럼명은 `c0`, `c1`, ... 순서로, 타입은 `varchar(32767)`로 생성됩니다. ```bash machloader -i -d data.csv -t new_table -C machloader -i -d data.csv -t new_table -C -H # 헤더를 컬럼명으로 사용 ``` ## 사용 예시 ```bash # 실제 가져오기 전 설정 확인 (--summary) machloader -i -d data.csv -t sensor_data --summary # 대용량 파일 가져오기 (로그 및 bad 파일 지정) machloader -i -d bigdata.csv -t sensor_data \ -H -z +0900 \ -l import_20240101.log -b import_20240101.bad # 전체 테이블 내보내기 machloader -o -d export_20240101.csv -t sensor_data -H -a ``` --- title: "16.4.4 csvimport / csvexport" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/csvimport-csvexport/ language: kr kind: page --- # 16.4.4 csvimport / csvexport `csvimport`와 `csvexport`는 CSV 파일 전용 간편 가져오기/내보내기 래퍼 도구입니다. `machloader`의 CSV 관련 옵션을 단순화하여 제공하며, 아래에서 명시되지 않은 옵션은 `machloader`와 동일하게 사용할 수 있습니다. ## csvimport CSV 파일을 Machbase 테이블로 가져옵니다. ### 옵션 목록 | 옵션 | 설명 | |------|------| | `-t`, `--table=TABLE_NAME` | 대상 테이블 이름 | | `-d`, `--data=DATA_FILE` | 가져올 CSV 파일 이름 | | `-s`, `--server=SERVER` | 서버 IP 주소 (기본값: 127.0.0.1) | | `-P`, `--port=PORT` | 서버 포트 번호 (기본값: 5656) | | `-u`, `--user=USER` | 사용자 이름 (기본값: SYS) | | `-p`, `--password=PASSWORD` | 사용자 비밀번호 (기본값: MANAGER) | | `-H` | CSV 파일의 첫 번째 행을 헤더로 인식하고 가져오기에서 제외 | | `-C` | 테이블이 없을 때 자동 생성 (`-H`와 함께 사용 시 헤더를 컬럼명으로 사용) | | `-m`, `--mode=MODE` | 가져오기 모드. `append`(기본값) 또는 `replace` | | `-a`, `--atime` | `_ARRIVAL_TIME` 컬럼 포함 | | `-F`, `--dateformat=DATEFORMAT` | datetime 컬럼 날짜 형식 | | `-l`, `--log=LOG_FILE` | 실행 로그 파일 | | `-b`, `--bad=BAD_FILE` | 가져오기 실패 행을 기록하는 bad 파일 | | `-I`, `--silent` | 배너 및 상태 출력 없이 실행 | ### 기본 사용법 테이블 이름과 파일 이름을 지정합니다. ```bash csvimport -t table_name -d data.csv ``` 옵션 없이 인수만으로도 실행할 수 있습니다 (순서 무관). ```bash csvimport table_name data.csv csvimport data.csv table_name ``` ### 헤더 행 처리 CSV 파일의 첫 번째 행을 헤더로 인식하고 데이터에서 제외합니다. ```bash csvimport -t table_name -d data.csv -H ``` ### 자동 테이블 생성 테이블이 없을 때 자동으로 생성합니다. ```bash # 컬럼명을 c0, c1, ... 으로 자동 생성 csvimport -t table_name -d data.csv -C # CSV 헤더를 컬럼명으로 사용 csvimport -t table_name -d data.csv -C -H ``` 자동 생성된 컬럼의 타입은 모두 `varchar(32767)`입니다. ### replace 모드 기존 데이터를 삭제하고 CSV 파일로 다시 채웁니다. ```bash csvimport -t table_name -d data.csv -m replace ``` ### 서버 접속 정보 지정 ```bash csvimport -s 192.168.0.10 -P 5656 -u SYS -p MANAGER \ -t sensor_data -d data.csv ``` ## csvexport Machbase 테이블 데이터를 CSV 파일로 내보냅니다. ### 옵션 목록 | 옵션 | 설명 | |------|------| | `-t`, `--table=TABLE_NAME` | 내보낼 테이블 이름 | | `-d`, `--data=DATA_FILE` | 저장할 CSV 파일 이름 | | `-s`, `--server=SERVER` | 서버 IP 주소 (기본값: 127.0.0.1) | | `-P`, `--port=PORT` | 서버 포트 번호 (기본값: 5656) | | `-u`, `--user=USER` | 사용자 이름 (기본값: SYS) | | `-p`, `--password=PASSWORD` | 사용자 비밀번호 (기본값: MANAGER) | | `-H` | 컬럼 이름을 CSV 헤더로 생성 | | `-a`, `--atime` | `_ARRIVAL_TIME` 컬럼 포함 | | `-F`, `--dateformat=DATEFORMAT` | datetime 컬럼 날짜 형식 | | `-l`, `--log=LOG_FILE` | 실행 로그 파일 | | `-I`, `--silent` | 배너 및 상태 출력 없이 실행 | ### 기본 사용법 ```bash csvexport -t table_name -d output.csv ``` 옵션 없이 인수만으로도 실행할 수 있습니다. ```bash csvexport table_name output.csv csvexport output.csv table_name ``` ### 헤더 포함 내보내기 컬럼 이름을 CSV 파일의 첫 번째 행(헤더)으로 출력합니다. ```bash csvexport -t table_name -d output.csv -H ``` ### `_ARRIVAL_TIME` 포함 내보내기 ```bash csvexport -t table_name -d output.csv -a ``` ## 사용 예시 ```bash # 기본 가져오기 csvimport -t sensor_data -d sensor_20240101.csv # 헤더가 있는 CSV 가져오기 csvimport -t sensor_data -d sensor_20240101.csv -H # 전체 내보내기 (헤더 포함) csvexport -t sensor_data -d export_20240101.csv -H # 로그 파일 지정하여 가져오기 csvimport -t sensor_data -d data.csv -H \ -l import.log -b import.bad # 원격 서버에서 내보내기 csvexport -s 192.168.0.10 -P 5656 -u SYS -p MANAGER \ -t sensor_data -d remote_export.csv -H -a ``` --- title: "16.4.5 tagmetaimport" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/tagmetaimport/ language: kr kind: page --- # 16.4.5 tagmetaimport `tagmetaimport`는 TAG 테이블의 메타데이터를 CSV 파일에서 일괄 가져오는 도구입니다. 대량의 TAG 이름과 메타데이터를 등록할 때 사용합니다. 기존 태그를 자동 갱신하거나 파일 전체를 하나의 트랜잭션으로 반영하는 도구는 아닙니다. ## 옵션 목록 ```bash tagmetaimport -h ``` | 옵션 | 설명 | |------|------| | `-s`, `--server=SERVER` | 서버 IP 주소 (기본값: 127.0.0.1) | | `-P`, `--port=PORT` | 서버 포트 번호 (기본값: 5656) | | `-u`, `--user=USER` | 사용자 이름 (기본값: SYS) | | `-p`, `--password=PASSWORD` | 사용자 비밀번호 (기본값: MANAGER) | | `-t`, `--table=TABLE_NAME` | 대상 메타데이터 저장 테이블 이름. 논리 sensor_tag에는 _SENSOR_TAG_META 지정 | | `-d`, `--data=DATA_FILE` | 메타데이터 CSV 파일 경로 | | `-l`, `--log=LOG_FILE` | 로그 파일 경로 | | `-b`, `--bad=BAD_FILE` | 입력 실패 레코드 저장 파일 경로 | | `-H`, `--header` | CSV 파일의 첫 번째 행을 헤더로 인식 | | `-D`, `--delimiter=DELIMITER` | 필드 구분자 (기본값: `,`) | | `-E`, `--encoding=CHARSET` | 파일 인코딩 (기본값: UTF8) | | `-I`, `--silent` | 진행 출력을 줄임. 완료 요약의 성공·실패 건수는 확인 | | `-h`, `--help` | 옵션 목록 출력 | ## 입력 파일 형식 메타데이터 CSV 파일은 TAG 테이블의 메타 컬럼 순서에 맞게 작성합니다. TAG 테이블 정의 예시: ```sql CREATE TAG TABLE sensor_tag ( name VARCHAR(64) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE SUMMARIZED ) METADATA ( unit VARCHAR(32), location VARCHAR(128) ); ``` 이 테이블의 메타데이터 CSV 파일 (`tag_meta.csv`): ``` name,unit,location sensor_001,celsius,Building-A Floor-1 sensor_002,celsius,Building-A Floor-2 sensor_003,bar,Boiler-Room sensor_004,rpm,Motor-Section ``` 헤더 없이 데이터만 있는 경우: ``` sensor_001,celsius,Building-A Floor-1 sensor_002,celsius,Building-A Floor-2 ``` ## 사용 예시 ### 기본 가져오기 ```bash tagmetaimport -s 127.0.0.1 -P 5656 -u SYS -p MANAGER \ -t _SENSOR_TAG_META -d tag_meta.csv -H ``` ### 헤더가 있는 CSV 파일 가져오기 ```bash tagmetaimport -s 127.0.0.1 -P 5656 -u SYS -p MANAGER \ -t _SENSOR_TAG_META -d tag_meta.csv -H ``` ### 원격 서버에 가져오기 ```bash tagmetaimport -s 192.168.0.10 -P 5656 -u SYS -p MANAGER \ -t _SENSOR_TAG_META -d tag_meta.csv -H ``` ### 탭 구분자 파일 ```bash tagmetaimport -s 127.0.0.1 -P 5656 -u SYS -p MANAGER \ -t _SENSOR_TAG_META -d tag_meta.tsv -D '\t' -H ``` ### EUC-KR 인코딩 파일 ```bash tagmetaimport -s 127.0.0.1 -P 5656 -u SYS -p MANAGER \ -t _SENSOR_TAG_META -d tag_meta_kr.csv -E MS949 -H ``` ## 동작 방식 - `-t`는 자동으로 논리 TAG를 METADATA 대상으로 바꾸지 않습니다. 논리 테이블 sensor_tag의 대상은 `_SENSOR_TAG_META`입니다. 이 이름은 도구의 입력 대상 지정에만 사용합니다. - 이미 존재하는 태그 이름은 일반 METADATA INSERT에서 오류이며 실패 건수와 bad/log 파일로 확인합니다. - 데이터가 입력된 이후에 메타데이터를 추가하려는 경우 이 도구를 사용하면 효율적입니다. - 소량의 메타데이터는 SQL INSERT 또는 `machsql`에서 직접 입력할 수도 있습니다. ```sql -- machsql에서 직접 메타데이터 삽입 INSERT INTO sensor_tag METADATA (name, unit, location) VALUES ('sensor_005', 'volt', 'Panel-Room'); ``` ## 주의 사항 - TAG 테이블이 사전에 생성되어 있어야 합니다. - CSV 파일의 컬럼 순서는 TAG 테이블의 메타 컬럼 순서와 일치해야 합니다. - `BASETIME` 컬럼(`time`)과 `SUMMARIZED` 컬럼(`value`)은 메타데이터 파일에 포함하지 않습니다. 기존 값 변경은 `UPDATE sensor_tag METADATA ...` 또는 명시적인 SQL UPSERT로 수행합니다. 재현 가능한 신규 입력·재입력 실패 예제는 [메타데이터 일괄 등록](../../../tag-table-usage/tagmetaimport/)을 참고하십시오. `_LAST_UPDATE_TIME`은 새 메타데이터 입력과 실제 값 변경 시 서버가 관리합니다. --- title: "16.4.7 machclusterctl" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/machclusterctl/ language: kr kind: page --- # 16.4.7 machclusterctl `machclusterctl`은 Machbase Cluster Edition의 클러스터 전체를 단일 명령으로 관리하는 도구입니다. YAML 설정 파일을 검증하고, 신규 설치, 실행 중 구성 반영, 업그레이드, 시작/종료, 상태 확인 등을 수행합니다. ## 주요 명령 | 명령 | 설명 | |------|------| | `validate` | `cluster.yaml` 검증 | | `install` | `cluster.yaml` 기반 신규 클러스터 설치 | | `apply` | 실행 중인 클러스터에 설정 변경 반영 | | `upgrade` | 패키지 업그레이드 (`--online`, `--full-stop`) | | `export` | 실행 중인 클러스터 구성을 flat YAML로 내보내기 | | `status` | 클러스터 전체 노드 상태 확인 | | `connect` | Broker/Warehouse alias에 `machsql`로 접속 | | `start` | 클러스터 전체 노드 시작 | | `stop` | 클러스터 전체 노드 정상 종료 | | `destroy` | 클러스터 제거 (데이터 포함) | ## 사용법 ```bash machclusterctl [options] ``` ## 명령 상세 ### validate YAML 설정 파일을 검증합니다. ```bash machclusterctl validate -f cluster.yaml ``` ### install YAML 설정 파일을 읽어 새 클러스터를 설치합니다. ```bash machclusterctl install -f cluster.yaml ``` ### apply 실행 중인 클러스터에 설정 변경을 반영합니다. ```bash machclusterctl apply -f cluster.yaml ``` ### upgrade 패키지를 업그레이드합니다. ```bash machclusterctl upgrade --online broker machclusterctl upgrade --full-stop ``` ### start 클러스터 전체를 순서에 맞게 시작합니다. Coordinator → Deployer → Broker → Warehouse 순으로 기동됩니다. ```bash machclusterctl start machclusterctl start -f cluster.yaml ``` ### stop 클러스터 전체를 정상 종료합니다. ```bash machclusterctl stop ``` ### destroy 클러스터를 완전히 제거합니다. 데이터베이스 파일도 삭제되므로 주의해서 사용합니다. ```bash machclusterctl destroy ``` ### status 클러스터 각 노드의 현재 상태를 출력합니다. ```bash machclusterctl status ``` ### connect Broker 노드에 `machsql`로 접속합니다. ```bash machclusterctl connect ``` ### export 현재 클러스터 구성을 YAML 파일로 내보냅니다. ```bash machclusterctl export -o cluster_backup.yaml ``` ## YAML 설정 파일 구조 `machclusterctl validate`, `install`, `apply`에 사용하는 YAML 설정 파일의 기본 구조입니다. ```yaml cluster: coordinator: host: 192.168.0.32 port: 5101 http_port: 5102 home: /home/machbase/coordinator1 deployer: - host: 192.168.0.32 port: 5201 home: /home/machbase/deployer1 broker: - host: 192.168.0.32 port: 5301 http_port: 5302 home: /home/machbase/broker1 service_port: 5757 warehouse: - group: Group1 host: 192.168.0.32 port: 5401 http_port: 5402 home: /home/machbase/warehouse_a1 service_port: 5400 ``` ## 옵션 | 옵션 | 설명 | |------|------| | `-f`, `--file` | 클러스터 설정 YAML 파일 경로 | | `-s`, `--silent` | 진행 로그를 줄여 출력 | | `-v`, `--verbose` | 상세 진행 로그 출력 | | `--node` | `start`/`stop` 대상 노드 alias 지정 | | `--type` | `start`/`stop` 대상 노드 타입 지정 | | `-o`, `--output` | 출력 파일 경로 (`export` 명령에서 사용) | | `-h`, `--help` | 도움말 출력 | ## 사용 예시 ```bash # YAML 검증 machclusterctl validate -f my_cluster.yaml # 클러스터 신규 설치 machclusterctl install -f my_cluster.yaml # 실행 중인 클러스터에 변경 반영 machclusterctl apply -f my_cluster.yaml # 클러스터 시작 machclusterctl start # 특정 타입 또는 노드만 중지/시작 machclusterctl stop --node broker-1 machclusterctl start --type warehouse # 상태 확인 machclusterctl status # Broker에 접속하여 SQL 실행 machclusterctl connect # 클러스터 종료 machclusterctl stop # 설정 내보내기 machclusterctl export -o cluster_config_backup.yaml ``` --- title: "16.4.8 machcoordinatoradmin" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/machcoordinatoradmin/ language: kr kind: page --- # 16.4.8 machcoordinatoradmin `machcoordinatoradmin`은 Machbase Cluster Edition의 Coordinator 노드를 관리하고 클러스터 구성을 제어하는 도구입니다. Cluster Edition 패키지에만 포함됩니다. ## 옵션 목록 ```bash machcoordinatoradmin -h ``` ### 기본 관리 옵션 | 옵션 | 설명 | |------|------| | `-u`, `--startup` | Coordinator 프로세스 시작 | | `-s`, `--shutdown` | Coordinator 프로세스 정상 종료 | | `-k`, `--kill` | Coordinator 프로세스 강제 중지 | | `-c`, `--createdb` | Coordinator 메타데이터 생성 | | `-d`, `--destroydb` | Coordinator 메타데이터 및 패키지 파일 삭제 | | `-e`, `--check` | Coordinator 프로세스 실행 여부 확인 | | `-i`, `--silent` | 배너 출력 없이 실행 | | `--home-path=path` | Machbase 홈 경로 지정 | ### 설정 조회 옵션 | 옵션 | 설명 | |------|------| | `--configuration[=name]` | 설정 키와 값 출력. 특정 키만 출력 가능 | | `--configure` | 시스템 속성 목록 전체 출력 | ### 클러스터 상태 제어 | 옵션 | 설명 | |------|------| | `--activate` | 클러스터 상태를 Service로 전환 | | `--deactivate` | 클러스터 상태를 Deactivate로 전환 | | `--cluster-status` | 클러스터 각 노드 상태 요약 출력 | | `--cluster-status-full` | 클러스터 각 노드 상태 상세 출력 | | `--cluster-node` | 클러스터 정보 출력 | | `--verbose` | 상태 출력 시 Deployer 상태 포함 | ### 패키지 관리 | 옵션 | 설명 | |------|------| | `--list-package[=package]` | 등록된 패키지 목록 출력. 특정 패키지만 출력 가능 | | `--add-package=package` | 패키지 추가 | | `--remove-package=package` | 패키지 삭제 | ### 노드 관리 | 옵션 | 설명 | |------|------| | `--list-node[=node]` | 노드 정보 목록 출력 | | `--add-node=node` | 노드 추가 | | `--remove-node=node` | 노드 삭제 | | `--attach-node=node` | 기존 노드를 클러스터 메타에 연결 | | `--detach-node=node` | 노드를 클러스터 메타에서 분리 | | `--upgrade-node=node` | 노드 업그레이드 | | `--startup-node=node` | 특정 노드 시작 | | `--shutdown-node=node` | 특정 노드 정상 종료 | | `--kill-node=node` | 특정 노드 강제 중지 | ### Lookup 노드 관리 | 옵션 | 설명 | |------|------| | `--startup-lookup` | Lookup 노드 시작 | | `--shutdown-lookup` | Lookup 노드 종료 | | `--set-lookup-master=node` | Lookup master 노드 지정 | ### 웨어하우스 그룹/상태 관리 | 옵션 | 설명 | |------|------| | `--set-group-state=[normal\|readonly]` | 특정 웨어하우스 그룹 상태 변경 | | `--set-warehouse-state=[normal\|scrapped]` | `--node`로 지정한 Warehouse 노드 상태 변경 | | `--force-restore-warehouse=node` | scrapped Warehouse 노드 강제 복구 | ### 브로커 관리 | 옵션 | 설명 | |------|------| | `--deactivate-broker=node` | 지정 노드를 inactive 상태로 전환 | | `--activate-broker=node` | 지정 노드를 normal 상태로 전환 | ### 스냅샷 관리 | 옵션 | 설명 | |------|------| | `--snapshot-interval=sec` | 스냅샷 실행 주기(초) 설정 | | `--exec-snapshot` | 스냅샷 즉시 실행 (`--group` 필요) | | `--snapshot-recover=node` | 지정 노드 스냅샷 복구 | | `--exec-sync=node` | 지정 노드 동기화 실행 | | `--snapshot-clean` | 스냅샷 정리 | ### 호스트 리소스 모니터링 | 옵션 | 설명 | |------|------| | `--get-host-resource` | 각 노드의 호스트 리소스 정보 출력 | | `--host-resource-enable` | 호스트 리소스 정보 수집 시작 | | `--host-resource-disable` | 호스트 리소스 정보 수집 중지 | ### 추가 옵션 (다른 옵션과 함께 사용) | 추가 옵션 | 필수 옵션 | 설명 | |----------|----------|------| | `--file-name=filename` | `--add-package` | 패키지 파일 이름 | | `--port-no=portno` | `--add-node`, `--attach-node` | 서비스 포트 번호 | | `--http-admin-port=portno` | Coordinator/Deployer `--add-node`, `--attach-node` | 관리 REST 포트 번호 | | `--deployer=node` | `--add-node` | Deployer 노드 이름 | | `--package-name=name` | `--add-node`, `--upgrade-node` | 설치 소스 패키지 이름 | | `--home-path=path` | `--add-node`, `--attach-node` | 노드 설치 경로 | | `--node-type=[broker\|warehouse\|lookup]` | `--add-node`, `--attach-node` | 노드 유형 | | `--lookup-type=[master\|slave\|monitor]` | `--add-node`, `--attach-node` | Lookup 노드 유형 | | `--node=node` | `--set-warehouse-state` | 상태 변경 대상 노드 | | `--alias=alias` | `--add-node`, `--attach-node` | 노드 별칭 | | `--dbs-path=path` | `--add-node` (Broker/Warehouse) | 데이터베이스 파일 경로 | | `--group=groupname` | `--add-node`, `--attach-node`, `--set-group-state`, `--exec-snapshot` | 노드 그룹 이름 | | `--replication=host:port` | `--add-node`, `--attach-node` | 복제 대상 host:port | | `--no-replicate` | `--add-node`, `--attach-node` | 복제 사용 안 함 | | `--primary=host:port` | `-u`, `--startup` | Secondary Coordinator의 Primary 지정 | | `--host=host` | `--get-host-resource` | 특정 호스트 지정 | | `--metric=[cpu\|memory\|disk\|network]` | `--get-host-resource` | 출력할 메트릭 종류 | ## 사용 예시 ### 실행 상태 확인 ```bash machcoordinatoradmin -e ``` ### 클러스터 상태 확인 ```bash machcoordinatoradmin --cluster-status machcoordinatoradmin --cluster-status-full ``` ### 클러스터 활성화/비활성화 ```bash machcoordinatoradmin --activate machcoordinatoradmin --deactivate ``` ### Warehouse 노드 추가 ```bash machcoordinatoradmin \ --add-node=192.168.0.32:5401 \ --node-type=warehouse \ --deployer=192.168.0.32:5201 \ --package-name=machbase \ --home-path=/home/machbase/warehouse_a1 \ --port-no=5400 \ --group=Group1 \ --alias=warehouse-a1 \ --dbs-path=/data/machbase/warehouse_a1_dbs ``` ### 노드 목록 확인 ```bash machcoordinatoradmin --list-node machcoordinatoradmin --list-node=192.168.0.32:5401 ``` ### 웨어하우스 그룹 읽기 전용 전환 ```bash machcoordinatoradmin --set-group-state=readonly --group=Group1 ``` ### 설정 조회 ```bash machcoordinatoradmin --configuration machcoordinatoradmin --configuration=decision ``` ### 호스트 리소스 모니터링 ```bash machcoordinatoradmin --host-resource-enable machcoordinatoradmin --get-host-resource machcoordinatoradmin --get-host-resource --metric=cpu machcoordinatoradmin --get-host-resource --host=192.168.0.33 machcoordinatoradmin --host-resource-disable ``` --- title: "16.4.9 machdeployeradmin" url: https://docs.machbase.com/kr/dbms/reference/command-line-tools/machdeployeradmin/ language: kr kind: page --- # 16.4.9 machdeployeradmin `machdeployeradmin`은 Machbase Cluster Edition의 Deployer 노드를 직접 관리하는 도구입니다. Deployer는 Coordinator의 지시에 따라 각 노드에 패키지를 배포하고 설치 작업을 수행합니다. 일반적으로 Deployer 제어는 `machcoordinatoradmin`을 통하는 것이 권장됩니다. `machcoordinatoradmin`으로 제어가 불가능한 경우에 `machdeployeradmin`을 직접 사용합니다. Cluster Edition 패키지에만 포함됩니다. ## 옵션 목록 ```bash machdeployeradmin -h ``` | 옵션 | 설명 | |------|------| | `-u`, `--startup` | Deployer 프로세스 시작 | | `-s`, `--shutdown` | Deployer 프로세스 정상 종료 | | `-k`, `--kill` | Deployer 프로세스 강제 중지 | | `-c`, `--createdb` | Deployer 메타데이터 생성 | | `-d`, `--destroydb` | Deployer 메타데이터 삭제 | | `-e`, `--check` | Deployer 프로세스 실행 여부 확인 | | `-i`, `--silent` | 배너 출력 없이 실행 | ## 프로세스 관리 ### 시작 ```bash machdeployeradmin -u ``` ### 정상 종료 ```bash machdeployeradmin -s ``` ### 강제 중지 ```bash machdeployeradmin -k ``` ### 실행 상태 확인 ```bash machdeployeradmin -e ``` 실행 중이면 PID를 출력합니다. ``` Machbase Deployer is running with pid(29373)! ``` ## 메타데이터 관리 Deployer 메타데이터를 새로 생성합니다. ```bash machdeployeradmin -c ``` Deployer 메타데이터를 삭제합니다. ```bash machdeployeradmin -d ``` ## Deployer 역할 Deployer는 다음 작업을 Coordinator의 지시에 따라 수행합니다. - Broker, Warehouse, Lookup 노드에 Machbase 패키지 배포 및 설치 - 노드 설정 파일 생성 및 관리 - 노드 시작/종료 지시 전달 - 노드 업그레이드 지원 ## 사용 예시 ```bash # Deployer 초기 설정 machdeployeradmin -c machdeployeradmin -u # 실행 상태 확인 machdeployeradmin -e # 정상 종료 machdeployeradmin -s # 문제 발생 시 강제 중지 machdeployeradmin -k ``` ## 참고 클러스터 구성 및 노드 관리의 대부분은 `machcoordinatoradmin`을 통해 수행합니다. `machdeployeradmin`은 Coordinator와의 통신이 불가능하거나 Deployer 자체에 문제가 있을 때 직접 개입하는 용도로 사용합니다. 자세한 클러스터 관리 방법은 [machcoordinatoradmin](../machcoordinatoradmin/)을 참고하십시오. --- title: "16.6 지원 범위와 제약" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/ language: kr kind: section --- # 16.6 지원 범위와 제약 Machbase의 Edition별, 테이블 타입별, SDK별 기능 지원 범위와 알려진 제약 사항을 정리한 빠른 참조 모음입니다. 기능의 동작 원리나 사용 예시는 각 기능 장을, 특정 환경에서의 지원 여부 확인에는 이 섹션을 활용하십시오. ## 이 섹션의 구성 | 페이지 | 내용 | |--------|------| | [Edition별 기능 지원표](./edition/) | Standard Edition vs Cluster Edition 기능 비교 | | [테이블 타입별 기능 지원표](./table-types-type/) | TAG / LOG / LOOKUP / VOLATILE / TRANSACTION 테이블별 지원 기능 | | [SDK별 기능 지원표](/dbms/development-tools-integration/sdk-support-scope/) | JDBC, Python, Go, .NET, Node.js 지원 범위 | | [ROLLUP 지원 범위](./rollup/) | Edition별·테이블 타입별 ROLLUP 지원 범위 | | [백업/마운트 지원표](./backup-mount/) | BACKUP / MOUNT 기능의 Edition별 지원 여부 | | [권한별 기능 지원표](./privileges/) | 데이터베이스 권한 및 테이블 권한 목록 | | [TRANSACTION 기능 지원표](./rdb/) | TRANSACTION 테이블 지원 SQL 기능 및 제약 | | [버전 및 호환성](./compatibility-version/) | 업그레이드 시 주의사항, 지원 OS/플랫폼 | | [서버와 SDK 호환성](./compatibility-xma-protocol/) | 서버와 SDK 버전 조합별 기능 지원 범위 | | [LOOKUP SQL/JSON 지원표](./lookup-sql-json/) | LOOKUP 테이블 SQL/JSON 기능 지원 현황과 제약 | | [TAG data UPDATE 지원표](./tag-data-update/) | TAG UPDATE 조건 및 대상 컬럼 지원 현황 | 지원표에 없는 내부 object·flag·protocol 동작에 의존하지 말고, SDK 기능은 server와 client version을 함께 확인합니다. 기능별 오류 진단은 [문제 해결](/dbms/troubleshooting/)을 참고하십시오. ## 표기 규칙 이 섹션의 지원 여부 표에서 사용하는 기호는 다음과 같습니다. | 기호 | 의미 | |:----:|------| | O | 완전 지원 | | X | 미지원 | | △ | 일부 지원 또는 제약 있음 | --- title: "16.6.1 Edition별 기능 지원표" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/edition/ language: kr kind: page --- # 16.6.1 Edition별 기능 지원표 Machbase는 단일 서버용 **Standard Edition**과 다중 노드 수평 확장용 **Cluster Edition**을 제공합니다. 핵심 시계열 기능은 공유하지만, 확장성과 고가용성 요구에 따라 지원 기능 범위가 다릅니다. ## Edition별 기능 비교 | 기능 | Standard | Cluster | 비고 | |------|:--------:|:-------:|------| | **테이블 유형** | | | | | TAG 테이블 | O | O | | | LOG 테이블 | O | O | | | LOOKUP 테이블 | O | O | | | TRANSACTION 테이블 | O | X | Cluster Edition 미지원 | | VOLATILE 테이블 | O | O | 메모리 데이터의 node·restart lifecycle은 배포 구성에서 확인 | | **데이터 관리** | | | | | ROLLUP (기본) | O | O | | | Custom ROLLUP | O | X | Cluster Edition 미지원 | | ROLLUP_REBUILD | O | X | Cluster Edition 미지원 | | **백업 및 복구** | | | | | 논리 다중 데이터베이스 | O | X | Standard Edition 전용. DB별 CPU·메모리·디스크 물리 quota는 제공하지 않음 | | BACKUP DATABASE | O | O | | | BACKUP TABLE | O | O | | | MOUNT DATABASE | O | X | Cluster Edition 미지원 | | UMOUNT DATABASE | O | X | Cluster Edition 미지원 | | machadmin -r 복구 | O | X | Cluster Edition 미지원 | | **확장성 및 HA** | | | | | 수평 확장 | X | O | Warehouse 노드 추가로 확장 | | HA (고가용성) | X | O | Broker/Warehouse 이중화 | | AUTH KEY 인증 | O | O | | ## Cluster Edition 제약 사항 요약 Cluster Edition은 단일 노드 중심의 로컬 파일 작업과 TRANSACTION 기능에 제약이 있습니다. - **TRANSACTION 테이블**: 분산 환경에서 ACID 트랜잭션을 보장하는 TRANSACTION 테이블은 미지원. 트랜잭션이 필요한 데이터는 외부 RDBMS와 연동하십시오. - **VOLATILE 테이블**: 생성·DML은 지원하지만 메모리 데이터는 node-local이며 노드 간 공유되지 않습니다. 접속 Broker·routing과 node restart에 따른 데이터 범위를 검증해야 합니다. - **MOUNT/UMOUNT**: 로컬 파일 시스템 기반 백업 마운트는 분산 환경에서 미지원. - **Custom ROLLUP / ROLLUP_REBUILD**: 분산 집계 구조 차이로 커스텀 롤업 재정의 및 재구축 미지원. ## Edition 선택 기준 | 요구 사항 | 권장 Edition | |-----------|-------------| | 단일 서버의 처리량과 저장 용량으로 운영 가능한 워크로드 | Standard Edition | | 단일 서버 범위를 넘어 수평 확장이 필요한 워크로드 | Cluster Edition | | 고가용성 (장애 자동 복구) 필요 | Cluster Edition | | TRANSACTION 테이블 또는 MOUNT 기능 필요 | Standard Edition | | 실시간 수집량이 단일 서버 용량을 넘어 노드 추가가 필요한 경우 | Cluster Edition | --- title: "16.6.2 테이블 타입별 기능 지원표" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/table-types-type/ language: kr kind: page --- # 16.6.2 테이블 타입별 기능 지원표 Machbase는 용도에 따라 다섯 가지 테이블 유형을 제공합니다. 각 테이블 유형은 설계 목적에 따라 지원하는 기능 범위가 다릅니다. ## 테이블 유형 개요 | 테이블 유형 | 주요 용도 | |------------|----------| | **TAG** | 시계열 센서 데이터 고속 수집 및 집계 (ROLLUP) | | **LOG** | 로그·이벤트를 정의한 컬럼에 순차 저장, 텍스트 검색 | | **LOOKUP** | 메타데이터, 코드 테이블, 참조 데이터 (UPDATE/DELETE 지원) | | **VOLATILE** | 메모리 기반 서버 상태·캐시, 재시작 시 데이터 소멸 | | **TRANSACTION** | 트랜잭션이 필요한 일반 관계형 데이터 | ## 테이블 유형별 기능 지원 종합 표 | 기능 | TAG | LOG | LOOKUP | VOLATILE | TRANSACTION | |------|:---:|:---:|:------:|:--------:|:---:| | **쓰기** | | | | | | | INSERT (SQL) | O | O | O | O | O | | **수정/삭제** | | | | | | | UPDATE | △ | X | O | O | O | | DELETE | O | O | O | O | O | | **트랜잭션** | | | | | | | Transaction (COMMIT/ROLLBACK) | X | X | X | X | O | | **집계 및 검색** | | | | | | | ROLLUP | O | X | X | X | X | | 텍스트 검색 (KEYWORD INDEX) | X | O | X | X | X | | **JSON** | | | | | | | JSON 컬럼 | O | O | O | X | O | | JSON path query | O | O | O | X | O | | **고정소수점** | | | | | | | DECIMAL / NUMERIC 컬럼 | O | O | O | O | O | | **고정 길이 ARRAY** | | | | | | | ARRAY 컬럼 생성 | O | O | O | O | O | | ARRAY ADD/DROP COLUMN | △ | O | O | O | O | | **인덱스** | | | | | | | 기본 인덱스 | O | O | O | O | O | | LSM 인덱스 | X | O | X | X | X | | **조회** | | | | | | | SELECT | O | O | O | O | O | | 최신값 조회(`SCAN_BACKWARD`, TAG stat) | O | X | X | X | X | | JOIN (다른 테이블과) | △ | △ | O | O | O | | Subquery | O | O | O | O | O | | VIEW | O | O | O | O | O | > 기호: O = 지원, X = 미지원, △ = 일부 지원 또는 제약 있음 Append가 지원하는 테이블 유형은 클라이언트 API에 따라 다릅니다. 사용하는 언어와 API의 지원 범위는 [SDK Append 지원표](/dbms/development-tools-integration/sdk-support-scope/#append-table-type-matrix)를 확인하십시오. DECIMAL은 다섯 가지 테이블 유형에서 사용할 수 있는 정확한 고정소수점 타입입니다. `NUMERIC`, `DEC`, `FIXED`, `NUMBER`는 DECIMAL의 별칭이며, 전체 자릿수(precision)는 최대 65, 소수 자릿수(scale)는 최대 30입니다. 상세 규칙은 [DECIMAL과 NUMERIC 고정소수점 타입](../../sql/types/decimal-numeric-fixed-point/)을 참고하십시오. ARRAY ADD/DROP은 Standard Edition의 LOG, VOLATILE, LOOKUP, TRANSACTION, TAG METADATA에서 지원합니다. TAG 열의 `△`는 TAG DATA 일반 컬럼을 ALTER로 추가할 수 없고 TAG METADATA만 지원한다는 의미입니다. Cluster Edition에서는 LOG 경로만 지원합니다. 정확한 문법과 기존 row의 DEFAULT 규칙은 [DDL 문법](../../sql/syntax/ddl-syntax/#add-column)과 [숫자 ARRAY 타입](../../sql/types/array/)을 참고하십시오. ## 주요 제약 상세 ### TAG 테이블 UPDATE 제약 (△, Standard Edition) TAG 테이블의 UPDATE는 다음 조건을 모두 만족해야 합니다. Cluster Edition에서는 TAG data UPDATE를 사용할 수 없습니다. - `WHERE` 절에 태그 선택 조건(`name =`, `name IN`, `name LIKE`) 포함 - `WHERE` 절에 BASETIME 컬럼 조건 포함 - SET 대상은 실제 데이터 컬럼 - `time` (BASETIME) 컬럼과 `name` 컬럼, 메타데이터 컬럼은 data UPDATE로 수정 불가 - SET 우변에서 기존 행의 컬럼을 참조할 수 없으며, 상수·bind·column-free 식만 사용 가능 ```sql -- 가능: 태그 조건과 시간 조건으로 데이터 컬럼 업데이트 UPDATE sensor_data SET value = 101 WHERE name = 'sensor01' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); -- 불가: BASETIME 컬럼 업데이트 UPDATE sensor_data SET time = SYSDATE WHERE name = 'sensor01' AND time >= TO_DATE('2026-07-01', 'YYYY-MM-DD'); ``` 상세 내용은 [TAG data UPDATE 지원표](../tag-data-update/)를 참고하십시오. ### LOOKUP과 VOLATILE의 트랜잭션 범위 LOOKUP과 VOLATILE 테이블의 각 DML은 문장 단위로 반영됩니다. 여러 DML을 `BEGIN`과 `COMMIT`/`ROLLBACK`으로 묶는 TRANSACTION 테이블 트랜잭션에는 참여하지 않습니다. ### JSON 컬럼 지원 범위 JSON 컬럼은 TAG, LOG, LOOKUP, TRANSACTION 테이블에서 지원합니다. VOLATILE 테이블은 JSON 타입 컬럼 생성을 지원하지 않습니다. LOOKUP 테이블의 JSON 컬럼은 일반 컬럼으로 사용할 수 있지만 primary key로는 사용할 수 없습니다. 상세 내용은 [JSON 타입의 테이블 타입별 지원 범위](../../sql/types/table-types-type-support-scope-json/)를 참고하십시오. ## TAG 테이블 최신값과 시간 범위 조회 TAG 테이블은 역방향 스캔과 시간 조건으로 최신값과 범위를 조회합니다. ```sql -- 특정 태그의 최신 5개 값 조회 SELECT /*+ SCAN_BACKWARD(sensor_data) */ * FROM sensor_data WHERE name = 'sensor01' LIMIT 5; -- 시간 범위 조회 SELECT * FROM sensor_data WHERE name = 'sensor01' AND time BETWEEN TO_DATE('2024-01-01') AND TO_DATE('2024-01-02'); ``` --- title: "16.6.3 TRANSACTION 기능 지원표" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/rdb/ language: kr kind: page --- # 16.6.3 TRANSACTION 기능 지원표 Machbase TRANSACTION 테이블은 트랜잭션이 필요한 일반 관계형 데이터를 저장합니다. Machbase SQL과 JDBC/ODBC 등 지원 드라이버를 통해 접근합니다. > **주의**: TRANSACTION 테이블은 **Standard Edition에서만 지원**됩니다. Cluster Edition에서는 TRANSACTION 테이블을 생성하거나 사용할 수 없습니다. 무수식 `CREATE TABLE`, `CREATE TRANSACTION TABLE`, `CREATE TXN TABLE`은 모두 TRANSACTION 테이블을 생성합니다. 따라서 Cluster Edition에서는 세 문법이 모두 거부됩니다. Cluster Edition에서 LOG 테이블을 만들 때는 `CREATE LOG TABLE`을 사용합니다. ## SQL 기능 지원 여부 | 기능 | 지원 여부 | 비고 | |------|:---------:|------| | **기본 DML** | | | | SELECT | O | | | INSERT | O | | | UPDATE | O | | | DELETE | O | | | INSERT ... ON DUPLICATE KEY UPDATE | O | PRIMARY KEY·UNIQUE INDEX 충돌 시 기존 row 갱신 | | **트랜잭션** | | | | Transaction (COMMIT/ROLLBACK) | O | plain `BEGIN`, `COMMIT`, `ROLLBACK` | | TRANSACTION TRUNCATE의 ROLLBACK | O | 명시적 트랜잭션 안의 전체 행 삭제로 처리 | | Savepoint | X | 미지원 | | **쿼리 기능** | | | | Prepared Statement | O | | | 파라미터 바인딩 | O | | | JOIN | O | 다른 테이블 유형과 조인 가능 | | Subquery | O | | | VIEW | O | | | **객체** | | | | SEQUENCE | O | `CREATE SEQUENCE` | | PRIMARY KEY / UNIQUE INDEX | O | 단일 PRIMARY KEY와 단일·복합 UNIQUE INDEX | | 보조 INDEX | O | 단일·복합 BTREE 인덱스 | | JSON path INDEX | O | `json_column->'$.path'` | | AUTO_INCREMENT | O | `LONG`/`INT64` 컬럼 단위 PRIMARY KEY | | ALTER ADD/DROP COLUMN | O | 컬럼 정의에 괄호 사용 | | ALTER RENAME COLUMN / RENAME TO | O | 컬럼명·테이블명 변경 | | ALTER MODIFY COLUMN | X | 미지원 | | Trigger | X | 미지원 | | Stored Procedure | X | 미지원 | | Foreign Key | X | 미지원 | `AUTO_INCREMENT` 사용법은 [AUTO_INCREMENT](/dbms/reference/sql/syntax/auto-increment-syntax/), upsert는 [INSERT ON DUPLICATE KEY UPDATE](/dbms/rdb-table-usage/insert-on-duplicate-key-update/)를 참고하십시오. Append는 client별 경로가 다르므로 [SDK Append matrix](/dbms/development-tools-integration/sdk-support-scope/#append-table-type-matrix)를 정본으로 사용합니다. ## 트랜잭션과 동시 접근의 경계 활성 트랜잭션에서도 다른 테이블 타입의 SELECT와 혼합 JOIN은 허용됩니다. 하지만 LOG·TAG·LOOKUP·VOLATILE 쓰기를 같은 TRANSACTION 트랜잭션으로 묶지는 못합니다. 허용된 조회가 모든 타입에 공통인 스냅샷 시점을 보장하는 것도 아닙니다. 일반 제약 오류는 실패한 문장과 전체 트랜잭션을 구분합니다. 앞선 성공 변경을 취소하려면 ROLLBACK이 필요하며, 롤백 전용 상태에서는 후속 작업을 계속하지 말고 종료해야 합니다. 열린 TRANSACTION 커서는 COMMIT·ROLLBACK을 차단할 수 있습니다. 현재 여러 TRANSACTION 테이블의 커밋은 테이블별 저장소 핸들에 순차 적용됩니다. 정상적인 여러 테이블 COMMIT·ROLLBACK 지원과, 커밋 중 장애까지 포함한 다중 테이블 원자성 보장은 같은 뜻이 아닙니다. 오류·응답 유실 뒤에는 업무 키로 반영 상태를 확인하세요. WAL의 오래된 읽기 스냅샷을 쓰기로 전환하는 충돌은 TRANSACTION_BUSY_TIMEOUT_MS=-1이어도 대기로 해소되지 않습니다. [트랜잭션 실습](../../../rdb-table-usage/transaction/)과 [두 연결 충돌 실습](../../../rdb-table-usage/locking-conflict-timeout/)을 참고하세요. ## 관련 문서 - [TRANSACTION 테이블 활용](../../../rdb-table-usage/) - [TRANSACTION DDL과 DML](../../sql/syntax/) - [SDK 기능 지원 범위](../../../development-tools-integration/sdk-support-scope/) --- title: "16.6.4 TAG data UPDATE 지원표" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/tag-data-update/ language: kr kind: page --- # 16.6.4 TAG data UPDATE 지원표 TAG 테이블의 실제 시계열 데이터는 `UPDATE table_name SET ... WHERE ...` 구문으로 수정할 수 있습니다. 이 페이지는 TAG data UPDATE에서 허용되는 WHERE 조건과 SET 대상을 정리합니다. 메타데이터 수정은 별도의 `UPDATE ... METADATA` 구문을 사용합니다. Machbase 8.7.0부터 지원되는 기능 TAG data UPDATE는 Standard Edition의 논리 TAG 테이블에서만 지원합니다. Cluster Edition과 내부 raw component table에 대한 직접 UPDATE는 지원하지 않습니다. ## WHERE 조건별 지원 현황 TAG data UPDATE에는 하나의 태그 선택 조건과 하나 이상의 BASETIME 축 조건이 필요합니다. | WHERE 조건 | 지원 | 비고 | |-----------|:---:|------| | `name = 'tag-01'` | O | 단일 태그 선택 | | `name = ?`, `name = :tag_name` | O | positional/named bind로 단일 태그 선택 | | `? = name`, `:tag_name = name` | O | 역방향 등치도 지원하며 컬럼-왼쪽 형식을 권장 | | `name IN ('tag-01', 'tag-02')` | O | 리터럴/바인드 값 목록 지원, 서브쿼리 `IN`은 미지원 | | `name LIKE 'tag-%'` | O | 패턴에 맞는 태그를 대상으로 확장 | | `time = t1` | O | BASETIME 컬럼 등치 조건 | | `time = ?`, `time = :base_time` | O | positional/named bind로 기준 시간 지정 | | `? = time`, `:base_time = time` | O | 역방향 등치 지원 | | `time BETWEEN t1 AND t2` | O | 양 끝 포함 | | `time >= t1 AND time < t2` | O | `>`, `>=`, `<`, `<=` 조합 지원 | | `time >= ? AND time < ?` | O | 범위 값에도 bind marker 사용 가능 | | 한쪽 시간 조건 | O | 예: `time >= t1` | | 데이터 컬럼 predicate | O | 예: `value > 100`, 태그/시간 조건과 함께 사용 | | 조건 없는 UPDATE | X | 전체 TAG data UPDATE는 허용하지 않음 | | 태그 선택 없는 시간 조건만 사용 | X | 대상 태그를 지정해야 함 | | 시간 조건 없는 태그 조건만 사용 | X | BASETIME 범위를 지정해야 함 | | `OR` 조건 | X | TAG data UPDATE 조건에서는 허용하지 않음 | | 서브쿼리/집계식 | X | UPDATE 대상 결정 조건으로 사용할 수 없음 | | 태그/축 컬럼을 함수·연산식으로 감싼 표현식 | X | 태그 선택자와 BASETIME은 해당 컬럼을 직접 지정해야 함 | Bind parameter는 값만 대체합니다. 태그 선택 조건과 BASETIME 조건의 필수 여부, 허용되는 조건 구조와 SET 대상은 변경하지 않습니다. prepared statement 재실행은 최신 bind 값으로 대상을 다시 선택하며, 일치하는 행이 없으면 affected rows `0`으로 성공합니다. ## SET 대상 컬럼별 지원 현황 | SET 대상 | 지원 | 비고 | |---------|:---:|------| | 데이터 컬럼 | O | `value`, 보조 컬럼 등 사용자 데이터 컬럼 | | `SUMMARIZED` 데이터 컬럼 | O | 원본 TAG 데이터가 갱신됨 | | 여러 데이터 컬럼 | O | 같은 UPDATE 문에서 함께 지정 가능 | | `name` (PRIMARY KEY) | X | 태그 이름은 변경할 수 없음 | | `time` (BASETIME) | X | 시간 축 컬럼은 변경할 수 없음 | | 메타데이터 컬럼 | X | `UPDATE table_name METADATA SET ...` 사용 | | 숨김/시스템 컬럼 | X | 내부 컬럼은 UPDATE 대상이 아님 | SET 표현식에는 상수, bind 변수, 기존 행 컬럼을 참조하지 않는 산술식·함수·`CASE` 표현식·문자열 연결, NULL 값(컬럼 제약이 허용하는 경우)을 사용할 수 있습니다. SET 우변에서 기존 행 컬럼을 참조할 수 없으며, 서브쿼리와 집계식도 사용할 수 없습니다. ## 정본 실행 문법과 parameter metadata는 [TAG data UPDATE](../../sql/syntax/dml-syntax/tag-data-update-syntax/#tag-data-update-predicate-bind)를, SDK별 marker API는 [Named Bind Parameter](../../sql/syntax/named-bind-parameter-syntax/)를, 진단은 [TAG 제약과 문제 해결](../../../tag-table-usage/constraints-errors-troubleshooting/)을 참고하십시오. --- title: "16.6.5 LOOKUP SQL/JSON 지원표" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/lookup-sql-json/ language: kr kind: page --- # 16.6.5 LOOKUP SQL/JSON 지원표 이 페이지는 LOOKUP 테이블의 SQL 기능과 JSON 관련 제약을 정리합니다. ## 지원 현황 | 기능 | 지원 | 비고 | |------|:---:|------| | **기본 CRUD** | | | | INSERT | O | 일반 INSERT 사용 | | SELECT | O | 기본 키(PK) 조건과 일반 검색 조건 모두 사용 가능 | | UPDATE (PK 조건) | O | Primary key fast path 사용 | | DELETE (PK 조건) | O | Primary key fast path 사용 | | UPDATE (일반 검색 조건) | O | 조건에 맞는 기본 키 집합을 수집한 뒤 갱신 | | DELETE (일반 검색 조건) | O | 조건에 맞는 기본 키 집합을 수집한 뒤 삭제 | | **JSON 기능** | | | | JSON 타입 컬럼 | O | 일반 컬럼으로 생성, 저장, 조회, 갱신 가능 | | JSON path query (`$.key`) | O | `->`, `JSON_EXTRACT_*`, `JSON_TYPEOF`, `JSON_IS_VALID` 사용 가능 | | JSON PK | X | JSON 컬럼은 primary key로 선언할 수 없음 | | JSON path index | X | 별도 JSON path index는 지원하지 않음 | | **기타** | | | | 명시적 트랜잭션 (`BEGIN`/`COMMIT`/`ROLLBACK`) | X | 각 DML은 개별 문장 단위로 반영되며 여러 문장을 함께 롤백할 수 없음 | | Prepared Statement | O | 기본 키 조건과 일반 검색 조건에서 매개변수 바인딩 지원 | | Append API | △ | 일반 SQL INSERT가 기본이며, Append는 별도 LOOKUP append 정책을 따름 | ## 정본 LOOKUP JSON 스키마와 실행 예제는 [JSON 컬럼과 조회](../../../lookup-table-usage/json-column-query/)를, UPDATE·DELETE 문법은 [DML 문법](../../sql/syntax/dml-syntax/)을 참고하십시오. --- title: "16.6.6 ROLLUP 지원 범위" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/rollup/ language: kr kind: page --- # 16.6.6 ROLLUP 지원 범위 ## Edition과 테이블 범위 | 기능 | Standard | Cluster | |---|:---:|:---:| | 시간축 TAG의 일반·조건·확장 ROLLUP 생성·조회·제어 | O | O | | WITH ROLLUP 자동 생성 | O | O | | 지원 JSON 경로·문서 전체 집계 | O | O | | Custom INTO...AS | O | X | | ROLLUP_REBUILD | 제한된 대상·인수에서 지원 | X | 거리축 TAG, LOG, TRANSACTION, VOLATILE, LOOKUP에 시간축 ROLLUP을 적용하지 않습니다. Cluster의 상태는 관련 노드와 계층별로 확인합니다. ## 생성 유형과 컬럼 | 유형 | 필요한 조건 | |---|---| | 일반 숫자 | 지원 숫자 DATA 컬럼; 명시 생성 시 SUMMARIZED는 필수가 아님 | | JSON 경로 | JSON DATA 컬럼과 집계할 숫자 경로 | | JSON 문서 전체 | JSON SUMMARIZED 컬럼 | | WITH ROLLUP | 시간축 TAG의 세 번째 SUMMARIZED 컬럼 | | FROM 계층 | 더 큰 정수배 간격과 동일한 확장·모드 조건 | | Custom | 소스 시간축 TAG 하나, 사전 생성한 호환 대상 TAG | ## 집계와 선택 일반 숫자 ROLLUP은 MIN/MAX/SUM/COUNT/AVG/SUMSQ, 확장은 FIRST/LAST를 추가로 제공합니다. Custom의 부분 결과는 사용자가 재집계합니다. 평균은 합계와 유효 건수로, FIRST/LAST는 대응 시각을 함께 유지해 합칩니다. JSON 문서 전체의 COUNT는 저장된 집계 건수이며 원본 COUNT(value)와 무조건 같지 않습니다. 후보 선택은 조건·컬럼·경로·모드·간격에 따릅니다. 일반/확장만으로 우선순위를 단정하거나, 일 버킷에 24 HOUR ROLLUP이 자동 적용된다고 가정하지 않습니다. [조회 규칙](../../../tag-rollup-usage/query-syntax-rollup/)을 확인합니다. ## REBUILD는 생성 지원과 다름 완전한 자동 SEC/MIN/HOUR 계층과 지원 Custom 경로를 대상으로 합니다. 임의 수동 이름, 부분 자동 계층, 10 MIN Custom 등 생성 가능한 모든 구성을 재구성할 수 있는 것은 아닙니다. 현재 Custom 시간 경계 처리 간격은 1 SEC·1 MIN·1 HOUR이며 SELECT의 버킷과도 맞아야 합니다. 상수 시각 인수, 버킷 전체 확장, 상태 전환과 오류 후 확인은 [REBUILD 레퍼런스](../../sql/syntax/rollup-rebuild-syntax/)를 따릅니다. ## 권한 작업 계정에는 대상 데이터베이스 접속과 생성·삭제·조회 등 필요한 권한을 부여합니다. 다음은 존재하는 작업 계정에 생성·삭제 권한을 부여하는 예시이며 모든 필요 권한을 한 번에 구성하는 스크립트는 아닙니다. ```sql GRANT CREATE, DROP ON DATABASE MACHBASEDB TO rollup_user; ``` [권한 관리](../../../security-access-control/privileges/)에서 소유자와 작업 범위를 확인하십시오. --- title: "16.6.7 권한별 기능 지원표" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/privileges/ language: kr kind: page --- # 16.6.7 권한별 기능 지원표 Machbase 권한은 적용 범위에 따라 **데이터베이스 권한**과 **테이블 권한** 두 가지로 나뉩니다. ## 데이터베이스 권한 데이터베이스 권한은 지정한 active database 범위에 적용됩니다. `MOUNT`는 `MACHBASEDB`에 부여하고, mounted database 접근에는 `USAGE`와 table `SELECT`를 별도로 부여합니다. | 권한 | 허용하는 작업 | 기본 보유 | |------|-------------|:--------:| | `CONNECT` | active database 연결, `USE`, 객체 탐색 | O(MACHBASEDB 호환) | | `CREATE` | 테이블, 뷰, 인덱스, 롤업, 테이블스페이스, 리텐션 생성 | O | | `DROP` | 테이블, 뷰, 인덱스, 롤업, 테이블스페이스, 리텐션 삭제 | O | | `ALTER` | 테이블 구조 변경, `ALTER SYSTEM` 실행 | X | | `BACKUP` | `BACKUP DATABASE` 실행 | X | | `MOUNT` | `MOUNT DATABASE` / `UMOUNT DATABASE` 실행 | X | | `USAGE` | mounted database 탐색 | X | | `DDL` | CREATE + DROP 묶음 (합성 권한) | — | | `ALL` | CONNECT, CREATE, DROP, ALTER, BACKUP 일괄 부여 | — | > "기본 보유 O"는 `CREATE USER`로 생성된 사용자의 `MACHBASEDB` 호환 기본 권한을 뜻합니다. > 다른 논리 데이터베이스의 권한은 별도로 부여합니다. ## 테이블 권한 테이블 권한은 특정 테이블에 대한 DML 작업을 제어합니다. | 권한 | 허용하는 작업 | |------|-------------| | `SELECT` | 특정 테이블 SELECT 조회 | | `INSERT` | 특정 테이블 INSERT | | `DELETE` | 특정 테이블 DELETE | | `UPDATE` | 특정 테이블 UPDATE | ## GRANT / REVOKE 문법 ```sql -- 데이터베이스 권한 부여 GRANT CONNECT ON DATABASE factory_a TO app_user; GRANT CREATE ON DATABASE factory_a TO app_user; GRANT BACKUP ON DATABASE factory_a TO backup_user; GRANT ALL ON DATABASE factory_a TO admin_user; -- 테이블 권한 부여 GRANT SELECT ON sys.sensor_data TO reader_user; GRANT INSERT ON sys.sensor_data TO writer_user; -- 권한 취소 REVOKE SELECT ON sys.sensor_data FROM reader_user; REVOKE BACKUP ON DATABASE factory_a FROM backup_user; ``` ## 권한이 필요한 주요 작업 | 작업 | 필요 권한 종류 | 필요 권한 | |------|-------------|---------| | `CREATE TABLE` | 데이터베이스 | CREATE | | `DROP TABLE` | 데이터베이스 | DROP | | `ALTER TABLE` | 데이터베이스 | ALTER | | `BACKUP DATABASE` | 데이터베이스 | BACKUP | | `MOUNT DATABASE` | 데이터베이스 | MOUNT | | 테이블 SELECT | 테이블 | SELECT | | 테이블 INSERT | 테이블 | INSERT | | 테이블 UPDATE | 테이블 | UPDATE | | 테이블 DELETE | 테이블 | DELETE | ## 권한 현황 조회 ```sql -- 사용자 목록 SELECT user_name, user_id FROM m$sys_users; -- 데이터베이스 권한 조회 SELECT * FROM m$sys_grant_databases WHERE grantee = 'APP_USER'; -- 테이블 권한 조회 SELECT * FROM m$sys_grant_tables WHERE grantee = 'APP_USER'; ``` ## 상세 레퍼런스 권한 모델 전체 설명과 예제는 [권한 관리](/dbms/security-access-control/privileges/) 섹션을 참고하십시오. --- title: "16.6.8 백업/마운트 지원표" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/backup-mount/ language: kr kind: page --- # 16.6.8 백업/마운트 지원표 백업은 데이터를 파일로 저장하고, 마운트는 저장된 백업 파일을 데이터베이스에 연결하여 조회하는 기능입니다. ## Edition별 지원 여부 | 기능 | Standard | Cluster | 비고 | |------|:--------:|:-------:|------| | 논리 다중 데이터베이스 | O | X | Standard Edition 전용 | | BACKUP DATABASE | O | O | 전체 데이터베이스 백업 | | BACKUP TABLE | O | O | 특정 테이블만 백업 | | MOUNT DATABASE | O | X | Cluster Edition 미지원 | | UMOUNT DATABASE | O | X | Cluster Edition 미지원 | | machadmin -r 복구 | O | X | Cluster Edition 미지원 | `BACKUP DATABASE database_name INTO DISK`는 하나의 active logical database를 백업합니다. 여러 active database가 포함된 full-instance image는 logical `MOUNT`/`RESTORE DATABASE`의 입력으로 사용할 수 없습니다. mounted database 조회에는 `USAGE`와 table `SELECT`가 필요하며 `USE`와 쓰기는 지원하지 않습니다. ## 테이블 타입별 백업 지원 | 테이블 유형 | BACKUP 지원 | MOUNT 후 조회 | 비고 | |------------|:-----------:|:------------:|------| | TAG 테이블 | O | O | | | LOG 테이블 | O | O | | | LOOKUP 테이블 | O | O | | | TRANSACTION 테이블 | O | O | | | VOLATILE 테이블 | X | X | 메모리 기반으로 백업 불가 | ## 정본 - 문법: [BACKUP · RESTORE · MOUNT](../../sql/syntax/backup-restore-mount-syntax/) - 운영 절차: [백업, 복원, 마운트](../../../operations-configuration-recovery/backup-restore-mount/) --- title: "16.6.9 서버와 SDK 호환성" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/compatibility-xma-protocol/ language: kr kind: page --- # 16.6.9 서버와 SDK 호환성 Machbase 서버와 SDK의 버전이 다르면 기본 연결은 가능하더라도 최신 인증, 메타데이터, Named Bind Parameter 기능이 제한될 수 있습니다. 이 절에서는 서버와 SDK 버전 조합별 지원 범위와 업그레이드 순서를 설명합니다. ## 서버와 SDK 버전 호환 표 | 서버 버전 | 8.5 클라이언트 드라이버 | 8.7.0 클라이언트 드라이버 | |-----------|:---------------------:|:---------------------:| | **8.7.0 서버** | 제한적 호환 | 완전 호환 | | **8.5 서버** | 완전 호환 | 하위 호환, 8.7.0 이름 API 미지원 | - **완전 호환**: 같은 버전 조합입니다. 실제 사용 가능 기능은 Edition, 테이블 유형과 SDK별 지원 범위에 따르며, 해당 기능이 포함된 빌드를 사용해야 합니다. - **제한적 호환**: 기본 연결은 가능하나 8.7.0 신규 기능(AUTH KEY 확장 등)이 동작하지 않을 수 있습니다. - **하위 호환**: 8.5 서버 범위의 기능만 사용 가능합니다. ## Machbase 8.7.0 SDK의 주요 변경 사항 ### AUTH KEY 인증 확장 8.7.0에서 AUTH KEY challenge 인증 방식이 확장되었습니다. - 지원 서명 방식: `ECDSA`, `RSA_PKCS1_V15`, `RSA_PSS` - 8.5 이하 드라이버는 신규 서명 방식(`RSA_PSS`)을 지원하지 않을 수 있습니다. - AUTH KEY 인증을 사용하는 경우 드라이버를 8.7.0으로 업데이트하십시오. ```text -- AUTH KEY 등록 (서버) ALTER USER app_user ADD AUTH KEY ( KEY='-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n', VALID_BEFORE='2047-12-31' ); ``` ### 연결 문자열 호환성 Machbase SQLCLI와 ODBC에서 AUTH KEY 관련 파라미터: ```ini AUTH_MODE=CHALLENGE; AUTH_KEY_FILE=./private_key.pem; AUTH_SIG_SCHEME=ECDSA; ``` 8.5 드라이버는 `AUTH_SIG_SCHEME` 파라미터를 무시할 수 있습니다. ### Nullable 메타데이터 결과 컬럼이 NULL을 허용하는지 확인하는 API와 서버·SDK 조합별 제약은 [Nullable 메타데이터 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-nullable-metadata)를 참고하십시오. 호환성을 점검할 때는 서버와 클라이언트 버전을 함께 기록합니다. ### Named Bind Parameter 이름이 있는 매개변수를 서버에 준비된 문장으로 전달하는지, 위치 순서로 바인딩하는지, 클라이언트에서 SQL 문자열로 변환하는지는 SDK마다 다릅니다. [SDK 기능 지원 범위](/dbms/development-tools-integration/sdk-support-scope/#support-scope-sdk-transaction-prepare-bind)에서 사용하는 클라이언트의 동작을 확인하십시오. ### ARRAY와 선택 컬럼 Append 고정 길이 숫자 ARRAY와 선택 컬럼 Append는 Machbase DBMS 8.7.0에서 지원합니다. DBMS 8.7.0 서버와 ARRAY 기능이 포함된 SDK 빌드를 함께 사용하십시오. 구버전 서버 또는 기능이 포함되지 않은 SDK는 ARRAY 메타데이터나 값을 기존 scalar 타입으로 대체하지 않으며 해당 요청을 오류로 처리합니다. ARRAY의 SQL 요소 위치와 Machbase 전용 SDK position은 0-based입니다. 기존 1-based SQL, sparse 객체와 indexed Append target은 위치를 1씩 낮춥니다. 저장 데이터와 dense ARRAY 요소 순서는 바뀌지 않습니다. JDBC parameter ordinal이나 `java.sql.Array` slice처럼 표준 API가 정의한 1-based 위치는 이 변경의 대상이 아닙니다. Cluster Edition은 coordinator, broker와 warehouse를 모두 ARRAY를 지원하는 같은 DBMS 8.7.0 빌드로 구성합니다. 혼합 버전 상태에서는 ARRAY DDL이나 ARRAY 데이터를 사용하는 작업을 시작하지 마십시오. SQL과 SDK별 요구사항은 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)과 [Sparse ARRAY와 선택 컬럼 Append API](/dbms/development-tools-integration/data-input-load-export/array-append/)를 참고하십시오. ## SDK 버전 확인 JDBC: ```java Connection conn = DriverManager.getConnection(url, props); DatabaseMetaData meta = conn.getMetaData(); System.out.println("Driver: " + meta.getDriverVersion()); ``` Machbase SQLCLI: ```c SQLGetInfo(conn, SQL_DRIVER_VER, buf, sizeof(buf), NULL); ``` ODBC: ```c SQLGetInfo(conn, SQL_DRIVER_VER, buf, sizeof(buf), NULL); ``` ## 업그레이드 권장 사항 1. 서버와 SDK를 같은 버전(8.7.0)으로 함께 업그레이드하십시오. 2. SDK를 순차적으로 업그레이드하는 경우, 업그레이드 기간 동안 8.5 SDK가 8.7.0 서버에 제한적으로 연결될 수 있음을 인지하십시오. 3. AUTH KEY 인증을 사용하는 경우 SDK를 가장 먼저 업그레이드하십시오. 4. Nullable 메타데이터를 애플리케이션 로직에 사용하는 경우 서버와 SDK를 모두 8.7.0으로 업그레이드하십시오. 5. Named Bind Parameter의 이름 기반 SDK API를 사용하는 경우 서버와 SDK를 모두 8.7.0으로 업그레이드하십시오. 6. ARRAY 또는 선택 컬럼 Append를 사용하는 경우 서버는 Machbase DBMS 8.7.0으로, 클라이언트는 해당 기능이 포함된 SDK 빌드로 업그레이드하십시오. Cluster Edition은 모든 노드를 함께 맞춥니다. --- title: "16.6.10 버전 및 호환성" url: https://docs.machbase.com/kr/dbms/reference/support-scope-constraints/compatibility-version/ language: kr kind: page --- # 16.6.10 버전 및 호환성 Machbase 8.7.0 버전의 하위 호환성, 업그레이드 주의사항, 지원 OS/플랫폼 정리입니다. ## 8.7.0 버전 하위 호환성 ### 클라이언트 드라이버 호환 | 서버 버전 | 8.5 클라이언트 드라이버 | 8.7.0 클라이언트 드라이버 | |-----------|:---------------------:|:---------------------:| | 8.7.0 서버 | 제한적 호환 | 완전 호환 | | 8.5 서버 | 완전 호환 | 하위 호환 | - 8.7.0 서버에 8.5 클라이언트 드라이버를 사용하면 일부 신규 기능이 동작하지 않을 수 있습니다. - 구버전 서버 또는 드라이버 조합에서는 Nullable 메타데이터가 기존 값이나 판정 불가 값으로 반환될 수 있습니다. 이 값에 의존하는 애플리케이션은 서버와 SDK를 모두 8.7.0으로 업그레이드하십시오. - CAST의 모든 대상 타입과 길이·정밀도 옵션을 사용하는 SQL은 8.7.0 서버에서 지원됩니다. 같은 cardinality의 숫자 ARRAY 전체를 `CAST(array_expression AS TYPE[N])`로 변환하는 문법도 이 버전부터 사용할 수 있습니다. Cluster Edition에서는 모든 cluster node를 CAST와 ARRAY를 지원하는 동일 버전으로 구성해야 합니다. 자세한 문법과 변환 규칙은 [CAST 함수](/dbms/reference/sql/functions/functions-full/#cast)를 참고하십시오. - 8.7.0 서버는 `CREATE INDEX IF NOT EXISTS`를 지원합니다. 같은 database와 owner에 동일한 index name이 있으면 기존 정의를 유지하고 성공하므로, 반복 배포 후 실제 index mapping을 확인해야 합니다. 구버전 서버에서는 이 문법을 사용할 수 없습니다. 자세한 계약은 [INDEX 문법](/dbms/reference/sql/syntax/index-syntax/#create-index-if-not-exists)을 참고하십시오. - 8.7.0 Standard Edition 서버는 TAG data UPDATE의 NAME과 BASETIME 조건 값에 positional 또는 named bind parameter를 사용할 수 있습니다. 구버전 서버에서는 같은 prepared UPDATE가 `ERR-02190`으로 거부될 수 있습니다. 조건 형태와 SDK API는 [TAG data UPDATE bind](/dbms/reference/sql/syntax/dml-syntax/tag-data-update-syntax/#tag-data-update-predicate-bind)를 참고하십시오. - 8.7.0 서버의 BASE DISTANCE TAG 통계 뷰는 축 컬럼을 `*_DISTANCE` 이름과 원본 `DOUBLE`, `LONG`, `ULONG` 타입으로 제공합니다. 기존 `*_TIME` 이름은 alias로 제공되지 않으며, 기존 테이블도 서버 재시작 후 새 스키마를 사용합니다. BASE TIME TAG의 `*_TIME DATETIME` 스키마는 유지됩니다. 애플리케이션 SQL과 result mapping은 [TAG별 통계 뷰](/dbms/tag-table-usage/query-analysis/#tag-stat-axis-schema)의 변환표에 따라 변경하십시오. - 8.7.0의 Standard Edition에서는 SELECT/JOIN 계획 개선으로 테이블 스캔 순서와 정렬하지 않은 결과의 반환 순서가 구버전과 달라질 수 있습니다. 결과 순서가 필요하면 `ORDER BY`를 사용하고, 업그레이드 후에는 [SELECT/JOIN 옵티마이저](/dbms/performance-tuning/performance-query-tuning/#select-join-optimizer)의 절차에 따라 결과와 실행 계획을 함께 확인하십시오. - 8.7.0 JDBC 드라이버는 다중 호스트 URL에서 연결 단계의 I/O 오류가 발생하면 다음 호스트로 연결을 시도합니다. 구버전 드라이버가 첫 호스트의 일부 socket 오류에서 연결을 종료하는 환경에서는 8.7.0 JDBC 드라이버로 교체하고 [다중 호스트 연결](/dbms/development-tools-integration/jdbc/#jdbc-multi-host)의 URL과 timeout 설정을 확인하십시오. - Machbase DBMS 8.7.0은 고정 길이 숫자 ARRAY와 선택 컬럼 Append를 지원합니다. ARRAY를 사용하는 애플리케이션은 DBMS 8.7.0 서버와 해당 기능이 포함된 SDK 빌드를 함께 사용하고, Cluster Edition은 모든 노드를 함께 업그레이드하십시오. 자세한 SQL과 API는 [숫자 ARRAY 타입](/dbms/reference/sql/types/array/)과 [Sparse ARRAY와 선택 컬럼 Append API](/dbms/development-tools-integration/data-input-load-export/array-append/)를 참고하십시오. - ARRAY 컬럼은 Standard Edition의 LOG, VOLATILE, LOOKUP, TRANSACTION, TAG METADATA와 Cluster Edition의 LOG 테이블에서 `ADD COLUMN`과 `DROP COLUMN`을 지원합니다. 테이블별 기존 row의 DEFAULT 적용 차이는 [DDL 문법](/dbms/reference/sql/syntax/ddl-syntax/#add-column)을 확인하십시오. - ARRAY의 공개 position은 0-based입니다. 초기 1-based ARRAY SQL, sparse 객체와 indexed Append target을 사용한 코드는 각 위치를 1씩 낮춰야 합니다. 저장된 ARRAY 데이터와 dense ARRAY 요소 순서에는 변경이 없으므로 데이터 마이그레이션은 필요하지 않습니다. - 서버와 SDK의 버전 조합에 따른 기능 차이는 [서버와 SDK 호환성](../compatibility-xma-protocol/)을 참고하십시오. ### 8.7.0에서 제거된 기능 Machbase 8.7.0은 다음 기능과 인터페이스를 제공하지 않습니다. 제거된 설정, SQL, C API에는 호환 계층이 없으므로 업그레이드 전에 설정 파일, 운영 SQL, 애플리케이션을 변경해야 합니다. | 8.5 기능 또는 인터페이스 | 8.7.0 상태 | 사용자 영향 | 전환 방법 | |--------------------------|------------|-------------|-----------| | DB HTTP/REST (`/machbase`, `/machiot`, 포트 5657) | 제거 | 기존 HTTP 조회와 Append 요청을 사용할 수 없음 | SQLCLI, ODBC, JDBC, Python, Go, Node.js 또는 .NET SDK 사용 | | WebAdmin/MWA, 정적 ClusterAdmin UI | 제거 | 웹 UI와 관련 시작 스크립트를 사용할 수 없음 | 서버·클러스터 명령행 도구 사용 | | STREAM SQL과 카탈로그 | 제거 | 등록된 STREAM을 실행하거나 상태를 조회할 수 없음 | Fluentd 또는 애플리케이션 작업으로 처리 | | Result Cache | 제거 | 결과 캐시 설정, 상태 조회, flush 명령을 사용할 수 없음 | 인덱스·ROLLUP·쿼리 최적화 또는 애플리케이션 캐시 사용 | | `machcli.h`와 `MachCLI*()` | 제거 | 기존 C/C++ 소스와 바이너리를 그대로 사용할 수 없음 | Machbase SQLCLI 또는 ODBC로 이전 | 다음 항목은 이름이 비슷하지만 계속 지원합니다. | 유지 기능 | 설명 | |-----------|------| | Machbase SQLCLI | ``의 `SQL*` API. ODBC와는 별도의 API 집합입니다. | | ODBC, JDBC 및 언어별 SDK | Python, Go, Node.js, .NET을 포함한 지원 드라이버를 계속 사용할 수 있습니다. | | MachEngine API | 기존 `Mach*` API를 계속 사용할 수 있습니다. | | PVO Cache | 실행 계획 객체를 재사용하는 캐시이며 제거된 Result Cache와 다른 기능입니다. | | Coordinator 관리 REST | 데이터 SQL REST가 아닌 관리용 API입니다. Coordinator의 `/admin/` 경로를 계속 사용할 수 있습니다. | #### 업그레이드 전에 설정 파일 정리 다음 프로퍼티가 8.7.0의 `machbase.conf`에 남아 있으면 알 수 없는 프로퍼티로 처리되어 서버가 시작되지 않습니다. 바이너리를 교체하기 전에 모두 삭제합니다. ```text HTTP_AUTH HTTP_ENABLE HTTP_MAX_MEM HTTP_PORT_NO RS_CACHE_APPROXIMATE_RESULT_ENABLE RS_CACHE_ENABLE RS_CACHE_MAX_MEMORY_PER_QUERY RS_CACHE_MAX_MEMORY_SIZE RS_CACHE_MAX_RECORD_PER_QUERY RS_CACHE_TIME_BOUND_MSEC STREAM_THREAD_COUNT STREAM_WAIT_MS ``` 기존 STREAM 정의가 필요하면 8.5 서버를 중지하기 전에 `V$STREAMS`와 관련 SQL을 별도로 기록합니다. 8.7.0에서는 `SYS_STREAM_STMTS`, `V$STREAMS`, `V$HTTP_STATUS`, `V$RS_CACHE_LIST`, `V$RS_CACHE_STAT`이 등록되지 않습니다. 업그레이드 후 다음 쿼리의 결과가 각각 `0`인지 확인합니다. ```sql SELECT COUNT(*) AS removed_property_count FROM V$PROPERTY WHERE NAME IN ( 'HTTP_AUTH', 'HTTP_ENABLE', 'HTTP_MAX_MEM', 'HTTP_PORT_NO', 'RS_CACHE_APPROXIMATE_RESULT_ENABLE', 'RS_CACHE_ENABLE', 'RS_CACHE_MAX_MEMORY_PER_QUERY', 'RS_CACHE_MAX_MEMORY_SIZE', 'RS_CACHE_MAX_RECORD_PER_QUERY', 'RS_CACHE_TIME_BOUND_MSEC', 'STREAM_THREAD_COUNT', 'STREAM_WAIT_MS' ); SELECT COUNT(*) AS removed_table_count FROM V$TABLES WHERE NAME IN ( 'SYS_STREAM_STMTS', 'V$HTTP_STATUS', 'V$RS_CACHE_LIST', 'V$RS_CACHE_STAT', 'V$STREAMS' ); ``` ### DDL 동시성 호환 Machbase 8.7.0은 Edition에 따라 DDL 동시 실행 정책이 다릅니다. | Edition | 8.7.0 동작 | `DDL_LOCK_TIMEOUT` | |---------|------------|--------------------| | Standard | 서로 다른 독립 객체의 DDL을 동시에 수행할 수 있음 | 제공함. 기본값 `0`(NOWAIT) | | Cluster | 기존 카탈로그 범위 DDL 정책 유지 | 제공하지 않음 | Standard Edition에서 같은 객체나 직접 관련된 객체의 DDL이 충돌하면 기본 설정에서는 `ERR-02031: Resource busy ()`가 즉시 반환됩니다. 이전 버전의 대기 동작을 전제로 작성한 배포 스크립트는 업그레이드 후 다음 중 하나를 명시적으로 적용합니다. - 배포 세션에서 `ALTER SESSION SET DDL_LOCK_TIMEOUT = seconds`로 제한된 대기 시간을 설정합니다. - `ERR-02031`에만 제한된 재시도와 대기 간격을 적용합니다. - 재시도 전에 객체 상태를 다시 확인하고 `already exists`, 권한, 문법 오류는 재시도하지 않습니다. 자세한 충돌 관계와 설정 방법은 [DDL 동시성과 잠금](/dbms/reference/sql/syntax/ddl-syntax/#ddl-concurrency)을 참고하십시오. ### 백업 파일 호환성 | 백업 파일 버전 | 8.7.0에서 복원 | 비고 | |--------------|:-----------:|------| | 8.5 백업 | O | `MOUNT` 또는 `machadmin -r` 사용 | | 8.7.0 백업 | O | | | 8.4 이하 백업 | △ | 버전에 따라 다름, 테스트 필요 | ## 업그레이드 정본 실행 순서, 지원 플랫폼과 사전 점검은 [업그레이드](../../../installation-deployment-upgrade/upgrade/)를 참고하십시오. 이 페이지는 SQL·server·client 호환성 사실만 유지합니다. --- title: "16.7 오류 코드 사전" url: https://docs.machbase.com/kr/dbms/reference/error-codes/ language: kr kind: page --- # 16.7 오류 코드 사전 Machbase 오류는 machsql, 드라이버 예외 메시지와 서버 trace 로그에 표시됩니다. 이 페이지는 Machbase 8.7.0의 대표 오류와 전체 오류 메시지 목록을 제공합니다. 오류 메시지는 실행 경로에 따라 `ERR-02010: ...` 형식의 문자열 또는 드라이버별 예외로 노출됩니다. 메시지 문구는 제품 개선 과정에서 바뀔 수 있으므로 애플리케이션에서는 문자열이 아니라 오류 코드를 기준으로 처리합니다. ## SQL 파서와 함수 오류 | 코드 | 메시지 | 대표 원인 | |------|--------|-----------| | `ERR-02009` | `Insufficient parser memory.` | SQL 파서 메모리 부족 | | `ERR-02010` | `Syntax error: near token (%s).` | SQL 문법 오류 | | `ERR-02011` | `Unrecognized token (%s).` | 인식할 수 없는 토큰 사용 | | `ERR-02034` | `Invalid format of time expression.` | 시간 표현식 형식 오류 | | `ERR-02035` | `Function [%s] does not exist.` | 존재하지 않는 함수 호출 | | `ERR-02036` | `Function [%s] has an invalid argument.` | 함수 인자 개수 또는 값 오류 | | `ERR-02037` | `Function [%s] argument data type does not match.` | 함수 인자 타입 불일치 | | `ERR-02040` | `Invalid time range.` | 허용되지 않는 시간 범위 | ## 테이블, 컬럼, 입력 데이터 오류 | 코드 | 메시지 | 대표 원인 | |------|--------|-----------| | `ERR-02014` | `Column name is duplicated: (%s).` | 중복 컬럼명 사용 | | `ERR-02015` | `Invalid column type: (%s).` | 지원하지 않는 컬럼 타입 지정 | | `ERR-02024` | `Table %s already exists.` | 같은 이름의 테이블이 이미 존재 | | `ERR-02025` | `Table %s does not exist.` | 대상 테이블이 존재하지 않음 | | `ERR-02026` | `The number of insert values and that of columns are mismatched.` | INSERT 컬럼 수와 값 수 불일치 | | `ERR-02030` | `Column name (%s) does not exist.` | 존재하지 않는 컬럼 지정 | ## 시스템 리소스 오류 | 코드 | 메시지 | 대표 원인 | |------|--------|-----------| | `ERR-01007` | `There is no available disk space for writing <%lld>bytes to the file<%s>, errno = %d.` | 데이터 파일을 기록할 디스크 공간 부족 | | `ERR-01346` | `Current Allocate Memory / PROCESS_MAX_SIZE (%llu/%llu), increase PROCESS_MAX_SIZE property and restart.` | `PROCESS_MAX_SIZE` 한도 초과 | ## 전체 목록 사용 방법 아래 전체 목록은 제품 오류 카탈로그의 영문 메시지를 표시합니다. `KEY`는 소스 코드에서 오류를 구분하는 식별자이며, `%s`, `%d`, `%llu` 같은 자리표시자는 오류가 발생할 때 객체 이름이나 숫자 등 실제 값으로 바뀝니다. 메시지를 대조할 때는 자리표시자의 차이를 고려하고, 가능한 경우 `ERR-xxxxx` 코드를 기준으로 검색하십시오. 목록은 같은 페이지에서 1,000번 단위 범위로 나뉩니다. 브라우저 찾기 기능으로 오류 코드, 오류 식별자 또는 메시지 일부를 검색할 수 있습니다. 메시지만으로 원인이나 재시도 가능 여부를 판단하기 어려우면 [문제 해결](/dbms/troubleshooting/)과 해당 기능 문서의 제약을 함께 확인하십시오. ## 전체 오류 메시지 다음 1,062개 항목은 Machbase 8.7.0 NFX 오류 카탈로그에서 제거된 기능의 항목을 제외한 결과입니다. ### `ERR-00000`–`ERR-00999` (157) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-00001 | ERR_FILE_CREATE | Failed to create file<%s>, errno = %d. | | ERR-00002 | ERR_FILE_TRUNCATE | Failed to truncate file<%s>, errno = %d. | | ERR-00003 | ERR_FILE_DUP | Failed to duplicate file<%s>, errno = %d. | | ERR-00004 | ERR_FILE_COPY | Failed to copy file<%s> to file<%s>, errno = %d. | | ERR-00005 | ERR_FILE_RENAME | Failed to rename file<%s> to file<%s>, errno = %d. | | ERR-00006 | ERR_FILE_REMOVE | Failed to remove file<%s>, errno = %d. | | ERR-00007 | ERR_FILE_GETKEY | Failed to get key file<%s>, errno = %d. | | ERR-00008 | ERR_FILE_PIPE | Failed to create pipe<%s>, errno = %d. | | ERR-00009 | ERR_FILE_STAT | Failed to stat file<%s>, errno = %d. | | ERR-00010 | ERR_FILE_OPEN | Failed to open file<%s>, errno = %d. | | ERR-00011 | ERR_FILE_CLOSE | Failed to close file<%s>, errno = %d. | | ERR-00012 | ERR_FILE_SEEK | Failed to seek file<%s>, offset:%lld, Whence:%d, errno = %d. | | ERR-00013 | ERR_FILE_READ | Failed to read file<%s>, size:%llu, errno = %d. | | ERR-00014 | ERR_FILE_WRITE | Failed to write file<%s>, size:%llu, errno = %d. | | ERR-00015 | ERR_FILE_READ_SIZE | Failed to read file<%s> (offset:%llu, req size:%llu, read size: %llu), errno = %d. | | ERR-00016 | ERR_FILE_WRITE_SIZE | Failed to write file<%s> (offset:%llu, req size:%llu, read size: %llu), errno = %d. | | ERR-00017 | ERR_FILE_SYNC | Failed to sync file<%s>, errno = %d. | | ERR-00018 | ERR_FILE_LOCK | Failed to lock file<%s>, errno = %d. | | ERR-00019 | ERR_FILE_TRYLOCK | Failed to trylock file<%s>, errno = %d. | | ERR-00020 | ERR_FILE_UNLOCK | Failed to unlock file<%s>, errno = %d. | | ERR-00021 | ERR_FILE_NO_EXTENSION | There is no file extension. | | ERR-00022 | ERR_FILE_RENAME_RETRY | Failed to rename file<%s> to file<%s>, retry count<%d>, msec<%d>, errno = %d. | | ERR-00031 | ERR_STRING_SNPRINTF | Error occurred during snprintf: buffer size<%d>, errno = %d. | | ERR-00061 | ERR_ENV_GET | Failed to getenv variable<%s>, errno = %d. | | ERR-00062 | ERR_ENV_SET | Failed to setenv variable<%s> to value<%s>, errno = %d. | | ERR-00067 | ERR_DIR_OPEN | Failed to opendir <%s>, errno = %d. | | ERR-00068 | ERR_DIR_CLOSE | Failed to closedir, errno = %d. | | ERR-00069 | ERR_DIR_READ | Failed to readdir, errno = %d. | | ERR-00070 | ERR_DIR_REWIND | Failed to rewinddir, errno = %d. | | ERR-00071 | ERR_DIR_MAKE | Failed to makedir <%s>, errno = %d. | | ERR-00072 | ERR_DIR_REMOVE | Failed to removedir, errno = %d. | | ERR-00073 | ERR_DIR_SETCWD | Failed to setcwd, errno = %d. | | ERR-00074 | ERR_DIR_GETCWD | Failed to getcwd, errno = %d. | | ERR-00075 | ERR_DIR_GETHOME | Failed to gethome, errno = %d. | | ERR-00076 | ERR_DIR_PATH_TOO_LONG1 | Path<%s> is too long, errno = %d. | | ERR-00077 | ERR_DIR_PATH_TOO_LONG2 | Path<%s/%s> is too long, errno = %d. | | ERR-00078 | ERR_DIR_PATH_TOO_LONG3 | Path<%s/%s/%s> is too long, errno = %d. | | ERR-00079 | ERR_DIR_NOT_EXIST | The directory does not exist in this path<%s>, errno = %d. | | ERR-00080 | ERR_DIR_REMOVE_WITH_INFO | Failed to call removedir (%s), errno = %d. | | ERR-00091 | ULL_ERR_PMD_SQLITE3_ERROR | %1$s failed: [%2$d: %3$s]. | | ERR-00092 | ULL_ERR_PMD_SQLITE3_DISK_FULL | %1$s failed because the metadata store is full: [%2$d: %3$s]. | | ERR-00121 | ERR_STACK_CREATE | Stack create failed, errno = %d. | | ERR-00122 | ERR_STACK_PUSH | Stack push failed, errno = %d. | | ERR-00123 | ERR_STACK_POP | Stack pop failed, errno = %d. | | ERR-00131 | ERR_MEMORY_ALLOC | Failed to allocate memory(%lu bytes), errno = %d. | | ERR-00132 | ERR_MEMORY_ALLOC_BOUND | Memory allocation error (alloc'd: %llu, max: %llu). | | ERR-00133 | ERR_PM_PROCESS_MEMORY_LIMIT | Failed to allocate memory (ID = %d) (Request Size = %llu) : (Current Allocated Size / PROCESS_MAX_SIZE (%llu/%llu)). | | ERR-00141 | ERR_MEMPOOL_CREATE | Failed to create memory pool, errno = %d. | | ERR-00142 | ERR_MEMPOOL_ALLOC | Failed to allocate memory from memory pool, errno = %d. | | ERR-00151 | ERR_MUTEX_CREATE | Failed to create mutex, errno = %d. | | ERR-00152 | ERR_MUTEX_DESTROY | Failed to destroy mutex, errno = %d. | | ERR-00153 | ERR_MUTEX_LOCK | Failed to lock mutex, errno = %d. | | ERR-00154 | ERR_MUTEX_TRYLOCK | Failed to trylock mutex, errno = %d. | | ERR-00155 | ERR_MUTEX_UNLOCK | Failed to unlock mutex, errno = %d. | | ERR-00161 | ERR_QUEUE_CREATE | Failed to create queue, errno = %d. | | ERR-00162 | ERR_QUEUE_DESTROY | Failed to destroy queue, errno = %d. | | ERR-00163 | ERR_QUEUE_ENQUEUE | Failed to enqueue queue, errno = %d. | | ERR-00164 | ERR_QUEUE_DEQUEUE | Failed to dequeue queue, errno = %d. | | ERR-00171 | ERR_THR_ATTR_CREATE | Failed to create thread_attr, errno = %d. | | ERR-00172 | ERR_THR_ATTR_DESTROY | Failed to destroy thread_attr, errno = %d. | | ERR-00173 | ERR_THR_ATTR_SET_BOUND | Failed to set thread_attr bound, errno = %d. | | ERR-00174 | ERR_THR_ATTR_SET_DETACH | Failed to set thread_attr detach, errno = %d. | | ERR-00175 | ERR_THR_ATTR_SET_STACK_SIZE | Failed to set thread_attr stack size, errno = %d. | | ERR-00176 | ERR_THR_CREATE | Failed to create thread, errno = %d. | | ERR-00177 | ERR_THR_DETACH | Failed to detach thread, errno = %d. | | ERR-00178 | ERR_THR_JOIN | Failed to join thread, errno = %d. | | ERR-00179 | ERR_THR_GETID | Failed to get id of thread, errno = %d. | | ERR-00191 | ERR_THR_CV_CREATE | Failed to create thread condition variable, errno = %d. | | ERR-00192 | ERR_THR_CV_DESTROY | Failed to destroy thread condition variable, errno = %d. | | ERR-00193 | ERR_THR_CV_TIMEDWAIT | Failed to call cond_timedwait, errno = %d. | | ERR-00194 | ERR_THR_CV_SIGNAL | Failed to call cond_signal, errno = %d. | | ERR-00195 | ERR_THR_CV_BROADCAST | Failed to call cond_broadcast, errno = %d. | | ERR-00196 | ERR_THR_CV_WAIT | Failed to call cond_wait, errno = %d. | | ERR-00201 | ERR_RWMUTEX_CREATE | Failed to create rwlock, errno = %d. | | ERR-00202 | ERR_RWMUTEX_DESTROY | Failed to destroy rwlock, errno = %d. | | ERR-00203 | ERR_RWMUTEX_LOCK_READ | Failed to call rwlock_lock_read, errno = %d. | | ERR-00204 | ERR_RWMUTEX_TRYLOCK_READ | Failed to call rwlock_trylock_read, errno = %d. | | ERR-00205 | ERR_RWMUTEX_LOCK_WRITE | Failed to call rwlock_lock_write, errno = %d. | | ERR-00206 | ERR_RWMUTEX_TRYLOCK_WRITE | Failed to call rwlock_trylock_write, errno = %d. | | ERR-00211 | ERR_RBTREE_TOO_SMALL_BUFFER | RBTREE buffer<%d> is too small for value<%d>, errno = %d. | | ERR-00212 | ERR_RBTREE_CURSOR_OP_NOT_APPLICABLE | RBTREE cursor op not applicable. errno = %d. | | ERR-00213 | ERR_RBTREE_ALREADY_FREE_NODE | RBTREE node is already freed, errno = %d. | | ERR-00216 | ERR_TREEMAP_KEY_EXISTS | Key already exists. | | ERR-00221 | ERR_LZO_COMPRESS | LZO compress failed, errno = %d. | | ERR-00222 | ERR_LZO_DECOMPRESS | LZO decompress failed, errno = %d. | | ERR-00231 | ERR_GET_CPU_COUNT | Failed to get CPU count, errno = %d. | | ERR-00232 | ERR_CONF_NO_FILE | Configuration file does not exist(%S). | | ERR-00251 | ERR_TLSF_MEL_INITIALIZE | Tlsf memory manager initialization failed, errno = %d. | | ERR-00252 | ERR_TLSF_MEL_FINALIZE | Tlsf memory manager finalization failed, errno = %d. | | ERR-00253 | ERR_TLSF_MEL_ALLOC | Tlsf memory manager allocation(%lld) failed, errno = %d. | | ERR-00254 | ERR_TLSF_MEL_FREE | Tlsf memory manager free failed, errno = %d. | | ERR-00255 | ERR_TLSF_MEL_CONTROL | Tlsf memory manager control failed, errno = %d. | | ERR-00256 | ERR_TLSF_MEL_SHRINK | Tlsf memory manager shrink failed, errno = %d. | | ERR-00257 | ERR_TLSF_MEL_GETSTATISTICS | Tlsf memory manager getstatistics failed, errno = %d. | | ERR-00271 | ERR_SESSION_CLOSED | The session is closed. | | ERR-00272 | ERR_SESSION_CANCELED | The session is canceled. | | ERR-00291 | ERR_LICENSE_INVALID | The license is invalid or expired. | | ERR-00292 | ERR_LICENSE_NOTEXIST_VALUE | The value<%s> does not exist in the license file. | | ERR-00293 | ERR_LICENSE_GET_HARDWARE_KEY | Failed to get hardware key, errno =%d | | ERR-00294 | ERR_LICENSE_VERIFY | Failed to verify the license, errno = %d | | ERR-00300 | ERR_INVALID_DATE_VALUE | Invalid date value.(%s) | | ERR-00301 | ERR_INVALID_NETWORK_TYPE | Invalid network string.(%s) | | ERR-00321 | ERR_SHA_SHA1_INIT_ERROR | Error in initializing sha1, errno = %d | | ERR-00322 | ERR_SHA_SHA1_UPDATE_ERROR | Error in updating sha1, errno = %d | | ERR-00323 | ERR_SHA_SHA1_FINAL_ERROR | Error in finalizing sha1, errno = %d | | ERR-00324 | ERR_SHA_INVALID_TYPE_ERROR | Invalid SHA type.(%d) | | ERR-00325 | ERR_SHA_INVALID_HEX_STRING | Invalid SHA hex string.(%s) | | ERR-00341 | ERR_PARALLEL_JOB_MANAGER_THREAD_ABNORMAL_SHUTDOWN | Parallel job thread abnormally terminated | | ERR-00342 | ERR_PARALLEL_JOB_MANAGER_INVALID_THREAD_COUNT | The thread count should be between %d and %d | | ERR-00361 | ERR_RESFILE_BUFFER_SET_LOG_ERROR | Error in setting a log to the buffer of the result file: %s, errno = %d | | ERR-00381 | ERR_PCRE_COMPILE_ERROR | Regular expression error: an error occurred at offset %d of (%s). | | ERR-00400 | ERR_VERSION_NO_META | This DB file is older than binary (no meta-version table). Check database image and binary. | | ERR-00401 | ERR_VERSION_MISMATCH | Version mismatched. In Executable DB(%d.%d) META(%d.%d) CM(%d.%d) But, In File DB(%d.%d) META(%d.%d) CM(%d.%d) | | ERR-00402 | ERR_META_VERSION_TOO_HIGH | Incompatible meta version. File Meta Version(%d.%d) is higher than Executable Version(%d.%d) | | ERR-00420 | ERR_GET_SYS_INFO | Error in getting system information by the sysinfo, errno = %d | | ERR-00421 | ERR_GET_STACK_SIZE | Error in getting stack information by the pmuSysSetStackSize, errno = %d | | ERR-00422 | ERR_SET_STACK_SIZE | Error in setting stack information by the pmuSysSetStackSize, errno = %d | | ERR-00431 | ERR_MEM_MMAP | mmap (size<%u>) error, errno = %d | | ERR-00432 | ERR_MEM_UNMMAP | unmap (address<%p>, size<%u>) error, errno = %d | | ERR-00451 | ERR_CPU_AFFINITY_SET | Failed to set the CPU affinity [%u, %u), errno = %d | | ERR-00452 | ERR_CPU_AFFINITY_INVALID_CPUID | The IDs of CPUs should be between [0, %u), but [%u, %u) given. | | ERR-00453 | ERR_CPU_AFFINITY_INVALID_CPURANGE | Maximum abs value of CPU_AFFINITY_COUNT(%d) should be less than CPU count(%u). | | ERR-00461 | ERR_SYSCONF_CPUCNT | Failed to get the number of CPUs in sysconf, errno = %d | | ERR-00471 | ERR_PM_HEAP_INIT | Failed to initialize a heap. | | ERR-00472 | ERR_PM_HEAP_PUSH | Heap push failed, errno = %d | | ERR-00481 | ERR_PM_AUTH_NONCE_GENERATE | Failed to generate auth nonce. | | ERR-00482 | ERR_PM_AUTH_SIGN | Failed to sign auth challenge. | | ERR-00483 | ERR_PM_AUTH_VERIFY | Failed to verify auth signature. | | ERR-00484 | ERR_PM_AUTH_INVALID_KEY | Invalid auth key. | | ERR-00485 | ERR_PM_AUTH_INVALID_SIG_SCHEME | Invalid auth signature scheme. | | ERR-00486 | ERR_PM_AUTH_INVALID_SIGNATURE | Invalid auth signature. | | ERR-00487 | ERR_PM_AUTH_INVALID_NONCE | Invalid auth nonce. | | ERR-00488 | ERR_PM_AUTH_KEY_FILE_TOO_LARGE | Auth key file is too large. path=[%s], size=[%llu], limit=[%llu] | | ERR-00491 | ERR_JSON_DUMP | Error in json dump. | | ERR-00492 | ERR_JSON_LOAD | Error in json load. | | ERR-00493 | ERR_JSON_OBJ | json object error: %s | | ERR-00494 | ERR_JSON_ARR | Error in json-array. | | ERR-00495 | ERR_JSON_STR | Error in json-string (%s). | | ERR-00496 | ERR_JSON_INT | Error in json-integer (%lld). | | ERR-00497 | ERR_JSON_REAL | Error in json-real (%lf). | | ERR-00498 | ERR_JSON_COPY | Error in json copy. | | ERR-00499 | ERR_JSON_PACK | Error in json pack. | | ERR-00500 | ERR_JSON_UPACK | Error in json unpack. | | ERR-00501 | ERR_JSON_EXTR_PATH | No data matches for the json path (%s) | | ERR-00502 | ERR_JSON_PATH_LEN | Json path is too long. | | ERR-00503 | ERR_JSON_OBJECT_VALUE_SET | Error json object set (%s). | | ERR-00504 | ERR_JSON_OBJECT_ARRAY_APPEND | Error json array append. | | ERR-00505 | ERR_JSON_ENCODE | Error encode base64. | | ERR-00506 | ERR_JSON_DECODE | Error decode base64. | | ERR-00507 | ERR_JSON_OBJECT_VALUE_DEL | Error json object del (%s). | | ERR-00600 | ERR_INVALID_PROPERTY_VALUE | Invalid property value: %s. | | ERR-00601 | ERR_PM_CONVERSION_UTF8 | Failed to convert %s to UTF8. (%s, errno=%d) | | ERR-00602 | ERR_PM_CONVERTSION_STRING_LENGTH | Buffer size is not enough for code conversion. (%d > %d) | | ERR-00611 | ERR_INVALID_PROPERTY_EXPRESSION | Invalid property expression for %s: %s. | | ERR-00701 | ERR_PM_GEOHASH_INVALID_PRECISION | Geohash invalid precision (%u) | | ERR-00702 | ERR_PM_GEOHASH_INVALID_LENGTH | Geohash invalid length | | ERR-00703 | ERR_PM_GEOHASH_INVALID_DIRECTION | Geohash invalid direction | ### `ERR-01000`–`ERR-01999` (191) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-01000 | ERR_SM_INVALID_DISK_FILE | File<%s> is invalid. | | ERR-01001 | ERR_SM_INVALID_OBJ_STORAGE_ID | Invalid object storage id, errno = %d. | | ERR-01002 | ERR_SM_INVALID_ALREADY_FREE_OBJECT_STORAGE | Object storage<%d> already freed, errno = %d. | | ERR-01003 | ERR_SM_DBS_DIR_ALREADY_EXIST | Group storage dir<%s> already exists, errno = %d. | | ERR-01004 | ERR_SM_DBS_INVALID_OBJECT_FILENAME | Object filename<%s> is invalid, errno = %d. | | ERR-01005 | ERR_SM_DISK_FILE_IN_USE | Disk file<%s> is in use, errno = %d. | | ERR-01006 | ERR_SM_NOT_SUPPORT_FUNCTION | Functionality is not supported yet. | | ERR-01007 | ERR_SM_FILE_NO_AVAILABLE_DISK_SPACE | There is no available disk space for writing <%lld>bytes to the file<%s>, errno = %d. | | ERR-01008 | ERR_SM_FILE_DUPLICATE | Error in the duplicating file<%s>, errno = %d. | | ERR-01009 | ERR_SM_WRONG_READ_SIZE | Error in the read file size.(<io: %u>, <disk: %u>) | | ERR-01010 | ERR_SM_SPACE_NOT_AVAILABLE_4_APPEND | Used media space is reached to threshold. (%4.1lf%% cap < %4.1lf%% used) | | ERR-01011 | ERR_SM_FILE_WRITE_SIZE_MISMATCH | Error in the write file size.(<write: %u>, <written: %u>) | | ERR-01031 | ERR_SM_DB_ALREADY_MOUNTED | The database in <%s> has already been mounted. | | ERR-01032 | ERR_SM_DB_NOT_MOUNTED | The database in <%s> is not mounted. | | ERR-01033 | ERR_SM_DB_MOUNTING | The mount operation of database in <%s> is not completed. | | ERR-01034 | ERR_SM_DB_MOUNT_BUSY | The mounted database<%s> is busy. | | ERR-01035 | ERR_SM_DB_ALREADY_EXIST | The database creation is not complete. Destroy it and create a new one. | | ERR-01036 | ERR_SM_DB_CREATE_NOT_COMPLETE | The database creation is not complete. Destroy it and create a new one. | | ERR-01037 | ERR_SM_DB_MOUNT_INVALIDE_BASEDB | The mount database<%s> is not backed up from the primary database | | ERR-01038 | ERR_SM_DB_COULD_NOT_FIND_MOUNTDB | Cannot find MountDB with <TBSID: %lld>. | | ERR-01039 | ERR_SM_DB_STATE_OF_MOUNTDB_IS_ABNORMAL | Mount DB<%s>'s state is invalid. | | ERR-01101 | ERR_SM_COLUMN_PARTITION_CACHE_READ_BLOCK | Error in reading column partition cache block. Reading block of RID<%lld> in the column partition<%lld> failed, errno = %d. | | ERR-01102 | ERR_SM_INVALID_CACHE_OBJECT | Invalid cache object. | | ERR-01103 | ERR_SM_CHECKPOINT_THREAD_ABNORMAL_SHUTDOWN | Error occurred in checkpoint thread. Processing abnormal shutdown. | | ERR-01104 | ERR_SM_CACHE_WAIT_READ_PAGE | Error in waiting to read a page. | | ERR-01105 | ERR_SM_CACHE_PAGE_CLEAR_THREAD_ABNORMAL_SHUTDOWN | Error in clear thread of the page cache. | | ERR-01106 | ERR_SM_CACHE_PAGE_MAX_SET_SMALLER_SIZE | It<%llu> is smaller than the max size value of the page cache currently set<%llu>. | | ERR-01107 | ERR_SM_CACHE_PAGE_MAX_SET_IMPOSSIBLE_SIZE | It<%llu> is impossible to set a value larger than the memory size set in the current process<%llu>. | | ERR-01108 | ERR_SM_CP_INVALID_PAGE_ID | Invalid page id in column partition. Page id<%d> is greater than the page max id<%d>. | | ERR-01201 | ERR_SM_ALREADY_EXIST_TABLE_ID_TABLES | Duplicated table id<%llu> in SYS_STORAGE_TABLES, errno = %d. | | ERR-01202 | ERR_SM_ALREADY_EXIST_TABLE_ID_COLUMNS | Duplicated table id<%llu>, column id<%u> in the SYS_STORAGE_COLUMNS, errno = %d. | | ERR-01203 | ERR_SM_NOT_EXIST_TABLE_ID_IN_TABLES | Table id<%lld> does not exist in SYS_STORAGE_TABLES, errno = %d. | | ERR-01204 | ERR_SM_ALREADY_EXIST_INDEX_ID_INDEXES | Duplicated (table id<%llu>, index id<%llu>) in SYS_STORAGE_INDEXES, errno = %d. | | ERR-01205 | ERR_SM_ALREADY_EXIST_INDEX_ID_COLUMNS | Duplicated (table id<%llu>, index id<%llu>, column id<%u>) in SYS_STORAGE_INDEXES_COLUMNS, errno = %d. | | ERR-01206 | ERR_SM_NOT_EXIST_INDEX_ID_IN_INDEXES | Index ID<%llu> of table ID<%llu> does not exist in SYS_STORAGE_INDEXES, errno = %d. | | ERR-01207 | ERR_SM_INVALID_RECOVERY_MODE_STRING | Available recovery modes: simple, complex, reset | | ERR-01301 | ERR_SM_NOT_EXIST_PARTION_RANGE | Partition range does not exist. Partition id is less than <%lld> in the table(id<%lld>) with partitions between <%lld> and <%lld>. | | ERR-01302 | ERR_SM_NOT_EXIST_RECORD_RANGE | Invalid record range. No such record whose id is less than <%llu> in the table(id<%llu>) with records between <%llu> and <%llu>. | | ERR-01303 | ERR_SM_TOO_MANY_COLUMNS_FOR_TABLE | Maximum number of columns in a table is %d. | | ERR-01304 | ERR_SM_INVALID_COLUMN_ID | Invalid column ID (<%d>). | | ERR-01305 | ERR_SM_TABLE_NOT_EXIST | Invalid table ID (<%llu>). | | ERR-01306 | ERR_SM_TABLE_ALREADY_DROPPED | Table has been dropped. | | ERR-01307 | ERR_SM_TABLE_STRUCTURE_MODIFIED | Table structure was modified. | | ERR-01308 | ERR_SM_TABLE_INVALID_FIXED_COLUMN_SIZE | Invalid fixed column size. Invalid value size(<%u>) for the fixed column. | | ERR-01309 | ERR_SM_TABLE_VAR_COLUMN_SIZE_TOO_BIG | Invalid varying column size. Value size(<%u>) for the variable column is greater than the max size (<%u>). | | ERR-01310 | ERR_SM_TABLE_FLUSH_THREAD_ABNORMAL_SHUTDOWN | Table flush thread terminated abnormally. | | ERR-01311 | ERR_SM_TABLE_COLUMN_PARTITION_PREPARE_THREAD_ABNORMAL_SHUTDOWN | Table column partition prepare thread terminated abnormally. | | ERR-01312 | ERR_SM_TABLE_COLUMN_PARTITION_FILE_READ_HEAD | Failed to read the head of the table column partition file (<%s>). | | ERR-01313 | ERR_SM_TABLE_COLUMN_PARTITION_FILE_READ | Failed to read the table column partition file (<%s>). | | ERR-01314 | ERR_SM_TABLE_INDEX_BUILD_THREAD_ABNORMAL_SHUTDOWN | Index build thread terminated abnormally. | | ERR-01315 | ERR_SM_TABLE_INVALID_TYPE | Invalid table type<%d>. | | ERR-01316 | ERR_SM_TABLE_COLUMN_SIZE_TOO_BIG | Column size<%u> is too big. | | ERR-01317 | ERR_SM_TABLE_COLUMN_INVALID_TIME_VALUE | Value of the time column(<%lld>) is less than the last time value(<%lld>). | | ERR-01318 | ERR_SM_TABLE_COLUMN_INVALID_VARCHAR_SIZE | The size of VARCHAR column must be less than (<%llu>). | | ERR-01319 | ERR_SM_TABLE_COLUMN_INVALID_VALUE_SIZE | The size of column value must be less than (<%u>). | | ERR-01320 | ERR_SM_TABLE_COLUMN_REFERENCED_BY_INDEX | There is an index on the column(<%u>) of the table(<%llu>) | | ERR-01321 | ERR_SM_TABLE_NOT_SUPPORT_FUNCTION | This feature is not supported on this table type. | | ERR-01322 | ERR_SM_TABLE_COLUMN_INVALID_NEWSIZE | The new column size(<%u>) should be greater than the old one(<%u>) | | ERR-01323 | ERR_SM_TABLE_COLUMN_MAX | The table(%llu) reached max column count limit (%u) already. | | ERR-01324 | ERR_SM_TABLE_COLUMN_PARTITION_FILE_ADJUST_END_RID | An error occurred adjusting end rid of the table<%llu> column partition(<%llu>), errno = %d. | | ERR-01325 | ERR_SM_TABLE_COLUMN_TOO_SMALL_END_RID | The end RID<%lld> of the column<%d> is less than the end RID<%llu> of the table<%llu> | | ERR-01330 | ERR_SM_TABLE_COLUMN_NOT_FOUND | The column with ID<%hu> does not exist in the table with ID<%llu> | | ERR-01331 | ERR_SM_TABLE_CHECKPOINT_THREAD_ABNORMAL_SHUTDOWN | Table checkpoint thread terminated abnormally. | | ERR-01332 | ERR_SM_TABLE_NOT_EXIST_PARTION | Partition ID <%llu> of the table(id<%llu>) does not exist between <%llu> and <%llu>. | | ERR-01333 | ERR_SM_TABLE_MOUNT_ALREADY | The table<%llu> in the backup database<%s> has been mounted already. | | ERR-01334 | ERR_SM_TABLE_MOUNT_BUSY_WITH_MOUNTING | The table is busy with mounting. | | ERR-01335 | ERR_SM_TABLE_MOUNT_BUSY_WITH_UNMOUNTING | The mounted table is busy with unmounting. | | ERR-01336 | ERR_SM_TABLE_MOUNT_INVALID_STATE | The mounted table is invalid. | | ERR-01337 | ERR_SM_TABLE_MOUNT_IS_BUSY | The mounted table is busy. | | ERR-01338 | ERR_SM_TABLE_MOUNT_NOT_EXIST | The table is not mounted. | | ERR-01339 | ERR_SM_TABLE_MOUNT_TABLE_NOT_SAME_WITH_TABLE | The table<%llu> of the backup tablespace<%s> is different from the table in main database. | | ERR-01340 | ERR_SM_TABLE_MOUNT_TABLE_DROPPED_IN_MAIN_DATABASE | The table<%llu> of the backup tablespace<%s> is dropped from the main database. | | ERR-01341 | ERR_SM_TABLE_HAS_MOUNTED_TABLE | There is a mounted table in the table<%llu>. | | ERR-01342 | ERR_SM_TABLE_MOUNT_HAS_FUTURE_DATA | The mount table<end_rid:%llu> has more furture data than the base table<end_rid:%llu. | | ERR-01343 | ERR_SM_TABLE_UPDATE_COLUMN_INDEX_CREATED | Cannot update columns with indexes in VOLATILE / LOOKUP table. | | ERR-01344 | ERR_SM_TABLE_VOLITILE_MEMORY_LIMIT | The memory size<%llu bytes> of VOLATILE / LOOKUP tables exceeds <%llu bytes>. | | ERR-01345 | ERR_SM_TABLE_COLUMN_VALUE_NOT_NULL | The value of the column<%u> must not be NULL | | ERR-01346 | ERR_SM_PROCESS_MEMORY_LIMIT | Current Allocate Memory / PROCESS_MAX_SIZE (%llu/%llu), increase PROCESS_MAX_SIZE property and restart. | | ERR-01401 | ERR_SM_INDEX_INVALID_TYPE | Invalid index type. Index type<%d> does not exist. | | ERR-01402 | ERR_SM_INDEX_NOT_EXIST_IN_TABLE | Index id(<%llu>) does not exist in table id <%llu>. | | ERR-01403 | ERR_SM_INDEX_INVALID_COLUMN_COUNT | Index has invalid column count(<%d>). | | ERR-01404 | ERR_SM_INDEX_INVALID_KEYVALUE_COUNT | Index has invalid key value count(<%d>). | | ERR-01405 | ERR_SM_INDEX_INVALID_KEYVALUE_SIZE | Index has invalid key value size(<%d>). | | ERR-01406 | ERR_SM_INDEX_INVALID_FILE | Index column file(<%s>) is invalid. | | ERR-01407 | ERR_SM_INDEX_COLUMN_PARTITION_FILE_READ_HEAD | Failed to read the head of the index column partition file(<%s>). | | ERR-01408 | ERR_SM_INDEX_COLUMN_PARTITION_FILE_READ | Failed to read the index column partition file(<%s>). | | ERR-01409 | ERR_SM_INDEX_COLUMN_INVALID_COLUMN_TYPE | Type of the column for the index is invalid. | | ERR-01410 | ERR_SM_INDEX_FLUSH_THREAD_ABNORMAL_SHUTDOWN | Index flush thread terminated abnormally. | | ERR-01411 | ERR_SM_INDEX_BUILD_THREAD_ABNORMAL_SHUTDOWN | Index build thread terminated abnormally. | | ERR-01412 | ERR_SM_KDW_INDEX_INVALID_KEY_SIZE | The keyword size<%d> should be less than the max size<%d>. | | ERR-01413 | ERR_SM_INDEX_INVALID_WORDBITCNT | The word bit count(%d) is over than %d in the partition<%lld> of the index <%lld> | | ERR-01414 | ERR_SM_INDEX_INVALID_KEYVALCNT | Invalid key count <%u> is not equal to the count <%u> in partition <%lld> of index <%lld>. | | ERR-01415 | ERR_SM_INDEX_INVALID_LEVEL | The level<%u> of the index is bigger than the max level<%u> | | ERR-01416 | ERR_SM_INDEX_INVALID_LEVEL_PART_SIZE | The partition size<%u> of level<%u> is bigger than the max level<%u> | | ERR-01417 | ERR_SM_INDEX_ALREADY_DROPPED | The index has been dropped. | | ERR-01418 | ERR_SM_INDEX_UNIQUE_VIOLATION | The key already exists in the unique index. | | ERR-01419 | ERR_SM_INDEX_PRIMARY_INDEX_ALREADY_CREATED | The primary index is already created on the table. | | ERR-01420 | ERR_SM_INDEX_INVALID_KEYVALUE_N_BITVECTOR_COUNT | The number<%llu> of key values is different from the number<%llu> of bitvectors. | | ERR-01421 | ERR_SM_INDEX_LSM_INVALID_PART_FILE | The partition file<%llu> on the level<%u> of the index<%llu> is invalid.(KPC:%u, BPC:%u) | | ERR-01422 | ERR_SM_INDEX_PRAIMARY_INDEX_NOT_NULL | NULL value is not allowed for the primary index column | | ERR-01423 | ERR_SM_KEYVALUE_CACHE_EXHAUSTED | TAG cache exhausted, increase TAG_CACHE_MAX_MEMORY_SIZE(%llu) | | ERR-01424 | ERR_SM_KEYVALUE_CACHE_TIMEOUT | Could not allocate TAG cache: (Table,part=%llu,%llu) offset/size=%llu/%llu | | ERR-01425 | ERR_SM_KEYVALUE_INDEX_MEMORY_LIMIT | Failed to allocate index memory (Current Allocated Size / Threshold size (%llu/%llu)). | | ERR-01426 | ERR_SM_KEYVALUE_NOT_READY_TO_BUILD_INDEX | Not ready to build keyvalue index (Current Count / Target Count (%llu/%llu) in File). | | ERR-01501 | ERR_SM_CPFILE_INVALID_PAGE_ID | Invalid page id in cpfile. Page id<%d> for the column partition file<%s> is greater than the page max id. | | ERR-01502 | ERR_SM_CPFILE_INVALID_PAGE_TIMESTAMP | Error in reading page<%d> in the column partition file<%s>. Page timestamps <head:%lld, tail:%lld> are invalid. | | ERR-01503 | ERR_SM_CPFILE_INVALID_PAGE_CHECKSUM | Error in reading page<%d> in the column partition file<%s>. Page checksum <write:%#X, read:%#X> are invalid | | ERR-01504 | ERR_SM_CPFILE_FILE_INVALID_SIZE | The size<%u> of the column partition file<%s> is too small. It is supposed to be greater than the size<%u> | | ERR-01505 | ERR_SM_CPFILE_FILE_INVALID_PAGE_UPDATE | The offset<%u> and size<%u> of the update value for the page<id:%u, offset:%u, size:%u> in the column partition file<%s> is invalid | | ERR-01506 | ERR_SM_CPFILE_INVALIDE_FILE_HEAD_CRC | The checksum<write:%#X, read:%#X> of the head of the column partition file<%s> is invalid. | | ERR-01551 | ERR_SM_FDCACHE_GET_FD_FOR_FILE | Error in getting the fd of the file<%s> from the fd cache. | | ERR-01601 | ERR_SM_AGER_THREAD_ABNORMAL_SHUTDOWN | Ager thread terminated abnormally. | | ERR-01631 | ERR_SM_BACKUP_NOT_EXIST_BACKUP_ROOT_DIR | There is no root dir<%s> for the database backup. | | ERR-01632 | ERR_SM_BACKUP_NOT_DATABASE_DESTROYED | The database is not destroyed. | | ERR-01633 | ERR_SM_BACKUP_STATFILE_WRITE | Failed to write data<%u> of the backup stat file<%s>. | | ERR-01634 | ERR_SM_BACKUP_STATFILE_READ | Failed to read data<%u> of the backup stat file<%s>. | | ERR-01635 | ERR_SM_BACKUP_STATFILE_INVALID | The backup statfile<%s> is invalid(CRC<H:%u, B:%u, T:%u). | | ERR-01636 | ERR_SM_BACKUP_NOT_COMPLETE | The backup <%s> is not completed. | | ERR-01637 | ERR_SM_BACKUP_DIR_ALREADY_EXIST | The backup <%s> has already exist. | | ERR-01638 | ERR_SM_BACKUP_INVALID_END_RID | The end rid<%llu> of the table<%llu> in the restored database is invalid. | | ERR-01639 | ERR_SM_BACKUP_NAME_TOO_LONG | The name<%s> of backup is too long, errno = %d. | | ERR-01640 | ERR_SM_BACKUP_FILE_ALREADY_EXIST | The backup file<%s> already exists. | | ERR-01641 | ERR_SM_BACKUP_FILE_INVALID_MAGIC_STRING | The backup file<%s> has the invalid magic string<%s>. | | ERR-01642 | ERR_SM_BACKUP_FILE_HEAD_INVALID_CRC32 | The header of backup file<%s> has the invalid crc32<%u>. | | ERR-01643 | ERR_SM_BACKUP_FILE_INVALID_FILENAME_LEN | Length<%u> of backup file<%s> is too long. | | ERR-01644 | ERR_SM_BACKUP_FILE_INVALID_PAGESIZE | The page size <%u> of backup file<%s> is invalid. | | ERR-01645 | ERR_SM_BACKUP_FILE_INVALID_SIZE | The file size <%llu> of the head is different from the size<%llu> on the disk. | | ERR-01646 | ERR_SM_BACKUP_FILE_INVALID_STATE | The backup file is invalid since the backup is not completed. | | ERR-01647 | ERR_SM_INC_BACKUP_NOT_LATEST | An incremental backup requires a previous backup. | | ERR-01648 | ERR_SM_INC_BACKUP_TARGET_NOT_SAME | Backup targets are different from that of previous target. | | ERR-01701 | ERR_SM_TBS_REFERENCED_BY_OBJECTS | The tablespace<%s> is still referenced by other objects such as tables and indexes. | | ERR-01702 | ERR_SM_TBS_NOT_EXIST | The tablespace<%s> does not exist in the database. | | ERR-01703 | ERR_SM_TBS_CANNOT_DROP_SYSTEM_TBS | The SYSTEM_TABLESPACE cannot be dropped. | | ERR-01704 | ERR_SM_TBS_ALEADY_EXIST | Tablespace already exists. <%s> | | ERR-01705 | ERR_SM_TBS_DISKDIR_ALEADY_EXIST | The dir<%s> for the tablespace<%s> of datadisk<%s> already exists. | | ERR-01706 | ERR_SM_TBS_PHYDISK_NOT_EXIST | Disk<%s> does not exist in the tablespace<%s>. | | ERR-01707 | ERR_SM_TBS_PHYDISK_INVALID_PARALLEL_IO | The parallel I/O of a disk should be between %d and %d. | | ERR-01708 | ERR_SM_TBS_FILE_READ | Failed to read <%ld> bytes from the file<%s>, errno = %d. | | ERR-01709 | ERR_SM_TBS_FILE_PAGE_INVALID_TIMESTAMP | The page<offset:%u, size:%u> of the file<%s> is invalid because it has the invalid timestamp<head:%lld, tail:%lld> | | ERR-01710 | ERR_SM_TBS_FILE_PAGE_INVALID_CRC32 | The page<offset:%u, size:%u> of the file<%s> is invalid because it has the invalid crc<memory:%u, disk:%u> | | ERR-01711 | ERR_SM_TBS_VIRDISK_DIR_CREATE | Failed to create directory<%s> for virtual disk. | | ERR-01712 | ERR_SM_TBS_MEMORY_DIR_SHORTAGE | Failed to allocate memory for directory to be removed. | | ERR-01801 | ERR_SM_EXTCP_WAIT_READ_VALUE | Error in waiting to read value: value offset<%lld>, value size<%u>, and file<%s> | | ERR-01821 | ERR_SM_DWFILE_INVALID_IMAGE | The image in the DWFile<%s> is invalid. | | ERR-01841 | ERR_SM_ART_ABORT | The operation is aborted by ART. | | ERR-01851 | ERR_SM_NO_VAR_IN_TAG | Variable length columns are not allowed in tag table. | | ERR-01852 | ERR_SM_DELETE_IN_PROGRESS | Another deletion is in progress for table <%llX>. | | ERR-01853 | ERR_SM_KEYVALUE_CREATE_APPENDFILE | Cannot create append file for Key-Value table <%llX>, errno = %d. | | ERR-01854 | ERR_SM_KEYVALUE_SYNC_APPENDFILE | Cannot sync append file for Key-Value table <%llX>, errno = %d. | | ERR-01855 | ERR_SM_KEYVALUE_CLOSE_APPENDFILE | Cannot close append file for Key-Value table <%llX>, errno = %d. | | ERR-01856 | ERR_SM_KEYVALUE_CREATE_DATAFILE | Cannot create data file <%llX> for Key-Value table <%llX>, errno = %d. | | ERR-01857 | ERR_SM_KEYVALUE_OPEN_DATAFILE | Cannot open data file <%llX> for Key-Value table <%llX>, errno = %d. | | ERR-01858 | ERR_SM_KEYVALUE_READ_DATAFILE | Cannot read data file <%llX> for Key-Value table <%llX>, errno = %d. | | ERR-01859 | ERR_SM_KEYVALUE_WRITE_DATAFILE | Cannot write data file <%llX> for Key-Value table <%llX>, errno = %d. | | ERR-01860 | ERR_SM_KEYVALUE_CORRUPTED_DATAFILE | Data file <%llX> is corrupted for Key-Value table <%llX>. | | ERR-01861 | ERR_SM_KEYVALUE_CREATE_INDEXFILE | Cannot create index file <%llX> for Key-Value table <%llX>, errno = %d. | | ERR-01862 | ERR_SM_KEYVALUE_OPEN_INDEXFILE | Cannot open index file <%llX> for Key-Value table <%llX>, errno = %d. | | ERR-01863 | ERR_SM_KEYVALUE_READ_INDEXFILE | Cannot read <.%s> file <%llX> for Key-Value table <%llX>, errno = %d. | | ERR-01864 | ERR_SM_KEYVALUE_WRITE_INDEXFILE | Cannot write <.%s> file <%llX> for Key-Value table <%llX>, errno = %d. | | ERR-01865 | ERR_SM_KEYVALUE_CORRUPTED_INDEXFILE | Index file <%llX> is corrupted for Key-Value table <%llX>. | | ERR-01866 | ERR_SM_KEYVALUE_IOERROR | Cannot perform I/O for Key-Value table <%llX>. | | ERR-01867 | ERR_SM_KEYVALUE_INVALID_PATH_APPENDFILE | Invalid path to append file for Key-Value table <%llX>, errno = %d. | | ERR-01868 | ERR_SM_KEYVALUE_OPEN_APPENDFILE | Cannot open append file for Key-Value table <%llX>, errno = %d. | | ERR-01869 | ERR_SM_KEYVALUE_NO_DATAFILE | RID-based SELECT is not allowed without datafile, Table<%llX>/RID<%llu>. | | ERR-01870 | ERR_SM_KEYVALUE_OPEN_MOUNTED_APPENDFILE | Cannot open append file for mounted Key-Value table <%llX>, errno = %d. | | ERR-01871 | ERR_SM_KEYVALUE_READ_MOUNTED_APPENDFILE | Cannot read append file for mounted Key-Value table <%llX>, errno = %d. | | ERR-01872 | ERR_SM_BACKUP_IN_PROGRESS | Another backup is in progress for table <%llX>. | | ERR-01873 | ERR_SM_KEYVALUE_NO_INDEXFILE | No index-file <%llx> for Key-Value table Table<%llX>. | | ERR-01874 | ERR_SM_KEYVALUE_OPEN_FILE | Cannot open file <%llX> for Key-Value table <%llX> path<%s>, errno = %d. | | ERR-01875 | ERR_SM_KEYVALUE_NO_UNPURGED_NODE | Cannot find unpurged node for Key-Value table <%llX>. | | ERR-01876 | ERR_SM_KEYVALUE_FILE_DECOMPRESS | Failed to use %s to decompress file <%llX> for key-value table <%llx>, error = %d. | | ERR-01877 | ERR_SM_KEYVALUE_NOT_FOUND_STAT_DATA | Tag stat for id[%llu] is not found. | | ERR-01878 | ERR_SM_STAT_WRITE_FILE | Cannot write stat file for Key-Value table <%llX> path<%s> errno = %d. | | ERR-01879 | ERR_SM_STAT_READ_FILE | Cannot read stat file for Key-Value table <%llX> path<%s> errno = %d. | | ERR-01880 | ERR_SM_STAT_OPEN_FILE | Cannot open stat file for Key-Value table <%llX> path<%s>, errno = %d. | | ERR-01881 | ERR_SM_STAT_INVALID_FILE | Stat File Invalid TableID[%llu], TablePath[%s]. | | ERR-01882 | ERR_SM_KEYVALUE_NO_KVINDEXFILE | No kvindex-file <%llx> for Key-Value table Table<%llX>. | | ERR-01883 | ERR_SM_KEYVALUE_INVALID_TIME_VALUE | Value of the time column(<%lld>) must be greater than or equal to <%lld>. | | ERR-01884 | ERR_SM_KEYVALUE_THREAD_STOPPED | keyvalue table<%llx> thread for [%s] stopped. | | ERR-01885 | ERR_SM_KEYVALUE_DATA_CORRUPTED | Data row value is corrupted: required RID<%llu>, value RID<%llu>. | | ERR-01886 | ERR_SM_KEYVALUE_VDATA_CORRUPTED | %s varchar data is corrupted: required VRID<%u>, value VRID<%u>. | | ERR-01887 | ERR_SM_UPDATE_IN_PROGRESS | Another update is in progress for table <%llX>. | | ERR-01900 | ERR_SM_SNAPSHOT_NOT_VALID | Snapshot ID <%s> is invalid. | | ERR-01901 | ERR_SM_SNAPSHOT_NO_TABLE | Cannot snapshot with no table. | | ERR-01902 | ERR_SM_SNAPSHOT_TIMEOUT | Snapshot timed out. | | ERR-01903 | ERR_SM_SNAPSHOT_NOT_EXISTS | Snapshot ID <%s> does not exist. | | ERR-01904 | ERR_SM_SNAPSHOT_ALREADY_EXISTS | Snapshot ID <%s> already exists. | | ERR-01910 | ERR_SM_FREEZE_NO_TABLE | Cannot freeze with no table. | | ERR-01911 | ERR_SM_ALREADY_FROZEN | Snapshot already frozen. | | ERR-01951 | ERR_SM_FUNCTION_CALL | Failed to call function <%s>, errno=%d | | ERR-01952 | ERR_SM_TABLE_RESOURCE_BUSY | Table (0x%llx) resource busy (%s). | ### `ERR-02000`–`ERR-02999` (420) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-02000 | QPE_TEST | Memory allocation error, Error code = %d | | ERR-02001 | ERR_QP_OPEN_META | Error in opening meta. | | ERR-02002 | ERR_QP_EXEC_META | Error in executing meta. | | ERR-02003 | ERR_QP_CLOSE_META | Error in closing meta. | | ERR-02004 | ERR_QP_CRT_HASH | Error in creating hash. (errno=%d) | | ERR-02005 | ERR_QP_ALLOC_MEM | Error in allocating memory. | | ERR-02006 | ERR_QP_HASH_ADD | Error in adding hash. (errno=%d) | | ERR-02007 | ERR_QP_FETCH_META | Error in fetching meta. | | ERR-02008 | ERR_QP_HASH_TRAV | Error in traversing hash. | | ERR-02009 | ERR_QP_MEMORY_INSUFFICIENT | Insufficient parser memory. | | ERR-02010 | ERR_QP_PARSE_ERROR | Syntax error: near token (%s). | | ERR-02011 | ERR_QP_TOKEN_ERROR | Unrecognized token (%s). | | ERR-02012 | ERR_QP_SINGLE_ROW_ERROR | Single row error. Single-row subquery returns more than one row. (NOT USED) | | ERR-02013 | ERR_QP_NEED_GROUPBY_ERROR | A GROUP BY clause is required before HAVING. | | ERR-02014 | ERR_QP_COLUMN_NAME_DUPLICATED | Column name is duplicated: (%s). | | ERR-02015 | ERR_QP_COLUMN_TYPE_INVALID | Invalid column type: (%s). | | ERR-02016 | ERR_QP_NO_TABLE_PROPETY_FOUND | Table property (%s) does not exist. | | ERR-02017 | ERR_QP_NO_TABLE_PROPETY_CONVERT | Error in converting table property. Cannot convert string (%s) to integer. | | ERR-02018 | ERR_QP_NO_TABLE_PROPETY_VALUE_RANGE | Table property value is out of range: (%s). | | ERR-02019 | ERR_QP_VARCHAR_TYPE_SIZE_ERROR | Column size must be specified for a variable-length column type. | | ERR-02020 | ERR_QP_TYPE_SIZE_ZERO | Invalid size specified. Cannot specify type size to (%s). | | ERR-02021 | ERR_QP_CREATE_INDEX_INVALID_BITMAP_DATATYPE | Cannot create bitmap index on data type (%s) | | ERR-02022 | ERR_QP_CREATE_INDEX_INVALID_KEYWORD_DATATYPE | Cannot create keyword index on data type (%s) | | ERR-02023 | ERR_QP_SNPRINTF_ERROR | snprintf function error (%d). | | ERR-02024 | ERR_QP_TABLE_CREATE_DUPLICATE | Table %s already exists. | | ERR-02025 | ERR_QP_TABLE_NO_EXISTS | Table %s does not exist. | | ERR-02026 | ERR_QP_TABLE_INSERT_COLUMN_MISMATCH | The number of insert values does not match the number of columns. | | ERR-02027 | ERR_QP_TABLE_INSERT_COLUMN_INT_CONVERSION | Error in table insert column integer conversion. Insert value conversion to integer error (%s). | | ERR-02028 | ERR_QP_TABLE_INSERT_COLUMN_DOUBLE_CONVERSION | Error in table insert column double conversion. Insert value conversion to double error (%s) | | ERR-02029 | ERR_QP_TABLE_INSERT_COLUMN_TIME_FORMAT | Error in table insert column time format. Insert _arrival_time value conversion error. | | ERR-02030 | ERR_QP_TABLE_INSERT_NO_COLUMN | Column name (%s) does not exist. | | ERR-02031 | ERR_QP_TABLE_RESOURCE_BUSY | Resource busy (%s). | | ERR-02032 | ERR_QP_TYPE_COMPARE_CONVERSION | Type conversion error: error occurred while comparing the values of type (%s) and type (%s). | | ERR-02033 | ERR_QP_TYPE_CONCAT | Cannot concatenate non varchar types. | | ERR-02034 | ERR_QP_TIME_FORMAT | Invalid format of time expression. | | ERR-02035 | ERR_QP_FUNCTION_NO_EXISTS | Function [%s] does not exist. | | ERR-02036 | ERR_QP_FUNCTION_ARG | Function [%s] has an invalid argument. | | ERR-02037 | ERR_QP_FUNCTION_ARG_TYPE | Function [%s] argument data type does not match. | | ERR-02038 | ERR_QP_TABLE_NO_SUCH_FOR_STAR | Table [%s] does not exist. | | ERR-02039 | ERR_QP_TABLE_NO_SPECIFIED_FOR_STAR | No table specified in the target list. | | ERR-02040 | ERR_QP_TIME_RANGE_ERROR | Invalid time range. | | ERR-02041 | ERR_QP_TIME_NEGATIVE_ERROR | Time value must be positive. | | ERR-02042 | ERR_QP_NULL_EXPRESSION | Expression cannot have a NULL value. | | ERR-02043 | ERR_QP_AGGR_WHERE | Group function is not allowed here. | | ERR-02044 | ERR_QP_NO_GROUPBY | Not a GROUP BY expression. | | ERR-02045 | ERR_QP_TYPE_UNKNOWN | Type is not supported(typecode is %u). Internal error. | | ERR-02046 | ERR_QP_BUFFER_SHORTAGE | String buffer is not enough. | | ERR-02047 | ERR_QP_LOCK_BUFFER_SHORTAGE | Lock buffer is not enough. Table counts are too many. | | ERR-02048 | ERR_QP_BIND_COUNT_OVERFLOW | Bind parameter count is overflowed. (max=%u) | | ERR-02049 | ERR_QP_BIND_UNABLE | Cannot apply bind parameter. | | ERR-02050 | ERR_QP_BIND_BUFFER_CORRUPTED | Bind data from client is corrupted. | | ERR-02051 | ERR_QP_BIND_TYPE_UNKNOWN | Bind data type unknown (typecode is %u). | | ERR-02052 | ERR_QP_INSERT_UNABLE_TABLE | Cannot insert data into this table (%s). | | ERR-02053 | ERR_QP_TYPE_VALUE_CONVERSION | Failed to convert type (%s) to type (%s). | | ERR-02054 | ERR_QP_AGGR_ERROR_ON_FUNCTION | Aggregation error on function usage (NOT USED) | | ERR-02055 | ERR_QP_ERROR_ON_INSERT_VALUE | Invalid insert value. | | ERR-02056 | ERR_QP_COLUMN_NAME_NOT_FOUND | Column name (%s) not found. | | ERR-02057 | ERR_QP_SEARCH_STRING_ERROR | Only literal type can be used in SEARCH keyword. | | ERR-02058 | ERR_QP_INDEX_CREATE_DUPLICATE | Index %s already exists | | ERR-02059 | ERR_QP_INDEX_NO_EXISTS | Index %s does not exist | | ERR-02060 | ERR_QP_INDEX_ONLY_ONE_COLUMN | Composite index is not supported. | | ERR-02061 | ERR_QP_DIVIDE_BY_ZERO | Cannot divide a value by zero. | | ERR-02062 | ERR_QP_DATE_CALC_INVALID | Cannot calculate date type. | | ERR-02063 | ERR_QP_SEARCH_TYPE_INVALID | Invalid search type. Search type must be VARCHAR. | | ERR-02064 | ERR_QP_ADD_TIME_FORMAT_ERROR | Invalid time format. (format: "year/mon/day hour:min:sec") | | ERR-02065 | ERR_QP_NO_INDEX_PROPETY_FOUND | Index property (%s) does not exist. | | ERR-02066 | ERR_QP_INDEX_PROPETY_VALUE_INVALID | Invalid index property value: (%s). | | ERR-02067 | ERR_QP_TO_ADDR4_FUNCTION_ARG | Error in TO_ADDR4 function aggregate. Argument type to TO_ADDR4 function must be an integer. | | ERR-02068 | ERR_QP_IPV4_FORMAT | Invalid IPv4 address format (%s). | | ERR-02069 | ERR_QP_INDEX_FOR_INVALID_TABLE | %s index can only be created for %s table. | | ERR-02070 | ERR_QP_INDEX_NEEDED_FOR_SEARCH | Search predicate needs keyword index. | | ERR-02071 | ERR_QP_INDEX_COUNT | Only one index is allowed for a single column. | | ERR-02072 | ERR_QP_DELETE_UNABLE_TABLE | Cannot delete data from this table (%s). | | ERR-02073 | ERR_QP_TABLE_DELETE_CONDITION | Invalid DELETE condition. %s | | ERR-02074 | ERR_QP_TABLE_DELETE_TIME_RANGE | Invalid delete time range. BEFORE time range should be older than present. | | ERR-02075 | ERR_QP_TABLE_DROP_NO_INFO_IN_DB | Table(%s) record does not exist in meta database. | | ERR-02076 | ERR_QP_TABLEID_DROP_NO_INFO_IN_DB | Table(%lld) record does not exist in meta database. | | ERR-02077 | ERR_QP_VARCHAR_SIZE_MAX | Invalid %s size. %s type size cannot be more than %d. | | ERR-02078 | ERR_QP_UNKNOWN_STMT_TYPE | Invalid statement type. Statement type(%d) is unsupported. | | ERR-02079 | ERR_QP_FUNCTION_ARG_COUNT | The number of arguments for function (%s) does not match. | | ERR-02080 | ERR_QP_USER_NOT_EXIST | User (%s) does not exist. | | ERR-02081 | ERR_QP_USER_PASSWORD_ERROR | Invalid username/password. | | ERR-02082 | ERR_QP_USER_ALREADY_EXISTS | User (%s) already exists. | | ERR-02083 | ERR_QP_USER_SELF_DROP | You cannot drop yourself(%s). | | ERR-02084 | ERR_QP_USER_TABLE_EXIST | User drop error. This user's tables still exist. Drop those tables first. | | ERR-02085 | ERR_QP_USER_NO_ALTER_PRIV | The user(%s) does not have alter privileges. | | ERR-02086 | ERR_QP_USER_NO_CONNECT_PRIV | The user(%s) does not have connect privileges. | | ERR-02087 | ERR_QP_USER_NO_PRIV_TABLE_ACCESS | The user does not have access privileges on table(%s.%s). | | ERR-02088 | ERR_QP_ALTER_TABLE_NO_RIGHT | Error in altering table. Only the LOG table can be altered. | | ERR-02089 | ERR_QP_ALTER_TABLE_SAME_COLUMN_EXISTS | Error in altering table. Column name(%s) already exists. | | ERR-02090 | ERR_QP_ALTER_TABLE_MODIFY_TYPE | Error in altering table. Only varchar type can be modified. | | ERR-02091 | ERR_QP_ALTER_TABLE_MODIFY_VARCHAR_SIZE | Error in altering table. Varchar length should be greater than previous value length | | ERR-02092 | ERR_QP_ALTER_TABLE_DROP_BUILTIN_COLUMN | Error in altering table. Column (%s) cannot be dropped. | | ERR-02093 | ERR_QP_ALTER_TABLE_DROP_COLUMN_ON_INDEX | Error in altering table. Column (%s) having index cannot be dropped. | | ERR-02094 | ERR_QP_ALTER_TABLE_DUP_COLUMN | Error in altering table. Column (%s) already exists. | | ERR-02095 | ERR_QP_TRUNCATE_NON_LOG_TABLE | Error in truncating table. Only the LOG table can be truncated. | | ERR-02096 | ERR_QP_TABLE_TRUNCATE_NO_EXISTS | Error in truncating table. Table %s does not exist. | | ERR-02097 | ERR_QP_TABLE_DROP_COLUMN_LIMIT | Error in altering table. The table must have at least one column. | | ERR-02098 | ERR_QP_NOT_EQUJOIN | Error in joining tables. Only equi-join is allowed. | | ERR-02099 | ERR_QP_JOIN_OR | Error in joining tables. The OR condition for a join predicate is not allowed. | | ERR-02100 | ERR_QP_JOIN_FUNCTION_EXPR | Error in joining tables. The join predicate cannot use functions. | | ERR-02101 | ERR_QP_JOIN_PERMUTATION | Error in joining tables. Cannot join without join predicate. | | ERR-02104 | ERR_QP_COLLECTOR_NO_TEMPLATE_EXISTS | The template file (%s) does not exist. | | ERR-02105 | ERR_QP_COLLECTOR_TEMPLATE_FORMAT_INVALID | The template format (%s : %s : %d) is invalid. | | ERR-02109 | ERR_QP_JOIN_LOG_LOG | Cannot join two or more LOG tables. | | ERR-02110 | ERR_QP_KEYWD_MIN_LENGTH | Search condition argument is too short. It needs more than (%d) characters. | | ERR-02111 | ERR_QP_NO_SUCH_COMMAND | Invalid option. | | ERR-02112 | ERR_QP_NO_DISTINCT_GRBY | Cannot use DISTINCT with GROUP BY clause. | | ERR-02113 | ERR_QP_NO_DISTINCT_AGGR | Cannot use DISTINCT with aggregation function. | | ERR-02114 | ERR_QP_FUNCTION_DISTINCT | DISTINCT clause is not allowed here. | | ERR-02115 | ERR_QP_INVALID_COL_NAME | Internal column cannot be modified. | | ERR-02116 | ERR_QP_SEARCH_FILTER | Search predicate must use an index. | | ERR-02117 | ERR_QP_TABLE_NAME_INVALID | DDL on table (%s) is forbidden. | | ERR-02118 | ERR_QP_TABLE_LOCK_ALREADY_INIT | Lock object was already initialized. (Do not use select and append simultaneously in single session.) | | ERR-02119 | ERR_QP_NOT_IMPLEMENTED | This functionality has not been implemented. | | ERR-02120 | ERR_QP_SESSION_ID_INVALID | Invalid session ID (%s). | | ERR-02121 | ERR_QP_SESSION_PRIV_OF_KILL | No privileges to kill the session. | | ERR-02122 | ERR_QP_SESSION_PRIV_OF_CANCEL | No privileges to cancel the session. | | ERR-02123 | ERR_QP_NOT_EXIST_TABLE_ID_META | Table id (%lld) does not exist in meta database. | | ERR-02124 | ERR_QP_NOT_EXIST_COLUMN_ID_META | Column id (%llu) does not exist in table (%llu). | | ERR-02125 | ERR_QP_VARCHAR_TO_DATE_HEURISTIC | Error in converting string (%s) to datetime with heuristic method. Check the default date string format in this session. | | ERR-02126 | ERR_QP_NO_ORDERBY_SUBQ | ORDER BY clause is not allowed in a subquery | | ERR-02127 | ERR_QP_ORDERBY_TERMS | Only integer constants must be used for ORDER BY column position. | | ERR-02128 | ERR_QP_ORDERBY_OOR | ORDER BY column position %d is out of range - should be between 1 and %d. | | ERR-02129 | ERR_QP_GRBY_INT | GROUP BY terms must be integer constants | | ERR-02130 | ERR_QP_NO_GRBY_HAVING | A GROUP BY clause is required before HAVING | | ERR-02131 | ERR_QP_SUBQ_NOT_SINGLE | Single row error. Single-row subquery returns more than one row. | | ERR-02132 | ERR_QP_SUBQ_NOT_ALLOWED | Cannot use subquery on HAVING, ORDER BY and GROUP BY clauses. | | ERR-02133 | ERR_QP_INVALID_SUBQ | Invalid subquery. | | ERR-02134 | ERR_QP_REGEX_MAX_COUNT | Too many REGEXP in WHERE clause. No more than %d REGEXP in WHERE clause. | | ERR-02135 | ERR_QP_WHERE_TYPE | WHERE clause has to return a boolean result. | | ERR-02136 | ERR_QP_TBS_INVALID_TYPE | Invalid tablespace type. | | ERR-02137 | ERR_QP_TBS_TOO_MANY_DISKS | There are too many disks<%ud> for tablespace %s. | | ERR-02138 | ERR_QP_TBS_DISK_INVALID_PARALLEL_IO_VALUE | The PARALLEL_IO value<%d> for the disk<%s> must be higher than <%d>. | | ERR-02139 | ERR_QP_NO_MINMAX_ON_VARCHAR | MINMAX CACHE is not allowed for VARCHAR column(%s). | | ERR-02140 | ERR_QP_TABLE_NOT_SUPPORT_TABLESPACE | This type of tables do not support the tablespace functionality. | | ERR-02141 | ERR_QP_TYPE_COMPARE_NOT_APPLICABLE | Type comparison error. | | ERR-02142 | ERR_QP_NO_AGGR_LOB | Cannot use lob type in the GROUP BY clause. | | ERR-02143 | ERR_QP_NO_ORDER_LOB | Cannot use lob type in the ORDER BY clause. | | ERR-02144 | ERR_QP_OUTERJOIN_LIMIT | Outerjoin permits only 2 tables. | | ERR-02145 | ERR_QP_NOT_NUMBER_STRING | The string cannot be converted to number value.(%s) | | ERR-02146 | ERR_QP_TS_JOIN_LIMIT | Cannot join tables with timeseries function. | | ERR-02147 | ERR_QP_TS_VIEW_LIMIT | Cannot use inline view with timeseries function. | | ERR-02148 | ERR_QP_IPV6_FORMAT | Invalid IPv6 address format.(%s) | | ERR-02149 | ERR_QP_CONTAINS_TYPE_INVALID | Error in executing CONTAINS. Cannot convert from type(%d) to type(%d). | | ERR-02150 | ERR_QP_IP_NETWORK_TYPE_CLASS_MISMATCHED | Network type error. Network Mask length does not match with the column's length.(mask=%s, column=%s) | | ERR-02151 | ERR_QP_NO_MORE_DISK_FOR_EVALUATION | Error in adding disk to tablespace. You cannot use multiple disks for tablespace without valid license. | | ERR-02152 | ERR_QP_PARTITION_PROPERTY_NOT_COMPLETE | Error in setting column property. You should specify a positive value of column property PARTITION_PAGE_COUNT as well as PAGE_VALUE_COUNT. | | ERR-02153 | ERR_QP_UNKNOWN_COLUMN_PROPERTY | Invalid column property name (%s). Specify a valid property name. | | ERR-02154 | ERR_QP_SET_OP_NO_SELECT | Select set operator parsing error. | | ERR-02155 | ERR_QP_UNSURPPORTED_SET_OP | Only UNION ALL set operator is supported. | | ERR-02156 | ERR_QP_SET_OP_TARGET_MISMATCH | Set operator column types do not match at column (%d). | | ERR-02157 | ERR_QP_VALIDATE_INTERNAL | Internal error on validating query | | ERR-02158 | ERR_QP_INVALID_TYPE | Error in evaluating data type. You must specify a valid data type. | | ERR-02159 | ERR_QP_INVALID_BACKUP_RANGE | 'FROM DATETIME' must be earlier than 'TO DATETIME'. | | ERR-02160 | ERR_QP_UNMOUNT_NOT_MOUNTED_TABLE | Error in doing unmount table(%s). You can unmount only mounted tables. | | ERR-02161 | ERR_QP_NO_DDL_ON_MOUTE_MODE | Error in executing DDL. You cannot execute DDL with mounted DB. (*NOT USED*) | | ERR-02162 | ERR_QP_NO_UNMOUTE_DB | Error in doing unmount DB. You cannot umount database which is not mounted. | | ERR-02163 | ERR_QP_WRONG_RESTORE_PATH | Invalid directory path (%s). You should specify a valid path. | | ERR-02164 | ERR_QP_INVALID_ALTER_INDEX_PROPETY | Invalid index property. Property (%s) for index cannot be altered. | | ERR-02165 | ERR_QP_FUNCTION_POS | Function (%s) is not allowed here. | | ERR-02166 | ERR_QP_FUNCTION_ORDER_BY | Cannot use ORDER BY clause with aggregation function. | | ERR-02167 | ERR_QP_FUNCTION_GROUP_CONCAT_WRONG_SEPARATOR | GROUP_CONCAT function error. Separator should be a string constant. | | ERR-02168 | ERR_QP_OPERATOR_ARG_ERROR | Operator argument count or type does not match. | | ERR-02169 | ERR_QP_WRONG_COLUMN_PROPERTY_VALUE | Invalid column property value: (%s) | | ERR-02170 | ERR_QP_NO_ALIAS_IN_TABLE_INLINE_VIEW | Every specified table or inline view in FROM clause must have its own alias. | | ERR-02171 | ERR_QP_PRIMARY_KEY_DUPLICATE_PK_DECL | VOLATILE / LOOKUP / TRANSACTION table cannot have more than one primary key. | | ERR-02172 | ERR_QP_PRIMARY_KEY_INVALID_TABLE | Primary key is allowed only for VOLATILE / LOOKUP / TRANSACTION table. | | ERR-02173 | ERR_QP_VOLATILE_TABLE_INVALID_TYPE | Cannot create columns with data type (%s) in VOLATILE / LOOKUP table. | | ERR-02174 | ERR_QP_INDEX_TARGET_COLUMN_DUPLICATE | The index already exists in the column(%s). | | ERR-02175 | ERR_QP_VTABLE_UPDATE_INVALID_FORM | SET clause must be written as a list of 'column = value' expression. | | ERR-02176 | ERR_QP_VTABLE_UPDATE_TO_PRIMARY_KEY | Cannot update primary key column in SET clause. | | ERR-02177 | ERR_QP_VTABLE_UPDATE_NOT_IN_VOLATILE | ON DUPLICATE UPDATE clause is allowed only in LOOKUP / VOLATILE / TRANSACTION table. | | ERR-02178 | ERR_QP_TABLE_UPDATE_NO_COLUMN | Error in updating table. Column name (%s) does not exist in this table. | | ERR-02179 | ERR_QP_VTABLE_INSERT_WITHOUT_PRIMARY_KEY_VAL | INSERT on a %s table without primary key value cannot be proceeded. | | ERR-02180 | ERR_QP_VTABLE_UPDATE_ON_NO_PRIMARY_KEY | Primary key is mandatory for UPDATE. | | ERR-02181 | ERR_QP_INDEX_WITH_PRIMARY_KEY_PREFIX | Invalid index name starting with (%s) which is the same as primary key index. | | ERR-02182 | ERR_QP_PRIMARY_KEY_INDEX_DROP | You cannot drop the primary key index (%s). | | ERR-02183 | ERR_QP_APPEND_TO_VTABLE_UNSUPPORTED | Append mode for table (%s) is not supported. | | ERR-02184 | ERR_QP_PROPERTY_ON_INVALID_TABLE_TYPE | Specified property value is invalid in %s table. | | ERR-02185 | ERR_QP_MOUNT_DB_DUPLICATED | Invalid database name. This database name is already used for mount. | | ERR-02186 | ERR_QP_MOUNT_DB_INVALID | Invalid database name. | | ERR-02187 | ERR_QP_UNMOUNT_TABLE_IN_ACCESS | Error in unmounting database. Some tables in mounted database are accessed by other transactions | | ERR-02188 | ERR_QP_MOUNT_DB_NOT_FOUND | The database is not mounted. | | ERR-02189 | ERR_QP_DELETE_WHERE_INVALID_TABLE | Error in deleting rows. Only rows in VOLATILE / LOOKUP table can be deleted. | | ERR-02190 | ERR_QP_UPDATE_DELETE_WHERE_INVALID_CONDITION | Invalid UPDATE/DELETE condition. Specify it as (primary key column) = (value) | | ERR-02191 | ERR_QP_DELETE_WHERE_UNSUPPORTED | WHERE clause in DELETE statement is not supported yet. | | ERR-02192 | ERR_QP_KEYWORD_INDEX_TYPE | Index type for keyword index only supports keyword bitmap or keyword LSM. | | ERR-02195 | ERR_QP_BUFFER_OVERFLOW | Buffer size insufficient. | | ERR-02196 | ERR_QP_NO_FILE_TO_LOAD | Error in loading data. File (%s) does not exist. | | ERR-02197 | ERR_QP_LOAD_TABLE_ALREADY_EXISTS | Error in loading data with automatic mode. The table (%s) already exists. | | ERR-02198 | ERR_QP_LOAD_TABLE_NON_EXISTS | Error loading data. Table (%s) does not exist. | | ERR-02199 | ERR_QP_LOAD_TABLE_PARSING_ERROR | CSV parsing error on line %d: [%s]. | | ERR-02200 | ERR_QP_LOAD_TABLE_DELIMITOR_ERROR | [%s] is not a valid string terminator or enclosure. | | ERR-02201 | ERR_QP_LOAD_TABLE_UNKNOWN_AUTOMODE | The automatic loading mode is invalid. | | ERR-02202 | ERR_QP_LOAD_TABLE_HEADER_DETECT_ERROR | Automatic column detection failed because the data is empty or the headers are invalid. | | ERR-02203 | ERR_QP_LOAD_TABLE_UNKNOWN_ENCODINGMODE | Invalid encoding. | | ERR-02204 | ERR_QP_LOAD_TABLE_CHAR_CONVERSION_ERROR | Failed to convert %s to UTF8. | | ERR-02205 | ERR_QP_NO_SUPPORT_DOUBLE_MOD | A modulo operator can be applied only for integer types. | | ERR-02206 | ERR_QP_NO_SUPPORT_TBS_NON_AUTO | Tablespace name cannot be specified in non automode | | ERR-02207 | ERR_QP_SAVE_FILE_ALREADY_EXISTS | Error in saving table into file (%s). File already exists. | | ERR-02208 | ERR_QP_EXPR_TYPE | Expression argument type does not match. | | ERR-02221 | ERR_QP_NO_MANAGER_NAME_SETTED | Manager name is not specified. | | ERR-02222 | ERR_QP_RECEIVE_DIFF_PROTOCOL | Error in read protocol. Send %s protocol, but received %d protocol. | | ERR-02223 | ERR_QP_COLLECTORMANAGER_CONNECT | Unable to establish connection with collectormanager (%s). | | ERR-02224 | ERR_QP_NO_COLLECTOR_NAME_SETTED | Manager name is not specified. | | ERR-02225 | ERR_QP_SET_COLUMN_UNIT_ERROR | Invalid set column unit. | | ERR-02226 | ERR_QP_INVALID_CHARACTER | Invalid character ('%c'). | | ERR-02228 | ERR_QP_UNSUPPORT_PROCEDURE | Invalid procedure (%s). | | ERR-02229 | ERR_QP_INVALID_ARG_VALUE | Invalid argument value for function (%s). | | ERR-02230 | ERR_QP_PROCEDURE_WRONG_NUMBER_OF_ARGUMENTS | Wrong number of arguments in call to '%s'. | | ERR-02231 | ERR_QP_STRCPY_ERROR | strcpy function error (%d). | | ERR-02232 | ERR_QP_CALC_TYPE | Calculation argument type (%s), (%s) error. | | ERR-02233 | ERR_QP_INSERT_VALUE_LOCATION | Error occurred at column (%u): (%s) | | ERR-02234 | ERR_QP_SET_OP_COUNT | Set operator column counts do not match (%d and %d). | | ERR-02235 | ERR_QP_SERIES_BY | SERIES BY clause is not allowed here. | | ERR-02236 | ERR_QP_TOO_MANY_TABLES_IN_JOIN | For a table list in FROM clause, The number of tables should be less than 32. | | ERR-02237 | ERR_QP_INDEX_NOT_CREATED_ON_TABLE | The index <%s> is not an index for the table <%s>. | | ERR-02238 | ERR_QP_NO_JOIN_TYPE | This type of join is not allowed. | | ERR-02239 | ERR_QP_INVALID_USE_AGGR_FUNC | Invalid use of aggregation function. | | ERR-02240 | ERR_QP_INVALID_COLUMN_TYPE_FOR_FETCH | Cannot fetch column with type (%s). | | ERR-02241 | ERR_QP_UNSUPPORTED_JOIN_TABLES | Join between LOG table and fixed table is not supported in Cluster Edition. | | ERR-02242 | ERR_QP_EQUIJOIN_WITH_LOGTABLE_JOIN | Only equality predicates are supported when joining LOG tables in Cluster Edition. | | ERR-02243 | ERR_QP_UNSUPPORTED_ROW_BASED_DELETE | DELETE statement with the number of rows is not supported in Cluster Edition. | | ERR-02246 | ERR_QP_IDENTIFIER_TOO_LONG | Identifier %.*s is too long. | | ERR-02247 | ERR_QP_DATETIME_NOT_PROPER | DATETIME earlier than 1970-01-01 00:00:00 (UTC) is not valid. | | ERR-02248 | ERR_QP_INSUFFICIENT_COLUMN_DEF | Insufficient column definitions. | | ERR-02249 | ERR_QP_TABLE_DELETE_INVALID_COND | Invalid DELETE condition. | | ERR-02250 | ERR_QP_TAGDATA_COMPONENT_DDL_BLOCKED | You cannot execute DDL on compoment table/index of TAGDATA table explictly. | | ERR-02251 | ERR_QP_TAGDATA_DUPLICATE_FLAG | You cannot define columns with duplicate flag (%s) in TAGDATA table. | | ERR-02252 | ERR_QP_TAGDATA_INVALID_TYPE_FOR_FLAG | Invalid column type (%s) for flag (%s) in TAGDATA table. | | ERR-02253 | ERR_QP_TAGDATA_INSUFFICIENT_MANDATORY | Mandatory column definition (PRIMARY KEY / BASE TIME) is missing. | | ERR-02254 | ERR_QP_INVALID_TAGDATA_FLAG_ON_OTHER_TABLE | Column flag (%s) is only allowed for TAG table. | | ERR-02255 | ERR_QP_TAGDATA_INSERT_META_NO_PK | Primary key of TAGDATA table is not defined in metadata. | | ERR-02256 | ERR_QP_TAGDATA_INVALID_META_COLUMN_CLAUSE | Metadata column definition is allowed only in TAGDATA table. | | ERR-02257 | ERR_QP_TAGDATA_INSERT_META_INVALID_TYPE | Metadata insertion is allowed only in TAGDATA table. | | ERR-02258 | ERR_QP_TAGDATA_ALREADY_INSERTED | Metadata key (%.*s) for the TAG table has already been inserted. | | ERR-02259 | ERR_QP_TAGDATA_NOT_FOUND | Metadata of TAGDATA table is not found. (Key = %s) | | ERR-02260 | ERR_QP_TAGDATA_ALLOC_FAILURE | Failed to allocate new metadata of TAGDATA table (Current Size=%llu). | | ERR-02261 | ERR_QP_NO_TAGDATA_METADATA_INSERT_UPDATE | You cannot insert metadata into TAGDATA table with ON DUPLICATE KEY UPDATE clause. | | ERR-02262 | ERR_QP_TAGDATA_DIRECT_DML_BLOCKED | Direct DML on component tables of TAGDATA table is not allowed. | | ERR-02263 | ERR_QP_TAGDATA_MORE_TAGDATA_TABLE | You can create only one TAGDATA table. | | ERR-02264 | ERR_QP_TAGDATA_SCAN_OTHER_COLUMN_IN_ROLLUP | Cannot read a column (%s) in ROLLUP query because it is not a ROLLUP column. | | ERR-02265 | ERR_QP_TAGDATA_SCAN_WITHOUT_KEY_CONDITION | Reading TAGDATA table without primary key condition is not allowed. | | ERR-02266 | ERR_QP_TAGDATA_DELETE_RAW_CONDITION | You cannot delete raw data of TAGDATA table with WHERE condition. | | ERR-02267 | ERR_QP_TAGDATA_UNSUPPORTED_KEY_PREDICATE | Primary key in TAGDATA table should be compared by '=' or 'IN' operation. | | ERR-02268 | ERR_QP_TAGDATA_COMPARE_KEY_ONLY_CONSTANT | Primary key in TAGDATA table should be compared with constant value. | | ERR-02269 | ERR_QP_TAGDATA_OUTERJOIN | Outerjoin on TAGDATA table is not allowed. | | ERR-02270 | ERR_QP_TAGDATA_NAME_VIOLATION | TAGDATA table's name should be 'TAG'. | | ERR-02271 | ERR_QP_TAGDATA_NOT_CONSTANT_PK_VALUE | You must insert key value of TAGDATA table as constant. | | ERR-02272 | ERR_QP_NOT_EXIST_INDEX_ID_META | Index id (%llu) does not exist in meta database. | | ERR-02273 | ERR_QP_TAGDATA_USER_NO_PRIV_DDL | The user does not have privileges on TAGDATA DDL. | | ERR-02274 | ERR_QP_TAGDATA_FREE_FAILURE | Failed to free new metadata of TAGDATA table. | | ERR-02275 | ERR_QP_TAGDATA_INSERT_SELECT_IN_EE | The INSERT SELECT statement to the TAGDATA table is not allowed in enterprise edition. | | ERR-02276 | ERR_QP_TAGDATA_COMPONENT_EXISTS | Component table (%s) of TAGDATA table already exists. | | ERR-02277 | ERR_QP_TAGDATA_COMPONENT_NAME_RESERVED | Table or index name that starts with '_TAG' is reserved. | | ERR-02278 | ERR_QP_UPDATE_INVALID_TABLE_TYPE | UPDATE statement is not allowed for %s. | | ERR-02279 | ERR_QP_TAGDATA_INVALID_PRIMARY_NAME | Invalid tag name insertion to TAGDATA table (name = '%s'). | | ERR-02280 | ERR_QP_TAGDATA_INVALID_BIND_TAGNAME | Invalid tag name insertion due to wrong bind variable. | | ERR-02281 | ERR_QP_DURATION_NOT_APPLICABLE | DURATION clause is not applicable on %s. | | ERR-02282 | ERR_QP_DELETE_ALREADY_DOING | The DELETE statement for table '%s' is already been executed. | | ERR-02283 | ERR_QP_TAGDATA_IN_SUBQUERY_NOT_ALLOWED | IN subquery on TAGDATA table is not allowed. | | ERR-02284 | ERR_QP_INTERNAL_NULL_EXIST | Internal NULL value exists in the condition expression. | | ERR-02285 | ERR_QP_INTERNAL_ERROR | Internal error: %s. | | ERR-02286 | ERR_QP_KV_TABLE_MEMORY_ALLOC | Memory allocation failed while creating TAGDATA table. You may need to decrease TAG_DATA_PART_SIZE in machbase.conf. | | ERR-02287 | ERR_QP_TAGDATA_NAME_TRUNCATED | TAGDATA name value (%s) is too long. | | ERR-02288 | ERR_QP_AGGR_EXPECTED | Aggregate function is expected at (%.*s). | | ERR-02289 | ERR_QP_NON_CONST | Non-constant expression is not allowed for PIVOT values. | | ERR-02290 | ERR_QP_CANNOT_ALTER | %s cannot be altered. | | ERR-02291 | ERR_QP_INVALID_TABLE_NAME_TAG | Table name 'TAG' must be used for TAGDATA table. | | ERR-02292 | ERR_QP_TAGDATA_INVALID_CONSTRAINT_ORDER | The order of columns in TAGDATA table must be (PRIMARY, BASE TIME, SUMMARIZED, other columns, .. ). | | ERR-02293 | ERR_QP_NO_BIND_PARAM_COLUMN | Column meta for bind param[%d] is not available. | | ERR-02294 | ERR_QP_TAGDATA_JOIN | Joining more than one TAGDATA table is not supported. | | ERR-02295 | ERR_QP_INVALID_ORDINAL_NUMBER | Invalid ordinal number ID_COLUMN (%lld) and TIME_COLUMN (%lld). | | ERR-02296 | ERR_QP_INTERPOLATION_ONE_BETWEEN | Interpolation requires only one BETWEEN expression. | | ERR-02297 | ERR_QP_BETWEEN_HAS_INVALID_EXPR | BETWEEN has invalid expression (%s). | | ERR-02298 | ERR_QP_NOT_FACTOR_OF_INTERPOLATION_INTERVAL | FREQUENCE must be a factor of INTERPOLATION_INTERVAL (%lld). | | ERR-02299 | ERR_QP_ONLY_BETWEEN_SUPPORTED | Only BETWEEN condition is supported. | | ERR-02300 | ERR_QP_INSUFFICIENT_COLUMN_FOR_INTERPOLATION | Interpolation column is missing. (%s) | | ERR-02301 | ERR_QP_INSUFFICIENT_PROPERTIES_FOR_INTERPOLATION | Some properties are missing for interpolation. | | ERR-02302 | ERR_QP_INVALID_INTERVAL_INTERPOLATION_PROPERTY | Invalid interpolation interval property: %lld. | | ERR-02303 | ERR_QP_INVALID_ROLLUP_UNIT | You must use higher ROLLUP unit. | | ERR-02304 | ERR_QP_INTERPOLATION_VIEW_LIMIT | Interpolation is not applicable on (%s). | | ERR-02305 | ERR_QP_INTERPOLATION_ONE_TARGET | JOIN is not applicable for interpolation. | | ERR-02306 | ERR_QP_CHEKPOINT_INVALID_SIZE | Interpolation interval value(%lld) should be less than checkpoint interval value(%lld). | | ERR-02307 | ERR_QP_ROLLUPUNIT_INTERVALVALUE | Interpolation interval value(%lld) should be less than ROLLUP unit (%s). | | ERR-02308 | ERR_QP_ROLLUPUNIT_CHECKPOINTVALUE | Checkpoint interval value(%lld) should be less than ROLLUP unit (%s). | | ERR-02309 | ERR_QP_INVALID_INTERPOLATION_DIVIDE | Checkpoint interval value(%lld) should be divide by interpolation value(%lld). | | ERR-02310 | ERR_QP_INVALID_CHECKPOINT_INTERPOLATION_PROPERTY | Invalid interpolation checkpoint property: %lld. | | ERR-02311 | ERR_QP_INVALID_KEYWORD_INTERPOLATION | %s cannot be used in interpolation query. | | ERR-02312 | ERR_QP_NOT_INTERPOLATION_TABLE | Rollup delete can only be done on the Interpolation Tag table. | | ERR-02313 | ERR_QP_ROLLUP_REBUILD_RANGE_ERROR | Unable to execute ROLLUP DELETE with the given range. | | ERR-02314 | ERR_QP_TAG_UNSUPPORT_DURATION_BACKUP | Regular duration backup does not support a backup of the TAG table (Try incremental backup which permits the action on the TAG table). | | ERR-02315 | ERR_QP_FOG_SNAPSHOT_NOT_SUPPORTED | Snapshot is not supported. | | ERR-02316 | ERR_QP_INVALID_EXPR_IN_DURATION | Invalid expression in DURATION clause: %.*s | | ERR-02317 | ERR_QP_FUNCTION_EXECUTION | Function execution failed: %s | | ERR-02318 | ERR_QP_LOOKUP_NODE_CONNECT_FAIL | Cannot connect to the lookup node. | | ERR-02319 | ERR_QP_LOOKUP_NODE_ERROR | Error on Lookup Node | | ERR-02320 | ERR_QP_LOOKUP_NODE_PENDING | Lookup Node is not ready | | ERR-02321 | ERR_QP_LOOKUP_NODE_NIL | No data was found in the lookup node. | | ERR-02322 | ERR_QP_LOOKUP_TABLE_MISSING_PRIMARY_KEY | Mandatory column definition (PRIMARY KEY) is missing. | | ERR-02323 | ERR_QP_EXEC_FUNCTION_NOT_SUPPORTED_TABLE_TYPE | EXEC %s is not supported for %s table type. | | ERR-02324 | ERR_QP_USED_TAG_ID_DATA | Cannot delete tagmeta. there exist data with deleted_tag key. | | ERR-02325 | ERR_QP_INTEGER_OVERFLOW | Integer %s type overflow. | | ERR-02326 | ERR_QP_EDGE_BACKUP_MOUNT_NOT_SUPPORTED | Backup/Mount is not supported. | | ERR-02327 | ERR_QP_PIVOT_IN_ROLLUP_NOT_SUPPORTED | Pivot is not supported in rollup query. | | ERR-02328 | ERR_QP_TAGMETA_INSERT_COUNT_EXCEEDED | Cannot insert a new tag since the number of tags has exceeded MAX_TAG_COUNT(%lld). | | ERR-02329 | ERR_QP_TAGMETA_INSERT_COUNT_EXCEEDED_LIMIT | Cannot insert a new tag since the number of tags has exceeded TAG_COUNT_LIMIT(%lld). | | ERR-02330 | ERR_QP_KV_INSUFFICIENT_MANDATORY | Mandatory column definition (ULONG / DATETIME) is missing. | | ERR-02331 | ERR_QP_RANGE_EXPR | RANGE expression is not applicable on the table (%s). | | ERR-02332 | ERR_QP_UNABLE_CREATE_INDEX_ON_COLUMN | Unable to create an index on the column (%s). | | ERR-02333 | ERR_QP_KV_TABLE_PREDICATE_MAX_OVER | Column (%s) cannot exceed %d. | | ERR-02334 | ERR_QP_TAG_INDEX_NOT_YET_SUPPORTED | Tag Index is not yet supported. | | ERR-02335 | ERR_QP_FAILED_TO_DELETE_ALL | Failed to delete all on this table. It is recommended to use EXEC TABLE_REFRESH(%s). | | ERR-02336 | ERR_QP_CASCADE_ONLY_TAG_TABLE | CASCADE option is not applicable on %s. | | ERR-02337 | ERR_QP_TAGMETA_DUPLICATE_FLAG | Unable to define more than one column attribute (%s). | | ERR-02339 | ERR_QP_TAGMETA_DIFFERENT_SUMMARY_TYPE | The type of %s column (%s) is different from that of VALUE column (%s). | | ERR-02340 | ERR_QP_TAGMETA_NOT_FOUND_SUMMARY_VALUE | SUMMARIZED column does not exist for %s. | | ERR-02341 | ERR_QP_SUMMARY_GREATER_THAN_USL | SUMMARIZED value is greater than UPPER LIMIT. | | ERR-02342 | ERR_QP_SUMMARY_LESS_THAN_LSL | SUMMARIZED value is less than LOWER LIMIT. | | ERR-02343 | ERR_QP_LSL_GREATER_THAN_USL | LOWER LIMIT must not be greater than UPPER LIMIT. | | ERR-02344 | ERR_QP_NOT_NUMERIC_TYPE | Not numeric type. (%s) | | ERR-02345 | ERR_QP_INVALID_TAGMETA_FLAG_ON_OTHER_TABLE | Column flag (%s) is only allowed for TAGMETA table. | | ERR-02346 | ERR_QP_DEFAULT_ONLY_FOR_TYPE_DATETIME | Column type (%s) is not allowed for default value. | | ERR-02347 | ERR_QP_DEFAULT_ONLY_FOR_FLAG_SYSDATE | SYSDATE is only allowed for default value. | | ERR-02348 | ERR_QP_ALTER_SET_PROP_NOT_SUPPORT_ON_CLUSTER | Alter table set %s not support on cluster. | | ERR-02349 | ERR_QP_BIND_VARIABLE_NOT_SUPPORTED_NEW_TAG | Bind variable is not supported for new tag. | | ERR-02350 | ERR_QP_WINDOW_FUNCTION_OVER_EXISTS | The function (%s) requires OVER clause. | | ERR-02351 | ERR_QP_NO_WINDOW_CONTEXT | Window function is allowed only in SELECT list. | | ERR-02352 | ERR_QP_FUNCTION_OVER | OVER clause is not applicable on (%s). | | ERR-02353 | ERR_QP_OVER_INVALID_TYPE | Invalid data type (%s) in OVER clause. | | ERR-02354 | ERR_QP_OVER_CONSTANT | Constant is not allowed in OVER clause. | | ERR-02355 | ERR_QP_TYPE_UNSUPPORTED | Type (%s) is not supported. | | ERR-02356 | ERR_QP_FIRST_DAY_OF_THE_MONTH | Origin must be the first day of the month. | | ERR-02357 | ERR_QP_JOIN_NOT_APPLICABLE | JOIN is not applicable on the table (%s). | | ERR-02358 | ERR_QP_INVALID_METADATA_ALTER_TABLE | When altering a table, the METADATA keyword is only applied to the tag table. | | ERR-02359 | ERR_QP_TAG_TABLE_ONLY_META_CHANGE | Tag table (%s) can only be modified in the metadata area. | | ERR-02360 | ERR_QP_WINDOW_FUNCTION_NOT_ALLOWED | Window function is not allowed with %s. | | ERR-02361 | ERR_QP_TABLE_STRUCTURE_MODIFIED | Table (%d) structure was modified. | | ERR-02362 | ERR_QP_STATEMENT_NOT_SUPPORTED | This statement is not supported. | | ERR-02600 | ERR_QP_WRONG_SEQUENCE_TABLE_TYPE | SEQUENCE property is not applicable in the table. | | ERR-02601 | ERR_QP_INVALID_FUNCTION_IN_SEQUENCE_COLUMN | Invalid function in a SEQUENCE column. NEXTVAL must be used. | | ERR-02602 | ERR_QP_INVALID_NEXTVAL_FUNCTION_QUERY | NEXTVAL is applicable only in INSERT statement. | | ERR-02603 | ERR_QP_INVALID_COLUMN_NEXTVAL | NEXTVAL is applicable only in SEQUENCE columns. | | ERR-02604 | ERR_QP_INVALID_SEQUENCE_COLUMN_DATA_TYPE | Sequence column must be LONG type. | | ERR-02651 | ERR_QP_EXIST_DEPENDENT_ROLLUP_TABLE | Dependent ROLLUP (%s) exists. | | ERR-02652 | ERR_QP_NOT_ROLLUP_TABLE | Not a ROLLUP table. (%s) | | ERR-02653 | ERR_QP_ROLLUP_INTERVAL_GREATER_THAN_SRC_ROLLUP | Rollup interval must be greater than source rollup interval. | | ERR-02654 | ERR_QP_ROLLUP_NOT_FOUND | ROLLUP (%s) is not found. | | ERR-02655 | ERR_QP_ROLLUP_INTERVAL_DIVIDE_REMAINDER_ZERO | Rollup interval source rollup interval Must Divide Zero. | | ERR-02656 | ERR_QP_ROLLUP_INTERVAL_POSITIVE_INTEGER | Rollup interval must positive integer. | | ERR-02657 | ERR_QP_ROLLUP_INTERVAL_SMALLER_THAN_YEAR | Rollup interval must be smaller than year. | | ERR-02658 | ERR_QP_ROLLUP_NOT_ENABLE | ROLLUP is not enabled for %s. | | ERR-02659 | ERR_QP_ROLLUP_MAX_COUNT | Rollup maximum count is 100. | | ERR-02670 | ERR_QP_ROLLUP_SOURCE_USERID | Rollup user ID(%d) is not equal to Source user ID(%d) | | ERR-02671 | ERR_QP_ROLLUP_COLUMN_INVALID_TYPE | Invalid type for ROLLUP column (%s). | | ERR-02672 | ERR_QP_ROLLUP_JSON_PATH_NOT_EXISTS | Json path is not specified on %s. | | ERR-02673 | ERR_QP_ROLLUP_JSON_PATH_EXISTS | Json path is not applicable on %s. | | ERR-02674 | ERR_QP_ROLLUP_NOT_FOUND_COLUMN | ROLLUP query must have a target column. | | ERR-02675 | ERR_QP_CAN_SCAN_ONE_ROLLUP_COLUMN | Cannot use more than one ROLLUP column in a ROLLUP query. | | ERR-02676 | ERR_QP_NOT_TAG_TABLE | Not a TAG table. | | ERR-02677 | ERR_QP_INVALID_ROLLUP_TIME_UNIT | Invalid rollup time unit (%s). | | ERR-02678 | ERR_QP_NEED_SUMMARIZED_COLUMN | WITH ROLLUP requires a SUMMARIZED column. | | ERR-02679 | ERR_QP_AUTO_GENERATE_ROLLUP_FAIL | Failed to create ROLLUP by WITH ROLLUP option. | | ERR-02680 | ERR_QP_PROCESS_ALREADY_START | PROCESS %s (%s) is already started. | | ERR-02681 | ERR_QP_PROCESS_ALREADY_STOP | PROCESS %s (%s) is already stopped. | | ERR-02682 | ERR_QP_ROLLUP_EXT_TYPE_DIFFER | ROLLUP extension type is different. | | ERR-02683 | ERR_QP_TAGDATA_SCAN_OTHER_TIME_COLUMN_IN_ROLLUP | Cannot read a column (%s) in ROLLUP query because it is not a ROLLUP time column. | | ERR-02684 | ERR_QP_NOT_EXIST_DEPENDENT_ROLLUP_TABLE | Dependent ROLLUP table does not exist. | | ERR-02685 | ERR_QP_NO_APPLICABLE_ROLLUP_TABLE | There are no applicable ROLLUP tables. | | ERR-02686 | ERR_QP_RENAME_NO_APPLICABLE_ROLLUP_TABLE | The names of column(%s) associated with ROLLUP cannot be changed. | | ERR-02687 | ERR_QP_ROLLUP_WAKEUP_INTERVAL_SMALLER_THAN_SRC_ROLLUP | Rollup wakeup interval must be same or smaller than rollup interval. | | ERR-02688 | ERR_QP_ROLLUP_WAKEUP_INTERVAL_DIVIDE_REMAINDER_ZERO | Rollup wakeup interval must exactly divide the rollup interval. | | ERR-02689 | ERR_QP_CUSTOM_ROLLUP_FROM_ALIAS_NOT_ALLOWED | Cannot use alias in custom ROLLUP SELECT FROM clause. | | ERR-02690 | ERR_QP_CUSTOM_ROLLUP_OWNER_MISMATCH | Custom ROLLUP source and destination table owners must be same. (source:%s, destination:%s) | | ERR-02691 | ERR_QP_INDEX_TABLE_OWNER_MISMATCH | Index owner and table owner must be same. (index owner:%s, table owner:%s) | | ERR-02692 | ERR_QP_CIRCULAR_VIEW_DEFINITION | Circular view definition is not allowed: (%s). | | ERR-02700 | ERR_QP_DUPLICATE_RETENTION | Policy (%s) already exists. | | ERR-02701 | ERR_QP_NOT_EXISTS_RETENTION | Policy (%s) does not exist. | | ERR-02702 | ERR_QP_EXIST_DEPENDENT_RETENTION_TABLE | Policy (%s) is in use. | | ERR-02703 | ERR_QP_NOT_EXISTS_RETENTIONJOB | Table (%s) has no retention policy. | | ERR-02704 | ERR_QP_DUPLICATE_RETENTIONJOB | Table (%s) already has a retention policy. | | ERR-02705 | ERR_QP_RETENTION_DURATION_RANGE | Retention duration must be longer than 1 day. | | ERR-02706 | ERR_QP_RETENTION_INTERVAL_RANGE | Retention interval must be longer than 1 hour. | | ERR-02707 | ERR_QP_RETENTION_TABLE_TYPE | Retention is not applicable on the table (%s). | | ERR-02708 | ERR_QP_RETENTION_PRIVILEGE | Only SYS user can create or drop RETENTION. | | ERR-02813 | ERR_QP_INVALID_ROLLUP_EXPR | Invalid ROLLUP expression. (Token = %s, Unit = %ld) | | ERR-02814 | ERR_QP_INVALID_ROLLUP_TARGET | Invalid ROLLUP target. BASETIME column of TAGDATA table is the only target. | | ERR-02815 | ERR_QP_DIFFERENT_ROLLUP_EXPR | Different ROLLUP expressions are used in a single SELECT query. | | ERR-02816 | ERR_QP_INVALID_USE_IN_ROLLUP | Only rollup column with aggregate function can be referenced in ROLLUP SELECT query. | | ERR-02817 | ERR_QP_INVALID_ROLLUP_NOT_SELECT | ROLLUP expression must be used in SELECT query. | | ERR-02818 | ERR_QP_UNSUPPORT_ROLLUP_TARGET | Invalid ROLLUP target (%s). | | ERR-02819 | ERR_QP_ROLLUP_RUNNING | ROLLUP thread is running. | | ERR-02820 | ERR_QP_ROLLUP_NOT_RUNNING | ROLLUP thread is not running. | | ERR-02821 | ERR_QP_OPERATION_IN_PROGRESS | Another DDL/DELETE/SNAPSHOT is in progress. | | ERR-02822 | ERR_QP_INVALID_EXPRESSION_IN_ROLLUP_QUERY | Invalid expression in ROLLUP query : %.*s | | ERR-02823 | ERR_QP_ROLLUP_SELECT_FROM | Invalid table in ROLLUP query: %s | | ERR-02824 | ERR_QP_CUSTOM_ROLLUP_FIRST_COLUMN_NOT_TAGNAME | In custom ROLLUP SELECT, first column must be TAG key column (%s). | | ERR-02825 | ERR_QP_INVALID_EXTENDED_COLUMN_ROLLUP_QUERY | Extended column(%s) cannot be used in ROLLUP query. | | ERR-02826 | ERR_QP_CANT_REVOKE | User (%s) can't revoke from table (%s.%s). | | ERR-02827 | ERR_QP_USER_NO_GRANT_PRIV | User does not have grant privileges. | | ERR-02828 | ERR_QP_USER_NO_REVOKE_PRIV | User does not have revoke privileges. | | ERR-02829 | ERR_QP_USER_ONLY_SYS_CAN_DO_CREATE_DROP | Only SYS user can create or drop user. | | ERR-02830 | ERR_QP_USER_NO_PRIV_TABLE_FOR_EACH_CASE | The user does not have (%s) privilege on table(%s.%s). | | ERR-02831 | ERR_QP_USER_NO_GRANT_UPDATE_PRIV_FOR_LOG_TABLE | You can't grant UPDATE privilege on Log Table. | | ERR-02832 | ERR_QP_USER_NO_REVOKE_UPDATE_PRIV_FOR_LOG_TABLE | You can't revoke UPDATE privilege on Log Table. | | ERR-02833 | ERR_QP_USER_SELECT_ONLY_FOR_MOUNT_TABLE | You can only grant SELECT privileges on Mounted database. | | ERR-02834 | ERR_QP_PASSWORD_REUSED | Cannot use new password as previously used. | | ERR-02835 | ERR_QP_USER_NO_PRIV_DATABASE_FOR_EACH_CASE | The user does not have (%s) privilege on database(%s). | | ERR-02837 | ERR_QP_CUSTOM_ROLLUP_NOT_SUPPORTED_IN_CLUSTER | Custom rollup is not supported in cluster edition. | | ERR-02838 | ERR_QP_USER_NO_MOUNT_PRIV | The user(%s) does not have mount privileges. | | ERR-02839 | ERR_QP_DATABASE_NOT_FOUND | Database (%s) does not exist. | | ERR-02840 | ERR_QP_DATABASE_NOT_ACTIVE | Database (%s) is not an active database. | | ERR-02841 | ERR_QP_DATABASE_USE_IN_TRANSACTION | Cannot change the current database while a transaction is active. | | ERR-02842 | ERR_QP_DATABASE_ALREADY_EXISTS | Database (%s) already exists. | | ERR-02843 | ERR_QP_DATABASE_SELF_DROP | Cannot drop current database (%s). | | ERR-02844 | ERR_QP_DATABASE_DEFAULT_DROP | Default database (%s) cannot be dropped. | | ERR-02845 | ERR_QP_DATABASE_READ_ONLY | Database (%s) is read only. | | ERR-02846 | ERR_QP_DATABASE_RESERVED_NAME | Database name (%s) is reserved and cannot be used. | | ERR-02847 | ERR_QP_PREPARED_CATALOG_CHANGED | Prepared statement target database (%s) changed. | ### `ERR-03000`–`ERR-03999` (65) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-03000 | MMP_STMT_OVERFLOWS | Statement ID overflow (Limit = %u, Curr = %u). | | ERR-03001 | MMP_STMT_QUERY_ZERO | Statement query length is zero. | | ERR-03002 | MMT_TASK_POOL_INITIALIZE_ERROR | Task pool initialization error. | | ERR-03003 | MMS_STMT_POOL_INITIALIZE_ERROR | Statement pool initialization error. | | ERR-03004 | MMT_QUEUE_CREATE_ERROR | Queue creation error. | | ERR-03005 | MMS_STMT_ALLOC_ERROR | Statement allocation error. | | ERR-03006 | MMP_META_UNKNOWN_TYPE_ERROR | Unknown meta type error (typecode is %u). Internal error. | | ERR-03007 | MMP_PROTOCOL_BUFFER_INSUFFICIENT | Insufficient protocol buffer size. Increase it. | | ERR-03008 | MMP_PROTOCOL_STATE_INVALID | Invalid protocol state. Check your application again. (Protocol = %s, State = %s) | | ERR-03009 | MMP_EXECUTE_PROTOCOL_DATA_INVALID | Invalid execute protocol data (%s). | | ERR-03010 | MMS_FETCH_PROTOCOL_INSUFFICIENT | Error in fetch protocol: not enough buffer size to execute it. Increase the size. | | ERR-03011 | MMP_SEND_ERROR | Send error. | | ERR-03012 | MMP_MEMORY_ALLOC_ERROR | Memory allocation error. | | ERR-03013 | MMP_STMT_APPEND_TABLE_ZERO | Invalid table name for append table. Table name is omitted. | | ERR-03014 | MMP_APPEND_PROTOCOL_DATA_INVALID | Invalid append protocol data (%s). | | ERR-03015 | MMP_STMT_APPEND_NO_ENDIAN | Endian is not specified for append. Check endian information. | | ERR-03016 | MMP_STMT_APPEND_MAX_COLUMN | Too many columns are specified for append. Cannot append more than %d columns | | ERR-03017 | MMP_STMT_APPEND_MAX_RECORD_SIZE | Too large record size for append. Cannot append more than %d bytes per record. | | ERR-03018 | MMP_STMT_APPEND_MAX_BLOCK_SIZE | The specified maximum block size (%d) was exceeded. Check the application's append data structure. | | ERR-03019 | MMP_STMT_EXPLAIN_PLAN_ERROR | Explain plan error. Use it for SELECT statement only. | | ERR-03020 | MMP_STMT_EXPLAIN_ONLY_DIRECT_EXECUTE | Explain plan is not allowed in prepared mode. | | ERR-03021 | MMP_CONNECT_VERSION_MISMATCHED | Protocol versions do not match: server (%d.%d.%d), client (%d.%d.%d). | | ERR-03022 | MMP_OS_GET_HANDLE_LIMIT_ERROR | Failed to get handle limit from the system. | | ERR-03023 | MMP_OS_CHECK_HANDLE_LIMIT_ERROR | Handle limit(%d) from the system is less than that of property(%d). Tune system handle limit or decrease the property 'HANDLE_LIMIT' | | ERR-03024 | ERR_MM_SESSION_ID_NOT_FOUND | Invalid session ID (%llu). | | ERR-03025 | ERR_MM_SESSION_SELF_OP_ERROR | Not enough privileges to manipulate the session. (%llu) | | ERR-03026 | ERR_MM_SESSION_DIFF_USER_CANCEL | You should log in with the same user name in the target session. Now (%d) Target(%d) | | ERR-03027 | ERR_MM_SESSION_CANCELLED | This statement has been canceled. | | ERR-03028 | ERR_MM_NO_SESSION_PROPETY | Invalid session property name. Name (%s) does not exist. | | ERR-03029 | ERR_MM_SESSION_PROPETY_CONVERT | Error in converting session property (%s). Cannot convert string (%s) to integer. | | ERR-03030 | ERR_MM_SESSION_PROPETY_VALUE_RANGE | Invalid session property value. Check the session value (%s) | | ERR-03031 | ERR_MM_PROTOCOL_BROKEN | Protocol error. | | ERR-03032 | ERR_MM_LICENSE_NO_META | Error in getting license meta. Check DB image and binary. | | ERR-03033 | ERR_MM_LICENSE_OPEN_META | Error in opening meta. | | ERR-03034 | ERR_MM_LICENSE_EXEC_META | Error in executing meta. | | ERR-03035 | ERR_MM_LICENSE_CLOSE_META | Error in closing meta. | | ERR-03036 | ERR_MM_LICENSE_EXPIRED | The license is expired(%s). | | ERR-03037 | ERR_MM_LICENSE_INVALID | The license is invalid or the license file does not exist(%s). | | ERR-03038 | ERR_MM_LICENSE_VIOLATION | License violation detected (%s). contact sales@machbase.com | | ERR-03039 | ERR_MM_SESSION_COUNT_EXCEED | Session count exceeded the maximum (%llu). | | ERR-03040 | ERR_MM_SHUTDOWN_FAIL | Unable to shutdown since the server is busy. | | ERR-03041 | ERR_MM_APPEND_BATCH | AppendBatch error: %s. | | ERR-03042 | ERR_MM_RECOVERY_BEGUN | Recovery in progress. | | ERR-03043 | ERR_MM_EXECARRAY_NOT_FOR_SELECT | Array Execute is not applicable for SELECT query. | | ERR-03044 | MMP_CONNECT_WRONG_TIMEZONE | Invalid TIMEZONE string: %s. | | ERR-03045 | ERR_MM_INVALID_CONTEXT | Invalid context at %s. | | ERR-03046 | ERR_MM_CM_ERROR | Communication module error (rc=%d): [%s]. | | ERR-03047 | ERR_MM_FUNCTION | Failed to call function %s (rc=%d) | | ERR-03048 | ERR_MM_PREPARED_STMT_USER_CHANGED | Prepared statement cannot be used after CONNECT USER. | | ERR-03200 | ERR_MM_SERVER_NOT_RUNNING | Server is not running. | | ERR-03201 | ERR_MM_INVALID_STMT_STATE | Invalid statement state: (%d) | | ERR-03202 | ERR_MM_COLUMN_RANGE | Column index is out of range. | | ERR-03203 | ERR_MM_BUFFER_SIZE_EXCEEDED | The data length exceeded the buffer size. | | ERR-03204 | ERR_MM_APPEND_PARAM_IP_STRING_NULL | Append data ip string is null. | | ERR-03205 | ERR_MM_APPEND_PARAM_DATETIME_STRING_NULL | Append data datetime string(%s) is null. | | ERR-03206 | ERR_MM_INVALID_COLUMN_TYPE | Invalid column type (%d). | | ERR-03207 | ERR_MM_INVALID_STMT_TYPE | Invalid statement type (%d). | | ERR-03208 | ERR_MM_SERVER_THREAD_ERR | Server thread error: %d - %s | | ERR-03209 | ERR_MM_BUSY_STMT_STATE | statement is busy. (%d) | | ERR-03210 | ERR_MM_CONN_INVALID_STATE | This connection already has been already disconnected | | ERR-03211 | ERR_MM_DB_EXIST | Database already exists. | | ERR-03212 | ERR_MM_DB_NOT_EXIST | Database does not exist. | | ERR-03213 | ERR_MM_SERVER_RUNNING | Server is running. | | ERR-03214 | ERR_MM_DBS_OPEN_FAIL | Failed to open dbs(%s) directory. | | ERR-03215 | ERR_MM_ALTER_SESSION | ALTER SESSION statement is not supported. | ### `ERR-04000`–`ERR-04999` (23) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-04000 | CMI_PROTOCOL_MSG_ERROR_IN_CONNECTION | Protocol message error in connection. | | ERR-04001 | CMI_PROTOCOL_LENGTH_ERROR_IN_CONNECTION | Protocol length error in connection (%llu but %llu). | | ERR-04002 | CMI_DOUBLE_CREATE_COMMUNICATION_CHANNEL | Cannot create duplicate communication channels. | | ERR-04003 | CMI_SOCKET_CREATION_ERROR | Socket creation error (%d). | | ERR-04004 | CMI_BIND_ERROR | Socket bind error. Errorcode is (%d) | | ERR-04005 | CMI_LISTEN_ERROR | Listen error (%d). | | ERR-04006 | CMI_POLL_CREATION_ERROR | Poll creation error (%d). | | ERR-04007 | CMI_POLL_ADD_ERROR | Poll add error (%d). | | ERR-04008 | CMI_CONNECTION_ERROR | Creation error (%d). | | ERR-04009 | CMI_SEND_ERROR | Send error (%d). | | ERR-04010 | CMI_RECV_ERROR | Receive error (%d). | | ERR-04011 | CMI_DISPATCH_ERROR | Dispatch error (%d). | | ERR-04012 | CMI_SETSOCKOPT_ERROR | nbp_sock_set_opt() error (%d). | | ERR-04013 | CMI_RECV_RETRY_ERROR | Failed to receive accept data repeatedly in %u milliseconds. | | ERR-04014 | CMI_MEMORY_ALLOC_ERROR | Memory allocation error. | | ERR-04015 | CMI_INVALID_PROTOCOL_ERROR | Receive invalid protocol (%d). | | ERR-04016 | CMI_TIMEDOUT_ERROR | Communication timed out error. (%d) | | ERR-04017 | CMI_SOCKET_CLOSED | Remote socket closed. | | ERR-04018 | CMI_INVALID_BIND_IP_ADDR | BIND_IP_ADDRESS [%s] is invalid | | ERR-04019 | CMI_BIND_ADDR_NOT_AVAILABLE | BIND_IP_ADDRESS [%s] is not available. Errorcode is[%d] | | ERR-04020 | CMI_BIND_PORT_INUSE | Port[%d] is already in use. Errorcode is [%d] | | ERR-04021 | CMI_OS_NOT_SUPPORT_FUNCTION | Function[%s] is not supported in this OS[%s] | | ERR-04999 | ERR_QP_ROLLUP_NOT_SUPPORTED_ON_DISTANCE_AXIS | ROLLUP is not supported on DISTANCE axis TAG table. | ### `ERR-05000`–`ERR-05999` (1) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-05002 | AD_MANAGER_GENERATE_ERROR | msg does not used | ### `ERR-06000`–`ERR-06999` (35) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-06000 | ERR_LM_FUNCTION_EXECUTION | Function execution failed: "%s". | | ERR-06001 | ERR_LM_RESPONSE_FAILED | Response failed: %s | | ERR-06002 | ERR_LM_ACCEPT_TIMEOUT | Accept timeout: "%s:%u". | | ERR-06003 | ERR_LM_SEND_BUFFER_OVERFLOW | Send buffer overflow: "%s". | | ERR-06004 | ERR_LM_COMMAND_EXECUTION_FAILED | Command execution failed: Application-Id = %u, Command-Code = %u. | | ERR-06005 | ERR_LM_UNSUPPORTED_COMMAND | Unsupported command: Application-Id = %u, Command-Code = %u. | | ERR-06006 | ERR_LM_ALREADY_CONNECTED | Already connected: "%s". | | ERR-06007 | ERR_LM_CSTR_TO_INT32_FAILED | Failed to convert string "%s" to int. | | ERR-06008 | ERR_LM_DESTINATION_HOST_TOO_LONG | Destination-Host too long: "%s". | | ERR-06009 | ERR_LM_DISCONNECTED | Disconnected: "%s". | | ERR-06010 | ERR_LM_HOST_NOT_FOUND | Host not found: "%s". | | ERR-06011 | ERR_LM_GROUPED_AVP_TOO_DEEP | Grouped AVP too deep: %d. | | ERR-06012 | ERR_LM_HANDSHAKE_TIMEOUT | Handshake timeout: "%s". | | ERR-06013 | ERR_LM_INITIALIZED | Link manager already initialized. | | ERR-06014 | ERR_LM_INVALID_HEADER | Invalid header: "%s". | | ERR-06015 | ERR_LM_INVALID_HOST | Invalid host: "%s" and "%s". | | ERR-06016 | ERR_LM_INVALID_PORT_NO | Invalid port no: %d. | | ERR-06017 | ERR_LM_MESSAGE_TOO_LONG | Message too long: %d | | ERR-06018 | ERR_LM_MISSING_AVP | Missing AVP: "%s" | | ERR-06019 | ERR_LM_NOT_INITIALIZED | Link manager not initialized. | | ERR-06020 | ERR_LM_NO_OPENED_GROUPED_AVP_FOUND | No opened grouped AVP found. | | ERR-06021 | ERR_LM_NULL_POINTER_ACCESS | NULL pointer access: "%s". | | ERR-06022 | ERR_LM_ORIGIN_HOST_TOO_LONG | Origin-Host too long: "%s". | | ERR-06023 | ERR_LM_REQUIRE_REQUEST_MESSAGE | Require request message. | | ERR-06024 | ERR_LM_SESSION_ID_TOO_LONG | Session-Id too long: "%s". | | ERR-06025 | ERR_LM_CONNECTION_TIMEOUT | Connection timeout: "%s". | | ERR-06026 | ERR_LM_UNABLE_TO_BIND_ADDRESS | Unable to bind address: "%s". | | ERR-06027 | ERR_LM_ABORT_CALLBACK_TIMEOUT | Abort callback: "Timeout". | | ERR-06028 | ERR_LM_ABORT_CALLBACK_DISCONNECTED | Abort callback: "Disconnected". | | ERR-06029 | ERR_LM_ABORT_CALLBACK_SHUTDOWN | Abort callback: "Shutdown". | | ERR-06030 | ERR_LM_NO_MORE_ADDRESS | No more address: "%s". | | ERR-06031 | ERR_LM_HANDSHAKE_FAILED | Handshake failed: "%s". | | ERR-06032 | ERR_LM_PROCESS_MEMORY_LIMIT | Failed to allocate connection (Current Allocate Memory / PROCESS_MAX_SIZE (%llu/%llu)). | | ERR-06033 | ERR_LM_ABORT_CONN_FREED | connection object for (%s) has been freed. please retry. | | ERR-06034 | ERR_LM_ABORT_SEND_RETRY_COUNT_EXHAUSETED | The number of send repetitions has been exhausted. | ### `ERR-07000`–`ERR-07999` (50) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-07000 | ERR_XM_CREATE_HASH | Error in creating hashtable for global metadata. | | ERR-07001 | ERR_XM_OPEN_META | Error in opening meta. Cannot open meta database. | | ERR-07002 | ERR_XM_EXEC_META | Error in executing meta. Cannot execute meta database | | ERR-07003 | ERR_XM_CLOSE_META | Error in closing meta. Cannot close meta database. | | ERR-07004 | ERR_XM_FETCH_META | Error in fetching meta. | | ERR-07005 | ERR_XM_GLOB_OBJECT_NOT_EXISTS_BY_LOID | No global object found. (Local Object ID=%llu, Host=%s) | | ERR-07006 | ERR_XM_GLOB_OBJECT_NOT_EXISTS_BY_GOID | No global object found. (Global Object ID=%llu, Host=%s) | | ERR-07007 | ERR_XM_GLOB_OBJECT_EXISTS | Global object already exists. (Global Object ID=%llu, Host=%s) | | ERR-07008 | ERR_XM_HASH_ADD_FAILURE | Error in hash add (Memory allocation failed). | | ERR-07009 | ERR_XM_STATEMENT_ALREADY_EXISTS | Failed to add query statement due to unfinished one. | | ERR-07010 | ERR_XM_STATEMENT_NO_EXISTS | Failed to find query statement. | | ERR-07011 | ERR_XM_NOT_SUPPORTED_YET | This query type is not supported yet. | | ERR-07012 | ERR_XM_NOT_SUPPORTED_PLANNODE_YET | This plan node (%s) is not supported yet. | | ERR-07013 | ERR_XM_STATEMENT_CANCELLED | Statement is canceled by the broker. | | ERR-07014 | ERR_XM_NODE_INFO_EXISTS | Node information already exists. | | ERR-07015 | ERR_XM_INVALID_MESSAGE | Invalid message from XM: %u | | ERR-07016 | ERR_XM_DDL_ON_WAREHOUSE_NOT_SUPPORTED | DDL/DELETE statement on warehouse node is not supported. | | ERR-07017 | ERR_XM_NOT_SUPPORTED_AGG_FUNC_YET | This aggregate function (%s) is not supported yet. | | ERR-07018 | ERR_XM_STANDBY_INSERT | INSERT/APPEND to warehouse standby is not available. | | ERR-07019 | ERR_XM_UNSUPPORTED_STMT_TYPE | Unsupported query statement type in the Cluster Edition. | | ERR-07020 | ERR_XM_INTERNAL_ERROR | XM internal error (XMART_POINT:%s) | | ERR-07021 | ERR_XM_XMART_TARGET_NOT_NODE_ID | [XM-ART] Targeted node is not valid. (%s) | | ERR-07022 | ERR_XM_ERROR_VIA_ANSWER_MSG | An error occurred after processing a %s message. [Src='%s']: %s | | ERR-07023 | ERR_XM_ERROR_STAFF_ALREADY_GONE | Execution unit from remote note is already gone. | | ERR-07024 | ERR_XM_INVALID_APPEND_ON_WAREHOUSE | APPEND operation on warehouse node is not supported. | | ERR-07025 | ERR_XM_INVALID_NODE_HOSTS | Host information from broker is invalid. Please check coordinator's status. | | ERR-07026 | ERR_XM_CLUSTER_INVALID | Cluster node information is invalid. | | ERR-07027 | ERR_XM_CLUSTER_CHANGED | Cluster node information has changed during query execution. | | ERR-07028 | ERR_XM_CANNOT_EXPLAIN_STAGE | This execution plan does not need to generate stage(s). | | ERR-07029 | ERR_XM_CLUSTER_CONNECTION_ABORT_TIMEOUT | Cluster connection aborted: Time-out | | ERR-07030 | ERR_XM_CLUSTER_CONNECTION_ABORT_LINK_BROKEN | Cluster connection aborted: Disconnected by warehouse. | | ERR-07031 | ERR_XM_BLOCKED_BY_DEACTIVATED_MODE | DML/DDL is disabled in DEACTIVATED mode. | | ERR-07032 | ERR_XM_DELETE_NOT_AVAILABLE | DELETE is not available since a read-only group exists. | | ERR-07033 | ERR_XM_WAREHOUSE_DROPPED_OUT | Participating warehouse has been dropped out. | | ERR-07034 | ERR_XM_INVALID_BROKER_STORED | Broker info has been changed. Please free statement and initalize again. | | ERR-07035 | ERR_XM_WAREHOUSE_DIRECT_DML_NOW_ALLOWED | Direct DML on warehouse is not allowed. | | ERR-07036 | ERR_XM_WAREHOUSE_NOT_AVAILABLE | Warehouse is not available. | | ERR-07037 | ERR_XM_STAGE_MEMORY_LIMIT | Execution stage memory usage exceeded the limit. (used: %llu, maximum: %llu) | | ERR-07038 | ERR_XM_ARCHIVE_INTERNAL_ERROR | XM archiving error occurred. (%s) | | ERR-07039 | ERR_XM_BROKER_DISCONN | Broker (%s) is disconnected. | | ERR-07040 | ERR_XM_BROKER_NOT_LEADER | Only leader broker can execute DML on LOOKUP table. | | ERR-07041 | ERR_XM_BROKER_REMOTE_ERROR | Remote error. (%s) | | ERR-07042 | ERR_XM_RESTORE_LOOKUP_TIMEOUT | LOOKUP table restore timeout: (%s) | | ERR-07043 | ERR_XM_BROKER_NOT_ACTIVE | Broker is not ACTIVE. | | ERR-07044 | ERR_XM_VERSION_NOT_MATCH | XM version does not match. (%s - %s) | | ERR-07045 | ERR_XM_SNAPSHOT_FAIL | Snapshot failed: %s. | | ERR-07046 | ERR_XM_MESSAGE_EXPIRED | Message %d is expired. | | ERR-07047 | ERR_XM_SNAPSHOT_RECOVER_IN_PROGRESS | Snapshot recover is in progress. | | ERR-07048 | ERR_XM_QUEUE_TIMEOUT | Queue timeout. | | ERR-07049 | ERR_XM_VERSION_UNMATCHED | XM version does not match. (%d - %d) | ### `ERR-08000`–`ERR-08999` (109) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-08000 | ERR_CC_FUNCTION_EXECUTION | Function execution failed: "%s". | | ERR-08001 | ERR_CC_BUFFER_OVERRUN | Buffer overrun. | | ERR-08002 | ERR_CC_END_OF_FILE | End of file. | | ERR-08003 | ERR_CC_HEADER_OCCURS_TOO_MANY_TIMES | Header occurs too many times: "%s". | | ERR-08004 | ERR_CC_JSON_DEPTH_OUT_OF_RANGE | JSON depth: Out of range. | | ERR-08005 | ERR_CC_CONNECTION_TIMEOUT | Connection timeout. | | ERR-08006 | ERR_CC_PACKAGE_NOT_FOUND | Package not found: "%s". | | ERR-08007 | ERR_CC_FILE_NAME_MISMATCH | File name mismatch: "%s" and "%s" | | ERR-08008 | ERR_CC_BLOCK_SIZE_OVERRUN | Block size overrun: %llu and %llu | | ERR-08009 | ERR_CC_FILE_SIZE_MISMATCH | File size mismatch: %llu and %llu | | ERR-08010 | ERR_CC_FILE_READ_SIZE_MISMATCH | File read size mismatch: %llu and %llu | | ERR-08011 | ERR_CC_FILE_WRITE_SIZE_MISMATCH | File write size mismatch: %llu and %llu | | ERR-08012 | ERR_CC_NODE_NOT_FOUND | Node not found: "%s" | | ERR-08013 | ERR_CC_NODE_EXIST | Node exist: "%s" | | ERR-08014 | ERR_CC_INVALID_PACKAGE_FILE_SIZE | Invalid package file size: "%s" = %llu / %llu | | ERR-08015 | ERR_CC_UNMATCHED_HOST | Unmatched host: "%s" and "%s" | | ERR-08016 | ERR_CC_DASHBOARD_INITIALIZED | Dashboard initialized | | ERR-08017 | ERR_CC_DASHBOARD_NOT_INITIALIZED | Dashboard is not initialized | | ERR-08018 | ERR_CC_NULL_POINTER_ACCESS | NULL pointer access: "%s". | | ERR-08019 | ERR_CC_STATUS_NOT_FOUND | Status not found: "%s" | | ERR-08020 | ERR_CC_HASH_INSERT_FAILED | Hash insert failed: "%s" | | ERR-08021 | ERR_CC_HASH_DELETE_FAILED | Hash delete failed: "%s" | | ERR-08022 | ERR_CC_HOST_TOO_LONG | Host too long: "%s" | | ERR-08023 | ERR_CC_ACTIVE_TOO_LONG | Active too long: "%s" | | ERR-08024 | ERR_CC_COORDINATOR_HOST_TOO_LONG | Coordinator-Host too long: "%s" | | ERR-08025 | ERR_CC_SPIN_TIMEOUT | Spin timeout | | ERR-08026 | ERR_CC_DDL_DISABLED | DDL disabled: %s | | ERR-08027 | ERR_CC_STATUS_UNINITALIZED | Status uninitialized | | ERR-08028 | ERR_CC_UNSUPPORTED_NODE_TYPE | Unsupported Node-Type: %u | | ERR-08029 | ERR_CC_SEQUENCE_NUMBER_UNINITIALIZED | Sequence number uninitialized | | ERR-08030 | ERR_CC_SEQUENCE_NUMBER_UNMATCHED | Sequence number unmatched: %lld and %lld | | ERR-08031 | ERR_CC_DDL_INCOMPLETED | DDL[%lld] incomplete: [%s] | | ERR-08032 | ERR_CC_INVALID_BROKER_COUNT | Invalid broker count: %lld | | ERR-08033 | ERR_CC_DDL_FAILED | DDL failed | | ERR-08034 | ERR_CC_DDL_SEQUENCE_NOT_FOUND | DDL sequence not found | | ERR-08035 | ERR_CC_INVALID_DDL_STATE | Invalid DDL state: %lld | | ERR-08036 | ERR_CC_INVALID_DDL_RETURNED | Invalid DDL returned: %lld and %lld | | ERR-08037 | ERR_CC_STANDBY_TOO_LONG | Standby too long: "%s" | | ERR-08038 | ERR_CC_INVALID_STATE_CHANGE | Invalid state change: %u => %u | | ERR-08039 | ERR_CC_INVALID_STATE | Invalid state: %u | | ERR-08040 | ERR_CC_INVALID_NODE_TYPE | Invalid Node-Type: %u | | ERR-08041 | ERR_CC_COORDINATOR_INACTIVE | Coordinator inactive | | ERR-08042 | ERR_CC_NOT_LEADER | Only leader can execute DDL. | | ERR-08043 | ERR_CC_DDL_TIMEOUT | DDL timeout | | ERR-08044 | ERR_CC_DDL_ERROR_MESSAGE | %s | | ERR-08045 | ERR_CC_DDL_NOT_FOUND | DDL not found: %llu | | ERR-08046 | ERR_CC_DDL_INCOMPLETNESS | DDL incompleteness: "%s" | | ERR-08047 | ERR_CC_UNSUPPORTED_PACKAGE | Unsupported package: "%s" | | ERR-08048 | ERR_CC_DDL_DISABLED_BY_MODE_CHANGE | DDL disabled by mode change | | ERR-08049 | ERR_CC_FAILED_TO_FORKED_COMMAND | Failed to fork and execute command: %s. Please check deployer's trace log. | | ERR-08050 | ERR_CC_FUNCTION_EXECUTION_WITH_RC | Function execution failed: "%s" (errno=%d). | | ERR-08051 | ERR_CC_DDL_DISABLED_READONLY_GROUP | DDL disabled because a part of group is not normal. | | ERR-08052 | ERR_CC_DDLSYNC_EXECUTE_FAILED_AFTER_RETRY | DDL[%llu] execution during DDL-Sync failed after several attempts. | | ERR-08053 | ERR_CC_INVALID_OPTION | Invalid option (%s). | | ERR-08054 | ERR_CC_GROUP_NOT_FOUND | Group (%s) is not found. | | ERR-08055 | ERR_CC_PORT_CHECK_REQUIRED | Check %s port number (%d). | | ERR-08056 | ERR_CC_DEPLOYER_DISABLED | Deployer is disabled: "%s". | | ERR-08057 | ERR_CC_ONLY_PRIMARY_COORDINATOR_AVAILABLE | Command is not available in the secondary coordinator. | | ERR-08058 | ERR_CC_CLUSTER_SYNC_FAILURE | Cluster synchronization failed. | | ERR-08059 | ERR_CC_INVALID_DECISION_STATE | Invaid decision state: %s | | ERR-08060 | ERR_CC_MISSING_ATTRIBUTE | Missing attribute: %s | | ERR-08061 | ERR_CC_ATTRIBUTE_OCCURS_TOO_MANY_TIMES | Attribute occurs too many times: %s | | ERR-08062 | ERR_CC_EXECUTE_COMMAND_FAILURE | Failed to execute command (%s). | | ERR-08063 | ERR_CC_PACKAGE_ALREADY_EXISTS | Package name or file name already exists (%s = %s). | | ERR-08064 | ERR_CC_HOST_RES_INFO_NOT_FOUND | Host resource info not found: "%s" | | ERR-08065 | ERR_CC_DISK_INFO_NOT_FOUND | Disk info not found: "%s" | | ERR-08066 | ERR_CC_INTEGER_OVERFLOW | Integer overflow. | | ERR-08067 | ERR_CC_COORDINATOR_COUNT_EXCEEDED | The number of coordinators exceeded %d. | | ERR-08068 | ERR_CC_DDL_DISABLED_BY_INITIAL_STATE | DDL disabled since some of nodes are in initial states. | | ERR-08069 | ERR_CC_COORD_ROLE_HANDSHAKE | Coordinator role handshake failed: [%s] | | ERR-08070 | ERR_CC_HOST_RESOURCE_DISABLED | Collecting host resource is disabled. | | ERR-08071 | ERR_CC_REQUEST_FAILED | Request to execute command %s failed. (code=%d) | | ERR-08072 | ERR_CC_CLUSTER_ACTIVATION_FAILED | Cluster activation failed: %lld / %lld. | | ERR-08073 | ERR_CC_ENVIRONMENT_VARIABLE_NOT_SET | Environment (%s) is not set. | | ERR-08074 | ERR_CC_OPTION_DUPLICATED | Option duplicated. | | ERR-08075 | ERR_CC_OPTION_REQUIRED | Option required (%s). | | ERR-08076 | ERR_CC_LOCK_FAILED | Cannot read Lock File! Check $MACHBASE_COORDINATOR_HOME/conf/machbasecoordinator.lock* and Tracelog in $MACHBASE_COORDINATOR_HOME/trc. | | ERR-08077 | ERR_CC_COORDINATOR_RUNNING | Machbase coordinator is running. | | ERR-08078 | ERR_CC_COORDINATOR_NOT_RUNNING | Machbase coordinator is not running. | | ERR-08079 | ERR_CC_COORDINATOR_PHASE1_FAILED | Machbase Coordinator %s Phase1 failed: %s | | ERR-08080 | ERR_CC_COORDINATOR_PHASE2_FAILED | Machbase Coordinator %s Phase2 failed: %s | | ERR-08081 | ERR_CC_COORDINATOR_DEAD | Machbase Coordinator has been DEAD! Check Tracelog in $MACHBASE_COORDINATOR_HOME/trc. | | ERR-08082 | ERR_CC_METADATA_NOT_CREATED | Machbase Coordinator metadata is not created. Check Tracelog in $MACHBASE_COORDINATOR_HOME/trc. | | ERR-08083 | ERR_CC_METADATA_ALREADY_CREATED | Machbase Coordinator metadata is already created. Check Tracelog in $MACHBASE_COORDINATOR_HOME/trc. | | ERR-08084 | ERR_CC_INVALID_PROCESS_ID | Invalid process id. | | ERR-08085 | ERR_CC_OPTION_INIT_FAILED | Option initialization error: %d. | | ERR-08086 | ERR_CC_OPTION_CHECK_FAILED | Option check error: %d. | | ERR-08087 | ERR_CC_OPTION_GET_FAILED | Option retrieval error: %d (%s). | | ERR-08088 | ERR_CC_COMMAND_OPTION_NOT_FOUND | Command option is not found. | | ERR-08089 | ERR_CC_COLLECTING_HOST_RES_FAILED | Failed to collect '%s': %s. | | ERR-08090 | ERR_CC_INVALID_ATTRIBUTE | Invalid attribute: %s | | ERR-08091 | ERR_CC_DDL_RECOVERY_FAILED | DDL recovery failed: %s. | | ERR-08092 | ERR_CC_DESIRED_STATE_NOT_APPLICABLE | Desired state (%s) is not applicable. | | ERR-08093 | ERR_CC_NODE_STILL_RUNNING | %s is still running. | | ERR-08094 | ERR_CC_SNAPSHOT_NOT_EXIST | SNAPSHOT on %s does not exist. | | ERR-08095 | ERR_CC_RECOVER_NON_READONLY | Group %s is not readonly mode. Snapshot recovery works only for a readonly group | | ERR-08096 | ERR_CC_SNAPSHOT_NOT_AVAILABLE | Snapshot is not available: %s | | ERR-08097 | ERR_CC_NOT_SCRAPPED | Warehouse %s is not scrapped. %s only works on a scrapped warehouse. | | ERR-08098 | ERR_CC_MASTER_NOT_FOUND | Cannot add lookup node %s (%s) before adding the lookup master node. | | ERR-08099 | ERR_CC_MASTER_NOT_MONITOR | Lookup monitor node (%s) cannot change the lookup master node. | | ERR-08100 | ERR_CC_SNAPSHOT_FAIL_PUBLISH | Failed to publish SnapshotID to warehouse. | | ERR-08101 | ERR_CC_NOT_READONLY | Group (%s) is not readonly. | | ERR-08102 | ERR_CC_NODE_FIX | Fix node (%s) failed. (refcnt=%d) | | ERR-08103 | ERR_CC_LOOKUP_ALREADY_RUNNING | Lookup node running already. (%s:%d) | | ERR-08104 | ERR_CC_LOOKUP_CONNECT_FAILED | Connect to lookup node failed. (%s:%d) | | ERR-08105 | ERR_CC_LOOKUP_STARTUP_FAILED | Startup lookup node failed. (%s:%d) | | ERR-08106 | ERR_CC_NORMAL_SHUTDOWN | Unable to shutdown warehouse (%s) since it is not INACTIVE status. | | ERR-08107 | ERR_CC_MESSAGE_EXPIRED | Expired message (%llu) received. | | ERR-08108 | ERR_CC_SNAPSHOT_FAIL | Snapshot failed: %s | ### `ERR-09000`–`ERR-09999` (11) | 코드 | 심볼 | 메시지 원문 | |------|------|------| | ERR-09000 | ERR_RP_BUFFER_POOL_ITEM_ALLOC_FAIL | Failed to allocate buffer pool item. | | ERR-09001 | ERR_RP_TARGET_FILE_OPEN_FAIL | Failed to open replication target file <Table %llu, FileID %llu, PartID %llu - Level %d Type %d> | | ERR-09002 | ERR_RP_PROTOCOL_ERROR | Invalid protocol received. | | ERR-09003 | ERR_RP_SOCKET_ERROR | Socket write failed. | | ERR-09004 | ERR_RP_APPEND_VALUE_ERROR | Failed to append target table<%llu>. | | ERR-09005 | ERR_RP_VALUE_BUFFER_ALLOC_MEM_FAIL | Failed to allocate value buffer. | | ERR-09006 | ERR_RP_TABLE_CURSOR_OPEN_FAIL | Failed to open table <%llu>'s cursor. | | ERR-09007 | ERR_RP_POLL_REMOVE | Failed to remove poll socket. (%d) | | ERR-09008 | ERR_RP_POLL_DESTROY | Failed to destroy poll socket. (%d) | | ERR-09009 | ERR_RP_CANNOT_REPLICATE2_LARGER | Cannot replicate to larger dbs. | | ERR-09010 | ERR_RP_HOST_NOT_FOUND | Host not found: "%s". | ## 오류 코드 확인 방법 - machsql 또는 드라이버에서 반환하는 오류 문자열을 확인합니다. - 서버 로그는 `$MACHBASE_HOME/trc/` 아래의 trace 로그를 확인합니다. 오류 발생 후 원인을 파악하기 어렵다면 [서버 로그 분석](/dbms/operations-configuration-recovery/diagnosis-observability/#log-diagnosis-logs-log-server-logs)과 [장애 징후 확인](/dbms/operations-configuration-recovery/diagnosis-observability/#monitoring-capacity-failure) 섹션을 참고하십시오. --- title: "16.8 AI Agent Reference" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/ language: kr kind: section --- # 16.8 AI Agent Reference AI Agent Reference는 AI 에이전트와 RAG 시스템이 Machbase DBMS 8.7 문서에서 정확한 정본을 찾도록 돕는 탐색 계층입니다. SQL 문법, SDK 지원 여부와 운영 절차를 이 절에서 다시 정의하지 않고 각 기능의 정본으로 연결합니다. ## 구성 | 페이지 | 목적 | |--------|------| | [Agent 사용 가이드](./guide-agent/) | 질문 분류, 검증 순서와 응답 원칙 | | [canonical-url-map](./canonical-url-map/) | 주제별 정규 문서 URL | | [task-map](./task-map/) | 사용자 작업별 읽기·검증 순서 | | [support-matrix](./support-matrix/) | Edition·테이블·SDK 지원표 정본 찾기 | | [constraints-index](./constraints-index/) | 제약과 오류 조건 정본 찾기 | | [evidence-map](./evidence-map/) | 주장 유형별 근거 선택 | | [terminology-disambiguation](./terminology-disambiguation/) | 혼동하기 쉬운 용어 확인 | | [sql-generation-rules](./sql-generation-rules/) | SQL 생성 전 검증 규칙 | | [sdk-api-selection-rules](./sdk-api-selection-rules/) | SDK와 API 선택 순서 | | [operations-checklist](./operations-checklist/) | 안전한 운영 답변 생성 순서 | | [error-resolution-map](./error-resolution-map/) | 오류 진단 정본 찾기 | | [llms.txt](./llms-txt/) | 간략한 기계 판독 문서 맵 | | [전체 본문과 RAG 인덱스](./llms-full-txt-chunk-index/) | 전체 Markdown과 JSON 문서 인덱스 | ## 기계 판독 출력 - [llms.txt](/kr/llms.txt) - [llms-full.txt](/kr/llms-full.txt) - [llms-chunks.json](/kr/llms-chunks.json) 위 출력은 현재 한국어 DBMS 문서만 포함합니다. Machbase Neo와 보존용 DBMS 8.5 문서는 포함하지 않습니다. ## 사용 원칙 1. 서버 버전, Edition, 테이블 타입과 SDK를 먼저 확인합니다. 2. 기능 정본과 지원 범위를 함께 읽습니다. 3. 확인되지 않은 문법, 기본값, 제한과 오류 코드를 만들지 않습니다. 4. 운영 변경은 대상, 영향, 복구 방법과 완료 조건을 명시합니다. 5. 답변 링크는 공개 canonical URL을 사용합니다. --- title: "16.8.1 Agent 사용 가이드" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/guide-agent/ language: kr kind: page --- # 16.8.1 Agent 사용 가이드 이 가이드는 Machbase 관련 질문을 문서 정본에 근거해 답변하는 순서를 정의합니다. ## 질문 분류 | 질문 유형 | 먼저 확인할 문서 | |----------|------------------| | 설치·업그레이드 | [설치, 배포, 업그레이드](/dbms/installation-deployment-upgrade/) | | 테이블 선택·설계 | [테이블 타입 개념과 선택](/dbms/data-modeling-table-design/) | | SQL 문법·함수 | [SQL 레퍼런스](/dbms/reference/sql/) | | SDK·연동 | [개발 및 애플리케이션 연동](/dbms/development-tools-integration/) | | 지원 여부·제약 | [지원 범위와 제약](/dbms/reference/support-scope-constraints/) | | 운영·복구 | [운영, 설정, 복구](/dbms/operations-configuration-recovery/) | | 오류·성능 | [문제 해결](/dbms/troubleshooting/) 및 [성능 튜닝](/dbms/performance-tuning/) | ## 응답 생성 순서 1. 질문에서 제품 버전, Edition, 테이블 타입, SDK와 작업 대상을 식별합니다. 2. [용어 구분](../terminology-disambiguation/)으로 Machbase 의미를 확인합니다. 3. [지원표](../support-matrix/)와 [제약 인덱스](../constraints-index/)를 확인합니다. 4. 문법·API·운영 절차의 canonical page에서 실제 형식을 확인합니다. 5. 예제는 전제 조건, 실행, 결과 확인과 정리를 포함해 작성합니다. 6. 불확실한 사실은 단정하지 않고 확인에 필요한 버전·명령·문서를 제시합니다. ## 근거와 링크 - 공개 답변에는 docs.machbase.com의 canonical URL을 사용합니다. - 내부 issue, commit이나 소스 경로는 공개 제품 동작의 대체 근거로 사용하지 않습니다. - 여러 문서가 충돌하면 최신 버전별 정본과 실제 지원 범위를 우선합니다. ## 안전 조회와 진단을 먼저 제시합니다. 데이터 삭제, 서버 재시작, 세션 종료, 설정 변경과 복구는 사용자의 대상과 승인 범위를 확인하지 않고 실행 단계로 제시하지 않습니다. --- title: "16.8.2 canonical-url-map" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/canonical-url-map/ language: kr kind: page --- # 16.8.2 canonical-url-map Machbase DBMS 8.7 문서의 주요 canonical URL입니다. 세부 페이지는 각 장의 목차에서 찾고, 이전 URL보다 아래 정본을 우선합니다. ## 시작과 개념 | 주제 | Canonical URL | |------|---------------| | DBMS 매뉴얼 | `/dbms/` | | 시작하기 | `/dbms/getting-started/` | | 핵심 개념 | `/dbms/core-concepts/` | | 설치·업그레이드 | `/dbms/installation-deployment-upgrade/` | | 테이블 타입 선택 | `/dbms/data-modeling-table-design/` | ## 테이블과 분석 | 주제 | Canonical URL | |------|---------------| | TAG | `/dbms/tag-table-usage/` | | ROLLUP | `/dbms/tag-rollup-usage/` | | LOG | `/dbms/log-table-usage/` | | TRANSACTION | `/dbms/rdb-table-usage/` | | LOOKUP | `/dbms/lookup-table-usage/` | | VOLATILE | `/dbms/volatile-table-usage/` | ## 개발·운영·레퍼런스 | 주제 | Canonical URL | |------|---------------| | SDK/API | `/dbms/development-tools-integration/` | | 성능 | `/dbms/performance-tuning/` | | 운영·복구 | `/dbms/operations-configuration-recovery/` | | 보안 | `/dbms/security-access-control/` | | 문제 해결 | `/dbms/troubleshooting/` | | SQL | `/dbms/reference/sql/` | | 설정 | `/dbms/reference/configuration/` | | 시스템 카탈로그 | `/dbms/reference/system-catalog/` | | 지원 범위 | `/dbms/reference/support-scope-constraints/` | | 오류 코드 | `/dbms/reference/error-codes/` | ## 기계 판독 URL | 출력 | 한국어 | 영어 | |------|--------|------| | 간략 맵 | `/kr/llms.txt` | `/llms.txt` | | 전체 본문 | `/kr/llms-full.txt` | `/llms-full.txt` | | 문서 인덱스 | `/kr/llms-chunks.json` | `/llms-chunks.json` | --- title: "16.8.3 task-map" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/task-map/ language: kr kind: page --- # 16.8.3 task-map 사용자 작업별로 확인할 정본과 완료 조건을 연결합니다. | 작업 | 확인 순서 | 완료 확인 | |------|-----------|-----------| | 처음 설치 | [설치](/dbms/installation-deployment-upgrade/) → [시작하기](/dbms/getting-started/) | 서버 상태, 연결, 샘플 조회 | | 테이블 선택 | [선택 기준](/dbms/data-modeling-table-design/) → 해당 테이블 장 | Edition·DML·축·보존 요구 충족 | | 대량 입력 | [연동 공통 개념](/dbms/development-tools-integration/concepts-common/) → SDK 페이지 | 성공/실패 건수와 flush 확인 | | SQL 작성 | [SQL 레퍼런스](/dbms/reference/sql/) → [지원 범위](/dbms/reference/support-scope-constraints/) | 실제 schema와 결과 확인 | | SDK 선택 | [연동 방식 선택](/dbms/development-tools-integration/selection-integration-method/) → [SDK 지원 범위](/dbms/development-tools-integration/sdk-support-scope/) | 서버·SDK 버전과 API 일치 | | 성능 진단 | [성능 접근법](/dbms/performance-tuning/performance-approach/) → 증상별 튜닝 | 기준값과 변경 후 측정 비교 | | 장애 진단 | [문제 해결](/dbms/troubleshooting/) → [오류 코드](/dbms/reference/error-codes/) | 원인, 조치, 재발 방지 기록 | | 백업·복구 | [백업·복구](/dbms/operations-configuration-recovery/backup-restore-mount/) | 복원 또는 MOUNT 조회 검증 | 작업에 쓰기·삭제·재시작이 포함되면 대상과 영향 범위를 확정한 뒤 실행 절차를 선택합니다. --- title: "16.8.4 support-matrix" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/support-matrix/ language: kr kind: page --- # 16.8.4 support-matrix 이 페이지는 지원표를 복제하지 않고 질문의 차원을 확인한 뒤 현재 정본으로 연결합니다. ## 확인 순서 1. [Edition별 지원표](/dbms/reference/support-scope-constraints/edition/)에서 Standard와 Cluster 범위를 확인합니다. 2. [테이블 타입별 지원표](/dbms/reference/support-scope-constraints/table-types-type/)에서 대상 테이블의 SQL·API 범위를 확인합니다. 3. [SDK 지원 범위](/dbms/development-tools-integration/sdk-support-scope/)에서 client API와 최소 provenance를 확인합니다. 4. 기능별 상세 지원표에서 조건과 예외를 확인합니다. | 기능군 | 정본 | |--------|------| | TAG data UPDATE | [TAG UPDATE 지원표](/dbms/reference/support-scope-constraints/tag-data-update/) | | ROLLUP | [ROLLUP 지원 범위](/dbms/reference/support-scope-constraints/rollup/) | | TRANSACTION | [TRANSACTION 지원 범위](/dbms/reference/support-scope-constraints/rdb/) | | 백업·MOUNT | [백업/MOUNT 지원표](/dbms/reference/support-scope-constraints/backup-mount/) | | 권한 | [권한 지원표](/dbms/reference/support-scope-constraints/privileges/) | 지원 여부를 답할 때는 `O/△/X`만 인용하지 말고 Edition, 테이블 타입, 서버와 SDK 버전, 필수 조건을 함께 제시합니다. --- title: "16.8.5 constraints-index" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/constraints-index/ language: kr kind: page --- # 16.8.5 constraints-index 제약 질문은 기능 이름만으로 답하지 않고 대상 객체와 실행 경로를 함께 확인합니다. | 제약 범주 | 정본 | |-----------|------| | Edition·테이블 타입 | [지원 범위와 제약](/dbms/reference/support-scope-constraints/) | | SQL 조건과 SET 대상 | [SQL 문법 사전](/dbms/reference/sql/syntax/) | | TAG | [TAG 제약과 문제 해결](/dbms/tag-table-usage/constraints-errors-troubleshooting/) | | ROLLUP | [ROLLUP 제약과 문제 해결](/dbms/troubleshooting/rollup/) | | LOG | [LOG 제약과 문제 해결](/dbms/log-table-usage/constraints-errors-troubleshooting/) | | TRANSACTION | [TRANSACTION 제약과 문제 해결](/dbms/rdb-table-usage/constraints-errors-troubleshooting/) | | LOOKUP | [LOOKUP 제약과 문제 해결](/dbms/lookup-table-usage/constraints-errors-troubleshooting/) | | VOLATILE | [VOLATILE 제약과 문제 해결](/dbms/volatile-table-usage/constraints-errors-troubleshooting/) | 오류가 발생한 경우 전체 SQL, schema, Edition, 서버 버전과 오류 코드를 보존한 뒤 정본의 허용 조건과 비교합니다. --- title: "16.8.6 evidence-map" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/evidence-map/ language: kr kind: page --- # 16.8.6 evidence-map 답변의 주장 유형에 맞는 공개 근거를 선택합니다. | 주장 유형 | 우선 근거 | |----------|-----------| | SQL 문법·함수·타입 | [SQL 레퍼런스](/dbms/reference/sql/) | | Edition·테이블·SDK 지원 | [지원 범위와 제약](/dbms/reference/support-scope-constraints/) 및 [SDK 지원 범위](/dbms/development-tools-integration/sdk-support-scope/) | | 설정 키·기본값 | [설정 레퍼런스](/dbms/reference/configuration/)와 배포본 설정 파일 | | 시스템 상태 컬럼 | [시스템 카탈로그](/dbms/reference/system-catalog/) | | CLI option | [명령행 도구](/dbms/reference/command-line-tools/)와 배포본 `--help` | | 오류 의미·조치 | [오류 코드](/dbms/reference/error-codes/) 및 [문제 해결](/dbms/troubleshooting/) | | 운영 절차 | [운영, 설정, 복구](/dbms/operations-configuration-recovery/) | ## 검증 규칙 - 버전과 Edition 조건을 근거와 함께 보존합니다. - 예제 결과를 일반 보장이나 성능 수치로 확대하지 않습니다. - 공개 정본에 없는 사실은 확인 필요 상태로 남깁니다. - 내부 개발 이력은 공개 매뉴얼 링크를 대신하지 않습니다. --- title: "16.8.7 terminology-disambiguation" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/terminology-disambiguation/ language: kr kind: page --- # 16.8.7 terminology-disambiguation 사용자 용어를 일반 데이터베이스 의미로 추측하지 말고 Machbase 정본에서 확인합니다. | 용어 | 확인할 정본 | |------|-------------| | TAG, LOG, LOOKUP, VOLATILE, TRANSACTION | [핵심 개념](/dbms/core-concepts/)과 [테이블 타입 선택](/dbms/data-modeling-table-design/) | | Append와 SQL INSERT | [연동 공통 개념](/dbms/development-tools-integration/concepts-common/) | | ROLLUP | [ROLLUP 활용](/dbms/tag-rollup-usage/) | | BASETIME, BASE DISTANCE, SUMMARIZED | [TAG 구조와 스키마](/dbms/tag-table-usage/table-structure-schema/) | | AUTH KEY | [인증과 AUTH KEY](/dbms/security-access-control/authentication-auth-key/) | | Broker, Warehouse | [Edition과 Cluster 개념](/dbms/core-concepts/concepts-edition/) | | database, owner, tablespace | [다중 데이터베이스 운영](/dbms/operations-configuration-recovery/multi-database/) | 답변에서는 제품 객체명과 SQL keyword를 그대로 유지하고, 사용자가 일반 의미로 쓴 용어와 Machbase 객체가 다르면 먼저 구분합니다. --- title: "16.8.8 sql-generation-rules" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/sql-generation-rules/ language: kr kind: page --- # 16.8.8 sql-generation-rules AI가 SQL을 생성할 때 적용할 검증 순서입니다. 실제 문법은 [SQL 레퍼런스](/dbms/reference/sql/)가 정본입니다. ## 생성 전 확인 1. 서버 버전과 Edition을 확인합니다. 2. 대상 database, owner, 테이블 타입과 `DESC` 결과를 확인합니다. 3. [SQL 문법 사전](/dbms/reference/sql/syntax/)에서 statement 형식을 확인합니다. 4. [함수 사전](/dbms/reference/sql/functions/)에서 인자와 반환 타입을 확인합니다. 5. [지원 범위](/dbms/reference/support-scope-constraints/)에서 Edition·테이블 제약을 확인합니다. ## 생성 규칙 - 다른 DBMS의 keyword, 함수, hint나 transaction 동작을 추측해 사용하지 않습니다. - identifier를 parameter marker로 대체하지 않습니다. - 시간·거리 범위, DELETE와 UPDATE는 예상 대상 행을 먼저 조회할 수 있게 작성합니다. - 결과 순서가 필요하면 `ORDER BY`를 명시합니다. - 변경 예제에는 결과 확인과 cleanup을 포함합니다. 오류가 발생하면 문법을 임의로 변형하지 말고 전체 오류와 schema를 [문제 해결](/dbms/troubleshooting/) 절차로 확인합니다. --- title: "16.8.9 sdk-api-selection-rules" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/sdk-api-selection-rules/ language: kr kind: page --- # 16.8.9 sdk-api-selection-rules SDK 이름만으로 기능을 가정하지 않고 요구사항과 실제 지원 범위를 함께 확인합니다. ## 선택 순서 1. 언어와 표준 인터페이스 요구사항을 [연동 방식 선택](/dbms/development-tools-integration/selection-integration-method/)에서 확인합니다. 2. Append, transaction, prepared statement, named bind, metadata와 AUTH KEY 요구사항을 정리합니다. 3. [SDK 지원 범위](/dbms/development-tools-integration/sdk-support-scope/)에서 지원 여부와 provenance를 확인합니다. 4. 선택한 SDK 페이지에서 설치, 연결 option, 타입 mapping과 오류 처리를 확인합니다. | 환경 | 정본 | |------|------| | C/C++ SQLCLI·ODBC | [SQLCLI와 ODBC](/dbms/development-tools-integration/cli-odbc/) | | Java | [JDBC](/dbms/development-tools-integration/jdbc/) | | Python | [Python](/dbms/development-tools-integration/python/) | | Node.js·TypeScript | [Node.js / TypeScript](/dbms/development-tools-integration/node-js-typescript/) | | .NET | [.NET Connector](/dbms/development-tools-integration/net-connector/) | | Go | [Go](/dbms/development-tools-integration/go/) | 서버와 SDK version 조합은 [호환성](/dbms/reference/support-scope-constraints/compatibility-xma-protocol/)을 함께 확인합니다. --- title: "16.8.10 operations-checklist" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/operations-checklist/ language: kr kind: page --- # 16.8.10 operations-checklist 운영 답변은 [운영, 설정, 복구](/dbms/operations-configuration-recovery/)의 절차를 참고하고 다음 안전 순서를 적용합니다. 1. 증상, 발생 시각, 전체 오류와 대상 database·node·table을 식별합니다. 2. `machadmin -e`, 관련 `V$` 뷰와 로그로 현재 상태를 읽기 전용으로 확인합니다. 3. 정상 동작과 장애 상태를 구분하고 변경이 필요한 근거를 제시합니다. 4. 변경 대상, 영향, downtime, rollback과 성공 조건을 명시합니다. 5. 사용자의 승인 범위를 확인한 뒤 한 단계씩 실행하고 결과를 재확인합니다. 서버 재시작, 세션 종료, 데이터 삭제, 설정 변경, backup 복구와 cluster node 변경을 진단 명령처럼 자동 제안하지 않습니다. 명령과 SQL은 해당 [운영 장](/dbms/operations-configuration-recovery/)에서 확인합니다. --- title: "16.8.11 error-resolution-map" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/error-resolution-map/ language: kr kind: page --- # 16.8.11 error-resolution-map 오류 번호나 원인을 추측하지 않고 전체 오류와 실행 문맥을 보존합니다. ## 진단 순서 1. 전체 `ERR-XXXXX` 메시지, SQL·명령, 발생 시각을 수집합니다. 2. 서버와 SDK 버전, Edition, 대상 database·owner·table과 연결 option을 기록합니다. 3. [오류 코드 사전](/dbms/reference/error-codes/)에서 메시지를 확인합니다. 4. [문제 해결](/dbms/troubleshooting/)에서 증상별 진단 절차를 적용합니다. 5. 조치 뒤 같은 입력과 확인 쿼리로 복구 여부를 검증합니다. | 증상 | 정본 | |------|------| | 서버·인증·연결 | [서버와 연결 문제](/dbms/troubleshooting/server-connection/) | | 입력·Append·파일 | [입력과 적재 문제](/dbms/troubleshooting/item/) | | 쿼리·성능·메모리 | [쿼리와 성능 문제](/dbms/troubleshooting/performance/) | | 백업·복구 | [백업과 복구 문제](/dbms/troubleshooting/recovery-backup/) | | Cluster | [Cluster 문제](/dbms/troubleshooting/cluster/) | 오류 문자열 일부만으로 임의의 오류 코드를 붙이거나, 재현 없이 destructive workaround를 권장하지 않습니다. --- title: "16.8.12 llms.txt" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/llms-txt/ language: kr kind: page --- # 16.8.12 llms.txt `llms.txt`는 LLM이 현재 Machbase DBMS 매뉴얼의 구조와 주요 정본을 빠르게 찾도록 제공하는 UTF-8 plain-text 목차입니다. ## URL | 언어 | URL | |------|-----| | 영어 | [https://docs.machbase.com/llms.txt](/llms.txt) | | 한국어 | [https://docs.machbase.com/kr/llms.txt](/kr/llms.txt) | 출력은 현재 `/dbms/` 문서만 포함합니다. Machbase Neo, 보존용 DBMS 8.5, draft와 alias 페이지는 포함하지 않습니다. ## 내용 - 제품과 매뉴얼 버전 - DBMS 상위 장과 canonical URL - SQL, SDK, 운영, 지원 범위와 오류 코드 정본 - AI Agent Reference - 전체 본문과 JSON 인덱스 URL ## 사용 방법 1. `llms.txt`에서 질문 주제의 canonical section을 찾습니다. 2. 개별 Markdown이 필요하면 해당 문서 URL의 `index.md`를 읽습니다. 3. 전체 corpus가 필요하면 [llms-full.txt](../llms-full-txt-chunk-index/)를 사용합니다. 4. crawler가 문서 단위 metadata를 필요로 하면 `llms-chunks.json`을 사용합니다. `llms.txt`는 제품 사실의 정본이 아니라 탐색용 인덱스입니다. 실제 답변은 링크된 현재 문서를 확인해 작성합니다. --- title: "16.8.13 전체 본문과 RAG 문서 인덱스" url: https://docs.machbase.com/kr/dbms/reference/ai-agent-reference/llms-full-txt-chunk-index/ language: kr kind: page --- # 16.8.13 전체 본문과 RAG 문서 인덱스 현재 DBMS 매뉴얼은 전체 Markdown 본문과 페이지 단위 JSON 인덱스를 제공합니다. ## 전체 본문 | 언어 | URL | |------|-----| | 영어 | [llms-full.txt](/llms-full.txt) | | 한국어 | [llms-full.txt](/kr/llms-full.txt) | 전체 본문은 navigation weight 순서로 published DBMS 페이지를 연결합니다. 각 페이지 경계에는 제목, 언어와 canonical URL이 있으며 본문은 source Markdown을 유지합니다. ## JSON 인덱스 | 언어 | URL | |------|-----| | 영어 | [llms-chunks.json](/llms-chunks.json) | | 한국어 | [llms-chunks.json](/kr/llms-chunks.json) | schema version 1의 최상위 필드는 다음과 같습니다. | 필드 | 설명 | |------|------| | `schema_version` | JSON 계약 버전. 현재 `1` | | `product` | `Machbase DBMS` | | `manual_version` | 문서 대상 제품 버전 | | `language` | `en` 또는 `kr` | | `document_count` | `documents` 배열 크기 | | `documents` | 문서 metadata 배열 | 각 문서는 `id`, `title`, `url`, `markdown_url`, `kind`, `parent_url`, `weight`, `last_modified`를 제공합니다. JSON에는 본문을 중복 저장하지 않습니다. `markdown_url`에서 페이지별 Markdown을 가져오거나 `llms-full.txt`를 corpus로 사용합니다. ## Chunk 경계 현재 인덱스는 한 published page를 한 document chunk로 취급합니다. SQL 문법, SDK 작업과 운영 절차가 가능한 한 개별 페이지에 유지되므로 URL과 제목을 안정적인 chunk 식별자로 사용할 수 있습니다. 큰 사전 페이지를 더 나눌 때도 기존 page `id`는 유지합니다. 빌드 시각 같은 비결정적 값은 출력하지 않습니다. Neo, DBMS 8.5, draft와 alias 페이지는 인덱스와 전체 본문에서 제외합니다.