Selecting arbitrary value as max - sql

I have two tables, STUDENTS and LOG.
The LOG table may have 0 or more records in it per student. The records in the LOG table are categorized by column SUBTYPE. Values for SUBTYPE are: 'H', 'L1', 'L2', 'L3', 'M', 'P1', 'P2', and 'Px'.
I am trying to produce a report that will list each student name one time, and the most severe of the codes in the LOG table.
I naturally think of MAX(subtype), but that won't work because I'm not trying to select the actual maximum alphanumeric value of the subtypes - I'm trying to select based on a pre-defined "SEVERITY". Severity, BTW, is not stored in the DB, so I need to define this somehow inside of the query.
For Example...
STUDENTS TABLE
ID LAST_NAME FIRST_NAME
------------------------------------
2 Smith John
3 Jones Bob
4 Bower Jack
LOG TABLE
ID STUDENTSID SUBTYPE
------------------------------------
1 2 P1
2 2 M
3 2 L1
4 3 L2
5 3 P2
6 3 H
The subtype values should actually be ranked/sorted as:
(From Least Severe)
1. P1
2. L1
3. M
4. L2
5. P2
6. H
7. L3
8. Px
(To Most Severe)
Below is what I need my report to look like. Note, Jack Bower is not on the report, as he does not have any records in the log table.
DESIRED REPORT OUTPUT:
John Smith --- M
Bob Jones ---- H
Can anybody point me in the right direction?

order by
case subtype
when 'P1' then 1
when 'L1' then 2
when 'M' then 3
...
end

You need to prioritize the subtypes accordingly using row_number function. Then select the first row for each student.
select id,first_name,last_name
from (
select s.*
,row_number() over(partition by s.id
order by case when l.subtype='P1' then 1
when l.subtype='L1' then 2
when l.subtype='M' then 3
when l.subtype='L2' then 4
when l.subtype='P2' then 5
when l.subtype='H' then 6
when l.subtype='L3' then 7
when l.subtype='Px' then 8
end desc) as rnum
from students s
join logs l on s.id=l.studentsid
) x
where rnum = 1

Related

PostgreSQL: how to delete duplicated rows grouped by the value of a column?

Given the following table, I need to delete every row corresponding to a certain "id" whenever all these rows are duplicated in a successive "id". Note that the deletion all rows for a specific "id" should happen only in case that every row between the two ids match (with the exception of the different "id" column).
id
name
subject
score
1
Ann
Maths
9
1
Ann
History
8
2
Ann
Maths
9
2
Ann
History
8
3
Ann
Maths
9
3
Ann
History
7
4
Bob
Maths
8
4
Bob
History
8
For this specific input, the updated output table should be:
id
name
subject
score
1
Ann
Maths
9
1
Ann
History
8
3
Ann
Maths
9
3
Ann
History
7
4
Bob
Maths
8
4
Bob
History
8
This because all records between id 1 and 2 are the exactly the same. This doesn't apply for "id" 1 and 3, as long as there's at least one row not in common between the two (id 1 has 8 in History while id 3 has 7 in the same subject).
So it is not as simple as deleting duplicated rows. Here's my attempt:
DELETE FROM table a
USING table b
WHERE a.name = b.name
AND a.subject = b.subject
AND a.score = b.score
AND a.ID < b.ID;
Can you help me?
You can first get all ids that shouldn't be deleted and then exclude them in the WHERE clause of the DELETE statement.
Step 1. In order to match unique ids that are not repeated for all rows, you can use PostgreSQL DISTINCT ON construct, that will allows you to get every row that is not duplicated on the fields "name", "subject", "score". Then retrieve these ids only once with a simple DISTINCT.
SELECT DISTINCT id
FROM (SELECT DISTINCT ON (name, subject, score) id
FROM tab
ORDER BY name, subject, score, id) ids_to_keep
Step 2. Hence you can build the DELETE statement using the NOT IN operator inside the WHERE clause:
DELETE FROM tab
WHERE id NOT IN (
SELECT DISTINCT id
FROM (SELECT DISTINCT ON (name, subject, score) id
FROM tab
ORDER BY name, subject, score, id) ids_to_keep
);
Check the demo here.

How to populate values in a cell from different table, separated by comma [duplicate]

This question already has answers here:
How to concatenate text from multiple rows into a single text string in SQL Server
(47 answers)
Closed 4 years ago.
I have six tables
a. Employee
EmployeeId EmployeeName
123 John
125 Peter
129 Jack
b. EmployeeParameterValue
EmployeeParameterValueId EmployeeId ParameterValueId
1 123 1
2 125 2
3 129 3
c. ParameterValue
ParameterValueId ParameterId Value
1 2 1, 2, 3
2 3 1, 2
3 2 3
d. Parameter
ParameterId Name
2 WorkedStates
3 WorkedType
e. WorkedStates
WorkedStatesId WorkedStatesName
1 CA
2 WA
3 NY
f. WorkedType
WorkedTypeId WorkedTypeName
1 Hourly
2 Salaried
I need to write a report in following format:
EmployeeId EmployeeName Parameter ParameterValue
123 John WorkedStates CA, WA, NY
125 Peter WorkedType Hourly, Salaried
129 Jack WorkedStates NY
I am able to write a query which fetches first 3 columns. But I am not able to write a query which fetches the data in the fourth column.
Please advise.
Clearly the design could be improved.
Now, you could split the delimited string, join to the appropriate table, and then re-aggregate the results, or you can consider a UDF
Example
Select [dbo].[ParameterValue](2,'1, 2, 3')
Returns
CA, WA, NY
The UDF if interested
CREATE FUNCTION [dbo].[ParameterValue] (#P int,#S varchar(max))
Returns varchar(max)
AS
Begin
Select #S = replace(#S,MapFrom,MapTo)
From (Select Top 10000
MapFrom = WorkedStatesId
,MapTo = WorkedStatesName
From WorkedStates
Where #P=2
Order By 1 desc
Union All
Select Top 10000
MapFrom = WorkedTypeId
,MapTo = WorkedTypeName
From WorkedType
Where #P=3
Order By 1 desc
) A
Return #S
End
-- Syntax : Select [dbo].[ParameterValue](2,'1, 2, 3')
I would not suggest this on a large table, but the performance may not be so bad.
Use JSON
save your data with json format.
If you save data with json format you have a object into any col of table that help you better organized your data in tables.
Save two or tree or more table fields in one json and save this json into one col of table.

Cross Join for Missing table (Select all and Select or Insert the Missing Row Only)

I have two table and have to fill in a list of missing values in one of the table based on the other one. First table has student's information and the second table has Grade related info, Grade and Grade description.
Table One
ID Name yearWithUs Grade Course Level
1 Jim 2004 4 4
2 Jim 2004 4 1
2 Jim 2003 3 3
4 Jim 2002 2 3
4 Jim 2002 2 1
3 Jim 2001 1 2
3 Jim 2001 1 1
Table two -- logic is.. A Student in a higher Course Level can change to a lower Course Level at anytime during the semester. And It can only go downward 1 level at a time. Example: Jim in his second grade first was assigned to attend course in level 3. He need to attend course in level 2 first before he can attend course in level 1. Means. Row for course level 2 at jim's first grade is missing.
Table Two
ID Grade Grade_Desc Course Level Course Desc
1 1 First Grade 1 Basic
2 1 First Grade 2 Normal
3 1 First Grade 3 Hard
4 1 First Grade 4 Expert
5 2 Second Grade 1
6 2 Second Grade 2
7 2 Second Grade 3
8 2 Second Grade 4
. . .
. . .
. . .
Logic of Table Two
ID Grade Grade_Desc Course Level Possible Move
1 1 First Grade 1 Null
2 1 First Grade 2 1
3 1 First Grade 3 2
4 1 First Grade 4 3
Ouptput one ... how to use select statement to return Jim's Grade?
ID Name Grade_Desc Grade yerWithUs Course Level
1 Jim Fourth Grade 4 2004 4
2 Jim Fourth Grade 4 2004 3
3 Jim Fourth Grade 4 2004 2
4 Jim Fourth Grade 4 2004 1
5 Jim Third Grade 3 2003 3
6 Jim Second Grade 2 2002 3
7 Jim Second Grade 2 2002 2
8 Jim Second Grade 2 2002 1
9 Jim First Grade 2 2001 2
10 Jim First Grade 2 2001 1
Output Two..How to retrieve only the missing row into a new temp table?
ID Name Grade_Desc Grade yearWithUs Course Level
2 Jim Fourth Grade 4 2004 3
3 Jim Fourth Grade 4 2004 2
7 Jim Second Grade 2 2002 2
I am currently is using a messy Cursor Statement to do it. The structure looks really messy and hard to debug return errors. I did a lot of research, and saw people use Cross Join to fill the missing portion which looks really clean (See example below)... I have tried the script it myself in many different way by using the cross join example below...obviously, I failed. I found a similar question in stackoverflow..but I am not able to understand how does it work and why without looking at the data....I need help to understand how to use cross join to rerun missing row? and I am open to any other possible solution.
"SELECT calendar.Date,
Category.Cat,
Score = ISNULL(Scores.Score, 0)
FROM Calendar
CROSS JOIN Catogory
LEFT JOIN Scores
ON Scores.Cat = Category.Cat
AND Scores.Date = Calendar.Date
WHERE Calendar.DayOfMonth = 1;"
Inserting missing rows with a join
Thank You
This will produce that output:
select distinct name, grade, Grade_Desc
from one
cross join two
If select is all you want then:
Select row_number() over(order by (select 1)) as id, * from
(Select distinct name from t1)t1
cross join t2
Here is fiddle http://sqlfiddle.com/#!6/a8a42/3
Try this out:
Create #Temp
DECLARE #Name VARCHAR(100) = 'Jim'
SELECT ROW_NUMBER() OVER (ORDER BY B.Grade DESC,B.CourseLevel DESC) ID,
A.Name,
B.Grade_Desc,
B.Grade,
A.YearWithUs,
B.[Course Level]
INTO #temp
FROM
(
SELECT DISTINCT Name,YearWithUs,Grade
FROM TableOne
WHERE Name = #Name
) A
INNER JOIN TableTwo B
ON A.Grade = B.Grade
Output One
SELECT *
FROM #temp
Output Two into #OutputTwo(temp table)
SELECT A.* INTO #OutputTwo
FROM #temp A
LEFT JOIN TableOne B
ON A.Grade = B.Grade
AND A.[Course Level] = B.[Course Level]
WHERE A.Grade IS NULL AND A.[Course Level] IS NULL

How to count the no. of HOLD entry between the Two ACTIVE entry

I want to count the number of HOLD entry between the Two ACTIVE entries.
S.NO DATA STATUS
-----------------------------
1 B active
2 D hold
3 C active
4 H hold
5 j hold
6 k hold
7 l hold
8 y active
The output should be
COUNT OF HOLD
-------------
2
When HOLD entry comes between two ACTIVE entries we take it as a batch of HOLD entry.
Then we Count the whole Batch of HOLD entry in the table. In the above Example, there is one hold entry between two active, this is counted as 1.
Then another 4 HOLD entries between another two ACTIVE, This is counted to be 1.
So there are two counts. 1+1. So the output is 2.
You can make use of LEAD function to look what is the status in the next row. Then count the occurrences where current row is active and next is hold.
Subtract 1 from it, and you have your output.
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table sam(
sno_ number,
data_ varchar2(5),
status_ varchar2(10)
);
insert into sam values(1,'a','hold');
insert into sam values(2,'b','active');
insert into sam values(3,'d','active');
insert into sam values(4,'s','hold');
insert into sam values(5,'c','active');
insert into sam values(6,'r','hold');
insert into sam values(7,'t','hold');
insert into sam values(8,'m','active');
insert into sam values(9,'y','hold');
Query:
select count(1) - 1 count_of_hold
from (
select status_, lead(status_,1,'hold') over (order by sno_) next_status_
from sam
)
where status_ = 'active' and next_status_ = 'hold';
Results:
| COUNT_OF_HOLD |
|---------------|
| 2 |

Grouping a row based on field in a different table in oracle

I am working with these two tables for the past two days
parts_list table:
PART_ID VENDOR_ID LABEL
1 5 A
1 2 B
1 3 C
2 2 D
2 3 E
3 3 F
vendor_prsdnc table:
VENDOR_ID PRSCDNC
5 3
2 2
3 1
Can anybody please tell me how to retrieve the label of each part from the vendor with highest precedence? For example the part with id one is supplied by 3 vendors but we need the one from vendor with highest precedence ie 5. The expected result is:
PART_ID VENDOR_ID LABEL
1 5 A
2 2 D
3 3 F
[Vendor Id is not proportional with the precedence ]
I have this query
SELECT
SDS.PART_ID,
SDSIS.VENDOR_ID,
MAX(SDSIS.PRSCDNC)
FROM PARTS_LIST SDS,VENDOR_PRSDNC SDSIS
WHERE SDS.VENDOR_ID=SDSIS.VENDOR_ID
GROUP BY SDS.PART_ID,SDSIS.VENDOR_ID;
but it does not return the expected result.
Not tested ,but it should work i think
select part_id,vendor_id,label
from
(
select pl.part_id
,pl.vendor_id
,pl.label
,vp.prscdnc
,max(vp.prscdnc) over (partition by pl.part_id) mx
from part_list pl,vendor_prsdnc vp
where pl.vendor_id=vp.vendor_id
)
where prscdnc =mx;