Как построить запрос диапазона в кассандре?

CREATE TABLE users ( 
userID uuid, 
firstname text, 
lastname text, 
state text, 
zip int,
age int,
PRIMARY KEY (userID) 
);

Я хочу построить следующие запросы:

select * from users where age between 30 and 40

select * from users where state in "AZ" AND "WA"

Я знаю, мне нужно еще две таблицы для этого запроса, но я не знаю, как это должно быть?

ИЗМЕНИТЬ

От комментариев Карло, я вижу, что это единственная возможность

CREATE TABLE users ( 
userID uuid, 
firstname text, 
lastname text, 
state text, 
zip int,
age int,
PRIMARY KEY (age,zip,userID) 
);

Теперь выберите пользователей с возрастом от 15 до 30. Это единственная возможность:

select * from users where age IN (15,16,17,....30)

Однако использование IN-оператора здесь не рекомендуется и является анти-шаблоном.

Как насчет создания вторичного индекса по возрасту?

CREATE index users_age ON users(age)

поможет ли это?

Спасибо

Ответ 1

Запросы по диапазонам - это прикольный вопрос. Способ выполнения запроса реального диапазона заключается в использовании составного первичного ключа, который делает диапазон для части кластеризации. Поскольку диапазон находится в части кластеризации, вы не можете выполнять запрошенные вами запросы: вам нужно, по крайней мере, иметь равное условие для всего ключа раздела. Рассмотрим пример:

CREATE TABLE users (
  mainland text,
  state text,
  uid int,
  name text,
  zip int,
  PRIMARY KEY ((mainland), state, uid)
) 

Теперь uid является int просто для облегчения тестирования

insert into users (mainland, state, uid, name, zip) VALUES ( 'northamerica', 'washington', 1, 'john', 98100);
insert into users (mainland, state, uid, name, zip) VALUES ( 'northamerica', 'texas', 2, 'lukas', 75000);
insert into users (mainland, state, uid, name, zip) VALUES ( 'northamerica', 'delaware', 3, 'henry', 19904);
insert into users (mainland, state, uid, name, zip) VALUES ( 'northamerica', 'delaware', 4, 'dawson', 19910);
insert into users (mainland, state, uid, name, zip) VALUES ( 'centraleurope', 'italy', 5, 'fabio', 20150);
insert into users (mainland, state, uid, name, zip) VALUES ( 'southamerica', 'argentina', 6, 'alex', 10840);

Теперь запрос может выполнить то, что вам нужно:

 select * from users where mainland = 'northamerica' and state > 'ca' and state < 'ny';

Выход

 mainland    | state    | uid | name   | zip
-------------+----------+-----+--------+-------
northamerica | delaware |   3 |  henry | 19904
northamerica | delaware |   4 | dawson | 19910

если вы поместили int (age, zipcode) в качестве первого столбца ключа кластеризации, вы можете выполнять одни и те же запросы, сравнивая целые числа.

УБЕДИТЕСЬ: большинство людей, глядя на эту ситуацию, начинают думать: "Хорошо, я могу поставить поддельный ключ раздела, который всегда один и тот же, а затем я могу выполнять запросы диапазона". Это огромная ошибка, ключ раздела отвечает за распределение данных по узлам. Установка ключа фиксированного раздела означает, что все данные будут завершены в том же node (и в его реплике).

Разделение мировой зоны на зоны 15/20 (для того, чтобы иметь ключ раздела 15/20) - это нечто, но этого недостаточно, и сделано только для создания допустимого примера.


EDIT: из-за изменения вопроса

Я не сказал, что это единственная возможность; если вы не можете найти правильный способ разделить пользователей и выполнить такой запрос, это одна из возможностей, а не единственная. Запросы диапазона должны выполняться при кластеризации ключевой части. Слабой точкой AGE в качестве ключа раздела является то, что вы не можете выполнить ОБНОВЛЕНИЕ над ним, в любое время, когда вам нужно обновить возраст пользователя, который вы должны выполнить, удалить и вставить (альтернативой может быть запись birth_year/birth_date, а не возраст, а затем рассчитать клиентскую сторону)

Чтобы ответить на вопрос о добавлении вторичного индекса: на самом деле запросы по вторичному индексу не поддерживают оператор IN. Из сообщения CQL, похоже, скоро они будут развиваться.

Неверный запрос: предикаты IN для столбцов не первичного ключа (xxx) еще не установлены поддерживается

Однако, даже если вторичный индекс будет поддерживать оператор IN, ваш запрос не изменится с

select * from users where age IN (15,16,17,....30)

Просто, чтобы прояснить мою концепцию: все, что не имеет "чистого" и "готового" решения, требует усилий пользователя моделировать данные таким образом, чтобы удовлетворить его потребности. Чтобы сделать пример (я не говорю, что это хорошее решение: я бы не использовал его)

CREATE TABLE users (
  years_range text,
  age int,
  uid int,
  PRIMARY KEY ((years_range), age, uid)
)

введите некоторые данные

insert into users (years_range, age , uid) VALUES ( '11_15', 14, 1);
insert into users (years_range, age , uid) VALUES ( '26_30', 28, 3);
insert into users (years_range, age , uid) VALUES ( '16_20', 16, 2);
insert into users (years_range, age , uid) VALUES ( '26_30', 29, 4);
insert into users (years_range, age , uid) VALUES ( '41_45', 41, 5);
insert into users (years_range, age , uid) VALUES ( '21_25', 23, 5);

данные запроса

select * from users where years_range in('11_15', '16_20', '21_25', '26_30') and age > 14 and age < 29;

Выход

 years_range | age | uid
-------------+-----+-----
       16_20 |  16 |   2
       21_25 |  23 |   5
       26_30 |  28 |   3

Это решение может решить вашу проблему и может быть использовано в небольшом кластере, где около 20 ключей (0_5... 106_110) могут иметь хороший дистрибутив. Но это решение, как и раньше, не позволяет ОБНОВИТЬ и уменьшает распределение ключа. Преимущество состоит в том, что у вас есть небольшие блоки IN.

В идеальном мире, где SI уже разрешает предложение IN, я бы использовал UUID в качестве ключа раздела years_range (задано как birth_year_range) как SI и "фильтровал" мою клиентскую часть данных (если вас интересует возраст 10 > 22 я просил бы IN('1991_1995', '1996_2000', '2001_2005', '2006_2010', '2011_2015') вычислять и удалять неиспользуемые годы в моем приложении)

НТН, Carlo

Ответ 2

Я обнаружил, что используя allow filtering, я могу запросить диапазон: Пример:

 CREATE TABLE users2 (
  mainland text,
  state text,
  uid int,
  name text,
  age int,
  PRIMARY KEY (uid, age, state)
) ;

insert into users2 (mainland, state, uid, name, age) VALUES ( 'northamerica', 'washington', 1, 'john', 81);
insert into users2 (mainland, state, uid, name, age) VALUES ( 'northamerica', 'texas', 1, 'lukas', 75);
insert into users2 (mainland, state, uid, name, age) VALUES ( 'northamerica', 'delaware', 1, 'henry', 19);
insert into users2 (mainland, state, uid, name, age) VALUES ( 'northamerica', 'delaware', 4, 'dawson', 90);
insert into users2 (mainland, state, uid, name, age) VALUES ( 'centraleurope', 'italy', 5, 'fabio', 50);
insert into users2 (mainland, state, uid, name, age) VALUES ( 'southamerica', 'argentina', 6, 'alex', 40);

select * from users2 where age>50 and age<=100 allow filtering;

    uid | age | state      | mainland     | name
-----+-----+------------+--------------+--------
   1 |  75 |      texas | northamerica |  lukas
   1 |  81 | washington | northamerica |   john
   2 |  75 |      texas | northamerica |  lukas
   4 |  90 |   delaware | northamerica | dawson

(4 rows)

Я не уверен, что это убийца производительности. Но это, похоже, работает. Infact, мне даже не нужно давать primary key, который uid в этом случае во время query execution