SQL JOIN Cheatsheet - SQL JOIN Query Reference

For developers who join tables but still get the row counts wrong. The recurring trap is that a JOIN on a one-to-many parent duplicates parent rows, inflating results unless you dedupe. By the end you can pick INNER vs LEFT by whether un-matched rows should survive, understand the duplication pitfall on one-to-many joins, express candidate-missing conditions with LEFT JOIN ... WHERE ... IS NULL, and choose EXISTS for correlated subqueries.

Databases·41 commands·Last updated 2026-07-21
sqljoinDatabaseQuery

Typical Use Case

For engineers who write reports daily, chase numbers that do not match, or speed up an existing query. The difficulty is never the syntax. Filtering a right-hand condition in WHERE after a LEFT JOIN quietly turns the outer join into an inner one, and joining many tables with the wrong driving table multiplies rows and confuses the optimiser. Covers the practical forms of INNER, LEFT, RIGHT, FULL and CROSS, self-joins, joins against aggregates, EXPLAIN, indexes, FORCE INDEX, and EXISTS.

INNER JOIN 5

SELECT * FROM users INNER JOIN orders ON users.id = orders.user_id
Return only matched rows from both tables
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id
Short form JOIN (defaults to INNER)
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id AND o.status = 'paid'
Multiple ON conditions jointly decide the match
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id WHERE o.total > 100
Filter the whole result with WHERE after JOIN
SELECT u.name, o.total, p.amount FROM users u JOIN orders o ON u.id = o.user_id JOIN payments p ON o.id = p.order_id
Chained multi-table INNER JOIN

LEFT / RIGHT JOIN 6

SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id
All left rows; right NULL if no match
SELECT * FROM users RIGHT JOIN orders ON users.id = orders.user_id
All right rows; left NULL if no match
SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id WHERE orders.id IS NULL
Anti-join: left-only rows (users with no orders)
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'
ON condition in LEFT JOIN does not drop left rows
SELECT * FROM orders o RIGHT JOIN users u ON u.id = o.user_id WHERE o.id IS NULL
RIGHT JOIN anti-join (users with no orders)
SELECT u.name, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id
LEFT JOIN + aggregate, count users incl. zero orders

FULL / CROSS JOIN 5

SELECT * FROM users FULL OUTER JOIN orders ON users.id = orders.user_id
All rows from both; NULL where unmatched
SELECT * FROM users u FULL JOIN orders o ON u.id = o.user_id WHERE u.id IS NULL OR o.user_id IS NULL
Symmetric difference (rows unique to each side)
SELECT * FROM table1 CROSS JOIN table2
Cartesian product
SELECT * FROM table1, table2
Implicit CROSS JOIN (comma)
SELECT * FROM sizes CROSS JOIN colors
All combinations (size × color)

Self & Multi-table 5

SELECT e.name, m.name AS manager FROM employees e LEFT JOIN employees m ON e.manager_id = m.id
Self-join (employee-manager)
SELECT a.name, b.name AS parent FROM categories a JOIN categories b ON a.parent_id = b.id
Self-join (category-parent)
SELECT * FROM a JOIN b ON a.id = b.a_id JOIN c ON b.id = c.b_id
Three-table join
SELECT t1.name AS grandchild, t3.name AS grandparent FROM tree t1 JOIN tree t2 ON t1.parent_id = t2.id JOIN tree t3 ON t2.parent_id = t3.id
Multi-level self-join (3 generations)
SELECT u.name, o.total, p.amount FROM users u JOIN orders o ON u.id = o.user_id JOIN payments p ON o.id = p.order_id
Three-table (user-order-payment)

JOIN & Aggregation 5

SELECT u.name, COUNT(o.id) AS order_count FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id
Order count per user
SELECT u.name, SUM(o.total) AS total_spent FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING SUM(o.total) > 1000
HAVING filters aggregates (spent > 1000)
SELECT c.name, AVG(o.total) AS avg_order FROM customers c JOIN orders o ON c.id = o.customer_id GROUP BY c.id
Group average
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING COUNT(o.id) = 0
LEFT JOIN + HAVING for zero-order users
SELECT DATE(o.created_at) AS d, COUNT(*) FROM orders o JOIN users u ON u.id = o.user_id GROUP BY DATE(o.created_at)
Group by date

JOIN Performance 6

EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id
Show execution plan
EXPLAIN ANALYZE SELECT * FROM users u JOIN orders o ON u.id = o.user_id
Run & show timings (PostgreSQL / MySQL 8.0+)
CREATE INDEX idx_orders_user_id ON orders(user_id)
Index foreign key to speed up JOIN
SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id
Select only needed columns (avoid SELECT *)
SELECT * FROM users u STRAIGHT_JOIN orders o ON u.id = o.user_id
MySQL: force left table as driver
SELECT * FROM users u JOIN orders o FORCE INDEX(idx_user_id) ON u.id = o.user_id
MySQL: force a specific index

USING & Natural Join 4

SELECT * FROM users JOIN orders USING (user_id)
USING shorthand (same-named join column)
SELECT * FROM users u JOIN orders o USING (user_id, tenant_id)
USING multiple columns (merges same-named col)
SELECT * FROM users NATURAL JOIN orders
Natural join (auto-match all same-named cols; use with care)
SELECT * FROM users u JOIN orders o ON u.id = o.user_id AND u.tenant_id = o.tenant_id
Multi-column join (can use USING)

Subquery & EXISTS 5

SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)
Users who have orders
SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)
Users with no orders
SELECT * FROM orders o WHERE o.user_id IN (SELECT id FROM users WHERE status = 'active')
IN subquery filter
SELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS cnt FROM users u
Correlated subquery (scalar)
SELECT * FROM orders WHERE user_id = (SELECT MAX(user_id) FROM orders)
Subquery returning a single value

Parameter matrix

ParameterEffectExample
INNER JOIN ... ON只保留两侧都匹配的行;不匹配的行直接消失SELECT * FROM users INNER JOIN orders ON users.id = orders.user_id
JOIN u ON ...(表别名)JOIN 是 INNER JOIN 的缩写;写别名让多表连接时的列引用不易混淆SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id
LEFT JOIN ... ON保留左表全部行,右表无匹配时补 NULL,是「左表为主」的默认选择SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id
RIGHT JOIN ... ON保留右表全部行;实际工程里通常改写成 LEFT JOIN 更易读SELECT * FROM users RIGHT JOIN orders ON users.id = orders.user_id
... WHERE o.id IS NULL反连接写法:找出左表中没有右表记录的实体(无订单用户、未分类商品)SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id WHERE orders.id IS NULL
LEFT JOIN ... ON u.id = o.user_id AND o.status = 'paid'把状态条件放进 ON 里,才不会把 LEFT JOIN 降级成 INNER JOINSELECT u.name, o.total FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'
FULL OUTER JOIN两侧都保留;MySQL 不支持,需要用 LEFT JOIN 与 RIGHT JOIN 加 UNION 模拟SELECT * FROM users FULL OUTER JOIN orders ON users.id = orders.user_id
CROSS JOIN / 逗号连接笛卡尔积,行数是两表行数相乘;只在「所有组合都合法」的小表上用SELECT * FROM sizes CROSS JOIN colors
自连接 e LEFT JOIN employees m同一张表连接自身,是层级关系(员工-经理、分类-父分类)的标准写法SELECT e.name, m.name AS manager FROM employees e LEFT JOIN employees m ON e.manager_id = m.id
多表链式 JOIN三张以上表要逐段写清 ON 条件,漏一段就会产生笛卡尔积SELECT u.name, o.total, p.amount FROM users u JOIN orders o ON u.id = o.user_id JOIN payments p ON o.id = p.order_id
COUNT(o.id) AS order_count统计时数的是具体列而不是 *,这样无匹配的 LEFT JOIN 行才会正确计为 0SELECT u.name, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id
GROUP BY ... HAVING SUM(o.total) > 1000HAVING 过滤聚合结果,WHERE 过滤聚合前的行;两者顺序不能互换SELECT u.name, SUM(o.total) AS total_spent FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING SUM(o.total) > 1000
SELECT DATE(o.created_at) AS d, COUNT(*)按函数结果分组做时间序列统计;注意部分数据库要求函数与列同组SELECT DATE(o.created_at) AS d, COUNT(*) FROM orders o JOIN users u ON u.id = o.user_id GROUP BY DATE(o.created_at)
EXPLAIN SELECT ...查看执行计划里的驱动表、连接类型与索引命中情况,是排 JOIN 慢查询的第一步EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id
EXPLAIN ANALYZE SELECT ...真正执行并给出每步实际行数与耗时,能暴露「估算行数与实际差几个数量级」的统计信息问题EXPLAIN ANALYZE SELECT * FROM users u JOIN orders o ON u.id = o.user_id
CREATE INDEX idx_orders_user_id ON orders(user_id)为连接键建索引;没有索引的连接在大表上必然全表扫描CREATE INDEX idx_orders_user_id ON orders(user_id)
STRAIGHT_JOIN强制按 FROM 的书写顺序连接,作为优化器选错驱动表时的临时手段SELECT * FROM users u STRAIGHT_JOIN orders o ON u.id = o.user_id
FORCE INDEX (idx_user_id)强制指定某表用哪个索引,比 STRAIGHT_JOIN 更精确,但会把索引固化在查询里SELECT * FROM users u JOIN orders o FORCE INDEX(idx_user_id) ON u.id = o.user_id
JOIN ... USING (user_id)按同名列连接,且该列在结果里只出现一次;适合规范一致的宽表SELECT * FROM users JOIN orders USING (user_id)
NATURAL JOIN隐式按所有同名列连接;表一改列名语义就静默改变,极不推荐SELECT * FROM users NATURAL JOIN orders
ON u.id = o.user_id AND u.tenant_id = o.tenant_id多租户场景必须把租户列写进 ON,否则会跨租户串数据SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id AND u.tenant_id = o.tenant_id
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)相关子查询的存在性判断,找到即停;比 IN 更能正确处理含 NULL 的列SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)
WHERE NOT EXISTS (...)反连接的存在性判断,通常比 LEFT JOIN + IS NULL 执行计划更干净SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)
user_id IN (SELECT id FROM users WHERE status = 'active')半连接写法;子查询结果含 NULL 时 NOT IN 会整体返回空集SELECT * FROM orders o WHERE o.user_id IN (SELECT id FROM users WHERE status = 'active')
SELECT (SELECT COUNT(*) ...) AS cnt标量子查询作投影,对每一行执行一次;行数多时明显变慢,应改用 JOIN + GROUP BYSELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS cnt FROM users u
WHERE user_id = (SELECT MAX(user_id) FROM orders)取极值行的经典写法,比排序取第一行更直观SELECT * FROM orders WHERE user_id = (SELECT MAX(user_id) FROM orders)

Common pitfalls

Symptom本该显示「无订单的用户」,结果这些用户整行都没了。

Cause写的是 LEFT JOIN 却把右表条件放在了 WHERE:WHERE orders.status = 'paid' 在补 NULL 后不成立,等于把 LEFT JOIN 降级成 INNER JOIN。

Fix把右表条件移进 ON 子句里;若确实是只要已支付订单,就明确改用 INNER JOIN。

Symptom统计订单数时「没有订单的用户」被算成了 1。

Cause聚合用了 COUNT(*) 或 COUNT(o.*),无匹配时补出来的一行 NULL 也被计入。

Fix改成 COUNT(o.id) 这类指定可空列的写法,无匹配时 COUNT 忽略 NULL 并返回 0。

Symptom行数暴涨,多表连接后结果比预期大几个数量级。

Cause三表链式连接时某一段 ON 条件写漏或写错,导致中间产生笛卡尔积。

Fix逐段验证:先只 join 两张表看行数,再加第三张;EXPLAIN 里确认每段的驱动表与 rows 估算。

SymptomJOIN 突然变慢,数据量没涨但耗时翻倍。

Cause连接键缺索引,或统计信息陈旧导致优化器选错驱动表。

FixEXPLAIN 看是否全表扫描,给连接键建索引;必要时用 EXPLAIN ANALYZE 对比估算与实际行数。

Symptom同一份 SQL 在不同数据库(MySQL / PostgreSQL)行为不一致。

CauseFULL OUTER JOIN 与 USING 支持程度不同,字符串拼接与 GROUP BY 规则(是否要求所有非聚合列都在组内)也不一致。

Fix按目标数据库写等价实现;跨库报表尽量收敛到 JOIN + 显式列名 + 严格 GROUP BY。

SymptomNATURAL JOIN 后结果少了一列,语义突然变了。

CauseNATURAL JOIN 隐式按所有同名列连接,任一侧新增同名列都会静默改变连接条件。

Fix改用显式 JOIN ... ON,把连接列写死,避免 schema 变更带来隐性 bug。

Symptom多租户库里查出了别的租户的数据。

Cause连接条件只写了主键相等,忘了 tenant_id;或者过滤租户的 WHERE 只加在了一侧。

Fix把 tenant_id 写进 ON 子句,并在两侧都用 tenant_id 过滤,必要时对租户列建复合索引。

SymptomWHERE user_id NOT IN (SELECT ...) 返回空结果。

Cause子查询结果里含 NULL,NOT IN 遇到 NULL 会整体判定为未知,从而一行都不返回。

Fix改用 NOT EXISTS,或先在子查询里过滤掉 NULL。

Troubleshooting

  1. 1看执行计划与连接类型

    EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id

    重点看每个表的 type、key 与 rows。

  2. 2对比估算与实际行数

    EXPLAIN ANALYZE SELECT * FROM users u JOIN orders o ON u.id = o.user_id

    两者差几个数量级说明统计信息需要更新。

  3. 3确认连接键是否命中索引

    grep -n "CREATE INDEX" schema.sql

    索引缺失是最常见的 JOIN 性能根因。

  4. 4验证多租户过滤是否成对出现

    grep -n "tenant_id" queries.sql

    每个涉及多租户表的查询都应两侧过滤。

  5. 5检查是否误用 NATURAL JOIN

    grep -n "NATURAL JOIN" queries.sql

    生产查询里出现它基本都需要改成显式 ON。

  6. 6排查是否把右表条件写进了 WHERE

    grep -n "LEFT JOIN" -A 3 queries.sql

    紧随其后的 WHERE 若引用右表列,就是降级成内连接的信号。

Command Examples

Anti-join: find users with no orders at all

SELECT u.name, o.total FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid' WHERE o.id IS NULL

Putting the condition in ON rather than WHERE is what makes this work: an ON clause only decides whether the right row matches, it never filters rows out of the left table. Users with zero orders therefore survive the join, and WHERE o.id IS NULL picks them out. Move o.status into WHERE and every user without a paid order gets mislabelled as having no orders.

Zero-order users: LEFT JOIN plus aggregation plus HAVING

SELECT u.name, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING COUNT(o.id) = 0

COUNT(o.id), never COUNT(*): with no matching order every o.id is NULL so COUNT(o.id) is 0, while COUNT(*) still sees the surviving left row and returns 1, so the condition never fires. Whenever you need "how many" rather than "does it exist", count a nullable column from the outer-joined side.

Self-join to rebuild a hierarchy from a flat table

SELECT t1.name AS grandchild, t3.name AS grandparent FROM tree t1 JOIN tree t2 ON t1.parent_id = t2.id JOIN tree t3 ON t2.parent_id = t3.id

Three aliases of the same tree table joined in sequence walk parent_id up two generations. A NULL grandparent does not mean the query is wrong: the root simply has no parent. If you need to tell roots from broken links, switch to a LEFT JOIN and test t3.name IS NULL.

Compare the execution plan before and after indexing the foreign key

EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id CREATE INDEX idx_orders_user_id ON orders(user_id) EXPLAIN ANALYZE SELECT * FROM users u JOIN orders o ON u.id = o.user_id

A Nested Loop plan with a large row estimate means orders.user_id is not indexed, so the database rescans the whole orders table once per user row. On a large orders table that is a disaster. Re-run EXPLAIN ANALYZE after creating the index and confirm the actual scanned row count dropped before calling it done.

Output

Nested Loop  (cost=0.00..45231.44 rows=1500)

Use EXISTS instead of IN for a large subquery

SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id) SELECT * FROM orders o WHERE o.user_id IN (SELECT id FROM users WHERE status = 'active')

The semantics differ in an important way: EXISTS and NOT EXISTS short-circuit at the first match, while IN generally has to materialize the whole subquery result. With a users table of hundreds of thousands of rows, the intermediate IN result can exceed the main query; switching to EXISTS usually turns a Materialize into a Semi Join.

Common Pitfalls

  • Omitting ON (or writing ON 1=1, or mistyping a.id=b.id as a.id=a.id) yields a cartesian product: two 50k-row tables return 2.5 billion rows, filling the disk and exhausting the connection pool. In production this usually shows up as "the API got slow", not as a SQL error, which makes it very hard to trace. Before running, check that the number of ON predicates equals the number of join keys.
  • NULL never compares equal in an inner join: ON a.user_id = o.user_id treats NULL as non-matching, not as matching. Any nullable join column silently drops rows. When a join column can be NULL, wrap it in NULLIF or use IS NOT DISTINCT FROM semantics, and add o.user_id IS NOT NULL to the predicate.
  • SELECT DISTINCT removes duplicate rows but does nothing about their source. A LEFT JOIN against a detail table (orders, items) legitimately multiplies rows, and DISTINCT then throws away the amount and quantity along with the duplicates: a user with three 100-total orders collapses to a single row of 100. Aggregate with GROUP BY on the primary key instead of masking it with DISTINCT.
  • Moving a right-table filter into the WHERE clause of a LEFT JOIN silently degrades it to an INNER JOIN: the very rows the LEFT JOIN was meant to preserve (users with no orders) are removed by WHERE o.total > 100. To keep every left row, right-table conditions belong in ON.
  • SELECT * in a JOIN yields duplicate column names for identically named columns (id, created_at from both sides), and positional or name-based access can silently read the wrong table. Worse, count(o.id) stays correct so the bug survives for years. Select only the columns you need and alias them explicitly.
  • Forgetting an index on the join key forces a full table scan, and the rows estimate in EXPLAIN reveals it. Before adding an index for users JOIN orders ON u.id = o.user_id, check whether orders.user_id is already indexed; a redundant index only slows writes.

Tips

  • A LEFT JOIN WHERE condition in ON only affects right-table matching; in WHERE it filters the whole result set.
  • CROSS JOIN produces a Cartesian product — use cautiously on large data.
  • Table aliases (u, o) make multi-table queries more concise and readable.
  • EXISTS subqueries can outperform JOIN in some cases, especially when only existence matters.
  • Index the JOIN columns, otherwise a full table scan hurts performance.
  • USING(a, b) is cleaner than ON x.a = y.a AND x.b = y.b, and merges same-named columns.

FAQ

How do I choose between INNER JOIN and LEFT JOIN?

Decide with one question: do you need to keep left-table rows that have no match on the right? If a record only counts when both sides exist, use INNER — SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id, where JOIN defaults to INNER. If every left row must survive even without orders, use LEFT JOIN and expect NULLs on the right. The classic trap is filter placement: LEFT JOIN ... WHERE o.status = 'paid' drops the orderless users because NULL never compares true, while putting the condition in ON — ON u.id = o.user_id AND o.status = 'paid' — only affects matching and preserves the left row count.

My JOIN returns far more rows than expected — why?

Almost always one-to-many fan-out. Joining users to orders emits one row per order, so a user with three orders becomes three rows; adding a payments join multiplies it again. Remedies: select only the columns you actually need instead of SELECT *; collapse to one row per group with an alias plus aggregation such as SELECT u.name, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id; or skip the JOIN entirely when you only test existence and use WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id), which can even be faster in some cases.

How do I find rows with no match, such as users who never ordered?

Use LEFT JOIN with an IS NULL test: SELECT * FROM users LEFT JOIN orders ON users.id = orders.user_id WHERE orders.id IS NULL. Matched rows have a non-null id on the right and get filtered away, leaving exactly the orderless users. HAVING gives the same answer: SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id HAVING COUNT(o.id) = 0. Or skip the JOIN entirely with SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id). When you need rows exclusive to either side, reach for FULL OUTER JOIN with WHERE u.id IS NULL OR o.user_id IS NULL to get the symmetric difference.

Between USING, NATURAL JOIN and ON, which should I write for a multi-table query?

ON is the general and clearest form, and it is mandatory when the columns have different names: ON u.id = o.user_id AND u.tenant_id = o.tenant_id. When the join columns share a name, USING is shorter: SELECT * FROM users JOIN orders USING (user_id), and multiple columns work too as USING (user_id, tenant_id) — the result set merges identically named columns into one field, removing the u. versus o. ambiguity. NATURAL JOIN matches on every shared column name automatically, so a schema change breaks it silently; the sheet flags it as risky and recommends reserving it for throwaway queries.

A JOIN query suddenly got slow — what do I check first?

Start with the plan: EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id shows the join method, and EXPLAIN ANALYZE (PostgreSQL / MySQL 8.0+) actually runs it and reports timings. The usual culprit is a missing index on the join column — add one with CREATE INDEX idx_orders_user_id ON orders(user_id) to avoid full scans. Next, replace SELECT * with just the columns you need to reduce table lookups. When MySQL picks the wrong driving table, force the order with STRAIGHT_JOIN or pin the index with FORCE INDEX(idx_user_id). Remember that filtering after aggregation (COUNT, SUM, AVG) belongs in HAVING, not WHERE.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us