Development environment
ConradBI home

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, OR and NOT. Choose between values with CASE.
  • A missing value is [NULL]. A comparison with a missing value is never met, so test for it with = [NULL] or fill it with COALESCE.
  • The same line gives the same answer on your computer or inside SQL Server.
In this guide
  1. What HU++ is
  2. Where you write it
  3. Before you start
  4. Your first condition: keep shipped orders
  5. Your first value: a Revenue column
  6. How a line is built
  7. Conditions
  8. Values
  9. Missing values
  10. Text and numbers
  11. Dates
  12. The code window
  13. Recipes
  14. When something goes wrong
  15. Reference

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? CASE does 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
Email 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.

  1. Open Prep, then your Stepped Transform Prep.
  2. In Step Management, pick Row Filter from the list and click Add.
  3. In the code window, type:
HU++
[Status] = "Shipped"
  1. 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

  1. In Step Management, pick Custom Column and click Add.
  2. In Column name, type Revenue.
  3. In the code window, type:
HU++
[Units] * [Unit Price]
  1. 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:

HU++
[Units] >= 100

How 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.

HU++
[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

HU++
[Region] IN ("East", "West")
HU++
[Units] BETWEEN 10 AND 100

BETWEEN 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:

HU++
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

HU++
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:

HU++
CASE WHEN [Units] >= 100 THEN "Large" WHEN [Units] >= 10 THEN "Medium" ELSE "Small" END

An 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.

  1. A comparison with a missing value is neither met nor unmet. A Row Filter drops the row, and NOT doesn't bring it back. Where Discount is empty, [Discount] < 0.2 drops the order, and so does NOT ([Discount] < 0.2).
  2. = [NULL] tests for a missing value, and != [NULL] for one that's there. [Ship Date] = [NULL] finds the orders that haven't shipped.
  3. 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:

HU++
[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:

HU++
[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] < 10 is met by "9.5". Text that isn't a number, like "n/a", meets neither < nor >=.
  • NUMBER turns 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, and TRIM takes 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:

HU++
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:

HU++
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):

HU++
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] > 10 works 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

HU++
IsThisCalendarYear([Order Date])

Keep the last 30 days

HU++
IsWithinLastDays([Order Date], 30)

Drop cancelled and test orders, but keep orders with no status

HU++
[Status] NOT IN ("Cancelled", "Test") OR [Status] = [NULL]

Find orders still open after a week

HU++
[Ship Date] = [NULL] AND DATEDIFF("day", [Order Date], [TODAY]) > 7

Revenue after discount

HU++
[Units] * [Unit Price] * (1 - COALESCE([Discount], 0))

Margin percent, to one decimal place

Add the Revenue column from your first value first.

HU++
ROUND(([Revenue] - [Cost]) / [Revenue] * 100, 1)

An order with no revenue gets an empty value, not an error.

Order size band

HU++
CASE WHEN [Units] >= 100 THEN "Large" WHEN [Units] >= 10 THEN "Medium" ELSE "Small" END

Shipping status

HU++
CASE WHEN [Ship Date] = [NULL] THEN "Not shipped" WHEN DATEDIFF("day", [Order Date], [Ship Date]) <= 3 THEN "On time" ELSE "Late" END

Days to ship

HU++
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:

HU++
STARTOF("month", [Order Date])

A year and quarter label

HU++
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:

HU++
DATE([Year], 12, 31)

Email domain

HU++
LOWER(MID([Email], FIND([Email], "@") + 1, LEN([Email])))

Product family from the code

HU++
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 us

What changed in this guide

  • October 30, 2026: First edition, written for ConradBI 1.2.51.