
PostgreSQL是一个免费、开源的数据库服务器,它在灵活的BSD许可证下发行。与其他开放源代码数据库系统(比如MySQL和Firebird)和商业数据库系统(比如Oracle、Sybase、IBM的DB2和Microsoft SQL Server)之外,为用户又提供了一种选择。
- 1996年,PostgreSQL发行了第一个版本6.0,目前的稳定版本是11.2。
- PostgreSQL一开始是被设计运行在类UNIX操作系统上,目前支持的操作系统有Linux、OS X、Solaris、Windows等。支持的数据类型有文本、图片、音频和视频,且提供了很多可编程接口,如C/C++、Python、Java、Ruby、Perl。
- PostGIS是一个开源的,免费提供的PostgreSQL数据库管理系统(又称DBMS)的空间数据库扩展器。所以PostgreSQL(又名Postgres)是数据库,PostGIS就像是该数据库的附加组件。简而言之,PostGIS将空间函数(如距离,面积,并集,交集和特殊几何数据类型)添加到PostgreSQL。空间数据库存储和操作空间对象,就像数据库中的任何其他对象一样。PG 多年来在 GIS 领域处于优势地位,因为它有丰富的几何类型和空间操作函数,相比之下mysql就差很多,instagram就是因为PG的空间数据库扩展POSTGIS远远强于MYSQL的my spatial而采用PGSQL的。
下面是目前数据库引擎的使用趋势及排名:

在本教程中,我们将使用PostGIS和pgRouting提供的空间及网络功能模块来直接处理数据,进行空间查询,为什么我们舍弃诸如ArcGIS、QGIS这样的软件,而直接使用数据库呢?原因如下:

1.1 创建数据库
采用SQL语句与pgAdmin4对照方式进行:
CREATE DATABASE testdbWITHOWNER = postgresENCODING = 'UTF8'CONNECTION LIMIT = -1;
创建postgis和pgrouting能力
create extension postgis;
create extension pgrouting;
查看当前数据库版本
SELECT version();
SELECT postgis_version();
1.2 创建数据表
非空间数据表
CREATE TABLE weather (
city varchar(80),
temp_lo int, -- 最低温度
temp_hi int, -- 最高温度
prcp real, -- 湿度
date date
);
空间数据表
CREATE TABLE cities (
name varchar(80),
location point
);
删除数据表
DROP TABLE weather;
1.3 插入及删除数据
INSERT INTO weather VALUES ('San Francisco', 5, 12, 0.25, '2019-01-10');
INSERT INTO weather VALUES ('San Francisco', 26, 32, 0.25, '2017-09-12');
INSERT INTO weather VALUES ('San Francisco', 15, 17, 0.25, '2018-04-02');
INSERT INTO weather VALUES ('Beijing', -5, 6, 0.25, '2018-02-10');
INSERT INTO weather VALUES ('Beijing', 1, 3, 0.25, '2019-01-10');
INSERT INTO cities VALUES ('San Francisco', '(-194.0, 53.0)');
但我们在pgAdmin4中可以看到,这个空间数据似乎并不以WKT或WKB形式存在,而是简单的字符串形式。
CREATE TABLE cities (
name varchar(80),
location geometry(point, 4326)
);
INSERT INTO cities VALUES ('San Francisco', ST_GeomFromText('POINT(-122.41 37.5)', 4326));
删除数据
DELETE FROM cities
1.4 查看数据
下面使用一般SQL来对数据进行查询:
# 查看所有字段及数据
SELECT * FROM weather;
# 对特定字段进行计算
SELECT city, (temp_hi+temp_lo)/2 AS temp_avg, date FROM weather;
# 使用WHERE语句进行date区间查询
SELECT * FROM weather WHERE date between '2018-10-01' AND '2019-01-20';
对数据进行排序:
SELECT geom, name_zh, labelrank, postal FROM countries ORDER BY labelrank ASC, postal DESC;
对数据进行分类汇总:
## 使用GROUP BY进行分类
SELECT city , SUM(temp_hi) FROM weather GROUP BY city;
## 使用HAVING 进行过滤
SELECT city , SUM(temp_hi) FROM weather GROUP BY city HAVING SUM(temp_hi) > 10;
寻找非重复项
SELECT DISTINCT city FROM weather;
寻找指定项目数据
# 前2项数据
SELECT * FROM weather FETCH FIRST 2 ROW ONLY;
# 第3-4项数据
SELECT * FROM weather OFFSET 2 ROWS FETCH FIRST 2 ROW ONLY;
值指定的WHERE查询
SELECT * FROM weather WHERE temp_lo IN (-5, 5);
模糊查询
SELECT * FROM weather WHERE city LIKE '%ei%';
JOIN查询
关于JOIN查询,是SQL中一个非常重要的部分,其查询结果可以参见以下图形:

CREATE TABLE basket_a (
id INT PRIMARY KEY,
fruit VARCHAR (100) NOT NULL
);
CREATE TABLE basket_b (
id INT PRIMARY KEY,
fruit VARCHAR (100) NOT NULL
);
INSERT INTO basket_a (id, fruit) VALUES (1, 'Apple'),(2, 'Orange'),(3, 'Banana'),(4, 'Cucumber');
INSERT INTO basket_b (id, fruit) VALUES (1,'Orange'), (2,'Apple'),(3,'Watermelon'),(4,'Pear');
INNER JOIN
## inner join查询
SELECT
a.id id_a, a.fruit fruit_a, b.id id_b, b.fruit fruit_b
FROM
basket_a a
INNER JOIN basket_b b ON a.fruit = b.fruit;

LEFT JOIN
SELECT
a.id id_a, a.fruit fruit_a, b.id id_b, b.fruit fruit_b
FROM
basket_a a
LEFT JOIN basket_b b ON a.fruit = b.fruit;

RIGHT JOIN
SELECT
a.id id_a, a.fruit fruit_a, b.id id_b, b.fruit fruit_b
FROM
basket_a a
RIGHT JOIN basket_b b ON a.fruit = b.fruit

FULL JOIN
SELECT
a.id id_a,
a.fruit fruit_a,
b.id id_b,
b.fruit fruit_b
FROM
basket_a a
FULL OUTER JOIN basket_b b ON a.fruit = b.fruit;

关于Postgresql更多SQL语句知识点,可以参见http://www.postgresqltutorial.com/postgresql-union/
1.5 空间数据加载
从https://www.naturalearthdata.com/downloads/ 下载数据ne_110m_admin_0_countries、10m_places、10m_roads等数据。
1、使用QGIS的DBManager进行加载(首先要建立数据连接)
2、使用命令行工具shp2pgsql
shp2pgsql -c -g geom -D -s 4326 -i -I ne_110m_admin_0_countries countries2 | psql -d testdb -U postgres -h 127.0.0.1 -p 5432
3、使用ogr2ogr工具
ogr2ogr -append -f "PostgreSQL" PG:"dbname=testdb" ne_110m_admin_0_countries.shp -nln countries3 -nlt PROMOTE_TO_MULTI
1.6 空间数据查询
SELECT id, name_zh, economy, continent, geom FROM countries;
# 计算国土面积
SELECT id, name_zh, economy, continent, ST_Area(geom) as Area, geom FROM countries;
# 查看WKT
SELECT id, name_zh, economy, continent, ST_Area(geom) as Area, ST_AsText(geom) as geom FROM countries;
# 查询洛杉矶所在的国家
SELECT id, name_zh, economy, continent, ST_Area(geom) as Area, geom FROM countries
WHERE ST_Contains(geom, 'SRID=4326;POINT(-122.41 37.5)');
或
SELECT id, name_zh, economy, continent, ST_Area(geom) as Area, geom FROM countries
WHERE ST_Contains(geom, ST_GeomFromText('POINT(-122.41 37.5)', 4326));
# 查询洛杉矶构造的缓冲区类的国家
SELECT id, name_zh, economy, continent, ST_Area(geom) as Area, geom FROM countries
WHERE ST_Distance(geom, ST_GeomFromText('POINT(-122.41 37.5)', 4326)) < 20;
等价于
SELECT id, name_zh, economy, continent, geom FROM countries
WHERE ST_DWithin(geom, ST_GeomFromText('POINT(-122.41 37.5)', 4326), 20);
# 计算洛杉矶到世界各国距离
SELECT id, name_zh, economy, continent,
ST_Distance(geom, ST_GeomFromText('POINT(-122.41 37.5)', 4326)) AS distance,
geom FROM countries;
关联查询
# 两张表之间的关联查询,寻找洛杉矶所在的国家
SELECT countries.name_zh FROM places, countries
WHERE ST_Contains(countries.geom, places.geom) AND places.name_zh = '洛杉矶';
# 美国所有的城市
SELECT places.name_zh, places.geom as geom
FROM places, countries
WHERE ST_Contains(countries.geom, places.geom) AND countries.name_zh = '美国';
寻找领土面积最大的前5个国家和地区
SELECT name_zh, ST_Area(geom)/10000 AS hectares
FROM countries
ORDER BY hectares DESC
LIMIT 5;
寻找加拿大境内所有道路
SELECT roads.name, roads.geom AS geom FROM roads, countries
WHERE ST_Contains(countries.geom, roads.geom) AND countries.name_zh='加拿大';
加拿大道路长度
SELECT sum(ST_Length(roads.geom)) AS length FROM roads, countries
WHERE ST_Contains(countries.geom, roads.geom) AND countries.name_zh='加拿大';
各国道路长度列表
SELECT countries.name_zh, sum(ST_Length(roads.geom)) AS length FROM roads, countries
WHERE ST_Contains(countries.geom, roads.geom)
GROUP BY countries.name_zh
ORDER BY length DESC;
北京附近的道路
SELECT
r.*
FROM
roads AS r,
places AS p
WHERE
p.name_zh = '北京' AND ST_Distance(r.geom, p.geom) < 1;
