Parameters

SettingValue
DatabaseSQLite 3.50.4 (64-bit)
OSArch Linux x86_64
CPUAMD Ryzen 5 6600H
Table Rows208,561

Table Schema

CREATE TABLE "verse_text" (
  "id" INTEGER, 
  "verse_id" INTEGER, 
  "version_id" INTEGER, 
  "text" TEXT
);

View Definition

CREATE VIEW v_passage AS
SELECT
  ver.code,
  b.book_name,
  ba.abbreviation,
  c.chapter_num,
  v.verse_num,
  vt.text,
  vt.word_count,
  vt.char_length
FROM verse v
JOIN verse_text vt ON vt.verse_id = v.id
JOIN version ver ON vt.version_id = ver.id
JOIN chapter c ON v.chapter_id = c.id
JOIN book b ON c.book_id = b.id
JOIN book_abbreviation ba ON ba.book_id = b.id

Experiment

10 warm-up runs followed by 100 timed runs for each query type.


Query Comparison

Query Type Virtual Columns SELECT Calculated Column
Word Count
SELECT 
  book_name,
  AVG(word_count),
  MAX(word_count),
  MIN(word_count)
FROM v_passage
GROUP BY book_name;
SELECT 
  book_name,
  AVG(LENGTH(text) - LENGTH(REPLACE(text, ' ', '')) + 1),
  MAX(LENGTH(text) - LENGTH(REPLACE(text, ' ', '')) + 1),
  MIN(LENGTH(text) - LENGTH(REPLACE(text, ' ', '')) + 1)
FROM v_passage
GROUP BY book_name;
Character Count
SELECT 
  book_name,
  AVG(char_length),
  MAX(char_length),
  MIN(char_length)
FROM v_passage
GROUP BY book_name;
SELECT 
  book_name,
  AVG(LENGTH(text)),
  MAX(LENGTH(text)),
  MIN(LENGTH(text))
FROM v_passage
GROUP BY book_name;

Results

Word Count - WHERE code = 1981

QueryAvg (s)Min (s)Max (s)StdDev (s)
Virtual Column0.04970.04890.05180.0005
SELECT Calculation0.09170.09020.09560.0011
Word count with filter

Word Count - WHERE code = 'NRSVUE'

QueryAvg (s)Min (s)Max (s)StdDev (s)
Virtual Column0.21190.20800.23500.0046
SELECT Calculation0.35810.35150.38120.0059
Word count NRSVUE filter

Word Count - No WHERE Clause

QueryAvg (s)Min (s)Max (s)StdDev (s)
Virtual Column1.93521.90172.02990.0256
SELECT Calculation1.74481.71681.85550.0267
Word count no filter

Character Length - No WHERE Clause

QueryAvg (s)Min (s)Max (s)StdDev (s)
Virtual Column0.71810.70340.74470.0102
SELECT Calculation0.70140.68570.73150.0110
Char length no filter

Character Length - WHERE code = 'NRSVUE'

QueryAvg (s)Min (s)Max (s)StdDev (s)
Virtual Column0.12500.12340.13080.0013
SELECT Calculation0.16630.16410.17530.0015
Char length NRSVUE filter

Character Length - WHERE code = 1981

QueryAvg (s)Min (s)Max (s)StdDev (s)
Virtual Column0.02790.02750.03130.0005
SELECT Calculation0.04150.04090.04670.0006
Char length 1981 filter

Conclusion

When no predicates are applied, the normal SELECT calculation is generally faster. However, when predicates are applied over the version, the virtual column queries perform significantly faster.

When a column is declared as GENERATED ALWAYS AS (...), SQLite can internally index or cache some computation logic, even if the column is VIRTUAL. Even though virtual columns aren't physically stored, the query planner can optimize evaluation order more effectively when the expression is named in the schema versus dynamically written.

In other test cases, execution speed when using indexes appears to be identical. But in other cases, virtual columns provide an additional performance benefit over SELECT calculations, and vice-versa.

💡 Key Takeaway: Use virtual columns when queries include WHERE clauses on filtered datasets. Use SELECT calculations when querying the full table without filters.