Фильтрация и агрегирование графовых данных в Microsoft Fabric

Фильтрация сужает результаты до строк, которые имеют значение. Агрегирование преобразует эти строки в подсчеты, итоги и средние значения. В этой статье показано, как применять оба метода в запросах GQL к графу в Microsoft Fabric.

В примерах используется пример набора данных социальной сети. Описание процесса выполнения запросов и инструкций от начала до конца см. в руководстве по языку GQL.

Используйте эту статью для фильтрации строк и шаблонов, сгруппированной и несгруппированной агрегации, фильтров для агрегатных функций и условной маршрутизации. Для построения многошаговых шаблонов и выбора путей см. раздел Создание запросов с шаблонами графов. Готовые к адаптации бизнес-задачи см. в разделе Написание типовых GQL-запросов.

Необходимые условия

Фильтрация строк с помощью FILTER

Используется FILTER для хранения только строк, удовлетворяющих условию. Поместите FILTER после MATCH, чтобы сузить количество соответствующих результатов.

Следующий запрос возвращает всех женщин с их именем и днем рождения:

MATCH (p:Person)
FILTER p.gender = 'female'
RETURN p.firstName, p.lastName, p.birthday

Объедините несколько условий с AND и OR. Например, следующий запрос возвращает имена всех женщин, родившихся до 1990 года:

MATCH (p:Person)
FILTER p.gender = 'female' AND p.birthday < 19900101
RETURN p.firstName, p.lastName

Tip

Используйте встроенное WHERE предложение, когда условие определяет, какой узел или ребро может участвовать в матче. Используйте FILTER, когда условие применяется к строке, созданной более ранней инструкцией. Расположение предикатов может изменять результаты поиска кратчайшего пути; см. Размещайте предикаты до или после выбора пути.

Фильтрация во время сопоставления шаблонов

Встроенные WHERE предложения внутри MATCH шаблона ограничивают, какие узлы и рёбра подходят для совпадения. Оптимизатор запросов может применять эквивалентные предикаты при сканировании, поэтому встроенный синтаксис не является по своей сути быстрее, чем отдельный FILTER. Выберите форму, которая выражает задуманную семантику.

Например, чтобы найти людей, родившихся до 1994 года, и компанию, в которой они работают, ограничивая результаты компаниями, название которых начинается на "A":

MATCH (p:Person WHERE p.birthday < 19940101)-[:workAt]->(c:Company WHERE c.name STARTS WITH 'A')
RETURN p.firstName, p.lastName, c.name

Фильтруйте граничные свойства таким же образом. Например, чтобы выбрать только тех, кто начал работать в компании в 2000 году или позднее.

MATCH (p:Person)-[w:workAt WHERE w.workFrom >= 2000]->(c:Company)
RETURN p.firstName, p.lastName, c.name, w.workFrom

Для получения дополнительной информации о выборе между встроенной и послематчевой фильтрацией см. Оптимизировать производительность запросов GQL.

Обработка значений NULL в фильтрах

GQL использует трехзначную логику: предикаты оцениваются как TRUE, FALSEили UNKNOWN. Если значение свойства равно NULL, сравнения возвращаются UNKNOWN. FILTER трактует UNKNOWN как не соответствующий, поэтому строка исключается.

Используйте IS NULL и IS NOT NULL проверьте значения NULL явным образом:

-- Only include people whose browser is known
MATCH (p:Person)
FILTER p.browserUsed IS NOT NULL
RETURN p.firstName, p.browserUsed

Используется coalesce() для замены значения по умолчанию, если свойство может иметь значение NULL:

MATCH (p:Person)
RETURN p.firstName, coalesce(p.browserUsed, 'Unknown browser') AS browser

Предостережение

NULL = NULL вычисляет значение UNKNOWN, а не TRUE. Всегда используется IS NULL для проверки значений NULL, а не равенства.

Агрегировать результаты с помощью RETURN

Используйте агрегатные функции в RETURN для суммирования ваших результатов. Graph поддерживает следующие агрегатные функции: MAX, COLLECT_ONE, COLLECT_LIST, COLLECT_ELEMENTS, MIN, AVG, SUM и COUNT.

Например, чтобы подсчитать всех людей в графе:

MATCH (p:Person)
RETURN count(*) AS totalPeople

Чтобы подсчитать уникальные значения, например, сколько отдельных компаний используют людей:

MATCH (p:Person)-[:workAt]->(c:Company)
RETURN count(DISTINCT c) AS companyCount

Вход фильтра для одного агрегата

Добавляйте FILTER (WHERE predicate) после агрегата, чтобы фильтровать только входные данные этого агрегата. Другие агрегаты в том же RETURN по-прежнему получают все входные строки.

MATCH (p:Person)
RETURN count(*) AS allPeople,
       count(*) FILTER (WHERE p.birthday < 19900101) AS peopleBornBefore1990

Добавьте LIMIT в агрегатный фильтр, чтобы использовать не более указанного числа подходящих строк. Запрос применяет предикат перед ограничением, относящимся к агрегатной функции.

MATCH (p:Person)
RETURN count(*) FILTER (WHERE p.birthday < 19900101 LIMIT 100) AS sampleCount

FILTER и LIMIT, специфичные для агрегатов, не удаляют строки из остальной части запроса. Используйте инструкцию FILTER или предикат на уровне WHERE, если нужно отфильтровать поток строк.

Собрать значения

Используйте COLLECT_LIST возврат одного элемента списка для каждого входного значения, включая null-значения. Добавляйте DISTINCT , чтобы удалить дубликаторы.

MATCH (p:Person)
RETURN collect_list(DISTINCT p.browserUsed) AS browsers

COLLECT_ONE возвращает одно ненулевое входное значение. Выбранное значение не является детерминированным, поэтому используйте его только тогда, когда любое значение из группы приемлемо.

COLLECT_ELEMENTS принимает входные данные в виде списков и объединяет их элементы в один список:

MATCH (p:Person)
RETURN collect_elements([p.firstName, p.lastName]) AS names

Без группировки столбцов агрегированный запрос без входных строк возвращает пустой список из COLLECT_LIST и COLLECT_ELEMENTS и null из COLLECT_ONE.

Группировка результатов с помощью GROUP BY

Используйте GROUP BY в RETURN для группировки строк по общим значениям и вычисления агрегатов в каждой группе. Это группирование является эквивалентом GQL SQL GROUP BY.

Например, для подсчета сотрудников на компанию:

MATCH (p:Person)-[:workAt]->(c:Company)
LET companyId = c.id, companyName = c.name
RETURN companyId, companyName, count(*) AS employeeCount
GROUP BY companyId, companyName
ORDER BY employeeCount DESC

Группировать данные по нескольким столбцам и одновременно вычислять различные агрегаты. Например, разбиение числа пользователей и диапазона дней рождения по полу и браузеру, возвращая 10 наиболее распространенных сочетаний:

MATCH (p:Person)
LET gender = p.gender
LET browser = p.browserUsed
RETURN gender,
       browser,
       count(*) AS personCount,
       min(p.birthday) AS earliestBirthday,
       max(p.birthday) AS latestBirthday
GROUP BY gender, browser
ORDER BY personCount DESC
LIMIT 10

Note

Все не агрегатные выражения в RETURN должны присутствовать в GROUP BY. Выражения, которые не находятся в GROUP BY, должны использовать агрегатную функцию.

Сортировка и ограничение агрегированных результатов

Используйте ORDER BY и LIMIT вместе с GROUP BY, чтобы найти топ-N результаты.

Например, чтобы найти первые пять городов по количеству жителей:

MATCH (p:Person)-[:isLocatedIn]->(city:City)
LET cityId = city.id, cityName = city.name
RETURN cityId, cityName, count(*) AS residentCount
GROUP BY cityId, cityName
ORDER BY residentCount DESC
LIMIT 5

Это важно

Место ORDER BY перед LIMIT. LIMIT всегда применяется к уже отсортированному результирующему набору.

Направлять строки с помощью условных операторов

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

MATCH (p:Person)-[:workAt]->(c:Company)
LET companyId = c.id, companyName = c.name
RETURN companyId, companyName, count(*) AS employeeCount
GROUP BY companyId, companyName
NEXT
WHEN employeeCount >= 100u THEN
  RETURN companyId, companyName, employeeCount, 'Large' AS category
WHEN employeeCount >= 10u THEN
  RETURN companyId, companyName, employeeCount, 'Medium' AS category
ELSE
  RETURN companyId, companyName, employeeCount, 'Small' AS category

Ветки могут выполнять полноценные операторы запросов и вложенные процедуры, а не только возвращать выражения. Следующий пример отправляет три входных строки через разные формы ветвей. Одна ветвь запускает MATCH, одна объединяет два агрегата с UNION ALL и затем агрегирует их результаты, а резервная ветка возвращает константу:

FOR selector IN [1, 2, 3]
RETURN selector
NEXT
WHEN selector = 1 THEN
  MATCH (:Person)-[:knows]->(:Person)
  RETURN count(*) AS total, 'Knows edges' AS category
WHEN selector = 2 THEN {
  MATCH (:Person)-[:workAt]->(:Company)
  RETURN count(*) AS partialCount
  UNION ALL
  MATCH (:Person)-[:studyAt]->(:University)
  RETURN count(*) AS partialCount
  NEXT
  RETURN sum(partialCount) AS total, 'Affiliation edges' AS category
}
ELSE
  RETURN 0u AS total, 'No selected metric' AS category

Движок запросов оценивает предикаты в порядке для каждой строки. Он работает только на первой подходящей ветке. Если предикат вычисляется как UNKNOWN, вычисление продолжается со следующей ветвью. Без ELSE, строки, не совпадающие с WHEN ветвью, опускаются.

Условные утверждения отличаются от CASE выражений. Graph поддерживает простые CASE <expression> WHEN <value> выражения для выбора значений на основе равенства. Поисковые CASE WHEN <predicate> выражения не поддерживаются. Для получения дополнительной информации см. раздел Условные операторы.