Different SQL selection based on inter-record condition - sql

I have a table that holds allocations of problem reports (PRs) as follows:
TABLE "ALLOCATIONS"
ALLOCATIONID PRID DATEALLOCATED ENG_ID
1 401 20-SEP-06 10.48.00 1
2 401 20-SEP-06 10.48.00 2
3 401 20-SEP-06 10.48.00 2
4 402 20-SEP-06 12.35.00 1
5 402 20-SEP-06 12.43.00 1
6 402 20-SEP-06 13.43.00 2
7 700 14-OCT-12 13.30.05 1
8 700 14-OCT-12 13.30.35 2
9 700 14-OCT-12 14.30.35 2
The most recent allocation determines which engineer the PR is now assigned to. I want to find all the PRs that are assigned to engineer 2 for example.
So I look for the most recent allocation for each PRID, check the ENG_ID, then pull out the information from this table if the ENG_ID is correct.
This table contains the actual PR descriptions (and other info omitted here for clarity).
TABLE "PROBLEMS"
PRID TITLE
401 Something
402 Something
700 Something
To do this I have used the DATEALLOCATED field as follows:
SELECT PRID, TITLE FROM PROBLEMS p WHERE p.PRID IN
(
SELECT GROUPEDALLOC.PRID FROM allocations alloc INNER JOIN
(
SELECT PRID, MAX(DATEALLOCATED) AS MaxAllocationDate
FROM allocations
GROUP BY PRID
)
groupedAlloc ON alloc.PRID = groupedAlloc.PRID
AND ALLOC.DATEALLOCATED = groupedAlloc.MaxAllocationDate
AND ENG_ID = 2
)
ORDER BY PRID DESC;
Now this works fine for records 7,8,9 above which were inserted with a long date format that includes the seconds, however for the older records which didn't log the seconds this will obviously not work. For these records I want to fall back on the allocationID (which may or may not be sequential obviously - however it is a last resort and better than nothing).
My question is, how do I modify my query to perform this extra condition on the DATEALLOCATED (i just want to see if they are all equal for a particular PRID), and then use the ALLOCATIONID instead?
I am using OracleXE but I want to stick to standard SQL if possible.

Does this do it for you ?
WITH
BY_DATE
AS (SELECT PRID, MAX (DATEALLOCATED) AS MAXDATE FROM ALLOCATIONS GROUP BY PRID),
BY_ALLOC
AS (SELECT A.PRID, MAXDATE, MAX (ALLOCATIONID) AS MAXALLOC
FROM ALLOCATIONS A JOIN BY_DATE B ON
A.PRID = B.PRID AND
A.DATEALLOCATED = B.MAXDATE
GROUP BY A.PRID, MAXDATE)
SELECT A.PRID, A.ENG_ID
FROM ALLOCATIONS A JOIN BY_ALLOC B ON
A.ALLOCATIONID = B.MAXALLOC;

Related

How do I stop my query from pulling duplicates?

Yes, I know this seems simple:
SELECT DISTINCT(...)
Except, it apparently isn't
Here is my actual Query:
SELECT
DeclinationReasons.Reason,
EmployeeInformation.ID,
EmployeeInformation.Employee,
EmployeeInformation.Active,
CompletedTrainings.DecShotDate,
CompletedTrainings.DecShotLocation,
CompletedTrainings.DecReason,
CompletedTrainings.DecExplanation,
IIf([DecShotLocation]="MCS","Yes","No") AS YesMCS,
IIf([DecReason]=1,1,0) AS YesAllergy,
IIf([DecReason]=2,1,0) AS YesImmune,
IIf([DecReason]=3,1,0) AS YesAdverse,
IIf([DecReason]=4,1,0) AS YesMedical,
IIf([DecReason]=5,1,0) AS YesSpiritual,
IIf([DecReason]=6,1,0) AS YesOther,
IIf([DecReason]=7,1,0) AS YesAlready
FROM
EmployeeInformation
INNER JOIN (CompletedTrainings
LEFT JOIN DeclinationReasons ON CompletedTrainings.DecReason = DeclinationReasons.ReasonID)
ON EmployeeInformation.ID = CompletedTrainings.Employee
GROUP BY
DeclinationReasons.Reason,
EmployeeInformation.ID,
EmployeeInformation.Employee,
EmployeeInformation.Active,
CompletedTrainings.DecShotDate,
CompletedTrainings.DecShotLocation,
CompletedTrainings.DecReason,
CompletedTrainings.DecExplanation,
IIf([DecShotLocation]="MCS","Yes","No"),
IIf([DecReason]=1,1,0),
IIf([DecReason]=2,1,0),
IIf([DecReason]=3,1,0),
IIf([DecReason]=4,1,0),
IIf([DecReason]=5,1,0),
IIf([DecReason]=6,1,0),
IIf([DecReason]=7,1,0)
HAVING
((((EmployeeInformation.Active) Like -1)
AND ((CompletedTrainings.DecShotDate + 365 >= DATE())
OR (CompletedTrainings.DecShotDate IS NULL))));
This is Joining a few tables (obviously) in order to get a number of records. The problem is that if someone is duplicated on the table with a NULL in one of the date fields, and a date in another field, it pulls both the NULL and the DATE, or pulls multiple NULLS it might pull multiple dates but those are not present right at the moment.
I need the Nulls, they are actual data in this particular case, but if someone has a date and a NULL I need to pull only the newest record, I thought I could add MAX(RecordID) from the table, but that didn't change the results of the query either.
That code:
SELECT
DeclinationReasons.Reason,
EmployeeInformation.ID,
EmployeeInformation.Employee,
EmployeeInformation.Active,
MAX(CompletedTrainings.RecordID),
CompletedTrainings.DecShotDate
...
And it returned the same issue, Duplicated EmployeeInformation.ID with different DecShotDate values.
Currently it returns:
ID
Active
DecShotDate
etc. x a bunch
1
-1
date date
whatever goes
2
-1
in these
2
-1
date date
columns
These are being used in a report, that is to determine the total number of employees who fit the criteria of the report. The NULLs in DecShotDate are needed as they show people who did not refuse to get a flu vaccine in the current year, while the dates are people who did refuse.
Now I have come up with one simple solution, I could add a column to the CompletedTrainings Table that contains a date or other value, and add that to the HAVING statement. This might be the right solution as this is a yearly training questionnaire that employees have to fill out. But I am asking for advice before doing this.
Am I right in thinking I need to add a column to filter by so that older data isn't being pulled, or should I be able to do this by pulling recordID, and did I just bork that part of the query up?
Edited to add raw table views:
EmployeeInformation Table:
ID
Last
First
empID
Active
Termdate
DoH
Title
PT/FT/PD
PI
1
Doe
Jane
982
-1
date
Sr
PD
X
2
Roe
John
278
0
date
date
Jr
PD
X
3
Moe
Larry
1232
-1
date
Sr
FT
X
4
Zoe
Debbie
1424
-1
date
Sr
PT
X
DeclinationReasons Table:
ReasonID
Reason
1
Allergy
2
Already got it
3
Illness
CompletedTrainings Table:
RecordID
Employee
Training
...
DecShotdate
DecShotLocation
DecShotReason
DecExp
1
1
4
date
location
2
text
2
1
4
3
2
4
4
3
4
date
location
3
text
5
3
4
date
location
1
text
6
4
4
After some serious soul searching, I decided to use another column and filter by that.
In the end my query looks like this:
SELECT *
FROM (
(
SELECT RecordID, DecShotDate, DecShotLocation, DecReason, DecExplanation, Employee,
IIf([DecShotLocation]="MCS","Yes","No") AS YesMCS, IIf([DecReason]=1,1,0) AS YesAllergy,
IIf([DecReason]=2,1,0) AS YesImmune, IIf([DecReason]=3,1,0) AS YesAdverse,
IIf([DecReason]=4,1,0) AS YesMedical, IIf([DecReason]=5,1,0) AS YesSpiritual,
IIf([DecReason]=6,1,0) AS YesOther, IIf([DecReason]=7,1,0) AS YesAlready
FROM CompletedTrainings WHERE (CompletedDate > DATE() - 365 ) AND (Training = 69)) AS T1
LEFT JOIN
(
SELECT ID, Active FROM EmployeeInformation) AS T2 ON T1.Employee = T2.ID)
LEFT JOIN
(
SELECT Reason, ReasonID FROM DeclinationReasons) AS T3 ON T1.DecReason = T3.ReasonID;
This may not have been the best solution, but it did exactly what I needed. Which is to get the information by latest entry into the database.
Previously I had tried to use MAX(), DISTINCT(), etc. but always had a problem of multiple records being retrieved. In this case, I intentionally SELECT the most recent records first, then join them to the results of the next query, and so on. Until I have all the required data for my report.
I write this in hopes someone else finds it useful. Or even better if someone tells me why this is wrong, so as to improve my own skills.

SQL Query Count results as zero event record is not exist

I have an issue to an SQL Query (Oracle database).
I have two tables. One table is "ACCIDENTS " and the other is "REASONS". This second is table has some predefined default values.
- REASONS -
Reason 1
Reason 2
Reason 3
Reason 4
Now in the ACCIDENTS table we insert some accidents and reasons from the previous table like below
- ACCIDENTS -
Accident 1 - Reason 1
Accident 2 - Reason 1
Accident 3 - Reason 4
All I want to get the count of accidents GROUP BY all 4 reasons even if a reason does not exist in ACCIDENTS table. In this case I want to get Count = 0 like below:
REASONS COUNT (of Accidents)
Reason 1 2
Reason 2 0
Reason 3 0
Reason 4 1
Unsuccessfully I have already tried different type of JOIN tables but I don't get as results Reason 2 and Reason 3 because they don't exist in ACCIDENTS table. Every time the result is:
REASONS COUNT (of Accidents)
Reason 1 2
Reason 4 1
Any solutions/thoughts ?
Thanks in advance!
UPDATE !
This is the query:
SELECT R.REASON_NAME AS REASON, COUNT(A.ID) AS COUNT_OF_ACCIDENTS
FROM ACCIDENTS A
RIGHT JOIN REASONS R ON R.ID = A.REASON_ID
WHERE EXTRACT(YEAR FPOM A.DATE_OF_ACCIDENT) = 2017
GROUP BY R.REASON_NAME
If i remove the WHERE statement then I get all REASONS correctly but the Where for Year in Accidents table is mandatory.
You want a left join and aggregation:
select r.reason, count(a.reason)
from reasons r left join
accidents a
on r.reason = a.reason and
a.date_of_accident >= date '2017-01-01' and
a.date_of_accident < date '2018-01-01'
group by r.reason;

How to make one column fixed?

There is one scheme and different items inside it, so the scenario is that if user send SchemeID to the procedure then it should return the SchemeName(once) and all items inside a scheme i.e. DescriptionOfitem, Quantity, Rate, Amount... in this format
SchemeName DescriptionOfItems Quantity Unit Rate Amount
Scheme01 Bulbs 2 M2 200 400
Titles 10 M3 300 3000
SolarPanels 2 M2 1000 2000
Bricks 50 M9 50 2500
Total 7900
My try, it works but it also repeats the SchemeName for each row and can't find total
Select
Schemes.SchemeName,
ContractorsWorkDetails.ContractorsWorkDetailsItemDescription,
ContractorsWorkDetails.ContractorsWorkDetailsUnit,
ContractorsWorkDetails.ContractorsWorkDetailsItemQuantity,
ontractorsWorkDetails.ContractorsWorkDetailsItemRate,
ContractorsWorkDetails.ContractorsWorkDetailsAmount
From ContractorsWorkDetails
Inner Join Schemes
ON Schemes.pk_Schemes_SchemeID= ContractorsWorkDetails.fk_Schemes_ContractorsWorkDetails_SchemeID
Where ContractorsWorkDetails.fk_Schemes_ContractorsWorkDetails_SchemeID= 2
Update:
I tested the query as suggested below but it gives this kinda result
You can get the total using grouping sets. I would advise you to keep the schema name on each row. If you want it filtered out on certain rows, then do that at the application layer.
Now, having said that, I think this will do what you want in SQL:
Select (case when GROUPING(cwd.ContractorsWorkDetailsItemDescription) = 0
then 'Total'
when row_number() over (partition by s.SchemeName
order by cwd.ContractorsWorkDetailsItemDescription
) = 1
then s.SchemeName else ''
end) as SchemeName,
cwd.ContractorsWorkDetailsItemDescription,
cwd.ContractorsWorkDetailsUnit,
cwd.ContractorsWorkDetailsItemQuantity,
cwd.ContractorsWorkDetailsItemRate,
SUM(cwd.ContractorsWorkDetailsAmount) as ContractorsWorkDetailsAmount
From ContractorsWorkDetails cwd Inner Join
Schemes s
ON s.pk_Schemes_SchemeID = cwd.fk_Schemes_ContractorsWorkDetails_SchemeID
Where cwd.fk_Schemes_ContractorsWorkDetails_SchemeID = 2
group by GROUPING SETS ((s.SchemeName,
cwd.ContractorsWorkDetailsItemDescription,
cwd.ContractorsWorkDetailsUnit,
cwd.ContractorsWorkDetailsItemQuantity,
cwd.ContractorsWorkDetailsItemRate
), s.SchemeName)
Order By GROUPING(cwd.ContractorsWorkDetailsItemDescription),
s.SchemeName, cwd.ContractorsWorkDetailsItemDescription;
The reason you don't want to do this in SQL is because the result set no longer has a relational structure: the ordering of the rows is important.

Convert list of transitions (points in time) to list of states (periods of time)

Did something similar long ago, but when I think I'm doing the same thing now, it doesn't work.
A history table is a list of events happening to accounts. Some of those events are changes in status, in which case a multipurpose Detail column shows the new status. Sample:
... where Event_Type = 'Change_Status';
Acct Line Event_Type Detail
---- ---- ------------- -------
A 1 Change_Status Created
A 4 Change_Status Billed
A 7 Change_Status Paid
A 10 Change_Status Audited
B 1 Change_Status Created
B 6 Change_Status Billed
Now it is easy enough to join this to itself and get a table of time periods WHERE A.Acct = B.Acct and A.Line < B.Line but two things I'm failing on:
I also need to capture the last status, but in that case there is no end (B.*). I thought a left join would get it (B.Line is null) but it doesn't.
Need to eliminate periods that span more than one status, such as A-1 to A-7 Tried both items below, but either one eliminated everything.
AND A.LINE = (SELECT Max(Line) FROM Events TEMP
WHERE TEMP.Acct = A.Acct
AND TEMP.Line < B.Line or B.Line is null);
AND NOT EXISTS (SELECT Line FROM Events TEMP
WHERE TEMP.Acct = A.Acct
AND TEMP.Line between A.Line and B.Line);
If any of that is unclear, what I need to create is effectively
Acct Line Acct Line Status
---- ---- ---- ---- -------
from A 1 To A 4 Created
from A 4 To A 7 Billed
from A 7 To A 10 Paid
from A 10 To Audited
from B 1 To B 6 Created
I poked around with this on a postgres 9.1 database (so, ymmv). This is the query i came up with:
select
x.acct, x.line, y.line, x.status
from
statchanges x
left join statchanges y on x.acct = y.acct
and y.line > x.line
where
y.line is null or
(y.line - x.line =
(select min(y1.line - x1.line)
from statchanges x1, statchanges y1
where x1.acct = x.acct
and x1.line = x.line
and x1.acct = y1.acct
and y1.line > x1.line));
Important differences: 1- in the join clause, i'm joining on b.line > a.line, rather than a.line < b.line. This appears to be because (on postgres 9.1, at least) null is sorted after non-nulls, unless otherwise specified. 2- i'm jumping through some hoops to make sure i get the right min in the sub-query: making a very similar join (don't have to do a left join since we don't care about the nulls), and making sure the acct and starting line match with the outer query.
I'm not sure if this is completely what you're looking for, but it should hopefully give you some directions to explore.

Error when COUNT after MAX?

I have a table Act in a medical database :
IDAct Historic IDPatient
1 2001-01-01 1
1 2001-01-02 1
2 2001-01-01 2
3 2001-01-03 1
I would like to compute the column IDActPerPatient:
IDAct Historic IDPatient IDActPerPatient
1 2001-01-02 1 1
2 2001-01-01 2 1
3 2001-01-03 1 2
The Act table contains Acts with the historic of every modification of an act. (the index is the pair (IDAct,Historic)).
So I'm interested in the last modified acts:
SELECT A.IDActe, MAX(Historic) AS Historic FROM Act A GROUP BY IDAct
Now, I'd like to number the Acts per patient. So I count the number of acts with an IDAct less or equal for one patient.
I have created a view LastAct with the previous request and I try this one :
SELECT DA1.*, COUNT(*) AS IDActPerPatient
FROM LastAct DA1
INNER JOIN LastAct DA2 ON DA1.IDPatient = DA2.IDPatient
AND DA2.IDActe >= DA1.IDAct
GROUP BY DA1.IDAct
...which does not work!
I get large numbers in IDActPerPatient when an act has several version in historic (for a patient who has 1 act in 3 versions I have 81).
Do you have an idea where the problem comes from ?
SELECT A.IDActe,
MAX(Historic) AS Historic,
(SELECT COUNT(DISTINCT IDAct) FROM ACT B WHERE A.IDPatient=B.IDPatient)
FROM Act A
GROUP BY IDAct
?
Thank you! I don't use select in the select enough but It's really helpful here!
here is the fixed request:
SELECT A.IDAct, A.IDPatient,
MAX(Historic) AS Historic,
(SELECT COUNT(DISTINCT IDAct) FROM Act B WHERE A.IDPatient=B.IDPatient
AND A.IDAct>=B.IDAct) AS IDActPerPatient
FROM Act A
GROUP BY IDAct