如何让Golang更快地进入Postgres查询?任何具体的替代方案?

时间:2017-12-21 01:49:47

标签: postgresql go

我正在使用Golang和Postgres来过滤一些财务数据。我有一个Postgres数据库,它有一个包含单个股票市场的表(如果这是正确的术语)。此表包含id,symbol,date,open,high,low,close和volume的列。行总数为6,610,598,不同股票(符号)的数量为2174。

现在我想要做的是过滤该表中的数据,然后保存到另一个表中。所以第一个包含原始数据,第二个包含已清理的数据。

我们有三个参数,一个日期(EVALDATE)和两个整数(MINCTD& MINDP)。首先,我们必须只选择那些将通过我们最小日历交易日参数的股票。因此将选择(注意:我们使用golang)

symbols []string got its value from ( Select distinct symbol from table_name; )
[]filteredSymbols
var symbol, date string
var open, high, low, close float64
var volume int
for _, symbol := range symbols {
    var count int
    query := fmt.Sprintf("Select count(*) from table_name where symbol = '%s' and date >= '%s';", symbol, EVALDATE)
    row := db.QueryRow(query)
    if err := row.Scan(&count); err != nil ........
    if count >= MINCTD
        filteredSymbols = append(filteredSymbols, symbol)
}

基本上,上面的操作只询问从EVALDATE到当前日期(数据中的最新日期)有足够行数的符号,这些符号将满足MINCTD。上述操作耗时 30分钟

如果符号满足上面的第一个过滤器,它将经过第二个过滤器,它将测试在该时间段内(EVALDATE到LATEST_DATE)是否有足够的行包含完整数据(没有值的OHLC)。因此,下面的查询用于过滤通过上述过滤器的符号:

Select count(*) from table_name where symbol='symbol' and date>= 'EVALDATE' and open != 0 and high != 0 and low != 0 and close != 0;

此查询 36分钟

在获得通过两个过滤器的符号片段后,我将使用postgres查询再次获取其数据,然后开始批量插入另一个表。

所以 1小时6分钟是不可接受的。那我该怎么办?抓取所有数据,然后在内存中使用Golang进行过滤?

2 个答案:

答案 0 :(得分:1)

我从问题中注意到了一些事情。

尽量避免扫描600万行以达到2174个值(即避免Select distinct symbol from table_name;)。你没有(或者你可以建立)一个主表"带有符号主键的符号?

合并您的查询以测试数据,如下所示:

select
       count(*) c1
     , count(case when open != 0 and high != 0 and low != 0 and close != 0 then 1 end) as c2
from table_name 
where symbol='symbol' 
and date>= 'EVALDATE' 

(符号,日期)的索引有助于提高绩效。

答案 1 :(得分:0)

在Go中,在28.7秒内为3,142个符号清理7,914,698行,这比2,174个符号的6,610,598行优于3,960秒(1小时6分钟)。

输出:

$ go run clean.go
clean: 7914698 rows 28.679295705s

$ psql
psql (9.6.6)

peter=# \d clean
         Table "public.clean"
 Column |       Type       | Modifiers 
--------+------------------+-----------
 id     | integer          | 
 symbol | text             | not null
 date   | date             | not null
 close  | double precision | 
 volume | integer          | 
 open   | double precision | 
 high   | double precision | 
 low    | double precision | 
Indexes:
    "clean_pkey" PRIMARY KEY, btree (symbol, date)

peter=# SELECT COUNT(*) FROM clean;
  count  
---------
 7914698

peter=# SELECT COUNT(DISTINCT symbol) FROM clean;
 count 
-------
  3142

peter=# \q
$

clean.go

package main

import (
    "database/sql"
    "fmt"
    "strconv"
    "time"

    _ "github.com/lib/pq"
)

func clean(db *sql.DB, EVALDATE time.Time, MINCTD, MINDP int) (int64, time.Duration, error) {
    start := time.Now()

    tx, err := db.Begin()
    if err != nil {
        return 0, 0, err
    }
    committed := false
    defer func() {
        if !committed {
            tx.Rollback()
        }
    }()

    {
        const query = `DROP TABLE IF EXISTS clean;`
        if _, err := tx.Exec(query); err != nil {
            return 0, 0, err
        }
    }

    var nRows int64
    {
        const query = `
            CREATE TABLE clean AS
                SELECT id, symbol, date, close, volume, open, high, low 
                FROM unclean 
                WHERE symbol IN (
                    SELECT symbol
                    FROM unclean
                    WHERE date >= $1
                    GROUP BY symbol
                    HAVING 
                        COUNT(*) >= $2
                        AND 
                        COUNT(CASE WHEN NOT (open >0 AND high >0 AND low >0 AND close >0) THEN 1 END) <= $3
                )
                ORDER BY symbol, date
            ;
        `
        EVALDATE := EVALDATE.Format("'2006-01-02'")
        MINCTD := strconv.Itoa(MINCTD)
        MINDP := strconv.Itoa(MINDP)
        res, err := tx.Exec(query, EVALDATE, MINCTD, MINDP)
        if err != nil {
            return 0, 0, err
        }
        nRows, err = res.RowsAffected()
        if err != nil {
            return 0, 0, err
        }
    }

    {
        const query = `ALTER TABLE clean ADD PRIMARY KEY (symbol, date);`
        _, err := tx.Exec(query)
        if err != nil {
            return 0, 0, err
        }
    }

    if err = tx.Commit(); err != nil {
        return 0, 0, err
    }
    committed = true

    since := time.Since(start)

    {
        const query = `ANALYZE clean;`
        if _, err := db.Exec(query); err != nil {
            return nRows, since, err
        }
    }

    return nRows, since, nil
}

func main() {
    db, err := sql.Open("postgres", "user=peter password=peter dbname=peter")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer db.Close()
    var ( // one year
        EVALDATE = time.Now().AddDate(-1, 0, 0)
        MINCTD   = 240
        MINDP    = 5
    )
    nRows, since, err := clean(db, EVALDATE, MINCTD, MINDP)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("clean:", nRows, "rows", since)
    return
}

游乐场:https://play.golang.org/p/qVOQQ6mcU-1

参考文献:

金融市场技术分析:交易方法和应用综合指南,John J. Murphy。

数据库系统简介,第8版,C.J。日期。

PostgreSQL:简介和概念,Bruce Momjian。

PostgreSQL 9.6.6文档,PostgreSQL。