【问题标题】:SQL Subquery and CAST not workingSQL 子查询和 CAST 不起作用
【发布时间】:2013-05-07 08:55:14
【问题描述】:

我正在尝试从varchar 形式的一个表中获取数据,并将其传递给具有与 int 相同字段的其他查询。

Article 包含以下字段

(ArticleID int, ArticleTitle nvarchar(200), ArticleDesc nvarchar(MAX))

样本数据

1 Title1 Desc1
2 Title2 Desc2
3 Title3 Desc3

我有另一个名为 Banner 的表格,其中包含与文章相关的横幅

(BannerID int, BannerName nvarchar(200), BannerPath nvarchar(MAX), ArticleID varchar(200))

样本数据

**BannerID BannerName BannerFile ArticleID**
    100 Banner1 BannerPath1 '1','3','5'
    101 Banner2 BannerPath2 '2','3','5'
    102 Banner3 BannerPath3 '8','3','5'
    103 Banner4 BannerPath4 '10','30','5','2','3','5'

示例查询

SELECT ArticleTitle 
FROM Article 
WHERE CAST(ArticleID AS varchar(200)) IN (
    SELECT  ArticleID FROM Banner WHERE BannerID = 2
)

在我的实际项目中,我在横幅表中有多个字段,以便我可以分配横幅文章、作者、类别、页面.. 出于这个原因,我决定以这种格式将 ArticleID 或 WriteID 或 CatID 存储为单个字段'10','30','5','2','3','5'

我改变了我的结构,然后我可能最终为一个横幅创建数百条记录,一个横幅可以分配给这个article, writer, Category, Pages中的任何一个@

下面的查询返回零行可能是我的投射造成了问题,我将不胜感激如何在不改变我的数据库结构的情况下解决这个问题

SELECT ArticleTitle FROM Article WHERE CAST(ArticleID AS varchar(200)) IN (SELECT  ArticleID FROM Banner WHERE BannerID = 2)

更新:

没有冒犯我决定坚持我的设计的任何人,因为我提出的问题是网站的支持报告部分不会经常使用。关于不规范化表格我可能是错的......

我的实际场景假设用户访问url

`abc.com/article/article.aspx?articleID=30&CatID=10&PageID=3&writerID=3`

基于此 url,我可以使用 UNION 运行四个查询,以获得所需的横幅偏离路线我必须决定横幅优先级,所以我会这样做

     `SELECT  BannerName, BannerImage FROM Banner WHERE ArticleID LIKE '%''30''%'`
    UNION ALL
    `SELECT  BannerName, BannerImage FROM Banner WHERE CategoryID LIKE '%''10''%'`
     UNION ALL
     `Another query .......`

如果我这样做,那么查询将不得不在单个表中查找带有几行的横幅但如果我根据JW 规范化表,这是一种很好的做法,可能会导致每个横幅有 30-40 行在不同的表格中,这可能会影响性能,因为我必须为新文章(新杂志问题)添加新横幅。

我知道我违反了规范化的每一条法则,但我担心我必须为了性能而这样做,因为我可能最终每 100 个横幅有 2000 行,而且这会随着时间的推移而增长。

再次更新

我希望这张图片能让你大致了解我正在尝试做的事情

如果我这样做,那么每个横幅只需要 1 行,如果我进一步规范化并创建更多表格,那么我最终可能会为一个横幅设置几行 从横幅表中获取上面的图像样本,然后我的第一个横幅将有 27 行 第二条横幅 11 行 三分之二横幅 14 行。

为了避免这种情况,我想在他们尊重的字段中存储多个 articleID、IssueID、PageID ....。这种方法可能很脏,但很有效。

我确实有一些 -ve 反馈,从他们的角度来看是可以理解的。由于我提供了更多详细信息,因此我的方法完全不专业,或者请记住,网站可能有很好的流量并且这种方法可能是更快。

【问题讨论】:

  • ArticleID 真的保存为'1','3','5' 还是1,3,5
  • 我在查看问题时做了一个 SQL Fiddle:sqlfiddle.com/#!3/2ee3c/2。无论如何,我建议你接受 JW 在他的回答中给出的建议。
  • 我同意 JW 关于规范化的观点,我只是想知道为什么它不起作用。我的 id 以'1','3','5' 存储在数据库中,我可以将其更改为1,3,5。我同意这有一个设计缺陷,但它可以与数百个插入语句相同,因为我的横幅可以与 1)大约 10 个杂志问题和列表可以增长 2)大约 30 个类别。 3) 最多 25- 30 篇文章 4) 作家,随着我们不断添加新的杂志问题,作者可能有数百人 5) 最多 10 个不同的 pageID。
  • 它不起作用,因为碰巧包含逗号的 single 字符串值(并且,取决于您尝试的版本,引号)是不一样的作为 multiple int/string 值。我不知道系统会决定检查字符串的内部部分并神奇地将一个值转换为多个值的语言。然而,人们似乎认为 SQL 会以这种方式工作......我同意@JW웃 - 正确规范你的表。
  • “为了表现我必须这样做”可能是我听过的最糟糕的理由之一。您的设计排除了使用索引的可能性如果存在实际性能问题。您显示的 UNION 查询别无选择,只能扫描 整个 表以查找每个查询。

标签: asp.net sql-server sql-server-2008 join


【解决方案1】:

当您将逗号分隔的值保存在列中,而这些值将用于搜索记录时,这是一种非常糟糕的设计。

您需要正确规范化表格并将其重组为 3 表格设计,因为我可以在 ArticleBanner 上看到 Many-to-Many 关系。

建议的架构设计:

文章列表

  • 文章 ID (PK)
  • 文章标题
  • 文章描述

横幅表

  • 横幅 ID (PK)
  • 横幅名称
  • 横幅路径

Article_Banner 表

  • ArticleID (FK)(也是带有 BannerID 的复合 PK)
  • 横幅 ID (FK)

通过这种设计,您可以简单地查询您的记录,例如:

SELECT  a.*
FROM    Article a
        INNER JOIN Article_Banner b
            ON a.ArticleID = b.ArticleID
WHERE   b.BannerID = 2

结构的优点:

  • 可以轻松创建查询语句
  • 可以利用已定义的索引
  • 等等..

【讨论】:

  • 我同意你的观点,但在我的情况下,我必须根据存储在不同表中的不同表字段(如 ArticleID, Article_CategoryID, WrittersID, PagesID)显示横幅
  • 那么为什么不对ArticleBanner 做同样的事情呢?如果您继续当前的设计,您肯定会很难构建查询。
  • 我认为最好改变设计,但我仍然无法理解为什么这个查询不工作SELECT ArticleTitle FROM Article WHERE CAST(ArticleID AS varchar(200)) IN (SELECT ArticleID FROM Banner WHERE BannerID = 2) 以及为什么这是工作SELECT ArticleTitle FROM Article WHERE CAST(ArticleID AS varchar(200)) IN ('2','3','5') 假设子查询返回结果为@987654330 @出于好奇,我想知道第一个子查询返回什么结果。如何调试才能看到子查询的结果是什么?
  • 你的第一个语句不起作用的原因是因为子查询返回的ArticleID与一个像这样的整体值比较,''1','3','5'''1','3','5'非常不同
【解决方案2】:

除了John Woo's excellent answer,我会尝试回答“为什么查询没有返回任何结果”这个问题。

我将抛​​开WHERE b.BannerID = 2 子句,显然任何样本记录都不符合该子句。

查询的主要问题是IN clause。 IN 将告诉您是否在一组项目中找到一个项目。您期望它做的是遍历一组集合并告诉您是否找到该项目。

为了说明这一点,这里有两个简化的查询:

-- this will print 0
if '1' in ('''1'',''3'',''5''')
    print 1
else 
    print 0

-- this will print 1
if '1' in ('1', '3', '5')
    print 1
else 
    print 0

重点是IN是一个基于集合的操作,而不是一个会查找子字符串的字符串函数。

解决您的问题的一种可能方法是使用CHARINDEX 执行子字符串检测:

select ArticleTitle 
from Article a
join Banner b
  on charindex(CAST(a.ArticleID AS varchar(200)), b.ArticleID) > 0

这个版本不正确,因为搜索 id '1' 也会匹配像 '11','12' 这样的值。

为了得到正确的结果,你可能会得到一个类似的查询(为了确保你只匹配星号之间的值):

select ArticleTitle 
from Article a
join Banner b
  on charindex('''' + CAST(a.ArticleID AS varchar(200)) + '''', b.ArticleID) > 0

SQLFiddle:http://www.sqlfiddle.com/#!3/2ee3c/23

然而,这个查询有两个很大的缺点:

  • 对于相对较大的表,它变得非常慢,因为它不能使用任何索引,并且需要扫描Banner 表中Article 中的每一行
  • 代码变得有点复杂,添加的功能越多,推理就越困难,从而导致可维护性问题。

这两个问题是你做错了什么的气味。按照JW的方案,这两个问题就迎刃而解了。

【讨论】:

  • +1 了解我感到困惑的细节,我已经更新了我的问题。不顾你们任何人,我决定继续我的表现方法。
【解决方案3】:

完全同意上面的例子是不好的,不是他正在做的正确方法。但是,根本错误消息问题仍然存在,并且在某些情况下是一个问题。

我的情况是使用表格来保存一些自定义表单字段元素数据。在不列出整个结构的情况下,我将仅列出显示和重现问题所需的内容。在这种情况下,我还可以确认围绕 IsNumeric 解决的问题。也与子查询结合使用。该示例包含两个项目,模拟自定义字段元素/类型的项目名称和字段值。有些是名字,有些是劳动记录。可能是重量、温度、距离等等,它是客户可定义的额外数据。

Create Table dboSample (cKey VarChar(20), cData VarChar(50))

Insert Into dboSample (cKey, cData) Values ('name', 'Jim')
Insert Into dboSample (cKey, cData) Values ('name', 'Bob')
Insert Into dboSample (cKey, cData) Values ('labortime', '60')
Insert Into dboSample (cKey, cData) Values ('labortime', '00')
Insert Into dboSample (cKey, cData) Values ('labortime', '15')

Select * From (Select * From dboSample Where IsNumeric(cData) = 1) As dboSampleSub Where Cast(cData As Int) > 0

导致错误“将 varchar 值 'Jim' 转换为数据类型 int 时转换失败。”

较低的嵌套查询有一个 where 子句,将返回的行限制为仅包含基于数字的数据。然而,更高级别的演员清楚地看到子查询返回中未包含的行。它实际上是查看和处理下层嵌套查询的数据。找不到选择选项标志来防止这种情况发生。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-13
    • 1970-01-01
    • 2020-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-29
    相关资源
    最近更新 更多