如何检索与单个 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<int, Teacher>
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 匹配的多个值的主要内容,如果未能解决你的问题,请参考以下文章