Введение
Всем привет! Меня зовут Александр Андреев, я старший SRE дата-инженер и бывший BI/DWH-разработчик с многолетним опытом работы с BI-платформой QlikView/Qlik Sense. В своей большой статье-шпаргалке я хочу поделиться с вами практически всеми возможными вопросами и ответами с собеседований на должность Qlik-разработчика. Данная шпаргалка гарантированно закроет 99% возможных вопросов на собеседованиях на позиции, где упоминается Qlik в качестве BI-системы. Такихпозиций с каждым годом все больше, причем знание Qlik в качестве BI‑системы требуют как с чистых «биайщиков», так и с дата‑инженеров на некоторых сеньорских и lead позициях.
Подготовка к собеседованию на позицию QlikView/Qlik Sense разработчика, на мой взгляд, требует глубокого понимания как базовых концепций, так и продвинутых техник работы с платформой. В этой статье я собрал 85 наиболее важных вопросов, которые помогут вам систематизировать знания и успешно пройти техническое интервью.
Каждый ответ содержит не только теоретическое объяснение, но и практические примеры, которые демонстрируют реальное применение концепций Qlik. Вопросы организованы по темам, начиная с базовых и постепенно переходя к более сложным аспектам разработки.
Итак, поехали!
Базовые концепции
1. В чем основные различия между QlikView и Qlik Sense?
Архитектурные различия:
QlikView следует концепции «guided analytics» — разработчик создает фиксированные дашборды с предопределенной навигацией
Qlik Sense построен на принципах «self‑service BI» — пользователи могут самостоятельно создавать визуализации
Интерфейс и адаптивность:
QlikView имеет фиксированный размер листов (обычно1024×768 или кастомный лист)
Qlik Sense автоматически адаптируется под любые устройства
Технологическая база:
QlikView: классическое desktop-приложение с web-доступом
Qlik Sense: изначально web-based архитектура с HTML5
API и расширяемость:
QlikView: ограниченные возможности расширения через макросы и расширения
Qlik Sense: мощный REST API, WebSocket Engine API, современные web-технологии
2. Что такое ассоциативная модель данных Qlik?
Ассоциативная модель — это фундаментальная концепция, отличающая Qlik от традиционных BI‑инструментов. Ассоциативная модель — это:
In-memory технология: Все данные загружаются в оперативную память, что обеспечивает мгновенные вычисления
Автоматические связи: Таблицы связываются по полям с одинаковыми именами без явного указания JOIN
Двунаправленные ассоциации: В отличие от SQL, где JOIN работает в одном направлении, здесь связи работают в обе стороны
Цветовая индикация состояний связей:
Зеленый (Selected): Активно выбранные значения
Белый (Possible): Доступные для выбора значения, связанные с текущей выборкой
Серый (Excluded): Исключенные значения, не имеющие связи с выборкой
Светло-серый (Alternative): Альтернативные значения в том же поле
Практический пример:
Если в дашборде выбрать "2024" в поле Year:
- Все продажи 2024 года станут белыми (возможные значения)
- Клиенты, купившие в 2024, станут белыми
- Продукты, проданные в 2024, станут белыми
- Данные других лет станут серыми (исключенные значения)
3. Какие типы лицензий существуют в Qlik Sense?
Правильное понимание лицензирования важно для планирования развертывания и бюджетирования проектов в организации, где планируется развернуть Qlik. На моем предыдущем месте работы необходимость каждого вида лицензии для сотрудника приходилось обосновывать собственноручно. Итак, вот какие лицензии есть у Клика:
User-based лицензии:
Professional:
Создание и редактирование приложений
Публикация в стримах
Полный доступ к Data Load Editor
Создание и управление задачами перезагрузки
Analyzer:
Доступ только к опубликованным приложениям
Создание закладок и историй
Базовые self-service возможности (создание визуализаций в листах)
Не может редактировать загрузочный скрипт приложения
Capacity-based лицензии:
Analyzer Capacity:
Пул минут для анонимных или нечастых пользователей
Автоматическое выделение времени доступа
Идеально для больших групп пользователей с редким доступом
Минуты не сгорают и переносятся на следующий период
Token подход:
Временные лицензии для конкретных сессий
Используется для embedded analytics
Программное управление через API
4. Что такое QVD файл и каковы его преимущества?
QVD (QlikView Data) файлы — это особый формат сжатых файлов данных в Qlik.
Структура QVD:
XML заголовок с метаданными (структура таблицы, количество записей)
Бинарные данные в сжатом формате
Символьные таблицы для оптимизации хранения текста
Ключевые преимущества:
Скорость загрузки:
В 10-100 раз быстрее других источников
Оптимизированная загрузка при простых WHERE условиях
Пример: 10 млн записей из QVD грузятся в Qlik 5 секунд, а из БД - 5 минут
Компрессия:
Сжатие до 90% от исходного размера
Особенно эффективно для повторяющихся значений
Автоматическая оптимизация типов данных
QVD идеальны для инкрементальной загрузки:
// Загрузка только новых записей
NewData:
LOAD * FROM Database WHERE ModifiedDate > '$(vLastLoadDate)';
// Объединение с историческими данными
Concatenate (NewData)
LOAD * FROM Historical.qvd (qvd) WHERE NOT Exists(ID);
// Сохранение обновленного QVD
STORE NewData INTO Historical.qvd (qvd);
А еще QVD нужны для:
Снижения нагрузки на другие системы хранения и обработки данных
Возможности работы при недоступности источников данных
Использования в качестве централизованного хранилища данных, если у вас такого хранилища нет
5. Объясните понятие синтетических ключей
Синтетические ключи — это автоматический механизм Qlik для разрешения множественных связей между таблицами. Важно понимать, когда они возникают и как с ними работать.
Механизм возникновения:
Когда две таблицы имеют два или более общих поля, Qlik создает скрытую таблицу с префиксом $Syn:
// Таблица Orders
OrderID, CustomerID, ProductID, OrderDate
// Таблица Shipments
ShipmentID, CustomerID, ProductID, ShipDate
// Результат: $Syn1 таблица содержащая CustomerID+ProductID
Почему синтетические ключи - это плохо для приложения?
Производительность: Дополнительная таблица увеличивает сложность модели данных
Память: Хранение всех комбинаций значений может быть избыточным
Понимание: Усложняет логику
Непредсказуемость: Могут возникать неожиданные связи и циклические ссылки
Решения:
// Способ 1: Составной ключ
Orders:
LOAD
OrderID,
CustomerID & '|' & ProductID as Key,
OrderDate
FROM ...;
// Способ 2: Переименование полей
Orders:
LOAD
OrderID,
CustomerID as Order_CustomerID,
ProductID as Order_ProductID,
OrderDate
FROM ...;
// Способ 3: Удаление лишних полей
DROP Field ProductID FROM Shipments;
6. Что такое циклические ссылки?
Циклические ссылки возникают, когда таблицы связаны таким образом, что буквально образуется замкнутый круг на схеме данных. Это критическая проблема, которую необходимо решать.
Почему Qlik не допускает циклы:
Неоднозначность путей при работе ассоциативного движка
Бесконечные циклы при вычислениях
Непредсказуемые результаты агрегаций
Методы обнаружения:
Table Viewer показывает красные линии (в более ранних версиях Qlik на схеме данных просто виден цикл)
Сообщение об ошибке при загрузке
$Syn таблицы часто указывают на потенциальные циклы
Стратегии решения:
// 1. Разрыв цикла с помощью функции QUALIFY, которая позволяет игнорировать список полей при работе ассоциативного движка
QUALIFY CustomerID;
LOAD * FROM Regions;
UNQUALIFY CustomerID;
// 2. Link Table - создаем отдельную промежуточную таблицу
LinkTable:
LOAD DISTINCT
OrderID,
CustomerID,
RegionID
FROM Orders;
// 3. Денормализация
Orders:
LOAD
o.*,
c.CustomerName,
r.RegionName
FROM Orders o
LEFT JOIN Customers c ON o.CustomerID = c.CustomerID
LEFT JOIN Regions r ON c.RegionID = r.RegionID;
7. Какие режимы отображения данных существуют в Qlik?
Понимание состояний данных критично для интерпретации визуализаций и отладки логики приложений.
Основные состояния:
Selected (Выбранные) — Зеленый цвет:
Значения, явно выбранные пользователем
Активная выборка, влияющая на все вычисления
Можно отменить через Clear или Back
Possible (Возможные) — Белый цвет:
Значения, связанные с текущей выборкой
Доступны для дополнительного выбора
Показывают «что еще можно выбрать»
Excluded (Исключенные) — Серый цвет:
Значения без связи с текущей выборкой
Недоступны для выбора в текущем контексте
Индикатор «что отфильтровано»
Alternative (Альтернативные) — Светло‑серый:
Другие значения в том же поле, где есть выборка
Показывают альтернативные варианты выбора
Помогают понять контекст выборки
Дополнительные состояния в Qlik Sense:
SelectedExcluded — выбранные, но исключенные другими выборками
Locked — заблокированные значения
8. Что такое Master Items в Qlik Sense?
Master Items — это централизованная библиотека переиспользуемых компонентов, обеспечивающая консистентность и упрощающая поддержку приложения. Не стоит делать много разных мер и измерений — лучше сделать мастер-меры и мастер-измерения.
Типы Master Items:
Master Dimensions (Мастер-измерения):
// Простое измерение
ProductCategory
// Вычисляемое измерение
=If(Sales > 1000, 'High', 'Low')
// Drill-down измерение
Country > Region > City
Master Measures (Мастер-меры):
// С форматированием и цветом
Sum(Sales)
// Number formatting: #,##0
// Color: rgb(77,166,255)
// С модификаторами
Sum({<Year={$(vCurrentYear)}>} Sales)
Master Visualizations:
Сохраненные диаграммы с настройками, которые можно переиспользовать в приложении неоднократно
При обновлении мастер-визуализации обновятся все ее копии в приложении
Преимущества использования мастер-айтемов:
Консистентность: Одинаковые вычисления во всем приложении
Поддержка: Изменение в одном месте обновляет все остальные мастер-айтемы
Переиспользование: Экономия времени разработки
Документирование: Каждый мастер-айтем можно задокументировать внутри него
9. Как работает кэширование в Qlik?
Существуют разные уровни кэширования:
Result Cache (Кэш результатов):
Хранит результаты вычисленных выражений
Ключ кэша: выражение + контекст выборки
Очищается при изменении выборки
Associative Cache (Ассоциативный кэш):
Кэширует состояния possible/excluded
Ускоряет навигацию по модели
Обновляется инкрементально
Calculation Cache:
Промежуточные результаты сложных вычислений
Особенно эффективен для Aggr() функций
Управление кэшем:
// Принудительный пересчет (обход кэша)
=Sum(Sales) * (1 + Rand()*0.000001)
// Buffer Load - создается QVD с определенным временем жизни
Buffer (Expire 7) LOAD * FROM LargeTable;
// Incremental reload - перезагрузка данных будет только в режиме частичной загрузки (Partial Reload)
ADD ONLY LOAD * FROM NewData;
Лучшие практики:
Использовать одни и те же выражения для попадания в кэш
Избегать использование функций (Now(), Today()) в мерах
Выносить тяжелые вычисления в загрузочный скрипт, а не в меры в визуализациях
Использовать переменные для часто используемых выражений
10. Что такое раздел Section Access?
Section Access - это механизм безопасности для ограничения доступа к данным на уровне строк.
Section Access;
ACCESS:
LOAD * INLINE [
ACCESS, USERID, REGION
ADMIN, ADMIN1, *
USER, USER1, Europe
USER, USER2, Asia
];
Section Application;
Включить ограничения можно в настройках документа: «Opening» → «Initial Data Reduction Based on Section Access».
Объяснение:
ACCESS: ADMIN (полный доступ) или USER (ограниченный).
USERID: Имя пользователя (совпадает с логином QlikView).
REGION: Поле, ограничивающее данные (например, пользователь видит только свою зону).
*: Доступ ко всем значениям поля. Пример: USER1 увидит только данные, где REGION = 'Europe'.
Важные особенности:
Поля должны быть в UPPER CASE
(звездочка) означает все значения
ADMIN видит всё, USER — только свои данные
Проверка безопасности:
// Всегда оставляйте запасной ADMIN доступ
LOAD * INLINE [
ACCESS, USERID
ADMIN, INTERNAL\SA_ADMIN
];
Загрузочные скрипты
11. Какие есть способы загрузки данных в Qlik?
Основные методы загрузки:
Direct Load — прямая загрузка из источников:
// Из базы данных
LIB CONNECT TO 'PostgreSQL_Production';
LOAD CustomerID, CustomerName, Country
SQL SELECT * FROM Customers WHERE Active = 1;
// Из файлов
LOAD * FROM [lib://DataFiles/Sales.xlsx]
(ooxml, embedded labels, table is Sheet1);
Resident Load — загрузка из уже загруженных таблиц:
// Используется для трансформаций
TempTable:
LOAD * FROM Source.qvd (qvd);
FinalTable:
LOAD
CustomerID,
Upper(CustomerName) as CustomerName,
Country
RESIDENT TempTable
WHERE Country <> 'Unknown';
DROP TABLE TempTable;
Inline Load — ввод данных прямо в скрипте:
// Для справочников и mapping таблиц
Priorities:
LOAD * INLINE [
Priority, PriorityNum, Color
High, 3, Red
Medium, 2, Yellow
Low, 1, Green
];
From Field — загрузка из поля:
// Получение структурированных данных из поля
LOAD
CustomerID,
SubField(Tags, ',') as Tag
FROM Customers.qvd (qvd);
Binary Load — загрузка всей модели данных из qvf-файла приложения:
// Должен быть первой строкой в скрипте
Binary [lib://Apps/BaseDataModel.qvf];
// Дополнительные данные
LOAD * FROM AdditionalData.qvd (qvd);
12. Объясните разницу между JOIN, KEEP и CONCATENATE
JOIN — объединение таблиц в одну:
// Left Join - все записи из левой таблицы
Orders:
LOAD OrderID, CustomerID, Amount FROM Orders.csv;
LEFT JOIN (Orders)
LOAD CustomerID, CustomerName, Country FROM Customers.csv;
// Результат: Все заказы, даже если клиент не найден
// Inner Join - только совпадающие записи
INNER JOIN (Orders)
LOAD CustomerID, CustomerName FROM Customers.csv;
// Результат: Только заказы с существующими клиентами
// Right Join - все записи из правой таблицы
RIGHT JOIN (Orders)
LOAD CustomerID, CustomerName FROM Customers.csv;
// Результат: Все клиенты, даже без заказов
// Outer Join - все записи из обеих таблиц
OUTER JOIN (Orders)
LOAD CustomerID, CustomerName FROM Customers.csv;
// Результат: Все заказы и все клиенты
KEEP — фильтрация с сохранением таблиц раздельными:
// Left Keep - оставляет записи левой таблицы с совпадениями
Orders:
LOAD OrderID, CustomerID, Amount FROM Orders.csv;
LEFT KEEP (Orders)
Customers:
LOAD CustomerID, CustomerName FROM Customers.csv;
// Результат: В Orders остаются только заказы существующих клиентов
// Таблицы остаются раздельными!
// Inner Keep - оставляет только совпадающие в обеих
INNER KEEP (Orders)
// Обе таблицы содержат только связанные записи
CONCATENATE — добавление записей в таблицу (вертикальное объединение):
Sales2023:
LOAD * FROM Sales_2023.csv;
CONCATENATE (Sales2023)
LOAD * FROM Sales_2024.csv;
// Результат: Одна таблица со всеми продажами за два года
// Автоматическая конкатенация при одинаковой структуре
// Принудительная конкатенация при разной структуре
CONCATENATE (Sales2023) LOAD ...;
// Предотвращение автоматической конкатенации
NOCONCATENATE LOAD ...;
13. Как реализовать инкрементальную загрузку?
Базовая стратегия INSERT only:
// 1. Определение последней загруженной даты
IF FileSize('lib://QVD/Sales.qvd') > 0 THEN
MaxDate:
LOAD
Max(ModifiedDate) as MaxModifiedDate
FROM [lib://QVD/Sales.qvd] (qvd);
LET vMaxDate = Peek('MaxModifiedDate', 0, 'MaxDate');
DROP TABLE MaxDate;
ELSE
LET vMaxDate = '1900-01-01';
END IF
// 2. Загрузка только новых записей
NewData:
LOAD
*,
Now() as LoadTimestamp
FROM [lib://Database/Sales]
WHERE ModifiedDate > '$(vMaxDate)';
// 3. Объединение с историческими данными
IF FileSize('lib://QVD/Sales.qvd') > 0 THEN
CONCATENATE (NewData)
LOAD *
FROM [lib://QVD/Sales.qvd] (qvd);
END IF
// 4. Сохранение обновленного QVD
STORE NewData INTO [lib://QVD/Sales.qvd] (qvd);
Стратегия INSERT и UPDATE:
// Загрузка измененных записей
UpdatedData:
LOAD
SalesID,
CustomerID,
Amount,
ModifiedDate
FROM Database
WHERE ModifiedDate > '$(vMaxDate)';
// Создание списка обновленных ID
UpdatedIDs:
LOAD DISTINCT
SalesID as UpdatedID
RESIDENT UpdatedData;
// Загрузка исторических данных, исключая обновленные
CONCATENATE (UpdatedData)
LOAD *
FROM [lib://QVD/Sales.qvd] (qvd)
WHERE NOT EXISTS(UpdatedID, SalesID);
DROP TABLE UpdatedIDs;
Стратегия с DELETE:
// Загрузка активных записей из источника
ActiveRecords:
LOAD
SalesID,
1 as IsActive
FROM Database
WHERE DeletedFlag = 0;
// Применение фильтра к историческим данным
FinalData:
LOAD *
FROM [lib://QVD/Sales.qvd] (qvd)
WHERE EXISTS(IsActive, SalesID);
DROP TABLE ActiveRecords;
14. Что такое Preceding Load и когда его использовать?
Preceding Load — это мощная техника, позволяющая применять трансформации к результатам предыдущего LOAD оператора. Это улучшает читаемость и производительность кода.
Концепция и синтаксис:
// Preceding Load (выполняется вторым)
LOAD
*,
Year(OrderDate) as OrderYear,
Month(OrderDate) as OrderMonth,
'Q' & Ceil(Month(OrderDate)/3) as Quarter,
If(Amount > 1000, 'Large', 'Small') as OrderSize;
// Base Load (выполняется первым)
LOAD
OrderID,
CustomerID,
Date(OrderDate) as OrderDate,
Amount
FROM Orders.csv;
Преимущества:
Читаемость: Логические трансформации отделены от загрузки
Переиспользование: Можно ссылаться на вычисленные поля
Сложные примеры:
// Многоуровневый Preceding Load
LOAD
*,
If(RunningTotal > 10000, 'Premium', 'Regular') as CustomerType;
LOAD
*,
RangeSum(Amount, Peek('RunningTotal')) as RunningTotal;
LOAD
CustomerID,
OrderDate,
Amount
FROM Orders.qvd (qvd)
ORDER BY CustomerID, OrderDate;
// С агрегацией
LOAD
CustomerID,
CustomerName,
If(TotalAmount > AvgAmount, 'Above Average', 'Below Average') as Performance;
LOAD
CustomerID,
CustomerName,
Sum(Amount) as TotalAmount,
Avg(Total Amount) as AvgAmount
FROM Orders.qvd (qvd)
GROUP BY CustomerID, CustomerName;
15. Как работает функция ApplyMap?
ApplyMap — это эффективная альтернатива JOIN для простых подстановок значений.
Создание и использование mapping таблицы:
// Шаг 1: Создание mapping таблицы (всегда 2 поля)
CountryNames:
MAPPING LOAD
CountryCode, // Ключ поиска
CountryName // Возвращаемое значение
FROM Countries.csv;
// Шаг 2: Использование в загрузке
Orders:
LOAD
OrderID,
CustomerID,
ApplyMap('CountryNames', CountryCode) as Country,
ApplyMap('CountryNames', CountryCode, 'Unknown Country') as CountryWithDefault,
Amount
FROM Orders.csv;
Продвинутые техники:
// Множественные mapping таблицы
CustomerSegments:
MAPPING LOAD
CustomerID,
Segment
FROM Segments.csv;
ProductCategories:
MAPPING LOAD
ProductID,
Category
FROM Categories.csv;
// Цепочки ApplyMap
Sales:
LOAD
*,
ApplyMap('CustomerSegments', CustomerID, 'New') as Segment,
ApplyMap('ProductCategories', ProductID, 'Other') as Category,
ApplyMap('PriceTiers',
ApplyMap('ProductCategories', ProductID),
'Standard'
) as PriceTier // Вложенный ApplyMap
FROM Sales.qvd;
// Mapping с вычислениями
PriceBands:
MAPPING LOAD
RecNo() as PriceLevel,
'Band ' & RecNo() as PriceBand
AUTOGENERATE 10;
Преимущества над JOIN:
Не изменяет количество строк (как LEFT JOIN)
Лучшая производительность на больших объемах
Есть возможность задать значение по умолчанию
Не создает синтетических ключей
16. Объясните работу с переменными в скрипте
Переменные в Qlik нужны для создания динамических и переиспользуемых решений. На собеседовании вас с вероятностью 95% спросят про разницу между SET и LET.
Основные операторы:
// SET - присваивает текст как есть (без вычисления)
SET vFormula = Sum(Sales);
SET vToday = Today();
// vToday содержит текст "Today()", а не дату
// LET - вычисляет выражение при присваивании
LET vFormula = 'Sum(Sales)'; // Нужны кавычки для текста
LET vToday = Today(); // vToday содержит числовое значение даты
LET vCurrentYear = Year(Today()); // 2024
// Конкатенация и вычисления
LET vStartDate = Date(MonthStart(Today()), 'YYYY-MM-DD');
LET vTableName = 'Sales_' & Year(Today());
Использование переменных:
// В путях и именах
LOAD * FROM [lib://Data/$(vTableName).qvd] (qvd);
// В WHERE условиях
LOAD * FROM Orders
WHERE OrderDate >= '$(vStartDate)';
// В вычислениях
LET vRowCount = NoOfRows('Orders');
TRACE Loaded $(vRowCount) orders;
// Переменные с параметрами (Qlik Sense)
SET vCalculateSales = Sum({<Year={$1}, Month={$2}>} Sales);
// Использование: $(vCalculateSales(2024, 'Jan'))
Динамические переменные:
// Создание переменных в цикле
FOR i = 1 TO 12
LET vMonth$(i) = Month(MakeDate(2024, $(i), 1));
// Создает: vMonth1='Jan', vMonth2='Feb', и т.д.
NEXT i
// Чтение переменных из таблицы
Variables:
LOAD * INLINE [
VarName, VarValue
vMaxSales, Max(Sales)
vAvgSales, Avg(Sales)
];
FOR i = 0 TO NoOfRows('Variables')-1
LET vName = Peek('VarName', $(i), 'Variables');
LET vValue = Peek('VarValue', $(i), 'Variables');
LET $(vName) = '$(vValue)';
NEXT i
17. Как обработать NULL значения при загрузке?
Глобальные настройки:
// Определение как NULL интерпретируется при загрузке
SET NullInterpret = ''; // Пустые строки считаются NULL
SET NullInterpret = '<NULL>'; // Строка '<NULL>' считается NULL
// Замена NULL на значение
NullAsValue *; // Для всех полей
SET NullValue = 'N/A'; // NULL заменяется на 'N/A'
// Или для конкретных полей
NullAsValue CustomerName, ProductName;
SET NullValue = 'Unknown';
Обработка в LOAD операторах:
Customers:
LOAD
CustomerID,
// Различные способы обработки NULL
If(IsNull(CustomerName), 'Unknown Customer', CustomerName) as CustomerName,
If(Len(Trim(Email)) = 0, 'green@elephant.ru', Email) as Email,
Alt(Phone, Mobile, 'No Phone') as ContactPhone, // Первое не-NULL значение
Coalesce(Discount, 0) as Discount, // SQL-style, ноль если NULL
RangeMin(Price, 9999999) as Price // Защита от NULL в вычислениях
FROM Customers.csv;
// Обработка NULL в датах
LOAD
OrderID,
If(IsNull(ShipDate), Date(OrderDate + 3), ShipDate) as ShipDate,
If(DeliveryDate < OrderDate or IsNull(DeliveryDate),
Date(OrderDate + 7),
DeliveryDate) as ValidDeliveryDate
FROM Orders.csv;
Специальные случаи:
// Восстановление NULL (если нужно)
NullAsNull Field1, Field2;
// Подсчет NULL значений
NullCount(FieldName) as NullRecords
// Фильтрация NULL
WHERE NOT IsNull(CustomerID)
WHERE Len(Trim(ProductName)) > 0
18. Что такое Crosstable Load?
Crosstable Load — это трансформация данных из широкого формата данных (кросс‑таблицы) в длинный формат, оптимальный для анализа в Qlik. Говоря простым языком, «разворот таблицы» (многие из читателей точно делали такую операцию в MS PowerQuery, например)
Базовый синтаксис:
// Исходные данные:
// Product | Jan | Feb | Mar
// A | 100 | 150 | 200
// B | 200 | 180 | 220
// Трансформация:
CrossTable(Month, Sales)
LOAD
Product,
Jan,
Feb,
Mar
FROM MonthlySales.xlsx;
// Результат:
// Product | Month | Sales
// A | Jan | 100
// A | Feb | 150
// A | Mar | 200
// B | Jan | 200
// B | Feb | 180
// B | Mar | 220
Продвинутое использование:
// С несколькими qualifying полями
CrossTable(MetricType, MetricValue, 3) // 3 поля остаются как есть
LOAD
Year,
Region,
Product,
Sales_Actual,
Sales_Budget,
Sales_Forecast,
Costs_Actual,
Costs_Budget
FROM Planning.xlsx;
// Результат включает Year, Region, Product как измерения
// MetricType: Sales_Actual, Sales_Budget, и т.д.
// MetricValue: числовые значения
// Обработка после Crosstable
NormalizedData:
LOAD
Year,
Region,
Product,
SubField(MetricType, '_', 1) as Metric, // Sales, Costs
SubField(MetricType, '_', 2) as Version, // Actual, Budget
MetricValue
RESIDENT TempCrosstable;
19. Как использовать For...Next циклы в скрипте?
Циклы в Qlik скрипте позволяют автоматизировать повторяющиеся операции и динамически обрабатывать данные.
Основные типы циклов:
// 1. Числовой цикл
FOR i = 1 TO 12
LET vMonthName = Month(MakeDate(2024, $(i), 1));
MonthlyData:
LOAD
'$(vMonthName)' as Month,
$(i) as MonthNum,
*
FROM [lib://Data/Sales_2024_$(i).xlsx]
(ooxml, embedded labels);
NEXT i
// 2. Цикл по списку значений
FOR Each vYear IN 2020, 2021, 2022, 2023, 2024
YearlyData:
LOAD
*,
'$(vYear)' as Year
FROM [lib://Data/Sales_$(vYear).qvd] (qvd);
NEXT vYear
// 3. Цикл по файлам
FOR Each vFile IN FileList('lib://Data/*.csv')
LET vFileName = SubField('$(vFile)', '/', -1);
TRACE Loading $(vFileName);
Data:
LOAD
*,
'$(vFileName)' as SourceFile
FROM [$(vFile)]
(txt, utf8, embedded labels, delimiter is ',');
NEXT vFile
Продвинутые техники:
// Цикл по значениям из таблицы
Tables:
LOAD * INLINE [
TableName, FilterCondition
Orders, Status='Active'
Customers, Country='USA'
];
FOR i = 0 TO NoOfRows('Tables')-1
LET vTable = Peek('TableName', $(i), 'Tables');
LET vFilter = Peek('FilterCondition', $(i), 'Tables');
$(vTable)_Filtered:
LOAD * FROM [lib://Data/$(vTable).qvd] (qvd)
WHERE $(vFilter);
NEXT i
// Вложенные циклы
FOR vYear = 2020 TO 2024
FOR vMonth = 1 TO 12
LET vPeriod = '$(vYear)' & Num($(vMonth), '00');
IF FileSize('lib://Data/Sales_$(vPeriod).qvd') > 0 THEN
Sales:
LOAD * FROM [lib://Data/Sales_$(vPeriod).qvd] (qvd);
END IF
NEXT vMonth
NEXT vYear
20. Объясните концепцию Partial Reload
Partial Reload позволяет обновлять только часть данных без полной перезагрузки приложения. Это критично для больших приложений и оперативных обновлений.
Основные команды:
// ADD - добавляет данные только при Partial Reload
ADD LOAD * FROM NewOrders.csv;
// Загрузка выполняется только при Partial Reload
ADD ONLY LOAD * FROM TodayOrders.csv;
// REPLACE - заменяет данные при Partial Reload
REPLACE LOAD * FROM UpdatedCustomers.csv;
// Выполняется только при Partial Reload с заменой
REPLACE ONLY LOAD * FROM Corrections.csv;
Практические примеры:
// Обновление текущего дня
IF IsPartialReload() THEN
// Удаляем данные текущего дня
WHERE Date <> Today();
// Добавляем свежие данные
ADD LOAD * FROM TodayTransactions.csv;
ELSE
// Полная загрузка
LOAD * FROM AllTransactions.qvd (qvd);
END IF
// Обновление справочников
Products:
LOAD * FROM Products.qvd (qvd);
REPLACE ONLY LOAD * FROM ProductUpdates.csv;
// Условная логика
IF IsPartialReload() THEN
LET vReloadType = 'Partial';
// Загружаем только изменения
ELSE
LET vReloadType = 'Full';
// Полная загрузка
END IF
Важные особенности:
Значительно быстрее полной перезагрузки
Не выполняет DROP TABLE команды
Binary Load игнорируется при Partial Reload
Моделирование данных
21. Какие типы схем данных вы знаете в Qlik?
Star Schema (Звезда):
// Центральная таблица фактов
Facts:
LOAD
OrderID,
CustomerID,
ProductID,
DateID,
Amount,
Quantity
FROM Orders.qvd;
// Таблицы измерений вокруг
Customers:
LOAD CustomerID, CustomerName, Country FROM Customers.qvd;
Products:
LOAD ProductID, ProductName, Category FROM Products.qvd;
Calendar:
LOAD DateID, Date, Year, Month, Quarter FROM Calendar.qvd;
Преимущества Star Schema:
Простота понимания
Оптимальная производительность
Минимум JOIN операций
Идеальна для ассоциативной модели Qlik
Snowflake Schema (Снежинка):
// Нормализованные измерения
Products:
LOAD ProductID, ProductName, SubCategoryID FROM Products.qvd;
SubCategories:
LOAD SubCategoryID, SubCategoryName, CategoryID FROM SubCategories.qvd;
Categories:
LOAD CategoryID, CategoryName FROM Categories.qvd;
Когда использовать Snowflake:
Очень большие справочники с иерархиями
Необходимость частого обновления справочных данных
Требования к нормализации данных
Link Table Schema:
// Для связи нескольких таблиц фактов
LinkTable:
LOAD DISTINCT
OrderID,
CustomerID,
ProductID,
DateID,
'Orders' as FactType
FROM Orders.qvd;
CONCATENATE (LinkTable)
LOAD DISTINCT
ShipmentID,
CustomerID,
ProductID,
DateID,
'Shipments' as FactType
FROM Shipments.qvd;
// Факты без измерений
OrderFacts:
LOAD OrderID, Amount, Quantity FROM Orders.qvd;
ShipmentFacts:
LOAD ShipmentID, Cost, DeliveryDays FROM Shipments.qvd;
22. Как создать календарь в Qlik?
Календарь — обязательный компонент почти любого Qlik приложения. Его можно скачать из имеющейся у вас БД, а можно создать вручную (я обычно выбирал первый вариант, но покажу и второй):
Базовый Master Calendar:
// 1. Определение границ дат
DateBounds:
LOAD
Min(OrderDate) as MinDate,
Max(OrderDate) as MaxDate
RESIDENT Orders;
LET vMinDate = Num(Peek('MinDate', 0, 'DateBounds'));
LET vMaxDate = Num(Peek('MaxDate', 0, 'DateBounds'));
DROP TABLE DateBounds;
// 2. Генерация календаря
MasterCalendar:
LOAD
Date as OrderDate, // Ключевое поле связи
// Базовые поля
Date,
Year(Date) as Year,
Quarter(Date) as Quarter,
'Q' & Quarter(Date) as QuarterName,
Month(Date) as Month,
MonthName(Date) as MonthYear,
Week(Date) as Week,
WeekDay(Date) as WeekDay,
Day(Date) as Day,
// Полезные флаги
If(Date = Today(), 1, 0) as IsToday,
If(Date >= MonthStart(Today()) and Date <= Today(), 1, 0) as IsMTD,
If(Date >= YearStart(Today()) and Date <= Today(), 1, 0) as IsYTD,
// Финансовый календарь (если FY начинается с июля)
If(Month(Date) >= 7, Year(Date) + 1, Year(Date)) as FiscalYear,
Mod(Month(Date) - 7, 12) + 1 as FiscalMonth,
// Дополнительные поля
Date(MonthStart(Date), 'MMM-YYYY') as MonthStart,
Date(MonthEnd(Date), 'DD/MM/YYYY') as MonthEnd,
WeekStart(Date) as WeekStart,
WeekEnd(Date) as WeekEnd
;
LOAD
Date($(vMinDate) + RecNo() - 1) as Date
AUTOGENERATE $(vMaxDate) - $(vMinDate) + 1;
Расширенный календарь с праздниками:
// Добавление праздников
Holidays:
LOAD * INLINE [
Date, Holiday
2025-01-01, New Year
2025-01-07, Christmas
];
LEFT JOIN (MasterCalendar)
LOAD
Date,
Holiday,
If(Len(Holiday) > 0, 1, 0) as IsHoliday
RESIDENT Holidays;
// Рабочие дни
LEFT JOIN (MasterCalendar)
LOAD
Date,
If(WeekDay(Date) >= 5 or IsHoliday = 1, 0, 1) as IsWorkingDay
RESIDENT MasterCalendar;
23. Что такое Link Table и когда её использовать?
Link Table — это техника моделирования для связи нескольких таблиц фактов через общие измерения, избегая синтетических ключей.
Проблема без Link Table:
// Две таблицы фактов с общими измерениями создадут синтетический ключ
Orders:
LOAD OrderID, CustomerID, ProductID, OrderDate, OrderAmount FROM ...;
Shipments:
LOAD ShipmentID, CustomerID, ProductID, ShipDate, ShipmentCost FROM ...;
// Результат: $Syn ключ из CustomerID + ProductID
Решение с Link Table:
// 1. Создаем уникальные ключи в таблице фактов
Orders:
LOAD
OrderID,
CustomerID & '|' & ProductID & '|' & OrderDate as LinkKey,
OrderAmount,
OrderQuantity
FROM Orders.qvd;
Shipments:
LOAD
ShipmentID,
CustomerID & '|' & ProductID & '|' & ShipDate as LinkKey,
ShipmentCost,
DeliveryDays
FROM Shipments.qvd;
// 2. Создаем Link Table
LinkTable:
LOAD DISTINCT
LinkKey,
CustomerID,
ProductID,
OrderDate as Date,
'Order' as TransactionType
FROM Orders.qvd;
CONCATENATE (LinkTable)
LOAD DISTINCT
LinkKey,
CustomerID,
ProductID,
ShipDate as Date,
'Shipment' as TransactionType
FROM Shipments.qvd;
// 3. Подключаем измерения к Link Table
Customers:
LOAD CustomerID, CustomerName FROM Customers.qvd;
Products:
LOAD ProductID, ProductName FROM Products.qvd;
Преимущества Link Table:
Избегаем синтетических ключей
Гибкость в анализе разных фактов
Возможность общего календаря для разных дат
Относительно простая модель данных
24. Как оптимизировать модель данных?
1. Удаление неиспользуемых полей:
// При загрузке
LOAD
OrderID,
CustomerID,
Amount
// Не загружаем ненужные поля
FROM Orders.qvd;
// После загрузки
DROP FIELDS Field1, Field2 FROM Table1;
DROP FIELD Field3; // Из всех таблиц
// Анализ использования
// Document Analyzer покажет неиспользуемые поля
2. Оптимизация типов данных:
// Числовые ключи вместо текстовых
Orders:
LOAD
AutoNumber(CustomerID & '|' & ProductID) as Key,
// AutoNumber создает последовательные числа и существенно ускоряет работу приложения
Amount
FROM Orders.qvd;
// Правильные форматы дат
Date(Floor(Timestamp)) as Date, // Убираем время
// Вместо полного timestamp для дневной аналитики
3. Денормализация vs Нормализация:
// Денормализация маленьких справочников
Orders:
LOAD
o.*,
p.ProductName,
p.Category,
c.CustomerName,
c.Country
FROM Orders.qvd (qvd) o
LEFT JOIN (
LOAD ProductID, ProductName, Category
FROM Products.qvd (qvd)
) p ON o.ProductID = p.ProductID
LEFT JOIN (
LOAD CustomerID, CustomerName, Country
FROM Customers.qvd (qvd)
) c ON o.CustomerID = c.CustomerID;
// Экономим на ассоциациях
4. Использование флагов вместо сложных выражений:
// В скрипте
Orders:
LOAD
*,
If(Amount > 1000, 1, 0) as IsLargeOrder,
If(OrderDate >= YearStart(Today()), 1, 0) as IsCurrentYear,
If(Status = 'Delivered' and DeliveryDate <= DueDate, 1, 0) as IsOnTime
FROM Orders.qvd;
// В выражениях просто Sum(Sales * IsLargeOrder)
// Вместо Sum(If(Amount > 1000, Sales))
25. Объясните концепцию Canonical Date
Canonical Date решает проблему анализа данных с множественными датами (дата заказа, дата отгрузки, дата оплаты и т.д.).
Проблема множественных дат:
// Нельзя использовать один календарь для разных дат
Orders:
LOAD
OrderID,
OrderDate,
ShipDate,
DeliveryDate,
PaymentDate
FROM Orders.qvd;
// Как анализировать "продажи по месяцам" - по какой дате?
Решение с Canonical Date:
// 1. Основная таблица фактов
Orders:
LOAD
OrderID,
CustomerID,
ProductID,
OrderDate,
ShipDate,
DeliveryDate,
PaymentDate,
Amount
FROM Orders.qvd;
// 2. Создаем Canonical Date Bridge
DateBridge:
// Дата заказа
LOAD
OrderID,
OrderDate as CanonicalDate,
'Order' as DateType,
Amount as Value
RESIDENT Orders;
// Дата отгрузки
CONCATENATE (DateBridge)
LOAD
OrderID,
ShipDate as CanonicalDate,
'Shipment' as DateType,
Amount as Value
RESIDENT Orders
WHERE NOT IsNull(ShipDate);
// Дата доставки
CONCATENATE (DateBridge)
LOAD
OrderID,
DeliveryDate as CanonicalDate,
'Delivery' as DateType,
Amount as Value
RESIDENT Orders
WHERE NOT IsNull(DeliveryDate);
// Дата оплаты
CONCATENATE (DateBridge)
LOAD
OrderID,
PaymentDate as CanonicalDate,
'Payment' as DateType,
Amount as Value
RESIDENT Orders
WHERE NOT IsNull(PaymentDate);
// 3. Единый календарь связывается с CanonicalDate
Calendar:
LOAD ... as CanonicalDate ...;
Использование в анализе:
Выбор DateType = 'Order' для анализа по датам заказов
Выбор DateType = 'Payment' для анализа по датам оплат
Сравнение метрик по разным типам дат
26. Как работать с медленно изменяющимися измерениями (SCD)?
Slowly Changing Dimensions (SCD) — это измерения, которые изменяются со временем.
Type1 — Перезапись (без истории):
// Простая замена старых значений новыми
Products:
LOAD
ProductID,
ProductName,
CurrentPrice,
Category
FROM Products.csv;
// История изменений не сохраняется
// Подходит для исправления ошибок
Type2 — Полная история:
// Таблица с историей изменений
ProductHistory:
LOAD
ProductID,
ProductName,
Price,
Category,
ValidFrom,
ValidTo,
If(IsNull(ValidTo), 1, 0) as IsCurrent,
ProductID & '|' & ValidFrom as ProductVersionKey
FROM ProductHistory.qvd;
// Для анализа на конкретную дату
Orders:
LOAD
OrderID,
ProductID,
OrderDate,
Amount
FROM Orders.qvd;
// Связь через IntervalMatch
IntervalMatch(OrderDate, ProductID)
LOAD
ValidFrom,
ValidTo,
ProductID
RESIDENT ProductHistory;
// Или через WHERE в Set Analysis
// Sum({<IsCurrent={1}>} Sales) - продажи по текущим ценам
// Sum({<ValidFrom={"<=$(vAnalysisDate)"}, ValidTo={">=$(vAnalysisDate)"}>} Sales)
Type 3 — Ограниченная история:
// Хранение текущего и предыдущего значения
Products:
LOAD
ProductID,
ProductName,
CurrentPrice,
PreviousPrice,
PriceChangeDate,
CurrentCategory,
PreviousCategory
FROM Products.csv;
// Анализ влияния изменений
PriceImpact:
LOAD
ProductID,
Sum(Amount) as Sales,
Avg(CurrentPrice - PreviousPrice) as AvgPriceChange
RESIDENT Orders
GROUP BY ProductID;
27. Что такое Generic Load?
Generic Load преобразует entity-attribute-value (EAV) структуру в отдельные таблицы для каждого атрибута. Хотим разбить одну таблицу на несколько — используем:
Проблема EAV структуры:
// Типовая EAV таблица
EntityAttributes:
LOAD * INLINE [
EntityID, Attribute, Value
P001, Color, Red
P001, Size, Large
P001, Weight, 2.5
P002, Color, Blue
P002, Size, Medium
P002, Weight, 1.8
];
// Ее сложно использовать в анализе
Решение с Generic Load:
// Generic автоматически создает отдельные таблицы
Generic LOAD
EntityID,
Attribute,
Value
FROM EntityAttributes.csv;
// Результат - отдельные таблицы:
// Color: EntityID, Color
// Size: EntityID, Size
// Weight: EntityID, Weight
// Объединение обратно в одну таблицу (если нужно)
CombinedAttributes:
LOAD DISTINCT EntityID FROM EntityAttributes.csv;
FOR Each vTable in 'Color', 'Size', 'Weight'
LEFT JOIN (CombinedAttributes)
LOAD * RESIDENT [$(vTable)];
DROP TABLE [$(vTable)];
NEXT vTable
Практическое применение:
// Для динамических атрибутов продуктов
ProductSpecs:
Generic LOAD
ProductID,
SpecName,
SpecValue
FROM ProductSpecifications.qvd
WHERE SpecName IN ('RAM', 'Storage', 'ScreenSize', 'Processor');
// Использование в анализе
// Автоматически можем фильтровать:
// RAM = '8GB'
// Processor = 'Intel i7'
28. Как реализовать иерархию в Qlik?
Иерархические структуры (сотрудники-менеджеры, категории-подкатегории и прочие) требуют специальной обработки для эффективного анализа.
Hierarchy функция:
// Исходные данные: EmployeeID, ManagerID
EmployeeHierarchy:
Hierarchy(
EmployeeID, // Child (id)
ManagerID, // Parent (id)
EmployeeName,
ParentName, // Parent (имя)
EmployeePath, // Отношение
PathDelimiter, // Разделитель пути
Depth // Уровень глубины
)
LOAD
EmployeeID,
ManagerID,
EmployeeName,
Title,
Department
FROM Employees.csv;
// Результат добавляет поля:
// Employee1 (CEO)
// Employee2 (VP)
// Employee3 (Manager)
// PathDelimiter: CEO/VP/Manager
// Depth: 3
HierarchyBelongsTo — обратная иерархия:
// Показывает всех подчиненных для каждого уровня
EmployeeHierarchyBelongsTo:
HierarchyBelongsTo(
EmployeeID,
ManagerID,
EmployeeName,
AncestorID,
AncestorName
)
LOAD
EmployeeID,
ManagerID,
EmployeeName
FROM Employees.csv;
// Позволяет анализировать:
// "Все продажи команды менеджера X, включая подчиненных"
Рекурсивная иерархия для больших данных:
// Для оптимизации больших иерархий
Level1:
LOAD
EmployeeID,
EmployeeName,
ManagerID,
1 as Level
FROM Employees.csv
WHERE IsNull(ManagerID); // CEO level
FOR vLevel = 2 TO 10 // Максимум 10 уровней
Level$(vLevel):
LOAD
e.EmployeeID,
e.EmployeeName,
e.ManagerID,
$(vLevel) as Level
FROM Employees.csv e
WHERE EXISTS(EmployeeID, e.ManagerID)
AND NOT EXISTS(EmployeeID, e.EmployeeID);
IF NoOfRows('Level$(vLevel)') = 0 THEN
EXIT FOR;
END IF
NEXT vLevel
// Объединение всех уровней
Hierarchy:
LOAD * RESIDENT Level1;
FOR vLevel = 2 TO 10
IF TableNumber('Level$(vLevel)') >= 0 THEN
CONCATENATE (Hierarchy)
LOAD * RESIDENT Level$(vLevel);
DROP TABLE Level$(vLevel);
END IF
NEXT vLevel
29. Как обрабатывать большие объемы данных?
1. Многоуровневая QVD архитектура:
// Слой 1: Raw QVDs (точная копия источника)
RawOrders:
LOAD * FROM Database;
STORE RawOrders INTO Raw_Orders_$(vToday).qvd (qvd);
// Слой 2: Transform QVDs (очистка, трансформация)
TransformOrders:
LOAD
OrderID,
Upper(CustomerID) as CustomerID,
Date(Floor(OrderDate)) as OrderDate,
Amount
FROM Raw_Orders_*.qvd (qvd)
WHERE Amount > 0;
STORE TransformOrders INTO Transform_Orders.qvd (qvd);
// Слой 3: Datamart QVDs (агрегированные данные)
OrdersSummary:
LOAD
CustomerID,
Year(OrderDate) as Year,
Month(OrderDate) as Month,
Sum(Amount) as TotalAmount,
Count(OrderID) as OrderCount
FROM Transform_Orders.qvd (qvd)
GROUP BY CustomerID, Year(OrderDate), Month(OrderDate);
STORE OrdersSummary INTO Datamart_OrdersSummary.qvd (qvd);
2. Инкрементальная загрузка с партиционированием:
// Партиции по месяцам
FOR vYear = 2020 TO Year(Today())
FOR vMonth = 1 TO 12
LET vPartition = '$(vYear)' & Num($(vMonth), '00');
IF FileSize('lib://QVD/Orders_$(vPartition).qvd') > 0 THEN
// Инкрементальное обновление партиции
MaxDate:
LOAD Max(ModifiedDate) as MaxDate
FROM [lib://QVD/Orders_$(vPartition).qvd] (qvd);
LET vMaxDate = Peek('MaxDate', 0, 'MaxDate');
DROP TABLE MaxDate;
NewData:
LOAD * FROM Database
WHERE Year(OrderDate) = $(vYear)
AND Month(OrderDate) = $(vMonth)
AND ModifiedDate > '$(vMaxDate)';
IF NoOfRows('NewData') > 0 THEN
CONCATENATE (NewData)
LOAD * FROM [lib://QVD/Orders_$(vPartition).qvd] (qvd)
WHERE NOT EXISTS(OrderID);
STORE NewData INTO [lib://QVD/Orders_$(vPartition).qvd] (qvd);
DROP TABLE NewData;
END IF
END IF
NEXT vMonth
NEXT vYear
3. On-Demand App Generation (ODAG):
// Пример приложения с параметрами
SET vCustomerSelection = $(odagCustomers);
SET vDateFrom = $(odagDateFrom);
SET vDateTo = $(odagDateTo);
// Загрузка только выбранных данных
DetailedData:
LOAD * FROM AllOrders.qvd (qvd)
WHERE Match(CustomerID, $(vCustomerSelection))
AND OrderDate >= '$(vDateFrom)'
AND OrderDate <= '$(vDateTo)';
30. Объясните концепцию As-Of таблиц
As-Of таблицы позволяют выполнять анализ по состоянию на дату и сравнивать различные временные периоды через таблицу.
Создание As-Of таблицы:
// Базовый календарь
Calendar:
LOAD
Date,
Year(Date) as Year,
Month(Date) as Month
FROM ...;
// As-Of таблица
AsOfTable:
// Текущий период
LOAD
Date as AsOfDate,
Date as Date,
'Current' as PeriodType,
0 as PeriodOffset
RESIDENT Calendar;
// Предыдущий день
CONCATENATE (AsOfTable)
LOAD
Date as AsOfDate,
Date - 1 as Date,
'Previous Day' as PeriodType,
-1 as PeriodOffset
RESIDENT Calendar;
// Предыдущая неделя
CONCATENATE (AsOfTable)
LOAD
Date as AsOfDate,
Date - 7 as Date,
'Previous Week' as PeriodType,
-7 as PeriodOffset
RESIDENT Calendar;
// Предыдущий месяц
CONCATENATE (AsOfTable)
LOAD
Date as AsOfDate,
AddMonths(Date, -1) as Date,
'Previous Month' as PeriodType,
-30 as PeriodOffset
RESIDENT Calendar;
// Предыдущий год
CONCATENATE (AsOfTable)
LOAD
Date as AsOfDate,
AddYears(Date, -1) as Date,
'Previous Year' as PeriodType,
-365 as PeriodOffset
RESIDENT Calendar;
Расширенный пример для скользящих периодов:
// Rolling As-Of
AsOfRolling:
// Предыдущие 7 дней
LOAD
Date as AsOfDate,
Date(Date - RecNo() + 1) as Date,
'Last 7 Days' as RollingPeriod
RESIDENT Calendar
WHILE RecNo() <= 7;
// Предыдущие 30 дней
CONCATENATE (AsOfRolling)
LOAD
Date as AsOfDate,
Date(Date - RecNo() + 1) as Date,
'Last 30 Days' as RollingPeriod
RESIDENT Calendar
WHILE RecNo() <= 30;
// Предыдущие 12 месяцев
CONCATENATE (AsOfRolling)
LOAD
Date as AsOfDate,
MonthStart(Date, -RecNo() + 1) as Date,
'Last 12 Months' as RollingPeriod
RESIDENT Calendar
WHILE RecNo() <= 12;
Использование:
Выбираем одну AsOfDate
Автоматически получаем данные за все связанные периоды
Можем сравнивать: Текущий vs Предыдущий период или анализировать тренды за последние недели, месяцы или годы
Визуализация и UI
31. Как создать динамическое название диаграммы?
Динамические заголовки делают дашборды более информативными.
Базовые техники:
// Отображение текущей выборки
='Sales Analysis for ' & GetFieldSelections(Year, ', ', 10)
// Результат: "Sales Analysis for 2023, 2024"
// С подсчетом выборок
='Revenue by ' &
If(GetSelectedCount(Product) = 0, 'All Products',
If(GetSelectedCount(Product) = 1, Only(Product),
GetSelectedCount(Product) & ' Products'))
// С форматированием чисел
='Total Sales: ' & Num(Sum(Sales), '#,##0') &
' | Growth: ' & Num((Sum(Sales)/Sum({<Year={$(=Max(Year)-1)}>}Sales)-1), '0.0%')
Продвинутые примеры:
// Множественные условия и метрики
='Dashboard: ' &
If(GetSelectedCount(Country) > 0,
GetFieldSelections(Country, ', ', 5) & ' | ', '') &
If(GetSelectedCount(Year) > 0,
'Years: ' & GetFieldSelections(Year, '-', 10) & ' | ', '') &
'Updated: ' & Date(ReloadTime(), 'DD/MM/YYYY hh:mm')
// С переменными и KPI
='Performance: ' &
If($(vCurrentSales) > $(vTargetSales),
'Above Target ✓',
'Below Target ✗') &
' (' & Num($(vCurrentSales) / $(vTargetSales), '0%') & ')'
// Многострочные заголовки
='Sales Trend Analysis' & Chr(10) &
'Period: ' & MonthName(Min(Date)) & ' - ' & MonthName(Max(Date)) & Chr(10) &
'Records: ' & Num(Count(Distinct OrderID), '#,##0')
32. Как реализовать drill-down функциональность?
Drill-down позволяет пользователям углубляться в данные от общего к частному.
Программный drill-down:
// Динамическое измерение на основе выборок
=If(GetSelectedCount(Country) = 0,
Country,
If(GetSelectedCount(Region) = 0,
Region,
If(GetSelectedCount(City) = 0,
City,
Store)))
// С различными иерархиями
=If(vDrillPath = 'Geographic',
Pick(vDrillLevel, Country, Region, City, Store),
Pick(vDrillLevel, Year, Quarter, Month, Week))
Визуальная индикация уровня:
// В заголовке диаграммы
='Sales by ' &
Pick(vDrillLevel, 'Country', 'Region', 'City', 'Store') &
' (Level ' & vDrillLevel & ' of 4)'
33. Объясните conditional show/hide в визуализациях
Условное отображение создает адаптивные дашборды, показывающие релевантную информацию в зависимости от контекста. Это полезно, например, если данных в дашборде очень много, и их стоит показать только если юзер выберет что-нибудь в фильтрах.
Базовые условия:
// Показать при наличии выборки
GetSelectedCount(Product) > 0
// Показать при конкретном значении
GetFieldSelections(ReportType) = 'Detailed'
// Показать для определенных пользователей
Match(OSUser(), 'DOMAIN\Manager1', 'DOMAIN\Manager2') > 0
// Комбинированные условия
GetSelectedCount(Year) = 1 AND Sum(Sales) > 1000000
Практические примеры:
// Показать детальную таблицу только при малой выборке
Count(Distinct Customer) <= 100
// Показать предупреждение при неполных данных
Count({<Status={'Pending'}>} OrderID) > 0
// Ролевое отображение
// В Section Access добавить поле ROLE
Lookup('ROLE', 'USERID', OSUser(), 'SecurityTable') = 'Manager'
Управление производительностью:
// Не показывать тяжелые визуализации при больших выборках
// Calculation condition
Count(Distinct Product) * Count(Distinct Customer) < 10000
// С сообщением для пользователя
Calculation condition: GetSelectedCount(Year) > 0
Error message: "Please select at least one year"
34. Как создать динамические измерения?
Динамические измерения позволяют пользователям переключать группировки данных без изменения диаграмм.
Основные подходы:
// 1. Использование переменной с Pick()
// vDimensionSelector: 1=Product, 2=Category, 3=Brand
=Pick($(vDimensionSelector), Product, Category, Brand)
// 2. Условная логика
=If(vGroupBy = 'Time', Pick(vTimeLevel, Year, Quarter, Month, Week, Date), If(vGroupBy = 'Geography', Pick(vGeoLevel, Country, Region, City), If(vGroupBy = 'Product', Pick(vProdLevel, Category, SubCategory, Product), Customer)))
// 3. Island таблица для выбора
DimensionSelector:
LOAD * INLINE [ DimID, DimName, DimExpression 1, Product, Product 2, Category, Category 3, Brand, Brand 4, Customer Segment, CustomerSegment
];
// В диаграмме
=$(=Only(DimExpression))
Синхронизация с мерами:
// Адаптация мер под измерение
=Pick($(vDimensionSelector),
Sum(Sales), // Для Product
Avg(Aggr(Sum(Sales), Product)), // Для Category
Sum(Sales) / Count(Distinct Product) // Для Brand
)
// Динамическое форматирование
=Pick($(vDimensionSelector),
Num(Sum(Sales), '#,##0'),
Num(Sum(Sales), '$#,##0.00'),
Num(Sum(Sales), '0.0%')
)
35. Как оптимизировать производительность диаграмм?
1. Ограничение объема данных:
// Dimension Limits
// В свойствах диаграммы выбираем: Limitation → Fixed number → 10
// Или выбираем "Show others with threshold"
// Условное выражение
Count(Distinct Customer) < 10000
// Программное ограничение
// Показывать только топ-20 по продажам
=Aggr(
If(Rank(Sum(Sales)) <= 20, Product),
Product
)
2. Оптимизация выражений:
// ПЛОХО: вложенные условия в агрегации
Sum(If(Year = 2024 and Region = 'North', Sales))
// ХОРОШО: Set Analysis
Sum({<Year={2024}, Region={'North'}>} Sales)
// ПЛОХО: сложные вычисления в мерах
Sum(Sales * (1 + If(Customer = 'VIP', 0.1, 0)))
// ХОРОШО: предварительный расчет в скрипте
// В скрипте: If(Customer = 'VIP', 1.1, 1) as PriceFactor
Sum(Sales * PriceFactor)
3. Кэширование и переиспользование:
// Использование Master Measures
// Вместо повторения Sum(Sales) везде
// Переменные для сложных выражений
SET vYTDSales = Sum({<Date={">=$(=YearStart(Today()))<=$(=Today())"}>} Sales);
// Использование: $(vYTDSales)
// Кэш работает эффективнее с идентичными выражениями
36. Что такое Alternate States?
Alternate States позволяют иметь несколько независимых состояний выборки в одном приложении для сравнительного анализа.
Создание и использование:
// В QlikView: Document Properties → Alternate States
// В Qlik Sense: через скрипт или API
// Применение в выражениях
// State1 selection
Sum({State1} Sales)
// State2 selection
Sum({State2} Sales)
// Разница между состояниями
Sum({State1} Sales) - Sum({State2} Sales)
// Процент от State2
Sum({State1} Sales) / Sum({State2} Sales) * 100
Синхронизация состояний:
// Копирование выборки Year из State1 в State2
Sum({State2<Year=State1::Year>} Sales)
// Наследование из default state
Sum({State1<Product=$::Product>} Sales)
// Объединение выборок
Sum({State1<Customer=State1::Customer+State2::Customer>} Sales)
// Исключение
Sum({State1<Product=State1::Product-State2::Product>} Sales)
Практические применения:
// Сравнение периодов
// State1: Current Period
// State2: Comparison Period
// В визуализации
Measure 1: Sum({CurrentPeriod} Sales)
Measure 2: Sum({ComparisonPeriod} Sales)
Measure 3: Column(1) - Column(2) // Разница
Measure 4: Column(1) / Column(2) - 1 // Рост в %
// Сравнение сценариев
// StateActual: фактические данные
// StateBudget: бюджет
// StateForecast: прогноз
37. Как создать heat map в таблице?
Heat map в таблицах помогает быстро идентифицировать схожие паттерны и выбросы из выборки через цветовое кодирование.
Базовая реализация:
// Background Color Expression
// Градиент от красного к зеленому
ColorMix1(
(Sum(Sales) - Min(Total Sum(Sales))) /
(Max(Total Sum(Sales)) - Min(Total Sum(Sales))),
RGB(255,0,0), // Красный для минимума
RGB(0,255,0) // Зеленый для максимума
)
// Инвертированный градиент (зеленый→красный)
ColorMix1(
(Sum(Sales) - Min(Total Sum(Sales))) /
(Max(Total Sum(Sales)) - Min(Total Sum(Sales))),
RGB(0,255,0),
RGB(255,0,0)
)
Продвинутые техники:
// Трехцветный градиент через ColorMix2
ColorMix2( (Sum(Sales) - Min(Total Sum(Sales))) / (Max(Total Sum(Sales)) - Min(Total Sum(Sales))) * 2 - 1, RGB(255,0,0), // Ниже среднего RGB(255,255,255), // Среднее RGB(0,255,0) // Выше среднего
)
// С пороговыми значениями
If(Sum(Sales) < $(vThresholdLow), RGB(255,200,200), If(Sum(Sales) < $(vThresholdMed), RGB(255,255,200), If(Sum(Sales) < $(vThresholdHigh), RGB(200,255,200), RGB(100,255,100))))
// Относительно среднего по строке/столбцу
ColorMix1( (Sum(Sales) - Avg(Total <Product> Sum(Sales))) / StDev(Total <Product> Sum(Sales)), RGB(255,200,200), RGB(200,255,200)
)
Дополнительное форматирование:
// Text Color Expression (для контраста)
If(
(Sum(Sales) - Min(Total Sum(Sales))) /
(Max(Total Sum(Sales)) - Min(Total Sum(Sales))) > 0.5,
RGB(0,0,0), // Черный текст на светлом фоне
RGB(255,255,255) // Белый текст на темном фоне
)
// Комбинирование с иконками
If(Sum(Sales) > Sum(Target), '✓ ', '') & Num(Sum(Sales), '#,##0')
38. Как работать с контейнерами (Container objects)?
Контейнеры позволяют эффективно использовать пространство дашборда, размещая несколько визуализаций в одной области.
Set Analysis
39. Объясните базовый синтаксис Set Analysis
Set Analysis — это язык запросов в Qlik для создания пользовательских наборов данных в выражениях. Понимание его синтаксиса критически важно для разработки приложений.
Структура Set Expression:
// Базовая структура
{<SetModifier>}
// Полная структура
{SetIdentifier<SetModifier>}
// Компоненты:
Sum({$<Year={2024}>} Sales)
// │ │ │ │
// │ │ │ └── Значение модификатора
// │ │ └────── Поле модификатора
// │ └──────────── Set модификатор
// └────────────── Set идентификатор ($)
Set идентификаторы:
// $ - текущая выборка (по умолчанию)
Sum({$} Sales) // эквивалентно Sum(Sales)
// 1 - полный набор данных (игнорирует выборки)
Sum({1} Sales) // все продажи независимо от выборок
// $1 - предыдущая выборка (Back)
Sum({$1} Sales) // продажи для предыдущего состояния
// Bookmark01 - сохраненная закладка
Sum({Bookmark01} Sales) // продажи для состояния закладки
// Alternate State
Sum({State1} Sales) // продажи в альтернативном состоянии
Операторы модификаторов:
// = : заменить выборку
{<Year={2024}>} // только 2024
// += : добавить к текущей выборке
{<Year+={2024}>} // текущая выборка + 2024
// -= : исключить из выборки
{<Year-={2023}>} // исключить 2023
// *= : пересечение с текущей выборкой
{<Customer*={<Region={'North'}>}>} // клиенты из North в текущей выборке
// /= : симметрическая разность (XOR)
{<Product/={'A','B'}>} // продукты в текущей выборке ИЛИ A,B, но не оба
40. Как использовать Set Analysis с датами?
Работа с датами в Set Analysis требует внимания к синтаксису и форматированию:
Статические даты:
// Конкретная дата (формат должен совпадать с форматом поля)
Sum({<Date={'2024-01-15'}>} Sales)
// Диапазон дат с использованием поиска
Sum({<Date={">2024-01-01<2024-12-31"}>} Sales)
Sum({<Date={">=2024-01-01<=2024-12-31"}>} Sales) // включительно
// Несколько дат
Sum({<Date={'2024-01-01','2024-01-15','2024-01-31'}>} Sales)
Динамические даты:
// Текущий год
Sum({<Year={$(=Year(Today()))}>} Sales)
// Последние 30 дней
Sum({<Date={">=$(=Date(Today()-30))<=$(=Date(Today()))"}>} Sales)
// Текущий месяц (от начала до сегодня)
Sum({<Date={">=$(=MonthStart(Today()))<=$(=Today())"}>} Sales)
// YTD (Year to Date)
Sum({<Date={">=$(=YearStart(Today()))<=$(=Today())"}>} Sales)
// Прошлый год, тот же период YTD
Sum({<
Year={$(=Year(Today())-1)},
Date={"<=$(=Date(AddYears(Today(),-1)))"}
>} Sales)
Относительные периоды:
// Rolling 12 months
Sum({<Date={">=$(=AddMonths(Today(),-12))<=$(=Today())"}>} Sales)
// Последний полный месяц
Sum({<Date={">=$(=MonthStart(Today(),-1))<=$(=MonthEnd(Today(),-1))"}>} Sales)
// Тот же период прошлого года
Sum({<Date={">=$(=AddYears(Min(Date),-1))<=$(=AddYears(Max(Date),-1))"}>} Sales)
// Последние N дней (через переменную)
// vDaysBack = 90
Sum({<Date={">=$(=Date(Today()-$(vDaysBack)))<=$(=Today())"}>} Sales)
41. Как игнорировать выборки в Set Analysis?
Игнорирование выборок позволяет создавать контекстно-независимые вычисления для бенчмарков и сравнений.
Полное игнорирование всех выборок:
// Использование идентификатора 1
Sum({1} Sales) // сумма всех продаж в приложении
// Процент от общего
Sum(Sales) / Sum({1} Sales) * 100
Игнорирование конкретных полей:
// Синтаксис: Field=
Sum({<Product=, Region=, Customer=>} Sales)
// Игнорирует выборки в Product, Region, Customer
// Игнорирование всех полей даты
Sum({<Year=, Quarter=, Month=, Date=>} Sales)
// Комбинирование с другими модификаторами
Sum({<Product=, Region=, Year={2024}>} Sales)
// Игнорирует Product и Region, но устанавливает Year=2024
Выборочное наследование:
// Игнорировать все, кроме определенных полей
Sum({1<Year=$::Year, Month=$::Month>} Sales)
// Использует полный набор данных, но наследует выборки Year и Month
// Наследование из альтернативных состояний
Sum({1<Product=State1::Product>} Sales)
// Сложные комбинации
Sum({1<
Year=$::Year,
Customer={<Sales={">10000"}>},
Product=
>} Sales)
42. Объясните P() и E() функции в Set Analysis
P() и E() функции — это функции для работы с возможными и исключенными значениями.
P() — Possible values (возможные значения):
// Базовое использование
Sum({<Product=P()>} Sales)
// Сумма продаж для всех возможных (белых в фильтрах) продуктов
// P() с контекстом
Sum({<Customer=P({<Product={'Laptop'}>})>} Sales)
// Продажи клиентам, которые покупали Laptop
// P() с указанием поля и таблицы
Sum({<ProductID=P({<Year={2024}>} ProductID)>} Sales)
// Продукты, которые продавались в 2024
E() — Excluded values (исключенные значения):
// Базовое использование
Sum({<Product=E()>} Sales)
// Сумма продаж для всех исключенных (серых) продуктов
// E() с контекстом
Sum({<Customer=E({<Region={'North'}>})>} Sales)
// Продажи клиентам, которые НЕ из региона North
// Комбинирование P() и E()
Sum({<Product=P({<Year={2024}>}) - E({<Year={2023}>})>} Sales)
// Продукты, которые продавались в 2024, но НЕ в 2023
Продвинутые примеры:
// Клиенты, купившие все выбранные продукты
Count({<Customer=P({<Product=P()>} Customer)>} Distinct Customer)
// Продукты, не купленные выбранными клиентами
Sum({<Product=E({<Customer=P()>} Product)>} Sales)
// Многоуровневые зависимости
Sum({<
OrderID=P({<
Customer=P({<
Product={'Laptop','Phone'}
>} Customer)
>} OrderID)
>} Sales)
// Заказы клиентов, купивших и Laptop, и Phone
43. Как использовать переменные в Set Analysis?
Использование переменных в Set Analysis делает выражения динамическими и переиспользуемыми.
Прямая подстановка переменных:
// vCurrentYear = 2024
Sum({<Year={$(vCurrentYear)}>} Sales)
// vYearList = 2022,2023,2024
Sum({<Year={$(vYearList)}>} Sales)
// Текстовые значения требуют кавычек в переменной
// vRegion = 'North' // с кавычками
Sum({<Region={$(vRegion)}>} Sales)
Вычисляемые переменные:
// Использование = для вычисления
Sum({<Year={$(=Max(Year))}>} Sales)
Sum({<Year={$(=Max(Year)-1)}>} Sales) // предыдущий год
// Сложные вычисления
Sum({<Customer={$(=Chr(39) & Concat(Distinct Customer, Chr(39) & ',' & Chr(39)) & Chr(39))}>} Sales)
// Chr(39) - одинарная кавычка
Динамические поля и модификаторы:
// vDimension = 'Product' или 'Category'
Sum({<$(vDimension)={'A','B','C'}>} Sales)
// vSetModifier = 'Year={2024}, Region={North}'
Sum({<$(vSetModifier)>} Sales)
// Условные модификаторы
// vIncludeHistory = 1 или 0
Sum({<Year={$(=If($(vIncludeHistory)=1, '*', Year(Today())))}>} Sales)
Параметризованные переменные:
// Определение переменной с параметрами
SET vSalesByYear = Sum({<Year={$1}>} Sales);
// Использование
$(vSalesByYear(2024)) // продажи за 2024
$(vSalesByYear($(=Max(Year)))) // продажи за максимальный год
// Множественные параметры
SET vSalesByRegionYear = Sum({<Region={$1}, Year={$2}>} Sales);
$(vSalesByRegionYear('North', 2024))
44. Как сравнить периоды используя Set Analysis?
Сравнение периодов — одна из самых частых задач в бизнес-анализе:
Year-over-Year (YoY) сравнения:
// Текущий год vs Предыдущий год
// Текущий год
Sum({<Year={$(=Max(Year))}>} Sales)
// Предыдущий год
Sum({<Year={$(=Max(Year)-1)}>} Sales)
// YoY рост в %
(Sum({<Year={$(=Max(Year))}>} Sales) /
Sum({<Year={$(=Max(Year)-1)}>} Sales) - 1) * 100
// YoY с учетом выборки периода
(Sum(Sales) /
Sum({<Year={$(=Only(Year)-1)}>} Sales) - 1) * 100
Month-over-Month (MoM) сравнения:
// Определение дат
// vCurrentMonth = Max(MonthYear)
// vPreviousMonth = AddMonths(Max(MonthYear), -1)
// Текущий месяц
Sum({<MonthYear={"$(=Date(Max(MonthYear),'MMM-YYYY'))"}>} Sales)
// Предыдущий месяц
Sum({<MonthYear={"$(=Date(AddMonths(Max(MonthYear),-1),'MMM-YYYY'))"}>} Sales)
// MoM изменение
Sum({<MonthYear={"$(=Date(Max(MonthYear),'MMM-YYYY'))"}>} Sales) -
Sum({<MonthYear={"$(=Date(AddMonths(Max(MonthYear),-1),'MMM-YYYY'))"}>} Sales)
Сравнение с тем же периодом прошлого года:
// YTD текущего года
Sum({<Year={$(=Max(Year))}, Date={"<=$(=Max(Date))"}>} Sales)
// YTD предыдущего года (та же дата)
Sum({<Year={$(=Max(Year)-1)}, Date={"<=$(=AddYears(Max(Date),-1))"}>} Sales)
// Rolling 12 months vs Previous 12 months
// Текущие 12 месяцев
Sum({<Date={">=$(=AddMonths(Max(Date),-11))<=$(=Max(Date))"}>} Sales)
// Предыдущие 12 месяцев
Sum({<Date={">=$(=AddMonths(Max(Date),-23))<=$(=AddMonths(Max(Date),-12))"}>} Sales)
45. Объясните Element Functions в Set Analysis
Element Functions позволяют выполнять операции над множествами значений в Set Analysis.
Основные операции:
// + (Union) - объединение множеств
Sum({<Year={2023}+{2024}>} Sales)
// Эквивалентно: Year={2023,2024}
// - (Difference) - разность множеств
Sum({<Product={"*"}-{"ProductA","ProductB"}>} Sales)
// Все продукты кроме ProductA и ProductB
// * (Intersection) - пересечение множеств
Sum({<Customer={<Year={2023}>}*{<Year={2024}>}>} Sales)
// Клиенты, купившие И в 2023 И в 2024
// / (Symmetric Difference/XOR)
Sum({<Product={<Region={'North'}>}/{<Region={'South'}>}>} Sales)
// Продукты из North ИЛИ South, но не из обоих
Комбинированные операции:
// Сложные выражения с приоритетом операций
Sum({<Customer=
{<Year={2024}>} -
{<Product={'Returned'}>} +
{<Status={'VIP'}>}
>} Sales)
// Клиенты 2024 года, исключая вернувших товар, плюс все VIP
// Использование с P() и E()
Sum({<Product=P({<Year={2024}>})-E({<Profit={"<0"}>})>} Sales)
// Продукты 2024 года, исключая убыточные
// Вложенные операции
Sum({<OrderID=P({<
Customer=P({<Segment={'Premium'}>})*P({<Year={2024}>})
>})>} Sales)
// Заказы Premium клиентов в 2024 году
46. Как использовать Set Analysis с Aggr()?
Комбинация Set Analysis с Aggr() позволяет создавать сложные многоуровневые вычисления.
Базовые примеры:
// Подсчет клиентов с продажами > 10000
Count({<Customer={"=Sum(Sales)>10000"}>} Distinct Customer)
// Альтернатива через Aggr
Count(
Aggr(
If(Sum(Sales) > 10000, Customer),
Customer
)
)
// Set Analysis внутри Aggr
Sum(
Aggr(
Sum({<Year={2024}>} Sales),
Customer, Product
)
)
Продвинутые техники:
// Топ-10 продуктов по продажам в каждой категории
Sum({<Product={"=Rank(Sum(Sales), 4) <= 10"}>} Sales)
// Через Aggr с большим контролем
Sum(
Aggr(
If(Rank(Sum({<Year={2024}>} Sales)) <= 10,
Sum({<Year={2024}>} Sales),
0
),
Category, Product
)
)
// Клиенты с ростом продаж
Count({<Customer={"=Sum({<Year={2024}>}Sales) > Sum({<Year={2023}>}Sales)"}>}
Distinct Customer)
// Продукты, продающиеся во всех выбранных регионах
Sum({<Product={"=Count(Distinct Region) = GetSelectedCount(Region)"}>} Sales)
Оптимизация производительности:
// МЕДЛЕННО: поиск в Set Analysis
Sum({<Customer={"=Sum(Sales)>$(vThreshold)"}>} Sales)
// БЫСТРО: флаг в скрипте
// В скрипте:
If(CustomerTotalSales > $(vThreshold), 1, 0) as IsKeyCustomer
// В выражении:
Sum({<IsKeyCustomer={1}>} Sales)
// Или предварительный расчет в переменной
SET vKeyCustomers = =Chr(39) & Concat({<Customer={"=Sum(Sales)>10000"}>}
Distinct Customer, Chr(39) & ',' & Chr(39)) & Chr(39);
Sum({<Customer={$(vKeyCustomers)}>} Sales
47. Объясните indirect Set Analysis
Indirect Set Analysis позволяет создавать каскадные фильтры и сложные зависимости между полями.
Базовая косвенная фильтрация:
// Продажи клиентам, которые покупали Product A
Sum({<Customer=P({<Product={'A'}>} Customer)>} Sales)
// Разбор логики:
// 1. {<Product={'A'}>} - контекст где выбран Product A
// 2. P(...Customer) - возможные клиенты в этом контексте
// 3. Customer=... - применяем к основному набору
Многоуровневые зависимости:
// Заказы клиентов из магазинов северного региона
Sum({<
OrderID=P({<
Customer=P({<
Store=P({<Region={'North'}>} Store)
>} Customer)
>} OrderID)
>} Sales)
// Продукты, купленные VIP клиентами в 2024
Sum({<
Product=P({<
Customer=P({<Segment={'VIP'}>}),
Year={2024}
>} Product)
>} Sales)
Использование с альтернативными состояниями:
// Применение выборки из State1
Sum({<Product=State1::Product>} Sales)
// Комбинирование состояний
Sum({<
Customer=State1::Customer,
Product=State2::Product,
Year=$::Year // из основного состояния
>} Sales)
// Сложные межсостояния операции
Sum({<Customer=P({State1} Customer) * P({State2} Customer)>} Sales)
// Клиенты, выбранные в обоих состояниях
48. Как оптимизировать производительность Set Analysis?
1. Избегаем поисковые выражения:
// МЕДЛЕННО: поиск выполняется для каждой строки
Sum({<Product={"*Laptop*"}>} Sales)
Sum({<Customer={"=Sum(Sales)>10000"}>} Sales)
// БЫСТРО: точные значения или предварительный расчет
Sum({<ProductCategory={'Laptops'}>} Sales)
Sum({<IsKeyCustomer={1}>} Sales) // флаг из скрипта
2. Делаем редварительный расчет в переменных:
// МЕДЛЕННО: вычисление даты каждый раз
Sum({<Date={">=$(=MonthStart(Today()))"}>} Sales)
// БЫСТРО: переменная вычисляется один раз
// В скрипте: LET vMonthStart = MonthStart(Today());
Sum({<Date={">=$(vMonthStart)"}>} Sales)
// Для списков значений
SET vTop10Products = =Chr(39) & Concat(
{<Product={"=Rank(Sum(Sales))<=10"}>} Distinct Product,
Chr(39) & ',' & Chr(39)
) & Chr(39);
Sum({<Product={$(vTop10Products)}>} Sales)
3. Упрощение сложных выражений:
// МЕДЛЕННО: вложенные P() функции
Sum({<OrderID=P({<Customer=P({<Product=P({<Category={'Electronics'}>})>})>})>} Sales)
// БЫСТРО: прямые связи где возможно
Sum({<Category={'Electronics'}>} Sales)
// Или через флаги в модели данных
// В скрипте: If(Category='Electronics', 1, 0) as IsElectronics
Sum(Sales * IsElectronics)
4. Правильное использование модификаторов:
// Избегаем избыточных модификаторов
// ПЛОХО:
Sum({<Year={2024}, Quarter=, Month=, Week=, Date=>} Sales)
// ХОРОШО:
Sum({<Year={2024}, Quarter=, Month=>} Sales)
// Week и Date автоматически сбросятся при Quarter= и Month=
// Используем наследование где возможно
// Вместо копирования всех условий
Sum({$<Product={'A'}>} Sales) // наследует текущие выборки + Product=A
5. Мониторинг и отладка:
// Используйте GetFieldSelections для отладки
=GetFieldSelections(Product, ', ', 100)
// Проверяйте количество вычислений
=GetPossibleCount(Customer) // сколько клиентов в выборке
// Профилирование через Document Analyzer
// Смотрите CalcTime для каждого выражения
49. Объясните разницу между Sum() и Total Sum()
Базовая разница:
// Sum() - агрегирует в контексте текущих измерений
Sum(Sales)
// В таблице с измерениями Product, Region - сумма для каждой комбинации
// Total Sum() - игнорирует все измерения диаграммы
Sum(Total Sales)
// Всегда возвращает общую сумму независимо от строки
TOTAL с указанием измерений:
// Total <Field1, Field2> - игнорирует все измерения КРОМЕ указанных
Sum(Total <Region> Sales)
// Сумма в пределах каждого региона
// Пример с несколькими измерениями
// Таблица: Region | Product | Sales | % of Region
Sum(Sales) / Sum(Total <Region> Sales) * 100
// Процент продукта от продаж региона
// Вложенные проценты
// % от категории внутри региона
Sum(Sales) / Sum(Total <Region, Category> Sales) * 100
Продвинутое использование:
// Накопительная сумма с помощью Total
RangeSum(Above(Sum(Sales), 0, RowNo(Total)))
// Ранжирование внутри группы
Rank(Sum(Sales)) // Ранг в текущей группе
Rank(Total Sum(Sales)) // Ранг по всей таблице
Rank(Total <Region> Sum(Sales)) // Ранг внутри региона
Важные особенности:
TOTAL работает только в контексте диаграммы
Не влияет на Set Analysis
Может значительно влиять на производительность
50. Как работает функция Aggr()?
Aggr() — это «виртуальная таблица» внутри выражения, позволяющая создавать многоуровневые агрегации. По сути, «аггры» — это аналог оконных функций в SQL.
Базовый синтаксис и концепция:
Aggr(Expression, Dimension1 [, Dimension2...])
// Aggr создает временную таблицу с указанными измерениями
// и вычисляет Expression для каждой комбинации
Простые примеры:
// Средняя сумма продаж по клиентам
Avg(Aggr(Sum(Sales), Customer))
// 1. Aggr вычисляет Sum(Sales) для каждого Customer
// 2. Avg() берет среднее от этих сумм
// Количество клиентов с продажами > 10000
Count(Aggr(If(Sum(Sales) > 10000, 1), Customer))
// Или более элегантно:
Sum(Aggr(If(Sum(Sales) > 10000, 1, 0), Customer))
// Стандартное отклонение продаж по продуктам
StDev(Aggr(Sum(Sales), Product))
Продвинутое использование:
// Второй максимум продаж
Max(Aggr(Sum(Sales), Product), 2)
// Топ-3 продукта в каждой категории
Sum(Aggr(
If(Rank(Sum(Sales)) <= 3, Sum(Sales), 0),
Category, Product
))
// Медиана продаж клиентов
Median(Aggr(Sum(Sales), Customer))
// Процент клиентов от общего количества в регионе
Count(Distinct Customer) /
Count(Total <Region> Aggr(1, Customer, Region))
Aggr() с сортировкой:
// Важно: порядок влияет на функции like Rank(), Above(), etc.
Concat(Aggr(
Product & ': ' & Num(Sum(Sales), '#,##0'),
Product,
(Sum(Sales), DESC) // Сортировка по убыванию продаж
), Chr(10)) // Перенос строки как разделитель
// Накопительная сумма топ-продуктов
Sum(Aggr(
If(Rank(Sum(Sales)) <= 10,
RangeSum(Above(Sum(Sales), 0, Rank(Sum(Sales))))),
Product,
(Sum(Sales), DESC)
))
Оптимизация и ограничения:
// НЕЭФФЕКТИВНО: Aggr в Set Analysis
Count({<Customer={"=Sum(Aggr(Sum(Sales), Customer, Product)) > 10000"}>} Customer)
// ЭФФЕКТИВНО: Флаг в скрипте или упрощение
Count(Aggr(If(Sum(Sales) > 10000, Customer), Customer))
// Aggr() с NODISTINCT (по умолчанию DISTINCT)
Sum(Aggr(NODISTINCT Sales * 0.1, OrderID))
// Сохраняет дубликаты в виртуальной таблице
51. Объясните функции Rank() и HRank()
Функции ранжирования критически важны для создания топ-списков, процентилей и сравнительного анализа.
Rank() — вертикальное ранжирование (в столбце):
// Базовое использование
Rank(Sum(Sales))
// Возвращает позицию текущей строки по Sum(Sales)
// Обратное ранжирование
Rank(-Sum(Sales)) // Наименьшие значения получают ранг 1
HRank() — горизонтальное ранжирование (в строке, для pivot таблиц):
// В pivot таблице: Product x Month
HRank(Sum(Sales)) // Ранг месяца для каждого продукта
// Комбинирование с Total
HRank(Total Sum(Sales)) // Ранг по всей таблице
Продвинутые техники ранжирования:
// Ранжирование внутри группы
Rank(Total <Category> Sum(Sales))
// Ранг продукта внутри его категории
// Процентильный ранг
Rank(Sum(Sales), 4) * 100 // Процент от 0 до 100
// Топ-N с динамическим порогом
If(Rank(Sum(Sales)) <= vTopN, Sum(Sales))
// Выделение топ-10%
If(Rank(Sum(Sales), 4) >= 0.9, 'Top 10%', 'Other')
// Ранжирование с учетом нескольких критериев
Rank(Sum(Sales) * 1000000 + Sum(Quantity))
// Сначала по Sales, потом по Quantity
Решение проблем с одинаковыми значениями:
// Проблема: продукты с одинаковыми продажами
// Решение 1: добавить уникальность
Rank(Sum(Sales) + RowNo() * 0.00001)
// Решение 2: вторичная сортировка
Rank(Aggr(
Sum(Sales) * 1000000 + Sum(Quantity),
Product
))
// Решение 3: использовать Mode 2 или 3
Rank(Sum(Sales), 2) // Средний ранг для одинаковых
52. Как использовать функции Inter Record?
Inter Record функции позволяют обращаться к значениям других строк в таблице, что критично для временного анализа и вычисления трендов.
Основные функции:
// Above() - значение из строки выше
Above(Expression [, Offset [, Count]])
Above(Sum(Sales)) // Предыдущая строка
Above(Sum(Sales), 2) // 2 строки выше
Above(Sum(Sales), 0, 3) // Текущая и 2 строки выше (всего 3)
// Below() - значение из строки ниже
Below(Sum(Sales)) // Следующая строка
Below(Sum(Sales), 3) // 3 строки ниже
// Before() / After() - для pivot таблиц (горизонтально)
Before(Sum(Sales)) // Предыдущий столбец
After(Sum(Sales), 2) // 2 столбца вправо
Практические применения:
// Изменение по сравнению с предыдущим периодом
Sum(Sales) - Above(Sum(Sales))
// Процент изменения
(Sum(Sales) - Above(Sum(Sales))) / Above(Sum(Sales)) * 100
// Скользящее среднее за 3 периода
RangeAvg(Above(Sum(Sales), 0, 3))
// Скользящее среднее за 12 месяцев
RangeAvg(Above(Sum(Sales), 0, 12))
// Накопительная сумма
RangeSum(Above(Sum(Sales), 0, RowNo()))
// Альтернатива через running total
RangeSum(Above(Total Sum(Sales), 0, RowNo(Total)))
Продвинутые техники:
// Определение тренда (растущий/падающий)
If(Sum(Sales) > Above(Sum(Sales)) and
Above(Sum(Sales)) > Above(Sum(Sales), 2),
'↑↑',
If(Sum(Sales) < Above(Sum(Sales)) and
Above(Sum(Sales)) < Above(Sum(Sales), 2),
'↓↓',
'→'))
// Максимум за последние N периодов
RangeMax(Above(Sum(Sales), 0, 12))
// Volatility (изменчивость)
StDev(Above(Sum(Sales), 0, 12))
// Year-over-Year с проверкой
If(RowNo() > 12,
(Sum(Sales) - Above(Sum(Sales), 12)) / Above(Sum(Sales), 12) * 100)
// Заполнение пропущенных значений
Alt(Sum(Sales), Above(Alt(Sum(Sales), 0)))
Использование с Total:
// Сравнение с первой строкой
Sum(Sales) - First(Total Sum(Sales))
// Сравнение с последней строкой
Sum(Sales) - Last(Total Sum(Sales))
// Позиция относительно максимума
Sum(Sales) / Max(Total Aggr(Sum(Sales), Month))
53. Объясните функции FirstSortedValue() и LastSortedValue()
Эти функции возвращают значение одного поля, соответствующее первому или последнему значению при сортировке по другому полю.
Базовый синтаксис:
FirstSortedValue([DISTINCT] Expression, SortExpression [, n])
// Expression - что вернуть
// SortExpression - по чему сортировать
// n - какое по счету значение (по умолчанию 1)
Простые примеры:
// Продукт с максимальными продажами
FirstSortedValue(Product, -Sales)
// Минус перед Sales для сортировки по убыванию
// Клиент с минимальными продажами
FirstSortedValue(Customer, Sales)
// Без минуса - сортировка по возрастанию
// Дата последнего заказа
FirstSortedValue(OrderDate, -OrderDate)
// Или просто Max(OrderDate)
// Продукт с третьими по величине продажами
FirstSortedValue(Product, -Sales, 3)
Работа с агрегациями:
// Продукт с максимальной суммой продаж
FirstSortedValue(Product, -Aggr(Sum(Sales), Product))
// Регион с максимальным средним чеком
FirstSortedValue(Region, -Aggr(Avg(Sales), Region))
// Месяц с максимальным количеством новых клиентов
FirstSortedValue(
Month,
-Aggr(Count(Distinct If(FirstOrderDate = OrderDate, Customer)), Month)
)
Обработка одинаковых значений:
// Проблема: несколько продуктов с одинаковыми максимальными продажами
// Решение 1: DISTINCT
FirstSortedValue(DISTINCT Product, -Sales)
// Вернет NULL если есть несколько максимальных
// Решение 2: добавить уникальность
FirstSortedValue(Product, -Sales + Rand() * 0.0001)
// Решение 3: конкатенация всех максимальных
Concat(Aggr(
If(Sum(Sales) = Max(Total Aggr(Sum(Sales), Product)), Product),
Product
), ', ')
LastSortedValue() — противоположность:
// Продукт с минимальными продажами
LastSortedValue(Product, -Sales)
// Эквивалентно FirstSortedValue(Product, Sales)
// Самый старый заказ клиента
LastSortedValue(OrderID, OrderDate)
// Использование в KPI
='Best Product: ' & FirstSortedValue(Product, -Sales) &
' (' & Num(Max(Aggr(Sum(Sales), Product)), '#,##0') & ')'
Практические кейсы:
// Топ продавец в каждом регионе (в таблице)
FirstSortedValue(Salesperson, -Aggr(Sum(Sales), Region, Salesperson))
// Последний статус заказа
FirstSortedValue(Status, -StatusDate)
// Валюта с максимальным курсом на последнюю дату
FirstSortedValue(Currency, -Aggr(FirstSortedValue(Rate, -Date), Currency))
54. Как работают функции Range?
Range функции обрабатывают список значений как массив, что делает их незаменимыми для работы с несколькими полями или межстрочными вычислениями.
Основные Range функции:
// RangeSum - сумма всех числовых значений
RangeSum(1, 2, 3, 4, 5) // = 15
RangeSum(Sales1, Sales2, Sales3) // Сумма трех полей
RangeSum(Above(Sales, 0, 12)) // Сумма за 12 периодов
// RangeAvg - среднее числовых значений
RangeAvg(Q1, Q2, Q3, Q4) // Среднее за год
RangeAvg(Above(Sum(Sales), 0, 3)) // Скользящее среднее
// RangeMin / RangeMax
RangeMin(Price, DiscountPrice, 0) // Не меньше 0
RangeMax(0, Sum(Sales) - Sum(Costs)) // Прибыль или 0
// RangeCount - количество числовых значений
RangeCount(Field1, Field2, Field3) // Сколько не-null
Продвинутые функции:
// RangeStDev - стандартное отклонение
RangeStDev(Above(Sum(Sales), 0, 12)) // Волатильность за год
// RangeMode - наиболее частое значение
RangeMode(Rating1, Rating2, Rating3) // Самая частая оценка
// RangeCorrel - корреляция
RangeCorrel(
Above(Sum(Sales), 0, 12),
Above(Sum(Marketing), 0, 12)
) // Корреляция продаж и маркетинга
// RangeFractile - процентиль
RangeFractile(0.75, Above(Sum(Sales), 0, 12)) // 75-й процентиль
// RangeIRR / RangeNPV - финансовые функции
RangeIRR(Investment, CashFlow1, CashFlow2, CashFlow3)
RangeNPV(0.1, 0, Payment1, Payment2, Payment3) // NPV с 10% ставкой
Практические применения:
// Заполнение пропущенных значений средним
Alt(Sales, RangeAvg(Above(Sales), Below(Sales)))
// Сглаживание временных рядов
RangeAvg(Above(Sum(Sales), -1, 3)) // Среднее: пред, текущий, след
// Определение выбросов
If(Abs(Sum(Sales) - RangeAvg(Above(Sum(Sales), 0, 12))) >
2 * RangeStDev(Above(Sum(Sales), 0, 12)),
'Outlier',
'Normal')
// Накопительные вычисления с условиями
RangeSum(
If(Above(Sum(Sales)) > 0, Above(Sum(Sales)), 0),
If(Above(Sum(Sales), 2) > 0, Above(Sum(Sales), 2), 0),
If(Above(Sum(Sales), 3) > 0, Above(Sum(Sales), 3), 0)
)
Оптимизация с Range функциями:
// Вместо множественных If()
// ПЛОХО:
If(IsNull(Field1), 0, Field1) +
If(IsNull(Field2), 0, Field2) +
If(IsNull(Field3), 0, Field3)
// ХОРОШО:
RangeSum(Field1, Field2, Field3)
// Безопасное деление
RangeMax(Sum(Sales), 0.0001) / RangeMax(Sum(Costs), 0.0001)
// Избегаем деления на ноль
55. Объясните Fractile() и Median()
Функции процентилей критически важны для статистического анализа и понимания распределения данных.
Median() — медиана (50-й процентиль):
// Базовое использование
Median(Sales) // Медиана всех значений Sales
// С группировкой
Median(Total Sales) // Медиана по всем данным
Median(Total <Region> Sales) // Медиана внутри региона
// Медиана агрегированных значений
Median(Aggr(Sum(Sales), Customer)) // Медиана продаж по клиентам
Fractile() — любой процентиль:
// Синтаксис: Fractile(Expression, Fraction)
Fractile(Sales, 0.25) // 25-й процентиль (Q1)
Fractile(Sales, 0.50) // 50-й процентиль (медиана)
Fractile(Sales, 0.75) // 75-й процентиль (Q3)
Fractile(Sales, 0.90) // 90-й процентиль
// Квартили
Q1: Fractile(Sales, 0.25)
Q2: Fractile(Sales, 0.50) // = Median(Sales)
Q3: Fractile(Sales, 0.75)
// Межквартильный размах (IQR)
Fractile(Sales, 0.75) - Fractile(Sales, 0.25)
56. Как использовать функции для работы со строками?
Строковые функции необходимы для очистки данных, форматирования и извлечения информации из текстовых полей.
Базовые функции извлечения данных:
// Left, Right, Mid - извлечение подстрок
Left('ABCDEF', 3) // 'ABC'
Right('ABCDEF', 2) // 'EF'
Mid('ABCDEF', 2, 3) // 'BCD' (начиная с позиции 2, длина 3)
// Практические примеры
Left(CustomerCode, 2) as RegionCode // Первые 2 символа
Right(Phone, 4) as PhoneExt // Последние 4 цифры
Mid(ProductSKU, 4, 3) as ProductCategory // Категория из SKU
// Len - длина строки
Len('Hello') // 5
Len(Trim(' Hello ')) // 5 (после удаления пробелов)
If(Len(Email) > 0, Email, 'No Email') // Проверка на пустоту
Функции поиска и замены:
// Index - позиция подстроки
Index('user@example.ru', '@') // 5
Index('ABC-DEF-GHI', '-', 2) // 8 (второе вхождение)
// Извлечение домена email
Mid(Email, Index(Email, '@') + 1) as EmailDomain
// Replace - замена текста
Replace('2024-01-15', '-', '/') // '2025/01/01'
Replace(Phone, ' ', '') // Удаление пробелов
Replace(ProductName, Chr(10), ' ') // Замена переносов строк
// SubStringCount - количество вхождений
SubStringCount('A,B,C,D', ',') // 3
SubStringCount(Tags, 'Important') // Сколько раз встречается тег
Функции преобразования:
// Изменение регистра
Upper('Hello') // 'HELLO'
Lower('Hello') // 'hello'
Capitalize('hello world') // 'Hello World'
Proper('JOHN DOE') // 'John Doe' (в QlikView)
// Trim - удаление пробелов
Trim(' Hello ') // 'Hello'
LTrim(' Hello') // 'Hello' (только слева)
RTrim('Hello ') // 'Hello' (только справа)
// PurgeChar / KeepChar - фильтрация символов
PurgeChar('A1B2C3', '123') // 'ABC' (удалить цифры)
KeepChar('A1B2C3', '0123456789') // '123' (оставить только цифры)
KeepChar(Phone, '0123456789') // Только цифры телефона
Продвинутые техники:
// Разбор структурированных данных
// Имя и фамилия
SubField('John Doe', ' ', 1) as FirstName // 'John'
SubField('John Doe', ' ', 2) as LastName // 'Doe'
SubField('John Doe', ' ', -1) as LastName // Последнее слово
// Работа с путями файлов
SubField('C:\Data\Files\Report.xlsx', '\', -1) as FileName
SubField(SubField('Report.xlsx', '.', 1), '\', -1) as FileNameNoExt
// Маскирование данных
Left(CardNumber, 4) & Repeat('*', Len(CardNumber) - 8) & Right(CardNumber, 4)
// '1234********5678'
// Регулярные выражения (через расширения или Replace)
// Извлечение цифр
KeepChar(Field, '0123456789')
// Форматирование телефона
If(Len(KeepChar(Phone, '0123456789')) = 10,
'(' & Mid(KeepChar(Phone, '0123456789'), 1, 3) & ') ' &
Mid(KeepChar(Phone, '0123456789'), 4, 3) & '-' &
Mid(KeepChar(Phone, '0123456789'), 7, 4),
Phone)
// Создание инициалов
Concat(
Left(
SubField(CustomerName, ' '),
1
),
'.'
) & '.' as Initials
Работа с специальными символами:
// Chr() - символ по коду
Chr(10) // Перенос строки (LF)
Chr(13) // Возврат каретки (CR)
Chr(9) // Табуляция
Chr(39) // Одинарная кавычка
Chr(34) // Двойная кавычка
// Ord() - код символа
Ord('A') // 65
57. Объясните Dual() функцию
Dual() — уникальная функция Qlik, позволяющая создавать значения с двойным представлением: текстовым для отображения и числовым для сортировки и вычислений.
Базовая концепция:
Dual(TextValue, NumericValue)
// Примеры
Dual('High', 3) // Отображается 'High', сортируется как 3
Dual('Medium', 2) // Отображается 'Medium', сортируется как 2
Dual('Low', 1) // Отображается 'Low', сортируется как 1
// В действии
Priority:
LOAD
OrderID,
Dual(PriorityText, PriorityNum) as Priority
FROM Orders;
Практическое применение:
// Кастомная сортировка месяцев
MonthDual:
LOAD * INLINE [
MonthText, MonthNum
January, 1
February, 2
March, 3
// и т.д.
];
// Использование
LOAD
*,
Dual(MonthText, MonthNum) as Month
FROM Data;
// Теперь месяцы сортируются правильно
Использование в визуализациях:
// Цветовое кодирование с числовыми значениями
If(Performance > 0.9, Dual('Excellent', RGB(0,255,0)),
If(Performance > 0.7, Dual('Good', RGB(255,255,0)),
If(Performance > 0.5, Dual('Average', RGB(255,128,0)),
Dual('Poor', RGB(255,0,0)))))
// Динамические интервалы
Class(Sales, 10000) as SalesRange // Автоматические интервалы
// Или с Dual для лучшего контроля
If(Sales < 10000, Dual('0-10K', 1),
If(Sales < 50000, Dual('10-50K', 2),
If(Sales < 100000, Dual('50-100K', 3),
Dual('100K+', 4))))
58. Как работать с NULL и отсутствующими значениями?
Функции для работы с NULL:
// IsNull() - проверка на NULL
IsNull(Field) // Возвращает -1 (true) если NULL, 0 (false) если нет
// Примеры использования
If(IsNull(Discount), 0, Discount) as DiscountClean
If(Not IsNull(Email), 'Has Email', 'No Email') as EmailStatus
Count(If(IsNull(Phone), CustomerID)) as CustomersWithoutPhone
// Null() - функция возвращающая NULL
If(Sales < 0, Null(), Sales) as ValidSales
Функции подстановки значений:
// Alt() - первое не-NULL значение из списка
Alt(PreferredPhone, MobilePhone, HomePhone, 'No Phone') as ContactPhone
Alt(Discount, 0) as DiscountValue // 0 если Discount is NULL
// Coalesce() - аналог SQL COALESCE
Coalesce(Email, SecondaryEmail, 'noemail@company.com') as ContactEmail
// RangeMin/Max для безопасных вычислений
RangeMin(Price, 999999) as SafePrice // Избегаем NULL в Min()
RangeMax(Discount, 0) as SafeDiscount // Минимум 0
Обработка пустых строк vs NULL:
// Проверка на пустоту (NULL или пустая строка)
If(Len(Trim(Field)) = 0, 'Empty', Field) as CleanField
// Различение NULL и пустой строки
If(IsNull(Field), 'NULL',
If(Len(Field) = 0, 'Empty String',
Field)) as FieldStatus
// Замена различных вариантов "пустоты"
If(IsNull(Field) or Len(Trim(Field)) = 0 or Field = '-' or Field = 'N/A',
'No Data',
Field) as StandardizedField
Функции подсчета NULL:
// NullCount() - количество NULL значений
NullCount(Sales) as NullSalesCount
// MissingCount() - NULL + отсутствующие значения
MissingCount(Sales) as MissingSalesCount
// Процент заполненности
(Count(Sales) / Count(CustomerID)) * 100 as SalesCompleteness
// Полнота данных по полям
CrossTable(FieldName, Value)
LOAD
RecNo() as RowID,
*
FROM Data;
DataQuality:
LOAD
FieldName,
Count(If(Not IsNull(Value), RowID)) / Count(RowID) * 100 as Completeness
RESIDENT CrossTableData
GROUP BY FieldName;
Set Analysis с NULL:
// Исключение NULL значений
Sum({<Customer={"*"}>} Sales) // * означает все не-NULL значения
// Только NULL значения (требует флаг)
// В скрипте: If(IsNull(Region), 1, 0) as IsRegionNull
Sum({<IsRegionNull={1}>} Sales)
// Комбинированные условия
Sum({<Customer={"*"}, Product={"*"}>} Sales) // Оба поля не NULL
Лучшие практики для NULL:
// 1. Стандартизация в скрипте
NullAsValue CustomerName, ProductName;
Set NullValue = 'Unknown';
// 2. Безопасные вычисления
// Избегайте: Sum(Sales) / Count(Customer)
// Используйте: Sum(Sales) / RangeMax(Count(Customer), 1)
// 3. Явная обработка в агрегациях
Sum(If(Not IsNull(Discount), Sales * (1 - Discount), Sales))
// 4. Валидация данных
DataValidation:
LOAD
'Sales' as Field,
Count(If(IsNull(Sales), 1)) as NullCount,
Count(If(Sales < 0, 1)) as NegativeCount,
Count(If(Sales > 1000000, 1)) as PossibleErrors
FROM Transactions;
Безопасность и управление доступом
59. Как работает Section Access?
Section Access — это встроенный механизм безопасности Qlik для управления доступом к приложениям и данным на уровне строк. Это критически важная функция для энтерпрайз-решений.
Основная структура:
Section Access;
LOAD * INLINE [
ACCESS, USERID, PASSWORD, REGION
ADMIN, ADMIN, admin123, *
USER, USER1, pass1, North
USER, USER2, pass2, South
];
Section Application;
Ключевые правила:
Все поля в Section Access должны быть в UPPER CASE
Поля связываются с Section Application автоматически по именам
Звездочка (*) означает доступ ко всем значениям
ADMIN может видеть все данные и редактировать скрипт
USER видит только разрешенные данные
Механизм редукции данных:
Initial Data Reduction — физическое удаление данных при открытии
Dynamic Data Reduction — фильтрация в runtime
Strict Exclusion — запрет доступа при отсутствии в Section Access
60. Какие уровни доступа существуют в Section Access?
Основные уровни доступа:
ADMIN:
Полный доступ ко всем данным
Может редактировать скрипт загрузки
Может изменять Section Access
Видит все объекты и листы
USER:
Ограниченный доступ согласно правилам
Не может редактировать скрипт
Видит только разрешенные данные
Может создавать объекты (если разрешено)
NONE:
Явный запрет доступа
Используется для временной блокировки
61. Объясните Initial Data Reduction
Initial Data Reduction — это механизм постоянного удаления данных из приложения на основе правил Section Access.
Ключевые характеристики:
Данные физически удаляются при сохранении
Уменьшает размер файла приложения
Необратимая операция
Применяется при binary load
62. Как обеспечить безопасность на уровне строк и столбцов?
Row Level Security (уровень строк):
Section Access;
LOAD * INLINE [
ACCESS, USERID, COUNTRY, PRODUCT
USER, USER1, USA, *
USER, USER2, UK|Germany, Laptops
USER, USER3, *, Phones
];
// USER1: все продукты в USA
// USER2: только Laptops в UK и Germany
// USER3: только Phones во всех странах
Column Level Security (уровень столбцов):
Section Access;
LOAD * INLINE [
ACCESS, USERID, OMIT
USER, USER1, SALARY|BONUS|SSN
USER, USER2, SSN
ADMIN, ADMIN,
];
// USER1: не видит SALARY, BONUS, SSN
// USER2: не видит только SSN
// ADMIN: видит все поля
Комбинированный подход:
Используйте оба метода одновременно
Тестируйте все комбинации доступа
Документируйте матрицу доступа
63. Как реализовать аудит доступа?
Встроенные методы:
Логирование в скрипте:
// Запись доступа
LET vUser = OSUser();
LET vTimestamp = Now();
AuditLog:
LOAD
'$(vUser)' as UserID,
'$(vTimestamp)' as AccessTime,
DocumentName() as Document,
ComputerName() as Machine
AutoGenerate 1;
STORE AuditLog INTO [lib://Logs/AuditLog_$(vTimestamp).qvd] (qvd);
Кастомное решение:
// Таблица активности пользователей
SUB LogActivity(Action)
ActivityLog:
LOAD
OSUser() as User,
Now() as Timestamp,
'$(Action)' as Action
AutoGenerate 1;
CONCATENATE (ActivityLog)
LOAD * FROM [lib://Logs/ActivityLog.qvd] (qvd);
STORE ActivityLog INTO [lib://Logs/ActivityLog.qvd] (qvd);
END SUB
CALL LogActivity('Data Reload Started');
Производительность и оптимизация
64. Какие метрики важны для оценки производительности?
Ключевые метрики:
Response Time:
Время открытия приложения < 3 сек
Время отклика на выборку < 1 сек
Время расчета визуализаций < 0.5 сек
Memory Usage:
RAM footprint приложения
Memory per user (concurrent)
Peak memory during reload
CPU Utilization:
During reload
During user interactions
Calculation time per object
Инструменты мониторинга:
Document Analyzer (QlikView):
Object calculation times
Memory allocation by table
Expression complexity
Operations Monitor (Qlik Sense):
Session duration
App performance metrics
User concurrency
Performance Log:
// В скрипте
LET vStartTime = Now();
// ... операции загрузки ...
LET vDuration = Interval(Now() - vStartTime, 'hh:mm:ss');
TRACE Load completed in $(vDuration);
65. Как оптимизировать использование памяти?
Стратегии оптимизации:
Оптимизация модели данных:
Удаление неиспользуемых полей
Правильные типы данных (числа vs строки)
Денормализация малых справочников
Избегание синтетических ключей
Symbol Tables:
Уменьшение кардинальности
Использование функции AutoNumber() для ключей
Округление числовых значений
Эффективные структуры:
// Вместо timestamp используйте отдельные поля Date(Floor(Timestamp)) as Date, Hour(Timestamp) as Hour // Вместо длинных строк - кодированные в int ключи AutoNumber(ProductName & '|' & Category) as ProductKey
QVD оптимизация:
Инкрементальная загрузка
Сегментирование больших таблиц
66. Как оптимизировать выражения?
Основные принципы:
Set Analysis vs IF:
// Плохо
Sum(If(Year = 2024, Sales))
// Хорошо
Sum({<Year={2024}>} Sales)
2. Предварительные вычисления:
// В скрипте
If(OrderDate >= YearStart(Today()), 1, 0) as IsCurrentYear
// В выражении
Sum(Sales * IsCurrentYear)
3. Переменные для повторяющихся вычислений:
SET vYTDSales = Sum({<Date={">=$(=YearStart(Today()))"}>} Sales);
// Использование: $(vYTDSales)
4. Избегание некоторых функций:
Now(), Today() в выражениях
Rand() без необходимости
Сложные строковые операции
67. Объясните концепцию Calculation Condition
Calculation Condition предотвращает выполнение тяжелых вычислений при неоптимальных условиях.
Применение:
// Базовое условие
Count(Distinct Customer) < 1000
// С сообщением об ошибке
Calculation condition: GetSelectedCount(Year) > 0
Displayed message: "Please select at least one year"
// Комплексные условия
GetSelectedCount(Product) <= 10 AND
GetPossibleCount(Customer) < 5000
// Основанные на производительности
Sum(Aggr(1, Customer, Product)) < 100000 // Проверка размера виртуальной таблицы
68. Как работает кэширование в Qlik?
Типы кэширования:
Expression Cache:
Хранит результаты вычислений
Ключ: выражение + состояние выборки
Очищается при изменении выборки
Selection Cache:
Кэширует состояния возможных/исключенных значений
Ускоряет ассоциативные вычисления
QVD Buffer:
Buffer (Expire 7) LOAD * FROM Source;
// Кэширует в QVD на 7 дней
Оптимизация для кэширования:
Используйте идентичные выражения
Избегайте динамических элементов в визуализациях
Группируйте похожие вычисления
Управление кэшем:
Перезагрузка приложения очищает кэш
Блиц и прочие вопросы
69. Как работает Storytelling в Qlik Sense?
Storytelling — это инструмент для создания презентаций на основе данных.
Создаем «Story» в приложении.
Добавляем снимки визуализаций (snapshots).
Вставляем текст, стрелки, заголовки.
70. Что такое QIX Engine?
QIX Engine — это движок в памяти, который обрабатывает данные в Qlik Sense.
Загружает данные в RAM.
Индексирует их для быстрого поиска и фильтрации.
Поддерживает ассоциативные связи между таблицами.
Обеспечивает интерактивность: клик по фильтру мгновенно обновляет все визуализации. Пример: При фильтрации по году QIX Engine за миллисекунды пересчитывает все агрегации (суммы, средние) на основе нового набора данных.
71. Как использовать функцию NoConcatenate?
NoConcatenate предотвращает автоматическое объединение таблиц с одинаковыми полями.
Table1:
LOAD * INLINE [ID, Value, 1, 100];
Table2:
NOCONCATENATE LOAD * INLINE [ID, Value, 2, 200];
//Создаются две отдельные таблицы.
72. Как использовать функцию match?
Match возвращает позицию значения в списке.
Match(Region, 'Europe', 'Asia', 'America') // Возвращает 1, если Region = 'Europe'.
73. Как использовать функцию WildMatch?
WildMatch проверяет соответствие строки шаблону с подстановочными знаками.
WildMatch(Product, 'A*', 'B*') // Возвращает 1 для продуктов, начинающихся на 'A'.
74. Как настроить автоматическое обновление данных?
Настройте задачу в QlikView Management Console (QMC).
Укажите расписание и путь к QVW-файлу.
75. Как настроить уведомления в QlikView?
Используем триггер на уровне документа («Document Properties» → «Triggers»).
Добавляем действие «Alert» с условием, например, Sum(Sales) < 1000. Пример: Уведомление при падении продаж.
76. Как использовать функцию AutoNumber в Qlik Sense?
AutoNumber присваивает уникальные числа значениям. Если ключ связи между таблицами формата string, то гораздо лучше использовать AutoNumber для ускорения работы приложения.
LOAD AutoNumber(OrderID) AS OrderNum FROM Orders.qvd; // такой ключ работает гораздо быстрее
77. Как использовать функцию RecNo?
RecNo возвращает номер записи в исходном источнике.
LOAD RecNo() AS RecordNumber, * FROM Data.qvd;
78. Что такое Responsive Design в Qlik Sense?
Responsive Design адаптирует визуализации под размер экрана (ПК, планшет, телефон). Настройка встроена по умолчанию, но при желании можно оптимизировать размеры объектов.
79. Как настроить резервное копирование приложения?
Экспортируем приложение из QMC в .qvf.
Сохраняем на сервере или в облаке. Пример: Еженедельный бэкап в папку Backups.
80. Как настроить аудит безопасности в Qlik Sense?
В QMC включаем логирование действий пользователей.
Анализируем лог-файлы в «Monitoring Apps». Пример: Отслеживание входов и изменений.
81. Как настроить совместную работу в Qlik Sense?
Опубликовать приложение в поток (Stream).
Назначить права доступа в QMC.
82. Как настроить мониторинг производительности?
Использовать «Operations Monitor» в QMC или импортировать специальные приложения мониторинга из библиотеки кастомных приложений с сайта Qlik.
83. Как настроить интеграцию с API?
Использовать «REST Connector» в Data Load Editor.
Указать URL и параметры аутентификации.
84. Как создать пользовательскую рассылку отчета в Qlik Sense?
Использовать Qlik NPrinting для интеграции с Qlik Sense.
85. Что такое Insights в Qlik Sense?
Insights — это функция, которая автоматически генерирует визуализации на основе данных. Нажимаем «Insights» и выбираем поля для анализа. Пример: Автоматическая диаграмма продаж по регионам.
Надеюсь, эта статья-шпаргалка будет полезна для вас на собеседованиях на все позиции, в которых так или иначе упоминается BI-система Qlik во всех ее вариациях.
Спасибо, что дошли до самого конца и удачи вам!
P.S. Если вдруг захотите еще больше вопросов с собеседований BI-разработчиков и дата-инженеров, то милости прошу вас в мой скромный телеграм-канал.