student passed all subject in atleast one attempt - sql

Consider a data below:
STUDENT_ID SUBJECT_PASSING_FLG testnumber
123 YNYNNY 1
123 YNYNYY 2
123 NNNYNN 3
456 YYYYYY 2
789 YNYNYN 1
789 NYNYNY 3
Expected Output:
STUDENT_ID SUBJECT_PASSING_FLG
123 YNYYYY
456 YYYYYY
789 YYYYYY
Each character in SUBJECT_PASSING_FLG indicate the result of each subject and test number indicate the sequence number the test was attempted.
We want to find the final result as , if the student passed in atleast one attaempt in the subject then set flag as Y else N

Since you don't mention which database you use. I try it in sql-server 2019.
I can only come up solution if you have separator in SUBJECT_PASSING_FLG string.
schema
CREATE TABLE data
(
STUDENT_ID [nvarchar](50) NOT NULL,
SUBJECT_PASSING_FLG [nvarchar](50) NOT NULL,
testnumber int NOT NULL,
);
table
insert into data values
('123', 'Y,N,Y,N,N,Y', 1),
('123', 'Y,N,Y,N,Y,Y', 2),
('123', 'N,N,N,Y,N,N', 3),
('456', 'Y,Y,Y,Y,Y,Y', 2),
('789', 'Y,N,Y,N,Y,N', 1),
('789', 'N,Y,N,Y,N,Y', 3)
sql
select
STUDENT_ID,
STRING_AGG(case when total>0 then 'Y' else 'N' end, ',') WITHIN group ( order by num asc) as SUBJECT_PASSING_FLG
from (
select STUDENT_ID, num, sum(case value when 'Y' then 1 else 0 end) as total
from (
select
STUDENT_ID,
value,
(ROW_NUMBER() OVER(PARTITION by STUDENT_ID order by STUDENT_ID asc)-1)%6+1 as num
from data
cross APPLY STRING_SPLIT(SUBJECT_PASSING_FLG, ',')
) as q
group by STUDENT_ID, num
) as p
group by STUDENT_ID
result
STUDENT_ID SUBJECT_PASSING_FLG
123 Y,N,Y,Y,Y,Y
456 Y,Y,Y,Y,Y,Y
789 Y,Y,Y,Y,Y,Y
dbfiddle

insert into dataval values
(1,'YYNY',1),
(1,'YNNY',2),
(1,'NYNY',3),
(2,'YNNY',1),
(3,'YNNY',1),
(4,'YNNY',3),
(3,'YYNY',2),
(4,'YNYY',3);
WITH cte_seq AS (
SELECT 1 AS seq
UNION ALL
SELECT seq + 1 AS seq FROM cte_seq WHERE seq < 4
), cross_cte AS (
SELECT * FROM cte_seq CROSS apply dataval
), flag_wise_status AS (
SELECT student_id,
testnumber,
Substring(subject_passing_flg, seq, 1) AS subject_status,
seq AS subject_num
FROM cross_cte
), max_flag AS (
SELECT student_id,
subject_num,
Max(subject_status) AS subject_status
FROM flag_wise_status
GROUP BY student_id, subject_num
)
SELECT student_id,
String_agg(subject_status, '') AS result
FROM max_flag
GROUP BY student_id;
Result:
1 YYNY
2 YNNY
3 YYNY
4 YNYY
dbfiddle

Related

Throw error , if duplicate data (not duplicate records) found in a table

I have a table which contains information about products, I need to check for duplicate records and throw an error. There are two columns ,product key and product value, where product value contains both semi-colon separate and normal values. The sample data is given below. (No constraints )
source_id |Product_key | Product_value
-----------------------------------------------------------
1 xzy PRODUCT_TAG=SCENT;CODE=123;PRICE=234
1 xhmr POWDER
1 abc PRODUCT_TAG=COMB;CODE=123;PRICE=234
1 xhmr OIL
1 zrmt 123
Now i have to check if any two rows have the same product_key and product_tag value, also if the product_key is xhmr , then product_value should be considered as product_tag value. The query which i have written is given below
select source_id, PRODUCT_KEY, rec , (case when instr(rec,'PRODUCT_TAG')<>0 THEN regexp_substr(TRIM(rec), '[^=]+', 1,2)
ELSE rec
end) as PRODUCT_TAG
from
(select source_id,PRODUCT_KEY ,regexp_substr(TRIM(PRODUCT_VALUE), '[^;]+', 1,LEVEL) AS rec from products
connect by regexp_substr(TRIM(PRODUCT_VALUE), '[^;]+', 1,LEVEL) is not null
AND prior source_id = source_id
AND PRIOR SYS_GUID() IS NOT NULL) where instr(rec,'PRODUCT_TAG')<>0 or PRODUCT_KEY in('xhmr');
Output
source_id |Product_key | Product_value
-----------------------------------------------------------
1 xzy SCENT
1 xhmr POWDER
1 abc COMB
1 xhmr OIL
After this, I am taking a count of all the rows and all the distinct rows . If both counts are not equal, then throwing error.I was wondering , if all this could be done in a concise way.
I think you might do the following
Get the main query and put into a with clause
Then you can operate with it as many times as needed
You could write it like this:
Update
with main_query
as (
select source_id, PRODUCT_KEY, rec , (case when instr(rec,'PRODUCT_TAG')<>0 THEN regexp_substr(TRIM(rec), '[^=]+', 1,2)
ELSE rec
end) as PRODUCT_TAG
from
(select source_id,PRODUCT_KEY ,regexp_substr(TRIM(PRODUCT_VALUE), '[^;]+', 1,LEVEL) AS rec from products
connect by regexp_substr(TRIM(PRODUCT_VALUE), '[^;]+', 1,LEVEL) is not null
AND prior source_id = source_id
AND PRIOR SYS_GUID() IS NOT NULL) where instr(rec,'PRODUCT_TAG')<>0 or PRODUCT_KEY in('xhmr')
)
select
case when total_value = tot_dist then 'OK' -- whatever you want here
else 'ERROR' -- whatever you want here
end as result
from
( ( select count(*) as total_value from main_query ) ,
( select count(*) as total_dist from ( select distinct * from main_query ) )
)
Example in my case
SQL> desc my_test
Name Null? Type
----------------------------------------- -------- ----------------------------
C1 NUMBER
C2 NUMBER
SQL> select * from my_test ;
C1 C2
---------- ----------
1 1
1 1
2 2
SQL> with main_query as ( select c1 , c2 from my_test )
select a , b
from
( select count(*) as a from main_query ) ,
( select count(*) as b from ( select distinct * from main_query ) )
/ 2 3 4 5 6
A B
---------- ----------
3 2
SQL> with main_query as ( select c1 , c2 from my_test )
select case when a = b then 'OK' else 'ERROR' end as result
from
( select count(*) as a from main_query ) ,
( select count(*) as b from ( select distinct * from main_query ) )
/
RESUL
-----
ERROR

Issue with Row_Number() Over Partition

I've been trying to reset the row_number when the value changes on Column Value and I have no idea on how should i do this.
This is my SQL snippet:
WITH Sch(SubjectID, VisitID, Scheduled,Actual,UserId,RLev,SubjectTransactionID,SubjectTransactionTypeID,TransactionDateUTC,MissedVisit,FieldId,Value) as
(
select
svs.*,
CASE WHEN stdp.FieldID = 'FrequencyRegime' and svs.SubjectTransactionTypeID in (2,3) THEN
stdp.FieldID
WHEN stdp.FieldID is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.FieldID is NULL
THEN 'FrequencyRegime'
ELSE stdp.FieldID
END AS [FieldID],
CASE WHEN stdp.Value is NULL and svs.SubjectTransactionTypeID = 1
THEN NULL
WHEN stdp.Value IS NULL THEN
(SELECT TOP 1 stdp.Value from SubjectTransaction st
JOIN SubjectTransactionDataPoint STDP on stdp.SubjectTransactionID = st.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
where st.SubjectID = svs.SubjectID
order by st.ServerDateST desc)
ELSE stdp.Value END AS [Value]
from SubjectVisitSchedule svs
left join SubjectTransactionDataPoint stdp on svs.SubjectTransactionID = stdp.SubjectTransactionID and stdp.FieldID = 'FrequencyRegime'
)
select
Sch.*,
CASE WHEN sch.Value is not NULL THEN
ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID)
ELSE NULL
END as [FrequencyCounter],
CASE WHEN Sch.Value = 1 THEN 1--v.Quantity
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) <> 0
THEN 0
WHEN Sch.Value = 2 and (ROW_NUMBER() over(partition by Sch.Value, Sch.SubjectID order by Sch.SubjectID, Sch.VisitID) % 2) = 0
THEN 1
ELSE NULL
END AS [DispenseQuantity]
from Sch
--left join VisitDrugAssignment v on v.VisitID = Sch.VisitID
where SubjectID = '4E80718E-D0D8-4250-B5CF-02B7A259CAC4'
order by SubjectID, VisitID
This is my Dataset:
Based on the Dataset, I am trying to reset the FrequencyCounter to 1 every time the value changes for each subject, Right now it does 50% of what I want, It is counting when the value 1 or 2 is found, but when value 1 comes again after value 2 it continues the count from where it left. I want every time the value is changes the count to also start from the beginning.
It's difficult to reproduce and test without sample data, but if you want to know how to number rows based on change in column value, next approach may help. It's probably not the best one, but at least will give you a good start. Of course, I hope I understand your question correctly.
Data:
CREATE TABLE #Data (
[Id] int,
[Subject] varchar(3),
[Value] int
)
INSERT INTO #Data
([Id], [Subject], [Value])
VALUES
(1, '801', 1),
(2, '801', 2),
(3, '801', 2),
(4, '801', 2),
(5, '801', 1),
(6, '801', 2),
(7, '801', 2),
(8, '801', 2)
Statement:
;WITH ChangesCTE AS (
SELECT
*,
CASE
WHEN LAG([Value]) OVER (PARTITION BY [Subject] ORDER BY [Id]) <> [Value] THEN 1
ELSE 0
END AS [Change]
FROM #Data
), GroupsCTE AS (
SELECT
*,
SUM([Change]) OVER (PARTITION BY [Subject] ORDER BY [Id]) AS [GroupID]
FROM ChangesCTE
)
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY [GroupID] ORDER BY [Id]) AS Rn
FROM GroupsCTE
Result:
--------------------------------------
Id Subject Value Change GroupID Rn
--------------------------------------
1 801 1 0 0 1
2 801 2 1 1 1
3 801 2 0 1 2
4 801 2 0 1 3
5 801 1 1 2 1
6 801 2 1 3 1
7 801 2 0 3 2
8 801 2 0 3 3
As per my understanding, you need DENSE_RANK as you are looking for the row number will only change when value changed. The syntax will be as below-
WITH your_table(your_column)
AS
(
SELECT 2 UNION ALL
SELECT 10 UNION ALL
SELECT 2 UNION ALL
SELECT 11
)
SELECT *,DENSE_RANK() OVER (ORDER BY your_column)
FROM your_table

SQL - How to remove repeating values

My requirement is to remove the repeating values.
id name surname value
1 Vinduja Vijayan 5
3 Vinduja Vijayan 6
4 Vinduja Vijayan 7
Required output:
id name surname value
1 Vinduja Vijayan 5
3 NuLL Null 6
4 NULL NULL 7
This transformation should usually be applied in the application layer. It is possible to do in SQL, but not recommended, by using row_number() and case:
select id,
(case when row_number() over (partition by name, surname order by id) = 1
then name
end) as name,
(case when row_number() over (partition by name, surname order by id) = 1
then surname
end) as surname
from t
order by id;
Note that the final order by is very, very important. SQL result sets (like tables) are unordered by default. Without an explicit order by, the results could be in any order, and that would mess up your interpretation of the results.
DECLARE #table TABLE (
Id INT
,Name VARCHAR(20)
,Surname VARCHAR(20)
,value INT
);
INSERT into #table(ID,Name,Surname,value)
Select 1,'Vinduja','Vijayan',5
Union
Select 3,'Vinduja','Vijayan',6
Union
Select 4,'Vinduja','Vijayan',7
Select S.Id ,T.Name,T.Surname,S.value from (
Select * ,ROW_NUMBER() Over(Partition by name Order by name) [Row]
From #table)S
Left join #table T On T.Id =S.Id and S.[Row]=1
select
id,
case when rnk=1 then name end as name,
case when rnk=1 then surname end as surname ,
value
from
(
select
id,name,surname,value,
row_number()over(partition by name,surname order by id) as rnk
from table_name)repeatname
I'm not sure I understand your requirements. If you just want to display the data as described, then this won't work. But if you're trying to change the data in your table, this will do that.
DECLARE #Dupes TABLE
(
id INT
,name VARCHAR(30)
,surname VARCHAR(30)
,value INT
);
INSERT #Dupes
(
id
,name
,surname
,value
)
VALUES
(1, 'Vinduja', 'Vijayan', 5),
(3, 'Vinduja', 'Vijayan', 6),
(4, 'Vinduja', 'Vijayan', 7);
WITH cte AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY [name], surname ORDER BY id) AS RowNum
,id
,name
,surname
,value
FROM #Dupes
)
UPDATE cte
SET cte.name = NULL
,cte.surname = NULL
WHERE
cte.RowNum > 1;
SELECT *
FROM #Dupes;
--Results
+----+---------+---------+-------+
| id | name | surname | value |
+----+---------+---------+-------+
| 1 | Vinduja | Vijayan | 5 |
| 3 | NULL | NULL | 6 |
| 4 | NULL | NULL | 7 |
+----+---------+---------+-------+
And just for interest, using the LAG function. I assumed SQL Server.
select id,
iif(name = previous_name, null, name) name,
iif(surname = previous_surname, null, surname) surname
from (
select name, surname, id,
lag(name, 1, null) over (order by name, surname, id) previous_name,
lag(surname, 1, null) over (order by name, surname, id) previous_surname
from table_name ) a
order by a.name, a.surname, a.id

SQL: Collapse sequential Data in to one row

I am trying to Collapse sequential data in to one group. For Example :In below City1 data should show 2 rows.
Please help here.
CREATE TABLE #temp
(id INT NOT NULL IDENTITY(1, 1) ,
location1 VARCHAR(50)
)
INSERT INTO #temp VALUES ('City1')
INSERT INTO #temp VALUES ('City2')
INSERT INTO #temp VALUES ('City1')
INSERT INTO #temp VALUES ('City1')
INSERT INTO #temp VALUES ('City2')
INSERT INTO #temp VALUES ('City2')
SELECT * FROM #temp
Expected Output:
City1 1
city2 2
city1 3
city2 4
Please use like this. (Assuming you are using SQL 2012+)
solution 1
select location1, x1 from
(
select * , ROW_NUMBER() OVER (PARTITION BY x1 order by Id) rnk from
(
select * ,sum(p) over(order by id)+1 x1 from
(
select * , case when location1 = ISNULL(lag(location1) over (order by id),location1) then 0 else 1 end p
from temp2
)x
)k
)p where rnk = 1
OUTPUT
location1 x1
-------------------- -----------
City1 1
City2 2
City1 3
City2 4
(4 rows affected)
I think the most direct way to get what you want uses lag(). You seem to want where the changes occur:
select row_number() over (order by id) as new_id, location1
from (select t.*, lag(location1) over (order by id) as prev_location1
from #temp t
) t
where prev_location1 is null or prev_location1 <> location1;
Here is a rextester showing the solution.

SQL How do I transpose and group data into static columns? [duplicate]

This question already has answers here:
TSQL Pivot without aggregate function
(9 answers)
Closed 4 years ago.
I have a table with the following data:
UID LAST FIRST FUND AMOUNT STATUS
1 Smith John C 100 1
1 Smith John B 250 1
1 Smith John E 150 1
2 Jones Meg B 275 1
2 Jones Meg F 150 1
3 Carter Bill A 100 1
I would like to transpose the FUND, AMOUNT and STATUS values for each UID into a single row for each UID. The resulting table would have columns added for FUND_1, AMT_1, STATUS_1, FUND_2, AMT_2, STATUS_2, FUND_3, AMT_3, STATUS_3. Each UID may or may not have a total of 3 funds. If they do not, the remaining fund, amt, and status columns are left blank. The resulting table would appear as:
UID LAST FIRST FUND_1 AMT_1 STATUS_1 FUND_2 AMT_2 STATUS_2 FUND_3 AMT_3 STATUS_3
1 Smith John C 100 1 B 250 1 E 150 1
2 Jones Meg B 275 1 F 150 1
3 Carter Bill A 100 1
For clarification, this is how the data would move from the existing table to the resulting table for UID 1:
It seems I am unable to use PIVOT because FUND_1, FUND_2, FUND_3 will be different fund categories for each person. The question, TSQL Pivot without aggregate function helps but doesn't answer my question since I have multiple rows in what would be the the DBColumnName in that question.
This is a pretty common conditional aggregation. Notice how I posted consumable data as a table and insert statements. To be honest it took longer to do that portion than the actual code to select the data. You should do this in the future. Also you should avoid using keywords as column names.
declare #Something table
(
UID int
, LAST varchar(10)
, FIRST varchar(10)
, FUND char(1)
, AMOUNT int
, STATUS int
)
insert #Something values
(1, 'Smith', 'John', 'C', 100, 1)
, (1, 'Smith', 'John', 'B', 250, 1)
, (1, 'Smith', 'John', 'E', 150, 1)
, (2, 'Jones', 'Meg', 'B', 275, 1)
, (2, 'Jones', 'Meg', 'F', 150, 1)
, (3, 'Carter', 'Bill', 'A', 100, 1)
;
with SortedValues as
(
select *
, RowNum = ROW_NUMBER() over(partition by UID order by (select null))
from #Something
)
select UID
, Last
, First
, Fund_1 = max(case when RowNum = 1 then Fund end)
, Amt_1 = max(case when RowNum = 1 then Amount end)
, Status_1 = max(case when RowNum = 1 then Status end)
, Fund_2 = max(case when RowNum = 2 then Fund end)
, Amt_2 = max(case when RowNum = 2 then Amount end)
, Status_2 = max(case when RowNum = 2 then Status end)
, Fund_3 = max(case when RowNum = 3 then Fund end)
, Amt_3 = max(case when RowNum = 3 then Amount end)
, Status_3 = max(case when RowNum = 3 then Status end)
from SortedValues
group by UID
, Last
, First
order by UID
, Last
, First