Requisitos previos del curso compuesto (uno o más de a, b, c y ya sea xey, así como el estilo z)

Gracias a todos por la contribución, especialmente durante las horas de cierre de la recompensa, ha sido increíblemente útil.

Esta es una pregunta de seguimiento para Seleccionar cursos que están completamente satisfechos por una list dada de requisitos previos , y además explica la situación. Definitivamente se recomienda leer para ayudar a comprender mejor esta cuestión. (Los cursos y las asignaturas son entidades distintas, las materias solo son requisitos previos para los cursos y no es necesario que sean requisitos previos para otras asignaturas; piense en asignaturas de la escuela secundaria que conducen a posibles cursos universitarios)

Tengo mi database presentada como tal.

Prerequisite: +---------------+---------------+ | Id | Name | (Junction table) |---------------|---------------| CoursePrerequisites: | 1 | Maths | +---------------+---------------+ | 2 | English | | Course_FK | Prerequisite_FK | 3 | Art | |---------------|---------------| | 4 | Physics | | 1 | 1 | | 5 | Psychology | | 1 | 2 | +-------------------------------+ | 2 | 3 | | 2 | 5 | Course: | 5 | 4 | +---------------+---------------+ +---------------v---------------+ | Id | Name | |---------------|---------------| | 1 | Course1 | | 2 | Course2 | | 3 | Course3 | | 4 | Course4 | | 5 | Course5 | +---------------v---------------+ 

y he estado haciendo uso de la siguiente consulta:

 SELECT Course.id, course.Name, GROUP_CONCAT(DISTINCT Prerequisite.Name) AS 'Prerequisite Name(s)' FROM Course LEFT JOIN CoursePrerequisites ON Course.id = CoursePrerequisites.Course_FK LEFT JOIN Prerequisite ON Prerequisite.id = CoursePrerequisites.Prerequisite_FK WHERE NOT EXISTS (SELECT 1 FROM CoursePrerequisites WHERE Course.id = CoursePrerequisites.Course_FK AND CoursePrerequisites.Prerequisite_FK NOT IN (SELECT Prerequisite.id FROM Prerequisite Where Name = 'Art' OR Name = 'English' OR Name = 'Psychology'')) GROUP BY Course.id; 

Lo cual funciona bien para seleccionar cursos que están exactamente llenos por sus requisitos previos.

Sin embargo, he llegado a un obstáculo tratando de organizar la database de tal manera que es capaz de representar cursos con requisitos previos compuestos. Por ejemplo, un curso puede requerir inglés, matemáticas y arte o psicología. Otro ejemplo puede ser requisitos previos de inglés y dos de física, psicología, arte, etc.

¿Cuál sería una forma adecuada de estructurar la database para manejar estos types de requisitos previos? (Intenté hacer algunas búsquedas, pero no pude encontrar nada (editar: encontré esto, pero no fue útil: Modelar cursos y requisitos previos en la database ) y ¿cómo modificaría la consulta anterior para devolver solo los cursos que tienen cumplidos al less sus requisitos previos?

Para aclarar: dada una list de temas (de la tabla de requisitos previos), deseo devolver una list de los cursos que serían elegibles para esas materias. En el esquema actual de la database, dado Matemáticas, Inglés, Arte y Física, los cursos devueltos deben ser Curso1 y Curso5 (y NO Curso2 – tiene prerrequisitos Arte y Psicología, el último de los cuales no está satisfecho con la input dada) según lo estipulado por el tabla de unión Deseo ampliar la complejidad de los requisitos previos de un curso desde un simple 'AND' (Course1 requiere Maths AND English) a algo que pueda manejar 'OR' / One of x desde un set de y (p. Ej. Course1 ahora requiere English, Maths AND One o más de Arte o Psicología).

Edición de progreso:

He estado pensando en ampliar la tabla de unión con algunas columnas adicionales para 'al less uno de los siguientes' y 'al less dos de', etc., así como otra columna para 'todos' y colocar los requisitos previos en una estructura de esa manera. ¿Es esta una manera sensata de hacer esto y lo que sería una consulta eficiente en MySQL para consultar para encontrar cursos elegibles dada una list de temas?

Progreso:

Kuba Wyrostek ha sugerido a continuación enumerar todas las combinaciones de requisitos previos para cada curso en sets distintos. Si bien esto funcionaría, necesito hacer esto por ~ 6k filas, cada una con muchas enumeraciones. ¿Hay una manera más eficiente de lograr esto?

En mi opinión, la conjunción y la disyunción de modelado en una tabla siempre es incómoda y conduce a la violación de la forma normal o a la incapacidad de pnetworkingecir cuántas autoinscripciones son necesarias. Lo que entiendo es que sus requisitos previos generalmente pueden expressse siempre como alternativas de conjunciones. Entonces lo siguiente:

 Math AND English AND (Physics1 OR Physics2) 

puede expressse también como:

 (Math AND English AND Physics1) OR (Math AND English AND Physics2) 

Esto lleva a una conclusión, que probablemente necesite una tabla intermedia que describa los sets de requisitos previos . Un curso está disponible cuando cualquiera de los sets tiene éxito, mientras que el set es exitoso cuando se completan todos los sujetos del set.

Entonces la estructura puede verse así:

  Prerequisite: +---------------+---------------+ | Id | Name | |---------------|---------------| PrerequisiteSets: | 1 | Maths | +---------------+---------------+ | 2 | English | | SetNumber | Prerequisite_FK | 3 | Art | |---------------|---------------| | 4 | Physics | | 1 | 1 | | 5 | Psychology | | 1 | 2 | +-------------------------------+ | 1 | 4 | | 2 | 1 | | 2 | 2 | Course: | 2 | 5 | +---------------+---------------+ +---------------v---------------+ | Id | Name | |---------------|---------------| | 1 | Course1 | | 2 | Course2 | | 3 | Course3 | | 4 | Course4 | | 5 | Course5 | +---------------v---------------+ CoursePrerequisite: +---------------+---------------+ | Course_FK | SetNumber | |---------------|---------------| | 5 | 1 | | 5 | 2 | +---------------v---------------+ 

Un ejemplo Course5 se puede satisfacer con SetNumber 1 (Matemáticas, Inglés, Física) o SetNumber 2 (Matemáticas, Inglés, Psicología).

Lamentablemente, ahora es demasiado tarde para que pueda ayudarte con las consultas exactas, pero en caso de que lo necesites, puedo extender mi respuesta mañana. Buena suerte! 🙂

EDITAR

Para generar consultas, comenzaría con la observación, ese set particular se corresponde, cuando todos los requisitos previos en el set son un subset de requisitos previos dados. Esto lleva a la condición de que el número de requisitos previos distintos en el set debe coincidir con el número de requisitos previos en este set que también están en el set dado. Básicamente (suponiendo que SetNumber-Prerequisite_FK es un par único en la tabla):

 select SetNumber, count(Prerequisite_FK) as NumberOfRequinetworking, sum(case when Prerequisite.Name in ('Math','English','Art') then 1 else 0 end) as NumberOfMatching from PrerequisiteSets inner join Prerequisite on PrerequisiteSets.Prerequisite_FK = Prerequisite.ID group by SetNumber having count(Prerequisite_FK) = sum(case when Prerequisite.Name in ('Math','English','Art') then 1 else 0 end) 

Ahora, get Cursos finales se networkinguce a get todos los cursos, que al less se encuentra un número determinado en los resultados de la consulta anterior. Comenzando así (definitivamente se puede express mejor y optimizado con combinaciones, pero la idea general es la misma):

 select Id, Name from Course where Id in (select Course_FK from CoursePrerequisite where SetNumber in ( -- insert query from above (but only first column: SetNumber, skip the two latter) ) as MatchingSets ) as MatchingCourses 

Kuba Wyrostek ha sugerido a continuación enumerar todas las combinaciones de requisitos previos para cada curso en sets distintos. Si bien esto funcionaría, necesito hacer esto por ~ 6k filas, cada una con muchas enumeraciones. ¿Hay una manera más eficiente de lograr esto?

Almacenar sets es una elección obvia, estoy de acuerdo con Kuba. Pero sugiero un enfoque un poco diferente:

 prereqs: courses: +------+------------+ +------+------------+ | p_id | Name | | c_id | Name | |------|------------| |------|------------| | 1 | Math | | 1 | Course1 | | 2 | English | | 2 | Course2 | | 3 | Art | | 3 | Course3 | | 4 | Physics | | 4 | Course4 | | 5 | Psychology | | 5 | Course5 | +------+------------+ +------+------------+ compound_sets: compound_sets_prereqs: +-------+-------+-------+ +-------+-------+ | s_id | c_id | cnt | | s_id | p_id | |-------|-------|-------| |-------|-------| | 1 | 1 | 1 | | 1 | 1 | | 2 | 1 | 2 | | 1 | 2 | | 3 | 2 | 1 | | 2 | 3 | | 4 | 2 | null | | 2 | 4 | | 5 | 3 | null | | 2 | 5 | +-------+-------+-------+ | 3 | 1 | | 3 | 4 | | 4 | 1 | | 4 | 2 | | 5 | 2 | | 5 | 3 | +-------+-------+ 

La columna "cnt" anterior almacena el número mínimo de coincidencias requeridas, el valor NULL significa que todos los requisitos previos deben coincidir. Entonces en mi ejemplo tenemos los siguientes requisitos:

Curso1: (Matemáticas o Inglés) y (al less dos de Arte, Física y Psicología)
Curso2: (Matemáticas o Física) y (tanto Matemáticas como Inglés)
Curso 3: inglés y arte

Aquí está el SQL:

 select t.c_id , c.name from ( select c_id , sets_cnt -- flag the set if it meets the requirements , case when matched >= min_cnt then 1 else 0 end flag from ( select c.c_id , cs.s_id -- the number of matched prerequisites , count(p.p_id) matched -- if the cnt is null - we need -- to match all prerequisites , coalesce( cnt, count(csp.p_id) ) min_cnt -- the total number of sets the course has , ( select count(1) from compound_sets t where t.c_id = c.c_id ) sets_cnt from courses c join compound_sets cs on cs.c_id = c.c_id join compound_sets_prereqs csp on cs.s_id = csp.s_id left join ( select p_id from prereqs p -- this data comes from the outside where p.name in ( 'Physics', 'English', 'Math', 'Psychology' ) ) p on csp.p_id = p.p_id group by c.c_id, cs.s_id, cs.cnt ) t ) t , courses c where t.c_id = c.c_id group by t.c_id, c.name, sets_cnt -- check that all sets of this course meet the requirements having count( case when flag = 1 then 1 else null end ) = sets_cnt 

Este es un método de cortar y pegar de uno de mis laboratorios de capacitación para SQL personalizado, espero que sea el correcto, no puedo probarlo ahora, suena similar a tu tarea.

Solo uso pizzas y aderezos, generalmente lo hago antes del almuerzo 🙂

 CREATE TABLE Pizzas (Pizza# INTEGER NOT NULL PRIMARY KEY, PizzaName VARCHAR(30) NOT NULL UNIQUE ); INSERT INTO Pizzas VALUES(1, 'Margherita') ;INSERT INTO Pizzas VALUES(2, 'Salami') ;INSERT INTO Pizzas VALUES(3, 'Prosciutto') ;INSERT INTO Pizzas VALUES(4, 'Funghi') ;INSERT INTO Pizzas VALUES(5, 'Hawaii') ;INSERT INTO Pizzas VALUES(6, 'Calzone') ;INSERT INTO Pizzas VALUES(7, 'Quattro Stagioni') ;INSERT INTO Pizzas VALUES(8, 'Marinara') ;INSERT INTO Pizzas VALUES(9, 'Vegetaria') ;INSERT INTO Pizzas VALUES(10, 'Diavola') ;INSERT INTO Pizzas VALUES(11, 'Tonno') ;INSERT INTO Pizzas VALUES(12, 'Primavera') ;INSERT INTO Pizzas VALUES(13, 'Gorgonzola') ;INSERT INTO Pizzas VALUES(14, 'Fantasia') ;INSERT INTO Pizzas VALUES(15, 'Quattro Formaggi') ;INSERT INTO Pizzas VALUES(16, 'Napolitane') ;INSERT INTO Pizzas VALUES(17, 'Duplicato') ; CREATE TABLE Toppings (Topping# INTEGER NOT NULL PRIMARY KEY, Topping VARCHAR(30) NOT NULL UNIQUE ); INSERT INTO Toppings VALUES(1, 'Tomatoes') ;INSERT INTO Toppings VALUES(2, 'Mozzarella') ;INSERT INTO Toppings VALUES(3, 'Salami') ;INSERT INTO Toppings VALUES(4, 'Mushrooms') ;INSERT INTO Toppings VALUES(5, 'Chillies') ;INSERT INTO Toppings VALUES(6, 'Pepper') ;INSERT INTO Toppings VALUES(7, 'Onions') ;INSERT INTO Toppings VALUES(8, 'Garlic') ;INSERT INTO Toppings VALUES(9, 'Olives') ;INSERT INTO Toppings VALUES(10, 'Capers') ;INSERT INTO Toppings VALUES(11, 'Tuna') ;INSERT INTO Toppings VALUES(12, 'Squid') ;INSERT INTO Toppings VALUES(13, 'Pineapple') ;INSERT INTO Toppings VALUES(14, 'Spinach') ;INSERT INTO Toppings VALUES(15, 'Scallop') ;INSERT INTO Toppings VALUES(16, 'Ham') ;INSERT INTO Toppings VALUES(17, 'Gorgonzola') ;INSERT INTO Toppings VALUES(18, 'Asparagus') ;INSERT INTO Toppings VALUES(19, 'Fried egg') ;INSERT INTO Toppings VALUES(20, 'Anchovies') ;INSERT INTO Toppings VALUES(21, 'Corn') ;INSERT INTO Toppings VALUES(22, 'Artichock') ;INSERT INTO Toppings VALUES(23, 'Seafood') ;INSERT INTO Toppings VALUES(24, 'Brokkoli') ;INSERT INTO Toppings VALUES(25, 'Anchovis') ;INSERT INTO Toppings VALUES(26, 'Partablen') ;INSERT INTO Toppings VALUES(27, 'Goat cheese') ; CREATE TABLE PizzaToppings (Pizza# INTEGER NOT NULL, Topping# INTEGER NOT NULL, UNIQUE (Pizza#, Topping#) ) PRIMARY INDEX(Pizza#); INSERT INTO PizzaToppings VALUES(1, 1) ;INSERT INTO PizzaToppings VALUES(1, 2) ;INSERT INTO PizzaToppings VALUES(2, 1) ;INSERT INTO PizzaToppings VALUES(2, 2) ;INSERT INTO PizzaToppings VALUES(2, 3) ;INSERT INTO PizzaToppings VALUES(3, 1) ;INSERT INTO PizzaToppings VALUES(3, 2) ;INSERT INTO PizzaToppings VALUES(3, 16) ;INSERT INTO PizzaToppings VALUES(4, 1) ;INSERT INTO PizzaToppings VALUES(4, 2) ;INSERT INTO PizzaToppings VALUES(4, 4) ;INSERT INTO PizzaToppings VALUES(5, 1) ;INSERT INTO PizzaToppings VALUES(5, 2) ;INSERT INTO PizzaToppings VALUES(5, 13) ;INSERT INTO PizzaToppings VALUES(5, 16) ;INSERT INTO PizzaToppings VALUES(6, 1) ;INSERT INTO PizzaToppings VALUES(6, 2) ;INSERT INTO PizzaToppings VALUES(6, 4) ;INSERT INTO PizzaToppings VALUES(6, 11) ;INSERT INTO PizzaToppings VALUES(6, 22) ;INSERT INTO PizzaToppings VALUES(7, 1) ;INSERT INTO PizzaToppings VALUES(7, 2) ;INSERT INTO PizzaToppings VALUES(7, 4) ;INSERT INTO PizzaToppings VALUES(7, 6) ;INSERT INTO PizzaToppings VALUES(7, 16) ;INSERT INTO PizzaToppings VALUES(8, 1) ;INSERT INTO PizzaToppings VALUES(8, 2) ;INSERT INTO PizzaToppings VALUES(8, 8) ;INSERT INTO PizzaToppings VALUES(8, 9) ;INSERT INTO PizzaToppings VALUES(8, 12) ;INSERT INTO PizzaToppings VALUES(8, 15) ;INSERT INTO PizzaToppings VALUES(8, 16) ;INSERT INTO PizzaToppings VALUES(8, 23) ;INSERT INTO PizzaToppings VALUES(9, 1) ;INSERT INTO PizzaToppings VALUES(9, 2) ;INSERT INTO PizzaToppings VALUES(9, 5) ;INSERT INTO PizzaToppings VALUES(9, 6) ;INSERT INTO PizzaToppings VALUES(9, 7) ;INSERT INTO PizzaToppings VALUES(9, 8) ;INSERT INTO PizzaToppings VALUES(9, 9) ;INSERT INTO PizzaToppings VALUES(9, 14) ;INSERT INTO PizzaToppings VALUES(9, 18) ;INSERT INTO PizzaToppings VALUES(10, 1) ;INSERT INTO PizzaToppings VALUES(10, 2) ;INSERT INTO PizzaToppings VALUES(10, 5) ;INSERT INTO PizzaToppings VALUES(10, 7) ;INSERT INTO PizzaToppings VALUES(10, 9) ;INSERT INTO PizzaToppings VALUES(10, 10) ;INSERT INTO PizzaToppings VALUES(11, 1) ;INSERT INTO PizzaToppings VALUES(11, 2) ;INSERT INTO PizzaToppings VALUES(11, 7) ;INSERT INTO PizzaToppings VALUES(11, 11) ;INSERT INTO PizzaToppings VALUES(12, 1) ;INSERT INTO PizzaToppings VALUES(12, 2) ;INSERT INTO PizzaToppings VALUES(12, 3) ;INSERT INTO PizzaToppings VALUES(12, 4) ;INSERT INTO PizzaToppings VALUES(13, 1) ;INSERT INTO PizzaToppings VALUES(13, 2) ;INSERT INTO PizzaToppings VALUES(13, 16) ;INSERT INTO PizzaToppings VALUES(13, 17) ;INSERT INTO PizzaToppings VALUES(13, 24) ;INSERT INTO PizzaToppings VALUES(14, 1) ;INSERT INTO PizzaToppings VALUES(14, 2) ;INSERT INTO PizzaToppings VALUES(14, 10) ;INSERT INTO PizzaToppings VALUES(14, 19) ;INSERT INTO PizzaToppings VALUES(14, 20) ;INSERT INTO PizzaToppings VALUES(14, 21) ;INSERT INTO PizzaToppings VALUES(15, 1) ;INSERT INTO PizzaToppings VALUES(15, 2) ;INSERT INTO PizzaToppings VALUES(15, 17) ;INSERT INTO PizzaToppings VALUES(15, 26) ;INSERT INTO PizzaToppings VALUES(15, 27) ;INSERT INTO PizzaToppings VALUES(16, 1) ;INSERT INTO PizzaToppings VALUES(16, 2) ;INSERT INTO PizzaToppings VALUES(16, 4) ;INSERT INTO PizzaToppings VALUES(16, 5) ;INSERT INTO PizzaToppings VALUES(16, 16) ;INSERT INTO PizzaToppings VALUES(17, 1) ;INSERT INTO PizzaToppings VALUES(17, 2) ;INSERT INTO PizzaToppings VALUES(17, 4) ;INSERT INTO PizzaToppings VALUES(17, 6) ;INSERT INTO PizzaToppings VALUES(17, 16) ; REPLACE VIEW PizzaView AS SELECT P.Pizza# ,P.PizzaName ,T.Topping FROM Pizzas P JOIN PizzaToppings PT ON P.Pizza# = PT.Pizza# JOIN Toppings Z ON PT.Topping# = T.Topping# ; /*** 1. Return all pizzas which are a superset of the searched toppings. *At least* ('tomaten', 'mozzarella', 'salami') and maybe additional toppings: Salami, Primavera ***/ /*** 1. ***/ SELECT Pizza# ,PizzaName ,COUNT(*) AS #Toppings FROM PizzaView WHERE Topping IN ('tomatoes', 'mozzarella', 'salami') GROUP BY 1,2 HAVING COUNT(*) = 3 ; /*** 2. Return all pizzas which are a subset of the searched toppings. *At most* toppings ('tomaten', 'mozzarella', 'salami'), but no other toppings: Salami, Margherita ***/ /*** 2. ***/ SELECT Pizza# ,PizzaName ,COUNT(*) AS #Toppings FROM PizzaView GROUP BY 1,2 HAVING SUM(CASE WHEN Topping IN ('tomatoes', 'mozzarella', 'salami') THEN 0 ELSE 1 END) = 0 ORDER BY #Toppings DESC ; /*** 3. Return all pizzas which are a exactly made of the searched toppings. *All toppings* ('tomaten', 'mozzarella', 'salami'), but no other toppings Salami ***/ /*** 3. ***/ SELECT Pizza# ,PizzaName ,COUNT(*) AS #Toppings FROM PizzaView GROUP BY 1,2 HAVING SUM(CASE WHEN Topping IN ('tomatoes', 'mozzarella', 'salami') THEN 1 ELSE -1 END) = 3 ORDER BY #Toppings ; /*** 4. Return all pizzas which are a superset of the searched toppings. *At least* toppings ('tomaten' and 'mozzarella') and ('olives' or 'capers') Diavola, Fantasia, Marinara, Vegetaria ***/ /*** 4. ***/ SELECT Pizza# ,PizzaName ,COUNT(*) AS #Toppings ,SUM(CASE WHEN Topping IN ('olives', 'capers') THEN 1 ELSE 0 END) AS #Optional FROM PizzaView GROUP BY 1,2 HAVING SUM(CASE WHEN Topping IN ('tomatoes', 'mozzarella') THEN 1 ELSE 0 END) = 2 AND #Optional >= 1 ORDER BY 4 DESC ; /*** 5. Return all pizzas which are a superset of the searched toppings. *At least* toppings ('tomatoes' and 'olives') and maybe additional toppings, but no 'capers' Marinara, Vegetaria ***/ /*** 5. ***/ SELECT Pizza# ,PizzaName ,COUNT(*) AS #Toppings FROM PizzaView GROUP BY 1,2 HAVING SUM(CASE WHEN Topping IN ('tomatoes', 'olives') THEN 1 WHEN Topping IN ('capers') THEN -1 ELSE 0 END) = 2 ORDER BY #Toppings DESC ; /*** Instead of a list of toppings a table with searched toppings ***/ CREATE SET TABLE searched (grp INTEGER NOT NULL, topping VARCHAR(30) NOT NULL ); DELETE FROM searched; INSERT INTO searched VALUES(1,'tomatoes'); INSERT INTO searched VALUES(1,'mozzarella'); INSERT INTO searched VALUES(1,'salami'); /*** 1. ***/ SELECT Pizza# ,PizzaName ,COUNT(*) AS #Toppings FROM PizzaView p JOIN searched g ON p.Topping = g.Topping GROUP BY 1,2 HAVING COUNT(*) = (SELECT COUNT(*) FROM searched) ; /*** 2. ***/ SELECT Pizza# ,PizzaName ,COUNT(*) AS #Toppings FROM PizzaView p LEFT JOIN searched g ON p.Topping = g.Topping GROUP BY 1,2 HAVING COUNT(*) = COUNT(g.Topping) ; /*** 3. ***/ SELECT Pizza# ,PizzaName ,COUNT(*) AS #Toppings FROM PizzaView p LEFT JOIN searched g ON p.Topping = g.Topping GROUP BY 1,2 HAVING SUM(CASE WHEN g.Topping IS NOT NULL THEN 1 ELSE -1 END) = (SELECT COUNT(*) FROM searched) ; 

Nunca necesité hacer # 4 / # 5 con esa tabla buscada , pero debería ser posible usando la lógica anterior.

Me gustaría modelar esto de forma ligeramente diferente de lo que han sugerido tanto Kuba como Dmitry, aunque ambos han proporcionado el marco general para escribir esta respuesta.

Me disculpo de antemano, voy a romper el idioma de su model existente ya que no creo que "Arte" y "Psicología" sean sus Prerrequisitos. Son simplemente cursos que se combinan formando una entidad de requisito previo. Así que he cambiado el nombre de esta tabla Temas .

Todos los models de datos se pueden describir como entidades y relaciones que se pueden describir sin una database física real. En este caso, tienes esta entidad desafiante, el Requisito previo . Como entidad, está representada por los sujetos dentro de ella y el número de materias requeridas de su set de cursos. Esto encajaría muy bien con un catálogo de cursos donde se podría decir, para un curso determinado, cuáles son sus requisitos previos en una sola línea por requisito previo ("Arte, Psicología: se requiere 1", "Arte y Psicología: todos obligatorios", etc. )

La primera pregunta es:

  • ¿El número de asignaturas requeridas de un prerrequisito dado es suficientemente único e idiosincrásico para cada curso, o se aplica "Arte o Psicología – 1 obligatoria" a una gran cantidad de cursos? ¿Cambia a menudo por cada curso o es relativamente estático?

Si se aplica a una gran cantidad de cursos o es relativamente estático, debería estar en la tabla de requisitos previos. Si cada set de prerrequisitos para un curso es relativamente dynamic, debe situarse en la tabla PrerrequisitoCurso. Por ahora asumiré lo primero.

Los temas reales son una relación de muchos a muchos (cada tema puede ser parte de muchos prerrequisitos, cada requisito previo puede tener muchos temas) y se deben modelar en una tabla de references cruzadas.

A partir de aquí, es obvio que "Arte y Psicología – elija 1" y "Arte y Psicología – ambos requeridos" son entidades únicas. Por lo tanto, determinaría claramente cada posible set de materias de requisito previo, incluida la cantidad de materias requeridas .

 Prerequisite ---------------------- PrerequisiteID NumberOfSubjectsRequinetworking Subject --------------- SubjectID Name PrerequisiteSubject -------------------- PrequisiteSubjectID PrerequisiteID SubjectID Course ------ CourseID CoursePrerequisite ------------------ CoursePrerequisiteID PrerequisiteID CourseID 

Observe cómo esto mejora (si puedo ser tan audaz) en el model de Dmitry al asegurar una list distinta de combinaciones de temas prerrequisitos y permitir requisitos previos como "Arte o Psicología – elegir 1" para ser reutilizados en todos los cursos. Esto es (en mi opinión, basado en mi comprensión de su model de datos) el modelado adecuado de un requisito previo. Considere el escenario donde "Art and Psychology – choose 1" se modifica para include también Speech para todos los cursos. Aquí podría insert una fila en un lugar (la tabla PrerequisiteSubject) y se aplicaría a todos los cursos sin alterar ninguno de los otros requisitos previos.

Otra ventaja está en la consulta: para un set determinado de asignaturas, obtenga los requisitos previos que un alumno determinado debería cumplir (suponiendo que SubjectTaken son las asignaturas que el alumno ha tomado):

  select case when count(1) >= ct then 1 else 0 end as PrerequisiteMet, p.PrerequisiteID from subjectstaken st left join [subject] s inner join Prerequisitesubject PS inner join Prerequisite P on PS.prerequisiteid = P.prerequisiteiD on S.subjectid = PS.subjectID on s.name = st.name group by p.PrerequisiteID, ct 

Y luego los cursos que el estudiante podría tomar:

 select courseid from prerequisitesmet pm right join prerequisitecourse pc on pc.prerequisiteid = pm.PrerequisiteID group by courseid having sum(prerequisitemet) >= count(1) 

De todos modos, todo este modelado realmente depende de tus entidades "reutilizables". Parece que un requisito previo debería ser una entidad reutilizable, pero podría estar equivocado.