6

我有以下 sql 语句:

Select i.imageID, theImage, translationRating
From images i
inner join translationchains  t
on t.imageId = i.imageid
where i.userID=(someUserID) And i.translated =0 
and t.targetLang in (select targetLang from translationChains)

我想让它成为一个准备好的语句,以便在我的 java 代码中使用:

Select i.imageID, theImage, translationRating
From images i
inner join translationchains  t
on t.imageId = i.imageid
where i.userID=? And i.translated =0 
and t.targetLang in (select ? from translationChains)

第一个输入?是一个用户 ID(整数),它工作正常。

第二个输入是一个包含 languageId 的字符串 - 它是一个包含表示语言的数字的字符串,或者是列名称的字符串“targetLang”(=所有语言)

在我的java代码中,我做了以下事情:

Image.setInt(1, userID);
Image.setString(2, langID);
Image.executeQuery()

我的问题是,当我将字符串“targetLang”作为第二个参数发送时,准备好的语句将其插入为“targetLang”(带有“之前和之后”),使用数字这不是问题,因为 3='3',但是使用字符串,它给了我与我需要的不同的结果——我总是得到一个空的结果集,因为没有什么等于'targetLang'。我需要将此字符串插入到没有 '. 是否有可能或者我需要使用与准备好的语句不同的东西?

我知道我可以构建一个包含所有这些查询的字符串,但我正在寻找更优雅的 tnx


编辑:

这是创建表翻译链:

Create Table if not exists TranslationChains (
  ImageID int (10) NOT NULL,  
  SourceLang int NOT NULL,
  TargetLang int NOT NULL,
  Translated tinyint default 0,
  Translation text,
  Translator varchar (30),
  CONSTRAINT translate_image PRIMARY KEY (ImageID,SourceLang, TargetLang),
  FOREIGN KEY (ImageID) REFERENCES Images(ImageID) ON DELETE CASCADE,
  FOREIGN KEY (SourceLang) REFERENCES Languages(languageID) ON DELETE CASCADE,
  FOREIGN KEY (TargetLang) REFERENCES Languages(languageID) ON DELETE CASCADE)

如您所见,我正在尝试根据作为 int 列的“targetLang”列拍摄图像。每个数字代表一种语言。

现在我有两个选项可以从该表中选择图像:

  • 选择一种特定的语言,即给出一个数字作为第二个输入。
  • 选择所有语言,即将第二个输入设置为列名“targetLang”。

所以我不比较固定字符串“targetLang”。我想为此列选择所有可能的值(从 translationChains 中选择 targetLang)。

4

2 回答 2

3

查询参数只能代替文字值——即通常放置带引号的字符串文字、带引号的日期文字或数字文字的位置。因此,字符串值将始终被解释为字符串文字,就好像您已使用单引号将其放入查询中一样。

对于列名、表名、SQL 表达式、SQL 关键字等,您必须在调用 prepare() 之前将这些值插入到 SQL 查询中。

为了安全起见,请使用白名单,这样用户输入就不会被逐字插入 SQL 查询中。始终在查询中使用用户输入(或任何其他内容)之前对其进行验证。

在构建 SQL 查询时,我编写了许多白名单示例。

您还可以在我的演示文稿SQL Injection Myths and Fallacies和我的书SQL Antipatterns: Avoiding the Pitfalls of Database Programming中看到示例。


回复您的评论:

我不确定你在做什么。听起来您要么想t.targetLang等于一个文字数字,要么等于一个固定的字符串'targetLang'。如果是这样,我根本不知道您为什么要进行子查询-您应该只使用查询参数作为值:

where i.userID = ? And i.translated = 0 
and t.targetLang = ?

Image.setString(2, langID); // either '3' or 'targetLang'

但我不确定我是否完全理解你的描述。该数字是否代表您要比较的列的位置?与 t.targetLang 在同一行?如果是这样,我仍然认为您不需要子查询。您可以使用如下表达式:

where i.userID = ? And i.translated = 0 
and FIELD(t.targetLang, t.targetLang, t.column2, t.column3, t.column4) = ?

Image.setString(2, '1'); // for the case where you allow all langs
Image.setString(2, '3'); // for the case where you want to match a specific column.

请参阅 MySQL 中FIELD()函数的手册,该函数在表达式列表中搜索第一个参数。它返回匹配字段的整数位置。

使用此方法,您可以传递您希望 t.targetLang 匹配的位置,这允许您将动态部分作为值而不是列名传递。它还允许您避免子查询。

如果我仍然错了并且不理解您的问题,请编辑您的原始问题并提供更多详细信息。 SHOW CREATE TABLE translationChains有助于。您还可以回答我必须做出假设的一些事情,例如,您是否尝试将 t.targetLang 与同一行另一列中的值进行匹配?


好的,现在我更好地理解了你的目标。这是一个执行您想要的查询的查询:

SELECT i.imageID, theImage, translationRating FROM Images i INNER JOIN TranslationChains t ON t.imageId = i.imageid WHERE i.userID = ? AND i.translated = 0 AND ? IN (t.targetLang, 'targetLang')

您可以将整数作为第二个参数传递以匹配 t.targetLang,或者将文本字符串 'targetLang''targetLang与谓词中的值 ' 匹配。

不过,我不能推荐这个解决方案,因为它可能不会很好地使用索引,它可能必须通过将字符串参数与整数列进行比较来执行类型转换。

当您想要匹配所有语言时,更好的做法是在 WHERE 子句中不带最后一项的情况下执行查询。也就是说,在您的应用程序中,有条件地附加该术语,仅当您希望查询获取特定语言时。否则,省略该术语并跳过Image.setString().

String sql = "SELECT i.imageID, theImage, translationRating
FROM Images i
INNER JOIN TranslationChains t
  ON t.imageId = i.imageid
WHERE i.userID = ? AND i.translated = 0 ";

if (langId) {
  sql += " AND ? IN (t.targetLang, 'targetLang')";
}

. . .

Image.setInt(1, userID);
if (langId) {
  Image.setString(2, langID);
}
Image.executeQuery()
于 2013-04-04T18:54:29.907 回答
0

你可以改变:

和 t.targetLang in (select ? from translationChains)

到:

and (
        (
            ? = 'targetLang' and
            t.targetLang in (
                                    select
                                        targetLang
                                    from
                                        translationChains
                            )
        )
        or
        ? = t.targetLang
    )

并设置setString(3,langId),即使第三个参数与第二个相同。无论如何,据我所知,这更优雅或更有效,但应该可行!

于 2013-04-04T19:15:59.347 回答