Back to qSQL

qSQL

Functional Form

Skip to 2 practice questions

Practice questions on the functional (parse-tree) form of qSQL — building queries programmatically with ? and ! when column names or conditions are only known at runtime.


What Is Functional Form?

When you write a select or update statement in q, the interpreter does not run those keywords directly. It first rewrites them into an equivalent function call — a list of arguments that represents the same query in a form the engine can actually execute. That internal representation is called the functional form.

As a developer, you can write the functional form yourself, bypassing the qSQL keywords entirely. You would almost never do this for a fixed query — the qSQL syntax is clearer. The functional form earns its place when you need to assemble a query from pieces at runtime: the columns to return, the conditions to filter on, or the grouping keys might all come from user input, configuration, or a loop.


Two Operators, Four Arguments

Every qSQL statement maps to one of two operators:

qSQL statementOperator
select, exec?
update, delete!

In their basic form, both take four arguments, always in this order:

?[table; where; by; select]
![table; where; by; update]

(The functional select also has extended forms with extra arguments for row limits and sorting; see the KX reference for ?.)

The examples on this page use these two small tables:

trade:([] sym:`AAPL`MSFT`AAPL`GOOG; price:185.2 410.1 99.5 142.8; qty:100 200 50 300)
quotes:([] sym:`AAPL`MSFT; bid:185.1 410.0; ask:185.3 410.2)

Think of the last three arguments as the reverse of how you speak qSQL. In plain syntax you say select … by … from … where … — in functional form the order of those clauses is flipped: where comes before by, which comes before the select phrase.


What Each Argument Does

Argument 1 — the table

Either the table value itself, or its name as a backtick symbol:

trade          / the table value
`trade         / the name — q looks it up in the global namespace

Argument 2 — the where clause

A list of filter conditions. Each condition is written as a small list: (operator; left; right). An empty list () means "no filtering — return all rows":

()                                         / no filter
enlist (>;`price;100.0)                    / price > 100.0
((>;`price;100.0);(=;`sym;enlist`AAPL))   / price > 100.0 AND sym = `AAPL

Argument 3 — the by clause

Controls whether rows are grouped. For grouping, pass a dictionary from output column name to grouping expression, just like the select phrase:

0b                              / no grouping — plain select
1b                              / return distinct rows
(enlist`sym)!enlist`sym         / group by the sym column
`sym`date!`sym`date             / group by sym and date together

(For exec, the by argument can also be the empty list (); see below.)

Argument 4 — the select or update phrase

A dictionary that maps output column names to the expressions that produce them:

()                            / return all columns unchanged
`sym`price!`sym`price         / return sym and price as-is

/ vwap: sum[price*qty] % sum[qty]
(enlist`vwap)!enlist (%;(sum;(*;`price;`qty));(sum;`qty))

Using parse to Learn the Form

The fastest way to get the functional equivalent of any qSQL statement is to ask q to show you. Write your query as a string and pass it to parse:

parse "select sym, price from trade where price > 100"
?
`trade
,,(>;`price;100)
0b
`sym`price!`sym`price

Read the output from top to bottom: the operator, then the four arguments in order. You can lift this directly and adapt it for a dynamic version of the same query.

One thing to watch: parse shows q's internal k notation. The leading , you see in front of conditions is k's shorthand for enlist. When you write the functional form yourself, replace every bare , with enlist.


The Symbol Trap

Inside a functional form, a bare symbol is treated as a column name reference, not a data value. This catches beginners out immediately.

/ q reads `AAPL as "the column called AAPL" — this fails with 'AAPL
?[trade; enlist (=;`sym;`AAPL); 0b; `sym`price!`sym`price]

/ Wrapping in enlist tells q this is a value, not a column name
?[trade; enlist (=;`sym;enlist`AAPL); 0b; `sym`price!`sym`price]

The same applies when your filter value is a list of symbols:

enlist `AAPL`MSFT    / the two-symbol vector as a value, not column references

Whenever you have a symbol that is data rather than a column name, wrap it in enlist.


Side-by-Side: qSQL vs Functional

Selecting columns

/ qSQL
select sym, price from trade where price > 100.0

/ Functional
?[trade; enlist(>;`price;100.0); 0b; `sym`price!`sym`price]

Aggregating with grouping

/ qSQL
select max price by sym from trade

/ Functional
?[trade; (); (enlist`sym)!enlist`sym; (enlist`price)!enlist(max;`price)]

Exec — returning a plain list

/ qSQL
exec sym from trade

/ Functional (by=() with a single column name as the last argument gives exec — returns a list, not a table)
?[trade; (); (); `sym]

Update — adding a column

/ qSQL
update mid:(bid+ask)%2 from quotes

/ Functional
![quotes; (); 0b; (enlist`mid)!enlist(%;(+;`bid;`ask);2)]

Delete rows

/ qSQL
delete from trade where price < 0

/ Functional
![trade; enlist(<;`price;0); 0b; `symbol$()]

Delete a column

/ qSQL
delete price from trade

/ Functional (where clause must be empty when removing columns)
![trade; (); 0b; enlist`price]

When Should You Use It?

The functional form is a specialist tool. Reach for it only when the structure of your query is not fixed at the time you write the code:

  • The columns to return depend on user input or runtime configuration
  • The number of filter conditions changes from call to call
  • You are writing a general-purpose API function that accepts query parameters and builds the request from them
  • You are constructing many similar queries in a loop

For any query where all the columns, conditions, and groupings are known when you write it, plain qSQL reads better and should be the default choice. Both forms give identical results, because qSQL is translated into the functional form before it runs.

Practice Questions

Test your understanding with 2 hands-on questions