Start here
You only need a GribStream API token and a MySQL-compatible client or library.
- Create a free API token or use an existing token.
- Use
gribstreamas the username and the API token as the password. - Select a dataset such as
gfsas the database and connect.
mysql --host=mysql.gribstream.com \
--port=3307 \
--user=gribstream \
--password \
--database=gfs \
--quick
The MySQL CLI prompts for the token, keeping it out of shell history. In this command, --quick tells the CLI to print rows as they arrive instead of buffering the complete result. Other clients use different settings for incremental results.
MySQL queries use the same API token and quota as HTTP API queries. Usage is based on the weather data read to answer the query. If you are unsure which dataset to use, browse the model catalog; gfs is a useful global starting point.
Why MySQL?
Languages and tools with a MySQL driver can use that familiar connection to query GribStream. You do not need to operate a MySQL server, import weather files, or learn a new client library.
The connection is read only and purpose-built for weather data. Datasets appear as databases, weather fields are discoverable, and unsupported SQL returns a clear error.
Find a weather column, then query it
Each GribStream weather parameter appears as a dataset-specific MySQL column. After choosing a dataset, discover its columns before writing the query:
SHOW FULL COLUMNS FROM gfs.timeseries;
In the result, copy the Field value you need. The Comment gives its human-readable name, native units, and exact GS_VALUE(...) equivalent. For GFS 2 m temperature, the column is tmp_2_m_above_ground.
These three forms identify the same weather parameter. The weather column is the simplest SQL form; GS_VALUE is the exact-selector fallback when translating an existing HTTP API request or building a dynamic selector. The JSON form is the selector shown on model pages.
tmp_2_m_above_ground
GS_VALUE fallback
GS_VALUE(
'TMP',
'2 m above ground',
''
)
{
"name": "TMP",
"level": "2 m above ground",
"info": ""
}
Copy weather column names from SHOW FULL COLUMNS or gribstream.selector_columns rather than constructing them yourself. Most are readable; long or colliding names receive a deterministic suffix. Exact selector strings remain case-sensitive.
This returns the next six hours of GFS 2 m temperature for one point:
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
AND lat = 40.758
AND lon = -73.985
AND lead_time BETWEEN '0h' AND '48h'
ORDER BY forecasted_time
LIMIT 100;
This weather column is published in Kelvin. Check its Comment or the catalog for units rather than inferring them from the column name or alias.
The result is made of ordinary rows. Values below are illustrative:
forecasted_time temp_k
2026-08-07 12:00:00 298.4
2026-08-07 13:00:00 299.1
2026-08-07 14:00:00 299.7
Use the catalog discovery workflow to search columns by parameter name, inspect units, and recover the exact JSON selector or GS_VALUE expression when you need it.
If the connection already selected gfs, use FROM timeseries. Otherwise qualify the table as gfs.timeseries.
Start from a working query
Choose the example closest to your goal, expand it, and change only the dataset, discovered weather column, times, or locations you need. Copy column names from SHOW FULL COLUMNS instead of guessing how a selector is normalized.
Query several named locations over a time range
SELECT forecasted_time, name, lat, lon,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T06:00:00Z'
AND (lat, lon, name) IN (
(40.758, -73.985, 'Times Square'),
(29.7604, -95.3698, 'Houston'),
(51.5072, -0.1276, 'London')
);
Query a regular latitude-longitude grid
Smaller grid_step values select more points and consume more quota. Start coarse and narrow the area before increasing resolution.
SELECT forecasted_time, lat, lon,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T06:00:00Z'
AND lat BETWEEN 25 AND 50
AND lon BETWEEN -125 AND -66
AND grid_step = 1;
Query a few exact, non-contiguous forecast times
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time IN (
'2026-07-13T00:00:00Z',
'2026-07-13T06:00:00Z',
'2026-07-14T18:00:00Z'
)
AND lat = 40.758
AND lon = -73.985;
Query forecasts from specific model runs
SELECT forecasted_at, forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM gfs.runs
WHERE forecasted_at BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T12:00:00Z'
AND lead_time BETWEEN '0h' AND '48h'
AND lat = 40.758
AND lon = -73.985
ORDER BY forecasted_at, forecasted_time;
Reproduce the forecasts available before a historical cutoff
SELECT forecasted_at, forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-14T00:00:00Z'
AND forecasted_at <= '2026-07-12T18:00:00Z'
AND lat = 40.758
AND lon = -73.985
ORDER BY forecasted_time;
Query selected members of an ensemble
SELECT forecasted_time, member,
tmp_2_m_above_ground AS temp_k
FROM gefsatmos.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T12:00:00Z'
AND member IN (0, 1, 2)
AND lead_time BETWEEN '0h' AND '48h'
AND lat = 40.758
AND lon = -73.985;
Convert a value and keep only rows matching a threshold
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k,
temp_k - 273.15 AS temp_c
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-14T00:00:00Z'
AND lat = 40.758
AND lon = -73.985
AND temp_c BETWEEN 18 AND 24;
Find the warmest grid points for one forecast time
SELECT forecasted_time, lat, lon,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time = '2026-07-13T18:00:00Z'
AND lat BETWEEN 25 AND 50
AND lon BETWEEN -125 AND -66
AND grid_step = 0.5
ORDER BY temp_k DESC
LIMIT 20;
Connect from your language
Choose your language below. Each example connects securely, runs the same small weather query, and reads its rows. Store the token in an environment variable or secret manager; never commit it in a connection string.
MySQL 8 command-line client. TLS is negotiated automatically; add --quick for incremental output.
mysql \
-h mysql.gribstream.com -P 3307 \
-u gribstream -p -D gfs \
--quick
Connector/Python through SQLAlchemy and pandas. With chunksize, pandas returns an iterator of DataFrames instead of collecting the entire result.
import os
import certifi
import pandas as pd
from sqlalchemy import URL, create_engine
url = URL.create(
"mysql+mysqlconnector",
username="gribstream",
password=os.environ["GRIBSTREAM_API_TOKEN"],
host="mysql.gribstream.com",
port=3307,
database="gfs",
)
engine = create_engine(url, connect_args={
"ssl_verify_cert": True,
"ssl_verify_identity": True,
"ssl_ca": certifi.where(),
})
sql = """
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
AND lat = 40.758 AND lon = -73.985
ORDER BY forecasted_time
"""
for frame in pd.read_sql_query(sql, engine, chunksize=10_000):
print(frame.head())
Connector/J. Its forward-only streaming mode uses a fetch size of Integer.MIN_VALUE.
String SQL = """
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
AND lat = 40.758 AND lon = -73.985
ORDER BY forecasted_time
""";
String url = "jdbc:mysql://mysql.gribstream.com:3307/gfs?sslMode=VERIFY_IDENTITY";
Properties props = new Properties();
props.setProperty("user", "gribstream");
props.setProperty("password", System.getenv("GRIBSTREAM_API_TOKEN"));
try (Connection db = DriverManager.getConnection(url, props);
Statement statement = db.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY)) {
statement.setFetchSize(Integer.MIN_VALUE);
try (ResultSet rows = statement.executeQuery(SQL)) {
while (rows.next()) {
System.out.println(rows.getTimestamp("forecasted_time"));
}
}
}
MySqlConnector for .NET. The reader consumes rows sequentially.
const string SQL = @"
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
AND lat = 40.758 AND lon = -73.985
ORDER BY forecasted_time";
var builder = new MySqlConnectionStringBuilder {
Server = "mysql.gribstream.com",
Port = 3307,
Database = "gfs",
UserID = "gribstream",
Password = Environment.GetEnvironmentVariable("GRIBSTREAM_API_TOKEN"),
SslMode = MySqlSslMode.VerifyFull,
};
await using var db = new MySqlConnection(builder.ConnectionString);
await db.OpenAsync();
await using var command = new MySqlCommand(SQL, db);
await using var rows = await command.ExecuteReaderAsync(
CommandBehavior.SequentialAccess);
while (await rows.ReadAsync()) {
Console.WriteLine(rows.GetDateTime("forecasted_time"));
}
go-sql-driver/mysql. Iterating Rows.Next consumes the result without building a slice of all rows.
const query = `
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
AND lat = 40.758 AND lon = -73.985
ORDER BY forecasted_time`
cfg := mysql.NewConfig()
cfg.User = "gribstream"
cfg.Passwd = os.Getenv("GRIBSTREAM_API_TOKEN")
cfg.Net = "tcp"
cfg.Addr = "mysql.gribstream.com:3307"
cfg.DBName = "gfs"
cfg.TLSConfig = "true"
cfg.ParseTime = true
db, err := sql.Open("mysql", cfg.FormatDSN())
if err != nil { log.Fatal(err) }
defer db.Close()
rows, err := db.QueryContext(ctx, query)
if err != nil { log.Fatal(err) }
defer rows.Close()
for rows.Next() {
var validTime time.Time
var tempK float64
if err := rows.Scan(&validTime, &tempK); err != nil { log.Fatal(err) }
}
mysql2 callback API. The query stream honors Node backpressure.
import mysql from 'mysql2';
const SQL = `
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
AND lat = 40.758 AND lon = -73.985
ORDER BY forecasted_time`;
const db = mysql.createConnection({
host: 'mysql.gribstream.com',
port: 3307,
user: 'gribstream',
password: process.env.GRIBSTREAM_API_TOKEN,
database: 'gfs',
ssl: {},
});
db.query(SQL)
.stream({ highWaterMark: 64 })
.on('data', row => console.log(row.forecasted_time))
.on('error', err => { throw err; })
.on('end', () => db.end());
The Rust mysql crate. query_iter yields rows instead of collecting a Vec.
use mysql::{OptsBuilder, Pool, SslOpts};
use mysql::prelude::Queryable;
let sql = r#"
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
AND lat = 40.758 AND lon = -73.985
ORDER BY forecasted_time"#;
let opts = OptsBuilder::new()
.ip_or_hostname(Some("mysql.gribstream.com"))
.tcp_port(3307)
.user(Some("gribstream"))
.pass(std::env::var("GRIBSTREAM_API_TOKEN").ok())
.db_name(Some("gfs"))
.ssl_opts(Some(SslOpts::default()));
let pool = Pool::new(opts)?;
let mut db = pool.get_conn()?;
let rows = db.query_iter(sql)?;
for row in rows {
println!("{:?}", row?);
}
MySQL C API. mysql_use_result reads one row at a time.
const char *sql =
"SELECT forecasted_time, "
"tmp_2_m_above_ground AS temp_k "
"FROM timeseries "
"WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR "
"AND lat = 40.758 AND lon = -73.985 "
"ORDER BY forecasted_time";
MYSQL *db = mysql_init(NULL);
unsigned int ssl_mode = SSL_MODE_VERIFY_IDENTITY;
mysql_options(db, MYSQL_OPT_SSL_MODE, &ssl_mode);
mysql_real_connect(db, "mysql.gribstream.com", "gribstream",
getenv("GRIBSTREAM_API_TOKEN"), "gfs", 3307, NULL, 0);
mysql_query(db, sql);
MYSQL_RES *result = mysql_use_result(db);
MYSQL_ROW row;
while ((row = mysql_fetch_row(result)) != NULL) {
printf("%s\n", row[0]);
}
mysql_free_result(result);
mysql_close(db);
1. Attach GribStream
DuckDB can attach the connection through its MySQL extension and query discovered weather columns as remote tables. Put credentials in the standard MYSQL_* environment variables and attach in read-only mode.
export MYSQL_HOST=mysql.gribstream.com
export MYSQL_TCP_PORT=3307
export MYSQL_USER=gribstream
export MYSQL_PWD="$GRIBSTREAM_API_TOKEN"
export MYSQL_DATABASE=gfs
ATTACH 'ssl_mode=verify_identity' AS gribstream_db
(TYPE mysql, READ_ONLY);
2. Query one point or a regular grid
Normal attached-table queries currently support one point, as below, or a regular grid defined by latitude and longitude bounds plus grid_step. Keep the time, location, and lead-time conditions in the DuckDB query so they can be sent to GribStream before rows are returned.
SELECT forecasted_time, lead_time, lat, lon,
tmp_2_m_above_ground AS temp_k
FROM gribstream_db.gfs.timeseries
WHERE forecasted_time BETWEEN CURRENT_TIMESTAMP
AND CURRENT_TIMESTAMP + INTERVAL 6 HOUR
AND lat = 40.758
AND lon = -73.985
AND lead_time BETWEEN 0 AND 48
LIMIT 100;
3. Enumerate locations with a raw query
To request several separate coordinates at once, pass a supported GribStream SQL statement through DuckDB's mysql_query table function. The inner statement runs directly against the MySQL connection, so it can use GribStream's coordinate-tuple syntax.
SELECT *
FROM mysql_query('gribstream_db', $sql$
SELECT forecasted_time, name, lat, lon,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN NOW() AND NOW() + INTERVAL 6 HOUR
AND (lat, lon, name) IN (
(40.758, -73.985, 'Times Square'),
(29.7604, -95.3698, 'Houston')
)
AND lead_time BETWEEN '0h' AND '48h'
LIMIT 100
$sql$);
For large results, use your driver's row iterator, streaming mode, or equivalent so it does not collect the entire result in memory.
Choose timeseries or runs
Every dataset schema exposes the same two weather-table shapes. Choose the table from the question you are answering, not from the columns you want returned.
timeseries
Choose this for the best eligible forecast at each requested valid time.
Filter byforecasted_time
runs
Choose this to inspect predictions from one or more specific model runs.
Filter byforecasted_atandlead_time
forecasted_at is the model initialization time. forecasted_time is the valid time being predicted. lead_time is their difference in hours; it can be selected or filtered.
Example: query model-run history
SELECT forecasted_at, forecasted_time, lat, lon,
tmp_2_m_above_ground AS temp_k
FROM gfs.runs
WHERE forecasted_at BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T12:00:00Z'
AND lead_time BETWEEN '0h' AND '48h'
AND lat = 40.758
AND lon = -73.985
ORDER BY forecasted_at DESC, forecasted_time ASC;
Available columns, query controls, and freshness metadata
| Column | Meaning | Notes |
|---|---|---|
dataset | Dataset code that produced the row | Also the MySQL schema name |
forecasted_at | Model initialization time | On timeseries, <= is the only supported run-cutoff operator |
forecasted_time | Valid time being predicted | The primary time column for timeseries |
lat, lon, name | Resolved point and optional label | name is nullable |
member | Ensemble member identifier | Meaningful only for ensemble datasets |
index_updated_at | Latest source-data update associated with the row | Optional freshness metadata; distinct from both forecast timestamps |
lead_time, grid_step | Forecast lead time in hours and requested grid spacing in degrees | Selectable and filterable; grid_step is NULL for enumerated points |
| Dataset-specific weather columns | Native weather values such as tmp_2_m_above_ground | Discover with SHOW FULL COLUMNS; values are DOUBLE |
Select index_updated_at to see the latest source-data update associated with each row. It is freshness metadata, not model initialization time; use forecasted_at to identify the model run.
SELECT forecasted_at, forecasted_time, index_updated_at,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T06:00:00Z'
AND lat = 40.758
AND lon = -73.985
ORDER BY forecasted_time;
Discover datasets, weather columns, and exact selectors
Do not guess column names, parameter levels, or units. Find a dataset, inspect its weather columns, and use the exact selector mapping only when you need the API form or GS_VALUE fallback.
1. Find a dataset
SELECT code, full_name, provider, min_lead_time, max_lead_time,
is_ensemble, member_count, members, parameter_count
FROM gribstream.datasets
WHERE code LIKE '%gfs%'
ORDER BY code;
Check archive coverage and cadence before a large historical query
SELECT code, archive_start, archive_window, rolling_window,
time_resolution, run_cadence, min_lead_time, max_lead_time,
catalog_updated_at
FROM gribstream.datasets
WHERE code = 'gfs';
archive_start and archive_window describe published coverage; a rolling window can move forward. catalog_updated_at shows when the catalog metadata was refreshed. Select index_updated_at when row-level freshness matters.
2. Browse its weather columns
SHOW FULL COLUMNS FROM gfs.timeseries LIKE '%tmp%';
3. Search mappings or use an exact selector
SELECT short_name, full_name, units, has_code_table, variation_count
FROM gribstream.parameters
WHERE dataset = 'gfs'
AND (short_name = 'TMP' OR full_name LIKE '%temperature%')
ORDER BY short_name
LIMIT 20;
SELECT column_name, full_name, units, name, level, info,
selector_json, gs_value_sql
FROM gribstream.selector_columns
WHERE dataset = 'gfs'
AND column_name = 'tmp_2_m_above_ground';
gribstream.selector_columns maps each weather column to its human-readable name, units, JSON selector, and equivalent GS_VALUE expression. It requires one exact dataset predicate.
Use the generated column_name for ordinary SQL. Use GS_VALUE(name, level, info) when you need a direct one-to-one translation from an API selector. This gefsatmosmean selector has a non-empty info value, so all three arguments are required:
{
"name": "CAPE",
"level": "surface",
"info": "ens mean"
}
GS_VALUE(
'CAPE',
'surface',
'ens mean'
)
Model pages continue to show the canonical JSON selector and its exact GS_VALUE fallback. Copy generated SQL column names from the live schema, where collisions and long identifiers have already been resolved.
4. Find comparable signals across datasets
Shared parameters list common weather concepts, output units, and the datasets that support them. Use this catalog before comparing models, then resolve the exact selector tuple for each dataset instead of assuming that selector names match.
SELECT code, label, units, supported_datasets
FROM gribstream.shared_parameters
WHERE code = 'temperature_2m';
Example: calculate 10 m wind speed
After discovering the exact 10 m wind-component selectors for ifsoper, combine them with func.Hypot:
SELECT forecasted_time,
v_10u_sfc AS u_component,
v_10v_sfc AS v_component,
func.Hypot(u_component, v_component) AS wind_speed
FROM ifsoper.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T06:00:00Z'
AND lat = 48.8566
AND lon = 2.3522
ORDER BY forecasted_time;
Machine-readable syntax and MySQL discovery commands
Use these when you want the connection itself to describe its SQL syntax and examples:
SELECT topic, supported_syntax, example, notes
FROM gribstream.sql_dialect
ORDER BY topic;
SELECT name, description, `sql`
FROM gribstream.query_examples
ORDER BY name;
Standard MySQL commands such as SHOW TABLES, SHOW COLUMNS, DESCRIBE, and SHOW CREATE TABLE are also available. SHOW FULL COLUMNS includes concise weather descriptions and exact GS_VALUE mappings:
SHOW FULL COLUMNS FROM gfs.timeseries;
Catalog tables
gribstream.datasetsgribstream.parametersgribstream.parameter_variationsgribstream.selector_columnsgribstream.shared_parametersgribstream.sql_dialectgribstream.query_examples
Metadata queries support projections or *, DISTINCT, ORDER BY, LIMIT with a non-negative offset, and bounded combinations of =, !=, LIKE, IN, and null checks.
Values, calculations, and filters
Calculated columns use familiar SQL expressions and can refer to aliases defined earlier in the select list. Alias a weather column first, then reuse that alias. Keep unit conversions explicit:
SELECT forecasted_time,
tmp_2_m_above_ground AS temp_k,
temp_k - 273.15 AS temp_c
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T12:00:00Z'
AND lat = 40.758
AND lon = -73.985
AND (temp_c BETWEEN 18 AND 30 OR temp_c IS NULL);
Weather-value conditions support BETWEEN, numeric IN, null checks, parentheses, NOT, and boolean combinations. Join time, location, lead-time, member, and model-run conditions with AND; use OR only within weather-value conditions.
Functions and more complex calculations
Familiar numeric functions include ABS, CEIL/CEILING, FLOOR, ROUND, SQRT, POW/POWER, MOD, and TRUNCATE. Point labels support LOWER/LCASE, UPPER/UCASE, TRIM, and Unicode-aware CHAR_LENGTH. MySQL's byte-counting LENGTH is not supported; use CHAR_LENGTH for names.
SELECT forecasted_time,
ugrd_10_m_above_ground AS u,
vgrd_10_m_above_ground AS v,
func.Hypot(u, v) AS wind_speed
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T12:00:00Z'
AND lat = 40.758
AND lon = -73.985
AND wind_speed > 5;
Additional GribStream expression functions use the explicit func. namespace. Expressions support literals, parentheses, unary operators, arithmetic, comparisons, and boolean combinations. See the expression reference for registered func. calls and arguments.
Units, data types, and missing values
Weather parameter columns and GS_VALUE return native units exactly as published in the catalog; choosing an alias does not convert the value. Inspect has_code_table in gribstream.parameters before treating a coded field as a continuous measurement.
Weather values, calculated expressions, latitude, and longitude are returned as MySQL DOUBLE. Timestamp columns are DATETIME(6); dataset, point name, and member identifiers are strings. Missing numeric or timestamp values become SQL NULL. Test them with IS NULL or IS NOT NULL; = NULL is not supported.
Prepared statements
Normal MySQL placeholders work in selectors, calculations, timestamps, coordinates, members, and weather filters. Relative time functions are evaluated when the prepared statement executes, not when it is prepared.
SELECT forecasted_time AS valid_time,
GS_VALUE(?, ?) AS temp_k,
temp_k - ? AS temp_c
FROM gfs.timeseries
WHERE forecasted_time BETWEEN ? AND ?
AND lat = ?
AND lon = ?
AND temp_c BETWEEN ? AND ?
LIMIT 100;
Bounded DISTINCT results
Weather queries may use SELECT DISTINCT ... LIMIT n. Distinctness applies to the complete selected row. It cannot be combined with ORDER BY, and the query may still read the complete bounded selection before it knows that no more unique rows exist. Use it for bounded deduplication, not as a substitute for narrowing time and location.
SELECT DISTINCT name,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T06:00:00Z'
AND (lat, lon, name) IN (
(40.758, -73.985, 'Times Square'),
(29.7604, -95.3698, 'Houston')
)
LIMIT 100;
Time ranges, relative time, and time zones
timeseries.forecasted_time is valid time. runs.forecasted_at is model initialization time. Ranges require both bounds and include both endpoints when written with BETWEEN.
For adjoining windows, prefer a half-open range such as forecasted_time >= start AND forecasted_time < end. It avoids returning the boundary instant twice when consecutive queries are combined.
SELECT forecasted_at, forecasted_time,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN NOW() - INTERVAL 2 DAY AND NOW()
AND forecasted_at <= NOW() - INTERVAL 6 HOUR
AND lat = 40.758
AND lon = -73.985
LIMIT 100;
Supported relative-time functions and timestamp formats
NOW(), CURRENT_TIMESTAMP, and UTC_TIMESTAMP() are evaluated once per statement in UTC; fractional precision such as NOW(6) is accepted. DATE_ADD, ADDDATE, DATE_SUB, SUBDATE, and infix + INTERVAL/- INTERVAL support fixed integer units from microseconds through weeks. Calendar months and years are intentionally excluded because their duration varies.
Timestamp literals accept a date, a MySQL datetime, ISO T form, or RFC 3339 with an offset. A literal without an offset is UTC unless it is the input wall time to CONVERT_TZ.
Select exact, non-contiguous times
Use = for one exact time and IN for several non-contiguous times. Use forecasted_time on timeseries and forecasted_at on runs. The query cookbook includes a complete example.
Query a local calendar day and handle daylight saving time
SELECT forecasted_time,
CONVERT_TZ(forecasted_time, 'UTC', 'Europe/Paris') AS paris_time,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time >=
CONVERT_TZ('2026-10-25 00:00:00', 'Europe/Paris', 'UTC')
AND forecasted_time <
CONVERT_TZ('2026-10-26 00:00:00', 'Europe/Paris', 'UTC')
AND lat = 48.8566
AND lon = 2.3522
LIMIT 100;
Named IANA zones account for daylight-saving transitions. The Paris range above spans the 25-hour fall-back day on October 25, 2026; the same half-open pattern spans the 23-hour spring-forward day on March 29 correctly. Input wall times are converted to UTC immediately; ambiguous or nonexistent local times fail clearly. Result-side conversion also accepts forecasted_at and index_updated_at. Keep the original UTC column when a fall-back day may display the same local clock time twice. Projected timezone aliases are presentation-only and cannot be used in WHERE.
Connections remain in UTC. SET time_zone accepts UTC-equivalent values, SYSTEM, or DEFAULT; use CONVERT_TZ when you need local timestamps.
Use a historical model-run cutoff
On timeseries, forecasted_at <= timestamp excludes newer model runs. It is the only supported operator for that column on timeseries; use index_updated_at when data freshness is the question. The query cookbook includes a complete historical-cutoff example.
Points, grids, lead times, and ensembles
Use one coordinate pair for a point, a tuple list for several named points, or latitude and longitude bounds with grid_step for a regular grid. The query cookbook includes complete examples of each shape.
- One point: use
lat = value AND lon = value. - Several points: use
(lat, lon)or(lat, lon, name)withIN. - A grid: bound both coordinates and set
grid_stepin degrees. Smaller steps select more points and consume more quota.
Lead-time filters
Use quoted durations such as lead_time = '24h'. BETWEEN selects a closed range; paired >=/< comparisons express a half-open range; and a single comparison supplies only a minimum or maximum. Duration strings include forms such as '90m', '24h', or '168h'.
Ensemble-member filters
Check is_ensemble and the members JSON array in gribstream.datasets before selecting members; do not assume that every dataset is an ensemble or that member identifiers have the same range. The query cookbook includes a selected-members example.
Keep queries efficient
Quota usage is based on the weather data read to evaluate a query, not the number of rows returned. A condition on a weather value or calculated alias—such as temp_c > 30—can remove rows from the result only after those values have been read. A large query can therefore consume substantial quota even when its value filters return very few rows.
Conditions on forecast time, location, lead time, and ensemble member reduce the data selected. The following choices have the greatest effect:
- Use
timeseriesunless you specifically need history from multiple model runs. - Keep time ranges short or use an exact time list.
- Select only the weather parameter columns you need.
- Prefer exact points to a broad grid; when using a grid, choose an appropriate
grid_step. - Bound
lead_timeand select only the ensemble members you need.
Use ordering for exploration
ORDER BY is mainly intended for interactive sessions and data exploration, where receiving an immediately readable result is worth some additional work. For a bounded query, ordering first by the primary time column can usually return rows incrementally. Use forecasted_time for timeseries or forecasted_at for runs, choose either direction, and add selected columns or aliases as secondary keys when needed.
SELECT forecasted_time AS valid_time, name, lat, lon,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T12:00:00Z'
AND (lat, lon, name) IN (
(40.758, -73.985, 'Times Square'),
(29.7604, -95.3698, 'Houston'),
(51.5072, -0.1276, 'London')
)
ORDER BY valid_time DESC, temp_k DESC, name ASC;
For runs, begin with forecasted_at and bound both sides of lead_time. If an ordered result is too large to process safely, narrow the selection or remove ORDER BY and sort it in your application.
For backfills and other high-volume data pulls, omit ORDER BY. If order matters, sort the data after receiving it. This avoids making server-side sorting the throughput limit, especially when a broad selection is combined with weather-value filters.
Global rankings
An order beginning with a weather value must consider the complete selection before it can return any rows, so it requires LIMIT. Use this form for rankings rather than incremental output. The query cookbook includes a complete warmest-points example.
Use LIMIT for result size, not quota
LIMIT is useful for keeping interactive output small, but it does not define the amount of weather data read. Filtering and ordering may read far more data than the final result contains. Use the selection controls above when you need to reduce quota usage.
Advanced: validate a query with EXPLAIN
Most queries do not need EXPLAIN. Prefix a weather query with it to validate the statement and see diagnostic information without retrieving weather rows or consuming query quota. It is mainly useful for troubleshooting and support.
EXPLAIN
SELECT forecasted_time, lat, lon,
tmp_2_m_above_ground AS temp_k
FROM gfs.timeseries
WHERE forecasted_time BETWEEN '2026-07-13T00:00:00Z'
AND '2026-07-13T12:00:00Z'
AND (lat, lon) = (40.758, -73.985)
LIMIT 100;
Streaming, cancellation, and errors
Rows become available incrementally. Whether you see them immediately depends on the MySQL client or library, so use its unbuffered, streaming, iterative, or chunked result interface for large queries. The exact setting is client-specific; the client reference appendix links to each library's documentation.
- Client disconnect: closing the connection cancels its active query.
- MySQL CLI: press Ctrl+C to stop the active query.
Application cancellation and concurrent queries
A MySQL connection has one active statement at a time. Consume or close its result before reusing that connection; use a connection pool when an application genuinely needs concurrent queries. Every pooled connection authenticates independently.
Use the driver's cancellation API or cancelable query context; its name and behavior depend on the client. To cancel explicitly, run KILL QUERY <connection_id> from another connection using the same API token. The target connection remains reusable.
MySQL error codes
Errors use ordinary MySQL error responses so existing clients surface them naturally:
| Situation | MySQL code | What you see |
|---|---|---|
| SQL syntax | 1064 | A parser or validation error near the unsupported input. |
| Unsupported SQL | 1235 | A focused explanation of the unsupported construct. |
| Invalid token | 1045 | Access denied during connection authentication. |
| Cancelled query | 1317 | Query execution was interrupted. |
| Rate limit | 1226 | The message explains when to wait before retrying, when available. |
Troubleshooting
Reduce bandwidth for a large result
For large pulls, enable MySQL protocol compression if your client or driver supports it. The setting differs by client; use the official documentation in the appendix.
MySQL CLI: add --compression-algorithms=zstd when connecting.
From any client, check the negotiated compression with:
SHOW SESSION STATUS LIKE 'Compression%';
The TLS connection fails certificate or hostname verification
Connect to mysql.gribstream.com, not its IP address, and use an up-to-date trusted CA bundle. Keep identity verification enabled; disabling it can hide a wrong hostname, interception, or an incomplete trust store. Trust-store and verification settings vary by client; see the client reference appendix.
The connection times out
Confirm that your network allows outbound TCP connections to mysql.gribstream.com on port 3307. Corporate firewalls and restricted notebook environments sometimes block non-default database ports.
A weather column is unknown or an exact selector is rejected
Run SHOW FULL COLUMNS FROM <dataset>.timeseries and copy the Field value instead of guessing it. For exact-selector SQL, copy gs_value_sql from gribstream.selector_columns; selector strings are case-sensitive and must not be translated or normalized.
A value has an unexpected scale, unit, or meaning
GribStream returns the selector's published native values. Recheck units, description, and has_code_table in the catalog, and confirm the exact level and info. Unit conversions are explicit calculated columns; an AS alias alone never changes the data.
A time range, time zone, or ordered query is rejected
Provide both time bounds. Convert named-zone wall-time boundaries to UTC with CONVERT_TZ; do not guess an ambiguous or nonexistent DST time. For an unsafe ordered shape, narrow the selection or remove ORDER BY and sort in the consuming application.
No rows appear until the query finishes
Your client is buffering the result. Select its unbuffered, streaming, iterative, or chunked result mode. MySQL CLI: reconnect with --quick. pandas: pass chunksize. For other clients, follow the result-handling documentation in the appendix.
A query receives MySQL error 1226
Reduce concurrency or wait for the time given in the error message before retrying. An immediate retry loop only extends the rate limit.
Use the connection as a self-describing tool for AI agents
Any generic MySQL database connector—including one exposed as an MCP tool—can connect to mysql.gribstream.com:3307. Provide the API token through the connector's secret configuration.
Give the agent the skill file below. The schema and catalog tables let it discover datasets, weather columns, units, exact selector mappings, dialect rules, and ready-to-run examples before it builds a query.
- Read
gribstream.sql_dialectandgribstream.query_examples. - Search
gribstream.datasets, then inspectSHOW FULL COLUMNSfor the chosen weather table. - Use
gribstream.selector_columnswhen an exact JSON selector orGS_VALUEmapping is needed. - Build and execute the smallest bounded query that answers the question.
- Use
EXPLAINonly to validate or troubleshoot a query.
Download the GribStream MySQL agent skill for the complete workflow, syntax boundaries, and recovery rules.
Supported SQL and deliberate boundaries
Weather queries
SELECTfromtimeseriesandruns- Discovered weather columns and exact
GS_VALUEfallbacks - Calculated aliases, familiar numeric/string functions, and documented
func.calls - Time ranges and lists, points, grids, members, lead times
- Weather-value boolean filters
- Time-first ordering or bounded global top-N
- Bounded
DISTINCT, prepared statements, and optionalEXPLAINdiagnostics
Intentionally unsupported
- Writes, DDL, transactions, and locks
- Joins, subqueries, aggregation, and grouping
SELECT *for weather results- Unbounded global sorting and order expressions
- Non-zero weather-result offsets
- Unlisted MySQL functions and implicit SQL coercions
These boundaries keep queries predictable, results streamable, and errors clear instead of accepting SQL with surprising behavior.
Beta compatibility policy
Stable during beta. We intend to preserve the connection and token-authentication flow, dataset schemas, timeseries and runs, bounded time and location selection, released weather-column names, exact GS_VALUE selectors, calculated columns, filters, streaming, cancellation, and catalog discovery.
May evolve. Additional convenience functions, advanced ordering and DISTINCT forms, timezone presentation, prepared-statement details, and behavior specific to third-party tools may change as we learn from beta use.
How changes ship. Additive capabilities may appear without advance notice. If documented behavior must change incompatibly, we will make reasonable efforts to publish release notes and notify active users we can identify. This beta has no production availability commitment and may be withdrawn.
The running adapter publishes its release, source revision, and SQL compatibility version in gribstream.adapter_info. Released weather-column names are append-stable: adding a catalog selector will not rename a column already published by this adapter.
Beta release notes
August 8, 2026 — adapter 0.1.0, SQL dialect 1. Initial public beta contract with secure MySQL-compatible access, discoverable weather columns, bounded weather queries, streaming, and cancellation.
Full compatibility matrix
| Surface | Supported forms | Boundary |
|---|---|---|
| Session | USE, VERSION(), DATABASE(), UTC time functions, common session variables and status probes | Compatibility behavior, not a full MySQL server variable set |
| Discovery | SHOW DATABASES, SHOW TABLES, SHOW FULL COLUMNS, DESCRIBE, SHOW CREATE TABLE | Only GribStream schemas and tables |
| Metadata SELECT | Projections or *, DISTINCT, boolean filters, ORDER BY, LIMIT and non-negative offsets | Catalog and information_schema tables only |
| Weather SELECT | Explicit fixed and discovered weather columns, GS_VALUE fallback, calculations, bounded predicates, filters, and limited DISTINCT | No weather *, joins, subqueries, grouping, or aggregation |
| Ordering | Up to eight selected keys; time-first ordering or a limited global top-N | No expressions, ordinals, duplicate keys, or unbounded global sort |
| Prepared statements | Normal ? placeholders in selectors, expressions, times, locations, members, and filters | Up to five prepared statements per connection |
| Advanced diagnostics | EXPLAIN SELECT ... | Validates without executing; EXPLAIN ANALYZE is unsupported |
| Cancellation | KILL QUERY connection_id from a connection using the same token | Cannot inspect or cancel another token's work |
Appendix: client configuration references
TLS, compression, result buffering, cancellation, timeouts, and connection pooling are configured by the client or driver rather than by SQL. Use the documentation for the library you actually connect with.
| Client | Official documentation | Useful for |
|---|---|---|
| MySQL CLI | Client options and connection options | TLS, compression, --quick, timeouts, and other CLI flags |
| Python | Connector/Python connection arguments and pandas read_sql | TLS, compression, pooling, connection options, and chunked DataFrames |
| Java | Connector/J configuration properties | TLS, compression, timeouts, and JDBC behavior |
| C# / .NET | MySqlConnector connection options | TLS, compression, pooling, and timeouts |
| Go | go-sql-driver/mysql documentation | DSN options, TLS, compression, timeouts, and pooling |
| Node.js | mysql2 documentation | Connections, TLS, pools, prepared statements, and result streaming |
| Rust | mysql crate documentation | TLS, compression, pools, and row iteration |
| C | MySQL C API guide | Connection and result APIs |
| DuckDB | DuckDB MySQL extension | Environment-variable credentials, read-only ATTACH, TLS, remote-table queries, and mysql_query |
| DBeaver | MySQL connection settings and SSL configuration | MySQL 8 connection, SSL, certificate trust, and database navigation |
