PostgreSQL 抱怨主键中的元素不存在比较函数
PostgreSQL complains about inexistent comparison function for element in primary key
我在 PostgreSQL
数据库中有一个 table,我想在其中存储以下列:
STATION LOCATION SERVICE NORTH EAST
text point text real real
每个元组(STATION, LOCATION, SERVICE)
都是唯一的,所以我决定把它做成复合类型,并把它作为主键。
但是,当我尝试在数据库中插入新条目时 我收到以下错误:
psycopg2.ProgrammingError: could not identify a comparison function for type point
我猜它是在抱怨你不能在二维平面中对两个点进行排序,但我看不出这有什么关系。我设法在测试示例中使用了将点用作主键的复合类型,所以我看不出这有什么不同。
我想知道:
- 为什么会这样。
- 如何修复它,最好不更改 table 架构。
调试信息:
testdb=> \d ERROR_KEY
Composite type "public.error_key"
Column | Type | Modifiers
----------+-------+-----------
station | text |
location | point |
service | text |
testdb=> \d testtable
Table "public.testtable"
Column | Type | Modifiers
--------+-----------+-----------
key | error_key | not null
north | real |
east | real |
Indexes:
"testtable_pkey" PRIMARY KEY, btree (key)
作为参考,这是我用于插入的代码:
from collections import namedtuple
import psycopg2
DB_NAME = 'testdb'
DB_USER = 'testuser'
DB_HOST = 'localhost'
DB_PASSWORD = '123456'
PVT_TABLE_NAME = 'testtable'
Coordinate = namedtuple('Coordinate', ['lat', 'lon'])
PVT_Error_Key = namedtuple('PVT_Error_Key',
['station', 'location', 'service'])
PVT_Error_Entry = namedtuple(
'PVT_Error_Entry', ['key', 'north', 'east'])
def _adapt_coordinate(coord):
"""
Adapter from Python class to Postgre geometric point
"""
lat = psycopg2.extensions.adapt(coord.lat)
lon = psycopg2.extensions.adapt(coord.lon)
return psycopg2.extensions.AsIs("'(%s, %s)'" % (lat, lon))
def _connect_to_db(db_name, db_user, db_host, db_password):
"""
Connects to a database and returns a cursor object to handle the connection
"""
connection_str = ('dbname=\'%s\' user=\'%s\' host=\'%s\' password=\'%s\''
% (db_name, db_user, db_host, db_password))
return psycopg2.connect(connection_str).cursor()
def main():
# Register the adapter for the location
psycopg2.extensions.register_adapter(Coordinate, _adapt_coordinate)
cursor = _connect_to_db(DB_NAME, DB_USER, DB_HOST, DB_PASSWORD)
# Create a dummy entry
entry = PVT_Error_Entry(
key=PVT_Error_Key(station='GKIR',
location=Coordinate(lat=12, lon=10),
service='E1'),
north=1, east=2)
# Insert the dummy entry in the database
cursor.execute(
'INSERT INTO %s '
'(KEY, NORTH, EAST) '
'VALUES((%%s, %%s, %%s), %%s, %%s)'
% PVT_TABLE_NAME,
(entry.key.station, entry.key.location, entry.key.service,
entry.north, entry.east))
# Retrieve and print all entries of the database
cursor.execute('SELECT * FROM %s', (PVT_TABLE_NAME))
rows = cursor.fetchall()
print(rows)
if __name__ == '__main__':
main()
您不能在主键中使用 point
类型的列,例如:
create table my_table(location point primary key);
ERROR: data type point has no default operator class for access method "btree"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
报错信息很清楚,需要为类型创建一个完整的btree operatorclass。
此答案中描述了完整的过程:
更新。使用您在评论中提到的解决方法
create table my_table(
x numeric,
y numeric,
primary key (x, y));
insert into my_table values
(1.1, 1.2);
您可以随时创建一个视图,它可以像 table 一样被查询:
create view my_view as
select point(x, y) as location
from my_table;
select *
from my_view;
location
-----------
(1.1,1.2)
(1 row)
我在 PostgreSQL
数据库中有一个 table,我想在其中存储以下列:
STATION LOCATION SERVICE NORTH EAST
text point text real real
每个元组(STATION, LOCATION, SERVICE)
都是唯一的,所以我决定把它做成复合类型,并把它作为主键。
但是,当我尝试在数据库中插入新条目时 我收到以下错误:
psycopg2.ProgrammingError: could not identify a comparison function for type point
我猜它是在抱怨你不能在二维平面中对两个点进行排序,但我看不出这有什么关系。我设法在测试示例中使用了将点用作主键的复合类型,所以我看不出这有什么不同。
我想知道:
- 为什么会这样。
- 如何修复它,最好不更改 table 架构。
调试信息:
testdb=> \d ERROR_KEY
Composite type "public.error_key"
Column | Type | Modifiers
----------+-------+-----------
station | text |
location | point |
service | text |
testdb=> \d testtable
Table "public.testtable"
Column | Type | Modifiers
--------+-----------+-----------
key | error_key | not null
north | real |
east | real |
Indexes:
"testtable_pkey" PRIMARY KEY, btree (key)
作为参考,这是我用于插入的代码:
from collections import namedtuple
import psycopg2
DB_NAME = 'testdb'
DB_USER = 'testuser'
DB_HOST = 'localhost'
DB_PASSWORD = '123456'
PVT_TABLE_NAME = 'testtable'
Coordinate = namedtuple('Coordinate', ['lat', 'lon'])
PVT_Error_Key = namedtuple('PVT_Error_Key',
['station', 'location', 'service'])
PVT_Error_Entry = namedtuple(
'PVT_Error_Entry', ['key', 'north', 'east'])
def _adapt_coordinate(coord):
"""
Adapter from Python class to Postgre geometric point
"""
lat = psycopg2.extensions.adapt(coord.lat)
lon = psycopg2.extensions.adapt(coord.lon)
return psycopg2.extensions.AsIs("'(%s, %s)'" % (lat, lon))
def _connect_to_db(db_name, db_user, db_host, db_password):
"""
Connects to a database and returns a cursor object to handle the connection
"""
connection_str = ('dbname=\'%s\' user=\'%s\' host=\'%s\' password=\'%s\''
% (db_name, db_user, db_host, db_password))
return psycopg2.connect(connection_str).cursor()
def main():
# Register the adapter for the location
psycopg2.extensions.register_adapter(Coordinate, _adapt_coordinate)
cursor = _connect_to_db(DB_NAME, DB_USER, DB_HOST, DB_PASSWORD)
# Create a dummy entry
entry = PVT_Error_Entry(
key=PVT_Error_Key(station='GKIR',
location=Coordinate(lat=12, lon=10),
service='E1'),
north=1, east=2)
# Insert the dummy entry in the database
cursor.execute(
'INSERT INTO %s '
'(KEY, NORTH, EAST) '
'VALUES((%%s, %%s, %%s), %%s, %%s)'
% PVT_TABLE_NAME,
(entry.key.station, entry.key.location, entry.key.service,
entry.north, entry.east))
# Retrieve and print all entries of the database
cursor.execute('SELECT * FROM %s', (PVT_TABLE_NAME))
rows = cursor.fetchall()
print(rows)
if __name__ == '__main__':
main()
您不能在主键中使用 point
类型的列,例如:
create table my_table(location point primary key);
ERROR: data type point has no default operator class for access method "btree"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
报错信息很清楚,需要为类型创建一个完整的btree operatorclass。
此答案中描述了完整的过程:
更新。使用您在评论中提到的解决方法
create table my_table(
x numeric,
y numeric,
primary key (x, y));
insert into my_table values
(1.1, 1.2);
您可以随时创建一个视图,它可以像 table 一样被查询:
create view my_view as
select point(x, y) as location
from my_table;
select *
from my_view;
location
-----------
(1.1,1.2)
(1 row)