Home / How-to / Prepare your data
HU++: write conditions and values
Tell ConradBI which rows to keep and what a new column should say, in one line you can read out loud.
The short version
- HU++ is one line that ConradBI works out for every row. A condition answers yes or no. A value gives the row something new.
- Columns go in square brackets, text in double quotes, and dates as
DATE(2026, 1, 31):[Region] = "East". - Join conditions with
AND,ORandNOT. Choose between values withCASE. - A missing value is
[NULL]. A comparison with a missing value is never met, so test for it with= [NULL]or fill it withCOALESCE. - The same line gives the same answer on your computer or inside SQL Server.
In this guide
What HU++ is
Most data preparation asks two questions of every row. Should this row stay? What should a new column say? HU++ is how you answer them in ConradBI. You write one line, and ConradBI works it out row by row.
There are two kinds of line:
- A condition is met or not met:
[Status] = "Shipped". - A value gives the row something to keep:
[Units] * [Unit Price].
Each place you write HU++ asks for one kind, so you always know which one to write.
A line only reads the row in front of it. It runs no code and no loops, and it never changes your source. When your data comes from SQL Server, ConradBI turns the line into SQL and the server does the work. Otherwise it runs on your computer. Both give the same answer, so you write it once.
Coming from Excel or SQL?
CASEdoes the job of IF, text goes in double quotes, and a missing value is[NULL]. The reference has a short table of equivalents.
Where you write it
| Where | What you write | What it does |
|---|---|---|
| Row Filter step | A condition | Keeps the rows that meet it |
| Custom Column step | A value, a name and a type | Adds a column with that value |
| Join Element step, set to HU++ condition | A condition over both elements' columns | Joins the pairs of rows that meet it |
| A visual's Filters tab, By HU++ | A condition | Counts only the rows that meet it |
| An Interactive Report's filters | A condition | Filters the report's visuals |
| A dimension's Filter group | A condition over the dimension's columns | Keeps the dimension rows that meet it |
DAR-C Compute and FilterContext |
A value, or a condition | Does a calculation's math, or narrows its rows |
The Row Filter and Custom Column steps work the same in a Stepped Transform Prep and in a Stepped Definition Grain. For DAR-C, see the DAR-C guide.
In a step or a dimension filter, a line with a problem changes nothing until you fix it. A typo never empties your data.
Before you start
You need a Stepped Transform Prep with its source element bound, so columns enter its steps. If nothing enters a step yet, the designer says so above the code window.
The examples in this guide use one small Orders table, the raw order data behind the Sales fact in the DAR-C guide:
| Column | Type | Example |
|---|---|---|
| Order ID | Text | SO-1001 |
| Order Date | Date | 2026-03-14 |
| Ship Date | Date | 2026-03-17, or empty until it ships |
| Region | Text | East, West, North or South |
| Channel | Text | Online or Store |
| Product Code | Text | A-1020 |
| Units | Number | 12 |
| Unit Price | Decimal | 24.50 |
| Cost | Decimal | 180.00 |
| Discount | Decimal | 0.10, or empty |
| Status | Text | Shipped, Open or Cancelled |
| Text | ana@northwind.com |
Swap in your own column names as you go. The rest stays the same.
Your first condition: keep shipped orders
This takes about two minutes.
- Open Prep, then your Stepped Transform Prep.
- In Step Management, pick Row Filter from the list and click Add.
- In the code window, type:
[Status] = "Shipped"- Open the Data tab. It shows the result up to the selected step: only shipped orders are left.
Read back, the line says: "Keep the row when its Status is Shipped."
Capitals don't matter, in names or in text. [status] = "shipped" keeps the same rows.
Tip: Pick Row Filter, not Filter Rows. Filter Rows is the simpler step, where you pick one column, an operator and a value. Row Filter takes any HU++ condition.
Your first value: a Revenue column
- In Step Management, pick Custom Column and click Add.
- In Column name, type
Revenue. - In the code window, type:
[Units] * [Unit Price]- Next to Type, the designer says Gives Decimal: the type the value gives. Leave it, or pick another type and the value is converted to it. A value that won't convert is left empty.
The new column is added at the end. Steps after this one can use it as [Revenue].
A value can be more than math. It can be text, a date, a choice between values, or a condition, which gives a True or False column:
[Units] >= 100How a line is built
| Piece | Write it as | Example |
|---|---|---|
| A column | Its name in square brackets | [Unit Price] |
| Text | In double quotes. Double a quote inside it | "East", "5"" pipe" |
| A number | As it is | 12, 12.5, -3 |
| A date | DATE(year, month, day) |
DATE(2026, 1, 31) |
| A missing value | [NULL] |
[Ship Date] = [NULL] |
| Yes and no | [TRUE] and [FALSE] |
|
| Today's date | [TODAY] |
[Order Date] < [TODAY] |
A few rules make typing easier:
- Capitals don't matter in column names, commands or text comparisons.
- Type
[in the code window for a list of the columns and constants, and pick one instead of typing it. - Where two elements meet, as in a Join Element step, name the element too:
[Orders].[Region].
Conditions
Comparing
| Operator | Means | Example |
|---|---|---|
= |
Equal to | [Region] = "East" |
!= |
Not equal to. <> works too |
[Units] != 0 |
< <= |
Less than, at most | [Units] <= 10 |
> >= |
More than, at least | [Units] > 10 |
Numbers compare as numbers, dates as dates, and text without regard to capitals.
Joining conditions
AND needs both conditions met. OR needs either one. NOT turns a condition around.
[Region] = "East" AND ([Channel] = "Online" OR [Units] > 100)AND is worked out before OR. Without the parentheses, this line would also keep every order of more than 100 units, from any region. Use parentheses whenever you mix the two.
In a list, or between two values
[Region] IN ("East", "West")[Units] BETWEEN 10 AND 100BETWEEN includes both ends. Put NOT before either one for the opposite: [Status] NOT IN ("Cancelled", "Test") or [Units] NOT BETWEEN 10 AND 100.
Matching a pattern
LIKE matches text against a pattern. [*] stands for any run of characters, and [_] for exactly one.
| Line | Is met by |
|---|---|
[Product Code] LIKE "A-[*]" |
A-1020 and A-3300 |
[Product Code] LIKE "[*]0" |
Any code that ends in 0 |
[Product Code] LIKE "A-[_][_][_][_]" |
A- and exactly four more characters |
If you know SQL: % and _ are ordinary characters here. Use [*] and [_] instead.
For the everyday cases, CONTAINS, STARTSWITH and ENDSWITH read more easily:
CONTAINS([Email], "northwind")Values
Math
+, -, * and / work on numbers, with * and / before + and -. Parentheses settle the order: ([Units] + 1) / 2.
Two things give an empty value instead of an error: a missing number on either side, and dividing by 0. [Cost] / [Units] is empty for an order of 0 units.
Dates don't take + or -. Move a date with DATEADD and count between dates with DATEDIFF, both in Dates.
Joining text
CONCAT([Region], " - ", [Channel])This gives East - Online. A missing value adds nothing. Numbers come out as they are, and dates as 2026-03-14 00:00:00. For the date alone, take its first ten characters: LEFT(TEXT([Order Date]), 10).
Choosing with CASE
CASE gives the value of the first WHEN whose condition is met:
CASE WHEN [Units] >= 100 THEN "Large" WHEN [Units] >= 10 THEN "Medium" ELSE "Small" ENDAn order of 150 units is Large, 12 is Medium, and 5 is Small. The order of the WHENs matters, because the first one met wins.
ELSE is optional. Without it, a row that meets no condition gets an empty value.
In the code window, type CASE on a row and press Enter. You get a WHEN and a THEN to fill, + WHEN for another pair, and an ELSE while you edit the CASE.
When the values are of different types, the column takes a type that holds them all. A number and a piece of text give Text.
Missing values
A missing value, written [NULL], is the most common reason a line doesn't do what you expect. Three rules cover it.
- A comparison with a missing value is neither met nor unmet. A Row Filter drops the row, and
NOTdoesn't bring it back. Where Discount is empty,[Discount] < 0.2drops the order, and so doesNOT ([Discount] < 0.2). = [NULL]tests for a missing value, and!= [NULL]for one that's there.[Ship Date] = [NULL]finds the orders that haven't shipped.- Math with a missing value gives a missing value.
[Units] * [Unit Price] * (1 - [Discount])is empty wherever Discount is.
COALESCE fills the gap with the first value that isn't missing:
[Units] * [Unit Price] * (1 - COALESCE([Discount], 0))Lists work the same way. An order with no Status is neither IN nor NOT IN a list, so add it back when you want it:
[Status] NOT IN ("Cancelled", "Test") OR [Status] = [NULL][NULL] inside a list works too: [Status] IN ("Shipped", [NULL]).
Text and numbers
- Text compares without regard to capitals.
"east"equals"East". - Text that reads as a number compares as a number. If Code is a text column,
[Code] < 10is met by "9.5". Text that isn't a number, like "n/a", meets neither<nor>=. NUMBERturns text into a number.NUMBER([Code])gives an empty value when the text isn't a plain number. "1,000" isn't one, because commas don't count.- Spaces at the end don't count in
LEN, andTRIMtakes them off both ends.
The text functions are UPPER, LOWER, TRIM, LEFT, RIGHT, MID, LEN, REPLACE and FIND, all in the reference. Together they can take text apart. This gives the domain of an email address:
LOWER(MID([Email], FIND([Email], "@") + 1, LEN([Email])))FIND gives where the @ is, MID takes what comes after it, and LOWER makes it small letters. BOB@Contoso.COM gives contoso.com.
Dates
Write a date with DATE, so it can't be misread as month/day or day/month: DATE(2026, 1, 31). Text next to a date is read in year-month-day order, so [Order Date] >= "2026-01-01" works too.
Take a date apart with YEAR, MONTH, DAY, QUARTER, WEEKDAY and WEEK. WEEKDAY counts from 1 for Monday to 7 for Sunday. WEEK gives the ISO week.
Move a date with DATEADD("unit", count, date). The unit is "day", "week", "month", "quarter" or "year", always in quotes:
DATEADD("month", 1, [Order Date])A month after March 31 is April 30, because a month from the 31st lands on the month's last day. A negative count moves back.
Count between dates with DATEDIFF("unit", from, to):
DATEDIFF("day", [Order Date], [Ship Date])It counts the boundaries it crosses. DATEDIFF("year", DATE(2025, 12, 31), DATE(2026, 1, 1)) is 1, though the dates are a day apart.
Go to the start of a period with STARTOF("month", [Order Date]), or "week", "quarter" or "year". A week starts on Monday. MONTHEND gives the month's last day.
Ask about today. A date condition needs nothing after it:
| Condition | Is met by a date |
|---|---|
IsToday([Order Date]) |
Today |
IsThisMonth([Order Date]) |
In this calendar month |
IsLastMonth([Order Date]) |
In last calendar month |
IsYearToDate([Order Date]) |
From January 1 to the end of today |
IsWithinLastDays([Order Date], 30) |
In the last 30 days, today included |
IsWeekend([Order Date]) |
On a Saturday or a Sunday |
The reference lists all of them.
Today is the date of the computer doing the work: the SQL Server when the step runs there, and the processing computer otherwise. In a visual's Filters tab, it is the computer that draws the visual. Near midnight, two computers in different time zones can disagree by a day.
The code window
You write HU++ in a code window, not a plain text box. Each row holds one piece: a comparison, a value, AND or OR. A command like CASE, CONCAT, NOT or a function opens blocks under its row for its parts, and each block is shaded so you can see where it ends.
| To do this | Do this |
|---|---|
| See what fits on a row | Press Space at the start of the row, or Ctrl+Space |
| Pick a column or a constant | Type [ |
| Start a command, like CASE | Type its name and press Enter |
| Choose from a list | Up and Down, then Enter or Tab. Escape closes it |
| Move between rows and blocks | Tab and Shift+Tab, or Up and Down |
| Select a whole block | Ctrl+A, then Ctrl+C, Ctrl+X or Delete |
| Undo | Use the tool's Undo. Ctrl+Z on a row undoes typing in that row first |
A few more things to know:
- Type or paste a whole line on one row.
[Region] = "East" AND [Units] > 10works on a single row. The next time the step opens, it is laid out one piece per row. - Paste one line at a time. A row holds a single line, which is why every example in this guide is one line long.
- A CASE has its own controls. + WHEN adds a pair, ✕ takes one off, and the icon at the right of the CASE row deletes the whole CASE.
- Problems show under the code. The step runs once they are fixed. See When something goes wrong.
Recipes
Each recipe is one line, ready to copy. The first four go in a Row Filter step, the rest in a Custom Column step.
Keep this year's orders
IsThisCalendarYear([Order Date])Keep the last 30 days
IsWithinLastDays([Order Date], 30)Drop cancelled and test orders, but keep orders with no status
[Status] NOT IN ("Cancelled", "Test") OR [Status] = [NULL]Find orders still open after a week
[Ship Date] = [NULL] AND DATEDIFF("day", [Order Date], [TODAY]) > 7Revenue after discount
[Units] * [Unit Price] * (1 - COALESCE([Discount], 0))Margin percent, to one decimal place
Add the Revenue column from your first value first.
ROUND(([Revenue] - [Cost]) / [Revenue] * 100, 1)An order with no revenue gets an empty value, not an error.
Order size band
CASE WHEN [Units] >= 100 THEN "Large" WHEN [Units] >= 10 THEN "Medium" ELSE "Small" ENDShipping status
CASE WHEN [Ship Date] = [NULL] THEN "Not shipped" WHEN DATEDIFF("day", [Order Date], [Ship Date]) <= 3 THEN "On time" ELSE "Late" ENDDays to ship
DATEDIFF("day", [Order Date], [Ship Date])Empty until the order ships.
The month of the order, as a date
For a monthly grain or a monthly chart:
STARTOF("month", [Order Date])A year and quarter label
CONCAT(YEAR([Order Date]), "-Q", QUARTER([Order Date]))This gives 2026-Q1.
A date from a year
When a source gives only the year, end it on December 31:
DATE([Year], 12, 31)Email domain
LOWER(MID([Email], FIND([Email], "@") + 1, LEN([Email])))Product family from the code
LEFT([Product Code], 1)When something goes wrong
Problems show under the code window. These are the ones you'll meet most.
| What you see | What to do |
|---|---|
| "There is no column called [Revenu] here." | Check the spelling, or type [ and pick the column. A column added by a later step isn't there yet |
| "'Shipped' isn't a command: a column goes in brackets, text in quotes." | Put the text in quotes: "Shipped" |
| "Missing the closing quote of the text." | Close the quote |
| "Missing ] to close the name." | Close the bracket: [Status] |
| "A condition must compare or test something: this gives text." | A Row Filter needs a condition. Compare the column to something: [Region] = "East" |
| "Only one condition or value goes in a block: join conditions with AND or OR." | Two pieces sit side by side. Put AND or OR between conditions, or commas between a function's values |
| "Missing a condition after AND." | The line stops too early. Finish the condition, or take the AND off |
| "Missing THEN after the condition." | Every WHEN needs a THEN: WHEN [Units] > 10 THEN "Big" |
| "MONTH needs its values in parentheses" in a DATEADD | The unit needs quotes: DATEADD("month", 1, [Order Date]) |
| "DATEADD takes its unit in quotes, one of …" | Use one of the units listed, in the singular: "month", not "months" |
| "+ works on numbers: [Order Date] gives date." | Move a date with DATEADD("day", 1, [Order Date]) |
| "LIKE matches text: …" | Put the pattern in quotes: LIKE "A-[*]" |
| "DATE(2026, 2, 30) isn't a date." | That month has no such day. Check the numbers |
| "Give the column a name." or "There is already a column called … at this step." | Every Custom Column needs a name of its own |
| Rows disappear that you expected to keep | A comparison with a missing value isn't met. See Missing values |
| A new column is empty for some rows | Math with a missing value, dividing by 0, or a value that won't convert to the Type you picked |
| A date condition is off by a day | Today comes from the computer doing the work. See the note in Dates |
Reference
Operators
| Operator | What it does |
|---|---|
= != <> < <= > >= |
Compare two values |
+ - * / |
Math on numbers. * and / go first |
AND, OR |
Join conditions. AND goes first |
NOT |
Turns a condition around |
IN (…), NOT IN (…) |
Met when the value is, or isn't, in the list |
BETWEEN … AND …, NOT BETWEEN |
Met inside, or outside, the range. Both ends count |
LIKE |
Matches a pattern, with [*] and [_] |
( … ) |
Settles the order |
Constants
| Constant | Means |
|---|---|
[NULL] |
A missing value |
[TRUE], [FALSE] |
Yes and no |
[TODAY] |
Today's date, with no time |
[*] |
In a LIKE pattern, any run of characters |
[_] |
In a LIKE pattern, exactly one character |
Values and choices
| Function | What it gives | Example |
|---|---|---|
CASE … END |
The value of the first WHEN met, or the ELSE |
CASE WHEN [Units] > 10 THEN "Big" ELSE "Small" END |
COALESCE(value, value, …) |
The first value that isn't missing | COALESCE([Discount], 0) |
NUMBER(value) |
The value as a number, or empty | NUMBER([Code]) |
TEXT(value) |
The value as text | TEXT([Units]) |
TODATE(value) |
The value as a date, its time dropped | TODATE("2026-03-14") |
ISNUMBER(value) |
A condition: met when NUMBER would give a number | ISNUMBER([Code]) |
ISDATE(value) |
A condition: met when TODATE would give a date | ISDATE("2026-03-14") |
Text
| Function | What it gives | Example |
|---|---|---|
CONCAT(value, value, …) |
The values joined into one text | CONCAT([Region], " - ", [Channel]) |
UPPER(text), LOWER(text) |
Capitals, or small letters | UPPER([Region]) |
TRIM(text) |
The text without spaces at either end | TRIM([Region]) |
LEFT(text, count) |
The first characters | LEFT([Product Code], 1) |
RIGHT(text, count) |
The last characters | RIGHT([Order ID], 4) |
MID(text, start, count) |
Characters from a position, counting from 1 | MID([Order ID], 4, 2) |
LEN(text) |
The number of characters, without spaces at the end | LEN([Order ID]) |
REPLACE(text, find, with) |
The text with every find replaced | REPLACE([Product Code], "-", "") |
FIND(text, part) |
Where the part starts, or 0 | FIND([Email], "@") |
CONTAINS(text, part) |
A condition: the part is in the text | CONTAINS([Email], "northwind") |
STARTSWITH(text, part) |
A condition: the text starts with it | STARTSWITH([Product Code], "A") |
ENDSWITH(text, part) |
A condition: the text ends with it | ENDSWITH([Email], ".org") |
Numbers
| Function | What it gives | Example |
|---|---|---|
ROUND(number, places) |
Rounded, a half away from zero. Negative places round to tens or hundreds | ROUND(2.345, 2) gives 2.35 |
FLOOR(number), CEILING(number) |
Rounded down, or up, to a whole number | FLOOR(2.7) gives 2 |
ABS(number) |
The number without its sign | ABS(-3) gives 3 |
MOD(number, divisor) |
What is left after dividing | MOD(7, 3) gives 1 |
POWER(number, exponent) |
The number raised to the exponent | POWER(2, 10) gives 1024 |
GREATEST(value, …), LEAST(value, …) |
The largest, or smallest, of numbers or dates | GREATEST([Order Date], [Ship Date]) |
Dates
| Function | What it gives | Example |
|---|---|---|
DATE(year, month, day) |
A date | DATE(2026, 1, 31) |
YEAR(date), MONTH(date), DAY(date) |
A part of the date, as a number | YEAR([Order Date]) |
QUARTER(date) |
1 to 4 | QUARTER([Order Date]) |
WEEKDAY(date) |
1 for Monday to 7 for Sunday | WEEKDAY([Order Date]) |
WEEK(date) |
The ISO week, 1 to 53 | WEEK([Order Date]) |
DATEADD("unit", count, date) |
The date moved by a number of days, weeks, months, quarters or years | DATEADD("day", -7, [TODAY]) |
DATEDIFF("unit", from, to) |
The boundaries between two dates | DATEDIFF("day", [Order Date], [Ship Date]) |
STARTOF("unit", date) |
The first day of the week, month, quarter or year | STARTOF("quarter", [Order Date]) |
MONTHEND(date) |
The last day of the month | MONTHEND([Order Date]) |
Date conditions
Each takes a date and is met when the date falls in the window. A missing date isn't met.
| Window | Conditions |
|---|---|
| Days | IsToday, IsYesterday, IsTomorrow, IsPast, IsFuture, IsWeekend |
| Weeks, Monday to Sunday | IsThisWeek, IsLastWeek, IsNextWeek |
| Months | IsThisMonth, IsLastMonth, IsNextMonth, IsMonthToDate |
| Quarters | IsThisQuarter, IsLastQuarter, IsQuarterToDate |
| Years | IsThisCalendarYear, IsLastCalendarYear, IsThisRollingYear, IsYearToDate |
| A number of days | IsWithinLastDays(date, days): that many days back, through today |
IsThisRollingYear runs from this day a year ago to the end of today. IsPast means yesterday or earlier, and IsFuture tomorrow or later.
Types
A value has one of these types. The Custom Column step shows the type its value gives, and converts the value to the type you pick.
| Type | Holds |
|---|---|
| Number, Big Number | Whole numbers. Big Number holds larger ones |
| Decimal | Numbers with decimals |
| Text, Paragraph | Text. Paragraph holds longer text, and is what CONCAT, REPLACE and TEXT give |
| Date, Date & Time | A date, or a date with a time |
| True or False | Yes or no. A condition used as a value gives this |
If you know Excel or SQL
| Excel or SQL | HU++ |
|---|---|
IF(test, a, b) |
CASE WHEN test THEN a ELSE b END |
IS NULL, IS NOT NULL |
= [NULL], != [NULL] |
ISNULL(a, b), IFNULL(a, b) |
COALESCE(a, b) |
LIKE 'A%', LIKE 'A_C' |
LIKE "A[*]", LIKE "A[_]C" |
'text' in single quotes |
"text" in double quotes |
a & b to join text |
CONCAT(a, b) |
TODAY(), GETDATE() |
[TODAY], a date with no time |
EOMONTH(date, 0) |
MONTHEND(date) |
IFERROR(a / b, …) |
Not needed: dividing by 0 gives an empty value |
Missing something?
Tell us what you were trying to do and what this guide didn't answer. We update it with every release.
Write to usWhat changed in this guide
- October 30, 2026: First edition, written for ConradBI 1.2.51.
