简体   繁体   中英

How to retrieve multiple values matching a single SQL id

I'm trying to gather multiple "Class ID's" for a single "Teacher" using an SQL query inside a C# Web app. I've been able to successfully link the tables in the query but I'm only receiving one class taught per teacher, even when there are multiple classes taught by one teacher in the database.

Here's my code to generate the SQL query and post the information:

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;
            }

EDIT: Thank you for the help so far, I'm quite new to this So I appreciate the assistance. I've changed the syntax to an INNER JOIN but I'm still not getting the desired output; I want the output to be like this: "Mr Smith teaches Class A and Class B" where "Class A" and "Class B" are both fetched from the database. Here's my updated code:

 //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;
            }

Let us suppose you have two teachers:

1, John
2, Jane

Let us suppose you have 3 classes. John teaches Math+English, Jane teaches French

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

Your query here:

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

Conceptually does this first:

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

Then it filters according to what you've asked for, teacher id 1 in the teachers table:

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

Doing FROM a,b establishes no relation between the data sets at all.. it just combines everything from A with everything from B such that you get AxB number of rows out (6, in the case of 2 teachers and 3 classes)

You need to establish correlation between the datasets. That looks like this:

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

when the classes table has a TeacherId column that defines which teacher teaches the class. There are other kinds of joins that allow for eg teachers that don't teach any class, but start simple for now, and always follow this pattern when writing an SQL ( a INNER JOIN b ON column_from_a = column_from_b ) - never again write FROM a, b ; it has been unnecessary for about 30 years. If you're reading a tutorial that instructs you to write this way, throw it out and get a better one

The result from this query is:

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

Now that your join is fixed, let's examine the logic of what you're doing in your 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 is just a single object. There's no way it can hold more than one value. Every time the loop goes round you just overwite the existing teacher data with the next teacher. You need a collection of teachers as your results are going to have repeated teacher data in them:

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);
}

But wait.. We're still not done..

Because you specified a teacherID = 1, the teacher in the results (look at the example data I put for John above) is actually the same teacher repeated over and over, with a different class associated, so building this code like this isn't that useful perhaps..

There are multiple ways to fix this. One would be to run two separate queries. The other would be to use some fast lookup device, like a Dictionary, to keep track of if we've seen a particular Teacher before, and retrieve the one we already have rather than adding another copy of the same teacher details

I don't know how in-scope either of thse things are, but I'll leave it for you to ponder on with some discussion below.

Perhaps your classes should look like:

class Teacher{

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

}

class TaughtClass{

  string Name {get; set;}

}

And your code could first select * from teachers where id = 1 and then it could later select * from classes where teacherid = 1 , and fill up the TaughtClasses list on the one Teacher..

Even that approach is a bit awkward, when someone says "what if we have two teachers.. eg select * from teachers where name like 'j%' - it will return both john and jane..

That you can handle by pulling the results into a list of teacher - so you have 2 Teacher objects in your list, and you can then do a loop over the list to look up their Classes, something like (pseudocode):

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

   var reader = cmd.ExecuteQuery();

   //now do a loop here that reads TaughtClasses and fills up the t.Classes collection
}

You might regard that "run a query for the classes per teacher id" as inefficient (it's not actually that bad) and want to get all the relevant classes in one hit. That's doable too; consider:

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

After running a query for all teachers with a name like J% we can rerun the query above to get all of John's and janes classes in one hit. You'll need to use the arriving teacher id to sort out which local Teacher object gets which TaughtClasses assigned to their list


If this two queries idea is something disallowed (academic exercise?) take a look at a Dictionary<int, Teacher>

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

As you're looping over the teacher-classes joined query you can pull the teacher id and see if you've seen it before:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM