将同一表中的其他记录数组添加到每个记录

时间:2020-09-10 14:32:06

标签: sql postgresql sequelize.js

我的项目是拉丁语言学习应用程序。我的数据库在“单词”表中包含了我正在教的所有单词。它具有 lemma (单词的主要形式)以及用户需要学习的定义和其他信息。

我一次显示一个单词,让他们猜测/记住它的含义。显示正确的单词以及一些错误的单词,例如:

  • 罗马尼亚是什么意思?希腊语-/罗马/-腓尼基语-野蛮人
  • domus 是什么意思? / house /-马-墙-参议员

错误的选项是从同一张表中随机抽取的,并且必须来自与正确单词相同的词性(形容词,名词...);但是我只对他们的引理感兴趣。我的返回值看起来像这样(省略了一些属性):

[
  { 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?

1 个答案:

答案 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简化为简单的“选择*”。