【问题标题】:Dynamic table design (common lookup table), need a nice query to get the values动态表设计(通用查找表),需要一个很好的查询来获取值
【发布时间】:2011-01-12 13:36:51
【问题描述】:

sql2005

这是我的简化示例: (实际上这里有40多张桌子,我只展示了2张)

我有一个名为 tb_modules 的表,有 3 列(id、描述、表名作为 varchar):

 1, UserType, tb_usertype
 2, Religion, tb_religion

(最后一列实际上是不同表的名称)

我得到了另一个看起来像这样的表: tb_value(列:id、tb_modules_ID、usertype_OR_religion_ID) 价值观:

 1111, 1, 45
 1112, 1, 55
 1113, 2, 123
 1114, 2, 234

所以,我的意思是 45、55、123、234 是用户类型或宗教 ID (45、55 个用户类型,123、234 个宗教 ID)

别评判,数据库不是我设计的

问题 如何进行选择,显示 * from tb_value,加上一列 这一列将是 tb_usertype 中的 TITLE 或 tb_religion 表中的 RELIGIONNAME

我想做一个通用的东西。 最初在考虑可能是一个返回字符串的 SQL 函数,但我想我需要动态 SQL,这在函数中是不行的。

谁有更好的主意?

【问题讨论】:

  • 我认为您需要为此使用动态 sql,但我猜如果它在函数中不正常,那么您的查询也不会正常。
  • 实际的答案是你需要调用一个特殊的操作,叫做“construct a new design”。
  • 看看下面 Ken Down 的解决方案,它使用 UNION 视图从根本上为您纠正结构(并且遵守您的“不判断”请求)。

标签: sql-server-2005 database-design dynamic-sql sql-function


【解决方案1】:

一开始我们有这个——相当混乱。

为了稍微清理一下,我添加了两个视图和一个同义词:

create view v_Value as
select
      ID                      as ValueID
    , tb_modules_ID           as ModuleID
    , usertype_OR_religion_ID as RemoteID
from tb_value ;
go

create view v_Religion as
select
      ID
    , ReligionName as Title
from tb_religion ;
go

create synonym v_UserType for tb_UserType ;
go

现在模型看起来像

现在编写查询更容易了

;
with 
q_mod as (
    select
          m.ID                          as ModuleID
        , coalesce(x1.ID    , x2.ID)    as RemoteID
        , coalesce(x1.Title , x2.Title) as Title
        , m.Description                 as ModuleType
    from      tb_Modules as m
    left join v_UserType as x1 on m.TableName = 'tb_UserType'
    left join v_Religion as x2 on m.TableName = 'tb_Religion'
)
select
      a.ModuleID
    , v.ValueID
    , a.RemoteID
    , a.ModuleType
    , a.Title
from q_mod   as a
join v_Value as v on (v.ModuleID = a.ModuleID and v.RemoteID = a.RemoteID) ;

这个查询有一个明显的模式,所以如果你必须添加另一个模块类型的表,它可以创建为动态sql。添加另一个表时,请使用IDTitle 以避免必须使用视图。

编辑

构建动态 sql(或应用程序级别的查询)

修改第6、7行,x-index为tb_modules.id

coalesce(x1. , x2. , x3. ..) 

向左连接添加行(第 11 行下方)

left join v_SomeName as x3  on m.TableName = 'tb_SomeName'

SomeNametb_modules.description,x-index 匹配 tb_modules.id

编辑 2

最简单的方法可能是将上述查询打包到一个视图中,然后每次模式更改时动态创建并运行ALTER VIEW。这样查询就不会从应用程序的角度改变。

【讨论】:

  • 就像我告诉奥兹一样。这个问题是,如果我添加一个新的“模块”,那么我必须更改查询
  • @Swoosh -- 你必须动态构建它。
  • @Swoosh。阅读我的答案。 “动态”是为数据库保留的;损坏的文件是不可能的。你有没有向“客户”承诺过一些“动态”的东西?
【解决方案2】:

既然我们都同意这个设计是片状的,我会跳过任何关于它的 cmets。查询的模式是这样的:

-- Query 1
select tb_value.*,tb_religion.religion_name as ANY_DESCRIPTION
  from tb_value 
  JOIN tb_religion on tb_value.ANY_KIND_OF_ID = tb_religion.id
 WHERE tb_value.module_id = 2
-- combine it with...
UNION ALL
-- ...Query 2
select tb_value.*,tb_religion.title as ANY_DESCRIPTION
  from tb_value 
  JOIN tb_userType on tb_value.ANY_KIND_OF_ID = tb_userType.id
 WHERE tb_value.module_id = 1
-- combine it with...
UNION ALL
-- ...Query 3
select  lather, rinse, repeat for 40 tables!

您实际上可以定义一个对所有 40 个案例进行硬编码的视图,然后将过滤器应用于您想要的特定模块的查询。

【讨论】:

    【解决方案3】:

    要动态执行此操作,您需要能够创建如下所示的 sql 语句

    select tb_value.*, tb_usertype.title as Descr
    from tb_value
        inner join tb_usertype
            on tb_value.extid = tb_usertype.id
    where tb_value.tb_module_id = 1
    union all
    select tb_value.*, tb_religion.religionname as Descr
    from tb_value
        inner join tb_religion
            on tb_value.extid = tb_religion.id
    where tb_value.tb_module_id = 2
    -- union 40 other tables
    

    目前您不能这样做,因为您在 db 中没有任何信息告诉您从 tb_religion 和 tb_usertype 等中使用哪一列。您可以将其添加为 tb_module 中的新字段。

    如果您在 tb_module 中有要使用的字段名,您可以构建一个执行您想要的视图。 并且您可以向表 tb_modules 添加一个触发器,该触发器会在修改 tb_modules 时更改视图。这样,您在进行查询时就不需要使用来自客户端的动态 sql。您唯一需要担心的是在向 tb_modules 添加新行之前需要在 db 中创建表

    编辑 1 当然触发器中的代码需要动态构建alter view语句。

    编辑 2 您还需要有一个字段,其中包含有关 tb_usertype 和 tb_religion 等中的哪些列的信息,以针对 tb_value.extid (usertype_OR_religion_ID) 加入。或者您可以假设该字段将始终称为id

    编辑 3 以下是如何在 tb_module 上构建触发器以更改视图 v_values。我在 tb_modules 中添加了 fieldname 作为列,我假设相关表中的 id 字段称为 id。

    create trigger tb_modules_change on tb_modules after insert, delete, update
    as 
    
    declare @sql nvarchar(max)
    declare @moduleid int
    declare @tablename varchar(50)
    declare @fieldname varchar(50)
    
    set @sql = 'alter view v_value as '
    
    declare mcur cursor for
      select id, tablename, fieldname
      from tb_modules
    
    open mcur
    fetch next from mcur into @moduleid, @tablename, @fieldname
    while @@FETCH_STATUS = 0
    begin
        set @sql = @sql + 'select tb_value.*, '+@tablename+'.'+@fieldname+' '+
                          'from tb_value '+
                          'inner join '+@tablename+' '+
                            'on tb_value.extid = '+@tablename+'.id '+
                          'where tb_value.tb_module_id = '+cast(@moduleid as varchar(10))  
      fetch next from mcur into @moduleid, @tablename, @fieldname
    
      if @@FETCH_STATUS = 0
      begin
        set @sql = @sql + ' union all '
      end
    end
    close mcur
    deallocate mcur
    
    exec sp_executesql @sql
    

    【讨论】:

    • 哇。这是一个有趣的方法,从来没有想过这个。我不太喜欢触发器,但是您可以在该特定表上的任何插入、删除、更新上调用它...
    【解决方案4】:

    嗯..可能有更好的解决方案,但这是我的五分钱:

    SELECT 
    id,tb_modules_ID,usertype_OR_religion_ID,
    COALESCE(
     (SELECT TITLE FROM tb_usertype WHERE Id = usertype_OR_religion_ID),
     (SELECT RELIGIONNAME FROM tb_religion WHERE Id = usertype_OR_religion_ID),
     'N/A'
    ) AS SourceTable
    FROM tb_valuehere
    

    请注意,我现在无法检查该语句,因此我为任何语法错误保留自己...

    【讨论】:

    • 我对这个想法最大的问题是:如果我在 tb_modules 表中添加另一个“模块”,我需要在查询中添加一个新行。我想让它更有活力...
    • 那是真的..真的没有考虑到这一点
    • @Swoosh 如果添加另一个“模块”,您如何知道需要从该表中选择的列名?
    • @Lamak - 你也把它存储在表中,如果你添加一个新表作为记录,你可以有一个名为“IDcolumn”的列
    【解决方案5】:

    首先,使用您当前的设计唯一合理的解决方案是动态 SQL。您应该在中间层中编写一个模块来查询适当的表名并动态构建查询。试图在 T-SQL 中实现这一点将是一场噩梦。 T-SQL 不是为字符串构造而设计的。

    正确的解决方案是构建一个设计合理的新数据库,迁移数据并废弃现有设计。您在当前设计中遇到的问题只会越来越多。新开发人员将更难学习新系统。会很容易出错。将没有数据完整性(例如,强制属性“开始日期”可解析为日期)。自定义查询将是一件苦差事,等等。最终,您会遇到这样的一天,即在当前设计下,系统所需的信息类型太难提取了。

    【讨论】:

    • 在一个完美的世界里,客户有钱付钱给你做那件事,并且会给你时间做那件事......
    • @Swoosh。呃,在任何发达国家。如果您不属于其中之一,那就足够了。
    • @Swoosh - 我不完全同意这种观点。现在继续使用他们现有的设计会花费公司的资金,并且将来会花费他们越来越多的资金。继续维护无法满足其需求且维护成本将不断上升的数据库是否具有成本效益?所以,选择真的是:现在付钱,以后多付钱,或者现在付一些钱,以后少付。
    【解决方案6】:

    首先把非设计者从后面带出来,让他们摆脱痛苦。他们在伤害人。

    • 由于他们的无能,每次您向 Module 添加 row 时,您都必须修改每个使用它的查询。适合 www.dailywtf.com。

    • 您也没有参照完整性,因为您无法在 this_or_that 列上定义 FK。您的数据可能会暴露给由同一个非设计者编写的“代码”。毫无疑问,您知道这是造成死锁的地方。

    • 这是一种“判断”,以便您了解非设计的严重性,并为您的经理证明更换它的合理性。

    • SQL 是为关系数据库设计的,这意味着规范化。这对损坏的文件不利。当然,有些查询可能比其他查询更好(只看答案),但没有办法绕过取消设计,任何 SQL 查询都会受到阻碍,并且在添加模块行时需要更改。

    • “动态”是为数据库保留的,不能用于平蝇。

    两个答案。一个可以阻止每次添加模块行时更改现有查询的持续愚蠢行为(不客气);第二个回答你的问题。

    安全的未来查询

    CREATE VIEW UserReligion_vw AS
        SELECT  [XxxxId]     = id,    -- replace Xxxx
                [ReligionId] = usertype_OR_religion_ID
            FROM  tb_value
            WHERE tb_modules_ID = 1

    创建视图 UserReligion_vw AS 选择 [XxxxId] = ID, [ReligionId] = usertype_OR_religion_ID FROM tb_value 哪里 tb_modules_ID = 2 从现在开始,确保所有当前使用 undesign 的查询都被修改为使用正确的 View。不要将视图用​​于更新/删除/插入。

    回答

    好的,现在是主要问题。我可以想到其他方法,但这是最好的。您已经说过,您希望第三列也是一块未标准化的鸡排泄物,并为 [EITHER_Religion_OR_UserType_OR_This_OR_That] 提供 Title。是的,所以你也在教用户感到困惑;当模块数量增加时,他们会很高兴弄清楚列包含的内容。是的,问题总是会复杂化。

        SELECT  [XxxxId]   = id,
                [Whatever] = CASE tb_modules_ID
                    WHEN 1 THEN ( SELECT name      -- title, whatever
                        FROM  tb_religion
                        WHERE id = V.usertype_OR_religion_ID 
                        )
                    WHEN 2 THEN ( SELECT name      -- title, whatever
                        FROM  tb_usertype
                        WHERE id = V.usertype_OR_religion_ID 
                        )
                    ELSE "(UnknownModule)"         -- do not remove the brackets
                    END
            FROM  tb_value V
            WHERE conditions...                    -- you need something here
    
    这称为相关标量子查询

    • 它适用于自 4.9.2 以来的任何 Sybase 版本,没有任何限制。还有 SQL 2005(我上次看,无论如何,2009 年 8 月)。但是在MS上,如果tb_value的体积很大,你会得到一个StackTrace,所以要确保WHERE子句有一些条件。

    • 但是 MS 已经用他们的“新”2008 代码行破坏了服务器,所以它不能在所有情况下工作(你的损坏文件越差,它工作的可能性就越小;你的数据库设计越好,可能会起作用)。这就是为什么有些 MS 人每天都为下一个 Service Pack 祈祷,而其他人从不去教堂。

    【讨论】:

    • 显然这将比 40 个 SELECT 加 39 个 UNION 执行得更好。
    【解决方案7】:

    我猜你想要这样的东西:

    将表和每表一行添加到 tb_modules 中是直截了当的。

    SET NOCOUNT ON
    if OBJECT_ID('tb_modules') > 0 drop table tb_modules;
    if OBJECT_ID('tb_value') > 0 drop table tb_value;
    if OBJECT_ID('tb_usertype') > 0 drop table tb_usertype;
    if OBJECT_ID('tb_religion') > 0 drop table tb_religion;
    go
    
    create table dbo.tb_modules (
        id int, 
        description varchar(20), 
        tablename varchar(255)
    );
    
    insert into tb_modules values (  1, 'UserType', 'tb_usertype');
    insert into tb_modules values (  2, 'Religion', 'tb_religion');
    
    create table dbo.tb_value(
        id int,
        tb_modules_ID int,
        usertype_OR_religion_ID int
    );
    insert into tb_value values (   1111, 1, 45);
    insert into tb_value values (   1112, 1, 55);
    insert into tb_value values (   1113, 2, 123);
    insert into tb_value values (   1114, 2, 234);
    
    create table dbo.tb_usertype(
        id int,
        UserType varchar(30)
    );
    
    insert into tb_usertype values ( 45, 'User_type_45');
    insert into tb_usertype values ( 55, 'User_type_55');
    
    create table dbo.tb_religion(
        id int,
        Religion varchar(30)
    );
    
    insert into tb_religion values ( 123, 'Religion_123');
    insert into tb_religion values ( 234, 'Religion_234');
    
    -- start of query
    
    declare @sql varchar(max) = null
    
    Select @sql = case when @sql is null then '          ' else @sql + char(10) + 'union all '  end 
        + 'Select ' + str(id) + ' type, id, ' + description + ' description from '  + tablename  from   tb_modules 
    
    set @sql = 'select  v.id, tb_modules_ID , usertype_OR_religion_ID , t.description
    from tb_value v
        join ( ' + @sql + ') as t
    on v.tb_modules_ID = t.type and v.usertype_OR_religion_ID = t.id
    '
    
    Print @sql
    exec( @sql)
    

    【讨论】:

      【解决方案8】:

      我认为它打算与动态 sql 一起使用。

      也许将每个 tb_value.tb_modules_ID 行分解为它自己的临时表,以 tb_modules.tablename 命名。

      然后对与您的命名约定(通过前缀或后缀)匹配的临时表进行迭代,构建 sql 并进行连接。

      【讨论】:

      • 这不是一个“解决方案”,它会产生一个全新的问题,需要管理、维护等!!!
      猜你喜欢
      • 2017-01-17
      • 2013-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-06
      • 2020-02-22
      • 1970-01-01
      相关资源
      最近更新 更多