5 min read
Using Qdrant as a Relational Database

Qdrant is a “vector database” but it can also function quite happily as a relational database for many traditional use cases, thanks to its powerful payload system.

Understanding Qdrant Payloads

Every vector in Qdrant can have an associated payload – essentially a JSON document that can contain any structured data. This payload system is where the “relational database” magic happens:

{
  "id": 1,
  "vector": [0.1, 0.2, 0.3, ...],
  "payload": {
    "user_id": 12345,
    "name": "John Doe",
    "email": "john@example.com",
    "age": 30,
    "department": "Engineering",
    "salary": 75000,
    "created_at": "2024-01-15T10:30:00Z"
  }
}

You can query, filter, and sort based entirely on payload data, without ever touching the vector component.

Traditional Database Operations

Creating Records (INSERT)

from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct

client = QdrantClient("localhost", port=6333)

# Insert a new user record
client.upsert(
    collection_name="users",
    points=[
        PointStruct(
            id=1,
            vector=[0.0] * 128,  # Dummy vector if you don't need similarity search
            payload={
                "user_id": 12345,
                "name": "John Doe",
                "email": "john@example.com",
                "age": 30,
                "department": "Engineering"
            }
        )
    ]
)

Reading Records (SELECT)

# SELECT * FROM users WHERE department = 'Engineering'
result = client.scroll(
    collection_name="users",
    scroll_filter={
        "must": [
            {"key": "department", "match": {"value": "Engineering"}}
        ]
    },
    with_payload=True,
    with_vectors=False  # We don't need vectors for relational queries
)

# SELECT * FROM users WHERE age > 25 AND department = 'Engineering'
result = client.scroll(
    collection_name="users",
    scroll_filter={
        "must": [
            {"key": "age", "range": {"gt": 25}},
            {"key": "department", "match": {"value": "Engineering"}}
        ]
    },
    with_payload=True,
    with_vectors=False
)

Updating Records (UPDATE)

# UPDATE users SET salary = 80000 WHERE user_id = 12345
client.set_payload(
    collection_name="users",
    payload={"salary": 80000},
    points=[1]  # Point ID
)

Deleting Records (DELETE)

# DELETE FROM users WHERE user_id = 12345
client.delete(
    collection_name="users",
    points_selector=[1]  # Point ID
)

Advanced Querying Capabilities

Qdrant’s filtering system is surprisingly powerful for relational-style queries:

Range Queries

# Users aged between 25 and 35
scroll_filter={
    "must": [
        {"key": "age", "range": {"gte": 25, "lte": 35}}
    ]
}

Text Matching

# Users with names containing "John"
scroll_filter={
    "must": [
        {"key": "name", "match": {"text": "John"}}
    ]
}

Complex Boolean Logic

# (Engineering OR Marketing) AND age > 30
scroll_filter={
    "must": [
        {"key": "age", "range": {"gt": 30}},
        {
            "should": [
                {"key": "department", "match": {"value": "Engineering"}},
                {"key": "department", "match": {"value": "Marketing"}}
            ]
        }
    ]
}

When This Approach Makes Sense

Using Qdrant as a relational database is particularly powerful when you:

  1. Need both structured and semantic search: Store user profiles with traditional fields AND enable semantic search on descriptions or content
  2. Want unified infrastructure: One database for both your AI features and traditional data storage
  3. Have moderate scale requirements: Qdrant handles millions of records efficiently
  4. Value simplicity: Fewer moving parts in your infrastructure

Limitations to Consider

While Qdrant can function as a relational database, it’s important to understand its limitations:

  • No ACID transactions: Qdrant doesn’t provide traditional ACID guarantees
  • Limited aggregation: No built-in GROUP BY, SUM, COUNT operations
  • No foreign keys: You’ll need to handle relationships in application code
  • Schema flexibility: While flexible, this can lead to inconsistent data if not managed properly

Real-World Example: User Management System

Here’s how you might implement a simple user management system:

class UserManager:
    def __init__(self, client, collection_name="users"):
        self.client = client
        self.collection_name = collection_name

    def create_user(self, user_data):
        """Create a new user record"""
        point = PointStruct(
            id=user_data["user_id"],
            vector=[0.0] * 128,  # Dummy vector
            payload=user_data
        )
        self.client.upsert(self.collection_name, [point])

    def get_users_by_department(self, department):
        """Get all users in a specific department"""
        result = self.client.scroll(
            collection_name=self.collection_name,
            scroll_filter={
                "must": [{"key": "department", "match": {"value": department}}]
            },
            with_payload=True,
            with_vectors=False
        )
        return [point.payload for point in result[0]]

    def update_user_salary(self, user_id, new_salary):
        """Update a user's salary"""
        self.client.set_payload(
            collection_name=self.collection_name,
            payload={"salary": new_salary},
            points=[user_id]
        )

    def search_users_by_name(self, name_query):
        """Search users by name (fuzzy matching)"""
        result = self.client.scroll(
            collection_name=self.collection_name,
            scroll_filter={
                "must": [{"key": "name", "match": {"text": name_query}}]
            },
            with_payload=True,
            with_vectors=False
        )
        return [point.payload for point in result[0]]

The Best of Both Worlds

The real power emerges when you combine traditional relational queries with vector search capabilities. Imagine a user system where you can:

  1. Query users by traditional criteria (department, age, location)
  2. Find users with similar interests using semantic search
  3. Recommend connections based on profile similarity
  4. All in a single query!
# Find Engineering users similar to a specific user profile
similar_users = client.search(
    collection_name="users",
    query_vector=target_user_embedding,
    query_filter={
        "must": [
            {"key": "department", "match": {"value": "Engineering"}},
            {"key": "active", "match": {"value": True}}
        ]
    },
    limit=10
)

Conclusion

While Qdrant isn’t a traditional relational database, its payload system is sophisticated enough to handle many relational database use cases. For applications that need both structured data management AND semantic search capabilities, treating Qdrant as a relational database can significantly simplify your infrastructure.

The key is understanding when this approach fits your needs and when you might be better served by a traditional RDBMS. For many modern applications dealing with user-generated content, product catalogs, or content management systems, Qdrant’s dual nature as both a vector and “relational” database makes it a compelling choice.

Give it a try – you might be surprised at how naturally Qdrant can replace your traditional database for many use cases while simultaneously enabling powerful AI-driven features.