我的项目是拉丁语言学习应用程序。我的数据库在“单词”表中包含了我正在教的所有单词。它具有 lemma (单词的主要形式)以及用户需要学习的定义和其他信息。
我一次显示一个单词,让他们猜测/记住它的含义。显示正确的单词以及一些错误的单词,例如:
错误的选项是从同一张表中随机抽取的,并且必须来自与正确单词相同的词性(形容词,名词...);但是我只对他们的引理感兴趣。我的返回值看起来像这样(省略了一些属性):
[
{ lemma: 'Romanus', definition: 'Roman', options: ['Greek', 'Phoenician', 'barbarian'] },
{ lemma: 'domus', definition: 'house', options: ['horse', 'wall', 'senator'] }
]
我正在寻找的是比当前方法更有效的方法,该方法为每个单词运行一个新查询:
// All the necessary requires are here
class Word extends Model {
static async fetch() {
const words = await this.findAll({
limit: 10,
order: [Sequelize.literal('RANDOM()')],
attributes: ['lemma', 'definition'], // also a few other columns I need
});
const wordsWithOptions = await Promise.all(words.map(this.addOptions.bind(this)));
return wordsWithOptions;
}
static async addOptions(word) {
const options = await this.findAll({
order: [Sequelize.literal('RANDOM()')],
limit: 3,
attributes: ['lemma'],
where: {
partOfSpeech: word.dataValues.partOfSpeech,
lemma: { [Op.not]: word.dataValues.lemma },
},
});
return { ...word.dataValues, options: options.map((row) => row.dataValues.lemma) };
}
}
那么,有没有办法我可以使用原始SQL?续集怎么样?仍然可以帮助我的一件事是为我要尝试的操作取一个名字,以便我可以在Google上找到它。
编辑:我已经尝试了以下方法,至少到了某个地方:
const words = await this.findAll({
limit: 10,
order: [Sequelize.literal('RANDOM()')],
attributes: {
include: [[sequelize.literal(`(
SELECT lemma FROM words AS options
WHERE "partOfSpeech" = "options"."partOfSpeech"
ORDER BY RANDOM() LIMIT 1
)`), 'options']],
},
});
现在,这有两个问题。首先,当我需要三个选项时,我只有一个选项。但是如果查询中包含LIMIT 3
,我将得到:SequelizeDatabaseError: more than one row returned by a subquery used as an expression
。
第二个错误是,虽然上面的代码确实返回了某些内容,但它总是给出与选项相同的单词!我本来想用WHERE "partOfSpeech" = "options"."partOfSpeech"
来解决这个问题,但是后来我得到了SequelizeDatabaseError: invalid reference to FROM-clause entry for table "words"
。
所以,我如何告诉PostgreSQL“为结果中的每一行,添加一个包含三个引号数组的列,WHERE existingRow.partOfSpeech = wordToGoInTheArray.partOfSpeech?
”
答案 0 :(得分:0)
修订版
好吧,这似乎是一个不同的问题,也许应该这样发布,但是...
主要技术保持不变。加入而不是子选择。区别在于生成引理列表,然后将该引理列表传递到初始查询中。在一个单一的这可能会令人讨厌。
作为单个声明(实际上这还不错):
select w.lemma, w.defination, string_to_array(string_agg(o.defination,','), ',') as options
from words w
join lateral
(select defination
from words o
where o.part_of_speech = w.part_of_speech
and o.lemma != w.lemma
order by random()
limit 3
) o on 1=1
where w.lemma in( select lemma
from words
order by random()
limit 4 --<<< replace with parameter
)
group by w.lemma, w.defination;
另一种方法是构建一个小的SQL函数来随机选择指定数量的引理。此选择通过管道传递到(重命名的)函数中。
create or replace
function exam_lemma_definition_options(lemma_array_in text[])
returns table (lemma text
,definition text
,option text[]
)
language sql strict
as $$
select w.lemma, w.definition, string_to_array(string_agg(o.definition,','), ',') as options
from words w
join lateral
(select definition
from words o
where o.part_of_speech = w.part_of_speech
and o.lemma != w.lemma
order by random()
limit 3
) o on 1=1
where w.lemma = any(lemma_array_in)
group by w.lemma, w.definition;
$$;
create or replace
function exam_lemmas(num_of_lemmas integer)
returns text[]
language sql
strict
as $$
select string_to_array(string_agg(lemma,','),',')
from (select lemma
from words
order by random()
limit num_of_lemmas
) ll
$$;
使用这种方法,您的调用代码可减少对一条SQL语句的需求:
select *
from exam_lemma_definition_options(exam_lemmas(4))
order by lemma;
这允许您指定要选择的引理数(在本例中为4),仅受Words表中的行数限制。参见修订后的fiddle。
原始
无需使用子选择来获得选项字,而只需加入。
select w.lemma, w.definition, string_to_array(string_agg(o.definition,','), ',') as options
from words w
join lateral
(select definition
from words o
where o.part_of_speech = w.part_of_speech
and o.lemma != w.lemma
order by random()
limit 3
) o on 1=1
where w.lemma = any(array['Romanus', 'domus'])
group by w.lemma, w.definition;
请参见fiddle。显然,由于选择了random(),因此不必产生与您的问题相同的选项。但是它将获得匹配的词性。我将把您的源语言翻译交给您;或者您可以使用function选项并将SQL简化为简单的“选择*”。