修改表定义
如果您确实需要这些列NOT NULL
你确实需要字符串'default'
默认为engine_slug
,我建议引入列默认值:
COLUMN | TYPE | Modifiers
-----------------+-------------------------+---------------------
id | INTEGER | NOT NULL DEFAULT ...
engine_slug | CHARACTER VARYING(200) | NOT NULL DEFAULT 'default'
content_type_id | INTEGER | NOT NULL
object_id | text | NOT NULL
object_id_int | INTEGER |
title | CHARACTER VARYING(1000) | NOT NULL
description | text | NOT NULL DEFAULT ''
content | text | NOT NULL
url | CHARACTER VARYING(1000) | NOT NULL DEFAULT ''
meta_encoded | text | NOT NULL DEFAULT '{}'
search_tsv | tsvector | NOT NULL
...
DDL 语句将是:
ALTER TABLE watson_searchentry ALTER COLUMN engine_slug DEFAULT 'default';
Etc.
这样您就不必每次都手动插入这些值。
Also: object_id text NOT NULL, object_id_int INTEGER
?这很奇怪。我想你也有你的理由...
我将满足您更新的要求:
重点是更新列title
and content
in watson_searchentry
当然,你must add a UNIQUE强制执行您的要求的约束:
ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)
将使用随附的索引。对于初学者来说,通过这个查询。
顺便说一句,我几乎从不使用varchar(n)
在 Postgres 中。只是text
. 原因之一。
查询方式数据修改 CTE
这可以用数据修改公用表表达式(也称为“可写”CTE)重写为单个 SQL 查询。需要 Postgres 9.1 或更高版本。
此外,该查询仅删除必须删除的内容,并更新可以更新的内容。
WITH ctyp AS (
SELECT id AS content_type_id
FROM django_content_type
WHERE app_label = 'web'
AND model = 'member'
)
, sel AS (
SELECT ctyp.content_type_id
,m.id AS object_id_int
,m.id::text AS object_id -- explicit cast!
,m.name AS title
,concat_ws(' ', u.email,m.normalized_name,c.name) AS content
-- other columns have column default now.
FROM web_user u
JOIN web_member m ON m.user_id = u.id
JOIN web_country c ON c.id = m.country_id
CROSS JOIN ctyp
WHERE u.is_active
)
, del AS ( -- only if you want to del all other entries of same type
DELETE FROM watson_searchentry w
USING ctyp
WHERE w.content_type_id = ctyp.content_type_id
AND NOT EXISTS (
SELECT 1
FROM sel
WHERE sel.object_id_int = w.object_id_int
)
)
, up AS ( -- update existing rows
UPDATE watson_searchentry
SET object_id = s.object_id
,title = s.title
,content = s.content
FROM sel s
WHERE w.content_type_id = s.content_type_id
AND w.object_id_int = s.object_id_int
)
-- insert new rows
INSERT INTO watson_searchentry (
content_type_id, object_id_int, object_id, title, content)
SELECT sel.* -- safe to use, because col list is defined accordingly above
FROM sel
LEFT JOIN watson_searchentry w1 USING (content_type_id, object_id_int)
WHERE w1.content_type_id IS NULL;
-
子查询在django_content_type
总是返回单个值?否则,CROSS JOIN
可能会引起麻烦。
-
第一个 CTEsel
收集要插入的行。注意我是如何选择的匹配列名简化事情。
-
在 CTE 中del
我避免删除可以更新的行。
-
在 CTE 中up
这些行会被更新。
-
因此,我避免在最后插入之前未删除的行INSERT
.
可以轻松包装到 SQL 或 PL/pgSQL 函数中以供重复使用。
对于大量并发使用不安全。比您拥有的功能好得多,但对于并发写入仍然不是 100% 稳健。但根据您更新的信息,这不是问题。
用 DELETE 和 INSERT 替换 UPDATE 的成本可能会高很多,也可能不会高很多。在内部,每次更新都会产生一个新的行版本,因为MVCC模型.
速度第一
如果您并不真正关心保留旧行,则更简单的方法可能会更快:删除所有内容并插入新行。此外,包装到 plpgsql 函数中可以节省一些规划开销。您的函数基本上是通过一些小的简化并观察上面添加的默认值:
CREATE OR REPLACE FUNCTION update_member_search_index()
RETURNS VOID AS
$func$
DECLARE
_ctype_id int := (
SELECT id
FROM django_content_type
WHERE app_label='web'
AND model = 'member'
); -- you can assign at declaration time. saves another statement
BEGIN
DELETE FROM watson_searchentry
WHERE content_type_id = _ctype_id;
INSERT INTO watson_searchentry
(content_type_id, object_id, object_id_int, title, content)
SELECT _ctype_id, m.id, m.id::int,m.name
,u.email || ' ' || m.normalized_name || ' ' || c.name
FROM web_member m
JOIN web_user u USING (user_id)
JOIN web_country c ON c.id = m.country_id
WHERE u.is_active;
END
$func$ LANGUAGE plpgsql;
我什至不使用concat_ws(): 是安全的NULL
值并简化了代码,但比简单串联慢一点。
Also:
表上有一个触发器设置列的值search_tsv
基于这些列。
如果这是唯一需要触发器的时间,则将逻辑合并到该函数中会更快。否则,可能不值得大惊小怪。