Postgresql

欄位中的字數(全部和唯一) - 有沒有更優雅/最佳的方式?

  • August 18, 2020

回答這個問題

鑑於此表(根據問題建構):

CREATE TABLE wordcount (id SERIAL NOT NULL PRIMARY KEY, description TEXT NOT NULL);

INSERT INTO wordcount (description) VALUES ('What a great day');
INSERT INTO wordcount (description) VALUES ('This is a product. It is useful');

產生這個結果:

    id  | word_count | unique_word_count |  Description                        
---------+------------+-------------------+---------------
    1   |  4         | 4                 | What a great day
    2   |  7         | 6                 | This is a product. It is useful

我給出了(正確的)答案,你可以在這裡找到。

然而,在評論中,OP 然後又問了一個問題——如果有問題的字元串是 ['a', ' ', ' ', 'b']並且我的解決方案完全崩潰了怎麼辦——對於初學者來說,字元串甚至不會INSERT進入表格。

所以,現在的問題是,如何處理這樣的字元串 - 即撇號、方括號和 c。我將給出自己的答案,並為更優雅的解決方案提供獎金。

具有多種選擇的解決方案將受到高度評價,那些顯示出“跳出框框思考”證據的解決方案也將受到高度重視(對於陳詞濫調感到抱歉 - 但它適合這裡!:-))。我還將詳細解釋我的推理 - 這也將獲得榮譽!提及其他伺服器的選項也將獲得優勢。顯然,我只能將獎金獎勵給一個人,但我會贊成所有體面的答案。

我只能在兩天內提供獎金 - 所以我會發布我的答案並在允許時提供獎金(+100)。此外,任何處理我自己無法處理的字元串的解決方案 - 還沒有經過詳盡的測試。

至於您的解決方案:聰明且有可靠的解釋。但是這些情況呢:'', NULL, '"§$%', '-'? 沒有言語。計數應該是0- 但您的解決方案完全刪除了這些行。

此外,任何解決方案首先取決於 “word” 的確切定義,這可能會有很大的不同……

基於正則表達式的字元串處理

與您的解決方案類似,但有一些替代建議:

SELECT id
    , COALESCE(cardinality(arr), 0) AS word_count
    , unique_word_count
    , description
FROM  (
  SELECT *
       , string_to_array(trim(regexp_replace(description, '\W+', ' ', 'g')), ' ') AS arr
  FROM   wordcount
  ) a
LEFT   JOIN LATERAL (
  SELECT count(DISTINCT elem) AS unique_word_count
  FROM   unnest(arr) elem
  ) b ON true;

db<>fiddle here(擴展測試案例)

核心是regexp_replace(description, '\W+', ' ', 'g')將非單詞字元的所有子字元串替換為單個空格。請參閱正則表達式類速記轉義。這消除了遊戲早期的所有噪音。

隨後以便宜trim()的方式刪除前導/尾隨空格,string_to_array()並將準備好的字元串轉換為數組。

word_count直接從數組中獲取。再次:便宜。

unique_word_count來自帶有 的子LATERAL查詢count(DISTINCT ...)。該部分可能會或可能不會比總的未嵌套/聚合慢。這有點簡單。

COALESCE外部SELECT處理輸入(原始NULL問題未提及NOT NULL約束)。可選,以防您需要0而不是NULL.

或者(在使用短字元串的快速測試中更快):

SELECT id
    , count(*) AS word_count
    , count(DISTINCT elem) AS unique_word_count
    , description
FROM  (
  SELECT id, description
       , unnest(string_to_array(trim(regexp_replace(description, '\W+', ' ', 'g')), ' ')) AS elem
  FROM   wordcount
  ) sub
GROUP  BY id, description;

這會像您的答案一樣刪除0 個單詞的行。

(Ab-)使用文本搜尋解析器

使用文本搜尋功能ts_parse()更簡單。可能會或可能不會更快。但首先研究文本搜尋解析器辨識的各種標記,看看哪些符合您對“單詞”的定義:

SELECT * FROM ts_token_type('default')

僅適用於“ASCII Words”:(與上面不同,下劃線 ( _) 在這裡不被視為單詞字元):

SELECT w.id
    , count(*) AS word_count
    , count(DISTINCT token) AS unique_word_count
    , w.description
FROM   wordcount w, ts_parse('default', w.description) t
WHERE  t.tokid = 1 -- 'asciiword'
GROUP  BY w.id;

為了_避免分隔單詞,replace()請先使用 simple:

SELECT w.id
    , count(*) AS word_count
    , count(DISTINCT token) AS unique_word_count
    , w.description
FROM   wordcount w, ts_parse('default', replace(w.description, '_', 'x')) t
WHERE  t.tokid = 1 -- 'asciiword'
GROUP  BY w.id;

同樣,要保留所有行:

SELECT w.id
    , count(token) AS word_count
    , count(DISTINCT token) AS unique_word_count
    , w.description
FROM   wordcount w
LEFT   JOIN LATERAL (
  SELECT t.token
  FROM   ts_parse('default', w.description) t
  WHERE  t.tokid = 1 -- 'asciiword'
  ) t ON true
GROUP  BY w.id;

db<>在這裡擺弄

有關的:

引用自:https://dba.stackexchange.com/questions/239958