Back to Article List

What is MSSQL? A complete SQL Server guide

What is MSSQL? A complete SQL Server guide

Updated and tested 6 October 2026

MSSQL is Microsoft SQL Server. One product, two names, and Microsoft leans on the short one as heavily as anybody: the Linux service is mssql-server, the install sits in /opt/mssql and the container image reads MSSQL_SA_PASSWORD and MSSQL_PID. Its Visual Studio Code extension carries the MSSQL name too. SQL Server is the name on the licence and across learn.microsoft.com, while MSSQL is the internal short name that leaked out of the plumbing and into everyone's search bar.

What you get under either name is a relational database engine that speaks T-SQL, Microsoft's dialect of SQL, with procedural extensions and a long tail of behaviour that differs from PostgreSQL and MySQL in ways nobody warns you about until a query returns the wrong number. It runs on Windows and on Linux, it ships in editions with hard caps on memory and cores. The current release is SQL Server 2025.

This is the overview I wanted when I inherited my first MSSQL box: what version am I on, where does it live, why won't it let me connect and which parts of the dialect are going to surprise me. Some sections here are long because the detail matters and there's nowhere else to put it. Others are a paragraph, because a sibling article already does the job properly.

MSSQL server versions and lifecycle dates

Version numbers come in two flavours and you need both. The marketing year (2019, 2022, 2025) is what people say out loud. The internal major version is what @@VERSION prints, what the log folder is named and what half the tooling keys off. The map is short enough to memorise: 2017 is 14.x, 2019 is 15.x, 2022 is 16.x and 2025 is 17.x. That's why a default 2022 instance on Windows logs into MSSQL16.MSSQLSERVER and why the Configuration Manager snap-in for 2025 is SQLServerManager17.msc.

SQL Server 2025 went GA on 18 November 2025 with RTM build 17.0.1000.7, and it earns the year in its name. The two headline additions are a native vector data type (1 to 1,998 dimensions, float32 by default) with VECTOR_DISTANCE for exact k-nearest-neighbour search, and a set of regular expression functions: REGEXP_LIKE, REGEXP_REPLACE, REGEXP_SUBSTR, REGEXP_INSTR, REGEXP_COUNT, REGEXP_MATCHES and REGEXP_SPLIT_TO_TABLE.

Both come with caveats that matter more than the announcement. The approximate vector index and VECTOR_SEARCH are explicitly in preview. REGEXP_LIKE and the two table-valued functions, REGEXP_MATCHES and REGEXP_SPLIT_TO_TABLE, need database compatibility level 170 (the other scalar ones run at any level), which catches out people who upgrade an instance, run REGEXP_LIKE and get told the function doesn't exist. The engine behind them is RE2, so there are no backreferences and no lookaround. Don't promise your team PCRE parity.

Microsoft publishes the dates on the SQL Server 2025 lifecycle page and equivalents for each release, and the ones to have in a calendar are these. SQL Server 2022 is still the version you'll meet most often in the wild and it's fine, with mainstream support to 12 January 2028 and extended support to 12 January 2033. SQL Server 2019 lost mainstream support on 1 March 2025, so no more design changes and no new features, with extended support carrying security fixes to 9 January 2030. SQL Server 2017 runs out entirely on 13 October 2027.

On new installs in 2026 I don't hedge: install 2025. The support window is three years longer than 2022's, the Linux support matrix is newer and Express got a cap increase that changes what you can do without paying. The one reason to install 2022 instead is a third-party application whose vendor certifies 2022 and not 2025, which is a constraint I've hit more than once and lost the argument every time.

When the engine won't start you can't query it, which is why the registry and command-line routes in our walkthrough on how to check your SQL Server version and edition sit alongside the T-SQL ones. For what's on a machine right now, don't guess from folder names. SERVERPROPERTY('ProductLevel') and SERVERPROPERTY('ProductUpdateLevel') will tell you RTM versus CU. Cumulative updates ship monthly in a release's first year and every two months after that, so any build number printed in an article (including this one) is stale within weeks, and for a build list worth bookmarking there is only Microsoft's latest updates page.

MSSQL editions: Express, Developer, Standard and Enterprise

Editions are where SQL Server stops being like other databases. The engine is the same binary. What changes is a set of caps the licence enforces, and the caps moved in 2025, which means most of what's written about them online is wrong now.

Express is free and licensed for production use, customer-facing databases included. The maximum size of a single relational database was 10 GB through SQL Server 2022 and is 50 GB in SQL Server 2025. Five times the room, and for plenty of small databases that jump is the difference between running free and buying cores. The caps that didn't move are the ones that hurt: compute is limited to the lesser of one socket or four cores, and the buffer pool is 1,410 MB per instance. Hand an Express instance a 64 GB server and it will still hold 1.4 GB of data pages in memory.

Developer edition has every Enterprise feature switched on (on 2025 that's Enterprise Developer; Standard Developer mirrors Standard), costs nothing and is licensed for development and test only. Never production. The installer won't stop you either, so it ends up on production servers far more often than Microsoft would like.

Database size and buffer pool are the two numbers that decide most cases when you pick between SQL Server Express, Developer and Standard.

Standard in 2025 moved up to the lesser of 4 sockets or 32 cores with a 256 GB buffer pool, up from 24 cores and 128 GB in 2022, so a Standard licence stretches a good deal wider than it used to. Enterprise lifts the caps and the invoice reflects that. Production licensing for Standard and Enterprise is per-core and commercial, with a minimum of four core licences per physical processor and four per virtual machine when you licence individual VMs.

Where MSSQL runs: Windows, Linux and containers

SQL Server was a Windows-only product for most of its life and stopped being one in 2017. Nine years on, a good share of new deployments go straight to Linux. The targets are still not interchangeable. Windows carries every feature Microsoft ships, the Linux build gives you the engine with less around it, and containers run that same engine while adding a packaging problem you have to solve before anything starts.

Windows Server

The graphical installer is fine and I still use it on a machine I'm going to look after by hand. For anything repeatable, learn the command-line install, because it's the same syntax you'll paste into an image build later:

setup.exe /Q /ACTION=Install /FEATURES=SQLENGINE /INSTANCENAME=MSSQLSERVER ^
  /SQLSYSADMINACCOUNTS="BUILTIN\Administrators" ^
  /SECURITYMODE=SQL /SAPWD="<StrongPassword>" ^
  /TCPENABLED=1 /IACCEPTSQLSERVERLICENSETERMS

/IACCEPTSQLSERVERLICENSETERMS is required and setup refuses to run without it. /SECURITYMODE=SQL is what turns on mixed mode, and it needs /SAPWD alongside it. Leave /SECURITYMODE out and you get Windows authentication only, which is the correct choice for a domain-joined server and the wrong one for a box that a Linux application connects to. /SQLSYSADMINACCOUNTS is mandatory on everything except Express.

The wizard path, the feature list and the parts worth changing are all in installing SQL Server on Windows Server. One warning from Microsoft's own install page that people ignore: installing over Remote Desktop from media on your local machine can fail, so put the ISO on the server or on a network share before you start.

Linux

Read the support matrix before you pick a distribution, because this is where most blog posts are simply wrong. SQL Server 2025 supports RHEL 9.x and 10.x plus Ubuntu 22.04 and 24.04, on XFS or ext4 file systems. SUSE Linux Enterprise Server was dropped in 2025. Debian has never been supported. SQL Server 2022 supports RHEL 8.x and 9.x, SLES 15 SP1 to SP4 and Ubuntu 20.04 and 22.04, with no 24.04 on the list at all.

The practical consequence is worth stating plainly: on Ubuntu 24.04 you install SQL Server 2025, not 2022. Every tutorial that tells you to add the 2022 repo on 24.04 is describing an unsupported combination that may install and will not be supported when it breaks.

Our step-by-step for installing SQL Server on Ubuntu 24.04 exists because the unattended setup variables are easy to get wrong and the password policy rejects half the passwords people try. The repo paths are in there too. The install itself is four commands once the repo is in place, and apt-key is gone, so anything still using it is out of date:

curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg
sudo apt-get update
sudo apt-get install -y mssql-server
sudo /opt/mssql/bin/mssql-conf setup

The tools are a separate package, mssql-tools18, and the binaries live in /opt/mssql-tools18/bin, which is not on your default PATH. Configuration on Linux happens through mssql-conf. Configuration Manager never made the crossing. The keys are written into /var/opt/mssql/mssql.conf and the ones you'll touch are network.tcpport, memory.memorylimitmb and sqlagent.enabled. Two defaults to change on day one: the memory limit is 80% of physical RAM, which is too greedy on a box running anything else, and SQL Server Agent is off.

While we're here. The Linux binary is sqlservr, with no 'e'. That spelling looks like a leftover of the 8.3 filename era on Windows. It has outlived every reason for it, and I still typo it about once a week.

Docker and containers

The container image is the fastest route to a working instance and the easiest one to get subtly wrong:

docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=<password>" \
   -p 1433:1433 --name sql1 --hostname sql1 \
   -d \
   mcr.microsoft.com/mssql/server:2025-latest

Use MSSQL_SA_PASSWORD. SA_PASSWORD is deprecated and still appears in thousands of copied compose files. Containers for SQL Server 2019 and later start as a non-root user by default, which is the right behaviour and the reason bind-mounted data directories need their ownership sorted out before the engine will start.

Most of the container failures I get asked about start in the Dockerfile, and the init-script patterns and volume permissions behind them are picked apart in running SQL Server in a Docker container. The shape of the problem is always the same. Someone wants their schema loaded at container start, so they set sqlcmd as the image's ENTRYPOINT or CMD. Now sqlservr never runs as PID 1, there's no engine for sqlcmd to talk to, and the container exits within seconds.

The engine has to be the main process. Init SQL runs from a wrapper that backgrounds sqlservr and polls for readiness, from a sidecar or from a Kubernetes init container.

Any of these run happily on a Linux VPS with NVMe storage, and the friction is in the setup and not the hardware. LumaDock's one-click MSSQL template deploys the engine on Ubuntu in a couple of minutes if you'd rather skip the repo dance, and it doesn't hand you a SQL Server licence. Bring a key for Standard or Enterprise, or keep the instance inside Express or Developer terms, which is where most of the setups described on this page live anyway.

SQL Server tools in 2026

The tooling story changed more in the last twelve months than it had in the previous five, and if you last set up a client environment in 2023 your muscle memory is out of date.

DBeaver and the VS Code extension both fall short somewhere, which SQL Server Management Studio setup and the alternatives is blunt about, alongside the install itself. SSMS is still the reference client and the current release is SSMS 22. It's Windows-only, 64-bit only, and it needs .NET Framework 4.8. The install route changed: SSMS now comes through the Visual Studio Installer, so you download a stub called vs_SSMS.exe and the updates arrive on the same channel as Visual Studio's. The standalone installer is gone. SSMS 22 also added initial Windows Arm64 support, which matters if you're on a Snapdragon laptop and have been running SSMS under emulation.

Azure Data Studio is dead. It was retired on 28 February 2026, with no more updates and no security fixes, so it's something to remove from your machines. Microsoft's retirement notice points everyone at Visual Studio Code with the MSSQL extension, which is the cross-platform answer now and on macOS or Linux the only first-party one. It's a decent editor with a connection manager and a query results grid. What it lacks is the estate management surface, so the maintenance plan designer and Activity Monitor stay behind on Windows. Plan for a Windows machine somewhere if you administer servers as well as query them.

The tool people forget entirely is SQL Server Configuration Manager, which ships with SQL Server and not with SSMS, and has no Start menu entry because it's an MMC snap-in. Run SQLServerManager17.msc for 2025 or SQLServerManager16.msc for 2022. That's where the TCP/IP protocol lives, and it answers a surprising share of "I can't connect" problems on Windows.

Machine Learning Services, the R and Python integration, is a blank spot for me. Eleven years of running these servers and it has stayed switched off on every one of them, so I have no view here worth your attention. If that's the part you came here for, this article is the wrong one.

Your idea deserves better hosting

24/7 support 30-day money-back guaranteeRefund covers the VPS plan on your first order. Cancel anytime
Betalingscyclus

VPS.S1

28.38 RON Save  17 %
23.64 RON Maandelijks
  • 2 vCPU AMD EPYC
  • 2 GB RAMGEHEUGEN
  • 30 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen

VPS.S3

71.01 RON Save  33 %
47.33 RON Maandelijks
  • 4 vCPU AMD EPYC
  • 6 GB RAMGEHEUGEN
  • 70 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen

VPS.S4

94.70 RON Save  20 %
75.75 RON Maandelijks
  • 6 vCPU AMD EPYC
  • 8 GB RAMGEHEUGEN
  • 80 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen

EPYC VPS.P1

42.59 RON Save  22 %
33.11 RON Maandelijks
  • 2 vCPU AMD EPYC
  • 4 GB RAMGEHEUGEN
  • 40 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

EPYC VPS.P2

80.49 RON Save  24 %
61.54 RON Maandelijks
  • 2 vCPU AMD EPYC
  • 8 GB RAMGEHEUGEN
  • 80 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

EPYC VPS.P4

142.07 RON Save  23 %
108.91 RON Maandelijks
  • 4 vCPU AMD EPYC
  • 16 GB RAMGEHEUGEN
  • 160 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

EPYC VPS.P5

189.45 RON Save  25 %
142.08 RON Maandelijks
  • 8 vCPU AMD EPYC
  • 16 GB RAMGEHEUGEN
  • 180 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

EPYC VPS.P6

284.20 RON Save  25 %
213.14 RON Maandelijks
  • 8 vCPU AMD EPYC
  • 32 GB RAMGEHEUGEN
  • 200 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

EPYC VPS.P7

331.57 RON Save  29 %
236.82 RON Maandelijks
  • 16 vCPU AMD EPYC
  • 32 GB RAMGEHEUGEN
  • 240 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

Genoa VPS.G2

118.39 RON Save  20 %
94.70 RON Maandelijks
  • 2 vCPUAMD EPYC Genoa 4e generatie 9xx4 met 3,25 GHz of vergelijkbaar, op Zen 4-architectuur. AMD EPYC G4
  • 4 GB DDR5GEHEUGEN
  • 50 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

Genoa VPS.G4

213.14 RON Save  22 %
165.76 RON Maandelijks
  • 4 vCPUAMD EPYC processor met dedicated vCPU cores, op enterprise serverhardware. AMD EPYC G4
  • 8 GB DDR5GEHEUGEN
  • 100 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

Genoa VPS.G6

426.32 RON Save  22 %
331.57 RON Maandelijks
  • 8 vCPUAMD EPYC processor met dedicated vCPU cores, op enterprise serverhardware. AMD EPYC G4
  • 16 GB DDR5GEHEUGEN
  • 200 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

Genoa VPS.G7

757.94 RON Save  22 %
592.13 RON Maandelijks
  • 8 vCPUAMD EPYC processor met dedicated vCPU cores, op enterprise serverhardware. AMD EPYC G4
  • 32 GB DDR5GEHEUGEN
  • 250 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland. inbegrepen
  • Gratis auto back-upBevat één back-upslot dat je kunt instellen op dagelijks, wekelijks of maandelijks.

AMD Ryzen VPS.R1

80.49 RON Save  18 %
66.28 RON Maandelijks
  • 1 dedicated CPU AMD Ryzen 9 7950X met 4,5 GHz of vergelijkbaar, op Zen 4-architectuur. vCPU
  • 4 GB DDR5GEHEUGEN
  • 50 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6 inbegrepen IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland.
  • Auto back-up inbegrepen

AMD Ryzen VPS.R2

142.07 RON Save  17 %
118.39 RON Maandelijks
  • 2 dedicated CPUs AMD Ryzen 9 7950X met 4,5 GHz of vergelijkbaar, op Zen 4-architectuur. vCPU
  • 8 GB DDR5GEHEUGEN
  • 100 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6 inbegrepen IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland.
  • Auto back-up inbegrepen

AMD Ryzen VPS.R4

521.07 RON Save  18 %
426.32 RON Maandelijks
  • 8 dedicated CPUs AMD Ryzen 9 7950X met 4,5 GHz of vergelijkbaar, op Zen 4-architectuur. vCPU
  • 32 GB DDR5GEHEUGEN
  • 400 GB NVMeOPSLAG
  • Ongelimiteerde bandbreedte
  • IPv4 & IPv6 inbegrepen IPv6-ondersteuning is momenteel niet beschikbaar in Frankrijk, Finland of Nederland.
  • Auto back-up inbegrepen

*VAT excluded.

INCLUDED WITH EVERY PLAN

✓ No setup fees ✓1 Gbps network
✓ Full root access on KVM ✓ Free server monitoring ✓ Firewall management ✓24/7 support

Ports, remote access and connection strings

A default SQL Server instance listens on TCP 1433. A named instance doesn't: it takes a dynamic TCP port chosen when the service starts, and clients find it by asking the SQL Server Browser service on UDP 1434. That one design decision is responsible for a large fraction of all SQL Server connection failures, and it's why "just open 1433" is advice that works for exactly half of the installations out there.

Two other port facts worth carrying around. The Dedicated Admin Connection uses TCP 1434, the same number as the Browser but a different protocol, so the two coexist and the coincidence confuses people reading firewall rules. And database mirroring and Service Broker have no documented default port at all; 5022 and 4022 are conventions from the documentation examples, so treat any firewall rule referencing them as a local convention and not a standard.

Don't read configuration files back at yourself to find out what the engine is listening on. There is a DMV query for that. It sits with the SQL Server default port 1433 and how to change it. Changing the port is one command on Linux and a Configuration Manager dialog on Windows:

sudo /opt/mssql/bin/mssql-conf set network.tcpport 1435
sudo systemctl restart mssql-server

After that, every client has to name the port with a comma and not a colon, so it's Server=db.example.com,1435. Get that wrong and the error you see gives no hint that the port is the problem.

Getting a remote client connected takes four things lining up, and people usually check one of them. The engine has to be listening on TCP (on Windows the TCP/IP protocol is disabled by default on some editions). The port has to be open in the host firewall. The engine has to be in mixed authentication mode if the client is using a SQL login, and on Linux that's automatic because Linux is mixed mode only. And the login has to exist with rights on the target database.

Enabling remote connections to SQL Server takes that sequence step by step, with the firewall commands for ufw, firewalld and Windows.

Encryption defaults in SqlClient, JDBC and ODBC Driver 18

If you have ever upgraded a driver and watched every connection start failing with "The certificate chain was issued by an authority that is not trusted", this section is why. Every Microsoft client library flipped its encryption default within a few years of each other, and none of the applications using them were ready.

Microsoft.Data.SqlClient changed Encrypt from false to true in version 4.0. The JDBC driver changed encrypt to true in version 10.2, and Microsoft's own release notes call it a breaking change. ODBC Driver 18 changed Encrypt from no to yes and added a third value, strict, which uses TDS 8.0 and ignores TrustServerCertificate entirely because it always validates the certificate. And in mssql-tools18, sqlcmd's -N flag now defaults to mandatory, again documented as a breaking change from SQL Server 2022 and earlier.

Every one of those defaults is correct. The problem is that a fresh SQL Server instance presents a self-signed certificate, so encryption succeeds and validation fails. On a lab box you add TrustServerCertificate=True to the connection string, or pass -C to sqlcmd, and move on. On anything real you put a certificate the client trusts in front of the engine, which on Linux means network.tlscert and network.tlskey in mssql-conf. A correct ADO.NET string for a production server looks like this:

Server=tcp:db.example.com,1433;Database=mydb;User Id=myuser;Password=mypassword;Encrypt=True;TrustServerCertificate=False;

Every driver spells the same connection differently (there is a connection string reference for .NET, JDBC and ODBC with the working forms, named instances and Windows authentication included). The equivalents differ in more than syntax: the current JDBC driver (13.6.0, GA in September 2026) takes encrypt as a string so it can accept strict, and the ODBC keyword names don't line up with the ADO.NET ones at all.

SQL Server login and connection errors

Two error numbers account for most of the pain, and they fail in opposite directions. Error 18456 means the server heard you and turned you down. Error 26 means your client never found a server to ask, so nothing reached the authentication stage at all.

The state table for 18456, with all the documented values, is one page over at fixing SQL Server error 18456. The catch with "Login failed for user" is that the message your client receives always says State: 1 regardless of what really happened, because Microsoft deliberately hides the nature of the authentication failure from the network.

The real state number only exists in the server error log, on Linux at /var/opt/mssql/log/errorlog and on Windows under the instance's MSSQL\Log\ERRORLOG. Read that file and the diagnosis is usually instant: state 8 is a wrong password, states 2 and 5 mean the user ID isn't valid, state 18 means the password must be changed, state 58 means the server is in Windows-authentication-only mode and someone tried a SQL login, and state 11 is the Windows-admin-via-group-membership case where UAC isn't elevating. One irritation on Linux: the error log is UTF-16LE, so grep returns nothing useful until you run it through iconv.

Error 26, "Error Locating Server/Instance Specified", is a named instance problem every single time. When a client connects to HOST\INSTANCE, it first sends an SSRP packet to UDP 1434 asking the Browser service which port that instance is using, and error 26 means that packet got no reply. So the causes are a typo in the instance name, an instance that doesn't exist, the Browser service not running or a firewall that opened TCP 1433 and left UDP 1434 closed. That last one is the classic, and there is a full decision tree at fixing SQL error 26. Naming the port explicitly, as host\INSTANCE,1435, bypasses the Browser entirely and is my first move when debugging someone else's server. A default instance on 1433 never talks to the Browser at all, which is why you'll never see error 26 against one.

Beyond those two there's a long tail, and a lot of it is T-SQL and not networking. We keep the numbers, the causes and the first command to run for each in a SQL Server troubleshooting cheat sheet, and the ones that come up most are 10738 when an INSERT ... VALUES list exceeds 1,000 rows, 10713 when a MERGE is missing its terminating semicolon, then 8623 and 8632 when somebody pastes a ten-thousand-item IN list into a query.

T-SQL syntax and behaviour differences

T-SQL is close enough to standard SQL that you can be productive on day one and different enough that day three produces a bug you don't understand. What follows is the set of behaviours I'd want a developer moving from PostgreSQL or MySQL to know before they touch a production database.

INSERT and UPDATE

Two limits shape how you write inserts. A VALUES list attached to INSERT is capped at 1,000 rows and throws error 10738 past that. The same table value constructor used as a derived table has no such limit:

INSERT INTO dbo.Test ([Value])
SELECT drvd.[NewVal]
FROM (VALUES (0),(1),(2),(3)) AS drvd([NewVal]);

SELECT INTO gets its own treatment alongside INSERT INTO in SQL Server, including the part where indexes, constraints and triggers are not carried across to the new table. The OUTPUT clause is the other thing to learn early, because it hands back the rows an INSERT, UPDATE or DELETE touched without a second round trip. It comes with conditions people trip over: an OUTPUT without INTO fails against a table with enabled triggers, and the INTO target can't have triggers, foreign keys or check constraints.

Now the one that costs money. UPDATE ... FROM with a join is idiomatic T-SQL and it is not safe by default. If the join matches more than one source row per target row, SQL Server picks one arbitrarily and updates the target with it, and nothing in the output tells you that happened. Microsoft's documentation says the results are undefined and leaves it there. Oracle raises ORA-30926 when two source rows match the same target row, while PostgreSQL's UPDATE ... FROM behaves like SQL Server and says so in its documentation. I've watched this silently corrupt a pricing table, so I check the join cardinality with a SELECT before every multi-table update now, every time.

Clause order in that statement is fixed: SET, then OUTPUT, then FROM, then WHERE. There is more on the join and OUTPUT patterns in the UPDATE in SQL Server guide.

WHERE, NULL handling and CASE

BETWEEN is inclusive on both ends, which sounds harmless until dates are involved. BETWEEN '20240101' AND '20240131' against a datetime column silently drops almost all of 31 January, because the unspecified time part defaults to midnight and anything later that day falls outside the range. The half-open pattern is the fix and I use it without exception: >= '20240101' AND < DATEADD(day, 1, '20240131').

NOT IN has a sharper edge. Put a NULL anywhere in the list or in the subquery's results and every row evaluates to UNKNOWN, so the query returns nothing at all and looks like a data problem. The fault is in the logic. Use NOT EXISTS when a subquery might return NULLs. Why <> is preferable to !=, and what happens to a very large IN list, belong to the WHERE clause in SQL Server.

All five differences, plus the CASE ordering caveat with aggregates, are laid out across ISNULL, COALESCE, NULLIF and CASE. The two that matter most: ISNULL takes exactly two arguments, returns the type of the first one and evaluates its input once, while COALESCE is ANSI standard, takes many arguments, returns the highest-precedence type of all of them and is rewritten internally as a CASE, so its inputs can be evaluated more than once. That difference bites when one of the inputs is a subquery or a nondeterministic function. ISNULL also truncates: the replacement value is silently cut to the length of the checked expression, so ISNULL(short_col, 'replacement') on a varchar(5) column gives you repla.

Date and string functions

Stop using GETDATE(). It returns datetime with three fractional digits that round to .000, .003 and .007, which is a 1998 data type wearing a 2026 badge. SYSDATETIME() returns datetime2(7) and has been available since SQL Server 2008. SYSDATETIMEOFFSET() gives you the offset as well, which is what you want if the application has users outside one country.

DATETRUNC and DATE_BUCKET arrived in 2022 and turn up under SQL Server date functions, along with the AT TIME ZONE two-step for converting between zones. The behaviour to internalise first is that DATEDIFF counts boundaries crossed and not elapsed time, which is why DATEDIFF(year, '2005-12-31 23:59:59', '2006-01-01 00:00:00') returns 1 for a gap of one second. It also overflows: the maximum span for second is about 68 years and for millisecond about 24 days, after which you need DATEDIFF_BIG. And never build logic on DATENAME, which returns language-dependent text that changes with the login's default language. Use DATEPART. Ask it for iso_week when you want numbering that doesn't move with SET DATEFIRST.

Strings have a version problem more than a behaviour problem. CONCAT_WS arrived in 2017 and skips NULLs along with their separators, which makes address formatting a one-liner. TRIM also arrived in 2017 with its characters FROM argument, and only the LEADING, TRAILING and BOTH keywords are new in 2022, where they need database compatibility level 160. STRING_SPLIT is from 2016, needs compatibility level 130 or higher, takes a single-character separator only and returns rows in no guaranteed order, which people discover after shipping code that assumed the order matched the input. In SQL Server 2025 the length argument to SUBSTRING finally became optional.

Why LEN ignores trailing spaces and DATALENGTH doesn't is one of the details in the SQL Server string functions reference.

Data types, CAST and CONVERT

Data type precedence is a page most people never open, and it explains a whole class of runtime errors. When an operator combines two types, the lower-precedence one converts up, and the order is fixed. int outranks every string type, so WHERE code = 1 against a varchar column tries to convert the whole column to int and fails at runtime on the first non-numeric row. float outranks decimal, so mixing them silently costs you precision in a money calculation. And nvarchar outranks varchar, which is why a parameterised query from an application whose driver sends Unicode parameters can turn an index seek on a varchar column into a full scan. Microsoft's precedence list is worth reading once, because the fix is usually one explicit cast.

The style table and the precedence traps sit side by side in SQL Server data types, CAST and CONVERT. CAST is ANSI and portable, CONVERT is T-SQL only and takes a style argument, and that's the whole trade-off. The style codes are the reason to pick CONVERT: 112 gives yyyymmdd and 126 gives ISO 8601 with the T separator, and both are deterministic, which style 23 and most others up to 100 are not when a datetime is involved. TRY_CAST is not the blanket safety net people assume either. It returns NULL for a failed conversion and still raises an error for a conversion that isn't permitted at all, so TRY_CAST(4 AS XML) throws.

ALTER TABLE and sp_rename

ALTER TABLE ... ALTER COLUMN has a long list of things it refuses to touch, and that list is why schema migrations on SQL Server take longer than people plan for. A column that's part of a primary or foreign key, used in a check or unique constraint, referenced by a computed column or carrying a default definition is off limits until you drop the thing referencing it. Indexed columns can only change type if they're growing a varchar, nvarchar or varbinary. Dropping a column doesn't reclaim its disk space, which surprises everyone the first time they check the table size afterwards.

Add, drop and rename each take a different path (ALTER TABLE in SQL Server takes them in turn, along with the TRUNCATE versus DELETE question that always comes up alongside them). Renaming is worse than it looks: sp_rename works, and Microsoft's own caution on the page tells you not to use it on procedures, views, triggers or functions, because renaming doesn't update any of the references to the old name. Check sys.sql_expression_dependencies before you rename anything, and run sp_refreshsqlmodule afterwards on the views that used SELECT *.

CTEs and APPLY

Here's the correction that changes how people write queries. A common table expression is not a temporary table and its results are not materialised. Each outer reference to a CTE re-executes the query that defines it, so referencing a CTE three times in one statement does the work three times. Microsoft's documentation says so plainly and suggests a temporary object for multi-reference cases, which is exactly right and almost never what people do.

The recursive form has its own rules, and the hierarchy examples that make recursion click sit with common table expressions in SQL Server. The rules to know before you start are a default MAXRECURSION of 100 and a ban on DISTINCT, GROUP BY, TOP and outer joins inside the recursive member. There's also the semicolon rule, where a CTE inside a batch needs the preceding statement terminated, which is where the defensive ;WITH idiom comes from.

APPLY is the T-SQL feature I miss most when I'm working in another dialect. It evaluates the right-hand table source once per row of the left, which lets you join to a table-valued function that takes column values from the left side as arguments. CROSS APPLY drops the left row when the right side comes back empty and OUTER APPLY keeps it with NULLs, and there's no bare APPLY, one of the two keywords is mandatory. The top-N-per-group pattern built on CROSS APPLY is a community idiom and not documented Microsoft guidance, and it's still the cleanest way to get the three most recent orders per customer.

SQL Server compared with PostgreSQL and MySQL

Anyone weighing the two should start with the licensing maths, which is where our SQL Server and PostgreSQL comparison starts, before it gets to the dialect gaps during a port and who should pick which. The licence decides it far more often than the feature grid does. Standard and Enterprise are per core with the four-core minimum mentioned earlier in this article, which is a real number that changes how you size a virtual machine, while PostgreSQL costs nothing at any core count.

Set against that, SQL Server's optimiser is excellent, APPLY and window function coverage are strong, and the 2025 vector work is ahead of where a stock PostgreSQL install lands without extensions. The behavioural difference that generates the most confused bug reports in both directions is isolation, since PostgreSQL is multiversion by default and SQL Server uses locking read committed until you turn on read committed snapshot isolation.

Against MySQL the question is different, because the two rarely compete for the same project. MySQL owns the small web application, ships on every hosting panel on earth and has a much shallower learning curve. SQL Server shows up where there's a .NET application, a reporting estate or a set of stored procedures somebody wrote in 2011 that nobody wants to rewrite. Feature depth is where SQL Server pulls ahead: window functions, MERGE, columnstore indexes and a query optimiser that handles complicated joins without hand-holding. Ease of operation is where MySQL does, especially for a team without a DBA. Our SQL Server and MySQL comparison puts numbers against both sides.

Both comparisons have the same shape underneath. SQL Server is a strong engine attached to a commercial licence, so what you're weighing is the per-core invoice against the thing you're building. For a lot of projects, Express at 50 GB settles it without anyone opening a purchase order.

Connect to whatever MSSQL server you inherited and run SELECT @@VERSION before you plan anything else. If the first number is 15, you're on SQL Server 2019, mainstream support ended in March 2025 and you have until January 2030 before the security fixes stop. That's your upgrade window, and it's shorter than it sounds once you count the application testing.

Questions?

What is mssql-python?

It is Microsoft's own Python driver for SQL Server, Azure SQL Database, Azure SQL Managed Instance and SQL database in Fabric, installed from PyPI with pip install mssql-python. It connects through what Microsoft calls Direct Database Connectivity, so the Microsoft ODBC Driver does not have to be installed first, which is the main practical difference from pyodbc. The driver follows the Python DB-API 2.0 specification, needs Python 3.10 or later and has connection pooling and Microsoft Entra authentication built in.

Jörg L. Günther
About the author

Jörg L. Günther

Jörg has spent much of his career in enterprise software, working first with database deployments and customer environments, then in technical training and documentation. Since then he has written for companies working in virtualisation, database tooling and cloud infrastructure. The subjects he takes on often have a large operational component: database migrations, high-availability setups, storage configuration and procedures carried out on live systems. Jörg lives near Frankfurt. He plays piano and cycles for transport as much as for recreation. He's also been doing woodworking for several years - he says most of the furniture in his study is his own work.

Deploy your next project in seconds

Bring your winning ideas online faster, with modern hardware and unmetered bandwidth. Join a European cloud trusted by thousands of developers and businesses worldwide.