BQL

BQL is how you get answers out of a Niagara station. The Baja Query Language pulls points, histories, and component data into reports, dashboards, and exports – and written well, it does so without dragging the station down. Software Pile builds BQL-driven reporting that operators and managers actually use.

BQL Work We Deliver

  • Reports: energy, runtime, alarm summaries, and compliance exports built on BQL queries
  • Live dashboard data feeds that query the station efficiently for the values on screen
  • History and audit queries across large datasets, tuned so they return before the operator gives up
  • Scheduled exports to files, databases, and enterprise systems

Queries That Do Not Punish the Station

A careless BQL query over a large history set can stall a station for everyone. We write queries that scope, filter, and index sensibly – so reporting is fast and the control system stays responsive while it runs.

Tell us what you need to report on and your Niagara version. We will build the queries and the reports around them.

What Is BQL?

BQL — the Baja Query Language — is the query language built into the Niagara Framework. It is used to search a station’s component space and history data: finding components that match a set of criteria, extracting history records for a report, or feeding a live table in a graphic.

If you arrived here looking for Bloomberg’s BQL, that is a different and unrelated language for financial data. This page is about Niagara.

What Can You Query with BQL?

Two broadly different things, and knowing which one you are working with explains most confusion:

  • The component space — the live station tree. Queries that find all points of a given type, all devices in an alarm state, all components carrying a particular tag, or everything under a given folder.
  • History data — the stored records behind trends and reports. Queries that pull a time range, filter it, and aggregate it for an energy or runtime report.

The same language addresses both, but the practical patterns are quite different, and performance behaviour is different too.

BQL Syntax Basics

A BQL query is resolved through an ORD and typically selects a set of components or records, filters them with a where-style clause, and optionally selects specific properties. Queries can be scoped to a subtree rather than the whole station, which is usually the difference between a query that returns promptly and one that does not.

The most useful habit when learning it is to build queries incrementally in Workbench against a real station and watch both the result and the time taken, rather than composing a long query and running it against production first.

What Are the Common BQL Mistakes?

  • Querying from the station root when the answer lives in one subtree. Scope narrowly.
  • Running expensive queries on a live view that refreshes. A query that is acceptable once is not acceptable every few seconds on every open dashboard.
  • Pulling raw history and aggregating in the browser instead of aggregating in the query, moving far more data than necessary.
  • Assuming a query is portable. Behaviour and available syntax differ between Niagara versions; test against the version you will deploy on.
  • Relying on naming conventions that are not enforced. Queries built on point names break the first time someone names one differently. Tag-based queries are far more durable — which is a large part of the argument for Haystack tagging.

BQL for Reporting

Most commercial BQL work is reporting: energy consumption by area, equipment runtime for maintenance planning, alarm frequency by asset, comfort or setpoint compliance over a period, and scheduled exports that land in someone’s inbox or a business system rather than requiring anyone to open Workbench.

The engineering that makes those reports trustworthy is usually not the query itself. It is making sure the underlying histories exist, are collecting at a sensible interval, survived the last migration, and are tagged consistently enough that the query means the same thing in every building.

Related services: Niagara graphics, dashboards and reporting · engineering tools.

How a BQL Query Actually Resolves

A BQL query is never evaluated on its own. It is the last segment of an ORD chain, and everything to its left decides what the query is able to see. In station:|slot:/Drivers/BacnetNetwork|bql:select name, out.value from control:NumericPoint, the bql: segment inherits the BacnetNetwork component as its starting object and cannot reach anything outside that branch, no matter what the where clause says.

The engine walks the tree beneath that starting object and evaluates the where clause on each component after it has been reached. Cost therefore tracks components enumerated, not components matched: a filter that returns four rows costs the same as one returning four thousand when both start from the same place. That is also why a query which was quick during engineering can be slow in production without a single character changing. The count of components under the base ORD grew; the query did not.

History queries resolve through a different space and behave differently again. A history ORD names one point's record set rather than a branch of the live tree, so the size of the tree is irrelevant and the record count inside each history governs the time. A report covering two hundred points is two hundred resolutions unless the history space itself is queried first for the set.

Which scheme precedes the bql: segment therefore matters as much as the query text, because each one resolves against a different space with a different cost. These are the ones that turn up in front of a bql: segment most often:

  • slot: the ordinary slot path through the live component space, either absolute from the station root or relative to whatever resolved before it.
  • h: resolves a component by its persistent handle instead of its path. The handle survives a rename or a move that would break a slot path, at the price of an ORD no human can read and one that means nothing in any other station.
  • history: the history space rather than the component space. Aimed at a single history it returns records; aimed at the space itself it returns the histories, which is how you find out what actually exists to report on.
  • virtual: the virtual component space, where components are created on demand by a driver instead of being stored in the station. Queries here can force the driver to instantiate and fetch, so the cost includes network round trips to field devices, not just processor time.
  • neql: Niagara Entity Query Language, Niagara 4 only, which selects by tag and relation rather than by path or type. It is often the more durable half of a query that then hands its result to bql:.

Where the Query Runs, and Under Whose Rights

On a Niagara network, reporting queries normally belong on the Supervisor, because that is where history import descriptors have already archived the subordinate stations' records. A controller keeps only what its own capacity retains before rollover, so asking a JACE for last spring returns whatever survived rather than an error, and the report looks complete while covering a shorter period than it claims.

Running the same query on the controller also places it on the processor that is executing control logic. A large history query there competes with the engine cycle, and operators notice it as sluggish graphics and writes that take a moment to land. Because reports are scheduled, this arrives at the same hour every day, which makes it easy to blame on whatever else happens at that hour.

BQL reads and does nothing else. There is no update, insert or delete, so a scheduled report is not a route by which control state can drift, and the account behind it needs read rights and nothing more. The consequence in the other direction is easy to miss: results are filtered by that account's permissions, so a report running under a restricted user returns fewer rows rather than a permission error. Totals come back low with nothing on screen to say why.

What the Histories Have to Look Like Before the Numbers Mean Anything

Collection mode decides what an average means. An interval history writes on a fixed period, so records are evenly spaced and a plain average over them is a time-weighted average. A change-of-value history writes only when the value moves past a tolerance, so a point sitting still for six hours contributes one record while a hunting point contributes hundreds, and averaging those records weights the answer by how often the value moved. The same query run over the two collection types is answering two different questions.

Records also carry status, and status is invisible in a total. Fault, down, stale and null records sit alongside good ones, and a query that selects value without filtering on status folds them in. The failure is asymmetric: an outage leaves a gap, which drags a sum down by exactly the missing quantity while leaving an average looking entirely normal. So totals expose the problem and averages conceal it. Filter on status, then count what was excluded, because a report that quietly discarded a third of its records is describing the instrumentation rather than the building.

Timestamps carry a zone and a calendar. Records are stamped in the station's local time, so a Supervisor sitting in another zone and bucketing by day is grouping around a boundary that is not the site's midnight. Two days a year also hold 23 and 25 hours, so a daily total compared against the same date last year is comparing unequal periods. The error is small enough to read as noise and consistent enough to survive into a trend line.

Capacity has to cover the reporting period before the first report is written rather than after. A history extension configured to keep a fixed record count rolls the oldest records off without complaint: 5000 records at a fifteen minute interval is about 52 days, which satisfies a monthly report and quietly fails a quarterly one by returning a short period instead of an error.

Version Differences That Do Not Announce Themselves

The from clause names a Niagara type as module:Type, and both halves of that name are version specific. Modules were reorganized between NiagaraAX and Niagara 4, and some types moved or were renamed in the process, so a query carried across the migration can name a type the running version does not have. It does not raise an error. An unmatched type matches nothing and the query returns an empty table, which at a glance is indistinguishable from a building where nothing met the criteria.

The test that separates those two cases takes a minute. Widen the from clause to baja:Component, leave the base ORD alone, and count rows. Rows mean the scope is fine and the type name is wrong; no rows mean the base ORD is not resolving where you think it is. Run that before rebuilding anything, and check the finished query against figures somebody already knows to be correct.

In a mixed network the semantics follow where the query executes, not where the data originally came from. Histories imported onto a Niagara 4 Supervisor from an older station are Niagara 4 histories once they land, and queries over them run under Niagara 4 rules. A query resolved through the Niagara network to the older station itself executes there, under that station's syntax and type names. One report can therefore need two versions of a query, and the version that gets tested is usually the local one.

Where BQL Stops Being the Right Instrument

BQL queries one station's spaces. A question spanning several stations with no Supervisor between them has no single tree to resolve against, and the honest structure is to answer it per station and combine the results outside Niagara rather than to write a query that pretends the stations are one system.

The distinction that matters for integration is direction, not schedule. A scheduled export is BQL work: the station runs the query on its own clock, once, and pushes the result out to a file, a database, or a business system. What BQL is a poor instrument for is the reverse, an external system polling the station for current values on its own schedule, because every poll re-resolves the ORD and re-walks the tree from scratch, and several clients polling every few seconds multiply that cost with nothing shared between them. Exposing the points through an interface such as oBIX, where a client registers interest in specific objects and reads them back by address, costs the station far less for the same data.

Long-horizon analysis is the other boundary. Once the question spans several years and many buildings, and has to be set against data the station does not hold at all, such as weather, occupancy or utility billing, the station is the wrong place to keep asking. Export the histories into a relational database on a schedule and let the analysis run there. BQL's job in that arrangement is the export.

Frequently Asked Questions

Is BQL a version of SQL?

No. It borrows select, from and where from SQL and very little else. There are no tables and no schema to declare, the from clause names a Niagara type rather than a table, the row set is whatever the ORD chain reached, and there is no query planner choosing an access path on your behalf. Anything resembling a join across unrelated sets has to be done by whatever consumes the results.

Can BQL report on a period before the history existed?

No. A history extension starts collecting when it is added, and nothing reconstructs records for the time before that. When a report is requested against a point that was never being trended, the honest answer is that collection can start now and the earliest complete month arrives a month later. It is worth checking the actual histories against the requested period before any query gets written, because this is the most common reason a reporting request cannot be answered straight away.

How does a reporting request usually proceed?

It starts with the number rather than the query. Someone states the figure they want, which points or tags produce it, and over what period; the histories behind those points are checked for existence, collection mode and coverage of that period; the query is then built up incrementally in Workbench against a real station and timed at each step; and only after that is it bound to a view, a report, or an export schedule. The order is deliberate, because the expensive failure is discovering after a report is built and reviewed that the underlying data was never capable of answering the question.

What does the site have to provide or decide?

Access to a station, an account with read rights over the relevant parts of the tree, and a destination for the finished output. The rest are decisions a query cannot make on its own: which points count toward each figure when a building has several meters or several units serving one area, what should happen to records flagged bad (dropped silently, or shown as gaps the reader can see), and whether the report has to reconcile with a figure someone else already publishes, such as a utility bill. That last one changes the query, because reconciling to a bill means matching its billing period and its boundaries rather than the calendar month.

Can BQL query anything besides points and histories?

Yes. The component space holds everything in the station tree, so the same language answers configuration questions: which points have no history extension, which alarm extensions are not pointed at an alarm class, which control points are sitting at a manual override nobody released, which schedules exist and what is linked to them. These queries select properties of configuration rather than values. They are usually how a station gets audited before anyone trusts a report built on top of it.

Does BQL reporting need anything installed beyond the station?

No separate query engine, no. BQL is part of the framework, Workbench builds and tests the queries, and the station executes them. What varies is the destination: a view or report rendered in a browser goes out through the station's own web service, a file export needs a writable path on the host and a station permitted to write there, and an export into a database needs the relevant driver module present and permitted by the station license. That last one is a licensing question before it is an engineering one, and it is better answered before the query is written than after.