如何检索与单个 SQL id 匹配的多个值

Posted

技术标签:

【中文标题】如何检索与单个 SQL id 匹配的多个值【英文标题】:How to retrieve multiple values matching a single SQL id 【发布时间】:2021-12-29 15:38:21 【问题描述】:

我正在尝试使用 C# Web 应用程序中的 SQL 查询为单个“教师”收集多个“班级 ID”。我已经能够成功链接查询中的表,但我只收到每位教师教授的一门课程,即使数据库中有一位教师教授多门课程。

这是我生成 SQL 查询并发布信息的代码:

public Teacher FindTeacher(int TeacherId)
        
            mysqlConnection Conn = School.AccessDatabase();
            
            Conn.Open();

            MySqlCommand cmd = Conn.CreateCommand();

            cmd.CommandText = "Select * from Teachers, Classes where teachers.teacherid = " + TeacherId;

            MySqlDataReader ResultSet = cmd.ExecuteReader();

            Teacher SelectedTeacher = new Teacher();

            while (ResultSet.Read())
            
                int Id = Convert.ToInt32(ResultSet["teacherid"]);
                string TeacherFName = ResultSet["teacherfname"].ToString();
                string TeacherLName = ResultSet["teacherlname"].ToString();
                string TaughtClassName = ResultSet["classname"].ToString();
                string TaughtClassCode = ResultSet["classcode"].ToString();

                SelectedTeacher.TeacherId = Id;
                SelectedTeacher.TeacherFName = TeacherFName;
                SelectedTeacher.TeacherLName = TeacherLName;
                SelectedTeacher.TaughtClassCode = TaughtClassCode;
                SelectedTeacher.TaughtClassName = TaughtClassName;
            

编辑: 感谢您到目前为止的帮助,我对此很陌生所以我很感激您的帮助。 我已将语法更改为 INNER JOIN,但仍然没有得到所需的输出;我希望输出是这样的:“史密斯先生教 A 类和 B 类”,其中“A 类”和“B 类”都是从数据库中获取的。 这是我更新的代码:

 //Set up and define query for DB
            MySqlCommand cmd = Conn.CreateCommand();
            cmd.CommandText = "Select * from Teachers Join Classes on teachers.teacherid = classes.teacherid where teachers.teacherid=" + TeacherId;

            //Collect query result in a variable 
            MySqlDataReader ResultSet = cmd.ExecuteReader();

            //Create a variable in which to store the current teacher
            Teacher SelectedTeacher = new Teacher();

            //go through each row of the query result
            while (ResultSet.Read())
            
                int Id = Convert.ToInt32(ResultSet["teacherid"]);
                string TeacherFName = ResultSet["teacherfname"].ToString();
                string TeacherLName = ResultSet["teacherlname"].ToString();
                string TaughtClassName = ResultSet["classname"].ToString();
                string TaughtClassCode = ResultSet["classcode"].ToString();

                SelectedTeacher.TeacherId = Id;
                SelectedTeacher.TeacherFName = TeacherFName;
                SelectedTeacher.TeacherLName = TeacherLName;
                SelectedTeacher.TaughtClassCode = TaughtClassCode;
                SelectedTeacher.TaughtClassName = TaughtClassName;
            

【问题讨论】:

今日提示:切换到现代、明确的JOIN 语法。更容易编写(没有错误),更容易阅读(和维护),并且在需要时更容易转换为外连接。 该查询对两个表(所有教师与所有班级配对)进行交叉连接,然后将行减少到只有一位教师与所有班级配对。再看看结果。然后接受jarlh 的建议。 FROM table_a, table_b 语法应该被根除。 另外,您在所有 MySql 对象上都缺少 using 语句,并且您在这里有 SQL 注入,您应该改为参数化 【参考方案1】:

假设你有两位老师:

1, John
2, Jane

假设您有 3 个班级。约翰教数学+英语,简教法语

ClassId, ClassName, TeacherId
101, Math, 1     <-- john
102, English, 1  <-- john
103, French, 2   <-- jane

您的查询:

Select * from Teachers, Classes where teachers.teacherid = 1

概念上首先这样做:

1, John, 101, Math, 1
1, John, 102, English, 1
1, John, 103, French, 2
2, Jane, 101, Math, 1
2, Jane, 102, English, 1
2, Jane, 103, French, 2

然后它会根据您的要求进行过滤,教师表中的教师 ID 为 1:

1, John, 101, Math, 1
1, John, 102, English, 1
1, John, 103, French, 2
^
filter operated on this column

执行FROM a,b 根本不会在数据集之间建立任何关系。它只是将 A 中的所有内容与 B 中的所有内容结合起来,这样您就可以得到 AxB 行数(6,在 2 位教师和 3 个班级的情况下)

您需要在数据集之间建立相关性。看起来像这样:

SELECT * 
FROM
  teachers t
  INNER JOIN classes c ON t.teacherid = c.teacherid

当 classes 表有一个 TeacherId 列时,该列定义了哪位教师教授课程。还有其他类型的连接允许例如老师们不教任何课程,但现在从简单开始,并且在编写 SQL 时始终遵循这种模式 (a INNER JOIN b ON column_from_a = column_from_b) - 永远不要再写 FROM a, b;大约 30 年来它一直没有必要。如果您正在阅读指导您以这种方式编写的教程,请将其丢弃并获得更好的内容

这个查询的结果是:

1, John, 101, Math, 1
1, John, 102, English, 1

现在您的连接已修复,让我们检查一下您在 C# 中所做操作的逻辑:

while (ResultSet.Read())

    int Id = Convert.ToInt32(ResultSet["teacherid"]);
    string TeacherFName = ResultSet["teacherfname"].ToString();
    string TeacherLName = ResultSet["teacherlname"].ToString();
    string TaughtClassName = ResultSet["classname"].ToString();
    string TaughtClassCode = ResultSet["classcode"].ToString();

    SelectedTeacher.TeacherId = Id;
    SelectedTeacher.TeacherFName = TeacherFName;
    SelectedTeacher.TeacherLName = TeacherLName;
    SelectedTeacher.TaughtClassCode = TaughtClassCode;
    SelectedTeacher.TaughtClassName = TaughtClassName;

SelectedTeacher 只是一个对象。它不可能保存多个值。每次循环进行时,您只需用下一位老师覆盖现有的老师数据。您需要一组教师,因为您的结果中会包含重复的教师数据:

List<Teacher> selectedTeachers = new List<Teacher>();

while (ResultSet.Read())

    int id = Convert.ToInt32(ResultSet["teacherid"]);
    string teacherFName = ResultSet["teacherfname"].ToString();
    string teacherLName = ResultSet["teacherlname"].ToString();
    string taughtClassName = ResultSet["classname"].ToString();
    string taughtClassCode = ResultSet["classcode"].ToString();

    Teacher selectedTeacher = new Teacher();

    selectedTeacher.TeacherId = id;
    selectedTeacher.TeacherFName = teacherFName;
    selectedTeacher.TeacherLName = teacherLName;
    selectedTeacher.TaughtClassCode = taughtClassCode;
    selectedTeacher.TaughtClassName = taughtClassName;

    selectedTeachers.Add(selectedTeacher);

但是等等..我们还没有完成..

因为你指定了一个teacherID = 1,结果中的老师(看看我上面给约翰的示例数据)实际上是同一个老师一遍又一遍地重复,关联了不同的班级,所以像这样构建这段代码也许不是很有用..

有多种方法可以解决此问题。一种是运行两个单独的查询。另一种是使用一些快速查找设备,例如字典,来跟踪我们以前是否见过某个特定的老师,并检索我们已经拥有的那个,而不是添加相同老师详细信息的另一个副本

我不知道这两件事的范围有多大,但我会留给你思考下面的一些讨论。

也许你的类应该是这样的:

class Teacher

  string Name get; set;
  List<TaughtClass> Classes  get; set;  = new();



class TaughtClass

  string Name get; set;


您的代码可以先select * from teachers where id = 1,然后再select * from classes where teacherid = 1,并填写一个教师的TaughtClasses列表..

即使这种方法也有点尴尬,当有人说“如果我们有两个老师怎么办......例如select * from teachers where name like 'j%' - 它会同时返回 john 和 jane..

您可以通过将结果拉入教师列表来处理 - 因此您的列表中有 2 个教师对象,然后您可以在列表中执行循环以查找它们的类,例如(伪代码):

foreach(var t in selectedTeachers)
   cmd.CommandText = "SELECT * FROM TaughtClasses WHERE teacherId = @tid";
   cmd.Parameters.Clear();
   cmd.Parameters.AddWithValue("@tid", t.TeacherId);      //NB: addwithvalue is safe to use in mysql

   var reader = cmd.ExecuteQuery();

   //now do a loop here that loops over the reader, makes TaughtClass objects and fills up the t.Classes collection


您可能认为“对每个教师 ID 的班级进行查询”效率低下(实际上并没有那么糟糕),并希望一次获得所有相关的 TaughtClass 数据。这也是可行的;考虑:

SELECT c.* 
FROM
  teachers t
  INNER JOIN classes c ON t.teacherid = c.teacherid
WHERE
  t.name like `j%'

在对所有名称为 J% 的教师运行查询后,我们可以重新运行上述查询以一次性获取 John 和 Jane 的所有课程。这将使用教师和班级之间的连接(因为我们给出了教师姓名,但我们希望输出班级数据),并且只选择 c.* 这是班级数据。

您需要在课程数据中使用到达的教师 ID,以区分哪个本地教师对象获得分配给其列表的哪些 TaughtClass。这基本上将涉及搜索您已经制作的本地教师列表,并找到合适的,然后将 TaughtClass 添加到他们的列表中


如果这两个查询的想法是不允许的(学术练习?)看看Dictionary&lt;int, Teacher&gt;

var d = new Dictionary<int, Teacher>();

当您循环访问teacher-classes join 查询时,您可以提取教师 ID 并查看您以前是否见过它:

int id = Convert.ToInt32(ResultSet["teacherid"]);

if(!d.TryGetValue(id, out Teacher t))
  //teacher is not found locally in the dictionary, create a new teacher and add them

  string f = ResultSet["teacherfname"].ToString();
  string l = ResultSet["teacherlname"].ToString();

  d[id] = t = new Teacher() Id = id, FName = f, LName l ;



//at this point t is either the new teacher, or one you added previously

//you can now make a new TaughtClass and add it to t.Classes

哪种方法更好?嗯..棘手的问题,那里没有好的答案。多重查询方法可能会返回不相关的数据(如果有人在您下载 John 和 Jane 的时间之间添加了另一位名叫 Jim 的老师,然后根据获取课程的 LIKE 'j%' 开始选择他们的课程怎么办?如果你选择一个大块数据查询那么您不会遇到这个问题,但是您确实会下载大量重复数据。如果您使用数据库事务来使查询保持一致,那么您会使用大量服务器资源..

.. 没有一颗灵丹妙药,您可以选择马马虎虎中最好的一个。我认为他们在这个练习中都会没事的:D

【讨论】:

是的!这很好,但我认为这不是我的问题的解决方案 - 我实际上有另一个控制器,用于列出多个教师。 不错的答案!我认为您对多个教师的解决方案应该是多个班级,因为 op 是针对单个教师进行过滤。 我添加了关于在客户端重建分层数据的各种选项的更长讨论

以上是关于如何检索与单个 SQL id 匹配的多个值的主要内容,如果未能解决你的问题,请参考以下文章

通过比较多个表条目从单个表中检索数据的 SQL 语句

具有单个键的多个值的STL集合

MongoDB - 查询匹配数组值的多个文档;

如何在单个查询中计算与基表结果匹配的多个表的记录?

Spring jdbc中多个表数据到单个bean的映射

如何在单个字段或列中连接多个值? (MySQL)