Postgresql порядок перекрытия массива - PullRequest
0 голосов
/ 05 мая 2020

Я хочу отсортировать таблицу с массивом. Рекорд с наибольшим количеством перекрытий должен быть сверху. У меня уже есть оператор where для фильтрации записей с помощью массивов. Этим же массивом хочу определить количество перекрытий для сортировки. У вас есть идеи, как может выглядеть инструкция order by?

Моя таблица

SELECT * FROM "nodes"
+-----------+---------------------------+
| name      |            tags           |
+-----------+---------------------------+
| Max       | ["foo", "orange", "app"]  |
| Peter     | ["foo", "bar", "baz"]     |
| Maria     | ["foo", "bar"]            |
| John      | ["apple"]                 |
+-----------+---------------------------+

Результат с where

SELECT * FROM "nodes" WHERE (tags && '{"foo", "bar", "baz"}')
+-----------+---------------------------+
| name      |            tags           |
+-----------+---------------------------+
| Max       | ["foo", "orange", "app"]  |
| Peter     | ["foo", "bar", "baz"]     |
| Maria     | ["foo", "bar"]            |
+-----------+---------------------------+

Результат с заказом

SELECT * FROM "nodes" WHERE (tags && '{"foo", "bar", "baz"}') ORDER BY ????
+-----------+---------------------------+
| name      |            tags           |
+-----------+---------------------------+
| Peter     | ["foo", "bar", "baz"]     |
| Maria     | ["foo", "bar"]            |
| Max       | ["foo", "orange", "app"]  |
+-----------+---------------------------+

Ответы [ 2 ]

1 голос
/ 05 мая 2020

Единственное, что я могу придумать, - это создать функцию, которая вычисляет количество общих элементов:

create or replace function num_overlaps(p_one text[], p_other text[])
  returns bigint
as
$$
  select count(*)
  from (
    select *
    from unnest(p_one)
    intersect   
    select *
    from unnest(p_other)
  ) x
$$
language sql
immutable;

Затем используйте ее в предложении order by:

SELECT *
FROM nodes 
WHERE tags && '{"foo", "bar", "baz"}'
order by num_overlaps(tags, '{"foo", "bar", "baz"}') desc;

Недостаток в том, что вам нужно повторить список тегов, которые вы тестируете.


Мне неясно, являются ли эти значения массивами JSON (потому что это синтаксис в образце данных) или собственными массивами Postgres (из-за оператора &&, который не работает для массивов JSON) - если вы используете jsonb, вы можете заменить unnest() на jsonb_array_elements_text()

0 голосов
/ 05 мая 2020

Прежде всего, массивы нужны для идентификаторов обеих сторон оператора &&, например STRING_TO_ARRAY(translate(tags::text, '[] "', ''), ',')::text[] вместо tags и STRING_TO_ARRAY('foo,bar,baz',',')) вместо '{"foo", "bar", "baz"}' шаблона соответственно.

Затем вы можете разложить элементы массива для столбца тегов, используя функцию JSON_ARRAY_ELEMENTS(), чтобы подсчитать появление каждого элемента возвращаемых value столбцов в шаблоне '{"foo", "bar", "baz"}' с помощью функций STRPOS() и SIGN() вместе с агрегированием SUM():

SELECT name, tags::text
  FROM "nodes" 
 CROSS JOIN JSON_ARRAY_ELEMENTS(tags) AS js
 WHERE ( STRING_TO_ARRAY(translate(tags::text, '[] "', ''), ',')::text[] 
      && STRING_TO_ARRAY('foo,bar,baz',','))
 GROUP BY name, tags::text    
 ORDER BY SUM( SIGN( STRPOS('{"foo", "bar", "baz"}'::text,value::text) ) ) DESC

Но у вас могут быть повторяющиеся элементы в столбце tags. В этом случае вышеуказанный запрос не выполняется. Итак, я предлагаю использовать этот ниже, содержащий строки, исключенные ключевым словом DISTINCT:

SELECT name, tags 
  FROM
  (
   SELECT DISTINCT name, tags::text, STRPOS('{"foo", "bar", "baz"}'::text,value::text)
     FROM "nodes" 
    CROSS JOIN JSON_ARRAY_ELEMENTS(tags) AS js
    WHERE ( STRING_TO_ARRAY(translate(tags::text, '[] "', ''), ',')::text[] 
         && STRING_TO_ARRAY('foo,bar,baz',','))
  ) n    
  GROUP BY name, tags::text    
  ORDER BY SUM( SIGN( strpos ) ) DESC

Demo

...