TSQL : conditional query - sql

I have the following table :
| RoomID | OrderID | Occupancy | rn |
+--------+---------+-----------+----+
| 01 | 101 | Vacant | 1 |
| 01 | 102 | Occupied | 2 |
| 01 | 103 | Occupied | 3 |
| 01 | 104 | Vacant | 4 |
| 02 | 201 | Vacant | 1 |
| 02 | 202 | Occupied | 2 |
| 02 | 203 | Vacant | 3 |
| 03 | 301 | Vacant | 1 |
| 03 | 302 | Occupied | 2 |
| 03 | 303 | Occupied | 3 |
| 03 | 304 | Occupied | 4 |
| 04 | 401 | Occupied | 1 |
| 04 | 402 | Occupied | 2 |
| 04 | 403 | Vacant | 3 |
| 04 | 404 | Occupied | 4 |
I need to flag the RoomIDs where all of the following requirments are met as 'Yes' and if one or more requirements are not met as 'No':
when rn = 1 the Occupancy is vacant
When rn = 2 the Occupancy is Occupied
Any rn larger than 2 (3,4,5..) has an Occupancy of vacant
The result should look like the following:
| RoomID | OrderID |
+--------+---------+
| 01 | Yes |
| 02 | Yes |
| 03 | No |
| 04 | No |
I have an impression that this is easy but I cannot see it at the moment, thank you in advance for your help !

Builds on 'Yes' > 'No'
select RoomId,
min(case when rn<>2 and Occupancy='vacant'
or rn=2 and Occupancy='Occupied'
then 'Yes' else 'No' end) res
from myTable
group by RoomId

Using a CASE to calculate the rule for each row.
When the minimum of that rule (grouped by room_id) is 0, then at least 1 of them didn't follow the rule.
SELECT RoomID,
IIF(MIN(
CASE
WHEN rn <> 2 AND Occupancy = 'Vacant' THEN 1
WHEN rn = 2 AND Occupancy = 'Occupied' THEN 1
ELSE 0
END)=0,'No','Yes') as OccupancyRule
FROM RoomOccupancyTable
GROUP BY RoomID;

You can use Conditional Aggregation to check each condition.
with roomquery as (
select RoomId,
min(case when rn<>2 and Occupancy='vacant' then 1 else 0 end) cond1,
min(case when rn=2 and Occupancy='Occupied' then 1 else 0 end) cond2
from rooms r
group by RoomId)
select RoomId,
case when Cond1=1 and Cond2=1 then 'Yes' else 'No' end Status
from roomquery
Note: you can rewrite it and use only one condition or eve remove the CTE (with statement) altogether, but I believe this is more readable and maintainable.

I guess this will help you.
SELECT RoomID,
CASE WHEN ((rn = 1 OR rn > 2) AND Occupancy = 'Vacant') AND (rn = 2 AND Occupancy = 'Occupied') THEN 'Yes' ELSE 'No' END
FROM Table_Name

Here is another answer that counts the number of violations per RoomID in a subquery and then prints "yes" or "no" accordingly:
SELECT sub.roomid,
CASE WHEN sum(sub.orderid_no) IS NOT NULL THEN 'No'
ELSE 'Yes'
END AS orderID
FROM
(SELECT roomid,
CASE WHEN rn = 1 AND occupancy = 'occupied' THEN 1
WHEN rn = 2 AND occupancy = 'vacant' THEN 1
WHEN rn > 2 AND occupancy = 'occupied' THEN 1
END AS orderid_no
FROM T) sub
GROUP BY sub.roomid
Tested here: http://sqlfiddle.com/#!9/d9d467/14
Also, I think your output above is mistaken. According to your data, RoomID 01 should have OrderID "No"

Related

how to get balance sheet (debit , credit , balance) from transactions table in SQL?

if I have transactions table like that:
+----+--------+------------+-------------+--------+
| id | userID | debitAccID | creditAccID | amount |
+----+--------+------------+-------------+--------+
| 1 | 1 | 1 | 2 | 500 |
| 2 | 1 | 1 | 3 | 600 |
| 3 | 1 | 3 | 1 | 200 |
+----+--------+------------+-------------+--------+
how what query to use to get a table for account with id 1 like that:
+----+--------+------------+-------------+--------+
| debit | credit |balance |
+----+--------+------------+-------------+--------+
| | 500 | | 500 |
| | 600 | | 1100 |
| | | 200| 900 |
+----+--------+------------+-------------+--------+
900
Assuming the id column shows the correct order of transactions, you can use case and window with the default of rows between unlimited preceding and current row to get your output:
select id, user_id,
case when user_id = debit_acc_id then amount else 0 end as debit,
case when user_id = credit_acc_id then amount else 0 end as credit,
sum(case when user_id = debit_acc_id then amount else 0 end) over w
- sum(case when user_id = credit_acc_id then amount else 0 end) over w as balance
from transactions
where user_id = 1
window w as (partition by user_id order by id)
order by user_id, id;
db<>fiddle here

Partition By - Sum all values Excluding Maximum Value

I have data as follows
+----+------+--------+
| ID | Code | Weight |
+----+------+--------+
| 1 | M | 200 |
| 1 | 2A | 50 |
| 1 | 2B | 50 |
| 2 | | 350 |
| 2 | M | 350 |
| 2 | 3A | 120 |
| 2 | 3B | 120 |
| 3 | 5A | 100 |
| 4 | | 200 |
| 4 | | 100 |
+----+------+--------+
For ID 1 the max weight is 200, I want to subtract sum of all weights from ID 1 except the max value that is 200.
There might be a case when there are 2 rows containing max values for same id. Example for ID 2 we have 2 rows containing max value i.e. 350 . In such scenario I want to sum all values except the max value. But I would mark weight 0 for 1 of the 2 rows containing max value. That row would be the one where Code is NULL/Blank.
Case where there is only 1 row for an ID the row would be kept as is.
Another scenario could be one where there is only row containing max weight but Code is NULL/Blank in such case we would simply do what we did for ID 1. Sum all values except max value and subtract from row containing max value.
Desired Output
+----+------+--------+---------------+
| ID | Code | Weight | Actual Weight |
+----+------+--------+---------------+
| 1 | M | 200 | 100 |
| 1 | 2A | 50 | 50 |
| 1 | 2B | 50 | 50 |
| 2 | | 350 | 0 |
| 2 | M | 350 | 110 |
| 2 | 3A | 120 | 120 |
| 2 | 3B | 120 | 120 |
| 3 | 5A | 100 | 100 |
| 4 | | 200 | 100 |
| 4 | | 100 | 100 |
+----+------+--------+---------------+
I want to create column Actual Weight as shown above. I can't find a way to apply partition by excluding max value and create column Actual Weight.
dense_rank() to identify the row with max weight, dr = 1 is rows with max weight
row_number() to differentiate the max weight row for Code = blank from others
with cte as
(
select *,
dr = dense_rank() over (partition by ID order by [Weight] desc),
rn = row_number() over (partition by ID order by [Weight] desc, Code desc)
from tbl
)
select *,
ActWeight = case when dr = 1 and rn <> 1
then 0
when dr = 1 and rn = 1
then [Weight]
- sum(case when dr <> 1 then [Weight] else 0 end) over (partition by ID)
else [Weight]
end
from cte
dbfiddle demo
Hmmm . . . I think you just want window functions and conditional logic:
select t.*,
(case when 1 = row_number() over (partition by id order by weight desc, (case when code <> '' then 2 else 1 end))
then weight - sum(case when weight <> max_weight then weight else 0 end) over (partition by id)
else weight
end) as actual_weight
from (select t.*,
max(weight) over (partition by id, code) as max_weight
from t
) t

Case statement gives unexpected output

I have a subquery producing some rows like this:
| year | quarter | id | status |
|------|---------|----|--------|
| 2020 | 4 | 1 | no |
| 2021 | 1 | 1 | yes |
| | | | |
I then want to add an extra column to track status change. I have tried this:
select
*,
case
when (year = 2020 and quarter = 4 and status = 'no') and (year = 2021 and quarter = 1 and status = 'yes')
then 1 else 0
end as status_change_during_2021_q1
from(
...
...
) t
order by id, year, quarter
limit 50
However, this is the output I get:
| year | quarter | id | status | status_change_during_2021_q1 |
|------|---------|----|--------|------------------------------|
| 2020 | 4 | 1 | no | 0 |
| 2021 | 1 | 1 | yes | 0 |
I do not understand why the second row isn't set to 1 for status_change...?
You are trying to compare values across rows. Use lag():
(case when status = 'yes' and
lag(status) over (partition by id order by year, quarter) = 'no'
then 1 else 0
end)
Your version cannot do anything useful, because, for instance, year cannot be both 2021 and 2020 in the same row.

PostreSQL returning only specific value

I've got below structure:
sample_table
code | delivery | end_date | type
---------------+----------------+--------------+------
C086000-T10001 | OK | 2014-11-12 | 01
C086000-T10001 | OK | 2014-11-11 | 03
C086000-T10002 | FALSE | 2014-12-10 | 03
C086000-T10002 | FALSE | 2014-01-04 | 03
C086000-T10003 | FALSE | 2014-02-28 | 03
C086000-T10003 | FALSE | 2014-11-12 | 01
C086000-T10003 | FALSE | 2014-08-20 | 01
I want to output how many code (counted) has the OK delivery status.
I was trying to do something like:
SELECT sample_table.code AS code, sample_table.delivery AS delivered
FROM sample_table
WHERE COUNT(sample_table.delivery = "OK")
GROUP BY code, delivered
Edit
The expected output should be like below
code | delivered | all_type |
---------------+----------------+--------------+
C086000-T10001 | 2 | 04 |
C086000-T10002 | 0 | 06 |
C086000-T10003 | 0 | 05 |
In Postgres, the filter() clause comes handy for this:
select
code,
count(*) filter(where delivery = 'OK') delivered,
sum(type) all_type
from sample_table
group by code
Try this:
SELECT count(*)
FROM sample_table
WHERE sample_table.delivery = "OK"
If you want it by code:
SELECT code, count(*)
FROM sample_table
WHERE sample_table.delivery = "OK"
group by code
With conditional aggregation:
SELECT code,
SUM(CASE WHEN delivery = 'OK' THEN 1 ELSE 0 END ) AS delivered,
SUM(type) all_type
FROM sample_table
GROUP BY code

SQL Server : get Count() of a related table column where some condition

Given tables CollegeMajors
| Id | Major |
|----|-------------|
| 1 | Accounting |
| 2 | Math |
| 3 | Engineering |
and EnrolledStudents
| Id | CollegeMajorId | Name | HasGraduated |
|----|----------------|-----------------|--------------|
| 1 | 1 | Grace Smith | 1 |
| 2 | 1 | Tony Fabio | 0 |
| 3 | 1 | Michael Ross | 1 |
| 4 | 3 | Fletcher Thomas | 1 |
| 5 | 2 | Dwayne Johnson | 0 |
I want to do a query like
Select
CollegeMajors.Major,
Count(select number of students who have graduated) AS TotalGraduated,
Count(select number of students who have not graduated) AS TotalNotGraduated
From
CollegeMajors
Inner Join
EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
and I'm expecting these kind of results
| Major | TotalGraduated | TotalNotGraduated |
|-------------|----------------|-------------------|
| Accounting | 2 | 1 |
| Math | 0 | 1 |
| Engineering | 1 | 0 |
So the question is, what kind of query goes inside the COUNT to achieve the above?
Select CollegeMajors.Major
, COUNT(CASE WHEN EnrolledStudents.HasGraduated= 0 then 1 ELSE NULL END) as "TotalNotGraduated",
COUNT(CASE WHEN EnrolledStudents.HasGraduated = 1 then 1 ELSE NULL END) as "TotalGraduated"
From CollegeMajors
InnerJoin EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
GROUP BY CollegeMajors.Major
You can use the CASE statement inside your COUNT to achieve the desired result.Please try the below updated query.
Select CollegeMajors.Major
, COUNT(CASE WHEN EnrolledStudents.HasGraduated= 0 then 1 ELSE NULL END) as "TotalNotGraduated",
COUNT(CASE WHEN EnrolledStudents.HasGraduated = 1 then 1 ELSE NULL END) as "TotalGraduated"
From CollegeMajors
InnerJoin EnrolledStudents On EnrolledStudents.CollegeMajorId = CollegeMajors.Id
GROUP BY CollegeMajors.Major
You can try this for graduated count:
Select Count(*) From EnrolledStudents group by CollegeMajorId having HasGraduated = 1
And change 1 to zero for not graduated ones:
Select Count(*) From EnrolledStudents group by CollegeMajorId having HasGraduated = 0