cube — тип данных для многомерных кубов
Этот модуль реализует тип данных cube для представления многомерных кубов.
Этот модуль считается «доверенным», то есть его могут устанавливать обычные пользователи с правом CREATE в текущей базе данных.
Синтаксис
В Таблице 1 показаны допустимые внешние представления типа cube. Буквы x, y и т. д. обозначают числа с плавающей запятой.
Таблица 1. Внешние представления кубов
| Внешний синтаксис | Значение |
|---|---|
| x | Одномерная точка (или одномерный интервал нулевой длины) |
| (x) | То же, что и выше |
| x1,x2,...,xn | Точка в n-мерном пространстве, представленная внутри как куб нулевого объема |
| (x1,x2,...,xn) | То же, что и выше |
| (x),(y) | Одномерный интервал, начинающийся в точке x и заканчивающийся в y, либо наоборот; порядок значения не имеет |
| [(x),(y)] | То же, что и выше |
| (x1,...,xn),(y1,...,yn) | n-мерный куб, представленный парой диагонально противоположных углов |
| [(x1,...,xn),(y1,...,yn)] | То же, что и выше |
В каком порядке вводятся противоположные углы куба, не имеет значения. Функции, принимающие тип cube, автоматически меняют углы местами, если нужно создать единое внутреннее представление «левый нижний — правый верхний». Когда эти углы совмещаются, в cube для экономии пространства хранится только один угол с флагом «является точкой».
Пробелы во входных данных игнорируются, так что [(x),(y)] не отличается от
[ ( x ), ( y ) ].
Точность
Значения хранятся внутри как 64-битные числа с плавающей запятой. Это означает, что числа с более чем 16 значащими цифрами будут усекаться.
Применение
Ниже приводятся специальные операторы, предоставляемые для работы с типом cube.
cube && cube → boolean
Кубы пересекаются?
cube @> cube → boolean
Первый куб содержит второй?
cube <@ cube → boolean
Первый куб содержится во втором?
cube -> integer → float8
Извлекает n-ю координату куба (считая с 1).
cube ~> integer → float8
Извлекает n-ю координату куба, считая следующим образом: n = 2 * k - 1 обозначает нижнюю границу куба k-й размерности, а n = 2 * k обозначает верхнюю границу k-й размерности. Отрицательные n обозначают обратное значение соответствующей положительной координаты. Этот оператор разработан для поддержки KNN-GiST.
cube <-> cube → float8
Вычисляет евклидово расстояние между двумя кубами.
cube <#> cube → float8
Вычисляет расстояние городских кварталов (метрику L-1) между двумя кубами.
cube <=> cube → float8
Вычисляет расстояние Чебышева (метрику L-бесконечность) между двумя кубами.
Помимо операторов, показанных выше, для типа cube имеются обычные операторы сравнения, показанные в подразделе Операторы сравнения. Эти операторы сначала сравнивают первые координаты, и если они равны, сравнивают вторые и т. д. Они предназначены в основном для поддержки класса операторов индекса В-дерева для типа cube, который может быть полезен, например, если вы хотите наложить ограничение UNIQUE на столбец типа cube. В других случаях эта сортировка не очень полезна с практической точки зрения.
Модуль cube также предоставляет класс операторов индекса GiST для значений
cube. Индекс GiST для cube можно использовать для поиска значений при помощи
операторов =, &&, @> и <@ в предложениях WHERE.
Кроме того, индекс GiST для cube можно использовать для поиска ближайших соседей
при помощи операторов метрики <->, <#> и <=> в предложениях ORDER BY.
Например, ближайшего соседа точки в трехмерном пространстве (0.5, 0.5, 0.5)
можно эффективно найти так:
SELECT c FROM test ORDER BY c <-> cube(array[0.5,0.5,0.5]) LIMIT 1;
Оператор ~> можно также использовать таким образом, чтобы эффективно выбрать
первые несколько значений, отсортированных по выбранной координате. Например,
чтобы получить первые несколько кубов, упорядоченных по возрастанию первой
координаты (левого нижнего угла), можно использовать следующий запрос:
SELECT c FROM test ORDER BY c ~> 1 LIMIT 5;
А чтобы получить двумерные кубы, упорядоченные по убыванию первой координаты (правого верхнего угла):
SELECT c FROM test ORDER BY c ~> 3 DESC LIMIT 5;
Ниже приводятся все доступные функции для работы с кубами.
cube ( float8 ) → cube
Создает одномерный куб, у которого обе координаты равны.
Пример:
cube(1) → (1)
cube ( float8, float8 ) → cube
Создает одномерный куб.
Пример:
cube(1, 2) → (1),(2)
cube ( float8[] ) → cube
Создает куб нулевого объема, используя координаты, определяемые массивом.
Пример:
cube(ARRAY[1,2,3]) → (1, 2, 3)
cube ( float8[], float8[] ) → cube
Создает куб с координатами правого верхнего и левого нижнего углов, определяемыми двумя массивами, которые должны быть одинаковой длины.
Пример:
cube(ARRAY[1,2], ARRAY[3,4]) → (1, 2),(3, 4)
cube ( cube, float8 ) → cube
Создает новый куб, добавляя размерность к существующему кубу с одинаковым значением новой координаты для обоих углов. Это полезно для поэтапного построения кубов из вычисляемых значений.
Пример:
cube('(1,2),(3,4)'::cube, 5) → (1, 2, 5),(3, 4, 5)
cube ( cube, float8, float8 ) → cube
Создает новый куб, добавляя размерность к существующему кубу. Это полезно для поэтапного построения кубов из вычисляемых значений.
Пример:
cube('(1,2),(3,4)'::cube, 5, 6) → (1, 2, 5),(3, 4, 6)
cube_dim ( cube ) → integer
Возвращает число размерностей куба.
Пример:
cube_dim('(1,2),(3,4)') → 2
cube_ll_coord ( cube, integer ) → float8
Возвращает значение n-й координаты левого нижнего угла куба.
Пример:
cube_ll_coord('(1,2),(3,4)', 2) → 2
cube_ur_coord ( cube, integer ) → float8
Возвращает значение n-й координаты правого верхнего угла куба.
Пример:
cube_ur_coord('(1,2),(3,4)', 2) → 4
cube_is_point ( cube ) → boolean
Возвращает true, если куб является точкой, то есть если два определяющих его угла совпадают.
Пример:
cube_is_point(cube(1,1)) → t
cube_distance ( cube, cube ) → float8
Возвращает расстояние между двумя кубами. Если оба куба являются точками, вычисляется обычная функция расстояния.
Пример:
cube_distance('(1,2)', '(3,4)') → 2.8284271247461903
cube_subset ( cube, integer[] ) → cube
Создает новый куб из существующего, используя список индексов размерностей из массива. Может применяться для получения конечных точек в одном измерении, для удаления измерений или изменения их порядка.
Примеры:
cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[2]) → (3),(7)
cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[3,2,1,1]) → (5, 3, 1, 1),(8, 7, 6, 6)
cube_union ( cube, cube ) → cube
Создает объединение двух кубов.
Пример:
cube_union('(1,2)', '(3,4)') → (1, 2),(3, 4)
cube_inter ( cube, cube ) → cube
Создает пересечение двух кубов.
Пример:
cube_inter('(1,2)', '(3,4)') → (3, 4),(1, 2)
cube_enlarge ( c cube, r double, n integer ) → cube
Увеличивает размер куба на заданный радиус r как минимум в n измерениях. Если радиус отрицательных, куб, наоборот, уменьшается. Все определенные размерности изменяются на величину радиуса r. Координаты левого нижнего угла уменьшаются на r, а координаты правого верхнего угла увеличиваются на r. Если координата левого нижнего угла становится больше соответствующей координаты правого верхнего угла (это происходит, только когда r < 0), обе координаты устанавливаются в свое среднее значение. Если n превышает число определенных размерностей и куб увеличивается (r > 0), то добавляются дополнительные размерности, недостающие до n; в качестве начального значения для дополнительных координат используется 0. Эта функция полезна для создания вокруг точки ограничивающих прямоугольников с целью поиска ближайших точек.
Пример:
cube_enlarge('(1,2),(3,4)', 0.5, 3) → (0.5, 1.5, -0.5),(3.5, 4.5, 0.5)
Поведение по умолчанию
Это объединение:
select cube_union('(0,5,2),(2,3,1)', '0');
cube_union
-------------------
(0, 0, 0),(2, 5, 2)
(1 row)
не противоречит здравому смыслу, как и это пересечение:
select cube_inter('(0,-1),(1,1)', '(-2),(2)');
cube_inter
-------------
(0, 0),(1, 0)
(1 row)
Предполагается, что во всех бинарных операциях с кубами разных размерностей куб с меньшей размерностью будет декартовой проекцией; т. е. в опущенных в строковом представлении координатах находятся нули. Значит, показанные выше примеры равнозначны следующим:
cube_union('(0,5,2),(2,3,1)','(0,0,0),(0,0,0)');
cube_inter('(0,-1),(1,1)','(-2,0),(2,0)');
В следующем предикате включения применяется синтаксис точек, хотя фактически второй аргумент представляется внутри кубом. Этот синтаксис избавляет от необходимости определять отдельный тип точек и функции для предикатов (box,point).
select cube_contains('(0,0),(1,1)', '0.5,0.5');
cube_contains
--------------
t
(1 row)
Примечания
Во избежание некорректного применения этого типа число размерностей кубов ограничено значением 100. Если вы хотите его увеличить, его можно изменить в cubedata.h.
Регрессионный тест типа cube
CREATE EXTENSION cube;
-- Тест, не провалят ли какие-либо из наших классов операторов проверку amvalidate
SELECT amname, opcname
FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod
WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid);
--
-- тестирование функций ввода и вывода
--
-- Любое число (одномерная точка)
SELECT '1'::cube AS cube;
SELECT '-1'::cube AS cube;
SELECT '1.'::cube AS cube;
SELECT '-1.'::cube AS cube;
SELECT '.1'::cube AS cube;
SELECT '-.1'::cube AS cube;
SELECT '1.0'::cube AS cube;
SELECT '-1.0'::cube AS cube;
SELECT 'infinity'::cube AS cube;
SELECT '-infinity'::cube AS cube;
SELECT 'NaN'::cube AS cube;
SELECT '.1234567890123456'::cube AS cube;
SELECT '+.1234567890123456'::cube AS cube;
SELECT '-.1234567890123456'::cube AS cube;
-- простые списки (точки)
SELECT '()'::cube AS cube;
SELECT '1,2'::cube AS cube;
SELECT '(1,2)'::cube AS cube;
SELECT '1,2,3,4,5'::cube AS cube;
SELECT '(1,2,3,4,5)'::cube AS cube;
-- двойные списки (кубы)
SELECT '(),()'::cube AS cube;
SELECT '(0),(0)'::cube AS cube;
SELECT '(0),(1)'::cube AS cube;
SELECT '[(0),(0)]'::cube AS cube;
SELECT '[(0),(1)]'::cube AS cube;
SELECT '(0,0,0,0),(0,0,0,0)'::cube AS cube;
SELECT '(0,0,0,0),(1,0,0,0)'::cube AS cube;
SELECT '[(0,0,0,0),(0,0,0,0)]'::cube AS cube;
SELECT '[(0,0,0,0),(1,0,0,0)]'::cube AS cube;
-- недопустимые входные данные: ошибки при разборе
SELECT ''::cube AS cube;
SELECT 'ABC'::cube AS cube;
SELECT '[]'::cube AS cube;
SELECT '[()]'::cube AS cube;
SELECT '[(1)]'::cube AS cube;
SELECT '[(1),]'::cube AS cube;
SELECT '[(1),2]'::cube AS cube;
SELECT '[(1),(2),(3)]'::cube AS cube;
SELECT '1,'::cube AS cube;
SELECT '1,2,'::cube AS cube;
SELECT '1,,2'::cube AS cube;
SELECT '(1,)'::cube AS cube;
SELECT '(1,2,)'::cube AS cube;
SELECT '(1,,2)'::cube AS cube;
-- недопустимые входные данные: семантические ошибки и мусор в конце
SELECT '[(1),(2)],'::cube AS cube; -- 0
SELECT '[(1,2,3),(2,3)]'::cube AS cube; -- 1
SELECT '[(1,2),(1,2,3)]'::cube AS cube; -- 1
SELECT '(1),(2),'::cube AS cube; -- 2
SELECT '(1,2,3),(2,3)'::cube AS cube; -- 3
SELECT '(1,2),(1,2,3)'::cube AS cube; -- 3
SELECT '(1,2,3)ab'::cube AS cube; -- 4
SELECT '(1,2,3)a'::cube AS cube; -- 5
SELECT '(1,2)('::cube AS cube; -- 5
SELECT '1,2ab'::cube AS cube; -- 6
SELECT '1 e7'::cube AS cube; -- 6
SELECT '1,2a'::cube AS cube; -- 7
SELECT '1..2'::cube AS cube; -- 7
SELECT '-1e-700'::cube AS cube; -- за пределами диапазона
-- Также проверить их API, не выдающим ошибки
SELECT pg_input_is_valid('(1,2)', 'cube');
SELECT pg_input_is_valid('[(1),]', 'cube');
SELECT pg_input_is_valid('-1e-700', 'cube');
SELECT * FROM pg_input_error_info('-1e-700', 'cube');
--
-- Тестирование построения кубов из значений float8
--
SELECT cube(0::float8);
SELECT cube(1::float8);
SELECT cube(1,2);
SELECT cube(cube(1,2),3);
SELECT cube(cube(1,2),3,4);
SELECT cube(cube(cube(1,2),3,4),5);
SELECT cube(cube(cube(1,2),3,4),5,6);
--
-- Проверить, было ли установлено приведение из text в cube.
--
SELECT '(0)'::text::cube;
--
-- Проверка приведения из float[] в cube
--
SELECT cube('{0,1,2}'::float[], '{3,4,5}'::float[]);
SELECT cube('{0,1,2}'::float[], '{3}'::float[]);
SELECT cube(NULL::float[], '{3}'::float[]);
SELECT cube('{0,1,2}'::float[]);
SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[3,2,1,1]);
SELECT cube_subset(cube('(1,3,5),(1,3,5)'), ARRAY[3,2,1,1]);
SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[4,0]);
SELECT cube_subset(cube('(6,7,8),(6,7,8)'), ARRAY[4,0]);
-- проверка пределов: это должно пройти ее
SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,100)));
-- а это завершится ошибкой
SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,101)));
--
-- Тестирование обработки точек
--
SELECT cube('(1,2),(1,2)'); -- cube_in
SELECT cube('{0,1,2}'::float[], '{0,1,2}'::float[]); -- cube_a_f8_f8
SELECT cube('{5,6,7,8}'::float[]); -- cube_a_f8
SELECT cube(1.37); -- cube_f8
SELECT cube(1.37, 1.37); -- cube_f8_f8
SELECT cube(cube(1,1), 42); -- cube_c_f8
SELECT cube(cube(1,2), 42); -- cube_c_f8
SELECT cube(cube(1,1), 42, 42); -- cube_c_f8_f8
SELECT cube(cube(1,1), 42, 24); -- cube_c_f8_f8
SELECT cube(cube(1,2), 42, 42); -- cube_c_f8_f8
SELECT cube(cube(1,2), 42, 24); -- cube_c_f8_f8
--
-- Тестирование пределов проверки измерений CUBE_MAX_DIM в cube_in.
--
-- создать слишком большой куб из литерала
select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube;
select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube;
-- из массива
select cube(array(SELECT 0 as a FROM generate_series(1,101)));
select cube(array(SELECT 0 as a FROM generate_series(1,101)),array(SELECT 0 as a FROM generate_series(1,101)));
-- расширить куб за пределы лимита
-- это должно сработать
select cube(array(SELECT 0 as a FROM generate_series(1,100)));
select cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100)));
-- это должно завершиться ошибкой
select cube(cube(array(SELECT 0 as a FROM generate_series(1,100))), 0);
select cube(cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))), 0, 0);
--
-- тестирование операторов
--
-- равенство/неравенство:
--
SELECT '24, 33.20'::cube = '24, 33.20'::cube AS bool;
SELECT '24, 33.20'::cube != '24, 33.20'::cube AS bool;
SELECT '24, 33.20'::cube = '24, 33.21'::cube AS bool;
SELECT '24, 33.20'::cube != '24, 33.21'::cube AS bool;
SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool;
SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool;
-- "меньше чем" / "больше чем"
-- (эти операторы полезны только для упорядочивания)
--
SELECT '1'::cube > '2'::cube AS bool;
SELECT '1'::cube < '2'::cube AS bool;
SELECT '1,1'::cube > '1,2'::cube AS bool;
SELECT '1,1'::cube < '1,2'::cube AS bool;
SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool;
SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool;
SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool;
SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool;
SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool;
SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool;
SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube > '(2,0),(3,1)'::cube AS bool;
SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube < '(2,0),(3,1)'::cube AS bool;
SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool;
SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool;
SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool;
SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool;
-- "перекрытие"
--
SELECT '1'::cube && '1'::cube AS bool;
SELECT '1'::cube && '2'::cube AS bool;
SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '0'::cube AS bool;
SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1'::cube AS bool;
SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1,1,1'::cube AS bool;
SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1,1),(2,2,2)]'::cube AS bool;
SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1),(2,2)]'::cube AS bool;
SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(2,1,1),(2,2,2)]'::cube AS bool;
-- "содержится в" (левый операнд — это куб, который полностью окружен
-- правым операндом):
--
SELECT '0'::cube <@ '0'::cube AS bool;
SELECT '0,0,0'::cube <@ '0,0,0'::cube AS bool;
SELECT '0,0'::cube <@ '0,0,1'::cube AS bool;
SELECT '0,0,0'::cube <@ '0,0,1'::cube AS bool;
SELECT '1,0,0'::cube <@ '0,0,1'::cube AS bool;
SELECT '(1,0,0),(0,0,1)'::cube <@ '(1,0,0),(0,0,1)'::cube AS bool;
SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1),(1,1,1)'::cube AS bool;
SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1,-1),(1,1,1,1)'::cube AS bool;
SELECT '0'::cube <@ '(-1),(1)'::cube AS bool;
SELECT '1'::cube <@ '(-1),(1)'::cube AS bool;
SELECT '-1'::cube <@ '(-1),(1)'::cube AS bool;
SELECT '(-1),(1)'::cube <@ '(-1),(1)'::cube AS bool;
SELECT '(-1),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool;
SELECT '(-2),(1)'::cube <@ '(-1),(1)'::cube AS bool;
SELECT '(-2),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool;
-- "содержит" (левый операнд — это куб, который полностью окружает
-- правый операнд)
--
SELECT '0'::cube @> '0'::cube AS bool;
SELECT '0,0,0'::cube @> '0,0,0'::cube AS bool;
SELECT '0,0,1'::cube @> '0,0'::cube AS bool;
SELECT '0,0,1'::cube @> '0,0,0'::cube AS bool;
SELECT '0,0,1'::cube @> '1,0,0'::cube AS bool;
SELECT '(1,0,0),(0,0,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool;
SELECT '(-1,-1,-1),(1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool;
SELECT '(-1,-1,-1,-1),(1,1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool;
SELECT '(-1),(1)'::cube @> '0'::cube AS bool;
SELECT '(-1),(1)'::cube @> '1'::cube AS bool;
SELECT '(-1),(1)'::cube @> '-1'::cube AS bool;
SELECT '(-1),(1)'::cube @> '(-1),(1)'::cube AS bool;
SELECT '(-1,-1),(1,1)'::cube @> '(-1),(1)'::cube AS bool;
SELECT '(-1),(1)'::cube @> '(-2),(1)'::cube AS bool;
SELECT '(-1,-1),(1,1)'::cube @> '(-2),(1)'::cube AS bool;
-- Текстирование функции distance
--
SELECT cube_distance('(0)'::cube,'(2,2,2,2)'::cube);
SELECT cube_distance('(0)'::cube,'(.3,.4)'::cube);
SELECT cube_distance('(2,3,4)'::cube,'(2,3,4)'::cube);
SELECT cube_distance('(42,42,42,42)'::cube,'(137,137,137,137)'::cube);
SELECT cube_distance('(42,42,42)'::cube,'(137,137)'::cube);
-- Тестирование функции cube (из text в cube)
--
SELECT cube('(1,1.2)'::text);
SELECT cube(NULL);
-- Тестирование функции cube_dim (размерности, хранимые в кубе)
--
SELECT cube_dim('(0)'::cube);
SELECT cube_dim('(0,0)'::cube);
SELECT cube_dim('(0,0,0)'::cube);
SELECT cube_dim('(42,42,42),(42,42,42)'::cube);
SELECT cube_dim('(4,8,15,16,23),(4,8,15,16,23)'::cube);
-- Тестирование функции cube_ll_coord (извлекает значения координат LL)
--
SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 1);
SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 2);
SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 3);
SELECT cube_ll_coord('(1,2),(1,2)'::cube, 1);
SELECT cube_ll_coord('(1,2),(1,2)'::cube, 2);
SELECT cube_ll_coord('(1,2),(1,2)'::cube, 3);
SELECT cube_ll_coord('(42,137)'::cube, 1);
SELECT cube_ll_coord('(42,137)'::cube, 2);
SELECT cube_ll_coord('(42,137)'::cube, 3);
-- Тестирование функции cube_ur_coord (извлекает значения координат UR)
--
SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 1);
SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 2);
SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 3);
SELECT cube_ur_coord('(1,2),(1,2)'::cube, 1);
SELECT cube_ur_coord('(1,2),(1,2)'::cube, 2);
SELECT cube_ur_coord('(1,2),(1,2)'::cube, 3);
SELECT cube_ur_coord('(42,137)'::cube, 1);
SELECT cube_ur_coord('(42,137)'::cube, 2);
SELECT cube_ur_coord('(42,137)'::cube, 3);
-- Тестирование функции cube_is_point
--
SELECT cube_is_point('(0)'::cube);
SELECT cube_is_point('(0,1,2)'::cube);
SELECT cube_is_point('(0,1,2),(0,1,2)'::cube);
SELECT cube_is_point('(0,1,2),(-1,1,2)'::cube);
SELECT cube_is_point('(0,1,2),(0,-1,2)'::cube);
SELECT cube_is_point('(0,1,2),(0,1,-2)'::cube);
-- Тестирование функции cube_enlarge (увеличение и сжатие кубов)
--
SELECT cube_enlarge('(0)'::cube, 0, 0);
SELECT cube_enlarge('(0)'::cube, 0, 1);
SELECT cube_enlarge('(0)'::cube, 0, 2);
SELECT cube_enlarge('(2),(-2)'::cube, 0, 4);
SELECT cube_enlarge('(0)'::cube, 1, 0);
SELECT cube_enlarge('(0)'::cube, 1, 1);
SELECT cube_enlarge('(0)'::cube, 1, 2);
SELECT cube_enlarge('(2),(-2)'::cube, 1, 4);
SELECT cube_enlarge('(0)'::cube, -1, 0);
SELECT cube_enlarge('(0)'::cube, -1, 1);
SELECT cube_enlarge('(0)'::cube, -1, 2);
SELECT cube_enlarge('(2),(-2)'::cube, -1, 4);
SELECT cube_enlarge('(0,0,0)'::cube, 1, 0);
SELECT cube_enlarge('(0,0,0)'::cube, 1, 2);
SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 1, 2);
SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 3, 2);
SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -1, 2);
SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -3, 2);
SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -23, 5);
SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -24, 5);
-- Тестирование функции cube_union (MBR для двух кубов)
--
SELECT cube_union('(1,2),(3,4)'::cube, '(5,6,7),(8,9,10)'::cube);
SELECT cube_union('(1,2)'::cube, '(4,2,0,0)'::cube);
SELECT cube_union('(1,2),(1,2)'::cube, '(4,2),(4,2)'::cube);
SELECT cube_union('(1,2),(1,2)'::cube, '(1,2),(1,2)'::cube);
SELECT cube_union('(1,2),(1,2)'::cube, '(1,2,0),(1,2,0)'::cube);
-- Тестирование функции cube_inter
--
SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (16,15)'::cube); -- пересекает
SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (6,5)'::cube); -- включает
SELECT cube_inter('(1,2),(10,11)'::cube, '(13,14), (16,15)'::cube); -- пересечения нет
SELECT cube_inter('(1,2),(10,11)'::cube, '(3,14), (16,15)'::cube); -- пересечения нет, но одна размерность пересекает
SELECT cube_inter('(1,2),(10,11)'::cube, '(10,11), (16,15)'::cube); -- пересечение точки
SELECT cube_inter('(1,2,3)'::cube, '(1,2,3)'::cube); -- аргументы точки
SELECT cube_inter('(1,2,3)'::cube, '(5,6,3)'::cube); -- аргументы точки
-- Тестирование функции cube_size
--
SELECT cube_size('(4,8),(15,16)'::cube);
SELECT cube_size('(42,137)'::cube);
-- Тестирование расстояний (евклидово кодовое расстояние может не быть точным до бита)
--
SET extra_float_digits = 0;
SELECT cube_distance('(1,1)'::cube, '(4,5)'::cube);
SELECT '(1,1)'::cube <-> '(4,5)'::cube as d_e;
RESET extra_float_digits;
SELECT distance_chebyshev('(1,1)'::cube, '(4,5)'::cube);
SELECT '(1,1)'::cube <=> '(4,5)'::cube as d_c;
SELECT distance_taxicab('(1,1)'::cube, '(4,5)'::cube);
SELECT '(1,1)'::cube <#> '(4,5)'::cube as d_t;
-- ноль для перекрытия
SELECT cube_distance('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
SELECT distance_chebyshev('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
-- обращение к координатам
SELECT cube(array[10,20,30], array[40,50,60])->1;
SELECT cube(array[40,50,60], array[10,20,30])->1;
SELECT cube(array[10,20,30], array[40,50,60])->6;
SELECT cube(array[10,20,30], array[40,50,60])->0;
SELECT cube(array[10,20,30], array[40,50,60])->7;
SELECT cube(array[10,20,30], array[40,50,60])->-1;
SELECT cube(array[10,20,30], array[40,50,60])->-6;
SELECT cube(array[10,20,30])->3;
SELECT cube(array[10,20,30])->6;
SELECT cube(array[10,20,30])->-6;
-- "нормализованное" обращение к координатам
SELECT cube(array[10,20,30], array[40,50,60])~>1;
SELECT cube(array[40,50,60], array[10,20,30])~>1;
SELECT cube(array[10,20,30], array[40,50,60])~>2;
SELECT cube(array[40,50,60], array[10,20,30])~>2;
SELECT cube(array[10,20,30], array[40,50,60])~>3;
SELECT cube(array[40,50,60], array[10,20,30])~>3;
SELECT cube(array[40,50,60], array[10,20,30])~>0;
SELECT cube(array[40,50,60], array[10,20,30])~>4;
SELECT cube(array[40,50,60], array[10,20,30])~>(-1);
-- Загрузка некоторых данных для примера и построение индекса
--
CREATE TABLE test_cube (c cube);
\copy test_cube from 'data/test_cube.data'
CREATE INDEX test_cube_ix ON test_cube USING gist (c);
SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' ORDER BY c;
-- Тестирование сортировки
SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c;
-- Тестирование сканирований только по индексу
SET enable_bitmapscan = false;
EXPLAIN (COSTS OFF)
SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c;
SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c;
RESET enable_bitmapscan;
-- Тестирование kNN
INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Некоторые крайние случаи
SET enable_seqscan = false;
-- Тестирование различных метрик
SET extra_float_digits = 0;
SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
RESET extra_float_digits;
SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
-- Тестирование сортировки по координатам
SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- возрастание по левой границе
SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- возрастание по правой границе
SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- возрастание по нижней границе
SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- возрастание по верхней границе
SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- убывание по левой границе
SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- убывание по правой границе
SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- убывание по нижней границе
SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- убывание по верхней границе
-- Те же запросы, но с последовательным сканированием (должны выдать те же результаты, что и выше)
RESET enable_seqscan;
SET enable_indexscan = OFF;
SET extra_float_digits = 0;
SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5;
RESET extra_float_digits;
SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5;
SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5;
SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- возрастание по левой границе
SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- возрастание по правой границе
SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- возрастание по нижней границе
SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- возрастание по верхней границе
SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- убывание по левой границе
SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- убывание по правой границе
SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- убывание по нижней границе
SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- убывание по верхней границе
RESET enable_indexscan;
Благодарности
Оригинальный автор: Джин Селков мл. (Gene Selkov, Jr.), selkovjr@mcs.anl.gov, Аргоннская национальная лаборатория, Отдел математики и компьютерных наук.
Я благодарен в первую очередь профессору Джо Геллерштейну (Joe Hellerstein) (https://dsf.berkeley.edu/jmh/) за пояснение сути GiST (http://gist.cs.berkeley.edu/) и его бывшему студенту Энди Донгу (Andy Dong) за пример, написанный для Illustra. Я также признателен всем разработчикам Postgres в настоящем и прошлом за возможность создать мой собственный мир и спокойно жить в нем. Еще я хотел бы выразить признательность Аргоннской лаборатории и Министерству энергетики США за годы постоянной поддержки моих исследований в области баз данных.
Небольшие изменения в этот пакет внес Бруно Вольф III (Bruno Wolff III) (bruno@wolff.to) в августе/сентябре 2002 г. В том числе он перешел от одинарной к двойной точности и добавил несколько новых функций.
Дополнительные изменения внес Джошуа Рейх (Joshua Reich) (josh@root.net) в июле 2006 г. В частности, он добавил cube(float8[], float8[]), подчистил код и перевел его на протокол вызовов версии V1 с устаревшего протокола V0.