SQL server INSERT Using WITH - sql-server

I have two tables
student and course_log
I have Students already in students table, I need to insert into course_log for each student a standard course (UNI 101) that all have to take or have take.
So if there are 10 students there will be 10 entries in the course_log table for each student with course UNI 101
I was thinking of using a with clause but it does not work that way i think
USE [university]
GO
with studentid as ( select id as stdID from student)
INSERT INTO [dbo].[course_log]
([course_name]
,[course_code]
,[STUDENT_ID])
VALUES
('STANDARD COURSE FOR ALL STUDENTS'
,'UNI 101'
,studentid.stdID)
The other one that was coming to my mind was to use a while loop ( as MSSQL does not have for loop) but i was actually thinking if the above would be much easier and doable.

No need of WITH clause
INSERT INTO [dbo].[course_log]
([course_name]
,[course_code]
,[STUDENT_ID])
SELECT
'STANDARD COURSE FOR ALL STUDENTS'
,'UNI 101'
,stdID
FROM student

Related

SSIS - Split multiple columns into single rows

Below is a very small example of the flat table I need to split. The first table is a Lesson table which has the ID, Name and Duration. The second table is a student table which only has the Student Name as a PK. And the third table will be a Many to Many of Lesson ID and Student Name.
Lesson Id
Lesson Name
Lesson Duration
Student1
Student2
Student3
Student4
1
Maths
1 Hour
Jean
Paul
Jane
Doe
2
English
1 Hour
Jean
Jane
Doe
I don't know how, using SSIS, I can assign Jean, Paul, Jane and Doe to their own tables using the Student 1, 2, 3 and 4 columns. When I figure this out, I imagine I can use the same logic to map the Lesson ID and columns to the third Many to Many table?
How do I handle duplicate entries, for example Jean Jane and Doe already exist from the first row so they do not need to be added to the Students table.
I assume I use a conditional split to skip null values? For example Student4 on the second row is Null.
Thanks for the assistance.
Were it me, I would design this as 3 data flows.
Data flow 1 - student population
Since we're assuming the name is what makes a student unique, we need to build a big list of the unique names.
SELECT D.*
FROM
(
SELECT S.Student1 AS StudentName
FROM dbo.MyTable AS S
UNION
SELECT S.Student2 AS StudentName
FROM dbo.MyTable AS S
UNION
SELECT S.Student3 AS StudentName
FROM dbo.MyTable AS S
UNION
SELECT S.Student4 AS StudentName
FROM dbo.MyTable AS S
)D
WHERE D.StudentName IS NOT NULL
ORDER BY D.StudentName;
The use of UNION in the query will handle deduplication of data and we wrap that in a derived table to filter the NULLs.
I add an explicit order by not that it's needed but since I'm assuming you're using the name as the primary key, let's avoid sort operation when we land the data.
Add an OLE DB Source to your data flow and instead of picking a table in the drop down, you'll use the above query.
Add an OLE DB Destination to the same data flow and connect the two. Assuming your target table looks something like
CREATE TABLE dbo.Student
(
StudentName varchar(50) NOT NULL CONSTRAINT PK__dbo__Student PRIMARY KEY(StudentName)
);
Data Flow 2 - Lessons
Dealers choice here, you can either write the query or just point at the source table.
A very good practice to get into with SSIS is to only bring the data you need into the buffers so I would write a query like
SELECT DISTINCT S.[Lesson Id], S.[Lesson Name], S.[Lesson Duration]
FROM dbo.MyTable AS S;
I favor a distinct here as I don't know enough about your data but if it were extended and a second Maths class was offered to accommodate another 4 students, it might be Lesson Id 1 again. Or it might be 3 as it indicates course time or something else.
Add an OLE DB Destination and land the data.
Data Flow 3 - Many to Many
There's a few different ways to handle this. I'd favor the lazy way and repeat our approach from the first data flow
SELECT D.*
FROM
(
SELECT S.Student1 AS StudentName, S.[Lesson Id]
FROM dbo.MyTable AS S
UNION
SELECT S.Student2 AS StudentName, S.[Lesson Id]
FROM dbo.MyTable AS S
UNION
SELECT S.Student3 AS StudentName, S.[Lesson Id]
FROM dbo.MyTable AS S
UNION
SELECT S.Student4 AS StudentName, S.[Lesson Id]
FROM dbo.MyTable AS S
)D
WHERE D.StudentName IS NOT NULL
ORDER BY D.StudentName;
And then land in your bridge table with an OLE DB Destination and be done with it.
If this has been homework/an assignment to have you learn the native components...
Do keep with the 3 data flow approach. Trying to do too much in one go is a recipe for trouble.
The operation of moving wide data to narrow data is an Unpivot operation. You'd use that in the Student and bridge table data flows but honestly, I think I've used that component less than 10 times in my career and/or answering SSIS questions here and I do a lot of that.
If the Unpivot operation generates a NULL, then yes, you'd likely want to use a Conditional Split to filter those rows out.
If your reference tables were more complex, then you'd likely be adding a Lookup component to your bridge table population step to retrieve the surrogate key.

Query on at least and not present in SQL table

I have two queries:
Query the id number of the students who at least enrolled subj1 and subj2 courses (I do not know how to code for at least)
Query the id number, name, and age of the students who did not enroll subj2 course.
I coded something as below - which returns an empty table even though I should get some values.
Select sno, sname, age
from student
where not exists (select cno from course where cno ='C2');
I'm assuming you have a table connecting students with courses in a many-to-many, so that each student can enroll in more than one course and each course can contain multiple students.
So for the sake of this example, let's call it StudentsToCourses.
This table should contain the student id and the course id, and it's primary key should contain the combination of both it's columns.
So the first query would be something like (to get student numbers enrolled into at least one of the two courses):
SELECT sno
FROM StudentToCourses
WHERE cno IN ('C1', 'C2')
or this (to get students enrolled into both courses):
SELECT sno
FROM StudentToCourses
WHERE cno IN ('C1', 'C2')
GROUP BY sno
HAVING COUNT(DISTINCT cno) = 2
Note that the subquery in the EXISTS operator is correlated to the main query using the student number.
The second query is almost the same as the first one, except instead of IN you use =, and instead of EXISTS you use NOT EXISTS.
Since this seems like a homework question, I'll leave it up to you to write the code, otherwise you will not learn anything from this.

Archiving data from Table1 to Table2 upon condition | Sql Server 2017 Express

I have a database with two tables:
Table Student with the following
columns:
StudentID int identity,
StudentFN,
StudentLN,
Active bit,
EnrollmentDate
Table ArchivedStudent with the following columns:
ArvchivedStudentID int identity,
StudentID int,
StudentFN,
StudentLN,
WithdrawalDate getdate(),
ReasonDropped
In the long run, I'd like to schedule automatic updates for the table AcrchivedStudent and move the data from columns StudentID, StudentFN and StudentLN from table Student to table ArchnivedStudent when column Active changes from 1 (true) to 0 (false).
Here's my start up script that is not working:
update [as]
set [as].StudentID = s.StudentID,
[as].StudentFN = s.StudentFN,
[as].StudentLN = s.StudentLN
from ArchivedStudent [as]
inner join Student s
on [as].StudentID = s.StudentID
where s.Active = 0
go
The issue is that it does not return any results.
Once I'll be able to update table ArchivedStudent, I'd like to delete data of the students whose Active status changed to 0 in the Student table.
Your question still isn't very clear on the process. For example, do you want to allow the student to be deactivated for a certain period of time before they are moved to the archive table or do you want the student to be immediately moved to the archived table once the student is deactivated?
If the latter, this is much easier:
INSERT INTO ArchivedStudent (StudentId, StudentFn, StudentLn, WithdrawalDate)
SELECT S.StudentId, S.StudentFn, S.StudentLn, GETDATE()
FROM Student S
WHERE StudentId = ?
DELETE FROM Student WHERE StudentId = ?
If the former, then that is more challenging and we will require more detail.
Update 1:
To set the Withdrawal date based off a calculated value, use the following:
INSERT INTO ArchivedStudent (StudentId, StudentFn, StudentLn, WithdrawalDate)
SELECT S.StudentId, S.StudentFn, S.StudentLn, CAST(DATEADD(D,14,GETDATE()) AS DATE)
FROM Student S
WHERE StudentId = ?
Note 1: In DATEADD(), use a positive value for future dates and use a negative value for past dates. You can remove the DATE CAST if you need the actual time in addition to the date.
Note 2: The DELETE script posted in the original answer still stands.
You need a trigger to do it :
CREATE TRIGGER ArchiveStudent ON Student
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO ArchivedStudent (StudentID, StudentFN, StudentLN)
SELECT
StudentID
, StudentFN
, StudentLN
FROM
Student
WHERE
Active = 0
DELETE FROM Student
WHERE
Active = 0
END
However, your approach it's simple, and risky at the same time. For instance, if someone made a student inactive by mistake, then the trigger will immediately insert that student into archive table then deleted. Surely you can retrieve it by many ways such as deleted, inserted tables or even get the max id of archive table, but why you put yourself in this situation in the first place?. This is one of many general issues could be experienced by the current approach. A better approach is to actually add more versioning or historian methods for the tables, and make the archives run either from SQL Job or a store procedure on a fixed dates rather than triggers. this would give you a scheduled and controlled data archiving.
You can even add a historian columns which will store the value of active column and the date of the change. Then, use trigger or store procedure to do it for you (or even a computed column with a generic scalar function that will be reused on multiple tables). for instance, if the student is inactive for 5 business days, then archive it and delete it from the table.
You could use a TRIGGER AFTER UPDATE on the Student table.
This trigger would:
- react only on UPDATE,
- transfer Student to ArchiveStudent, when Active is set to 0
- and set WithdrawalDate to 2 weeks from today.
Trigger:
CREATE TRIGGER [Student_Changed]
ON Student
AFTER UPDATE
AS
BEGIN
-- Archive
INSERT INTO ArchiveStudent
(StudentID, StudentFN, StudentLN, WithdrawalDate)
SELECT
DELETED.StudentID
,DELETED.[StudentFN]
,DELETED.[StudentLN]
,DATEADD(day, 14, GETDATE()) -- 2 Weeks from today
FROM DELETED
WHERE DELETED.Active = 1
-- Delete archived
DELETE FROM Student
WHERE StudentID = (SELECT DELETED.[StudentID] FROM DELETED)
AND Active = 0
END;
DEMO:
You can take a look at the SQL Fiddle solution here.
There are a number of solutions here that all appear partially correct, but with some issues. Your initial update of your archive table will not insert into the archive table, only update an existing row. And since you are trying to join between the live table and the archive table, you will get no results - well, no updates since an update statement doesn't produce "results" as such anyway.
So as other have said you would use two statements - one an insert statement and one a delete. I would tend to be on the careful side and make sure that I a) dont get duplicates in my archive table and b) dont delete from live before I am sure its made it into the archive. So the two statements would be:
insert archivestudent(...fieldlist...)
select * from student
where active=0
and not exists(select * from archivestudent where archivestudent.studentid=student.studentid)
delete student
where active=0
and exists(select * from archivestudent where archivedstudent.studentid=student.studentid)
You can then run this code whenever you wish, schedule it as a job to run each night, whatever makes sense in your app.
If, on the other hand you want to immediately run then a trigger is the way to go. Be aware though that triggers are set-based operations, meaning that the trigger runs once for all rows affected by an update. This means that the solution proposed by #Milan will fail if the triggering update affects more than one row, because the clause WHERE StudentID = (SELECT DELETED.[StudentID] FROM DELETED) will return more one value. An example might be update student set active=0 where enrolmentdate<'2017-01-01'
You should always join to the internal tables exposed inside a trigger, in this case the DELETED table
delete student
from deleted
join student on student.studentid=deleted.studentid
where active=0
I'd still be tempted to add the where exists/not exists clauses inside the trigger as well just to make it more error-proof.
You need two queries:
Insert
Insert into archivedstudent (studentid, student, studentln) select studentid, studentfn, studentln from student where active=0 and studentid not in (select studentid from archivedstudent);
And the delete
Delete from student where studentid in (select studentid from archivedstudent);
What you need is a trigger.SQL Server Trigger After Update for a Specific Value However you should be careful with triggers on large amounts of data, they can hurt performance.

Postgresql: One table dependent on another

I have three tables in my Database - Students, Books and Books2student.
Students table has StudentID, First Name, Last Name.
Books table has ISBN no, name, no. of copies available.
I want to create a third table Books2Students which has StudentID, books issued, issue date, due date.
How can I create the third table such that if I insert a record in it the no. of copies in Books table should decrease.
If I delete a Student from student table and that student has taken a book then no. of copies in Books table should increased.
You're looking at this the wrong way. The Books table should only have the total amount of copies, not the amount that you need right now. When you query the actual amount of books available, you subtract the amount which has been lent out. You might find that for performance reasons, you want to persist a computed value in the book table itself, but performance optimizations like this come with their own costs :)
Now, whether you do this or update the column manually, you'll need to make sure this works in a concurrency-safe way (so that you don't lend more books than you have, for example). Which gets you into the "distributed systems are hard" territory :)
You can use triggers. It's too simple.
Refer to tutorials: http://www.postgresqltutorial.com/creating-first-trigger-postgresql/
Ignoring the performance part (which is important), here is an example that you should adapt to your table's columns:
---------Creating function-----------
CREATE OR REPLACE FUNCTION student_to_book(p_ID)
RETURNS trigger AS
$BODY$
BEGIN
IF (TG_OP = 'INSERT') THEN
UPDATE Books2Students BS
SET BS.noCopies= noCopies+1
WHERE BS.BOOKS_ISSUED=p_ID;
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
UPDATE Books2Students BS
SET BS.noCopies= noCopies-1
WHERE BS.BOOKS_ISSUED=p_ID;
RETURN OLD;
END IF;
RETURN NULL;
END;
$BODY$
---------Creating TRIGGER-----------
CREATE TRIGGER student_manage
BEFORE INSERT OR DELETE
ON STUDENT
FOR EACH ROW
EXECUTE PROCEDURE student_to_book(ROW.StudentID);

break normalization rules , creat table /view from muliple tables

I have a question and am not sure if this is the correct forum to post it .
I have two tables, StudentTable and CourseTable, where each student takes more than one course.
Example: Student1 takes 2 courses: (C1, C2).
Student2 takes 3 courses (C1, C2, C3).
I need to create a table/view that contains student information from StudentTable, plus all the courses and the score for each course from CourseTable - in one row.
Example:
Row1= Student1_Id, C1_code, C1_name, C1_Score, C2_code, C2_name, C2_Score
Row2=
Student2_Id, C1_code, C1_name, C1_Score, C2_code, C2_name, C2_Score, C3_code, C3_name, C3_Score
Since Student1 has just two courses, I should enter NULL in 'Course 3 fields'
My struggle is in the insert statement. I tried the following but it showed an error.
Insert Into Newtable
( St_ID, C1_code,c1_name, C1_Score ,C2_code ,C2_name,C2_score,C3_code ,C3_name,C3_score)
Select
(Select St_ID from StudentTable)
,
(Select C_code,c_name,c_Score
from Coursetable,SudentTable
where course.Stid =Studet.stid)
,
(Select C_code,c_name,c_Score
from course ,student
where course.Stid =Studet.stid ),
(Select C_code,c_name,c_Score
from course ,student
where course.Stid =Studet.stid );
I'm fully aware that the New table/View will break the rules of normalization, but I need it for a specific purpose.
I tried also the PIVOT BY functionality but no luck with it.
FYI, I'm not expert in SQL syntax. I just know the basics.
I will be great full for any helpful suggestions to try.
I added My DB structure so you can have better Idea
First Table is Member table which Represent Students Information .The fields in this table are
member_sk (PrimaryKey), full_or_part_time, gender, age_at_entry, age_band_at_entry, disability, ethnicity,
widening_participation_level, nationality
Second Table is Modules table which include the Courses' scores that Student took .
The fields in this table are
Module_result_k(Primary Key), member_sk(Foreign key to connect to Member table), member_stage_sk ,module_k(Foreign key to connect to Module table), module_confirmed_grade_src, credit_or_result
Third Table is AllModuleInfo which is include general information for each course .The fields in this table are
Module_k (Primary key), module_name ,module_code, Module_credit, Module stage.
The New table that I will create has the following fields
member_sk (PrimaryKey), full_or_part_time, gender, age_at_entry, age_band_at_entry, disability, ethnicity,
widening_participation_level, nationality " This will be retrieved from Member table"
Also will include
Module 1_name ,module1_code, Module1_credit, Module1_ stage, member1_stage_sk , module1_confirmed_grade_src, credit1_or_result
Module 2_name ,module2_code, Module2_credit, Module2_ stage, member2_stage_sk , module2_confirmed_grade_src, credit2_or_result
-
-
-
I will repeat this fields 14 times which is equal to Maximum courses number that any of the students took.
//// I hope now my questions become more clear

Resources