Only assign values to entries relevant to condition - sql

I have the following code
with my_table (id, student, category, score)
as (values
(1, 'Alex', 'A', 11),
(2, 'Alex', 'D', 4),
(3, 'Bill', 'A', 81),
(4, 'Bill', 'B', 1),
(5, 'Bill', 'D', 22),
(6, 'Carl', 'C', 5),
(7, 'Carl', 'D', 10)
)
select
id, student, category, score,
case when
max(score) filter (where category in ('A', 'B', 'C')) over (partition by student) >
min(score) filter (where category = 'D') over (partition by student)
then 'Review'
else 'Pass'
end as result
from my_table
order by student, id
which outputs
id student category score conclusion
1 Alex A 11 Review
2 Alex D 4 Review
3 Bill A 81 Review
4 Bill B 1 Review
5 Bill D 22 Review
6 Carl C 5 Pass
7 Carl D 10 Pass
How can I edit it so only the entries where either A, B, or C are larger than D are assigned 'Review' to them. So in this case, the desired output would be:
id student category score conclusion
1 Alex A 11 Review
2 Alex D 4 Review
3 Bill A 81 Review
4 Bill B 1 Pass
5 Bill D 22 Review
6 Carl C 5 Pass
7 Carl D 10 Pass
For Bill, A>D so Review is assigned to it; B<D so Pass is assigned to it.

From your logic, you can try to use subquery to get count then compare
with my_table (id, student, category, score)
as (values
(1, 'Alex', 'A', 11),
(2, 'Alex', 'D', 4),
(3, 'Bill', 'A', 81),
(4, 'Bill', 'B', 1),
(5, 'Bill', 'D', 22),
(6, 'Carl', 'C', 5),
(7, 'Carl', 'D', 10)
)
SELECT id, student, category, score,
CASE WHEN
COUNT(*) filter (where D_Score<score) over (partition by student) = 0 OR score < D_Score
THEN 'Pass'
ELSE 'Review' END
FROM (
SELECT *,
min(score) filter (where category = 'D') over (partition by student) D_Score
FROM my_table
) t1
sqlfiddle

Related

How can we select distinct *, From and get only Distinct IDs?

I have a small dataset that looks like this.
SELECT *
INTO Order_Table
FROM (VALUES
(1, 456, 'repair', 'House'),
(2, 456, 'paint', 'House'),
(3, 678, 'repair', 'Fence'),
(4, 789, 'repair', 'House'),
(5, 789, 'paint', 'House'),
(6, 789, 'repair', 'Fence'),
(7, 789, 'paint', 'Fence')
)
v (OrderNum, CustomerNum, OrderDesc, Structure)
SELECT *
INTO Veg_Table
FROM (VALUES
(1, '12/01/2020'),
(2, '12/02/2020'),
(3, '12/03/2020'),
(4, '12/04/2020'),
(5, '12/05/2020'),
(6, '12/06/2020'),
(7, '12/07/2020'),
(1, '12/10/2020'),
(2, '12/11/2020'),
(3, '12/12/2020')
)
v (ID, MyDate)
I have a query that looks something like this...
Select Distinct CTE.ID, *
From (
Select *
From Order_Table as Hist
Inner Join Veg_Table As Veg
On Hist.OrderNum = Veg.ID) as CTE
How can this query be modified to give only unique IDs? I always get duplicate IDs.
I also tried: Where In (Select Distinct ID From Event_View)
That didn't work either.
I want to end up with something like this.
OrderNum CustomerNum OrderDesc Structure ID MyDate
1 456 repair House 1 12/1/2020
2 456 paint House 2 12/2/2020
3 678 repair Fence 3 12/3/2020
4 789 repair House 4 12/4/2020
5 789 paint House 5 12/5/2020
6 789 repair Fence 6 12/6/2020
7 789 paint Fence 7 12/7/2020
I suppose Row_Number() Over (Partition By ID) would do it, but I was hoping for a simpler solution using 'Distinct'.
Using a regular GROUP BY and MIN appears to give you what you want.
SELECT Hist.OrderNum, Hist.CustomerNum, Hist.OrderDesc, Hist.Structure, MIN(Veg.MyDate)
FROM #Order_Table AS Hist
INNER JOIN #Veg_Table AS Veg ON Hist.OrderNum = Veg.ID
GROUP BY Hist.OrderNum, Hist.CustomerNum, Hist.OrderDesc, Hist.Structure;

PostgreSQL: Adjust columns value based on criteria

Imagine the following data:
student category exam_id adjusted_category
Carl A 44 A
Carl A 55 A
Carl A 88 A
Carl A 1 A
Carl A 2 A
Carl A 3 A
Carl B 1 B
Carl B 2 B
Carl B 3 B
John C 100 C
John C 200 C
John C 300 C
If for the same user, both categories A and B are encountered but specific exam_ids are found (44, 55, 88), I'd like to adjust the category to be A. Otherwise, keep the same category.
The output I'm aiming is:
student adjusted_category
Carl A
Carl C
Code I'm currently attempting to modify:
with my_table (student, category, exam_id)
as (values
('Carl', 'A', 44),
('Carl', 'A', 55),
('Carl', 'A', 88),
('Carl', 'A', 1),
('Carl', 'A', 2),
('Carl', 'A', 3),
('Carl', 'B', 1),
('Carl', 'B', 2),
('Carl', 'B', 3),
('John', 'C', 100),
('John', 'C', 200),
('John', 'C', 300)
)
select *,
case
when category in ('A','B') and exam_id in (44, 55, 88) then 'A'
else category
end as adjusted_category
from my_table
The reason why the code above is not what I'm after is because I end up getting the adjusted category as A only where the exams id_s are 44, 55, or 88. Id's like all of the entries for Carl to have A as the adjusted category.
How can I achieve the desired output?
I may have misinterpreted your requirement. If so, you can change the bool_or() to bool_and(). The bool_or() expression over a window partitioned by student will return true if the student has any one of (44, 55, 88):
with my_table (student, category, exam_id)
as (values
('Carl', 'A', 44),
('Carl', 'A', 55),
('Carl', 'A', 88),
('Carl', 'A', 1),
('Carl', 'A', 2),
('Carl', 'A', 3),
('Carl', 'B', 1),
('Carl', 'B', 2),
('Carl', 'B', 3),
('John', 'C', 100),
('John', 'C', 200),
('John', 'C', 300)
)
select *,
case
when category in ('A','B')
and bool_or(exam_id in (44, 55, 88)) over (partition by student) then 'A'
else category
end as adjusted_category
from my_table;
db<>fiddle here

SQL query to fetch distinct records

Can someone help me out with this sql query on postgres which I have to write but I just can't come up with, I have tried my best to simplify the problem from 1 million records and more constraints to this, I know this looks easy, but I am still unable to resolve this somehow :-
Table_name = t
Column_1_name = id
Column_2_name = st
Column_1_elements = [1,1,1,1,2,2,2,3,3]
Column_2_elements = [a,b,c,d,a,c,d,b,d]
Now I want to print to those distinct ids from id where they do not have their corresponding st equals to 'b' or 'a'.
For example, for the above example, the ouput should be [2,3] as 2 does not have corresponding 'b' and 3 does not have 'a'. [even though 3 does not have c also, but we are not concerned about 'c']. id=1 is not returned in solution as it has a relation with both 'a' and 'b'.
Let me know if you need more clarity.
Thanks in advance for helping.
edit1:- The number of elements for id = 1,2,3 could be anything. I just want those ids where there corresponding st does not "contain" 'a' or 'b'.
if there is an id=4 which has just one st which is 'r', and there is an id=5 which contains 'a','b','c','d','e','f','k','z'.
Then we want id=4 in the output as well as it does not contain 'a' or 'b'..
You might need to correct the syntax a little bit based on you SQL engine but this one is a working solution in Google BigQuery -
with temp as (
select 1 as id, 'a' as st union all
select 1 as id, 'b' as st union all
select 1 as id, 'c' as st union all
select 1 as id, 'd' as st union all
select 2 as id, 'a' as st union all
select 2 as id, 'c' as st union all
select 2 as id, 'd' as st union all
select 3 as id, 'b' as st union all
select 3 as id, 'd' as st union all
select 4 as id, 'e' as st union all
select 5 as id, 'g' as st union all
select 5 as id, 'h' as st
)
-- add 2 columns for is_a and is_b flags
, temp2 as (
select *
, case when st = 'a' then 1 else 0 end is_a
,case when st = 'b' then 1 else 0 end as is_b
from temp
)
-- IDs that have both the flags as 1 should be filtered out (like ID = 1)
select id
from temp2
group by 1
having max(is_a) + max(is_b) < 2
This solution takes care of the problem you mentioned with ID 4 . Let me know if this works for you.
See if this works:
create table t (id integer, st varchar);
insert into t values (1, 'a'), (1, 'b'), (1, 'c'), (1, 'd'), (2, 'a'), (2, 'c'), (2, 'd'), (3, 'b'), (3, 'd'), (4, 'r');
insert into t values (5, 'a'), (5, 'b'), (5, 'c'), (5, 'd'), (5, 'e'), (5, 'f'), (5, 'k'), (5, 'z');
select id, array['a', 'b'] <# array_agg(st)::text[] as tf from t group by id;
id | tf
----+----
3 | f
5 | t
4 | f
2 | f
1 | t
select * from (select id, array['a', 'b'] <# array_agg(st)::text[] as tf from t group by id) as agg where agg.tf = 'f';
id | tf
----+----
3 | f
4 | f
2 | f
In the first select query the array_agg(st) aggregates all the st values for an id via the group by id. array['a', 'b'] <# array_agg(st)::text[] then asks if the a and b are both in the array_agg.
The query is then turned into a sub-query where the outer query selects those rows that where 'f'(false), in other words did not have both a and b in the aggregated id values.

Get the top most element for any element in chain (tree) in Oracle Sql

Consider the following structure with some nodes:
1
/\
2 3
| |
4 5
| |
6 7
|
8
or
with mydata as (
select 8 id ,6 id_before, 400 datum from dual union all
select 6,4, 300 from dual union all
select 4, 2, 200 from dual union all
select 2,1, 10 from dual union all
select 3, 1, 60 from dual union all
select 5, 3, 800 from dual union all
select 7, 5, 900 from dual
)
Now given the id of the node I want to get the root node id
e.g. for node 7 root note 1, for 5 root node 1 for 4 root node 1 etc.
I tried something like this:
select id, id_before,datum, SYS_CONNECT_BY_PATH(id_before,'/') as path,
SYS_CONNECT_BY_PATH(datum,'/') as datapath,
level,
CONNECT_BY_ROOT id_before
from mydata
where id=7
connect by id_before = prior id
with disappointing results:
7 5 900 /1/3/5 /60/800/900 3 1
7 5 900 /3/5 /800/900 2 3
7 5 900 /5 /900 1 5
Any ideas on how to fix this?
Thanks.
You can use CONNECT_BY_ISLEAF as a filter to find the root element:
Oracle Setup:
CREATE TABLE mydata ( id, id_before, datum ) as
select 8, 6, 400 from dual union all
select 6, 4, 300 from dual union all
select 4, 2, 200 from dual union all
select 2, 1, 10 from dual union all
select 3, 1, 60 from dual union all
select 5, 3, 800 from dual union all
select 7, 5, 900 from dual;
Query:
SELECT CONNECT_BY_ROOT( id ) AS id,
id_before AS root_id,
SYS_CONNECT_BY_PATH( id, ',' ) || ',' || id_before AS path
FROM mydata
WHERE CONNECT_BY_ISLEAF = 1
CONNECT BY id = PRIOR id_before;
Output:
ID | ROOT_ID | PATH
-: | ------: | :---------
2 | 1 | ,2,1
3 | 1 | ,3,1
4 | 1 | ,4,2,1
5 | 1 | ,5,3,1
6 | 1 | ,6,4,2,1
7 | 1 | ,7,5,3,1
8 | 1 | ,8,6,4,2,1
db<>fiddle here
This topic helped me a lot today.
Needed to find a way to get whole tree by item in the middle.
It's fair to share results here, even after 2 years of accepted answer.
create table ref (id number not null, prvni varchar2(1), druhy varchar2(1));
insert into ref (id, prvni, druhy) values (1, 'B', 'A');
insert into ref (id, prvni, druhy) values (2, 'C', 'A');
insert into ref (id, prvni, druhy) values (3, 'D', 'C');
insert into ref (id, prvni, druhy) values (4, 'E', 'A');
insert into ref (id, prvni, druhy) values (5, 'F', 'C');
insert into ref (id, prvni, druhy) values (6, 'G', 'F');
insert into ref (id, prvni, druhy) values (7, 'x', 'y');
insert into ref (id, prvni, druhy) values (8, 'z', 'v');
insert into ref (id, prvni, druhy) values (9, 'w', 'u');
create table zpr (pismeno varchar2(1), typ varchar2(6));
insert into zpr (pismeno, typ) values ('A', 'ORDERS');
insert into zpr (pismeno, typ) values ('B', 'ORDRSP');
insert into zpr (pismeno, typ) values ('C', 'DESADV');
insert into zpr (pismeno, typ) values ('D', 'RECADV');
insert into zpr (pismeno, typ) values ('E', 'RETANN');
insert into zpr (pismeno, typ) values ('F', 'INVOIC');
insert into zpr (pismeno, typ) values ('G', 'INVOIC');
insert into zpr (pismeno, typ) values ('x', 'APERAK');
insert into zpr (pismeno, typ) values ('y', 'APERAK');
insert into zpr (pismeno, typ) values ('z', 'APERAK');
insert into zpr (pismeno, typ) values ('u', 'APERAK');
insert into zpr (pismeno, typ) values ('v', 'APERAK');
insert into zpr (pismeno, typ) values ('w', 'APERAK');
-- recursion
WITH rcte(prvni,druhy,typ,lvl) AS
(SELECT r1.prvni, r1.druhy, a1.typ, 1 AS lvl
FROM ref r1, zpr a1
WHERE (r1.prvni = 'B' OR r1.druhy = 'B')
AND a1.pismeno = r1.druhy
UNION ALL
SELECT r2.prvni, r2.druhy, a2.typ, lvl + 1 AS lvl
FROM ref r2
INNER JOIN zpr a2
ON a2.pismeno = r2.druhy
INNER JOIN rcte rc
ON r2.prvni = rc.druhy),
orders AS
(SELECT * FROM rcte WHERE typ = 'ORDERS')
SELECT prvni,
z1.typ prvni_typ,
connect_by_root(druhy) AS druhy,
z2.typ druhy_typ,
sys_connect_by_path(druhy, ' => ') || ' => ' || prvni AS path,
sys_connect_by_path(z2.typ, ' => ') AS typy
FROM ref, zpr z1, zpr z2
WHERE ref.prvni = z1.pismeno
AND ref.druhy = z2.pismeno
--and CONNECT_BY_ISLEAF = 1
CONNECT BY PRIOR prvni = druhy
START WITH ref.druhy IN (SELECT druhy FROM orders);
Here's the fiddle: http://sqlfiddle.com/#!4/5a3181/38

How do I group the results by company?

I have the following sql fiddle:
CREATE TABLE companies (pk serial PRIMARY KEY, name text, max int);
INSERT INTO companies(name, max)
VALUES
('Company A', 3),
('Company B', 8),
('Company C', -1);
CREATE TABLE employees (pk serial PRIMARY KEY, company integer REFERENCES companies (pk),
name text, joined timestamp);
INSERT INTO employees (company, name, joined)
VALUES
(2, 'Jane', '2015-09-23 14:46:57'),
(2, 'Jack', '2015-09-23 14:46:57'),
(3, 'Frank', '2015-09-23 14:51:07'),
(2, 'Bob', '2015-09-23 14:56:11'),
(1, 'Carl', '2015-09-23 16:12:05'),
(1, 'Jason', '2015-09-23 16:15:35'),
(3, 'Fred', '2015-09-23 16:28:35'),
(2, 'Bruce', '2015-09-23 16:35:51'),
(1, 'Brian', '2015-09-23 16:36:17'),
(1, 'Ryan', '2015-09-23 16:36:22'),
(1, 'Peter', '2015-09-23 16:37:04'),
(3, 'Ed', '2015-09-23 16:37:11'),
(2, 'Jenny', '2015-09-23 16:37:15'),
(2, 'Jessica', '2015-09-24 09:52:46'),
(3, 'Anita', '2015-09-24 10:01:19'),
(3, 'Melanie', '2015-09-24 10:05:27'),
(3, 'Kathryn', '2015-09-24 10:05:29'),
(2, 'Ashely', '2015-09-24 10:19:46'),
(1, 'Valerie', '2015-09-24 14:49:05'),
(2, 'Jimmy', '2015-09-24 15:42:45'),
(3, 'Johnny', '2015-09-24 17:38:06'),
(1, 'Mick', '2015-09-25 14:49:10');
SELECT * -- choose the columns you want here
FROM (SELECT e.*, c.max,
row_number() over (partition by company order by joined desc) as rank
FROM employees e JOIN
companies c
on e.company = c.pk
) e
WHERE rank <= max or max = -1
This gives:
pk company name joined max rank
22 1 Mick 2015-09-25T14:49:10Z 3 1
19 1 Valerie 2015-09-24T14:49:05Z 3 2
11 1 Peter 2015-09-23T16:37:04Z 3 3
20 2 Jimmy 2015-09-24T15:42:45Z 8 1
18 2 Ashely 2015-09-24T10:19:46Z 8 2
14 2 Jessica 2015-09-24T09:52:46Z 8 3
13 2 Jenny 2015-09-23T16:37:15Z 8 4
8 2 Bruce 2015-09-23T16:35:51Z 8 5
4 2 Bob 2015-09-23T14:56:11Z 8 6
1 2 Jane 2015-09-23T14:46:57Z 8 7
2 2 Jack 2015-09-23T14:46:57Z 8 8
21 3 Johnny 2015-09-24T17:38:06Z -1 1
17 3 Kathryn 2015-09-24T10:05:29Z -1 2
16 3 Melanie 2015-09-24T10:05:27Z -1 3
15 3 Anita 2015-09-24T10:01:19Z -1 4
12 3 Ed 2015-09-23T16:37:11Z -1 5
7 3 Fred 2015-09-23T16:28:35Z -1 6
3 3 Frank 2015-09-23T14:51:07Z -1 7
How do I get it so that the results are grouped by company? e.g. I'd like 3 rows (1 for each company) and then an array of the employees for each. For instance, Company A would look like:
1 [{"name": "Mick", "joined": "2015-09-25T14:49:10Z", "rank": 1},{"name": "Valerie", "joined": "2015-09-24T14:49:05Z", "rank": 2},{"name": "Peter", "joined": "2015-09-23T16:37:04Z", "rank": 3}]
I have been trying various GROUP By statements and keep running into varous errors where the sql is invalid, etc.
Your sample output looks a bit like a JSON array (however, not a valid JSON value), so maybe you are looking for something like this:
select c.pk, jsonb_agg(to_jsonb(e))
from employees e
join companies c on e.company = c.pk
group by c.pk;
To get the three "most recent" employees you can use:
select c.pk, jsonb_agg(e3.emp)
from (
select company,
to_jsonb(e) as emp,
row_number() over (partition by company order by joined desc) as rn
from employees e
) e3
join companies c on e.company = c.pk
where e3.rn <= 3;
Online Example: https://rextester.com/RPSI96409