- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Writing SQL You Can Still Read Six Months Later
About Post
Somewhere in your codebase there's a query that everybody is afraid of.
It's 60 lines long. It has three nested subqueries, tables aliased as a, b and c, a CASE inside a SUM inside another CASE, and a WHERE clause with a magic status = 3 that nobody can explain. It works. Nobody touches it. And when the numbers on the report look wrong, everyone quietly hopes it's someone else's turn.
The person who wrote it wasn't careless. They were in the middle of the problem, and in the middle of a problem, everything makes sense. SQL readability is about the version of you who opens that file six months later with none of that context.
Start with formatting, because it's free
Most "unreadable" SQL is really "unformatted" SQL. A few habits fix a surprising amount:
- One clause per line.
SELECT,FROM, eachJOIN,WHERE,GROUP BY,ORDER BYeach start a new line. - One column per line once you have more than three or four. It makes diffs readable too: adding a column changes one line.
- Join conditions next to the join, not hidden in the
WHEREclause. - Pick a keyword case and stick to it. Uppercase keywords are the common convention, and they separate the language from your table names at a glance.
Here's the same query, before and after. Nothing about the logic changed:
select c.id,t.name,sum(p.amount) from contracts c join tenants t on t.id=c.tenant_id left join payments p on p.contract_id=c.id where c.status=3 group by c.id,t.name
SELECT
contracts.id AS contract_id,
tenants.name AS tenant_name,
SUM(payments.amount) AS total_paid
FROM contracts
JOIN tenants ON tenants.id = contracts.tenant_id
LEFT JOIN payments ON payments.contract_id = contracts.id
WHERE contracts.status = 'active'
GROUP BY contracts.id, tenants.name;
Notice two quieter changes: real table names instead of one-letter aliases, and 'active' instead of 3. Short aliases are fine for long table names (maintenance_requests AS requests), but a, b, c forces the reader to keep a lookup table in their head.
Name things like you name variables
Column aliases are documentation. SUM(amount) in a result set tells you nothing; total_paid tells you exactly what you're looking at, and it's what your application code will read.
The same goes for the schema itself. A column called flag or type2 will haunt every query that touches it. If a status is a number, either store a readable value or keep a well-known mapping in the codebase, never just in someone's memory.
CTEs: give each step a name
The biggest upgrade for complex queries is the common table expression, the WITH clause. It lets you split a query into named steps that read top to bottom, like a small program. MySQL supports them from 8.0, and PostgreSQL and SQL Server have had them for years.
Say you want tenants whose overdue balance is above a limit. The nested-subquery version reads inside out. The CTE version reads like a recipe:
WITH due AS (
SELECT contract_id, SUM(amount) AS total_due
FROM invoices
WHERE due_date < CURRENT_DATE
GROUP BY contract_id
),
paid AS (
SELECT contract_id, SUM(amount) AS total_paid
FROM payments
GROUP BY contract_id
)
SELECT
due.contract_id,
due.total_due - COALESCE(paid.total_paid, 0) AS overdue
FROM due
LEFT JOIN paid ON paid.contract_id = due.contract_id
WHERE due.total_due - COALESCE(paid.total_paid, 0) > 500;
Each block answers one question: what's due, what's paid, what's the difference. You can also debug it piece by piece: run just the due step and check its numbers before blaming the final query.
The COALESCE is there because a contract with no payments gets NULL from the left join, and anything minus NULL is NULL. That's exactly the kind of detail that's easy to miss when the logic is buried three levels deep.
Stop writing SELECT * in real code
SELECT * is great for poking around in a console. In application code it causes three slow problems:
- It hides intent. The reader can't see which columns actually matter.
- It moves more data than needed, including that large
TEXTorJSONcolumn someone added last year. - It changes behaviour silently. Add a column, and every query returns it. With joins, two tables with an
idorcreated_atcolumn can overwrite each other in your result array depending on your driver.
In Laravel, the same idea is ->select(['id', 'name', 'status']) on queries that run often or return many rows.
Comments: explain why, not what
SQL needs comments more than most code, because business rules hide in it. Don't comment -- join payments; the reader can see that. Comment the thing they can't see:
-- Cancelled contracts still count until their end date:
-- tenants can be billed for the notice period.
WHERE contracts.status IN ('active', 'cancelled')
AND contracts.end_date >= CURRENT_DATE
That comment will save someone from "fixing" a bug that isn't one.
Window functions: the tool that removes self-joins
Once your queries are readable, window functions are the next step up. They let you calculate across related rows without collapsing them the way GROUP BY does. Classic example: the latest payment per contract.
WITH ranked AS (
SELECT
payments.*,
ROW_NUMBER() OVER (
PARTITION BY contract_id
ORDER BY paid_at DESC
) AS rn
FROM payments
)
SELECT contract_id, amount, paid_at
FROM ranked
WHERE rn = 1;
PARTITION BY says "restart the numbering for each contract", and ORDER BY says "newest first". The older alternative is a self-join or a correlated subquery, which is harder to read and easier to get subtly wrong when two payments share a timestamp. (Yes, there's a payments.* inside the CTE; inside a step that you immediately narrow down, it's a reasonable shortcut.)
Other window functions worth knowing: SUM(...) OVER (...) for running totals, LAG() to compare a row with the previous one, and RANK() for leaderboards with ties. MySQL 8.0+ supports them all.
The six-month test: before you commit a query, imagine reading it with zero context. If you'd need to run it to understand it, split it into named steps, rename the mystery columns, and write down the one business rule that isn't obvious.
Checklist
- One clause per line, joins with their conditions.
- Readable aliases, for tables and for result columns.
- CTEs for anything with more than one logical step.
- Named columns instead of
SELECT *in application code. - Comments that explain business rules, not syntax.
- Window functions instead of clever self-joins.
What's the worst query you've inherited, and what was the first thing you did to make sense of it?

Be first to comment it...