Basic Queries
This guide covers the fundamentals of querying data in Velocity using the fluent query syntax.
Overview
Velocity provides a fluent, type-safe API for building SQL SELECT queries through the Query class. All queries start with new Query() and use method chaining to build complex SQL statements.
Key Components:
- Query - The main query builder class
- Table - Represents database tables
- Column - Represents table columns
- Schema - Container for tables
Creating Your First Query
Basic SELECT
Select specific columns from a table:
using YndigoBlue.Velocity;
using YndigoBlue.Velocity.Model;
using YndigoBlue.Velocity.Engine;
// Get table reference from schema
Table users = schema["users"];
// Build query using fluent syntax
Query query = new Query()
.Select([
users["user_id"],
users["username"],
users["email"]
])
.From(users);
// Execute query
using (Manager manager = new Manager(connection))
using (ResultSet results = manager.Retrieve(query))
{
foreach (Result row in results)
{
int id = row.GetFieldInteger("user_id");
string username = row.GetFieldString("username");
string email = row.GetFieldString("email");
Console.WriteLine($"{id}: {username} ({email})");
}
}
Generated SQL:
SELECT user_id, username, email
FROM users
FROM Clause
Single Table
Specify the table to query from:
Table products = schema["products"];
Query query = new Query()
.Select([
products["product_name"],
products["price"]
])
.From(products);
Generated SQL:
SELECT product_name, price
FROM products
Multiple Tables (Implicit Cross Join)
Query from multiple tables without an explicit JOIN creates a cross product:
Table categories = schema["categories"];
Table products = schema["products"];
Query query = new Query()
.Select([
categories["category_name"],
products["product_name"]
])
.From([categories, products]);
Generated SQL:
SELECT categories.category_name, products.product_name
FROM categories, products
Note: For explicit joins with ON conditions, see the Joins documentation.
SELECT * (All Columns)
All Columns from Single Table
Use SelectAll() to retrieve all columns:
Table customers = schema["customers"];
Query query = new Query()
.SelectAll(customers);
Generated SQL:
SELECT *
FROM customers
All Columns from Multiple Tables
Table orders = schema["orders"];
Table customers = schema["customers"];
Query query = new Query()
.SelectAll([orders, customers]);
Generated SQL:
SELECT *
FROM orders, customers
Column Aliases
Rename columns in the result set using aliases:
Basic Alias
Table users = schema["users"];
Query query = new Query()
.Select([
(users["username"], "user_name"),
(users["email"], "email_address")
])
.From(users);
using (Manager manager = new Manager(connection))
using (ResultSet results = manager.Retrieve(query))
{
foreach (Result row in results)
{
// Access by alias
string name = row.GetFieldString("user_name");
string email = row.GetFieldString("email_address");
}
}
Generated SQL:
SELECT username AS user_name, email AS email_address
FROM users
Multiple Aliases
Use tuple syntax for multiple aliased columns:
Query query = new Query()
.Select([
(users["user_id"], "id"),
(users["username"], "name"),
(users["email"], "contact_email"),
(users["created_at"], "registration_date")
])
.From(users);
Generated SQL:
SELECT user_id AS id,
username AS name,
email AS contact_email,
created_at AS registration_date
FROM users
DISTINCT
Remove duplicate rows from results:
DISTINCT on All Selected Columns
Table orders = schema["orders"];
Query query = new Query()
.Select(orders["customer_id"])
.From(orders)
.Distinct();
using (Manager manager = new Manager(connection))
using (ResultSet results = manager.Retrieve(query))
{
Console.WriteLine($"Unique customers: {results.Count}");
}
Generated SQL:
SELECT DISTINCT customer_id
FROM orders
DISTINCT with Multiple Columns
Table orders = schema["orders"];
Query query = new Query()
.Select([
orders["customer_id"],
orders["status"]
])
.From(orders)
.Distinct();
Generated SQL:
SELECT DISTINCT customer_id, status
FROM orders
Note: DISTINCT applies to the entire row, not individual columns. The above query returns unique combinations of customer_id and status.
Limit
Cap the number of rows returned using Limit():
Table products = schema["products"];
Query query = new Query()
.SelectAll(products)
.OrderBy(products["price"])
.Limit(10);
using (Manager manager = new Manager(connection))
using (ResultSet results = manager.Retrieve(query))
{
// results.Count <= 10
foreach (Result row in results)
{
Console.WriteLine(row.GetFieldString("product_name"));
}
}
Velocity translates Limit() to the appropriate native SQL syntax for each database:
| Database | Generated SQL |
|---|---|
| SQL Server | SELECT TOP 10 ... |
| Teradata | SELECT TOP 10 ... |
| MySQL | ... LIMIT 10 |
| PostgreSQL | ... LIMIT 10 |
| SQLite | ... LIMIT 10 |
| DB2 | ... FETCH FIRST 10 ROWS ONLY |
| Oracle | ... FETCH FIRST 10 ROWS ONLY |
Note: Without an
OrderBy()clause, the rows returned are not guaranteed to be in a consistent order. CombineLimit()withOrderBy()for deterministic results.
Retrieving Data
Use manager.Retrieve(query) to load all results into memory as a ResultSet, or manager.Search(query) to stream results row-by-row as a ResultReader. See Retrieve vs Search for a full comparison, decision matrix, and examples.
Reading Field Values
The Result class provides type-safe methods for reading values:
Integer Types
foreach (Result row in results)
{
byte flags = row.GetFieldByte("flags");
short smallNumber = row.GetFieldShort("small_number");
int id = row.GetFieldInteger("user_id");
long bigNumber = row.GetFieldLong("big_number");
}
Floating Point and Decimal Types
foreach (Result row in results)
{
float ratio = row.GetFieldFloat("ratio");
double percentage = row.GetFieldDouble("percentage");
decimal price = row.GetFieldDecimal("price");
}
Text Types
foreach (Result row in results)
{
string username = row.GetFieldString("username"); // General-purpose string accessor
string code = row.GetFieldChar("country_code"); // Fixed-length CHAR column
string email = row.GetFieldVarChar("email"); // VARCHAR column
string notes = row.GetFieldClob("notes"); // Large text / CLOB column
}
Date and Time Types
foreach (Result row in results)
{
DateOnly birthDate = row.GetFieldDate("birth_date"); // Date only
TimeOnly startTime = row.GetFieldTime("start_time"); // Time only
DateTime createdAt = row.GetFieldDateTime("created_at"); // Date and time
DateTimeOffset lastLogin = row.GetFieldTimestamp("last_login"); // Date, time, and timezone
TimeSpan duration = row.GetFieldInterval("duration"); // Time interval
}
Boolean
foreach (Result row in results)
{
bool isActive = row.GetFieldBoolean("is_active");
}
Binary
foreach (Result row in results)
{
byte[] data = row.GetFieldBlob("file_data");
}
GUID
foreach (Result row in results)
{
Guid trackingId = row.GetFieldGuid("tracking_id");
}
Checking for NULL
foreach (Result row in results)
{
if (row.IsNull("optional_field"))
{
Console.WriteLine("Field is NULL");
}
else
{
string value = row.GetFieldString("optional_field");
}
}
Complete Examples
Example 1: User List
using YndigoBlue.Velocity;
using YndigoBlue.Velocity.Model;
using YndigoBlue.Velocity.Engine;
// Get table reference
Table users = schema["users"];
// Build query
Query query = new Query()
.Select([
users["user_id"],
users["username"],
users["email"],
users["created_at"]
])
.From(users);
// Execute and display results
using (Manager manager = new Manager(connection))
using (ResultSet results = manager.Retrieve(query))
{
Console.WriteLine($"Total users: {results.Count}\n");
foreach (Result row in results)
{
int id = row.GetFieldInteger("user_id");
string username = row.GetFieldString("username");
string email = row.GetFieldString("email");
DateTime created = row.GetFieldDateTime("created_at");
Console.WriteLine($"User #{id}");
Console.WriteLine($" Username: {username}");
Console.WriteLine($" Email: {email}");
Console.WriteLine($" Joined: {created:d}");
Console.WriteLine();
}
}
Example 2: Product Catalog with Aliases
Table products = schema["products"];
// Use aliases for cleaner result access
Query query = new Query()
.Select([
(products["product_id"], "id"),
(products["product_name"], "name"),
(products["unit_price"], "price"),
(products["stock_quantity"], "stock")
])
.From(products);
using (Manager manager = new Manager(connection))
using (ResultSet results = manager.Retrieve(query))
{
foreach (Result row in results)
{
int id = row.GetFieldInteger("id");
string name = row.GetFieldString("name");
decimal price = row.GetFieldDecimal("price");
int stock = row.GetFieldInteger("stock");
Console.WriteLine($"{name} (${price:F2}) - {stock} in stock");
}
}
Example 3: Unique Values
Table orders = schema["orders"];
// Get list of unique order statuses
Query query = new Query()
.Select(orders["status"])
.From(orders)
.Distinct();
using (Manager manager = new Manager(connection))
using (ResultSet results = manager.Retrieve(query))
{
Console.WriteLine("Order Statuses:");
foreach (Result row in results)
{
Console.WriteLine($"- {row.GetFieldString("status")}");
}
}
Best Practices
Always use
usingstatements- Ensures proper disposal of Manager, ResultSet, and ResultReader
- Prevents connection leaks
Select only needed columns
- Avoid
SELECT *in production code - Reduces network traffic and memory usage
- Makes code more maintainable
- Avoid
Use appropriate data types
- Use the correct
GetField*method for each column type - Prevents runtime errors and type conversion issues
- Use the correct
Check for NULL values
- Always check
IsNull()before accessing nullable columns - Prevents unexpected exceptions
- Always check
Choose the right retrieval method
- Use
Retrieve()for small result sets or when you need random access - Use
Search()for large result sets or streaming scenarios
- Use
Next Steps
- Filtering Data - Add WHERE clauses to filter results
- Sorting Results - Order query results
- Expressions - Create calculated fields
- Grouping and Aggregation - Summarize data with GROUP BY
- Joins - Combine data from multiple tables