SQL subqueries PostgreSQL 12 - sql

I'm having a table similar to this:
first
last
date
pos
john
doe
18-03-2021
harris
potter
10-06-2021
john
doe
10-05-2021
harris
potter
14-06-2021
jessica
potter
14-06-2021
kermit
foster
The use case is as follow:
The pos column correspond to a positive covid test
The date column correspond to the vaccination date
To be elligible for a covid certificate, some one must either:
Been tested positive and have got 1 vaccine
Have receive 2 vaccine
I'm trying to write a query that return me:
totalDose, totalRequieredDose
For exemple:
If he has tested positive, the totalRequiredDose is 1 and if he has got 1 vaccine, he is elligible. As such, for Harry Potter, totalDoses=1 and totalRequieredDoses=1 and is elligible
If he has not been tested positive, the totalRequiredDose is 2 and if he has got 2 vaccines, he is elligible. As such, for John Doe, totalDoses=2 and totalRequieredDoses=2 and is elligible
first
last
totalDoses
totalRequieredDoses
john
doe
2
2
harris
potter
1
1
jessica
potter
1
2
kermit
foster
0
2
As Jessica Potter have a vaccine and no pos date, she must have 2 vaccines.
So the value 1/2
And Kermit foster have no pos value, he is 0/2
Etc.
I'm scratching my head to write a query (or pl/sql) that could return me such table.
Could someone give me some hints ?

We can aggregate by first and last name. The total doses is simply the count of non NULL vaccination dates. For the total required number of doses, we can start with a value of 2. This value can then be offset by 1 assuming there exists a non NULL date for the pos column, indicating that a given person tested positive at some point.
SELECT
first,
last,
COUNT(date) AS totalDoses,
2 - (COUNT(*) FILTER (WHERE pos IS NOT NULL) > 0)::int AS totalRequieredDoses
FROM yourTable
GROUP BY
first,
last
ORDER BY
COUNT(date) DESC,
first,
last;
Demo

Related

Is this multiple join on 2 tables possible?

I have 2 tables and I am having trouble joining it to give me the desired output.
First table is called Future. It is future meetings I have.
Date Name Subject Importance Location
7/08/2020 David Work 1 London
7/08/2020 George Updates 2 New York
7/08/2020 Frank New Appointments 5 London
7/08/2020 Steph Policy 1 Paris
The second table is called Previous. It is previous meetings I have had.
Date Name Subject Importance Location Time Rating
1/08/2020 David Work 3 London 23.50 4
2/10/2018 David Emails 3 New York 18.20 3
1/08/2019 George New Appointments5 London 55.10 2
3/04/2020 Steph Dismissal 1 Paris 33.20 5
Now what I need to is to reference my previous table by name to see the previous meetings I have had with this person and I want all the data from the Previous Table there. I also need to limit it to only showing maximum 5 previous meetings with each person.
Date Name Subject Importance Location Time Rating
7/08/2020 David Work 1 London - -
1/08/2020 David Work 3 London 23.50 4
2/10/2018 David Emails 3 New York 18.20 3
7/08/2020 George Updates 2 New York - -
1/08/2019 George New Appointments5 London 55.10 2
The Name column will need to be a left join, but then i need to just do a regular join on the other columns. Also unsure how to limit the name results to a maximum of 5 of the same value. Thanks for your help in advance.
Basically, you want union all:
select m.*
from ((select Date, Name, Subject, Importance, Location, NULL as time, NULL as rating
from future
) union all
(select Date, Name, Subject, Importance, Location, time, rating
from previous
)
) m
group by name, date desc;
You can apply other conditions to this result. It is not clear what other conditions you really want, but this is a start.

Using DB2 SQL, how can I avoid returning rows that aren't the most recent, for a condition that is NOT part of my where clause?

SCENARIO DATA:
Row Col1 Col2 Col3 Col4 Col5
1 Bob Smith 2000-01-01 John Doe
2 Bob Smith 2010-01-01 Jane Jones
3 Ted Jones 2005-01-01 Pete Mills
4 Ted Jones 2008-01-01 John Doe
My SQL:
select col1, col2 from schema.table where col4='John' and col5='Doe'
I have no clue how to restrict my data as per need stated below.
I'm searching for where John Doe is in column 4/5 BUT I only want to retrieve the row if it's the current relationship (based on column 3) for the person in columns 1/2.
In the above data, I DO want the 4th row because John Doe is currently related to Ted Jones. But I do NOT want to retrieve the first row...because the John Doe row is NOT the current relation for Bob Smith. The current relation for Bob Smith is to Jane Jones.
IF I were searching for Pete Mills, I wouldn't want to find anything because Pete Mills is not the current relationship for Ted Jones.
You need to use something that DB2 call OLAP functions (other RDMS engines tend to call them window functions - both terms are valid). An OLAP function allows you to return analytic data about a row, e.g. what number it is in a sorted list.
For your query, you only want to return the first row for each person, so we use an OLAP function called ROW_NUMBER(). When we use this function, we want to partition over the Col1/Col2 data (i.e. we are grouping on that data) and then reverse sort on Col3 to get the latest result. After that, we want to pick the rows that have the top value in the OLAP function, like so:
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY Col1, Col2 ORDER BY Col3 DESC) AS rn
FROM schema.table
) AS ranked_scenarios
WHERE rn = 1

SQL Server group by? [duplicate]

This question already has answers here:
Retrieving last record in each group from database - SQL Server 2005/2008
(2 answers)
Closed 4 years ago.
I'm not sure how to word my question so perhaps an example would be best. I'm looking for a function or statement that would produce the following result from a single table. For each name, return the row with largest id.
ID NAME ADDRESS
1 JOHN DOE 123 FAKE ST.
2 JOHN DOE 321 MAIN ST.
3 JOHN DOE 333 2ND AVE.
4 MARY JANE 222 1ST. AVE
5 MARY JANE 444 POPLAR ST.
6 SUZY JO 999 8TH AVE.
DESIRED RESULT
3 JOHN DOE 333 2ND AVE.
5 MARY JANE 444 POPLAR ST.
6 SUZY JO 999 8TH AVE.
One option is to use the row_number window function. This allows you to establish a row number to the result set. Then you can define the grouping and ordering within the over clause, in this case you want to partition by (group) the name field and order by the id field descending. Finally you filter those results where rn = 1 which returns the max result for each grouping.
select *
from (
select *, row_number() over (partition by name order by id desc) rn
from yourtable
) t
where rn = 1

Add incremental number by matching the value of another column

Below is my SQL Server 2012 query example. How do I add an extra column “StaffNo” to show an incremental integer?
This int always starts with 1, it doesn’t need to be matching with the staff name. for example “Joe” in this query running shows 1 in staff No, in next query running he could be 2 or 3 or any other number.
Same user always appear same staff No. Different user appear different No.
The number must be sequential and the increment is 1.
Because Staff is more than 100, so don’t write the query like “select case when staff = ‘Joe’ then 1 End”.
my query:
Staff CaseNumber
Joe 5880
Joe 4489
Joe 2235
Emily 7790
Emily 8813
expected result:
Staff CaseNumber StaffNo
Joe 5880 1
Joe 4489 1
Joe 2235 1
Emily 7790 2
Emily 8813 2
Use DENSE_RANK over the entire table, without a partiton, and order by the staff member's name.
SELECT
Staff,
CaseNumber,
DENSE_RANK() OVER (ORDER BY Staff) StaffNo
FROM yourTable;
Demo here:
Rextester

Excluding results that appear in another column of a CONNECT BY query

Have a heavy query (takes 15 minutes to run), but it's returning more results than I need. It's a CONNECT BY query, and I'm getting nodes that are descendants in the root node results. I.E.:
Ted
Bob
John
Bob
John
John
Normally, the way to resolve this is using a START WITH condition, typically requiring the parent of a node to be null. But due to the nature of the query, I don't have the START WITH values I need to compare to until I have the full resultset. I'm basically trying to double-query my results to say QUERY STUFF START WITH RECORDS THAT AREN'T IN THAT STUFF.
Here's the query (built with the help of Nicholas Krasnov, here: Oracle Self-Join on multiple possible column matches - CONNECT BY?):
select cudroot.root_user, cudroot.node_level, cudroot.user_id, cudroot.new_user_id,
cudbase.* -- Not really, just simplyfing
from css.user_desc cudbase
join (select connect_by_root(user_id) root_user,
user_id user_id,
new_user_id new_user_id,
level node_level
from (select cudordered.user_id,
coalesce(cudordered.new_user_id, cudordered.nextUser) new_user_id
from (select cud.user_id,
cud.new_user_id,
decode(cud.global_hr_id, null, null, lead(cud.user_id ignore nulls) over (partition by cud.global_hr_id order by cud.user_id)) nextUser
from css.user_desc cud
left join gsu.stg_userdata gstgu
on (gstgu.user_id = cud.user_id
or (gstgu.sap_asoc_global_id = cud.global_hr_id))
where upper(cud.user_type_code) in ('EMPLOYEE','CONTRACTOR','DIV_EMPLOYEE','DIV_CONTRACTOR','DIV_MYTEAPPROVED')) cudordered)
connect by nocycle user_id = prior new_user_id) cudroot
on cudbase.user_id = cudroot.user_id
order by
cudroot.root_user, cudroot.node_level, cudroot.user_id;
This gives me results about related users (based off of user_id renames or associated SAP IDs) that look like this:
ROOT_ID LEVEL USER_ID NEW_USER_ID
------------------------------------------------
A5093522 1 A5093522 FG096489
A5093522 2 FG096489 A5093665
A5093522 3 A5093665
FG096489 1 FG096489 A5093665
FG096489 2 A5093665
A5093665 1 A5093665
What I need is a way to filter the first join (select connect_by_root(user_id)... to exclude FG096489 and A5093665 from the root list.
The best START WITH I can think of would look like this (not tested yet):
start with user_id not in (select new_user_id
from (select coalesce(cudordered.new_user_id, cudordered.nextUser) new_user_id
from (select cud.new_user_id,
decode(cud.global_hr_id, null, null, lead(cud.user_id ignore nulls) over (partition by cud.global_hr_id order by cud.user_id)) nextUser
from css.user_desc cud
where upper(cud.user_type_code) in ('EMPLOYEE','CONTRACTOR','DIV_EMPLOYEE','DIV_CONTRACTOR','DIV_MYTEAPPROVED')) cudordered)
connect by nocycle user_id = prior new_user_id)
... but I'm effectively executing my 15 minute query twice.
I've looked at using partitions in the query, but there's not really a partition... I want to look at the full resultset of new_user_ids. Have also explored analytical functions such as rank()... my bag of tricks is empty.
Any ideas?
Clarification
The reason I don't want the extra records in the root list is because I only want one group of results for each user. I.E., if Bob Smith has had four accounts during his career here (people come and go frequently, as employees and/or contractors), I want to work with a set of accounts that all belong(ed) to Bob Smith.
If Bob came here as an contractor, converted to an employee, left, came back as a contractor in another country, and left/returned to a legal org that is now in our SAP system, his account rename/chain might look like:
Bob Smith CONTRACTOR ---- US0T0001 -> US001101 (given a new ID as an employee)
Bob Smith EMPLOYEE ---- US001101 -> EB0T0001 (contractor ID for the UK)
Bob Smith CONTRACTOR SAP001 EB0T000T (no rename performed)
Bob Smith EMPLOYEE SAP001 TE110001 (currently-active ID)
In the above example, the four accounts are linked by either a new_user_id field that was set when the user was renamed or through having the same SAP ID.
Because HR frequently fails to follow the business process, returning users may end up with any of those four ID being restored to them. I have to analyze all the IDs for Bob Smith and say "Bob Smith can only have TE110001 restored", and kick back an error if they try to restore something else. I have to do it for 90,000+ records.
The first column, "Bob Smith", is just an identifier to the group of associated accounts. In my original example, I'm using the root User ID as the identifier (e.g. US0T0001). If I use first/last names to identify users, I end up with collisions.
So Bob Smith would look like this:
US0T0001 1 CONTRACTOR ---- US0T0001 -> US001101 (given a new ID as an employee)
US0T0001 2 EMPLOYEE ---- US001101 -> EB0T0001 (contractor ID for the UK)
US0T0001 3 CONTRACTOR SAP001 EB0T0001 (no rename performed)
US0T0001 4 EMPLOYEE SAP001 TE110001 (currently-active ID)
... where 1, 2, 3, 4 are the levels in the heirarchy.
Since US0T0001, US001101, EB0T0001, and TE110001 are all accounted for, I don't want another group for them. But the results I have now have those accounts listed in multiple groups:
US001101 1 EMPLOYEE ---- US001101 -> EB0T0001 (
US001101 2 CONTRACTOR SAP001 EB0T0001
US001101 3 EMPLOYEE SAP001 TE110001
EB0T0001 1 CONTRACTOR SAP001 EB0T0001
EB0T0001 2 EMPLOYEE SAP001 TE110001
US001101 1 EMPLOYEE SAP001 TE110001
This causes two problems:
When I query the results for a User ID, I get hits from multiple groups
Each group will report a different expected user ID for Bob Smith.
You asked for an expanded set of records... here are some actual data:
-- NumRootUsers tells me how many accounts are associated with a user.
-- The new user ID field is explicitly set in the database, but may be null.
-- The calculated new user ID analyzes records to determine what the next related record is
NumRoot New User Calculated
RootUser Users Level UserId ID Field New User ID SapId LastName FirstName
-----------------------------------------------------------------------------------------------
BG100502 3 1 BG100502 BG1T0873 BG1T0873 GRIENS VAN KION
BG100502 3 2 BG1T0873 BG103443 BG103443 GRIENS VAN KION
BG100502 3 3 BG103443 41008318 VAN GRIENS KION
-- This group causes bad matches for Kion van Griens... the IDs are already accounted for,
-- and this group doesn't even grab all of the accounts for Kion. It's also using a new
-- ID to identify the group
BG1T0873 2 1 BG1T0873 BG103443 BG103443 GRIENS VAN KION
BG1T0873 2 2 BG103443 41008318 VAN GRIENS KION
-- Same here...
BG103443 1 1 BG103443 41008318 VAN GRIENS KION
-- Good group of records
BG100506 3 1 BG100506 BG100778 41008640 MALEN VAN LARS
BG100506 3 2 BG100778 BG1T0877 41008640 MALEN VAN LARS
BG100506 3 3 BG1T0877 41008640 VAN MALEN LARS
-- Bad, unwanted group of records
BG100778 2 1 BG100778 BG1T0877 41008640 MALEN VAN LARS
BG100778 2 2 BG1T0877 41008640 VAN MALEN LARS
-- Third group for Lars
BG1T0877 1 1 BG1T0877 41008640 VAN MALEN LARS
-- Jan... fields are set differently than the above examples, but the chain is calculated correctly
BG100525 3 1 BG100525 BG1T0894 41008651 ZANWIJK VAN JAN
BG100525 3 2 BG1T0894 TE035165 TE035165 41008651 VAN ZANWIJK JAN
BG100525 3 3 TE035165 41008651 VAN ZANWIJK JAN
-- Bad
BG1T0894 2 1 BG1T0894 TE035165 TE035165 41008651 VAN ZANWIJK JAN
BG1T0894 2 2 TE035165 41008651 VAN ZANWIJK JAN
-- Bad bad
TE035165 1 1 TE035165 41008651 VAN ZANWIJK JAN
-- Somebody goofed and gave Ziano a second SAP ID... but we still matched correctly
BG100527 3 1 BG100527 BG1T0896 41008652 STEFANI DE ZIANO
BG100527 3 2 BG1T0896 TE033030 TE033030 41008652 STEFANI DE ZIANO
BG100527 3 3 TE033030 42006172 DE STEFANI ZIANO
-- And we still got extra, unwanted groups
BG1T0896 3 2 BG1T0896 TE033030 TE033030 41008652 STEFANI DE ZIANO
BG1T0896 3 3 TE033030 42006172 DE STEFANI ZIANO
TE033030 3 3 TE033030 42006172 DE STEFANI ZIANO
-- Mark's a perfect example of the missing/frustrating data I'm dealing with... but we still matched correctly
BG102188 3 1 BG102188 BG1T0543 41008250 BULINS MARK
BG102188 3 2 BG1T0543 TE908583 41008250 BULINS R.J.M.A.
BG102188 3 3 TE908583 41008250 BULINS RICHARD JOHANNES MARTINUS ALPHISIUS
-- Not wanted
BG1T0543 3 2 BG1T0543 TE908583 41008250 BULINS R.J.M.A.
BG1T0543 3 3 TE908583 41008250 BULINS RICHARD JOHANNES MARTINUS ALPHISIUS
TE908583 3 3 TE908583 41008250 BULINS RICHARD JOHANNES MARTINUS ALPHISIUS
-- One more for good measure
BG1T0146 3 1 BG1T0146 BG105905 BG105905 LUIJENT VALERIE
BG1T0146 3 2 BG105905 TE034165 42006121 LUIJENT VALERIE
BG1T0146 3 3 TE034165 42006121 LUIJENT VALERIE
BG105905 3 2 BG105905 TE034165 42006121 LUIJENT VALERIE
BG105905 3 3 TE034165 42006121 LUIJENT VALERIE
TE034165 3 3 TE034165 42006121 LUIJENT VALERIE
Not sure if all that info makes it clearer or will make your eyes roll back into your head : )
Thanks for looking at this!
I think I have it. We have allowed ourselves to become fixated on the chronological order whereas in fact it doesn't matter. Your START WITH clause should be 'NEW_USER_ID IS NULL'.
To get chronological order you could 'ORDER BY cudroot.node_level * -1'.
I would also recommend that you look at using a WITH clause to form your base data and perform the heirarchical query on that.
Perhaps what you need here is multiple queries. Each query will find a subset of the records you are trying to find. Each query will hopefully be simpler and faster than a single, ginormous query. Something like:
where new_user_id is null and SAP ID is null
where new_user_id is not null and SAP ID is null
where new_user_id is null and SAP ID is not null
where new_user_id is not null and SAP ID is not null
(these are of the cuff examples)
I think part of the problem with solving this conundrum is that the problem space is too large. By subdividing this problem into smaller pieces, each piece will be workable.