SQL 60 🛢️ Aggregate Functions over Window Frames
The aggregate functions—SUM, AVG, MIN, MAX, COUNT—have two lives in SQL. The first is the GROUP BY form: the rows are collapsed, and one row per group is returned. The second is the window form: the OVER clause keeps all the rows, and the aggregate is attached to each one. This chapter is about the second form, and specifically about the frame clause that controls which rows the aggregate sees. The frame is the difference between a running total and a partition total, and the understanding of the frame is the difference between a correct report and a subtle bug.
Key point: The aggregate function with OVER computes the aggregate over the window frame. The frame is defined by the ROWS or the RANGE clause between two boundaries. The default frame with an ORDER BY is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which produces a running aggregate. Without the ORDER BY, the default frame is the whole partition. The explicit frame is the control: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW for the running total, ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING for the partition total, and the bounded frames for the moving windows.
Why the aggregate frames matter
The running total problem. The cumulative sum “add each row to the total so far” requires the frame to include the rows from the start of the partition to the current row. The default frame with the ORDER BY is exactly that, and the SUM(amount) OVER (ORDER BY order_date) produces the running total.
The partition total problem. The “each row and the partition’s total” requires the frame to include the whole partition. The default frame stops at the current row, and the explicit ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING is the requirement.
The moving window problem. The “7-day moving average” requires the frame to include the rows within the 7 days of the current row. The RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW is the frame. The ROWS and the RANGE differ: the ROWS counts the physical rows, and the RANGE uses the ORDER BY value.
The percentage problem. The “each row’s share of the partition” requires the row’s value and the partition’s total. The value / SUM(value) OVER (PARTITION BY ...) is the ratio, and the SUM needs the full frame.
The group aggregate problem. The “each row and the department’s average” requires the AVG over the partition. The AVG(salary) OVER (PARTITION BY department) with the default frame is the whole partition because there is no ORDER BY. The ORDER BY changes the frame to the running average.
The frame boundary problem. The frame has two boundaries: the start and the end. Each boundary is one of the UNBOUNDED PRECEDING, the N PRECEDING, the CURRENT ROW, the N FOLLOWING, and the UNBOUNDED FOLLOWING. The combinations define the frame.
a. The default frame and the running total
The default frame depends on the presence of the ORDER BY.
-- No ORDER BY: the whole partition
SELECT
department,
salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg
FROM employees;
The dept_avg is the department’s average, shown on every row. The frame is the whole partition because there is no ORDER BY.
-- With ORDER BY: the running aggregate
SELECT
order_date,
amount,
SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders;
The running_total is the cumulative sum from the first row to the current. The default frame with the ORDER BY is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW.
The explicit running total.
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM orders;
The ROWS version is the same result for the distinct dates. The ROWS and the RANGE differ when the ORDER BY values have the duplicates.
The RANGE and the ROWS difference.
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS range_sum,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS rows_sum
FROM orders;
The range_sum includes all the rows with the order_date equal to the current, and the rows_sum includes only the rows up to the current physical row. The two differ when the dates tie.
b. The partition total and the moving window
The partition total requires the full frame.
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS total
FROM orders;
The total is the same on every row: the sum of the whole partition. The ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING is the full frame.
The percentage of the total.
SELECT
order_date,
amount,
100.0 * amount / SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS pct
FROM orders;
The pct is each order’s share of the total. The SUM with the full frame is the denominator.
The moving average.
SELECT
order_date,
amount,
AVG(amount) OVER (
ORDER BY order_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3
FROM orders;
The moving_avg_3 is the average of the current row and the two before. The frame is the three-row window.
The moving window with the RANGE and the interval.
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW
) AS rolling_7_day
FROM orders;
The rolling_7_day is the sum of the amounts within the 7 days of the current row. The RANGE with the interval is the date-based window.
The bounded frame with the FOLLOWING.
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS three_row_sum
FROM orders;
The three_row_sum is the sum of the current row and the one before and after. The centered window is the tool for the local smoothing.
The unbounded following.
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) AS remaining
FROM orders;
The remaining is the sum from the current row to the end of the partition. The pattern is for the countdown and the remaining budget.
c. The frame syntax and the comparison
The frame syntax.
function() OVER (
PARTITION BY ...
ORDER BY ...
{ROWS | RANGE} BETWEEN <start> AND <end>
)
The boundaries.
| Boundary | Meaning |
|---|---|
UNBOUNDED PRECEDING | The partition start |
N PRECEDING | N rows/values before |
CURRENT ROW | The current row |
N FOLLOWING | N rows/values after |
UNBOUNDED FOLLOWING | The partition end |
The ROWS and the RANGE difference.
| Aspect | ROWS | RANGE |
|---|---|---|
| The unit | The physical rows | The ORDER BY values |
| The ties | The separate rows | The tied rows together |
| The interval | Not allowed | Allowed for the date/number |
| The default | — | The default when the ORDER BY |
The common frames.
| Frame | Result |
|---|---|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW | The running total |
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING | The partition total |
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW | The 3-row moving window |
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING | The centered 3-row window |
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING | The remaining |
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW | The rolling 7 days |
The aggregate with the frame.
SELECT
department,
employee_id,
salary,
SUM(salary) OVER (
PARTITION BY department
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_dept_total
FROM employees;
The running_dept_total is the cumulative salary within the department, ordered by the salary descending. The pattern is for the “how much of the department’s total is above this salary.”
Complete Example Session
-- ============================================
-- PART 1: SAMPLE DATA
-- ============================================
CREATE TABLE orders (
order_id INT,
order_date DATE,
amount DECIMAL(10,2)
);
INSERT INTO orders VALUES
(1, '2026-01-01', 100),
(2, '2026-01-02', 200),
(3, '2026-01-03', 150),
(4, '2026-01-04', 300),
(5, '2026-01-05', 250);
-- ============================================
-- PART 2: DEFAULT FRAME (RUNNING TOTAL)
-- ============================================
SELECT
order_date,
amount,
SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders;
-- ============================================
-- PART 3: EXPLICIT RUNNING TOTAL
-- ============================================
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total
FROM orders;
-- ============================================
-- PART 4: PARTITION TOTAL
-- ============================================
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS total
FROM orders;
-- ============================================
-- PART 5: PERCENTAGE OF TOTAL
-- ============================================
SELECT
order_date,
amount,
ROUND(100.0 * amount / SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
), 2) AS pct
FROM orders;
-- ============================================
-- PART 6: MOVING AVERAGE (3 ROWS)
-- ============================================
SELECT
order_date,
amount,
AVG(amount) OVER (
ORDER BY order_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3
FROM orders;
-- ============================================
-- PART 7: CENTERED WINDOW
-- ============================================
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS centered_sum
FROM orders;
-- ============================================
-- PART 8: REMAINING
-- ============================================
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) AS remaining
FROM orders;
-- ============================================
-- PART 9: ROLLING 7 DAYS
-- ============================================
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW
) AS rolling_7_day
FROM orders;
-- ============================================
-- PART 10: THE RANGE VS ROWS
-- ============================================
SELECT
order_date,
amount,
SUM(amount) OVER (
ORDER BY order_date
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS range_sum,
SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS rows_sum
FROM orders;
The ten parts covered the sample data, the default frame, the explicit running total, the partition total, the percentage of the total, the moving average, the centered window, the remaining, the rolling 7 days, and the RANGE versus the ROWS.
Quick Reference
The Frame Syntax
| Part | Options |
|---|---|
| The unit | ROWS, RANGE |
| The start | UNBOUNDED PRECEDING, N PRECEDING, CURRENT ROW |
| The end | CURRENT ROW, N FOLLOWING, UNBOUNDED FOLLOWING |
The Default Frame
| Condition | The Frame |
|---|---|
No ORDER BY | The whole partition |
With ORDER BY | RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
The Common Frames
| Frame | Result |
|---|---|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW | The running total |
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING | The partition total |
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW | The 3-row window |
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING | The centered 3-row |
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING | The remaining |
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW | The rolling 7 days |
The ROWS vs the RANGE
| Aspect | ROWS | RANGE |
|---|---|---|
| The unit | The rows | The values |
| The ties | The separate | The together |
| The interval | No | Yes |
| The use | The row count | The value range |
Best Practices
✅ Do This:
-- Use the explicit frame for the running total
SUM(amount) OVER (
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) -- ✅
-- Use the full frame for the partition total
SUM(amount) OVER (
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) -- ✅
-- Use the RANGE for the date-based window
SUM(amount) OVER (
ORDER BY date
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW
) -- ✅
-- Use the ROWS for the row-count window
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) -- ✅
-- Use the partition for the per-group aggregate
AVG(salary) OVER (PARTITION BY department) -- ✅
❌ Don’t Do This:
-- Don't assume the default frame is the whole partition
SUM(amount) OVER (ORDER BY date) -- the running total, not the total -- ⚠️
-- Don't use the RANGE for the row-count window
RANGE BETWEEN 2 PRECEDING AND CURRENT ROW -- the value-based -- ⚠️
-- Don't use the ROWS for the date interval
ROWS BETWEEN INTERVAL '6 days' PRECEDING -- error -- ❌
-- Don't forget the ORDER BY for the running total
SUM(amount) OVER () -- the whole partition, not the running -- ⚠️
-- Don't use the frame clause without the ORDER BY
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW -- the error in some DBs -- ⚠️
-- Don't use the window aggregate in the WHERE
WHERE SUM(amount) OVER (...) > 100 -- error -- ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The running total instead of the total | The default frame with the ORDER BY | Add the full frame |
| The total instead of the running | No ORDER BY | Add the ORDER BY |
The RANGE and the ROWS differ | The ties | Choose deliberately |
The interval in the ROWS | Not allowed | Use the RANGE |
The window aggregate in the WHERE | Not allowed | Wrap in the subquery |
| The moving window off-by-one | The boundary count | The N PRECEDING includes the current |
Real-World Examples
1. The Running Total
SELECT order_date, amount,
SUM(amount) OVER (ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running
FROM orders;
2. The Partition Total
SELECT order_date, amount,
SUM(amount) OVER (ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS total
FROM orders;
3. The Percentage
SELECT order_date, amount,
ROUND(100.0 * amount / SUM(amount) OVER (
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING), 2) AS pct
FROM orders;
4. The Moving Average
SELECT order_date, amount,
AVG(amount) OVER (ORDER BY order_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS avg3
FROM orders;
5. The Centered Window
SELECT order_date, amount,
SUM(amount) OVER (ORDER BY order_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS centered
FROM orders;
6. The Rolling 7 Days
SELECT order_date, amount,
SUM(amount) OVER (ORDER BY order_date
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW) AS rolling
FROM orders;
7. The Remaining Budget
SELECT order_date, amount,
SUM(amount) OVER (ORDER BY order_date
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS remaining
FROM orders;
8. The Department Average
SELECT department, employee_id, salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg
FROM employees;
9. The Running Department Total
SELECT department, employee_id, salary,
SUM(salary) OVER (PARTITION BY department ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running
FROM employees;
10. The Rank and the Running
SELECT employee_id, salary,
RANK() OVER (ORDER BY salary DESC) AS rank,
SUM(salary) OVER (ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running
FROM employees;
Visual
The Running Total
┌─────────────────────────────────────────────────────────────┐
│ SUM(amount) OVER (ORDER BY order_date │
│ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) │
│ │
│ ┌────────────┬────────┬───────────────┐ │
│ │ order_date │ amount │ running_total │ │
│ ├────────────┼────────┼───────────────┤ │
│ │ 2026-01-01 │ 100 │ 100 │ │
│ │ 2026-01-02 │ 200 │ 300 │ │
│ │ 2026-01-03 │ 150 │ 450 │ │
│ │ 2026-01-04 │ 300 │ 750 │ │
│ │ 2026-01-05 │ 250 │ 1000 │ │
│ └────────────┴────────┴───────────────┘ │
│ │
│ The frame grows by one row each time. │
│ │
└─────────────────────────────────────────────────────────────┘
The Partition Total
┌─────────────────────────────────────────────────────────────┐
│ SUM(amount) OVER (ORDER BY order_date │
│ ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)│
│ │
│ ┌────────────┬────────┬───────┐ │
│ │ order_date │ amount │ total │ │
│ ├────────────┼────────┼───────┤ │
│ │ 2026-01-01 │ 100 │ 1000 │ │
│ │ 2026-01-02 │ 200 │ 1000 │ │
│ │ 2026-01-03 │ 150 │ 1000 │ │
│ │ 2026-01-04 │ 300 │ 1000 │ │
│ │ 2026-01-05 │ 250 │ 1000 │ │
│ └────────────┴────────┴───────┘ │
│ │
│ The frame is the whole partition on every row. │
│ │
└─────────────────────────────────────────────────────────────┘
The Moving Window
┌─────────────────────────────────────────────────────────────┐
│ AVG(amount) OVER (ORDER BY order_date │
│ ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) │
│ │
│ ┌────────────┬────────┬──────────────┐ │
│ │ order_date │ amount │ moving_avg_3 │ │
│ ├────────────┼────────┼──────────────┤ │
│ │ 2026-01-01 │ 100 │ 100 │ ← 100 │
│ │ 2026-01-02 │ 200 │ 150 │ ← (100+200)/2 │
│ │ 2026-01-03 │ 150 │ 150 │ ← (100+200+150)/3 │
│ │ 2026-01-04 │ 300 │ 217 │ ← (200+150+300)/3 │
│ │ 2026-01-05 │ 250 │ 233 │ ← (150+300+250)/3 │
│ └────────────┴────────┴──────────────┘ │
│ │
│ The frame slides: the 2 PRECEDING drops off. │
│ │
└─────────────────────────────────────────────────────────────┘
The ROWS vs the RANGE
┌─────────────────────────────────────────────────────────────┐
│ ORDER BY order_date │
│ Dates: 01-01, 01-01, 01-02 │
│ Amounts: 100, 200, 300 │
│ │
│ RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW │
│ ┌────────────┬────────┬───────────┐ │
│ │ order_date │ amount │ range_sum │ │
│ ├────────────┼────────┼───────────┤ │
│ │ 01-01 │ 100 │ 300 │ ← both 01-01 rows │
│ │ 01-01 │ 200 │ 300 │ ← the tied rows │
│ │ 01-02 │ 300 │ 600 │ │
│ └────────────┴────────┴───────────┘ │
│ │
│ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW │
│ ┌────────────┬────────┬───────────┐ │
│ │ order_date │ amount │ rows_sum │ │
│ ├────────────┼────────┼───────────┤ │
│ │ 01-01 │ 100 │ 100 │ ← the current row │
│ │ 01-01 │ 200 │ 300 │ ← the current + prev │
│ │ 01-02 │ 300 │ 600 │ │
│ └────────────┴────────┴───────────┘ │
│ │
│ The RANGE groups the ties, the ROWS counts them. │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| The aggregate | SUM, AVG, MIN, MAX, COUNT |
| The window form | function() OVER (...) |
The default (no ORDER BY) | The whole partition |
The default (with ORDER BY) | RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
| The running total | ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
| The partition total | ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING |
| The moving window | ROWS BETWEEN N PRECEDING AND CURRENT ROW |
| The centered window | ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING |
| The remaining | ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING |
| The date window | RANGE BETWEEN INTERVAL 'N days' PRECEDING AND CURRENT ROW |
Key takeaways:
- The aggregate with the
OVERclause keeps all the rows. TheGROUP BYcollapses the rows, and the window form attaches the aggregate to each row. The difference is the output: one row per group versus one row per input. - The frame determines which rows the aggregate sees. The
ROWSand theRANGEclauses define the frame between the two boundaries. The default frame depends on the presence of theORDER BY. - The default frame with the
ORDER BYis the running aggregate. TheRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWproduces the cumulative sum. The explicit frame is the control when the other result is needed. - The partition total requires the full frame. The
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGis the frame that includes the whole partition. The percentage of the total is the ratio of the row’s value to the partition total. - The moving window is the bounded frame. The
ROWS BETWEEN 2 PRECEDING AND CURRENT ROWis the 3-row window. TheROWS BETWEEN 1 PRECEDING AND 1 FOLLOWINGis the centered window. The pattern is for the smoothing and the trend analysis. - The
ROWSand theRANGEdiffer on the ties. TheROWScounts the physical rows, and theRANGEgroups the tied values. TheRANGEwith the interval is the date-based window. The choice depends on the data and the intent. - The aggregate frame and the ranking functions are the same family. The
SUMover the frame, theRANKover the partition, and theLAGover the order are the window functions. TheOVERclause is the shared mechanism, and the frame is the detail that makes the aggregate specific.
Remember: The aggregate functions over the window frames are the tools for the running totals, the partition totals, the moving averages, and the percentages. The frame is the crucial detail: the ROWS and the RANGE, the boundaries, and the default. The ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW is the running total, and the ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING is the partition total. The explicit frame is the control, and the default frame is the trap. The aggregate functions are the same as the GROUP BY ones, and the window form is the difference.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!