본문으로 건너뛰기

Beancount 쿼리 언어 - SQL과 유사한 재무 쿼리

Beancount는 강력한 SQL과 유사한 쿼리 언어(BQL)를 제공하여 재무 데이터를 정확하게 분할, 분석 및 분석할 수 있습니다. 빠른 보고서를 생성하거나, 항목을 디버깅하거나, 복잡한 분석을 수행하려는 경우 BQL을 마스터하면 일반 텍스트 회계 장부의 잠재력을 최대한 활용할 수 있습니다. 이 가이드에서는 구조, 기능 및 모범 사례를 안내합니다. 🔍


쿼리 구조 및 실행

BQL의 핵심은 익숙한 SQL에서 영감을 받은 구문입니다. 쿼리는 bean-query 명령줄 도구를 사용하여 실행되며, 이 도구는 장부 파일을 처리하고 결과를 터미널에 직접 반환합니다.

기본 쿼리 형식

BQL 쿼리는 세 가지 주요 절로 구성됩니다. SELECT, FROMWHERE.

SELECT <target1>, <target2>, ...
FROM <entry-filter-expression>
WHERE <posting-filter-expression>;
  • SELECT: 검색할 데이터 열을 지정합니다.
  • FROM: 전체 트랜잭션을 처리 하기 전에 필터링합니다.
  • WHERE: 트랜잭션이 선택된 후에 개별 포스팅 라인을 필터링합니다.

2단계 필터링 시스템

FROMWHERE 절의 차이점을 이해하는 것은 정확한 쿼리를 작성하는 데 매우 중요합니다. BQL은 2단계 필터링 프로세스를 사용합니다.

  1. 트랜잭션 수준 (FROM) 이 절은 전체 트랜잭션에 작용합니다. 트랜잭션이 FROM 조건을 충족하면 전체 트랜잭션 (모든 포스팅 포함)이 다음 단계로 전달됩니다. 이것은 이중 장부 회계 시스템의 무결성을 유지하므로 데이터를 필터링하는 주요 방법입니다. 예를 들어, FROM year = 2024 필터링은 2024년에 발생한 모든 트랜잭션을 선택합니다.

  2. 포스팅 수준 (WHERE) 이 절은 FROM 절에서 선택한 트랜잭션 내의 개별 포스팅을 필터링합니다. 이는 프레젠테이션 및 트랜잭션의 특정 레그에 집중하는 데 유용합니다. 그러나 이 수준에서 필터링하면 항목의 한쪽 면만 볼 수 있으므로 출력에서 트랜잭션의 무결성을 "깨뜨릴" 수 있습니다. 예를 들어, Expenses:Groceries 계정으로의 모든 포스팅을 선택할 수 있습니다.


데이터 모델

데이터를 효과적으로 쿼리하려면 Beancount가 데이터를 구성하는 방식을 이해해야 합니다. 장부는 지시문의 목록이지만 BQL은 주로 Transaction 항목에 중점을 둡니다.

트랜잭션 구조

Transaction은 최상위 속성 및 Posting 객체 목록이 있는 컨테이너입니다.

Transaction
├── date
├── flag
├── payee
├── narration
├── tags
├── links
└── Postings[]
├── account
├── units
├── cost
├── price
└── metadata

사용 가능한 열 유형

트랜잭션 또는 해당 포스팅의 모든 속성을 SELECT할 수 있습니다.

  1. 트랜잭션 속성 이러한 열은 단일 트랜잭션 내의 모든 포스팅에 대해 동일합니다.

    SELECT
    date, -- 트랜잭션 날짜 (datetime.date)
    year, -- 트랜잭션 연도 (int)
    month, -- 트랜잭션 월 (int)
    day, -- 트랜잭션 일 (int)
    flag, -- 트랜잭션 플래그, 예: "*" 또는 "!" (str)
    payee, -- 수취인 (str)
    narration, -- 설명 또는 메모 (str)
    tags, -- 태그 집합, 예: #trip-2024 (set[str])
    links -- 링크 집합, 예: ^expense-report (set[str])
  2. 포스팅 속성 이러한 열은 각 개별 포스팅 라인에 특정합니다.

    SELECT
    account, -- 계정 이름 (str)
    position, -- 단위 및 비용을 포함한 전체 금액 (Position)
    units, -- 포스팅의 숫자 및 통화 (Amount)
    cost, -- 포스팅의 원가 기준 (Cost)
    price, -- 포스팅에 사용된 가격 (Amount)
    weight, -- 원가 기준으로 변환된 포지션 (Amount)
    balance -- 계정의 단위 누계 (Inventory)

쿼리 함수

BQL에는 SQL과 마찬가지로 집계 및 데이터 변환을 위한 함수 모음이 포함되어 있습니다.

집계 함수

집계 함수는 여러 행에 걸쳐 데이터를 요약합니다. GROUP BY와 함께 사용하면 그룹화된 요약을 제공합니다.

-- 포스팅 수 계산
SELECT COUNT(*)

-- 모든 포스팅 값 합산 (일반 통화로 변환)
SELECT SUM(position)

-- 첫 번째 및 마지막 트랜잭션 날짜 찾기
SELECT FIRST(date), LAST(date)

-- 최소 및 최대 포지션 값 찾기
SELECT MIN(position), MAX(position)

-- 각 계정의 합계를 구하기 위해 계정별로 그룹화
SELECT account, SUM(position) GROUP BY account

포지션/인벤토리 함수

position 열은 복합 객체입니다. 이러한 함수를 사용하면 특정 부분을 추출하거나 시장 가치를 계산할 수 있습니다.

-- 포지션에서 숫자 및 통화만 추출
SELECT UNITS(position)

-- 포지션의 총 비용 표시
SELECT COST(position)

-- 원가 기준으로 변환된 포지션 표시 (투자에 유용)
SELECT WEIGHT(position)

-- 최신 가격 데이터를 사용하여 시장 가치 계산
SELECT VALUE(position)

이러한 기능을 결합하여 강력한 보고서를 만들 수 있습니다. 예를 들어, 투자 포트폴리오의 총 비용 및 현재 시장 가치를 보려면 다음을 수행하십시오.

SELECT
account,
COST(SUM(position)) AS total_cost,
VALUE(SUM(position)) AS market_value
FROM
account ~ "Assets:Investments"
GROUP BY
account

고급 기능

기본 SELECT 문 외에도 BQL은 일반적인 재무 보고서에 대한 특수 명령을 제공합니다.

잔액 보고서

BALANCES 문은 특정 기간에 대한 대차 대조표 또는 손익 계산서를 생성합니다.

-- 2024년 초 현재의 간단한 대차 대조표 생성
BALANCES FROM close ON 2024-01-01
WHERE account ~ "^Assets|^Liabilities"

-- 2024 회계 연도에 대한 손익 계산서 생성
BALANCES FROM
OPEN ON 2024-01-01
CLOSE ON 2024-12-31
WHERE account ~ "^Income|^Expenses"

저널 보고서

JOURNAL 문은 하나 이상의 계정에 대한 자세한 활동을 표시하며, 기존 장부 보기와 유사합니다.

-- 원래 비용으로 확인 계정의 모든 활동 표시
JOURNAL "Assets:Checking" AT COST

-- 단위 (주식) 만 표시하는 모든 401k 트랜잭션 표시
JOURNAL "Assets:.*:401k" AT UNITS

인쇄 작업

PRINT 문은 원래 Beancount 파일 형식으로 일치하는 전체 트랜잭션을 출력하는 디버깅 도구입니다.

-- 2024년의 모든 투자 관련 트랜잭션 인쇄
PRINT FROM year = 2024
WHERE account ~ "Assets:Investments"

-- 일부 도구에서 생성한 고유 ID로 트랜잭션 찾기
PRINT FROM id = "8e7c47250d040ae2b85de580dd4f5c2a"

필터링 표현식

논리 연산자(AND, OR), 정규 표현식(~) 및 비교를 사용하여 정교한 필터를 만들 수 있습니다.

-- 2024년 하반기의 모든 출장비 찾기
SELECT * FROM
year = 2024 AND month >= 6
WHERE account ~ "Expenses:Travel"

-- 휴가 또는 비즈니스와 관련된 모든 트랜잭션 찾기
SELECT * FROM
"vacation-2024" IN tags OR
"business-trip" IN links

성능 고려 사항 ⚙️

bean-query는 효율성을 위해 설계되었지만 작동 흐름을 이해하면 대규모 장부에서 더 빠른 쿼리를 작성하는 데 도움이 될 수 있습니다.

  1. 데이터 로드: Beancount는 먼저 전체 장부 파일을 구문 분석하고 모든 트랜잭션을 연대순으로 정렬합니다. 이 전체 데이터 세트는 메모리에 보관됩니다.
  2. 쿼리 최적화: 쿼리 엔진은 최대 효율성을 위해 특정 순서로 필터를 적용합니다. FROM (트랜잭션) -> WHERE (포스팅) -> 집계. FROM 수준에서 필터링하는 것이 가장 빠릅니다.
  3. 메모리 사용량: 모든 작업은 메모리 내에서 수행됩니다. Position 객체 및 Inventory 집계는 최적화되어 있지만 매우 큰 결과 집합은 상당한 RAM을 소비할 수 있습니다. BQL은 디스크 기반 임시 스토리지를 사용하지 않습니다.

모범 사례

다음 팁에 따라 깨끗하고 효과적이며 유지 관리 가능한 쿼리를 작성하십시오.

  1. 쿼리 구성 특히 복잡한 쿼리의 경우 가독성을 위해 쿼리 형식을 지정하십시오. 줄 바꿈 및 들여 쓰기를 사용하여 절을 구분하십시오.

    -- 모든 2024년 비용에 대한 깨끗하고 읽기 쉬운 쿼리
    SELECT
    date,
    account,
    position
    FROM
    year = 2024
    WHERE
    account ~ "Expenses"
    ORDER BY
    date DESC;
  2. 디버깅 쿼리가 예상대로 작동하지 않으면 EXPLAIN을 사용하여 Beancount가 쿼리를 구문 분석하는 방법을 확인하십시오. 필터를 테스트하려면 SELECT DISTINCT를 사용하여 일치하는 고유 값을 확인하십시오.

    -- 쿼리 계획 보기
    EXPLAIN SELECT date, account, position;

    -- 정규 표현식과 일치하는 계정 테스트
    SELECT DISTINCT account
    WHERE account ~ "^Assets:.*";
  3. 잔액 어설션 BQL을 사용하여 장부의 balance 어설션을 다시 확인할 수 있습니다. 이 쿼리는 해당 계정에 대한 마지막 잔액 확인에 지정된 정확한 금액을 반환해야 합니다.

    -- 확인 계정의 최종 잔액 확인
    SELECT account, sum(position)
    FROM close ON 2025-01-01 -- 잔액 지시문의 날짜 사용
    WHERE account = "Assets:Checking";