PostgreSQL日期和时间函数

本章将详细介绍PostgreSQL中的日期和时间函数,包括当前时间获取、日期计算、格式化、时区处理等,帮助您高效处理时间相关的数据。

获取当前时间

基本时间函数

获取当前日期和时间:

SELECT 
    CURRENT_DATE AS current_date,                         -- 当前日期
    CURRENT_TIME AS current_time,                         -- 当前时间(含时区)
    CURRENT_TIMESTAMP AS current_timestamp,               -- 当前时间戳(含时区)
    NOW() AS now_function,                                -- 同CURRENT_TIMESTAMP
    LOCALTIME AS local_time,                              -- 本地时间(不含时区)
    LOCALTIMESTAMP AS local_timestamp;                    -- 本地时间戳(不含时区)

事务时间

获取事务开始时的时间:

SELECT 
    TRANSACTION_TIMESTAMP() AS transaction_time,          -- 事务开始时间
    STATEMENT_TIMESTAMP() AS statement_time,              -- 语句执行时间
    CLOCK_TIMESTAMP() AS clock_time;                      -- 实时时间(每次调用都不同)

日期时间构造

创建日期时间

从组件构造日期时间:

SELECT 
    DATE '2023-10-15' AS date_literal,                    -- 日期字面量
    TIME '14:30:00' AS time_literal,                      -- 时间字面量
    TIMESTAMP '2023-10-15 14:30:00' AS timestamp_literal, -- 时间戳字面量
    MAKE_DATE(2023, 10, 15) AS made_date,                 -- 构造日期
    MAKE_TIME(14, 30, 0) AS made_time,                    -- 构造时间
    MAKE_TIMESTAMP(2023, 10, 15, 14, 30, 0) AS made_timestamp; -- 构造时间戳

时区处理

处理不同时区的时间:

SELECT 
    TIMEZONE('UTC', CURRENT_TIMESTAMP) AS utc_time,       -- UTC时间
    TIMEZONE('Asia/Shanghai', CURRENT_TIMESTAMP) AS shanghai_time, -- 上海时间
    CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS utc_alt,      -- 另一种UTC表示
    CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Shanghai' AS shanghai_alt; -- 另一种上海时间表示

日期时间提取

提取日期部分

从日期时间中提取特定部分:

SELECT 
    EXTRACT(YEAR FROM CURRENT_DATE) AS current_year,      -- 提取年份
    EXTRACT(MONTH FROM CURRENT_DATE) AS current_month,    -- 提取月份
    EXTRACT(DAY FROM CURRENT_DATE) AS current_day,        -- 提取日期
    EXTRACT(DOW FROM CURRENT_DATE) AS day_of_week,        -- 星期几(0=周日)
    EXTRACT(DOY FROM CURRENT_DATE) AS day_of_year,        -- 年中第几天
    EXTRACT(HOUR FROM CURRENT_TIME) AS current_hour,      -- 提取小时
    EXTRACT(MINUTE FROM CURRENT_TIME) AS current_minute,  -- 提取分钟
    EXTRACT(SECOND FROM CURRENT_TIME) AS current_second;  -- 提取秒

日期部分函数

专门的日期部分提取函数:

SELECT 
    DATE_PART('year', CURRENT_DATE) AS year_part,         -- 年份
    DATE_PART('month', CURRENT_DATE) AS month_part,       -- 月份
    DATE_PART('day', CURRENT_DATE) AS day_part,           -- 日期
    DATE_TRUNC('month', CURRENT_DATE) AS truncated_month, -- 截断到月份
    DATE_TRUNC('hour', CURRENT_TIMESTAMP) AS truncated_hour; -- 截断到小时

日期时间计算

日期加减

对日期进行加减运算:

SELECT 
    CURRENT_DATE + INTERVAL '1 day' AS tomorrow,          -- 明天
    CURRENT_DATE - INTERVAL '1 week' AS last_week,        -- 一周前
    CURRENT_DATE + INTERVAL '3 months' AS three_months_later, -- 三个月后
    CURRENT_DATE - '2023-01-01'::DATE AS days_since_new_year; -- 距离元旦天数

年龄计算

计算年龄和时间间隔:

SELECT 
    AGE('1990-01-01'::DATE) AS age_interval,               -- 计算年龄
    AGE('2023-10-15'::DATE, '1990-01-01'::DATE) AS age_between, -- 计算两个日期间隔
    JUSTIFY_DAYS(INTERVAL '35 days') AS justified_days,   -- 规范化天数
    JUSTIFY_HOURS(INTERVAL '25 hours') AS justified_hours; -- 规范化小时

日期时间格式化

格式化输出

将日期时间格式化为字符串:

SELECT 
    TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD') AS formatted_date, -- 格式化日期
    TO_CHAR(CURRENT_TIMESTAMP, 'YYYY年MM月DD日 HH24:MI:SS') AS chinese_format, -- 中文格式
    TO_CHAR(CURRENT_TIMESTAMP, 'Day, Month DD, YYYY') AS english_format, -- 英文格式
    TO_CHAR(CURRENT_TIMESTAMP, 'DD/MM/YYYY HH12:MI:SS AM') AS us_format; -- 美式格式

字符串转日期

将字符串转换为日期时间:

SELECT 
    TO_DATE('2023-10-15', 'YYYY-MM-DD') AS parsed_date,   -- 解析日期字符串
    TO_TIMESTAMP('2023-10-15 14:30:00', 'YYYY-MM-DD HH24:MI:SS') AS parsed_timestamp, -- 解析时间戳
    TO_TIMESTAMP('15/10/2023 02:30:00 PM', 'DD/MM/YYYY HH12:MI:SS AM') AS parsed_us_timestamp; -- 解析美式时间戳

实际应用场景

时间范围查询

-- 查询今天的数据
SELECT * FROM orders 
WHERE DATE(order_date) = CURRENT_DATE;

-- 查询本周的数据
SELECT * FROM orders 
WHERE order_date >= DATE_TRUNC('week', CURRENT_DATE)
  AND order_date < DATE_TRUNC('week', CURRENT_DATE) + INTERVAL '1 week';

-- 查询本月的数据
SELECT * FROM orders 
WHERE DATE_TRUNC('month', order_date) = DATE_TRUNC('month', CURRENT_DATE);

-- 查询最近30天的数据
SELECT * FROM orders 
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days';

时间统计分析

-- 按小时统计订单数量
SELECT 
    EXTRACT(HOUR FROM order_date) AS order_hour,
    COUNT(*) AS order_count
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY EXTRACT(HOUR FROM order_date)
ORDER BY order_hour;

-- 按星期统计订单数量
SELECT 
    TO_CHAR(order_date, 'Day') AS day_of_week,
    COUNT(*) AS order_count
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY TO_CHAR(order_date, 'Day'), EXTRACT(DOW FROM order_date)
ORDER BY EXTRACT(DOW FROM order_date);

业务时间计算

-- 计算订单处理时间
SELECT 
    id,
    order_date,
    shipped_date,
    AGE(shipped_date, order_date) AS processing_time,
    EXTRACT(EPOCH FROM (shipped_date - order_date))/3600 AS processing_hours
FROM orders
WHERE shipped_date IS NOT NULL;

-- 计算用户注册时长
SELECT 
    username,
    created_at,
    AGE(CURRENT_DATE, DATE(created_at)) AS account_age,
    CASE 
        WHEN AGE(CURRENT_DATE, DATE(created_at)) > INTERVAL '1 year' THEN '资深用户'
        WHEN AGE(CURRENT_DATE, DATE(created_at)) > INTERVAL '6 months' THEN '活跃用户'
        ELSE '新用户'
    END AS user_category
FROM users;

时区处理

-- 存储UTC时间,按用户时区显示
SELECT 
    id,
    username,
    created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Shanghai' AS shanghai_time,
    created_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/New_York' AS new_york_time
FROM users;

-- 创建带时区的时间戳
CREATE TABLE events (
    id SERIAL PRIMARY KEY,
    event_name VARCHAR(255),
    event_time TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

性能优化

索引优化

为日期时间字段创建合适的索引:

-- 为日期字段创建索引
CREATE INDEX idx_orders_date ON orders (order_date);

-- 为日期范围查询创建索引
CREATE INDEX idx_orders_date_trunc ON orders (DATE_TRUNC('day', order_date));

-- 为时区转换创建函数索引
CREATE INDEX idx_orders_utc_time ON orders (order_date AT TIME ZONE 'UTC');

查询优化

优化日期时间相关的查询:

-- 好的查询(可以使用索引)
SELECT * FROM orders 
WHERE order_date >= '2023-10-01' AND order_date < '2023-11-01';

-- 不好的查询(可能无法使用索引)
SELECT * FROM orders 
WHERE EXTRACT(MONTH FROM order_date) = 10;

-- 优化后的查询
SELECT * FROM orders 
WHERE order_date >= DATE_TRUNC('month', CURRENT_DATE)
  AND order_date < DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month';

综合示例

-- 创建销售报告函数
CREATE OR REPLACE FUNCTION generate_monthly_sales_report(target_month DATE DEFAULT CURRENT_DATE)
RETURNS TABLE(
    report_period TEXT,
    total_orders BIGINT,
    total_sales NUMERIC(15,2),
    avg_order_value NUMERIC(10,2),
    daily_avg_orders NUMERIC(10,2),
    peak_day DATE,
    peak_day_orders BIGINT
) AS $$
DECLARE
    start_date DATE := DATE_TRUNC('month', target_month);
    end_date DATE := DATE_TRUNC('month', target_month) + INTERVAL '1 month';
BEGIN
    RETURN QUERY
    WITH daily_stats AS (
        SELECT 
            DATE(order_date) AS order_day,
            COUNT(*) AS daily_orders,
            SUM(total_amount) AS daily_sales
        FROM orders
        WHERE order_date >= start_date AND order_date < end_date
        GROUP BY DATE(order_date)
    ),
    peak_day_stats AS (
        SELECT 
            order_day,
            daily_orders
        FROM daily_stats
        ORDER BY daily_orders DESC
        LIMIT 1
    )
    SELECT 
        TO_CHAR(start_date, 'YYYY年MM月') AS report_period,
        COUNT(*) AS total_orders,
        SUM(total_amount) AS total_sales,
        ROUND(AVG(total_amount), 2) AS avg_order_value,
        ROUND(AVG(daily_orders), 2) AS daily_avg_orders,
        COALESCE((SELECT order_day FROM peak_day_stats), start_date) AS peak_day,
        COALESCE((SELECT daily_orders FROM peak_day_stats), 0) AS peak_day_orders
    FROM orders o
    LEFT JOIN daily_stats ds ON DATE(o.order_date) = ds.order_day
    WHERE order_date >= start_date AND order_date < end_date;
END;
$$ LANGUAGE plpgsql;

-- 使用函数生成报告
SELECT * FROM generate_monthly_sales_report('2023-10-01');

提示:在处理日期时间数据时,建议统一使用TIMESTAMPTZ(带时区的时间戳)类型,并在应用层进行时区转换,这样可以避免时区相关的错误。