Operators, modifiers, dollar-sign expansion, and the P() and E() functions: the foundation separating intermediate developers from senior ones.
Before advancing to complex patterns, you need to master the exact anatomy. Every part of a Set Analysis expression has a specific role - and confusing them is the source of 90% of bugs.
Sum( { set_identifier < set_modifier > } expression )
^ ^ ^
| | |
Set identifier Set modifier Aggregation
expression
// Practical examples of identifiers// Total sales - ignores any Region, Product, Year selection Sum({1} Amount) // Sales in current selection - identical to Sum(Amount) without Set Analysis Sum({$} Amount) // Sales under the previous selection Sum({$_1} Amount) // Sales under bookmark "BM01" state (e.g., Top 10 Customers) Sum({BM01} Amount)
Critical distinction: {1} ignores ALL selections, including selections on the expression's own field. {$} respects the current selection. Picking the wrong identifier is the most silent and dangerous mistake in Set Analysis.
Set operators allow you to combine, subtract, intersect, and modify data sets with precision. They are the foundation of set algebra in Qlik - and what makes Set Analysis genuinely powerful.
Combines two sets: the result contains all elements from both. Equivalent to logical OR when applied to different subsets of the same field.
// Sales of Electronics OR Furniture (regardless of current selection)
Sum({<Category={'Electronics'}> + <Category={'Furniture'}>} Amount)
// More concise equivalent for the same field:
Sum({<Category={'Electronics', 'Furniture'}>} Amount)
// More powerful union: sets from different fields
// Current selection + November records from any year
Sum({$ + <Month={'November'}>} Amount)
// Real-world case: customers in the current selection OR VIP customers
Count({$ + <CustomerSegment={'VIP'}>} DISTINCT CustomerID)
Returns the first set minus elements also in the second. Essential for exclusion analysis: "everything except X".
// All records EXCEPT returns
Sum({1 - <Status={'Return'}>} Amount)
// Current selection EXCEPT 'Product X'
Sum({$ - <Product={'Product X'}>} Amount)
// Real-world case: net revenue = gross minus cancellations and returns
Sum({1 - <TransactionType={'Cancellation', 'Return'}>} GrossAmount)
// All customers who did NOT buy last month
// (used for churn analysis)
Count({
<CustomerID=P({1}<Month={$(=Month(Today()))}> CustomerID)>
-
<CustomerID=P({1}<Month={$(=Month(AddMonths(Today(),-1)))}> CustomerID)>
} DISTINCT CustomerID)
Returns only elements present in both sets simultaneously. Equivalent to logical AND between distinct sets.
// Customers who bought Electronics AND are in the current selection
Count({
<CustomerID=P({1}<Category={'Electronics'}> CustomerID)>
*
$
} DISTINCT CustomerID)
// Real-world case: average ticket for customers who purchased
// BOTH in Q1 AND Q4 (seasonally recurring customers)
Avg({
<CustomerID=P({1}<Quarter={'Q1'}> CustomerID)>
*
<CustomerID=P({1}<Quarter={'Q4'}> CustomerID)>
} OrderValue)
// The * operator is the right tool for cohort analysis:
// "what is the LTV of customers who met both conditions"
Returns elements that are in one set OR the other, but not both. Useful for comparison analyses where you want to highlight what is exclusive to each set.
// Products sold in 2023 OR in 2024, but not in both
// (products that entered or left the portfolio)
Count({
<ProductID=P({1}<Year={2023}> ProductID)>
/
<ProductID=P({1}<Year={2024}> ProductID)>
} DISTINCT ProductID)
// Interpretation:
// Set A: products with sales in 2023
// Set B: products with sales in 2024
// A / B = new products (only in 2024) + discontinued products (only in 2023)
Modifiers alter the active selection for a specific field within the set. They are expressed between < > and determine exactly which values of each dimension participate in the calculation.
// Defines exactly which values
// the field must have in the set
Sum({<Year={2024}>} Amount)
Sum({<Region={'South', 'West'}>} Amount)
Sum({<Status={'Active'}>,
Year={2024}>} Amount)
// Overrides the current field selection
// and uses the specified values
// Removes the selection from a specific field
// while keeping other field selections intact
// Ignore Year selection, keep the rest
Sum({<Year=>} Amount)
// Ignore both Month AND Quarter, keep others
Sum({<Month=, Quarter=>} Amount)
// Practical for "full-year total" within
// a context filtered by week
The most powerful modifier type: the set of values is determined by an expression evaluated at render time. Combines Set Analysis with dynamic logic.
// Current year derived from expression - dynamic, never needs updating
Sum({<Year={$(=Year(Today()))}>} Amount)
// Previous month calculated dynamically
Sum({<Month={$(=Month(AddMonths(Today(), -1)))},
Year={$(=Year(AddMonths(Today(), -1)))}>} Amount)
// Top N values of the Product field (defined by variable vN)
// $(vN) expands to the variable value at calculation time
Sum({<ProductID=Top(Rank(Sum(Amount)), 1, $(vN))>} Amount)
// Dynamic period: from year start to today
Sum({<Date={">=$(=YearStart(Today()))<=$(=Today())"}>} Amount)
The difference between Year={2024} and Year={$(=Year(Today()))} seems small but is critical in production. The second always calculates the correct year, without needing to update the expression at each year boundary.
// Products whose name starts with "Pro"
Sum({<Product={"Pro*"}>} Amount)
// Customers whose email is from the @company.com domain
Count({<Email={"*@company.com"}>} DISTINCT CustomerID)
// Records with code containing "US" in any position
Sum({<CountryCode={"*US*"}>} Amount)
// Combining wildcard with expression:
// Products that start with the user-selected letter
// (vLetter is a variable set by an input box)
Sum({<Product={"$(vLetter)*"}>} Amount)
Dollar-sign expansion $( ) is the mechanism that makes Set Analysis truly dynamic. It resolves the content of the expansion before Set Analysis is evaluated - allowing variables, expressions, and even other Set Analysis expressions to define the set parameters.
// Pattern 1: variable as dynamic filter// Script: SET vCurrentYear = Year(Today()); // In expression: Sum({<Year={$(vCurrentYear)}>} Amount) // Expands to: Sum({<Year={2025}>} Amount) // Auto-updates on every reload - no expression changes needed // Pattern 2: dynamic expression as parameter // Period: from first day of month to today Sum({<Date={">=$(=MonthStart(Today()))<=$(=Today())"}>} Amount) // Pattern 3: variable containing a full Set Analysis expression // Script: SET vVIPFilter = '{<CustomerSegment={''VIP''}>}'; // In expression: Sum($(vVIPFilter) Amount) // Expands to: Sum({<CustomerSegment={'VIP'}>} Amount) // Reuse the filter across multiple charts without repetition
// Pattern 4: numeric expansion to avoid locale issues// Variable: LET vTarget = 150000.50; // Problem: in locales with comma as decimal separator, // $(vTarget) may expand to "150.000,50" - invalid in Set Analysis // Wrong (may break in different locales): Sum({<Amount={>$(vTarget)}>} Orders) // Correct - $(#vTarget) always returns the number without formatting: Sum({<Amount={>$(#vTarget)}>} Orders) // Always expands to: Sum({<Amount={>150000.5}>} Orders)
Rule of thumb: whenever a modifier value comes from a numeric variable, use $(#variable) instead of $(variable). Prevents silent bugs in environments with regional settings different from the development environment.
The P() (Possible Values) and E() (Excluded Values) functions are the most sophisticated tools in Set Analysis. They allow you to use the set of possible or excluded values from one field as the parameter for another field - creating high-precision cross-field filters.
// P(field) returns the set of POSSIBLE
// (non-excluded) values of a field
// in the current or a defined set
// Customers possible in the current selection
P(CustomerID)
// Products possible in set {1} filtered by Category
P({1}<Category={'Electronics'}> ProductID)
// Use: filter ANOTHER field by the possible values
Sum({<CustomerID=P({1}<Region={'West'}> CustomerID)>} Amount)
// "Sales of customers who are from the West region"
// E(field) returns the set of EXCLUDED
// (grayed out in Qlik) values of a field
// in the current selection
// Customers excluded by the current selection
E(CustomerID)
// Use: calculate over what was excluded
// (impact analysis - "what was left out")
Sum({<CustomerID=E(CustomerID)>} Amount)
// "Sales of customers who were excluded
// by the current selection" - impact analysis
The most powerful use of P() is cross-field filtering. Instead of listing explicit values, you instruct Qlik to "use the values of field X that satisfy condition Y as the filter for field Z".
// Case 1: sales generated by customers who purchased a specific product
// Without P(): impossible without a JOIN in script
// With P(): elegant and direct
Sum({
<CustomerID = P({1}<ProductID={'PROD-LAPTOP-001'}> CustomerID)>
} Amount)
// Step by step:
// 1. P({1}<ProductID={'PROD-LAPTOP-001'}> CustomerID)
// returns all CustomerIDs who bought PROD-LAPTOP-001
// 2. <CustomerID = [that set of IDs]>
// filters the data to those customers
// 3. Sum(Amount) over that filtered data
// Case 2: cross-sell analysis
// How much do Electronics buyers also spend in other categories?
Sum({
<CustomerID = P({1}<Category={'Electronics'}> CustomerID),
Category = {'Appliances'}>
} Amount)
// "Sales in the Appliances category
// made by customers who also bought Electronics"
// Case 3: hierarchy - sales of managers whose reports hit their target
Sum({
<ManagerID = P({1}<QuotaPercent={>100}> ManagerID)>
} BonusAmount)
// Cohort intersection: customers who purchased in BOTH periods
// (high-retention customers - bought in Q1 AND Q3)
Count({
<CustomerID =
P({1}<Quarter={'Q1'}> CustomerID)
*
P({1}<Quarter={'Q3'}> CustomerID)
>
} DISTINCT CustomerID)
// Cohort difference: new customers in the current period
// (bought now, but did NOT buy in the previous period)
Sum({
<CustomerID =
P({1}<Year={$(=Year(Today()))}> CustomerID)
-
P({1}<Year={$(=Year(Today())-1)}> CustomerID)
>
} Amount)
// "Revenue generated by first-time buyers this year"
P() and E() are evaluated in the context of the set that wraps them. P({1} Field) and P({$} Field) produce completely different results: the first ignores the selection, the second respects it. Always be explicit about which context you want.