I am trying to get those students who passed the first exams will be eligible for 2nd-month exams.
For example student_ID = 1 and Exam_ID = 1 pass then he will be eligible for the exams of Exam_ID = 2.
But the students failed in Exam_ID = 2 will not be eligible for exam_ID = 3 and passed students shouldn't be shown in Exam_ID = 2.
For exam2 entry only those students should show who passed exam1 and in exam2 will be showing for exam3 entry those who are failed in exam2. Mean the students who passed exam1 but fail in exam2.
I am confused what query will be work for this.
I tried this:
select student_Id
from Tbl_StudentsExamMarking
where Office_Id = 1
and Class_Id = 1
and Exam_Id = 1
and Status = 'Pass'
but I am getting only student_id=1 and 2 while student_id=2 passed the exam1 then he shouldn't be shown in exam1. I mean the students shouldn't show who passed the exam1 and students shouldn't show for exam2 who passed exam2.
on running query I am getting this result.
I tried to solve this problem. Kindly help me in this scenario.
Thanks.
You could join the table with itself and filter each join for the specific exam_id.
For example (You will need to replace #students with your table name):
In the following I make a subquery for exam_id = 1, a second subquery for exam_id = 2 and a third subquery for exam_id =3.
This gives you a "new table" that you then can filter.
SELECT
E1.studentid,
E1.Status AS [Status_exam2],
E2.Status AS Status_exam2,
E3.Status AS Status_exam3
FROM
(
-- The first exam_id as the main table as normally all students will do the first exam
SELECT *
FROM #students
Where exam_id = 1
) E1
LEFT JOIN
(
-- Left join with the second selection as maybe not all patients already did the
-- second exam
SELECT *
FROM #students
Where exam_id = 2
) E2 ON E1.studentid = E2.studentid
LEFT JOIN
(
SELECT *
FROM #students
Where exam_id = 3
) E3 ON E1.studentid = E3.studentid
With your example this gives the following output:
This "new" table, you can filter:
For example:
Patients passed exam_id1 but did not do the second or failed the second one
Add the following at the end of the above program:
WHERE E1.Status = 'Pass'
AND E2.Status is null or E2.Status = 'Fail'
This gives you the following:
You seem to be assuming that tests come in sequence and only depend on a single prerequisite. This isn't a recommended approach but is it the result that you need?
select Student_id, max(Exam_Id) as Last_Pass_Exam_Id
from Tbl_StudentsExamMarking
where Status = 'Passed'
group by Student_id
Assuming I'm understanding your challenge, try something like this:
-- Exam data mockup table and data.
DECLARE #ExamData TABLE ( Student_id INT, Class_id INT, Office_Id INT, Exam_Id INT, [Status] VARCHAR(4) );
INSERT INTO #ExamData ( Student_id, Class_id, Office_Id, Exam_Id, [Status] )
VALUES
( 1, 1, 1, 1, 'Pass' ),
( 2, 1, 1, 1, 'Pass' ),
( 9, 1, 1, 1, 'Fail' ),
( 10, 1, 1, 1, 'Fail' ),
( 1, 1, 1, 2, 'Pass' ),
( 2, 1, 1, 2, 'Fail' );
-- What exam has been passed?
DECLARE #PassedExamNo INT = 1;
-- Show students who have passed the specified exam and are eligible for the next.
-- Excludes:
-- Students who failed required exam.
-- Students who have already passed the next sequential exam.
-- Assumption: Exam ids are incremented by 1 for each sequential exam.
SELECT * FROM #ExamData AS e
WHERE
Office_Id = 1
AND Class_Id =1
AND Exam_Id = #PassedExamNo
AND [Status] = 'Pass'
AND NOT EXISTS (
SELECT * FROM #ExamData AS x WHERE
x.Office_Id = e.Office_Id AND x.Student_id = e.Student_id AND x.Exam_Id = ( e.Exam_Id + 1 ) AND x.[Status] = 'Pass'
);
Returns any student(s) who are eligible for the next exam:
+------------+----------+-----------+---------+--------+
| Student_id | Class_id | Office_Id | Exam_Id | Status |
+------------+----------+-----------+---------+--------+
| 2 | 1 | 1 | 1 | Pass |
+------------+----------+-----------+---------+--------+
You can run the above example in SSMS.
You can try the below query once. This will give the each student and the last exam he has taken and status
SELECT student_Id
,Exam_Id
,[Status]
FROM (
SELECT student_Id
,Exam_Id
,[Status]
,ROW_NUMBER() OVER (
PARTITION BY student_Id ORDER BY Exam_Id DESC
) [rownum]
FROM Tbl_StudentsExamMarking
WHERE Office_Id = 1
AND Class_Id = 1
) a
WHERE a.[rownum] = 1
Adding the new query as per your comment.
DECLARE #exam_id INT = 2
SELECT
student_Id
,Exam_Id
,[Status]
FROM Tbl_StudentsExamMarking
WHERE Office_Id = 1
AND Class_Id = 1
AND ((Exam_id = #exam_id AND [Status] = 'Fail')
OR (Exam_id = #exam_id - 1 AND [Status] = 'Pass'))
Sample output for Query 1
This is the complete answer to my query. I resolved after 5 days.
Select st.Student_Id, st.Student_Name,sm.Status,sm.Exam_Id from Tbl_StudentsExamMarking sm
inner join Tbl_Students st on st.Student_Id = sm.Student_Id
where sm.Office_Id = 1 and sm.Class_Id = 1 and sm.exam_id=2 and status='Fail' and Month(sm.Exam_Date)=08 and not exists(select * from Tbl_StudentsExamMarking as f where f.Student_Id=sm.Student_Id and f.Office_Id = sm.Office_Id and f.Status = 'pass' and f.Exam_Id = (sm.Exam_Id+1))
and exists(select * from Tbl_StudentsExamMarking as f where f.Student_Id=sm.Student_Id and f.Office_Id = sm.Office_Id and f.Status = 'Pass' and f.Exam_Id = (sm.Exam_Id-1))
and not exists(select * from Tbl_StudentsExamMarking as f where f.Student_Id=sm.Student_Id and f.Office_Id = sm.Office_Id and f.Status = 'Pass' and f.Exam_Id = (sm.Exam_Id))
--AND NOT EXISTS (SELECT * FROM Tbl_StudentsExamMarking AS x WHERE x.Office_Id = sm.Office_Id AND x.Student_id = sm.Student_id AND x.Exam_Id = ( sm.Exam_Id ) AND x.[Status] = 'Fail')
Union
Select st.Student_Id, st.Student_Name,sm.Status,sm.Exam_Id from Tbl_StudentsExamMarking sm
inner join Tbl_Students st on st.Student_Id = sm.Student_Id
where sm.Office_Id = 1 and sm.Class_Id = 1 and sm.exam_id=1 and status='Pass' and Month(sm.Exam_Date)=08
AND NOT EXISTS (SELECT * FROM Tbl_StudentsExamMarking AS x WHERE x.Office_Id = sm.Office_Id AND x.Student_id = sm.Student_id AND x.Exam_Id = ( sm.Exam_Id + 1 ) AND x.[Status] = 'Pass')
AND NOT EXISTS (SELECT * FROM Tbl_StudentsExamMarking AS x WHERE x.Office_Id = sm.Office_Id AND x.Student_id = sm.Student_id AND x.Exam_Id = ( sm.Exam_Id + 1 ) AND x.[Status] = 'Fail')
Related
I have this table with over 1,000,000 rows, and I am trying to return ONLY the records with the same [name] and have both NY = 1 and KS = 1, even though those will be in separate rows. For this example:
I want the query to return both records for James, because they both have NY = 1 and KS = 1, but if NY != 1 or KS != 1 for james, then I don't want the query to return it
There's probably a more efficient way, but you could give this a shot.
SELECT *
FROM
your_table AS a
WHERE
EXISTS(
SELECT TRUE
FROM your_table AS x
WHERE
a.number = x.number
AND x.KS = 1
)
AND EXISTS(
SELECT TRUE
FROM your_table AS x
WHERE
a.number = x.number
AND x.NY = 1
)
If you only want rows where one of these values is set:
SELECT t.*
FROM t
WHERE (x.NY = 1 AND
EXISTS (SELECT 1
FROM t t2
WHERE t2.number = t.number AND x.KS = 1
)
) AND
(x.KS = 1 AND
EXISTS (SELECT 1
FROM t t2
WHERE t2.number = t.number AND x.NY= 1
)
);
However, window functions might be a good bet:
select t.*
from (select t.*,
sum(ny) over (partition by number) as num_ny,
sum(ks) over (partition by number) as num_ks
from t
) t
where num_ny > 0 and num_ks > 0;
declare #table1 as table
(
sname varchar(50),
permno int,
symbol int,
groupdate date
)
insert into #table1(sname,permno,symbol,groupdate)
values
('ashan',1,0,'2019-01-01'),
('re',2,1,'2019-01-01'),
('saman',1,0,'2019-01-01'),
('ashan',0,1,'2019-01-01'),
('saman',1,1,'2019-01-01')
select * from #table1 a
inner join
(select x.sname from
(
select sname,count(sname)as xs
from #table1 group by sname
having count(sname)>1
)x)b on
a.sname=b.sname
where permno=1 and symbol=1
I have a table called recipes with following data.
page_no title
-----------------
1 pancake
2 pizza
3 pasta
5 cookie
page_no 0 is always blank, and missing page_no are blank, I want output as below, for the blank page NULL values in the result.
left_title right_title
------------------------
NULL pancake
Pizza pasta
NULL cookie
I have tried this SQL statement, but it's not returning the desired output:
SELECT
CASE WHEN id % 2 = 0
THEN title
END AS left_title,
CASE WHEN id %2 != 0
THEN title
END AS right_title
FROM
recipes
You are quite close. You just need aggregation:
select max(case when id % 2 = 0 then title end) as left_title,
max(case when id % 2 = 1 then title end) as right_title
from recipes
group by id / 2
order by min(id);
SQL Server does integer division, so id / 2 is always an integer.
Using CTE.. this should be give you a good CTE overview
DECLARE #table TABLE (
pageno int,
title varchar(30)
)
INSERT INTO #table
VALUES (1, 'pancake')
, (2, 'pizza')
, (3, 'pasta')
, (5, 'cookie')
;
WITH cte_pages
AS ( -- generate page numbers
SELECT
0 n,
MAX(pageno) maxpgno
FROM #table
UNION ALL
SELECT
n + 1 n,
maxpgno
FROM cte_pages
WHERE n <= maxpgno),
cte_left
AS ( --- even
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 = 0),
cte_right
AS ( --- odd
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 <> 0)
SELECT
tl.title left_title,
tr.title right_title --- final output
FROM cte_left l
INNER JOIN cte_right r
ON l.rn = r.rn
LEFT OUTER JOIN #table tl
ON tl.pageno = l.n
LEFT OUTER JOIN #table tr
ON tr.pageno = r.n
I have a database with following structure.
CREATE TABLE Party
(
PartyID INT IDENTITY
PRIMARY KEY ,
StatusID INT ,
Weigth INT ,
OldWeigth INT
);
GO
CREATE TABLE PartyLocation
(
PartyLocationID INT IDENTITY
PRIMARY KEY ,
PartyID INT FOREIGN KEY REFERENCES dbo.Party ( PartyID ) ,
LocationID INT ,
Distance INT
);
GO
CREATE TABLE PartyRole
(
PartyRoleID INT IDENTITY
PRIMARY KEY ,
PartyID INT FOREIGN KEY REFERENCES dbo.Party ( PartyID ) ,
RoleID INT
);
with some simple data.
INSERT INTO dbo.Party
( StatusID, Weigth, OldWeigth )
VALUES ( 1, -- StatusID - int
10, -- Age - int
20 -- OldAge - int
),
( 1, 15, 25 ),
( 2, 20, 30 );
INSERT INTO dbo.PartyLocation
( PartyID, LocationID, Distance )
VALUES ( 1, -- PartyID - int
1, -- LocationID - int
100 -- Distance - int
),
( 1, 2, 200 ),
( 1, 3, 300 ),
( 2, 1, 1000 ),
( 2, 2, 2000 ),
( 3, 1, 10000 );
INSERT INTO dbo.PartyRole
( PartyID, RoleID )
VALUES ( 1, -- PartyID - int
1 -- RoleID - int
),
( 1, 2 ),
( 1, 3 ),
( 2, 1 ),
( 2, 2 ),
( 3, 1 );
I want to query the following information
Return sum of Weigth of all parties that has roleID = 1 in PartyRole table
Return sum of OldWeigth of all parties that has statusID = 2
Return sum of distances of all parties that has locationID = 3
Return sum of distances of all parties that has roleID = 2
So the expected results are
FilteredWeigth FilteredOldWeigth FilteredDistance AnotherFilteredDistance
-------------- ----------------- ---------------- -----------------------
45 30 600 3600
Can we write a query that will query each table just once? If no what will be the most optimal way to query the data?
You can try this.
SELECT
FilteredWeigth = SUM(CASE WHEN RoleID = 1 AND RN_P = 1 THEN Weigth END) ,
FilteredOldWeigth = SUM(CASE WHEN StatusID = 2 AND RN_P = 1 THEN OldWeigth END),
FilteredDistance = SUM(CASE WHEN LocationID = 3 AND RN_L = 1 THEN Distance END),
AnotherFilteredDistance = SUM(CASE WHEN RoleID = 2 THEN Distance END)
FROM (
SELECT P.Weigth, P.StatusID, P.OldWeigth, PL.LocationID, PL.Distance, PR.RoleID,
RN_P = ROW_NUMBER() OVER (PARTITION BY P.PartyID ORDER BY PL.PartyLocationID),
RN_L = ROW_NUMBER() OVER (PARTITION BY PL.LocationID ORDER BY PR.PartyRoleID)
FROM Party P
INNER JOIN PartyLocation PL ON P.PartyID = PL.PartyID
INNER JOIN PartyRole PR ON P.PartyID = PR.PartyID
) AS T
the below gives
45 20 300 3600
the third column gives 300 which does not correspond to your expected result.
with q1
as
(
select sum(weigth) FilteredWeigth
from party join partyrole on party.partyid = partyrole.partyid
where partyrole.RoleID = '1'
),
q2 as
(
select sum(weigth) OldWeigth from party where StatusID = '2'
),
q3 as (
select sum(Distance) FilteredDistance
from party join PartyLocation on party.partyid = PartyLocation.partyid
where PartyLocation.locationID = '3'
),
q4 as
(
select sum(Distance) AnotherFilteredDistance
from party join partyrole on party.partyid = partyrole.partyid
join PartyLocation on party.partyid = PartyLocation.partyid
where partyrole.RoleID = '2'
)
select FilteredWeigth,OldWeigth,FilteredDistance,AnotherFilteredDistance
from q1,q2,q3,q4
When Using Individual Queries, you can achieve this using the following
Return sum of Weight of all parties that has roleID = 1 in PartyRole table
SELECT
SUM(Weight) FilteredWeigth
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 1
)
Return sum of OldWeigth of all parties that has statusID = 2
SELECT
SUM(OldWeigth) FilteredOldWeigth
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 2
)
Return sum of distances of all parties that has locationID = 3
SELECT
SUM(Distance) FilteredDistance
FROM dbo.PartyLocation
WHERE LocationID = 3
Return sum of distances of all parties that has roleID = 2
SELECT SUM(Distance) FROM PartyLocation PL
WHERE EXISTS
(
SELECT 1 FROM PartyRole PR
WHERE PR.PartyID = PL.PartyID
AND PR.Roleid = 2
)
If you want to get the result of all these in a single result set. then maybe you can try a pivot query. Like this
WITH CTE
AS
(
SELECT
'FilteredWeigth' ColNm,
SUM(Weigth) Val
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 1
)
UNION
SELECT
'FilteredOldWeigth' ColNm,
SUM(OldWeigth) Val
FROM dbo.Party P
WHERE EXISTS
(
SELECT
1
FROM dbo.PartyRole PR
WHERE PR. PartyID = P.PartyID
AND PR.RoleId = 2
)
UNION
SELECT
'FilteredDistance' ColNm,
SUM(Distance) Val
FROM dbo.PartyLocation
WHERE LocationID = 3
UNION
SELECT
'AnotherFilteredDistance' ColNm,
SUM(Distance) Val FROM PartyLocation PL
WHERE EXISTS
(
SELECT 1 FROM PartyRole PR
WHERE PR.PartyID = PL.PartyID
AND PR.Roleid = 2
)
)
SELECT
*
FROM CTE
PIVOT
(
SUM(Val)
FOR ColNm IN
(
[FilteredWeigth],[FilteredOldWeigth],[FilteredDistance],[AnotherFilteredDistance]
)
)Pvt
The Result Will be
I could think of only three possible options:
Union query with four different select statements as answered by #ab-bennett
Join all tables then use select statements as answered by sarslan
Mix of 1 and 2, based on experiments
Coming to the question you asked:
Can we write a query that will query each table just once?
Assuming best performance is the goal, following could happen in each of the above cases:
All select statements would have their own where clause. This would perform best when where produces few rows compared to the count(*). Note that Joins are terrible for very large tables.
A join is made once, and the desired output is obtained from the same Joined table. This would perform optimal when where produces significant number of rows and the table is not too big to join.
You can mix JOIN / IN / EXISTS / WHERE to optimize your queries based on number of rows you are having in table. This approach could be used when your dataset cardinality might not vary a lot.
I have tables like these
LoanPrograms
Id Name
------------
1 LP1
2 LP2
3 LP3
Channels
Id Name
----------
4 Channel1
5 Channel2
6 Channel3
LoanProgramsChannels
LoanProgramId Channelid
----------------------
1 4
1 5
2 4
I wanted to get data like these
LoanProgarmNames channel1 channel2 channel3
---------------- -------- -------- --------
LP1 y y N
LP2 y N N
LP3 N N N
I am quite new to SQL, I know I have to use PIVOT to achieve these, but not sure how can I achieve in these scenario. Can anybody help on this ?
Because you need to "fill in the gaps" the best way is to create a cartesian product using a CROSS JOIN:
;WITH CTE AS (
SELECT A.NAME AS LOANPROGRAMNAME
, B.NAME AS CHANNELNAME
, CASE WHEN C.CHANNELID IS NULL THEN 'N' ELSE 'Y' END AS LOANPROGRAMCHANNELS
FROM LOANPROGRAMS AS A
CROSS JOIN CHANNELS AS B
LEFT JOIN LOANPROGRAMSCHANNELS AS C
ON CAST(A.ID AS VARCHAR)+CAST(B.ID AS VARCHAR) =
CAST(C.LOANPROGRAMID AS VARCHAR)+CAST(C.CHANNELID AS VARCHAR))
SELECT LOANPROGRAMNAME, [CHANNEL1], [CHANNEL2], [CHANNEL3]
FROM CTE
PIVOT(MAX(LOANPROGRAMCHANNELS) FOR CHANNELNAME IN ([CHANNEL1], [CHANNEL2], [CHANNEL3])) PIV
Once you've got the condition (Y/N) you can pivot the required columns, as done here.
There might be better ways to code this, but I believe the following will provide what you want:
declare #tbLoanPrograms table (
ID int, Name varchar(12)
)
insert into #tbLoanPrograms (
ID , Name
)
values (1, 'LP1')
, (2, 'LP2')
, (3, 'LP3')
declare #tbChannels table (
ID int, Name varchar(12)
)
insert into #tbChannels (
ID , Name
)
values (4, 'Channel1')
, (5, 'Channel2')
, (6, 'Channel3')
declare #tbLoanProgramsChannels table (
LoanProgramId int, Channelid int
)
insert into #tbLoanProgramsChannels (
LoanProgramId , Channelid
)
values (1, 4)
, (1, 5)
, (2, 4)
select
t.Name
, Channel1 = max(Channel1)
, Channel2 = max(Channel2)
, Channel3 = max(Channel3)
from (
select
lp.Name
, Channel1 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 4
then 'y'
else 'N'
end
, Channel2 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 5
then 'y'
else 'N'
end
, Channel3 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 6
then 'y'
else 'N'
end
from
#tbLoanPrograms lp
left join #tbLoanProgramsChannels lpc on lpc.LoanProgramId = lp.ID
left join #tbChannels c on c.ID = lpc.Channelid
) t
group by t.Name
I have a table like this:
TransId. LayerNo. AccountId.
100. 1. 2.
100. 2. 3.
120. 1. 5.
120. 2. 6.
120. 3. 12.
70. 1. 2.
I want to find transId(s) where:
(LayerNo = 1 and (accountId = 2 or 5))
and
(LayerNo = 2 and (accountId = 3 or 6))
And result set would be row no 1,2,3,4.
How could I write query to get the result?
My database is SQL server 2008 r2
Thanks in advance
Nima
SELECT TransId
FROM your_table
WHERE ( layerno = 1
AND accountid IN ( 2, 5 ) )
INTERSECT
SELECT TransId
FROM your_table
WHERE ( layerno = 2
AND accountid IN ( 3, 6 ) )
One approach is to ensure that each transID must have two records that satisfy the conditions you outlined.
SELECT * FROM
TABLE
WHERE TransID IN(
SELECT TransId
FROM table
WHERE ( layerno = 1
AND accountid IN ( 2, 5 ) )
OR ( layerno = 2
AND accountid IN( 3, 6 ) )
GROUP BY
TransId
HAVING Count(*) = 2
)
However this could be a problem if you can have multple records where layerno = 1. So you can use self joins instead to ensure the criteria.
SELECT DISTINCT a.transid
FROM table a
INNER JOIN table b
ON a.transid = b.transid
INNER JOIN table c
ON a.transid = c.transid
WHERE b.layerno = 1
AND accountid IN ( 2, 5 )
AND c.layerno = 2
AND accountid IN ( 3, 6 )
That said Martin's INTERSECT approach is probably the best
Do you mean:
SELECT
TransId,
LayerNo,
AccountId
FROM Table
WHERE (LayerNo = 1 AND AccountId IN (2, 5)) OR
(LayerNo = 2 AND AccountId IN (3, 7))
create table #temp
( rowId Int Identity(1,1), transId int)
INSERT INTO #temp(transId)
select TransId
from TableName
where (layerNo = 1 and accountID IN (2, 5))
OR (layerNo = 2 and accountId IN (3, 6))
select * from #temp
SELECT
base.TransId,
base.LayerNo,
base.AccountId
FROM TableX AS base
JOIN TableX AS a
ON a.TransId = base.TransId
AND a.LayerNo = 1 AND a.AccountId IN (2, 5)
JOIN TableX AS b
ON b.TransId = base.TransId
AND b.LayerNo = 2 AND b.AccountId IN (3, 7)
WHERE (base.LayerNo = 1 AND base.AccountId IN (2, 5))
OR (base.LayerNo = 2 AND base.AccountId IN (3, 7))
This intersection is empty. If you take the values where LayerNo = 1 and LayerNo = 2 and intersect them their intersection is empty because these events are mutually exclusive. I believe this error comes from how the question was originally stated. I might be wrong but the predicate should have been
(LayerNo = 1 and (accountId = 2 or 5)) OR (LayerNo = 2 and (accountId = 3 or 6))
Replace the AND with an OR. If the predicate was stated correctly then the intersect is correct but will always be empty.
SELECT *
FROM table
WHERE (LayerNo = 1 AND (AccountID = 2 OR AccountID = 5))
OR (LayerNo = 2 AND (AccountID = 3 OR AccountID = 6))