點燈坊

失くすものさえない今が強くなるチャンスよ

使用 count 取得資料筆數

Sam Xiao's Avatar 2021-10-22

count 是實務上常見需求,這在 Knex 該如何寫呢 ?

Version

Knex 0.20.2
Knex-fp 0.0.8

SQL

SELECT count(*)
FROM books

count 最常見的應用是只求筆數即可。

count003

Knex

mySQL ('books')
  .count ({ count: '*'})
  .then (x => x[0].count)

但由於 SQL 是將 count 放在 result set 內,因此必須在 then 中先以 [0] 取得第一筆資料,再使用 count 取得。

Ramda

import { pluck, head } from 'ramda'

mySQL ('books')
  .count ({ count: '*'})
  .then (pluck ('count'))
  .then (head)

也可在 then 中使用 Ramda 產生 pure function,首先 pluckcount field 資料,再使用 head[0] 取出。

import { pluck, head, compose } from 'ramda'

let transform = compose (head, pluck ('count'))

mySQL ('books')
  .count ({ count: '*'})
  .then (transform)

由於 Promise 支援 composition law,也可將 then 所有 pure function 都使用 pipe 組合出 next 一次交給 then 執行。

Wink-fp

import { tug } from 'wink-fp'

mySQL ('books')
  .count ({ count: '*'})
  .then (tug('count'))
  .then (log)

由於 pluckhead 的組合經常使用,Wink-fp 提供了 tug,只要傳入 count field 即可。

Knex-fp

import { pipe, andThen as then } from 'ramda'
import { count } from 'knex-fp'
import { pipeP, tug } from 'wink-fp'

pipe (
  count ({ count: '*'}),
  then (tug ('count'))
) (mySQL ('books'))

也可使用 Knex-fp 的 count,如此就可使用 pipe 同時組合 Knex-fp 與 Wink-fp 的 function。

count004

SQL

select 'My Bookshelf' as title, count(*) as count
from books

有時我們在使用 count(*) 計算時,還想自己用 String 自訂 field。

raw000

Knex

import { map } from 'ramda'
import { format } from 'wink-fp'

mySQL ('books')
  .select (knex.raw (`'My Bookshelf' as title`))
  .count ({ count: '*'})
  .then (map(format('{title}: {count}')))

Knex 有提供 count 不意外,但 Knex 並沒有提供內建方法實現 'My Bookshelf' as title,必須使用 knex.raw 搭配 raw SQL。

map 為 Ramda 的 function,format 則屬 Wink-fp,由於 Knex 回傳 Promise,這些都必須寫在 .then

raw001

但 Knex 會 complain Knex.raw is deprecated,但結果依然正確。

import { map } from 'ramda'
import { format } from 'wink-fp'

mySQL ('books')
  .select (mySQL.raw (`'My Bookshelf' as title`))
  .count ({ count: '*'})
  .then (map (format ('{title}: {count}')))

比較正確方式是改用 Knex instance 的 raw

Knex-fp

import { pipe, map } from 'ramda'
import { format } from 'wink-fp'
import { select, count, raw, pipeK } from 'knex-fp'

pipeK (
  select (raw (mySQL) (`'My Bookshelf' as title`)),
  count ({ count:'*' })
)(
  map (format('{title}: {count}'))
) (mySQL ('books'))

也可使用pipeK 同時使用 Knex-fp 的 selectrawcount 組合與 Ramda 的 map 與 Wink-fp 的 format 組合。

raw002

Conclusion

  • Knex 並沒有提供所有 SQL 功能,有些必須使用 raw SQL
  • 由於 Knex 回傳 Promise,可繼續使用 Ramda 以 pure function 處理
  • Promise 支援 composition law,也可將所有 pure function 組合後一次傳給 then
  • pluckhead 組合經常使用,可改用 Wink-fp 的 `tug 只要一個 function 即可
  • 也可使用 Knex-fp 的 countselectraw,如此可使用 pipeK 組合 Knex-fp 與 Ramda、Wink-fp 的 function