How to Find the Lowest Salary in SQL Server

A payroll dashboard shows the lowest salary as zero, but HR says no active employee earns that amount. The query runs successfully. The problem is that it mixes placeholder values, inactive employees, and legitimate salaries without defining which records belong in the report.

Knowing how to find the lowest salary in SQL Server starts with MIN(), but reliable reporting also requires clear rules for missing values, duplicate salaries, and employee eligibility.

The following examples show how to retrieve the minimum salary, return matching employees, calculate department minimums, and test performance and access controls.

How to Find the Lowest Salary in SQL Server With MIN()

Use this query when you need only the lowest salary amount:

SELECT
MIN(Salary) AS LowestSalary
FROM dbo.Employees;

MIN() is an aggregate function: it calculates one summary value from a set of rows. Here, it returns the smallest non-null salary in dbo.Employees. It ignores NULL, which represents an unknown or missing value.

For example, salaries of 92000.00, 68000.00, 54000.00, and NULL produce:

LowestSalary
54000.00

Duplicates do not change the minimum. If five employees earn 54000.00, this query still returns one row containing that amount. Adding DISTINCT is unnecessary.

An ungrouped MIN() query also returns one row when no records qualify, but its value is NULL. That differs from a detail query, which can return no rows.

The syntax works in SQL Server, Azure SQL Database, and Azure SQL Managed Instance. This task does not require a different deployment model, special service tier, or extra infrastructure.

Pro Tip: I separate the requirement for “one salary amount” from “the employees earning that amount.” Those are different result shapes, and applications need to handle them differently.

Create a Safe Sample Payroll Table

Use a development database for this setup. The script creates a table and inserts fictional records; it does not modify existing employee data.

If dbo.Employees already exists, use a different demonstration name and adjust the queries. Do not remove an existing payroll table just to run a tutorial.

CREATE TABLE dbo.Employees
(
EmployeeID int NOT NULL
CONSTRAINT PK_Employees PRIMARY KEY,
EmployeeName nvarchar(100) NOT NULL,
DepartmentID int NOT NULL,
Salary decimal(12, 2) NULL,
IsActive bit NOT NULL,
CONSTRAINT CK_Employees_Salary
CHECK (Salary >= 0)
);

INSERT INTO dbo.Employees
(EmployeeID, EmployeeName, DepartmentID, Salary, IsActive)
VALUES
(1, N'Emma Carter', 10, 92000.00, 1),
(2, N'Noah Williams', 10, 68000.00, 1),
(3, N'Olivia Davis', 20, 54000.00, 1),
(4, N'Liam Johnson', 20, 54000.00, 1),
(5, N'Ava Martinez', 30, 61000.00, 1),
(6, N'James Wilson', 30, NULL, 1),
(7, N'Sophia Brown', 10, 42000.00, 0);

After running the query above, I got the expected output shown in the screenshot below.

How to Find the Lowest Salary in SQL
Find the Lowest Salary in SQL

A schema is a namespace for database objects; this example uses dbo. The primary key uniquely identifies each employee. In a complete model, DepartmentID would normally reference a department table through a foreign key.

decimal(12, 2) stores exact amounts with two decimal places. Avoid approximate types such as float for compensation amounts. IsActive is a bit flag indicating whether an employee belongs in the active workforce.

The check constraint rejects negative salaries but permits zero and NULL. That distinction is intentional: preventing negative values does not establish whether zero is valid.

Running the first query returns 42000.00, because it includes inactive Sophia. The lowest active salary is 54000.00.

Review SQL constraints when translating payroll rules into database validation.

Pro Tip: I include an inactive employee, duplicate minimums, and a missing salary in sample data. Unique, perfectly clean values make weak queries look correct.

How to Find the Lowest Salary in SQL Server for Active Employees

Apply eligibility rules before calculating the minimum:

SELECT
MIN(Salary) AS LowestActiveSalary
FROM dbo.Employees
WHERE IsActive = 1;

After running the query above, I got the expected output shown in the screenshot below.

How to Find the Lowest Salary in SQL Server

WHERE restricts the input rows, so the result is 54000.00.

If zero means “not loaded” in a legacy system, the reporting requirement might explicitly exclude it:

SELECT
MIN(Salary) AS LowestActiveSalary
FROM dbo.Employees
WHERE IsActive = 1
AND Salary > 0;

After running the query above, I got the expected output shown in the screenshot below.

How to Find Lowest Salary in SQL Server

This is a business decision, not a universal SQL Server best practice. Excluding zero can hide a data-quality problem, and some legitimate records may have zero base salary.

Do not replace missing salaries with zero before calculating the minimum. MIN(COALESCE(Salary, 0)) can make an unknown amount appear to be the lowest salary.

Also define comparable units. Annual salaries, hourly rates, different currencies, and full-time-equivalent compensation cannot be ranked meaningfully without normalization. For historical reporting, select the applicable effective-date records first rather than mixing current and previous salaries.

The SQL WHERE clause tutorial explains how filters determine which rows reach an aggregate.

Pro Tip: I document the salary basis beside the report definition. A technically correct minimum still misleads people when the input mixes annual pay and hourly rates.

Return Every Employee Earning the Lowest Salary

Use a subquery when the report needs names and employee identifiers:

SELECT
e.EmployeeID,
e.EmployeeName,
e.DepartmentID,
e.Salary
FROM dbo.Employees AS e
WHERE e.IsActive = 1
AND e.Salary =
(
SELECT MIN(e2.Salary)
FROM dbo.Employees AS e2
WHERE e2.IsActive = 1
)
ORDER BY e.EmployeeID;

After running the query above, I got the expected output shown in the screenshot below.

How to Find Lowest Salary in SQL

The subquery calculates the lowest active salary. The outer query returns every active employee earning it.

The result contains Olivia and Liam, both at 54000.00. Filtering both levels keeps the population consistent. Without the outer active filter, an inactive employee with the same salary could appear.

A common mistake is selecting EmployeeName beside MIN(Salary) without a suitable grouping strategy. SQL Server cannot infer which employee name should accompany the aggregate, especially when several employees tie.

For a compact alternative:

SELECT TOP (1) WITH TIES
EmployeeID,
EmployeeName,
DepartmentID,
Salary
FROM dbo.Employees
WHERE IsActive = 1
AND Salary IS NOT NULL
ORDER BY Salary ASC;

After running the query above, I got the expected output shown in the screenshot below.

Find Lowest Salary SQL Server

WITH TIES includes every row matching the first salary. Do not add EmployeeID to this ordering: it would make the ordering combinations unique and prevent salary ties from being retained.

If the requirement is exactly one employee, remove WITH TIES and order by Salary, EmployeeID. That produces a repeatable selection, not every lowest-paid employee.

See SQL subqueries for the relationship between inner calculations and outer results.

Pro Tip: I ask whether ties should produce multiple rows before connecting the query to an API. A response designed for one employee can break when payroll introduces a duplicate minimum.

Find the Lowest Salary in Each Department

Use GROUP BY to calculate a separate minimum for each department:

SELECT
DepartmentID,
MIN(Salary) AS LowestSalary,
COUNT(*) AS ActiveEmployees,
COUNT(Salary) AS EmployeesWithKnownSalary
FROM dbo.Employees
WHERE IsActive = 1
GROUP BY DepartmentID
ORDER BY DepartmentID;

After running the query above, I got the expected output shown in the screenshot below.

Find Lowest Salary SQL

GROUP BY forms one group per department. COUNT(*) counts active employee rows, while COUNT(Salary) counts only non-null salary values.

The minimums are 68000.00 for department 10, 54000.00 for department 20, and 61000.00 for department 30. The two counts also reveal that department 30 has an employee with missing compensation.

To return employees rather than department summaries, use a window aggregate:

WITH DepartmentPay AS
(
SELECT
EmployeeID,
EmployeeName,
DepartmentID,
Salary,
MIN(Salary) OVER (
PARTITION BY DepartmentID
) AS DepartmentMinimum
FROM dbo.Employees
WHERE IsActive = 1
)
SELECT
EmployeeID,
EmployeeName,
DepartmentID,
Salary
FROM DepartmentPay
WHERE Salary = DepartmentMinimum
ORDER BY DepartmentID, EmployeeID;

After running the query above, I got the expected output shown in the screenshot below.

Lowest Salary in SQL Server

A common table expression, or CTE, names the intermediate query. PARTITION BY divides the calculation by department without collapsing individual employee rows.

A department containing only unknown salaries has a null minimum and produces no matching employee rows in the second query. An empty department cannot appear unless the query starts from a department table.

Learn more about GROUP BY in SQL Server when building summary reports.

Pro Tip: I include known-salary counts in payroll validation reports. A minimum based on one known amount out of 200 employees deserves attention.

Test NULLs, Empty Results, and Common Query Mistakes

Test edge cases before publishing the report:

Test caseExpected behavior
Several employees share the minimumEmployee-detail query returns every tie
All qualifying salaries are NULLUngrouped MIN() returns one NULL
No employees qualifyUngrouped MIN() returns one NULL
Lowest employee is inactiveActive-only query excludes that employee
Zero salary existsIncluded unless the business filter excludes it

Two mistakes cause especially confusing results.

First, ascending ordering places nulls before non-null salaries in SQL Server. A TOP (1) query therefore needs Salary IS NOT NULL when searching for a known minimum.

Second, salary stored as text is compared as text. A varchar column can sort amounts differently from numeric order. Fix the data model and validate imported values rather than converting an entire text column during every report execution.

Avoid NOLOCK for payroll results. It permits uncommitted reads, so a report can reflect salary changes that later roll back.

If separate dashboard queries must agree, define their consistency requirement. An isolation level controls how transactions observe concurrent changes. Database-level row-versioning options can help some workloads, but require DBA review and capacity testing.

Pro Tip: I test the report during a simulated salary import, not just against idle data. Concurrency problems rarely appear in a quiet demonstration database.

Tune Minimum-Salary Queries Without Over-Indexing

Start with an actual execution plan, which shows the operations SQL Server used. Measure page reads and CPU time:

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT
MIN(Salary) AS LowestActiveSalary
FROM dbo.Employees
WHERE IsActive = 1;

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

STATISTICS IO reports logical and physical reads. STATISTICS TIME reports CPU and elapsed time. Small tables may scan efficiently, so a scan alone does not prove a problem.

For a frequent active-salary lookup, test this candidate:

CREATE NONCLUSTERED INDEX IX_Employees_ActiveSalary
ON dbo.Employees (Salary)
WHERE IsActive = 1
AND Salary IS NOT NULL;

A nonclustered index is a separate ordered access structure. This filtered index stores known active salaries and can help SQL Server find the low end efficiently.

Warning: Index creation consumes storage and resources and may block other activity. Review existing indexes and schedule production deployment appropriately.

For department-focused reports, (DepartmentID, Salary) may be a better key order. Included employee columns can avoid extra table lookups, but wider indexes increase storage and write overhead.

Use SQL query optimization tips alongside real plans. Statistics—metadata describing data distribution—help the optimizer estimate row counts and choose access paths.

Query Store, when enabled, preserves query performance history and plans for regression analysis. Tune before increasing Azure SQL compute, but do not promise direct savings: lower resource use reduces the bill only when the purchasing model or provisioned capacity reflects it.

Pro Tip: I keep an index only after testing both the report and salary-write workload. Optimizing a monthly query should not penalize every payroll update.

Secure and Maintain the Payroll Reporting Path

Use least privilege: grant only the access needed for the report. A stored procedure, a named database routine, can expose an approved aggregate without granting broad payroll-table access.

After creating the procedure and an authorized reporting role, grant execution:

GRANT EXECUTE
ON OBJECT::dbo.GetLowestActiveSalary
TO PayrollReportingRole;

The procedure should contain the approved active-employee query. With an appropriate same-owner ownership chain, callers need not receive direct table permissions. Review stored procedure execution permissions and test with a non-administrator identity.

Prefer Microsoft Entra authentication and managed identities for supported Azure applications. Keep unavoidable secrets in Azure Key Vault, not source code, spreadsheets, shared files, or unprotected application settings.

Require encrypted connections with server-certificate validation. An authorized administrator can check the session:

SELECT
encrypt_option
FROM sys.dm_exec_connections
WHERE session_id = @@SPID;

TRUE indicates encryption; it does not prove the client validated the certificate. DMV permissions vary by platform and version, so do not grant broad monitoring rights merely for this check.

Prefer private endpoints for Azure database access. Audit sensitive payroll access and alert on permission changes without writing salary results into general application logs.

The read-only query needs no special backup. Deploy supporting objects through reviewed scripts, and keep the database’s existing backup and restore-testing requirements intact.

Pro Tip: I test the reporting identity independently from the deployment identity. Successful execution as an administrator says little about application permissions.

Frequently Asked Questions

How do I find the lowest salary without NULL values?

MIN(Salary) already ignores null values. With TOP (1) ... ORDER BY Salary ASC, explicitly add WHERE Salary IS NOT NULL so an unknown salary does not appear first.

How do I find the employee with the lowest salary?

Compare each employee’s salary with a subquery that calculates MIN(Salary). This returns every matching employee; use deterministic TOP (1) ordering only when exactly one employee is required.

Does MIN return zero when the table is empty?

No. An ungrouped MIN() returns one row containing NULL when no rows qualify. Convert that to a display message in the application rather than silently labeling it zero salary.

How do I exclude zero salaries?

Add Salary > 0 to the qualifying filter. Do this only when the business definition excludes zero; otherwise, the report may conceal legitimate records or loading errors.

Can I run these queries in Azure SQL Database?

Yes. The aggregate, subquery, grouped, and window-function examples work in Azure SQL Database and Azure SQL Managed Instance. Validate permissions and execution plans in the target database.

Pro Tip: I keep the empty-result behavior consistent across reports so applications do not interpret “unknown” as “zero.”

You can now calculate minimum salaries, return tied employees, and build department-level reports. Keep eligibility rules explicit, protect payroll access, and measure performance before adding indexes or compute. I hope you found this article helpful.

You May Also Like