Django 中有没有办法始终将特定字段转换为 VARCHAR?

Is there a way in Django to always CAST a specific Field to VARCHAR?

背景情况说明:

我正在创建一个 Django2 应用程序,它使用 Microsoft SQL 服务器作为后端数据库。该数据库有几个包含 SQL_VARIANT 字段的 table(我无法管理或更改它们)。此字段包含空字符串或数字(它们用作主键或外键)

问题:

在 MSSQL 上,当您使用 SQL_VARIANT 字段和 VARCHAR 字段制作 JOIN 时,它会产生一行空结果,要解决这个问题,您必须使用 CAST([Table Name].[SQL_VARIANT Field Name] AS VARCHAR

将 SQL_VARIANT 字段显式转换为 VARCHAR

目标:

每次 Django 在 QuerySet 上创建 JOIN 或在 QuerySet

上调用 SQL_VARIANT 字段时,我想找到一种方法来执行 CAST([Table Name].[SQL_VARIANT Field Name] AS VARCHAR

我怎样才能做到这一点?

代码参考:

在我的问题中,我使用了来自 MSSQL 数据库的 3 tables(供应商、产品和产品分类帐条目)

供应商Table

create table [Vendor]
(
    Id sql_variant not null primary key, 
    Name varchar(30) not null,
    Address varchar(30) not null,
    City varchar(30) not null,
    Contact varchar(30) not null,
    Type int not null,
    Category int not null,
)

产品Table

create table [Product]
(
    Id varchar(20) not null primary key,
    Description varchar(60) not null,
    Class varchar(10) not null,
    [Vendor Id] varchar(20) not null,
)

产品分类帐条目table

create table [Product Ledger Entry]
(
    Id int not null primary key,
    [Product Id] varchar(20) not null,
    [Posting Date] datetime not null,
    [Entry Type] int not null,
    [Source Id] varchar(20) not null,
    [Document Id] varchar(20) not null,
    Quantity decimal(38,20) not null,
)

如果我想横穿所有 3 个 table,我会做 ...

SELECT
       [Vendor].[Id],
       [Vendor].[Name],
       [Product].[Id],
       [Product Ledger Entry].Quantity
FROM [Vendor]
    INNER JOIN [Product] ON ([Vendor].[Id] = [Product].[Vendor Id])
    INNER JOIN [Product Ledger Entry] ON ([Product].[Id] = [Product Ledger Entry].[Product Id])

但是这个查询没有产生任何行。仅通过对 SQL_VARIANT 字段进行显式转换,此查询显示异常结果。

SELECT
       [Vendor].[Id],
       [Vendor].[Name],
       [Product].[Id],
       [Product Ledger Entry].Quantity
FROM [Vendor]
    INNER JOIN [Product] ON (CAST([Vendor].[Id] AS VARCHAR(20)) = [Product].[Vendor Id])
    INNER JOIN [Product Ledger Entry] ON ([Product].[Id] = [Product Ledger Entry].[Product Id])

当你使用 Django 时也会发生同样的事情,这里是模型:

class Vendor(models.Model):
    id = models.CharField(db_column='Id', primary_key=True, , max_length=20)

    name = models.CharField(db_column='Name', max_length=30)

    # Address Description
    address = models.CharField(db_column='Address', max_length=30)

    # Province/City/Municipality
    city = models.CharField(db_column='City', max_length=30)

    class Meta:
        managed = False
        db_table = 'Vendor'

    def __str__(self):
        return f'{self.id} | {self.name}'
class Product(models.Model):
    id = models.CharField(db_column='No_', primary_key=True, max_length=20)

    description = models.CharField(db_column='Description', max_length=60)

    vendor_id = models.ForeignKey(
        'Vendor', on_delete=models.CASCADE,
        db_column='Vendor Id', to_field='id', related_name='products', db_index=False, blank=True
    )

    class Meta:
        managed = False
        db_table = 'Product'

    def __str__(self):
        return f'{self.id}'
class ProductLedgerEntry(models.Model):
    id = models.IntegerField(db_column='Id', primary_key=True)

    product_id = models.ForeignKey(
        'Product', on_delete=models.CASCADE,
        db_column='Product Id', to_field='id', related_name='ledger_entries', db_index=False
    )

    posting_date = models.DateTimeField(db_column='Posting Date')

    ENTRY_TYPE = [
        (0, 'Compra'),
        (1, 'Venta'),
        (2, 'Ajuste positivo'),
        (3, 'Ajuste negativo'),
        (4, 'Transferencia'),
        (5, 'Consumo'),
        (6, 'Salida desde fab.'),
    ]
    entry_type = models.IntegerField(
        db_column='Entry Type', choices=ENTRY_TYPE
    )

    quantity = models.DecimalField(db_column='Quantity', max_digits=38, decimal_places=20)

    class Meta:
        managed = False
        db_table = 'Product Ledger Entry'

    def __str__(self):
        return f'{self.product_id} | {self.variant_code} | {self.posting_date}'

只要我不使用 annotate(...)、select_related(...) 或任何其他 Django API 方法使 JOIN 在引擎盖,我可以设法获得正确的结果。但是,我想使用注释。

请帮帮我

AFAIK,使这成为可能的唯一方法是分叉您正在使用的 SQL 服务器后端(大概是 django-pyodbc-azure),以便在检测到 SQL_VARIANT场.

但是,这感觉是个很糟糕的主意;正如书 "Two Scoops of Django" 所说,避免创建 FrankenDjango 的诱惑!您的基础表不是由 Django 创建的;根据我的经验,最好对不是由 Django 创建和管理的表使用原始 SQL。您可以将 ORM 混合用于由 Django 管理或创建的表,将原始 SQL 用于在 Django 外部创建的遗留表。

通过 Django 的 execute() 方法使用绑定参数仍然为您提供 SQL 注入保护:https://docs.djangoproject.com/en/2.2/topics/db/sql/#executing-custom-sql-directly

祝你好运!