Nova SQLite Data Table & Filter ๐
Nova SQLite Data Table & Filter ๐
- row_data_json
- table
- row_data_json
- rows_json
- matched_rows
- total_rows
- where_clause
- table
- selected_rows
What it's for
Batch work ends up in a table eventually. You generate forty ACE-Step tracks, you want the twelve tagged blues with a bpm under 100, and the moment you hand-edit a list between runs, batch processing stops being worth it. This node decides which rows the rest of your workflow even sees:
Browser โโบ Data Table & Filter โโบ Row Iterator (or Single Row Filter)
The Nova Dynamic SQLite Browser reads a database out of ComfyUI's input/ folder and outputs rows. The Row Iterator loops one row per run and emits each column as its own output, so artist and bpm become wires you can feed into a prompt. In between, this node shows the whole table as an interactive grid and narrows it with conditions you build from dropdowns. The author's framing is blunt and right: you never write SQL, and the word WHERE never appears.
How it works
Filtering happens in Python, over rows already in memory. Conditions are stored as structured JSON (where_json), never assembled into a statement, so a value like O'Brien, 50% off or the classic x'; DROP TABLE albums;-- is only ever compared as text - a better answer than "we sanitised it carefully."
The SQL still exists, separately, on where_clause, and it escapes properly: quotes doubled, column names quoted, % and _ escaped for LIKE with ESCAPE '\' so they match literally instead of acting as wildcards. Conditions combine left to right, so A AND B OR C reads as (A AND B) OR C, up to 16 of them, and the operators cover the arithmetic plus contains, does not contain, starts with, ends with, in list, not in list, is empty, is not empty. Numbers compare as numbers when both sides look numeric, as text otherwise.
In the grid you click to select, Ctrl/โ-click to add, Shift-click for a range, drag to sweep, with all / invert / none shortcuts. The # column is the index the downstream nodes use, and selections are remembered by position - so a highlight survives changing conditions, re-running, and reloading the workflow.
Inputs and outputs worth knowing
Two of the six inputs are hidden in the UI and driven by the node itself - where_json (the condition builder) and selected_rows (clicking rows). You don't type into either.
The one you'll set is output_rows: matching rows (the default) passes everything the conditions matched, selected rows passes only what you highlighted - a hand-picked subset of a filter result, no export step. case_sensitive is off by default, which is what you want for text. max_display_rows (200) limits only what the grid shows; every match still passes on, so it can't silently truncate a batch.
Optionally connect row_data_json from the Browser, or a table (NOVA_TABLE) from another pack node to filter its rows instead. With both connected, row_data_json supplies the rows and table carries the database and table names forward.
Of the seven outputs: row_data_json is the pink wire into the Iterator or Single Row Filter, rows_json the same rows as a plain string, matched_rows and total_rows counters you can log, where_clause the escaped SQL, table the standard payload you wire into Nova Tag Writer, and selected_rows the count of highlighted rows.
Installing it
Nothing extra for this node - it comes with the pack:
comfy node install comfyui-novaaudioplayer
# or
cd ComfyUI/custom_nodes
git clone https://github.com/NovaFemme/ComfyUI-NovaAudioPlayer.git
Restart ComfyUI and hard-refresh (Ctrl + F5): all four SQLite nodes are custom JavaScript. The pack's declared packages, mutagen and pymysql, belong to its tagging and MySQL nodes; this one needs neither.
Databases go in ComfyUI/input/, top level or in sub-folders - .db, .sqlite, .sqlite3, scanned six levels deep, hidden folders skipped. Sub-folder databases appear by relative path. After copying one in, hit โณ to rescan; no restart needed.
Where people get burned
Press โถ Execute before you queue anything. It walks the chain from the Browser outward and fills every display with real data - no prompt submitted, no other node runs, and it calls the same Python a real execution does. Skipping it is how people queue a forty-track batch against a filter they never looked at.
The errors are literal and all fixable. Nothing connected means wire the Browser to row_data_json; 0 columns selected means you haven't clicked any column badges on the Browser; No rows usually means the upstream query failed, not that the table is empty. And output_rows set to selected rows with nothing highlighted passes nothing on, with a warning.
Changing table or database costs you wires. The downstream nodes grow one output per selected column, so switching tables rebuilds that selection and the wired outputs vanish. The Browser warns you on-node first.
A condition on a dropped column shows red, not a wrong answer. Deselect a column a condition refers to and it's outlined and warned about instead of quietly changing your result. Selected-but-filtered-out rows are never passed on either - conditions always apply first.
Should you use it
If your workflow already has a Browser and an Iterator, yes - it's what makes that chain usable, since you pick rows in a grid instead of writing SQL against a .db file you can't see. Treat it as a filter for a Nova row pipeline, not a general SQLite client. For "take twelve specific tracks out of this library and run them," it's the shortest path in the pack.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| where_json | STRING | [] | Set by the condition builder on the node: the conditions, as a JSON list. |
| selected_rows | STRING | [] | Set by clicking rows in the grid: the highlighted rows, as a JSON list. |
| output_rows | COMBO | matching rows | Which rows leave this node: everything the conditions match, or only the rows highlighted in the grid. |
| case_sensitive | BOOLEAN | false | Off: text is matched regardless of upper / lower case |
| max_display_rows | INT | 2001โ10000 | How many rows the table shows. All matching rows are still passed on. |
| row_data_jsonopt | NOVA_SQLITE_ROWS | Rows from the Nova Dynamic SQLite Browser. | |
| tableopt | NOVA_TABLE | A NOVA_TABLE from any pack node, so its rows can be filtered too. |
Outputs (7)
| Name | Type | Description |
|---|---|---|
| row_data_json | NOVA_SQLITE_ROWS | The rows leaving this node, for the Row Iterator and Single Row nodes. |
| rows_json | STRING | The same rows as plain text. |
| matched_rows | INT | How many rows the conditions matched. |
| total_rows | INT | How many rows came in. |
| where_clause | STRING | The same conditions as SQL, safely escaped. |
| table | NOVA_TABLE | The same rows as a NOVA_TABLE, for Nova Tag Writer and the other pack nodes. |
| selected_rows | INT | How many rows are highlighted in the grid. |