PostgreSQL中的SQL使用技巧汇编(第三篇)

tag: SQL基础

概述使用实例1、数组下标平移2、记录的变换3、十六进制和十进制的转换4、空间索引中的间隔(时间、地址)查询5、行变量插入到表中的简短形式6、ALTER TABLE ALTER COLUMN的使用7、schema.name使用quota_ident8、较长前缀的快速搜索9、统计子串的个数10、不同时区下的timestamp计算,连带索引11、使用不可变函数作为优化器的提示12、PostgreSQL中的自治事务13、数组中varchar变量重复赋值的优化14、Use IS DISTINCT FROM without COALESCE (这现在还是问题吗?)15、移植到Oracle, to_date函数的问题16、PostgreSQL dump错误字符的识别和修正17、注意带有NULL的NOT IN18、快速指定某些表字段的第一行19、薪酬最高的雇员20、其它会话变量21、XPATH函数索引22、psql带less23、数组的交集24、SQL语言中的void函数25、下一行的快速提取参考

概述

整理PostgreSQL中一些常见的SQL小技巧,绝大部分来源于网上。有些技巧可能随着PG版本的不断升级和优化,已经不再实用了,所以要查阅的时候,需要自己审慎的判断以后再做决定,最好亲手试一下,看看是不是达到预期的效果。

使用实例

1、数组下标平移

CREATE OR REPLACE FUNCTION shift_idx(anyarray) 
RETURNS anyarray AS $$
DECLARE 
  mi int := array_upper($1,1); 
  offset int := array_lower($1,1); 
BEGIN
  RETURN '[0:' || mi - offset || '] = ' || $1::varchar
END 
$$ LANGUAGE plpgsql IMMUTABLE STRICT;
CREATE FUNCTION

postgres=# SELECT shift_idx(array[1,2,3]);
   shift_idx   
---------------
 [0:2]={1,2,3}
(1 row)

2、记录的变换

使用函数而不是继承表来返回记录:

CREATE FUNCTION foo(OUT a intOUT b intIN c int)
RETURNS record AS $$
BEGIN
  a := c + 1; b := c + 2;
  RETURN;
END;
$$ LANGUAGE plpgsql;

CREATE TABLE t(c int);
INSERT INTO t VALUES(10),(20);

postgres=# SELECT c, foo(c) FROM t;
 c  |   foo   
----+---------
 10 | (11,12)
 20 | (21,22)
(2 rows)

postgres=# SELECT c, (foo(c)).* FROM t;
 c  | a  | b  
----+----+----
 10 | 11 | 12
 20 | 21 | 22
(2 rows)

3、十六进制和十进制的转换

(有点老哈,to_hex现在已经是正式函数了)

=# select to_hex(11);
    to_hex
   --------
    b
   (1 row)
postgres=# create or replace function to_dec(text) 
           returns integer as $$
           declare r int
           begin 
             execute E'select x\''||$1|| E'\'::integer' into r; 
             return r; 
           end
           $$ language plpgsql;
CREATE FUNCTION
postgres=# select to_dec('ff');
 to_dec 
--------
    255
(1 row)

更快速的版本:

CREATE FUNCTION hex2dec(in_hex TEXT)
RETURNS INT
IMMUTABLE STRICT LANGUAGE sql AS $body$
  SELECT CAST(CAST(('x' || CAST($1 AS text)) AS bit(8)) AS INT);
$body$;

任意长度的版本:

CREATE OR REPLACE FUNCTION hex2dec (varchar)
RETURNS varchar as 
$$
DECLARE 
    _x numeric;
    _count int;
    _digit int;
BEGIN
    _x :0;
    for _count in 1..length($1) loop 
        EXECUTE E'SELECT x\''||substring($1 from _count for 1)|| E'\'::integer' INTO _digit;
        _x := _x * 16 + _digit ;
    end loop;
    return _x::varchar;
end
;
$$ language plpgsql immutable
;

4、空间索引中的间隔(时间、地址)查询

注意:搜索间隔通常很慢,因为优化器不使用索引。原因在于开始列和结束列之间的依赖关系。一种解决方案是基于空间索引:它允许处理两个相关的值,就好像它们是单个值一样:

postgres=# EXPLAIN ANALYZE SELECT * FROM testip WHERE 19999999 BETWEEN startip AND endip;
                           QUERY PLAN
----------------------------------------------------------------
 Seq Scan on testip  (cost=0.00..19902.00 rows=200814 width=12) (actual time=3.457..434.218 rows=1 loops=1)
   Filter: ((19999999 >= startip) AND (19999999 <= endip))
 Total runtime: 434.299 ms
(3 rows)

Time: 435,865 ms

postgres=# CREATE INDEX ggg ON testip USING gist ((box(point(startip,startip),point(endip,endip))) box_ops);
CREATE INDEX
Time: 75530,079 ms
postgres=# EXPLAIN ANALYZE 
              SELECT * 
                 FROM testip 
                WHERE box(point(startip,startip),point(endip,endip)) @> box(point (19999999,19999999), point(19999999,19999999));
                                                                                                QUERY PLAN                                                            
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on testip  (cost=60.50..2550.14 rows=1000 width=12) (actual time=0.169..0.172 rows=1 loops=1)
   Recheck Cond: (box(point((startip)::double precision, (startip)::double precision), point((endip)::double precision, (endip)::double precision)) @> '(19999999,19999999),(19999999,19999999)'::box)
   ->  Bitmap Index Scan on ggg  (cost=0.00..60.25 rows=1000 width=0) (actual time=0.152..0.152 rows=1 loops=1)
         Index Cond: (box(point((startip)::double precision, (startip)::double precision), point((endip)::double precision, (endip)::double precision)) @> '(19999999,19999999),(19999999,19999999)'::box)
 Total runtime: 0.285 ms
(5 rows)

Time: 2,805 ms

5、行变量插入到表中的简短形式

postgres=# CREATE TABLE foo(a integer, b integer);
CREATE TABLE
postgres=# CREATE OR REPLACE FUNCTION fx() 
           RETURNS void as $$
             DECLARE r foo; 
           BEGIN 
             SELECT INTO r * FROM foo; 
             INSERT INTO foo VALUES(r.*); 
             RETURN; 
           END
           $$ LANGUAGE plpgsql;
CREATE FUNCTION
postgres=# SELECT fx();
 fx 
----

(1 row)

6、ALTER TABLE ALTER COLUMN的使用

我不知道ALTER TABLE ALTER COLUMN中的USING子句。我认为旧的类型必须是可以浇铸到新的类型。但事实并非如此。捷克布尔字面值不能直接转换为PostgreSQL的bool类型,但是,使用USING子句,我可以执行ALTER COLUMN type从varchar转换为bool:

postgres=# CREATE TABLE foo(a varchar);
CREATE TABLE
postgres=# INSERT INTO foo VALUES ('ano'); -- czech yes
INSERT 0 1
postgres=# ALTER TABLE foo ALTER COLUMN a TYPE boolean ;
ERROR:  column "a" cannot be cast to type "pg_catalog.bool"
postgres=# ALTER TABLE foo 
              ALTER COLUMN a TYPE boolean 
             USING CASE a 
                       WHEN 'ano' THEN true 
                       ELSE false END;
ALTER TABLE
postgres=# SELECT * FROM foo;
 a 
---
 t
(1 row)

7、schema.name使用quota_ident

使用单引号是防止SQL注入的一种保护。quote_ident函数检查它的参数,当参数包含有问题的字符时,它返回用单引号括起来的参数。这是非常简单实用的。问题是schema.name对,因为'是一个被监视的字符:

postgres=# select quote_ident('public.foo');
 quote_ident  
--------------
 "public.foo"
(1 row)

postgres=# select * from "public.foo";
ERROR:  relation "public.foo" does not exist
postgres=# select * from public."foo";
 a 
---
(0 rows)

postgres=# select * from "public"."foo";
 a 
---
(0 rows)

我们可以造一个函数来处理这种问题:

CREATE OR REPLACE FUNCTION quote_array(text[]) 
RETURNS text AS $$
SELECT array_to_string(array(SELECT quote_ident($1[i]) 
                                FROM generate_series(1, array_upper($1,1)) g(i)),
                       '.'
$$ LANGUAGE SQL IMMUTABLE;

CREATE OR REPLACE FUNCTION quote_schema_ident(text
RETURNS text AS $$
SELECT quote_array(string_to_array($1,'.'))
$$ LANGUAGE SQL IMMUTABLE;

postgres=# select quote_schema_ident('public.foo tab');
 quote_schema_ident 
--------------------
 public."foo tab"
(1 row)

8、较长前缀的快速搜索

搜索最长前缀是一项常见的任务——主要在通信中。我们从前缀集合中搜索某个数字的最长前缀。几年前,我用存储过程和改进的间隔减半方法解决了这个问题。也许稍微慢一点但是Hubert Lubaciewski描述了一个简单健壮的方法

--table contains about 5000 prefixes
postgres=# select * from prefixesx limit 5;
   costcode_name    | costcode 
--------------------+----------
 Afghanistan        |
 93
 Afghanistan Mobile | 9370
 Afghanistan Mobile |
 9379
 Afghanistan Mobile | 9380
 Alaska (USA)       |
 1907
(5 rows)

使用like来实现,速度慢点儿:

postgres=# SELECT * 
              FROM prefixesx 
             WHERE '420724181000' LIKE costcode || '%' 
             ORDER BY length(costcode) DESC 
             LIMIT 1;
          costcode_name          | costcode 
---------------------------------+----------
 Czech Republic Mobile - EuroTel | 42072

快速解决方案是从number生成所有可能的前缀并搜索这些前缀:

CREATE OR REPLACE FUNCTION prefixes(varchar
RETURNS varchar[] AS $$
SELECT ARRAY(SELECT substring($1 FROM 1 FOR i) 
                FROM generate_series(1length($1)) g(i))::varchar[]; 
$$ LANGUAGE sql IMMUTABLE;

postgres=# SELECT * 
              FROM prefixesx 
             WHERE costcode = ANY (prefixes('420724191000')) 
             ORDER BY length(costcode) DESC 
             LIMIT 1;
          costcode_name          | costcode 
---------------------------------+----------
 Czech Republic Mobile - EuroTel | 42072

在5000行时,这个查询比LIKE快4倍。稍微慢一点的查询与LIKE和LIKE firstchar || %

SELECT * 
   FROM prefixesx
  WHERE '420724181000' LIKE costcode || '%'
    AND costcode LIKE substring('420724191000' FROM 1 FOR 1) || '%'  
  ORDER BY length(costcode) DESC 
  LIMIT 1;

9、统计子串的个数

这个函数是基于流行的技巧-使用替换和长度函数没有循环,这是典型的解释器,内置函数比迭代更快:

CREATE OR REPLACE FUNCTION CountInString(text,text)
RETURNS integer AS $$
 SELECT(Length($1) - Length(REPLACE($1, $2''))) / Length($2) ;
$$ LANGUAGE SQL IMMUTABLE;

略加改造后的实现 :

CREATE OR REPLACE FUNCTION CountInString(texttext
RETURNS integer AS $$
  SELECT Array_upper(String_to_array($1,$2),1) - 1;
$$ LANGUAGE SQL IMMUTABLE;

10、不同时区下的timestamp计算,连带索引

在某个时区,最简单的时间规范是使用at time ZONE操作符。我们不能用timezone索引时间戳,因为我们可以更改当前时区,所以我们只能为某个指定时区的时间戳创建索引。来源:pgsql-general, Tom Lane

CREATE INDEX created_tz_paris ON data((created_on AT TIME ZONE 'Europe/Paris'));

11、使用不可变函数作为优化器的提示

对于高度特定的数据分布,我们可能会在预测和生成次优执行计划方面遇到问题。我们可以通过将一个SQL查询分解为更多的依赖查询并逐步求值来解决这些问题。以下查询使用实际结果(而不是估计)先前的查询,并且查询以最佳方式执行。Tom Lane的建议是基于使用在生成执行计划之前进行评估的不可变函数:

CREATE OR REPLACE FUNCTION getTypesLieuFromTheme(codeTheme text
RETURNS text[] AS $f$
SELECT ARRAY(SELECT codetylieu::text FROM rubtylieu WHERE codeth = $1);
$f$ LANGUAGE SQL IMMUTABLE;

in query
SELECT ...
  WHERE ... AND gl.codetylieu = ANY(getTypesLieuFromTheme('RES'))

look execution plan doesn't contain function call and query to table rubtylieu too
->  Bitmap Heap Scan on genrelieu gl (cost=2.23..6.22 rows=88 width=4) (actual time=0.022..0.075 rows=88 loops=1)
       Recheck Cond: ((codetylieu)::text = ANY ('
{RES}'::text[]))
       ->  Bitmap Index Scan on ind_genrelieu2 (cost=0.00..2.21 rows=88 width=0) (actual time=0.016..0.016 rows=88 loops=1)
                Index Cond: ((codetylieu)::text = ANY ('
{RES}'::text[]))

使用这个技巧作为最后一步。测试,请事先增加统计数字。

12、PostgreSQL中的自治事务

postgresql自治事务是一个有用的特性,主要用于日志记录。PostgreSQL还不支持这个特性。对于不可信的语言,我们可以模仿它。你可以看到Jon Roberts的解决方案。

CREATE OR REPLACE FUNCTION fn_log_error(p_function varchar, p_location int, p_error varchar
RETURNS void AS $$
DECLARE
  v_sql varchar;
  v_return varchar;
  v_error varchar;
BEGIN
  PERFORM dblink_connect('connection_name''dbname=...');

  v_sql := 'INSERT INTO error_log (function_name, location, error_message, error_time) '
           || '
VALUES (''' || p_function_name || '''
           || p_location || '
''' || p_error || ''', clock_timestamp())';
  SELECT INTO v_return * 
     FROM dblink_exec('
connection_name', v_sql, false);

  --get the error message
  SELECT INTO v_error * 
     FROM dblink_error_message('
connection_name');

  IF position('
ERROR' in v_error) > 0 OR position('WARNING' in v_error) > 0 THEN
    RAISE EXCEPTION '
%', v_error;
  END IF;

  PERFORM dblink_disconnect('
connection_name');
EXCEPTION
  WHEN others THEN
    PERFORM dblink_disconnect('
connection_name');
    RAISE EXCEPTION '
(%)', SQLERRM;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

13、数组中varchar变量重复赋值的优化

plpgsql对于密集的非SQL操作来说不是一种好的语言。它最适合作为SQL语句或原型的粘合剂。但有时plperl或plpython可以快得多。Plpgsql不喜欢对varchar或数组变量进行累积迭代。当我们不能使用Perl时,…我们可以使用SQL:

CREATE OR REPLACE FUNCTION SlowList(int-- slow function, usable for N <= 100
RETURNS varchar AS $$
DECLARE s varchar = '';
BEGIN
  FOR i IN 1..$1 LOOP
    s := '' || i || '';  -- slow is s := s || ..
  END LOOP;
  RETURN s;
END; $$ LANGUAGE plpgsql IMMUTABLE;

CREATE OR REPLACE FUNCTON FastList(int-- fast function
RETURNS varchar AS $$
BEGIN
  RETURN array_to_string(ARRAY(SELECT '' || i || ''
                                  FROM generate_series(1, $1) g(i)),
                         '');
END; $$ LANGUAGE plpgsql IMMUTABLE;

对于N < 100,执行时间小于4ms(所以没有任何理由使用肮脏的伎俩)。对于较大的N,执行时间不同(SlowList(10000) - 4000ms, FastList(10000) - 52ms)。作者—— Pavel Stěhule。

14、Use IS DISTINCT FROM without COALESCE (这现在还是问题吗?)

Sim Zacks报告迁移到8.2.4后查询速度很慢。这个版本在预测表达式COALESCE(column, false) = false的结果时存在问题,然后执行计划是次优的。修正后PostgreSQL选择最优执行计划。

SELECT ...
   FROM some_tab
        LEFT JOIN
        some_tab2
        ON sometab2.col IS DISTINCT FROM true--> join false or NULL 

所以不要在WHERE子句中使用COALESCE, 下周有人报告了类似的问题:

-- execution plan is suboptimal
((COALESCE((at_type)::integer, 1) = 1AND (COALESCE(at_language, 0::numeric) = 0::numeric))

-- execution plan is optimal
(at_type = 1 or at_type is nullAND (at_language = 0 or at_language is null)

15、移植到Oracle, to_date函数的问题

Peter Eisentraut报告了PostreSQL和Oracle之间to_date函数的不兼容性。

SQL> select to_date('31-DEC-200700:00:00', 'dd-mon-yyyy hh24:mi:ss') from
dual;

TO_DATE('
---------
31-DEC-07

On PostgreSQL:

select to_date('31-DEC-200700:00:00', 'dd-mon-yyyy hh24:mi:ss');
  to_date
--------------
 200700-12-31

Oracle忽略格式字符串中的空格,但PostgreSQL需要精确的格式。这种行为可以在删除空格的自定义函数中更改:

CREATE OR REPLACE FUNCTION fn_to_date(p_date varchar, p_format varchar)
RETURNS timestamp AS $$
 SELECT to_timestamp(replace($1' '''), replace($2' '''));
$$ LANGUAGE SQL STRICT IMMUTABLE;

# select fn_to_date('31-DEC-200700:00:00', 'dd-mon-yyyy hh24:mi:ss');

    fn_to_date
---------------------
 2007-12-31 00:00:00
(1 row)

16、PostgreSQL dump错误字符的识别和修正

对于转储中的正确字符,8.2比8.1更严格。原因是SQL注入安全修复(通过不正确的UTF8字符)。这种行为的改变可能会导致迁移问题。解决办法很简单。我们必须找到错误的字符并修复它。

$ iconv -f utf-8 -t utf-8 dump.sql > /dev/null
iconv: illegal input sequence at position 1000
$ head -c 1010 dump.sql | tail -c 20 | od -c
0000000 h . B u t i s n 222 t i t
0000020 h i s ?
0000024

详见: http://lca2007.linux.org.au/att_data/Miniconfs(2f)PostgreSQL/attachments/professional.pdf

评论:这还是个问题吗?

17、注意带有NULL的NOT IN

一些SQL开发人员不喜欢NULL。其他类似的。我也喜欢。NULL是必要的,可以节省很多工作。结果表达式中的NULL明确表示输入数据中存在问题。不喜欢NULL的人不理解它,并使用以下论点,引用“异常”:

postgres=# SELECT * 
              FROM (VALUES(10),(20),(30)) v(a) 
             WHERE a IN (1020, NULL); --> all is ok
 a  
----
 10
 20
(2 rows)

postgres=# SELECT * 
              FROM (VALUES(10),(20),(30)) v(a) 
             WHERE a NOT IN (1020, NULL);
 a 
---
(0 rows) --> anomaly, expects 30

但这并非特例。NOT IN操作符检查列表中的每一项,因为左边的值必须不同于所有项。由于与NULL的比较永远不会产生NULL以外的结果,因此没有任何值可以满足这个谓词。

18、快速指定某些表字段的第一行

我的任务很明确。我有地址簿与100,000项,我必须得到姓氏的所有第一个字符。这个任务是几个任务中的一个,其中相关子查询比其他任何任务都要快。因为seq扫描和agg函数需要扫描所有100000条记录,而相关子查询只需要32次访问函数索引:

SELECT * 
   FROM (
         SELECT substring('abcdefghijklmnopqrstuvwxyzěščřžýáíé' 
                          FROM i For 1
            FROM generate_series(1,32) g(i)
        ) p(onechar) 
  WHERE EXISTS(
               SELECT 1 
                  FROM address_book 
                 WHERE substring(surname from 1 for 1) = p.onechar  
                   and active
              );

另一个版本:

   SELECT * 
   FROM
     ( 
         SELECT substring('abcdefghijklmnopqrstuvwxyzěščřžýáíéúůňľó' FROM i For 1FROM generate_series(1,40) g(i) UNION SELECT 'ch' 
     ) p(onechar) 
   WHERE
     EXISTSSELECT 1 FROM a_osoba WHERE lower(substring(prijmeni from 1 for char_length(p.onechar))) = p.onechar );

19、薪酬最高的雇员

薪酬最高的员工这是典型的学校查询:在某个组中选择薪酬最高的员工。该查询可以通过派生表或相关子查询来解决。我们可以在PostgreSQL中使用非标准子句DISTINCT ON:

postgres=# SELECT * FROM employees ;
   name    | surname   | department | payment  
-----------+-----------+------------+-------
 Pavel     |
 Stehule   |          1 | 10000
 Zdenek    | Stehule   |          1 |  9000
 Vladimira |
 Stehulova |          2 |  9000
(3 rows)

postgres=# SELECT DISTINCT ON department * 
              FROM employees 
             ORDER BY department, payment DESC;
   name    | surname   | department | payment  
-----------+-----------+------------+-------
 Pavel     |
 Stehule   |          1 | 10000
 Vladimira | Stehulova |          2 |  9000
(2 rows)

注意,DISTINCT ON不可移值。这种解决方案的一大缺点是,它只显示了来自每个部门的一个人,即使有更多的员工获得相同的最高薪酬,也会产生不完整的结果。

20、其它会话变量

PostgreSQL不支持服务器会话变量。这意味着,我们必须更频繁地编写存储过程,因为有变量。一些替换是模块变量。这些变量必须在配置中被允许。Andreas Kretschmer发送:

- define in your postgresql.conf:
 custom_variable_classes = 'myvar'

use within psql:

test=# set myvar.benutzer = 'foo';
SET
test=*# select * from foo;
 id | name
----+------
 1 | foo
 2 | bar
(2 rows)

test=*# select * from foo where name=current_setting('myvar.benutzer');
 id | name
----+------
 1 | foo
(1 row)

test=*# set myvar.benutzer = 'none';
SET
test=*# select * from foo where name=current_setting('myvar.benutzer');
 id | name
----+------
(0 rows)

21、XPATH函数索引

8.3集成了xpath功能。在XML支持方面存在差距,因为GIST或GIN索引不支持XML类型。xpath函数返回xml值数组。但是我们可以将自定义类型转换写为int array:

CREATE OR REPLACE FUNCTION xml_list_to_int(xml[])
RETURNS int[] AS $$
SELECT ARRAY(SELECT to_number(($1[i])::text,'999999.99')::int
                FROM generate_series(1, array_upper($1,1)) g(i))
$$ LANGUAGE SQL IMMUTABLE;

CREATE CAST (xml[] AS int[]) WITH FUNCTION xml_list_to_int(xml[]);

-- array of integers are supported with GIST
CREATE INDEX fx ON foo USING
GIN((xpath('//id/text()',order_in_xml)::int[]));

22、psql带less

PSQL支持分页,但它无法与less提供的选项相比。下面是关于如何设置psql以使用less作为分页器的示例。作者:梅林·蒙居尔

#to profile
export PAGER=less
export LESS="-iMSx4 -FX"

#to .psqlrc
\timing
\pset pager always

23、数组的交集

CREATE OR REPLACE FUNCTION array_intersect(ANYARRAY, ANYARRAY)
RETURNS ANYARRAY
LANGUAGE SQL
AS $$
  SELECT ARRAY(
               SELECT $1[i] AS "the_intersection"
                  FROM generate_series(
                                       array_lower($1,1),
                                       array_upper($1,1)
                                      ) AS i
               INTERSECT
               SELECT $2[j] AS "the_intersection"
                  FROM generate_series(
                                       array_lower($2,1),
                                       array_upper($2,1)
                                      ) AS j
              );
$$;

另一个更快速的版本:

CREATE OR REPLACE FUNCTION array_intersect(ANYARRAY, ANYARRAY)
RETURNS ANYARRAY
LANGUAGE SQL
AS $$
  SELECT ARRAY(SELECT UNNEST($1INTERSECT SELECT UNNEST($2))
$$;

24、SQL语言中的void函数

SQL函数只能包含SQL语句。最后一条语句的结果就是函数的结果。结果的类型必须与函数的类型兼容。因为VOID是有效类型,所以任何语句都不能只返回一个异常。我们可以将NULL转换为VOID。

注意:不允许通过二进制协议使用void返回函数

CREATE OR REPLACE FUNCTION none() 
RETURNS VOID AS $$ SELECT NULL::VOID; $$
LANGUAGE SQL;

25、下一行的快速提取

对于旧的应用程序(ISAM应用程序在一系列行上工作),我们已经能够用一些值的组合(a1, b1, c1)搜索指定的下一行。传统的解决方案:

SELECT * 
   FROM data
  WHERE (a > a1)
     OR (a = a1 AND b > b1)
     OR (a = a1 AND b = b1 AND c > c1)
  ORDER BY a, b, c
  LIMIT 1;

Merlin Moncure 注意到简化形式使用行方程。这个表单更具可读性,并且允许使用多列索引:

SELECT *
   FROM data
  WHERE (a, b, c) > (a1, b1, c1)
  ORDER BY a, b, c
  LIMIT 1

ISAM访问降低SQL服务器性能。为了获得有效的SQL,我们必须对集合进行操作,但是当我们不能重写应用程序时,我们别无选择。

参考

[1] https://postgres.cz/wiki/PostgreSQL_SQL_Tricks_II
:https://postgres.cz/wiki/PostgreSQL_SQL_Tricks_II


请使用浏览器的分享功能分享到微信等