[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7185DFJGRCEPKP2XS4VC884":3,"skill-same:01M7185DFJGRCEPKP2XS4VC884":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":14,"repositoryHtmlUrl":58,"repositoryHomepageUrl":6,"ownerHtmlUrl":59,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":60,"install":61,"resources":74},"01M7185DFJGRCEPKP2XS4VC884","cc-skill-clickhouse-io",null,"ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads.","sapsapshen\u002Fai-memory-router","sapsapshen","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F20347959?v=4","01M6XDQEF1DP50DKF37H81J3J5","shenmian","01M6XDQEJPE41Y3DT2763X8ZJN",0,"MIT","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fcc-skill-clickhouse-io\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000013","分析与可视化","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"clickhouse","io","cc skill clickhouse io","clickhouse io","cc","community","2026","unknown","for","high-performance","analytical","query","patterns","performance","query optimization","data","Optimization","data engineering","database","analytics","2026-11-10 10:12:30",false,"---\nname: cc-skill-clickhouse-io\ndescription: \"ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads.\"\nrisk: unknown\nsource: community\ndate_added: \"2026-02-27\"\n---\n\n# ClickHouse Analytics Patterns\n\nClickHouse-specific patterns for high-performance analytics and data engineering.\n\n## Overview\n\nClickHouse is a column-oriented database management system (DBMS) for online analytical processing (OLAP). It's optimized for fast analytical queries on large datasets.\n\n**Key Features:**\n- Column-oriented storage\n- Data compression\n- Parallel query execution\n- Distributed queries\n- Real-time analytics\n\n## Table Design Patterns\n\n### MergeTree Engine (Most Common)\n\n```sql\nCREATE TABLE markets_analytics (\n    date Date,\n    market_id String,\n    market_name String,\n    volume UInt64,\n    trades UInt32,\n    unique_traders UInt32,\n    avg_trade_size Float64,\n    created_at DateTime\n) ENGINE = MergeTree()\nPARTITION BY toYYYYMM(date)\nORDER BY (date, market_id)\nSETTINGS index_granularity = 8192;\n```\n\n### ReplacingMergeTree (Deduplication)\n\n```sql\n-- For data that may have duplicates (e.g., from multiple sources)\nCREATE TABLE user_events (\n    event_id String,\n    user_id String,\n    event_type String,\n    timestamp DateTime,\n    properties String\n) ENGINE = ReplacingMergeTree()\nPARTITION BY toYYYYMM(timestamp)\nORDER BY (user_id, event_id, timestamp)\nPRIMARY KEY (user_id, event_id);\n```\n\n### AggregatingMergeTree (Pre-aggregation)\n\n```sql\n-- For maintaining aggregated metrics\nCREATE TABLE market_stats_hourly (\n    hour DateTime,\n    market_id String,\n    total_volume AggregateFunction(sum, UInt64),\n    total_trades AggregateFunction(count, UInt32),\n    unique_users AggregateFunction(uniq, String)\n) ENGINE = AggregatingMergeTree()\nPARTITION BY toYYYYMM(hour)\nORDER BY (hour, market_id);\n\n-- Query aggregated data\nSELECT\n    hour,\n    market_id,\n    sumMerge(total_volume) AS volume,\n    countMerge(total_trades) AS trades,\n    uniqMerge(unique_users) AS users\nFROM market_stats_hourly\nWHERE hour >= toStartOfHour(now() - INTERVAL 24 HOUR)\nGROUP BY hour, market_id\nORDER BY hour DESC;\n```\n\n## Query Optimization Patterns\n\n### Efficient Filtering\n\n```sql\n-- ✅ GOOD: Use indexed columns first\nSELECT *\nFROM markets_analytics\nWHERE date >= '2025-01-01'\n  AND market_id = 'market-123'\n  AND volume > 1000\nORDER BY date DESC\nLIMIT 100;\n\n-- ❌ BAD: Filter on non-indexed columns first\nSELECT *\nFROM markets_analytics\nWHERE volume > 1000\n  AND market_name LIKE '%election%'\n  AND date >= '2025-01-01';\n```\n\n### Aggregations\n\n```sql\n-- ✅ GOOD: Use ClickHouse-specific aggregation functions\nSELECT\n    toStartOfDay(created_at) AS day,\n    market_id,\n    sum(volume) AS total_volume,\n    count() AS total_trades,\n    uniq(trader_id) AS unique_traders,\n    avg(trade_size) AS avg_size\nFROM trades\nWHERE created_at >= today() - INTERVAL 7 DAY\nGROUP BY day, market_id\nORDER BY day DESC, total_volume DESC;\n\n-- ✅ Use quantile for percentiles (more efficient than percentile)\nSELECT\n    quantile(0.50)(trade_size) AS median,\n    quantile(0.95)(trade_size) AS p95,\n    quantile(0.99)(trade_size) AS p99\nFROM trades\nWHERE created_at >= now() - INTERVAL 1 HOUR;\n```\n\n### Window Functions\n\n```sql\n-- Calculate running totals\nSELECT\n    date,\n    market_id,\n    volume,\n    sum(volume) OVER (\n        PARTITION BY market_id\n        ORDER BY date\n        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n    ) AS cumulative_volume\nFROM markets_analytics\nWHERE date >= today() - INTERVAL 30 DAY\nORDER BY market_id, date;\n```\n\n## Data Insertion Patterns\n\n### Bulk Insert (Recommended)\n\n```typescript\nimport { ClickHouse } from 'clickhouse'\n\nconst clickhouse = new ClickHouse({\n  url: process.env.CLICKHOUSE_URL,\n  port: 8123,\n  basicAuth: {\n    username: process.env.CLICKHOUSE_USER,\n    password: process.env.CLICKHOUSE_PASSWORD\n  }\n})\n\n\u002F\u002F ✅ Batch insert (efficient)\nasync function bulkInsertTrades(trades: Trade[]) {\n  const values = trades.map(trade => `(\n    '${trade.id}',\n    '${trade.market_id}',\n    '${trade.user_id}',\n    ${trade.amount},\n    '${trade.timestamp.toISOString()}'\n  )`).join(',')\n\n  await clickhouse.query(`\n    INSERT INTO trades (id, market_id, user_id, amount, timestamp)\n    VALUES ${values}\n  `).toPromise()\n}\n\n\u002F\u002F ❌ Individual inserts (slow)\nasync function insertTrade(trade: Trade) {\n  \u002F\u002F Don't do this in a loop!\n  await clickhouse.query(`\n    INSERT INTO trades VALUES ('${trade.id}', ...)\n  `).toPromise()\n}\n```\n\n### Streaming Insert\n\n```typescript\n\u002F\u002F For continuous data ingestion\nimport { createWriteStream } from 'fs'\nimport { pipeline } from 'stream\u002Fpromises'\n\nasync function streamInserts() {\n  const stream = clickhouse.insert('trades').stream()\n\n  for await (const batch of dataSource) {\n    stream.write(batch)\n  }\n\n  await stream.end()\n}\n```\n\n## Materialized Views\n\n### Real-time Aggregations\n\n```sql\n-- Create materialized view for hourly stats\nCREATE MATERIALIZED VIEW market_stats_hourly_mv\nTO market_stats_hourly\nAS SELECT\n    toStartOfHour(timestamp) AS hour,\n    market_id,\n    sumState(amount) AS total_volume,\n    countState() AS total_trades,\n    uniqState(user_id) AS unique_users\nFROM trades\nGROUP BY hour, market_id;\n\n-- Query the materialized view\nSELECT\n    hour,\n    market_id,\n    sumMerge(total_volume) AS volume,\n    countMerge(total_trades) AS trades,\n    uniqMerge(unique_users) AS users\nFROM market_stats_hourly\nWHERE hour >= now() - INTERVAL 24 HOUR\nGROUP BY hour, market_id;\n```\n\n## Performance Monitoring\n\n### Query Performance\n\n```sql\n-- Check slow queries\nSELECT\n    query_id,\n    user,\n    query,\n    query_duration_ms,\n    read_rows,\n    read_bytes,\n    memory_usage\nFROM system.query_log\nWHERE type = 'QueryFinish'\n  AND query_duration_ms > 1000\n  AND event_time >= now() - INTERVAL 1 HOUR\nORDER BY query_duration_ms DESC\nLIMIT 10;\n```\n\n### Table Statistics\n\n```sql\n-- Check table sizes\nSELECT\n    database,\n    table,\n    formatReadableSize(sum(bytes)) AS size,\n    sum(rows) AS rows,\n    max(modification_time) AS latest_modification\nFROM system.parts\nWHERE active\nGROUP BY database, table\nORDER BY sum(bytes) DESC;\n```\n\n## Common Analytics Queries\n\n### Time Series Analysis\n\n```sql\n-- Daily active users\nSELECT\n    toDate(timestamp) AS date,\n    uniq(user_id) AS daily_active_users\nFROM events\nWHERE timestamp >= today() - INTERVAL 30 DAY\nGROUP BY date\nORDER BY date;\n\n-- Retention analysis\nSELECT\n    signup_date,\n    countIf(days_since_signup = 0) AS day_0,\n    countIf(days_since_signup = 1) AS day_1,\n    countIf(days_since_signup = 7) AS day_7,\n    countIf(days_since_signup = 30) AS day_30\nFROM (\n    SELECT\n        user_id,\n        min(toDate(timestamp)) AS signup_date,\n        toDate(timestamp) AS activity_date,\n        dateDiff('day', signup_date, activity_date) AS days_since_signup\n    FROM events\n    GROUP BY user_id, activity_date\n)\nGROUP BY signup_date\nORDER BY signup_date DESC;\n```\n\n### Funnel Analysis\n\n```sql\n-- Conversion funnel\nSELECT\n    countIf(step = 'viewed_market') AS viewed,\n    countIf(step = 'clicked_trade') AS clicked,\n    countIf(step = 'completed_trade') AS completed,\n    round(clicked \u002F viewed * 100, 2) AS view_to_click_rate,\n    round(completed \u002F clicked * 100, 2) AS click_to_completion_rate\nFROM (\n    SELECT\n        user_id,\n        session_id,\n        event_type AS step\n    FROM events\n    WHERE event_date = today()\n)\nGROUP BY session_id;\n```\n\n### Cohort Analysis\n\n```sql\n-- User cohorts by signup month\nSELECT\n    toStartOfMonth(signup_date) AS cohort,\n    toStartOfMonth(activity_date) AS month,\n    dateDiff('month', cohort, month) AS months_since_signup,\n    count(DISTINCT user_id) AS active_users\nFROM (\n    SELECT\n        user_id,\n        min(toDate(timestamp)) OVER (PARTITION BY user_id) AS signup_date,\n        toDate(timestamp) AS activity_date\n    FROM events\n)\nGROUP BY cohort, month, months_since_signup\nORDER BY cohort, months_since_signup;\n```\n\n## Data Pipeline Patterns\n\n### ETL Pattern\n\n```typescript\n\u002F\u002F Extract, Transform, Load\nasync function etlPipeline() {\n  \u002F\u002F 1. Extract from source\n  const rawData = await extractFromPostgres()\n\n  \u002F\u002F 2. Transform\n  const transformed = rawData.map(row => ({\n    date: new Date(row.created_at).toISOString().split('T')[0],\n    market_id: row.market_slug,\n    volume: parseFloat(row.total_volume),\n    trades: parseInt(row.trade_count)\n  }))\n\n  \u002F\u002F 3. Load to ClickHouse\n  await bulkInsertToClickHouse(transformed)\n}\n\n\u002F\u002F Run periodically\nsetInterval(etlPipeline, 60 * 60 * 1000)  \u002F\u002F Every hour\n```\n\n### Change Data Capture (CDC)\n\n```typescript\n\u002F\u002F Listen to PostgreSQL changes and sync to ClickHouse\nimport { Client } from 'pg'\n\nconst pgClient = new Client({ connectionString: process.env.DATABASE_URL })\n\npgClient.query('LISTEN market_updates')\n\npgClient.on('notification', async (msg) => {\n  const update = JSON.parse(msg.payload)\n\n  await clickhouse.insert('market_updates', [\n    {\n      market_id: update.id,\n      event_type: update.operation,  \u002F\u002F INSERT, UPDATE, DELETE\n      timestamp: new Date(),\n      data: JSON.stringify(update.new_data)\n    }\n  ])\n})\n```\n\n## Best Practices\n\n### 1. Partitioning Strategy\n- Partition by time (usually month or day)\n- Avoid too many partitions (performance impact)\n- Use DATE type for partition key\n\n### 2. Ordering Key\n- Put most frequently filtered columns first\n- Consider cardinality (high cardinality first)\n- Order impacts compression\n\n### 3. Data Types\n- Use smallest appropriate type (UInt32 vs UInt64)\n- Use LowCardinality for repeated strings\n- Use Enum for categorical data\n\n### 4. Avoid\n- SELECT * (specify columns)\n- FINAL (merge data before query instead)\n- Too many JOINs (denormalize for analytics)\n- Small frequent inserts (batch instead)\n\n### 5. Monitoring\n- Track query performance\n- Monitor disk usage\n- Check merge operations\n- Review slow query log\n\n**Remember**: ClickHouse excels at analytical workloads. Design tables for your query patterns, batch inserts, and leverage materialized views for real-time aggregations.\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n","# ClickHouse Analytics Patterns\n\nClickHouse-specific patterns for high-performance analytics and data engineering.\n\n## Overview\n\nClickHouse is a column-oriented database management system (DBMS) for online analytical processing (OLAP). It's optimized for fast analytical queries on large datasets.\n\n**Key Features:**\n- Column-oriented storage\n- Data compression\n- Parallel query execution\n- Distributed queries\n- Real-time analytics\n\n## Table Design Patterns\n\n### MergeTree Engine (Most Common)\n\n```sql\nCREATE TABLE markets_analytics (\n    date Date,\n    market_id String,\n    market_name String,\n    volume UInt64,\n    trades UInt32,\n    unique_traders UInt32,\n    avg_trade_size Float64,\n    created_at DateTime\n) ENGINE = MergeTree()\nPARTITION BY toYYYYMM(date)\nORDER BY (date, market_id)\nSETTINGS index_granularity = 8192;\n```\n\n### ReplacingMergeTree (Deduplication)\n\n```sql\n-- For data that may have duplicates (e.g., from multiple sources)\nCREATE TABLE user_events (\n    event_id String,\n    user_id String,\n    event_type String,\n    timestamp DateTime,\n    properties String\n) ENGINE = ReplacingMergeTree()\nPARTITION BY toYYYYMM(timestamp)\nORDER BY (user_id, event_id, timestamp)\nPRIMARY KEY (user_id, event_id);\n```\n\n### AggregatingMergeTree (Pre-aggregation)\n\n```sql\n-- For maintaining aggregated metrics\nCREATE TABLE market_stats_hourly (\n    hour DateTime,\n    market_id String,\n    total_volume AggregateFunction(sum, UInt64),\n    total_trades AggregateFunction(count, UInt32),\n    unique_users AggregateFunction(uniq, String)\n) ENGINE = AggregatingMergeTree()\nPARTITION BY toYYYYMM(hour)\nORDER BY (hour, market_id);\n\n-- Query aggregated data\nSELECT\n    hour,\n    market_id,\n    sumMerge(total_volume) AS volume,\n    countMerge(total_trades) AS trades,\n    uniqMerge(unique_users) AS users\nFROM market_stats_hourly\nWHERE hour >= toStartOfHour(now() - INTERVAL 24 HOUR)\nGROUP BY hour, market_id\nORDER BY hour DESC;\n```\n\n## Query Optimization Patterns\n\n### Efficient Filtering\n\n```sql\n-- ✅ GOOD: Use indexed columns first\nSELECT *\nFROM markets_analytics\nWHERE date >= '2025-01-01'\n  AND market_id = 'market-123'\n  AND volume > 1000\nORDER BY date DESC\nLIMIT 100;\n\n-- ❌ BAD: Filter on non-indexed columns first\nSELECT *\nFROM markets_analytics\nWHERE volume > 1000\n  AND market_name LIKE '%election%'\n  AND date >= '2025-01-01';\n```\n\n### Aggregations\n\n```sql\n-- ✅ GOOD: Use ClickHouse-specific aggregation functions\nSELECT\n    toStartOfDay(created_at) AS day,\n    market_id,\n    sum(volume) AS total_volume,\n    count() AS total_trades,\n    uniq(trader_id) AS unique_traders,\n    avg(trade_size) AS avg_size\nFROM trades\nWHERE created_at >= today() - INTERVAL 7 DAY\nGROUP BY day, market_id\nORDER BY day DESC, total_volume DESC;\n\n-- ✅ Use quantile for percentiles (more efficient than percentile)\nSELECT\n    quantile(0.50)(trade_size) AS median,\n    quantile(0.95)(trade_size) AS p95,\n    quantile(0.99)(trade_size) AS p99\nFROM trades\nWHERE created_at >= now() - INTERVAL 1 HOUR;\n```\n\n### Window Functions\n\n```sql\n-- Calculate running totals\nSELECT\n    date,\n    market_id,\n    volume,\n    sum(volume) OVER (\n        PARTITION BY market_id\n        ORDER BY date\n        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n    ) AS cumulative_volume\nFROM markets_analytics\nWHERE date >= today() - INTERVAL 30 DAY\nORDER BY market_id, date;\n```\n\n## Data Insertion Patterns\n\n### Bulk Insert (Recommended)\n\n```typescript\nimport { ClickHouse } from 'clickhouse'\n\nconst clickhouse = new ClickHouse({\n  url: process.env.CLICKHOUSE_URL,\n  port: 8123,\n  basicAuth: {\n    username: process.env.CLICKHOUSE_USER,\n    password: process.env.CLICKHOUSE_PASSWORD\n  }\n})\n\n\u002F\u002F ✅ Batch insert (efficient)\nasync function bulkInsertTrades(trades: Trade[]) {\n  const values = trades.map(trade => `(\n    '${trade.id}',\n    '${trade.market_id}',\n    '${trade.user_id}',\n    ${trade.amount},\n    '${trade.timestamp.toISOString()}'\n  )`).join(',')\n\n  await clickhouse.query(`\n    INSERT INTO trades (id, market_id, user_id, amount, timestamp)\n    VALUES ${values}\n  `).toPromise()\n}\n\n\u002F\u002F ❌ Individual inserts (slow)\nasync function insertTrade(trade: Trade) {\n  \u002F\u002F Don't do this in a loop!\n  await clickhouse.query(`\n    INSERT INTO trades VALUES ('${trade.id}', ...)\n  `).toPromise()\n}\n```\n\n### Streaming Insert\n\n```typescript\n\u002F\u002F For continuous data ingestion\nimport { createWriteStream } from 'fs'\nimport { pipeline } from 'stream\u002Fpromises'\n\nasync function streamInserts() {\n  const stream = clickhouse.insert('trades').stream()\n\n  for await (const batch of dataSource) {\n    stream.write(batch)\n  }\n\n  await stream.end()\n}\n```\n\n## Materialized Views\n\n### Real-time Aggregations\n\n```sql\n-- Create materialized view for hourly stats\nCREATE MATERIALIZED VIEW market_stats_hourly_mv\nTO market_stats_hourly\nAS SELECT\n    toStartOfHour(timestamp) AS hour,\n    market_id,\n    sumState(amount) AS total_volume,\n    countState() AS total_trades,\n    uniqState(user_id) AS unique_users\nFROM trades\nGROUP BY hour, market_id;\n\n-- Query the materialized view\nSELECT\n    hour,\n    market_id,\n    sumMerge(total_volume) AS volume,\n    countMerge(total_trades) AS trades,\n    uniqMerge(unique_users) AS users\nFROM market_stats_hourly\nWHERE hour >= now() - INTERVAL 24 HOUR\nGROUP BY hour, market_id;\n```\n\n## Performance Monitoring\n\n### Query Performance\n\n```sql\n-- Check slow queries\nSELECT\n    query_id,\n    user,\n    query,\n    query_duration_ms,\n    read_rows,\n    read_bytes,\n    memory_usage\nFROM system.query_log\nWHERE type = 'QueryFinish'\n  AND query_duration_ms > 1000\n  AND event_time >= now() - INTERVAL 1 HOUR\nORDER BY query_duration_ms DESC\nLIMIT 10;\n```\n\n### Table Statistics\n\n```sql\n-- Check table sizes\nSELECT\n    database,\n    table,\n    formatReadableSize(sum(bytes)) AS size,\n    sum(rows) AS rows,\n    max(modification_time) AS latest_modification\nFROM system.parts\nWHERE active\nGROUP BY database, table\nORDER BY sum(bytes) DESC;\n```\n\n## Common Analytics Queries\n\n### Time Series Analysis\n\n```sql\n-- Daily active users\nSELECT\n    toDate(timestamp) AS date,\n    uniq(user_id) AS daily_active_users\nFROM events\nWHERE timestamp >= today() - INTERVAL 30 DAY\nGROUP BY date\nORDER BY date;\n\n-- Retention analysis\nSELECT\n    signup_date,\n    countIf(days_since_signup = 0) AS day_0,\n    countIf(days_since_signup = 1) AS day_1,\n    countIf(days_since_signup = 7) AS day_7,\n    countIf(days_since_signup = 30) AS day_30\nFROM (\n    SELECT\n        user_id,\n        min(toDate(timestamp)) AS signup_date,\n        toDate(timestamp) AS activity_date,\n        dateDiff('day', signup_date, activity_date) AS days_since_signup\n    FROM events\n    GROUP BY user_id, activity_date\n)\nGROUP BY signup_date\nORDER BY signup_date DESC;\n```\n\n### Funnel Analysis\n\n```sql\n-- Conversion funnel\nSELECT\n    countIf(step = 'viewed_market') AS viewed,\n    countIf(step = 'clicked_trade') AS clicked,\n    countIf(step = 'completed_trade') AS completed,\n    round(clicked \u002F viewed * 100, 2) AS view_to_click_rate,\n    round(completed \u002F clicked * 100, 2) AS click_to_completion_rate\nFROM (\n    SELECT\n        user_id,\n        session_id,\n        event_type AS step\n    FROM events\n    WHERE event_date = today()\n)\nGROUP BY session_id;\n```\n\n### Cohort Analysis\n\n```sql\n-- User cohorts by signup month\nSELECT\n    toStartOfMonth(signup_date) AS cohort,\n    toStartOfMonth(activity_date) AS month,\n    dateDiff('month', cohort, month) AS months_since_signup,\n    count(DISTINCT user_id) AS active_users\nFROM (\n    SELECT\n        user_id,\n        min(toDate(timestamp)) OVER (PARTITION BY user_id) AS signup_date,\n        toDate(timestamp) AS activity_date\n    FROM events\n)\nGROUP BY cohort, month, months_since_signup\nORDER BY cohort, months_since_signup;\n```\n\n## Data Pipeline Patterns\n\n### ETL Pattern\n\n```typescript\n\u002F\u002F Extract, Transform, Load\nasync function etlPipeline() {\n  \u002F\u002F 1. Extract from source\n  const rawData = await extractFromPostgres()\n\n  \u002F\u002F 2. Transform\n  const transformed = rawData.map(row => ({\n    date: new Date(row.created_at).toISOString().split('T')[0],\n    market_id: row.market_slug,\n    volume: parseFloat(row.total_volume),\n    trades: parseInt(row.trade_count)\n  }))\n\n  \u002F\u002F 3. Load to ClickHouse\n  await bulkInsertToClickHouse(transformed)\n}\n\n\u002F\u002F Run periodically\nsetInterval(etlPipeline, 60 * 60 * 1000)  \u002F\u002F Every hour\n```\n\n### Change Data Capture (CDC)\n\n```typescript\n\u002F\u002F Listen to PostgreSQL changes and sync to ClickHouse\nimport { Client } from 'pg'\n\nconst pgClient = new Client({ connectionString: process.env.DATABASE_URL })\n\npgClient.query('LISTEN market_updates')\n\npgClient.on('notification', async (msg) => {\n  const update = JSON.parse(msg.payload)\n\n  await clickhouse.insert('market_updates', [\n    {\n      market_id: update.id,\n      event_type: update.operation,  \u002F\u002F INSERT, UPDATE, DELETE\n      timestamp: new Date(),\n      data: JSON.stringify(update.new_data)\n    }\n  ])\n})\n```\n\n## Best Practices\n\n### 1. Partitioning Strategy\n- Partition by time (usually month or day)\n- Avoid too many partitions (performance impact)\n- Use DATE type for partition key\n\n### 2. Ordering Key\n- Put most frequently filtered columns first\n- Consider cardinality (high cardinality first)\n- Order impacts compression\n\n### 3. Data Types\n- Use smallest appropriate type (UInt32 vs UInt64)\n- Use LowCardinality for repeated strings\n- Use Enum for categorical data\n\n### 4. Avoid\n- SELECT * (specify columns)\n- FINAL (merge data before query instead)\n- Too many JOINs (denormalize for analytics)\n- Small frequent inserts (batch instead)\n\n### 5. Monitoring\n- Track query performance\n- Monitor disk usage\n- Check merge operations\n- Review slow query log\n\n**Remember**: ClickHouse excels at analytical workloads. Design tables for your query patterns, batch inserts, and leverage materialized views for real-time aggregations.\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n",{"name":5,"risk":36,"source":34,"date_added":54,"description":7},"2026-02-27","main","7a38d8e7073e61c16eda85681cb32fb68a8657a5","skills-store\u002Fglobal\u002Fcc-skill-clickhouse-io","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router","https:\u002F\u002Fgithub.com\u002Fsapsapshen","2026-04-14 21:12:12",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add sapsapshen\u002Fai-memory-router --skill cc-skill-clickhouse-io",{"tool":67,"command":68},"bunx","bunx skills add sapsapshen\u002Fai-memory-router --skill cc-skill-clickhouse-io",{"tool":70,"command":71},"pnpm","pnpm dlx skills add sapsapshen\u002Fai-memory-router --skill cc-skill-clickhouse-io","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fcc-skill-clickhouse-io\u002FSKILL.md",true,[],[76,98,123,133,146,163],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":85,"publishedAt":49},"01M71ATQRKG48SH06FQ697T5G9","wechat-search","Search WeChat Official Account articles using OpenClaw's web search and fetch capabilities with compliance-focused design.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fwechat-search-release\u002FSKILL.md",[82],{"categoryNo":83,"categoryName":84,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000037","律师与法务",[86,87,88,89,90,91,92,93,94,95,96,97],"wechat search","wechat","search","capabilities","openclaw","design","web search","compliance","web","official","fetch","with",{"skillNo":99,"name":100,"nameZh":6,"description":101,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":102,"categories":103,"keywords":104,"publishedAt":49},"01M71ATNAVGZB35PFYM2GC5N00","trend-tap","Real-time trending topics aggregator across 7 platforms (X\u002FTwitter, Reddit, Google Trends, Hacker News, Zhihu, Bilibili, Weibo). Trigger: when user says 'trend', 'trends', 'trend tap', '热点', '热搜', or asks what is trending\u002Fhot. Do NOT use web_search — this skill already fetches live data from all platforms with zero API keys.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftrend-tap\u002FSKILL.md",[],[105,106,107,108,109,110,88,111,112,113,114,115,116,117,44,118,119,120,121,122],"trend","trend tap","tap","api keys","Bash","time","trending","hacker","aggregator","do","when","brew","live","google","install","read","across","api",{"skillNo":124,"name":125,"nameZh":6,"description":126,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":127,"categories":128,"keywords":129,"publishedAt":49},"01M71ATJ9AM5KM15AA8PS302EB","trend-radar","Real-time trending topics aggregator across 7 platforms (X\u002FTwitter, Reddit, Google Trends, Hacker News, Zhihu, Bilibili, Weibo). Trigger: when user says 'trend', 'trends', 'trend pulse', '热点', '热搜', or asks what is trending\u002Fhot. Do NOT use web_search — this skill already fetches live data from all platforms with zero API keys.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftrend-radar\u002FSKILL.md",[],[130,131,105,116,117,44,118,109,88,120,132,121,122,115,113,112,111,119,110,108],"trend radar","radar","pulse",{"skillNo":134,"name":135,"nameZh":6,"description":136,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":137,"categories":138,"keywords":139,"publishedAt":49},"01M71ATGF095N3EJM9G8SD0BB5","tech-news-daily","每日科技新闻汇——从全球多个 RSS 源抓取最新科技新闻，翻译（英→中）、去重、排版成每日简报。当用户询问科技新闻、科技新鲜事、每日科技、技术动态、Daily Tech News 或类似表达时触发此技能。","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftech-news-daily\u002FSKILL.md",[],[140,141,142,143,144,145],"news","tech","tech news daily","daily","rss","翻译",{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":150,"categories":151,"keywords":155,"publishedAt":49},"01M71ATFAEAMQ17M70NQNY7SR8","summarize","Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube).","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fsummarize\u002FSKILL.md",[152],{"categoryNo":153,"categoryName":154,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000021","资料检索与研究",[148,94,156,157,158,97,116,159,160,161,107,162,119],"Audio","cli","youtube","images","https","files","pdfs",{"skillNo":164,"name":165,"nameZh":6,"description":166,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":167,"categories":168,"keywords":169,"publishedAt":49},"01M71ATDNS3R0M4H936PACQAWZ","sonoscli","Control Sonos speakers (discover\u002Fstatus\u002Fplay\u002Fvolume\u002Fgroup).","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fsonoscli\u002FSKILL.md",[],[165,170,171,172,119,173,160,174,175,176,177,178],"status","play","go","latest","github","discover","control","cmd","volume"]