1

背景情况说明:

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

问题:

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

目标:

我想找到一种方法,CAST([Table Name].[SQL_VARIANT Field Name] AS VARCHAR每次 DjangoJOIN在 QuerySet 上创建或调用 QuerySetSQL_VARIANT上的字段时

我怎样才能做到这一点?

代码作为参考:

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

供应商表

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,
)

产品表

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,
)

产品分类帐条目表

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 张桌子,我会做......

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,我就可以设法获得正确的结果。但是,我想使用注释。

请帮我

4

1 回答 1

1

AFAIK,使这成为可能的唯一方法是分叉您正在使用的 SQL Server 后端(大概)在检测到字段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

祝你好运!

于 2019-09-01T17:54:46.973 回答