산술 연산자

산술 연산자는 덧셈, 뺄셈, 곱셈, 나눗셈을 위한 이항(binary) 연산자와 양수, 음수를 나타내기 위한 단항(unary) 연산자가 있다. 양수/음수의 부호를 나타내는 단항 연산자의 연산 우선순위가 이항 연산자보다 높다.

<expression>  <mathematical_operator>  <expression>

    <expression> ::=
        bit_string |
        character_string |
        numeric_value |
        date-time_value |
        collection_value |
        NULL

    <mathematical_operator> ::=
        <set_arithmetic_operator> |
        <arithmetic_operator>

            <arithmetic_operator> ::=
                + |
                - |
                * |
                { / | DIV } |
                { % | MOD }

            <set_arithmetic_operator> ::=
                UNION |
                DIFFERENCE |
                { INTERSECT | INTERSECTION }
  • <expression>: 연산을 수행할 수식을 선언한다.
  • <mathematical_operator>: 수학적 연산을 지정하는 연산자로서, 산술 연산자와 집합 연산자가 있다.
    • <set_arithmetic_operator>: 컬렉션 타입의 피연산자에 대해 합집합, 차집합, 교집합을 수행하는 집합 산술 연산자이다.
    • <arithmetic_operator>: 사칙 연산을 수행하기 위한 연산자이다.

다음은 CUBRID가 지원하는 산술 연산자의 설명 및 리턴 값을 나타낸 표이다.

산술 연산자

산술 연산자 설명 연산식 리턴 값
+ 더하기 연산 1+2 3
- 빼기 연산 1-2 -1
* 곱하기 연산 1*2 2
/ 나누기 연산 후, 몫을 반환한다. 1/2.0 0.500000000
DIV 나누기 연산 후, 몫을 반환한다. 피연산자는 정수 타입이어야 하며, 정수를 반환한다. 1 DIV 2 0
% , MOD 나누기 연산 후, 나머지를 반환한다. 피연산자는 정수 타입이어야 하며, 정수를 반환한다. 피연산자가 실수이면 MOD 함수를 이용한다. 1 % 2 1 MOD 2 1

수치형 데이터 타입의 산술 연산과 타입 변환

모든 수치형 데이터 타입을 산술 연산에 사용할 수 있으며, 연산 결과 타입은 피연산자의 데이터 타입과 연산의 종류에 따라 다르다. 아래는 피연산자 타입별 덧셈/뺄셈/곱셈 연산의 결과 데이터 타입을 정리한 표이다.

피연산자의 타입별 결과 데이터 타입

  INT NUMERIC FLOAT DOUBLE
INT INT 또는 BIGINT NUMERIC FLOAT DOUBLE
NUMERIC NUMERIC NUMERIC (p와 s도 변환됨) DOUBLE DOUBLE
FLOAT FLOAT DOUBLE FLOAT DOUBLE
DOUBLE DOUBLE DOUBLE DOUBLE DOUBLE

피연산자가 모두 동일한 데이터 타입이면 연산 결과의 타입이 변환되지 않으나, 나누기 연산의 경우 예외적으로 타입이 변환되므로 주의해야 한다. 분모, 즉 제수(divisor)가 0이면 에러가 발생한다.

아래는 피연산자가 모두 NUMERIC 타입인 경우, 연산 결과의 전체 자릿수(p)와 소수점 아래 자릿수(s)를 정리한 표이다.

NUMERIC 타입의 연산 결과

연산 결과의 최대 자릿수 결과의 소수점 이하 자릿수
N(p1, s1) + N(p2, s2) max(p1-s1, p2-s2)+max(s1, s2) +1 max(s1, s2)
N(p1, s1) - N(p2, s2) max(p1-s1, p2-s2)+max(s1, s2) max(s1, s2)
N(p1, s1) * N(p2, s2) p1+p2+1 s1+s2
N(p1, s1) / N(p2, s2) s2 > 0 이면 Pt = p1+max(s1, s2) + s2 - s1, 그 외에는 Pt = p1라 하고, s1 > s2 이면 St = s1, 그 외에는 s2라 하면, 소수점 이하 자릿수는 St < 9 이면 min(9-St, 38-Pt) + St, 그 외에는 St

예제

--int * int
SELECT 123*123;
      123*123
=============
        15129
-- int * int returns overflow error
SELECT (1234567890123*1234567890123);
ERROR: Data overflow on data type bigint.
-- int * numeric returns numeric type
SELECT (1234567890123*CAST(1234567890123 AS NUMERIC(15,2)));
 (1234567890123* cast(1234567890123 as numeric(15,2)))
======================
  1524157875322755800955129.00
-- int * float returns float type
SELECT (1234567890123*CAST(1234567890123 AS FLOAT));
 (1234567890123* cast(1234567890123 as float))
===============================================
                                  1.524158e+024
-- int * double returns double type
SELECT (1234567890123*CAST(1234567890123 AS DOUBLE));
 (1234567890123* cast(1234567890123 as double))
================================================
                          1.524157875322756e+024
-- numeric * numeric returns numeric type
SELECT (CAST(1234567890123 AS NUMERIC(15,2))*CAST(1234567890123 AS NUMERIC(15,2)));
 ( cast(1234567890123 as numeric(15,2))* cast(1234567890123 as numeric(15,2)))
======================
  1524157875322755800955129.0000
-- numeric * float returns double type
SELECT (CAST(1234567890123 AS NUMERIC(15,2))*CAST(1234567890123 AS FLOAT));
 ( cast(1234567890123 as numeric(15,2))* cast(1234567890123 as float))
=======================================================================
                                                 1.524157954716582e+024
-- numeric * double returns double type
SELECT (CAST(1234567890123 AS NUMERIC(15,2))*CAST(1234567890123 AS DOUBLE));
 ( cast(1234567890123 as numeric(15,2))* cast(1234567890123 as double))
========================================================================
                                                  1.524157875322756e+024
-- float * float returns float type
SELECT (CAST(1234567890123 AS FLOAT)*CAST(1234567890123 AS FLOAT));
 ( cast(1234567890123 as float)* cast(1234567890123 as float))
===============================================================
                                                  1.524158e+024
-- float * double returns float type
SELECT (CAST(1234567890123 AS FLOAT)*CAST(1234567890123 AS DOUBLE));
 ( cast(1234567890123 as float)* cast(1234567890123 as double))
================================================================
                                          1.524157954716582e+024
-- double * double returns float type
SELECT (CAST(1234567890123 AS DOUBLE)*CAST(1234567890123 AS DOUBLE));
 ( cast(1234567890123 as double)* cast(1234567890123 as double))
=================================================================
                                           1.524157875322756e+024
-- int / int returns int type without type conversion or rounding
SELECT 100100/100000;
  100100/100000
===============
              1
-- int / int returns int type without type conversion or rounding
SELECT 100100/200200;
  100100/200200
===============
              0
-- int / zero returns error
SELECT 100100/(100100-100100);
ERROR: Attempt to divide by zero.

날짜/시간 데이터 타입의 산술 연산과 타입 변환

피연산자가 모두 날짜/시간 데이터 타입이면 뺄셈 연산이 가능하며, 리턴 값의 타입은 BIGINT 이다. 이때 피연산자의 타입에 따라 연산 단위가 다르므로 주의한다. 날짜/시간 데이터 타입과 정수는 덧셈 및 뺄셈 연산이 가능하며, 이때 연산 단위와 리턴 값의 타입은 날짜/시간 데이터 타입을 따른다.

아래는 피연산자의 타입별로 허용하는 연산과 연산 결과의 데이터 타입을 정리한 표이다.

피연산자의 타입별 허용 연산과 결과 데이터 타입

  TIME (초 단위) DATE (일 단위) TIMESTAMP (초 단위) DATETIME (밀리초 단위) INT
TIME 뺄셈만 허용. BIGINT X X X 덧셈, 뺄셈 허용. TIME
DATE X 뺄셈만 허용. BIGINT 뺄셈만 허용. BIGINT 뺄셈만 허용. BIGINT 덧셈, 뺄셈 허용. DATE
TIMESTAMP X 뺄셈만 허용. BIGINT 뺄셈만 허용. BIGINT 뺄셈만 허용. BIGINT 덧셈, 뺄셈 허용. TIMESTAMP
DATETIME X 뺄셈만 허용. BIGINT 뺄셈만 허용. BIGINT 뺄셈만 허용. BIGINT 덧셈, 뺄셈 허용. DATETIME
INT 덧셈, 뺄셈 허용 TIME 덧셈, 뺄셈 허용. DATE 덧셈, 뺄셈 허용. TIMESTAMP 덧셈, 뺄셈 허용. DATETIME 모든 산술 연산 허용

Note

날짜/시간 산술 연산의 인자 중 하나라도 NULL 이 포함되어 있으면 수식의 결과로 NULL 이 반환된다.

예제

-- initial systimestamp value
SELECT SYSDATETIME;
  SYSDATETIME
===============================
  07:09:52.115 PM 01/14/2010
-- time type + 10(seconds) returns time type
SELECT (CAST (SYSDATETIME AS TIME) + 10);
 ( cast( SYS_DATETIME  as time)+10)
====================================
  07:10:02 PM
-- date type + 10 (days) returns date type
SELECT (CAST (SYSDATETIME AS DATE) + 10);
 ( cast( SYS_DATETIME  as date)+10)
====================================
  01/24/2010
-- timestamp type + 10(seconds) returns timestamp type
SELECT (CAST (SYSDATETIME AS TIMESTAMP) + 10);
 ( cast( SYS_DATETIME  as timestamp)+10)
=========================================
  07:10:02 PM 01/14/2010
-- systimestamp type + 10(milliseconds) returns systimestamp type
SELECT (SYSDATETIME  + 10);
 ( SYS_DATETIME +10)
===============================
  07:09:52.125 PM 01/14/2010
SELECT DATETIME '09/01/2009 03:30:30.001 pm'- TIMESTAMP '08/31/2009 03:30:30 pm';
 datetime '09/01/2009 03:30:30.001 pm'-timestamp '08/31/2009 03:30:30 pm'
=======================================
  86400001
SELECT TIMESTAMP '09/01/2009 03:30:30 pm'- TIMESTAMP '08/31/2009 03:30:30 pm';
 timestamp '09/01/2009 03:30:30 pm'-timestamp '08/31/2009 03:30:30 pm'
=======================================
  86400

타임존 파라미터들과 관련된 동작

TIMESTAMP 및 TIMESTAMP WITH LOCAL TIME ZONE 데이터 타입은 내부적으로 UNIX epoch 값(1970년 이후 경과한 시간(초))으로 저장되며, 윤초를 사용(tz_leap_second_support가 yes로 설정, 타임존 파라미터 참고)할 경우 가상 날짜-시간 값을 포함할 수 있다.

Virtual date-time       Unix timestamp
2008-12-31 23:59:58  -> 79399951
2008-12-31 23:59:59  -> 79399952
2008-12-31 23:59:60  -> 79399953    -> not real date (introduced by leap second)
2009-01-01 00:00:00  -> 79399954
2009-01-01 00:00:01  -> 79399955

TIMESTAMP 및 TIMESTAMPLTZ 값이 포함된 산술 연산은 Unix epoch 값에서 바로 수행되며, 존재하지 않는 날짜/시간 값에 해당하는 Unix epoch 값이 허용된다.

SELECT TIMESTAMPLTZ'2008-12-31 23:59:59 UTC'=TIMESTAMPLTZ'2008-12-31 23:59:59 UTC'+1;
timestampltz '2008-12-31 23:59:59 UTC'=timestampltz '2008-12-31 23:59:59 UTC'+1
=================================================================================
                                                                            0

따라서 위의 비교는 Unix 타임스탬프 79399952와 79399953을 비교하는 것과 같지만 동일한 값이 TIMESTAMPTZ로 사용되면 다음과 같이 동일하다.

SELECT TIMESTAMPTZ'2008-12-31 23:59:59 UTC'=TIMESTAMPTZ'2008-12-31 23:59:59 UTC'+1;
timestamptz '2008-12-31 23:59:59 UTC'=timestamptz '2008-12-31 23:59:59 UTC'+1
===============================================================================
                                                                            1

화면에는 다음과 같은 불일치가 나타난다.

SELECT TIMESTAMPLTZ'2008-12-31 23:59:59 UTC'+1;
timestampltz '2008-12-31 23:59:59 UTC'+1
=============================================
11:59:59 PM 12/31/2008 Etc/UTC UTC

Unix 타임스탬프 값 79399953과 관련된 ‘2008-12-31 23:59:60 UTC는 실제 날짜가 아니기 때문에 바로 이전 값이 사용되지만 내부적으로는 ‘2008-12-31 23:59:60 UTC’ 값과 동일하다.

TIMESTAMP WITH TIME ZONE 데이터 타입은 UNIX 타임스탬프와 타임존 식별자를 모두 포함한다. UNIX 타임스탬프 부분 값에서도 TIMESTAMPTZ에 대한 연산을 수행할 수 있으나, 이 경우 자동 조정 연산이 이어서 수행된다. 지역, 오프셋, 서머타임 등의 타임존 식별자를 포함하려면 TIMESTAMPTZ 객체의 날짜-시간이 유효해야 한다. timestamptz’2008-12-31 23:59:59 UTC’+1의 경우 유효하지 않은 날짜-시간(79399953,UTC) 대신 ‘2008-12-31 23:59:59 UTC’에 해당하는 (79399952,UTC)로 자동 변환된다.

DATETIMETZ 및 TIMESTAMPTZ를 포함하는 산술 연산 후에는, CUBRID에서 다음과 관련된 결과 값의 자동 조정을 수행한다.
  • 타임존 식별자 조정: 타임존이 포함된 날짜에 시간(초)을 더하면 내부적으로 저장된 오프셋 규칙과 서머타임 규칙이 변경될 수 있으므로, 이에 따라 타임존 식별자를 갱신 한다.
  • Unix 타임스탬프 조정(TIMESTAMPTZ에만 해당): 가상의 날짜-시간 값(윤초가 활성화된 경우)이 항상 바로 이전 Unix 타임스탬프 값으로 변환된다.