메인 콘텐츠로 건너뛰기
윈도우 함수를 사용하면 현재 행과 관련된 행 집합 전체에 걸쳐 계산을 수행할 수 있습니다. 수행할 수 있는 계산 중 일부는 집계 함수로 수행하는 계산과 비슷하지만, 윈도 함수는 행을 하나의 출력으로 그룹화하지 않으므로 개별 행이 그대로 반환됩니다.

표준 윈도우 함수

ClickHouse는 윈도우와 윈도우 함수를 정의하는 표준 구문을 지원합니다. 아래 표는 현재 각 기능의 지원 여부를 보여줍니다.
기능지원 여부
ad hoc 윈도우 지정 (count(*) over (partition by id order by time desc))
윈도우 함수가 포함된 표현식(예: (count(*) over ()) / 2)
WINDOW 절 (select ... from table window w as (partition by id))
ROWS 프레임
RANGE 프레임✅ (기본값)
DateTime RANGE OFFSET 프레임용 INTERVAL 구문❌ (RANGE는 모든 숫자형에서 작동하므로 대신 초 단위 숫자를 지정하십시오.)
GROUPS 프레임
프레임에 대한 집계 함수 계산 (sum(value) over (order by time))✅ (모든 집계 함수가 지원됩니다)
rank(), dense_rank(), row_number()
별칭: denseRank()
percent_rank()✅ 데이터셋의 파티션 내에서 값의 상대적 순위를 효율적으로 계산합니다. 이 함수는 ifNull((rank() OVER(PARTITION BY x ORDER BY y) - 1) / nullif(count(1) OVER(PARTITION BY x) - 1, 0), 0)처럼 더 장황하고 계산 비용이 큰 수동 SQL 계산을 사실상 대체합니다.
별칭: percentRank()
cume_dist()✅ 값 그룹 내에서 값의 누적 분포를 계산합니다. 현재 행의 값보다 작거나 같은 값을 가진 행의 비율을 반환합니다.
lag/lead(value, offset)
다음 우회 방법 중 하나를 사용할 수도 있습니다.
1) any(value) over (.... rows between <offset> preceding and <offset> preceding) 또는 lead의 경우 following 사용
2) lagInFrame/leadInFrame 사용. 이는 유사하게 동작하지만 윈도우 프레임을 따릅니다. lag/lead와 동일한 동작을 얻으려면 rows between unbounded preceding and unbounded following을 사용하십시오.
ntile(buckets)
다음과 같이 윈도우를 지정하십시오. (partition by x order by y rows between unbounded preceding and unbounded following)

ClickHouse 전용 윈도 함수

다음과 같은 ClickHouse 전용 윈도 함수를 제공합니다:

nonNegativeDerivative(metric_column, timestamp_column[, INTERVAL X UNITS])

timestamp_column을 기준으로 지정된 metric_column의 음수가 아닌 미분값을 계산합니다. INTERVAL은 생략할 수 있으며, 기본값은 INTERVAL 1 SECOND입니다. 각 행에 대해 계산되는 값은 다음과 같습니다:
  • 첫 번째 행: 0,
  • ii번째 행: metricimetrici1timestampitimestampi1interval{\text{metric}_i - \text{metric}_{i-1} \over \text{timestamp}_i - \text{timestamp}_{i-1}} * \text{interval}.

구문

aggregate_function (column_name)
  OVER ([[PARTITION BY grouping_column] [ORDER BY sorting_column] 
        [ROWS or RANGE expression_to_bound_rows_within_the_group]] | [window_name])
FROM table_name
WINDOW window_name as ([
  [PARTITION BY grouping_column]
  [ORDER BY sorting_column]
  [ROWS or RANGE expression_to_bound_rows_within_the_group]
])
  • PARTITION BY - 결과 집합을 어떻게 그룹으로 나눌지 정의합니다.
  • ORDER BY - aggregate_function을 계산할 때 그룹 내 행의 순서를 정의합니다.
  • ROWS or RANGE - 프레임의 경계를 정의하며, aggregate_function은 프레임 내에서 계산됩니다.
  • WINDOW - 여러 표현식에서 동일한 윈도우 정의를 사용할 수 있도록 합니다.
      PARTITION
┌─────────────────┐  <-- UNBOUNDED PRECEDING (BEGINNING of the PARTITION)
│                 │
│                 │
│=================│  <-- N PRECEDING  <─┐
│      N ROWS     │                     │  F
│  Before CURRENT │                     │  R
│~~~~~~~~~~~~~~~~~│  <-- CURRENT ROW    │  A
│     M ROWS      │                     │  M
│   After CURRENT │                     │  E
│=================│  <-- M FOLLOWING  <─┘
│                 │
│                 │
└─────────────────┘  <--- UNBOUNDED FOLLOWING (END of the PARTITION)

함수

이 함수들은 윈도 함수로만 사용할 수 있습니다.
  • row_number() - 현재 행이 속한 파티션 내에서 1부터 시작하는 번호를 매깁니다.
  • first_value(x) - 정렬된 프레임 내에서 평가된 첫 번째 값을 반환합니다.
  • last_value(x) - 정렬된 프레임 내에서 평가된 마지막 값을 반환합니다.
  • nth_value(x, offset) - 정렬된 프레임에서 n번째 행(offset)을 기준으로 평가된 첫 번째 non-NULL 값을 반환합니다.
  • rank() - 현재 행이 속한 파티션 내에서 중간 순위를 건너뛰는 방식으로 순위를 매깁니다.
  • dense_rank() - 현재 행이 속한 파티션 내에서 중간 순위를 건너뛰지 않는 방식으로 순위를 매깁니다.
  • lagInFrame(x) - 정렬된 프레임 내에서 현재 행보다 지정한 물리적 오프셋만큼 앞에 있는 행에서 평가된 값을 반환합니다.
  • leadInFrame(x) - 정렬된 프레임 내에서 현재 행보다 오프셋 행 수만큼 뒤에 있는 행에서 평가된 값을 반환합니다.

예시

윈도우 함수를 사용하는 몇 가지 예시를 살펴보겠습니다.

행 번호 지정

CREATE TABLE salaries
(
    `team` String,
    `player` String,
    `salary` UInt32,
    `position` String
)
Engine = Memory;

INSERT INTO salaries FORMAT Values
    ('Port Elizabeth Barbarians', 'Gary Chen', 195000, 'F'),
    ('New Coreystad Archdukes', 'Charles Juarez', 190000, 'F'),
    ('Port Elizabeth Barbarians', 'Michael Stanley', 150000, 'D'),
    ('New Coreystad Archdukes', 'Scott Harrison', 150000, 'D'),
    ('Port Elizabeth Barbarians', 'Robert George', 195000, 'M');
SELECT
    player,
    salary,
    row_number() OVER (ORDER BY salary ASC) AS row
FROM salaries;
┌─player──────────┬─salary─┬─row─┐
│ Michael Stanley │ 150000 │   1 │
│ Scott Harrison  │ 150000 │   2 │
│ Charles Juarez  │ 190000 │   3 │
│ Gary Chen       │ 195000 │   4 │
│ Robert George   │ 195000 │   5 │
└─────────────────┴────────┴─────┘
SELECT
    player,
    salary,
    row_number() OVER (ORDER BY salary ASC) AS row,
    rank() OVER (ORDER BY salary ASC) AS rank,
    dense_rank() OVER (ORDER BY salary ASC) AS denseRank
FROM salaries;
┌─player──────────┬─salary─┬─row─┬─rank─┬─denseRank─┐
│ Michael Stanley │ 150000 │   1 │    1 │         1 │
│ Scott Harrison  │ 150000 │   2 │    1 │         1 │
│ Charles Juarez  │ 190000 │   3 │    3 │         2 │
│ Gary Chen       │ 195000 │   4 │    4 │         3 │
│ Robert George   │ 195000 │   5 │    4 │         3 │
└─────────────────┴────────┴─────┴──────┴───────────┘

집계 함수

각 선수의 급여를 소속 팀의 평균 급여와 비교합니다.
SELECT
    player,
    salary,
    team,
    avg(salary) OVER (PARTITION BY team) AS teamAvg,
    salary - teamAvg AS diff
FROM salaries;
┌─player──────────┬─salary─┬─team──────────────────────┬─teamAvg─┬───diff─┐
│ Charles Juarez  │ 190000 │ New Coreystad Archdukes   │  170000 │  20000 │
│ Scott Harrison  │ 150000 │ New Coreystad Archdukes   │  170000 │ -20000 │
│ Gary Chen       │ 195000 │ Port Elizabeth Barbarians │  180000 │  15000 │
│ Michael Stanley │ 150000 │ Port Elizabeth Barbarians │  180000 │ -30000 │
│ Robert George   │ 195000 │ Port Elizabeth Barbarians │  180000 │  15000 │
└─────────────────┴────────┴───────────────────────────┴─────────┴────────┘
각 선수의 급여를 소속 팀의 최고 급여와 비교합니다.
SELECT
    player,
    salary,
    team,
    max(salary) OVER (PARTITION BY team) AS teamMax,
    salary - teamMax AS diff
FROM salaries;
┌─player──────────┬─salary─┬─team──────────────────────┬─teamMax─┬───diff─┐
│ Charles Juarez  │ 190000 │ New Coreystad Archdukes   │  190000 │      0 │
│ Scott Harrison  │ 150000 │ New Coreystad Archdukes   │  190000 │ -40000 │
│ Gary Chen       │ 195000 │ Port Elizabeth Barbarians │  195000 │      0 │
│ Michael Stanley │ 150000 │ Port Elizabeth Barbarians │  195000 │ -45000 │
│ Robert George   │ 195000 │ Port Elizabeth Barbarians │  195000 │      0 │
└─────────────────┴────────┴───────────────────────────┴─────────┴────────┘

컬럼별 파티셔닝

CREATE TABLE wf_partition
(
    `part_key` UInt64,
    `value` UInt64,
    `order` UInt64    
)
ENGINE = Memory;

INSERT INTO wf_partition FORMAT Values
   (1,1,1), (1,2,2), (1,3,3), (2,0,0), (3,0,0);

SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER (PARTITION BY part_key) AS frame_values
FROM wf_partition
ORDER BY
    part_key ASC,
    value ASC;

┌─part_key─┬─value─┬─order─┬─frame_values─┐
111 │ [1,2,3]      │   <
122 │ [1,2,3]      │    │  1-st group
133 │ [1,2,3]      │   <
200 │ [0]          │   <- 2-nd group
300 │ [0]          │   <- 3-d group
└──────────┴───────┴───────┴──────────────┘

프레임 경계 설정

CREATE TABLE wf_frame
(
    `part_key` UInt64,
    `value` UInt64,
    `order` UInt64
)
ENGINE = Memory;

INSERT INTO wf_frame FORMAT Values
   (1,1,1), (1,2,2), (1,3,3), (1,4,4), (1,5,5);
-- 프레임은 파티션의 경계로 제한됩니다 (BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER (
        PARTITION BY part_key 
        ORDER BY order ASC
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS frame_values
FROM wf_frame
ORDER BY
    part_key ASC,
    value ASC;
    
┌─part_key─┬─value─┬─order─┬─frame_values─┐
111 │ [1,2,3,4,5]  │
122 │ [1,2,3,4,5]  │
133 │ [1,2,3,4,5]  │
144 │ [1,2,3,4,5]  │
155 │ [1,2,3,4,5]  │
└──────────┴───────┴───────┴──────────────┘
-- 축약형 - 경계 표현식 없음, order by 없음,
-- `ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING`과 동일
SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER (PARTITION BY part_key) AS frame_values_short,
    groupArray(value) OVER (PARTITION BY part_key
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    ) AS frame_values
FROM wf_frame
ORDER BY
    part_key ASC,
    value ASC;
┌─part_key─┬─value─┬─order─┬─frame_values_short─┬─frame_values─┐
111 │ [1,2,3,4,5]        │ [1,2,3,4,5]  │
122 │ [1,2,3,4,5]        │ [1,2,3,4,5]  │
133 │ [1,2,3,4,5]        │ [1,2,3,4,5]  │
144 │ [1,2,3,4,5]        │ [1,2,3,4,5]  │
155 │ [1,2,3,4,5]        │ [1,2,3,4,5]  │
└──────────┴───────┴───────┴────────────────────┴──────────────┘
-- 프레임은 파티션의 시작부터 현재 행까지로 제한됩니다
SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER (
        PARTITION BY part_key 
        ORDER BY order ASC
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS frame_values
FROM wf_frame
ORDER BY
    part_key ASC,
    value ASC;

┌─part_key─┬─value─┬─order─┬─frame_values─┐
111 │ [1]          │
122 │ [1,2]        │
133 │ [1,2,3]      │
144 │ [1,2,3,4]    │
155 │ [1,2,3,4,5]  │
└──────────┴───────┴───────┴──────────────┘
-- 축약형 (프레임은 파티션의 시작부터 현재 행까지로 제한됨)
-- `ORDER BY order ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`와 동일
SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER (PARTITION BY part_key ORDER BY order ASC) AS frame_values_short,
    groupArray(value) OVER (PARTITION BY part_key ORDER BY order ASC
       ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS frame_values
FROM wf_frame
ORDER BY
    part_key ASC,
    value ASC;

┌─part_key─┬─value─┬─order─┬─frame_values_short─┬─frame_values─┐
111 │ [1]                │ [1]          │
122 │ [1,2]              │ [1,2]        │
133 │ [1,2,3]            │ [1,2,3]      │
144 │ [1,2,3,4]          │ [1,2,3,4]    │
155 │ [1,2,3,4,5]        │ [1,2,3,4,5]  │
└──────────┴───────┴───────┴────────────────────┴──────────────┘
-- 프레임은 파티션의 시작부터 현재 행까지로 경계가 지정되지만, 정렬 순서는 역순입니다
SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER (PARTITION BY part_key ORDER BY order DESC) AS frame_values
FROM wf_frame
ORDER BY
    part_key ASC,
    value ASC;

┌─part_key─┬─value─┬─order─┬─frame_values─┐
111 │ [5,4,3,2,1]  │
122 │ [5,4,3,2]    │
133 │ [5,4,3]      │
144 │ [5,4]        │
155 │ [5]          │
└──────────┴───────┴───────┴──────────────┘
-- 슬라이딩 프레임 - 1 PRECEDING ROW와 CURRENT ROW
SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER (
        PARTITION BY part_key 
        ORDER BY order ASC
        ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
    ) AS frame_values
FROM wf_frame
ORDER BY
    part_key ASC,
    value ASC;

┌─part_key─┬─value─┬─order─┬─frame_values─┐
111 │ [1]          │
122 │ [1,2]        │
133 │ [2,3]        │
144 │ [3,4]        │
155 │ [4,5]        │
└──────────┴───────┴───────┴──────────────┘
-- 슬라이딩 프레임 - ROWS BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING 
SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER (
        PARTITION BY part_key 
        ORDER BY order ASC
        ROWS BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING
    ) AS frame_values
FROM wf_frame
ORDER BY
    part_key ASC,
    value ASC;

┌─part_key─┬─value─┬─order─┬─frame_values─┐
111 │ [1,2,3,4,5]  │
122 │ [1,2,3,4,5]  │
133 │ [2,3,4,5]    │
144 │ [3,4,5]      │
155 │ [4,5]        │
└──────────┴───────┴───────┴──────────────┘
-- row_number는 프레임을 무시하므로, rn_1 = rn_2 = rn_3 != rn_4
SELECT
    part_key,
    value,
    order,
    groupArray(value) OVER w1 AS frame_values,
    row_number() OVER w1 AS rn_1,
    sum(1) OVER w1 AS rn_2,
    row_number() OVER w2 AS rn_3,
    sum(1) OVER w2 AS rn_4
FROM wf_frame
WINDOW
    w1 AS (PARTITION BY part_key ORDER BY order DESC),
    w2 AS (
        PARTITION BY part_key 
        ORDER BY order DESC 
        ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
    )
ORDER BY
    part_key ASC,
    value ASC;
┌─part_key─┬─value─┬─order─┬─frame_values─┬─rn_1─┬─rn_2─┬─rn_3─┬─rn_4─┐ │ 1 │ 1 │ 1 │ [5,4,3,2,1] │ 5 │ 5 │ 5 │ 2 │ │ 1 │ 2 │ 2 │ [5,4,3,2] │ 4 │ 4 │ 4 │ 2 │ │ 1 │ 3 │ 3 │ [5,4,3] │ 3 │ 3 │ 3 │ 2 │ │ 1 │ 4 │ 4 │ [5,4] │ 2 │ 2 │ 2 │ 2 │ │ 1 │ 5 │ 5 │ [5] │ 1 │ 1 │ 1 │ 1 │ └──────────┴───────┴───────┴──────────────┴──────┴──────┴──────┴──────┘

```sql
-- first_value와 last_value는 프레임을 준수합니다
SELECT
    groupArray(value) OVER w1 AS frame_values_1,
    first_value(value) OVER w1 AS first_value_1,
    last_value(value) OVER w1 AS last_value_1,
    groupArray(value) OVER w2 AS frame_values_2,
    first_value(value) OVER w2 AS first_value_2,
    last_value(value) OVER w2 AS last_value_2
FROM wf_frame
WINDOW
    w1 AS (PARTITION BY part_key ORDER BY order ASC),
    w2 AS (PARTITION BY part_key ORDER BY order ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)
ORDER BY
    part_key ASC,
    value ASC;

┌─frame_values_1─┬─first_value_1─┬─last_value_1─┬─frame_values_2─┬─first_value_2─┬─last_value_2─┐
│ [1]            │             1 │            1 │ [1]            │             1 │            1 │
│ [1,2]          │             1 │            2 │ [1,2]          │             1 │            2 │
│ [1,2,3]        │             1 │            3 │ [2,3]          │             2 │            3 │
│ [1,2,3,4]      │             1 │            4 │ [3,4]          │             3 │            4 │
│ [1,2,3,4,5]    │             1 │            5 │ [4,5]          │             4 │            5 │
└────────────────┴───────────────┴──────────────┴────────────────┴───────────────┴──────────────┘
-- 프레임 내의 두 번째 값
SELECT
    groupArray(value) OVER w1 AS frame_values_1,
    nth_value(value, 2) OVER w1 AS second_value
FROM wf_frame
WINDOW w1 AS (PARTITION BY part_key ORDER BY order ASC ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
ORDER BY
    part_key ASC,
    value ASC;

┌─frame_values_1─┬─second_value─┐
│ [1]            │            0
│ [1,2]          │            2
│ [1,2,3]        │            2
│ [1,2,3,4]      │            2
│ [2,3,4,5]      │            3
└────────────────┴──────────────┘
-- 프레임 내 두 번째 값 + 누락된 값에 대한 Null
SELECT
    groupArray(value) OVER w1 AS frame_values_1,
    nth_value(toNullable(value), 2) OVER w1 AS second_value
FROM wf_frame
WINDOW w1 AS (PARTITION BY part_key ORDER BY order ASC ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
ORDER BY
    part_key ASC,
    value ASC;
┌─frame_values_1─┬─second_value─┐ │ [1] │ ᴺᵁᴸᴸ │ │ [1,2] │ 2 │ │ [1,2,3] │ 2 │ │ [1,2,3,4] │ 2 │ │ [2,3,4,5] │ 3 │ └────────────────┴──────────────┘

실제 사용 예시

다음 예시는 실제 환경에서 자주 접하는 문제를 해결하는 방법을 보여줍니다.

부서별 최대/총 급여

CREATE TABLE employees
(
    `department` String,
    `employee_name` String,
    `salary` Float
)
ENGINE = Memory;

INSERT INTO employees FORMAT Values
   ('Finance', 'Jonh', 200),
   ('Finance', 'Joan', 210),
   ('Finance', 'Jean', 505),
   ('IT', 'Tim', 200),
   ('IT', 'Anna', 300),
   ('IT', 'Elen', 500);
SELECT
    department,
    employee_name AS emp,
    salary,
    max_salary_per_dep,
    total_salary_per_dep,
    round((salary / total_salary_per_dep) * 100, 2) AS `share_per_dep(%)`
FROM
(
    SELECT
        department,
        employee_name,
        salary,
        max(salary) OVER wndw AS max_salary_per_dep,
        sum(salary) OVER wndw AS total_salary_per_dep
    FROM employees
    WINDOW wndw AS (
        PARTITION BY department
        ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    )
    ORDER BY
        department ASC,
        employee_name ASC
);

┌─department─┬─emp──┬─salary─┬─max_salary_per_dep─┬─total_salary_per_dep─┬─share_per_dep(%)─┐
│ Finance    │ Jean │    50550591555.19
│ Finance    │ Joan │    21050591522.95
│ Finance    │ Jonh │    20050591521.86
│ IT         │ Anna │    300500100030
│ IT         │ Elen │    500500100050
│ IT         │ Tim  │    200500100020
└────────────┴──────┴────────┴────────────────────┴──────────────────────┴──────────────────┘

누적합

CREATE TABLE warehouse
(
    `item` String,
    `ts` DateTime,
    `value` Float
)
ENGINE = Memory

INSERT INTO warehouse VALUES
    ('sku38', '2020-01-01', 9),
    ('sku38', '2020-02-01', 1),
    ('sku38', '2020-03-01', -4),
    ('sku1', '2020-01-01', 1),
    ('sku1', '2020-02-01', 1),
    ('sku1', '2020-03-01', 1);
SELECT
    item,
    ts,
    value,
    sum(value) OVER (PARTITION BY item ORDER BY ts ASC) AS stock_balance
FROM warehouse
ORDER BY
    item ASC,
    ts ASC;

┌─item──┬──────────────────ts─┬─value─┬─stock_balance─┐
│ sku1  │ 2020-01-01 00:00:0011
│ sku1  │ 2020-02-01 00:00:0012
│ sku1  │ 2020-03-01 00:00:0013
│ sku38 │ 2020-01-01 00:00:0099
│ sku38 │ 2020-02-01 00:00:00110
│ sku38 │ 2020-03-01 00:00:00-46
└───────┴─────────────────────┴───────┴───────────────┘

이동 평균 / 슬라이딩 평균(3개 행 기준)

CREATE TABLE sensors
(
    `metric` String,
    `ts` DateTime,
    `value` Float
)
ENGINE = Memory;
insert into sensors values(‘cpu_temp’, ‘2020-01-01 00:00:00’, 87), (‘cpu_temp’, ‘2020-01-01 00:00:01’, 77), (‘cpu_temp’, ‘2020-01-01 00:00:02’, 93), (‘cpu_temp’, ‘2020-01-01 00:00:03’, 87), (‘cpu_temp’, ‘2020-01-01 00:00:04’, 87), (‘cpu_temp’, ‘2020-01-01 00:00:05’, 87), (‘cpu_temp’, ‘2020-01-01 00:00:06’, 87), (‘cpu_temp’, ‘2020-01-01 00:00:07’, 87);

```sql
SELECT
    metric,
    ts,
    value,
    avg(value) OVER (
        PARTITION BY metric 
        ORDER BY ts ASC 
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) AS moving_avg_temp
FROM sensors
ORDER BY
    metric ASC,
    ts ASC;

┌─metric───┬──────────────────ts─┬─value─┬───moving_avg_temp─┐
│ cpu_temp │ 2020-01-01 00:00:00 │    87 │                87 │
│ cpu_temp │ 2020-01-01 00:00:01 │    77 │                82 │
│ cpu_temp │ 2020-01-01 00:00:02 │    93 │ 85.66666666666667 │
│ cpu_temp │ 2020-01-01 00:00:03 │    87 │ 85.66666666666667 │
│ cpu_temp │ 2020-01-01 00:00:04 │    87 │                89 │
│ cpu_temp │ 2020-01-01 00:00:05 │    87 │                87 │
│ cpu_temp │ 2020-01-01 00:00:06 │    87 │                87 │
│ cpu_temp │ 2020-01-01 00:00:07 │    87 │                87 │
└──────────┴─────────────────────┴───────┴───────────────────┘

이동 평균 / 슬라이딩 평균(10초 기준)

SELECT
    metric,
    ts,
    value,
    avg(value) OVER (PARTITION BY metric ORDER BY ts
      RANGE BETWEEN 10 PRECEDING AND CURRENT ROW) AS moving_avg_10_seconds_temp
FROM sensors
ORDER BY
    metric ASC,
    ts ASC;
    
┌─metric───┬──────────────────ts─┬─value─┬─moving_avg_10_seconds_temp─┐
│ cpu_temp │ 2020-01-01 00:00:008787
│ cpu_temp │ 2020-01-01 00:01:107777
│ cpu_temp │ 2020-01-01 00:02:209393
│ cpu_temp │ 2020-01-01 00:03:308787
│ cpu_temp │ 2020-01-01 00:04:408787
│ cpu_temp │ 2020-01-01 00:05:508787
│ cpu_temp │ 2020-01-01 00:06:008787
│ cpu_temp │ 2020-01-01 00:07:108787
└──────────┴─────────────────────┴───────┴────────────────────────────┘

이동 평균 / 슬라이딩 평균(10일 기준)

온도는 초 단위 정밀도로 저장되지만, RangeORDER BY toDate(ts)를 사용하면 크기가 10인 프레임이 형성되며, toDate(ts)를 사용하므로 단위는 일입니다.
CREATE TABLE sensors
(
    `metric` String,
    `ts` DateTime,
    `value` Float
)
ENGINE = Memory;
insert into sensors values(‘ambient_temp’, ‘2020-01-01 00:00:00’, 16), (‘ambient_temp’, ‘2020-01-01 12:00:00’, 16), (‘ambient_temp’, ‘2020-01-02 11:00:00’, 9), (‘ambient_temp’, ‘2020-01-02 12:00:00’, 9), (‘ambient_temp’, ‘2020-02-01 10:00:00’, 10), (‘ambient_temp’, ‘2020-02-01 12:00:00’, 10), (‘ambient_temp’, ‘2020-02-10 12:00:00’, 12), (‘ambient_temp’, ‘2020-02-10 13:00:00’, 12), (‘ambient_temp’, ‘2020-02-20 12:00:01’, 16), (‘ambient_temp’, ‘2020-03-01 12:00:00’, 16), (‘ambient_temp’, ‘2020-03-01 12:00:00’, 16), (‘ambient_temp’, ‘2020-03-01 12:00:00’, 16);

```sql
SELECT
    metric,
    ts,
    value,
    round(avg(value) OVER (PARTITION BY metric ORDER BY toDate(ts) 
       RANGE BETWEEN 10 PRECEDING AND CURRENT ROW),2) AS moving_avg_10_days_temp
FROM sensors
ORDER BY
    metric ASC,
    ts ASC;

┌─metric───────┬──────────────────ts─┬─value─┬─moving_avg_10_days_temp─┐
│ ambient_temp │ 2020-01-01 00:00:00 │    16 │                      16 │
│ ambient_temp │ 2020-01-01 12:00:00 │    16 │                      16 │
│ ambient_temp │ 2020-01-02 11:00:00 │     9 │                    12.5 │
│ ambient_temp │ 2020-01-02 12:00:00 │     9 │                    12.5 │
│ ambient_temp │ 2020-02-01 10:00:00 │    10 │                      10 │
│ ambient_temp │ 2020-02-01 12:00:00 │    10 │                      10 │
│ ambient_temp │ 2020-02-10 12:00:00 │    12 │                      11 │
│ ambient_temp │ 2020-02-10 13:00:00 │    12 │                      11 │
│ ambient_temp │ 2020-02-20 12:00:01 │    16 │                   13.33 │
│ ambient_temp │ 2020-03-01 12:00:00 │    16 │                      16 │
│ ambient_temp │ 2020-03-01 12:00:00 │    16 │                      16 │
│ ambient_temp │ 2020-03-01 12:00:00 │    16 │                      16 │
└──────────────┴─────────────────────┴───────┴─────────────────────────┘

참고

GitHub 이슈

윈도우 함수의 초기 지원 로드맵은 이 이슈에 정리되어 있습니다. 윈도우 함수와 관련된 모든 GitHub 이슈에는 comp-window-functions 레이블이 지정되어 있습니다.

테스트

다음 테스트에는 현재 지원되는 문법의 예시가 포함되어 있습니다. https://github.com/ClickHouse/ClickHouse/blob/master/tests/performance/window&#95;functions.xml https://github.com/ClickHouse/ClickHouse/blob/master/tests/queries/0&#95;stateless/01591&#95;window&#95;functions.sql

Postgres 문서

https://www.postgresql.org/docs/current/sql-select.html#SQL-WINDOW https://www.postgresql.org/docs/devel/sql-expressions.html#SYNTAX-WINDOW-FUNCTIONS https://www.postgresql.org/docs/devel/functions-window.html https://www.postgresql.org/docs/devel/tutorial-window.html

MySQL 문서

https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html https://dev.mysql.com/doc/refman/8.0/en/window-functions-usage.html https://dev.mysql.com/doc/refman/8.0/en/window-functions-frames.html
마지막 수정일 2026년 6월 10일