I'm trying to write a PostgreSQL query to list the two instruments that are available for rent, with the lowest monthly rental fee, also tell when the next lesson for each listed instrument is scheduled. I have these two tables:
//Table lesson
lesson_id | instrument_type | start
001 | 01 | 2021-02-01
002 | 01 | 2021-02-02
003 | 02 | 2021-02-04
004 | 02 | 2021-02-05
005 | 03 | 2021-02-06
//Table instrument
instrument_id | fee_per_month | availability
01 | 300 | yes
02 | 400 | no
03 | 500 | yes
And I want:
instrument_type | fee_per_month | lesson_id | start
01 | 300 | 001 | 2021-02-01
03 | 500 | 005 | 2021-02-06
SQL is new to me, and I have tried my best but didn't succeed:
SELECT
instrument.type AS "instrument",
instrument.fee_per_month AS "fee/month",
lesson.start AS "next lesson"
FROM instrument, lesson
LEFT JOIN LATERAL (
SELECT lesson.*
FROM lesson
WHERE lesson.start >= current_timestamp AND lesson.instrument_type = instrument.type
ORDER BY lesson.start
limit 1
) lesson on true
GROUP BY "instrument", "rent/month", "next lesson"
ORDER BY "rent/month"
limit 2;
How should I do it correctly?
Hmmm . . . I think you are sort of on the right track. If I understand correctly:
SELECT i.instrument_id AS "instrument", i.fee_per_month,
l.lesson_id, l.start AS "next lesson"
FROM instrument i LEFT JOIN LATERAL
(SELECT l.*
FROM lesson l
WHERE l.start >= current_timestamp AND
l.instrument_id = i.instrument_id
ORDER BY l.start
LIMIT 1
) l
ON true
WHERE i.availability = 'yes'
ORDER BY i.fee_per_month
LIMIT 2;
This fixes up the query, the logic, and the names of things. Here is a db<>fiddle.
The major differences are:
No Cartesian product between instrument and lesson.
Introducing table aliases so the query is easier to write and to read.
Remove the GROUP BY. No aggregation is necessary.
Related
I'm trying to write a PostgreSQL query to list the two instruments with the lowest monthly rental fee, also tell when the next lesson for each listed instrument is scheduled. I have these two tables:
//Table lesson
lesson_id | instrument_id | start
001 | 01 | 2021-01-01 10:00:00
002 | 01 | 2021-01-02 10:00:00
003 | 02 | 2021-01-04 10:00:00
004 | 02 | 2021-01-05 10:00:00
//Table instrument
instrument_id | fee_per_month
01 | 300
02 | 400
03 | 500
And I want:
instrument_id | fee_per_month | lesson_id | start
01 | 300 | 001 | 2021-01-01 10:00:00
02 | 400 | 003 | 2021-01-04 10:00:00
Getting the two instruments with lowest fee has been solved. How do I get the next lesson for these two instrument with lowest fee?
One option uses a lateral join:
select i.*, l.lesson_id, l.start
from instrument i
left join lateral (
select l.*
from lesson l
where l.instrument_id = i.instrument_id and l.start >= current_date
order by l.start
limit 1
) l on true
This brings the first lesson today or after today's date for each instrument (if any).
You could also use distinct on:
select distinct on (i.instrument_id) i.*, l.lesson_id, l.start
from instrument i
left join lesson l on l.instrument_id = i.instrument_id and l.start >= current_date
order by i.instrument_id, l.start
I'm in need of some assistance to anyone familiar with Oracle SQL. I'm trying to use the Where Not Exists sub query, and is working fine with specific where clauses for specific customers, and then using UNION to join any other customer thereafter. I'm trying to use the SQL in a way where I'm not using UNION to join multiple customers, as there's 100's. I just don't know how to go about it.
I think I need to join the MAIN_ITEM table to the LOCATION table somehow, as it links the items in INVENTORY_LOCATIONS with a zone_code where the location_code can be compared against the table ZONE, showing any mismatches where location_code in INVENTORY_LOCATIONS does not exist in table ZONE. I'm not really sure if I"m explaining this properly, but hopefully my example below clears it up.
Many thanks in advance.
Current Query
select a.company, a.customer, c.customer_name, a.location_code, a.invt1, a.invt2, a.invt3, a.invt_qty
from inventory_locations a left join main_customer c
on a.company=c.company and a.customer=c.customer and a.ware_code=c.ware_code
where not exists (select 1 from zone b where b.location_code = a.location_code and b.zone_code='PM')
and a.company='M1'
and a.customer='100068'
UNION
select a.company, a.customer, c.customer_name, a.location_code, a.invt1, a.invt2, a.invt3, a.invt_qty
from inventory_locations a left join main_customer c
on a.company=c.company and a.customer=c.customer and a.ware_code=c.ware_code
where not exists (select 1 from zone b where b.location_code = a.location_code and b.zone_code='Z1')
and a.company='M1'
and a.customer='100012'
Table 1 - INVENTORY_LOCATIONS A
COMPANY | WARE_CODE |CUSTOMER | LOCATION_CODE |INVT1 | INVT2 | INVT3 | INVT_QTY
--------------------------------------------------------------------------------------
M1 | 01 | 100012 | 0101A |000052 | T100 | 000001001 | 60
M1 | 01 | 100012 | 0602A |000053 | T101 | 000001002 | 60
M1 | 01 | 100068 | 0601A |CANDY | T200 | 000001080 | 25
M1 | 01 | 100068 | 0102A |CANDY2 | T202 | 000001081 | 25
Table 2 - ZONE B
COMPANY | WARE_CODE |ZONE_CODE | LOCATION_CODE
--------------------------------------------------------
M1 | 01 |PM | 0101A
M1 | 01 |PM | 0102A
M1 | 01 |Z1 | 0601A
M1 | 01 |Z1 | 0602A
Table 3 - MAIN_ITEM D
COMPANY | WARE_CODE | CUSTOMER | ITEM_CODE | ZONE_CODE
----------------------------------------------------------------
M1 | 01 | 100012 | 000052 | PM
M1 | 01 | 100012 | 000053 | PM
M1 | 01 | 100068 | CANDY | Z1
M1 | 01 | 100068 | CANDY2 | Z1
Current results with above query.
COMPANY | CUSTOMER | CUSTOMER_NAME | LOCATION_CODE | INVT1 | INVT2 | INVT3 | INVT_QTY
---------------------------------------------------------------------------------------------------
M1 | 100012 | TEST COMP 1 | 0602A | 000053 | T101 | 000001002 | 60
M1 | 100068 | TEST COMP 2 | 0102A | CANDY2 | T202 | 000001081 | 25
Expected results with a query that doesn't use UNION to join multiple customers.
COMPANY | CUSTOMER | CUSTOMER_NAME | LOCATION_CODE | INVT1 | INVT2 | INVT3 | INVT_QTY
-------------------------------------------------------------------------------------------------
M1 | 100012 | TEST COMP 1 | 0602A | 000053 | T101 | 000001002 | 60
M1 | 100068 | TEST COMP 2 | 0102A | CANDY2 | T202 | 000001081 | 25
Thank you for taking the time to read and assist. Greatly appreciated.
I don't fully understand the semantics of your tables and am not sure of the primary keys which means the join conditions could need to be corrected.
However, my interpretation of your goal is to find which inventory_location rows imply a combination of location code and zone that is not in the zone table.
So I would do as follows:
Take the inventory_location table, and add on the customer_name and zone_code with joins. I am assuming each row has only one customer and only one zone. A "with" clause is convenient to treat this as if it were a single table.
Then take the location and zone code combinations and see which ones are missing from the zone table with a "where not exists" clause.
I apologize in advance for any typos/syntax errors. Without actually executing it, I think it would produce your requested output.
with inv_loc as (
select a.company, a.customer, c.customer_name, a.location_code, a.invt1, a.invt2, a.invt3, a.invt_qty, d.zone_code
from inventory_locations a
left join main_customer c on a.company=c.company and a.customer=c.customer and a.ware_code=c.ware_code
left join main_item d on d.company = a.company and d.customer = a.customer and d.ware_code = a.ware_code and d.item_code = a.invt1
)
select
company, customer, customer_name, location_code, invt1, invt2, invt3, invt_qty
from inv_loc i
where not exists (
select 1 from zone b
where b.location_code = i.location_code and b.zone_code =i.zone_code
)
I've got a table in SQL Server 2016 (I believe it was originally from 2008 or 2012 and it's just moved to the 2016 cluster) with patient events, the type of event, and the severity of the event (called a grade). There are several instances where the same patient will have multiple events occur but with varying grades. So, a sample of data will look something like this:
| Pt_id | Event | Grade |
+-------+----------------+-------+
| 01 | Pain | 2 |
| 01 | Pain | 4 |
| 01 | Nausea | 2 |
| 02 | Headache | 2 |
| 02 | Headache | 3 |
| 03 | Blurred Vision | 3 |
| 03 | Blurred Vision | 4 |
| 03 | Bluured Vision | 3 |
| 03 | Nausea | 4 |
| 03 | Nausea | 2 |
I'm trying to get the highest grade for each of the different events per patient. My desired output for that data would be as follows:
| Pt_id | Event | Grade |
+-------+----------------+-------+
| 01 | Pain | 4 |
| 01 | Nausea | 2 |
| 02 | Headache | 3 |
| 03 | Blurred Vision | 4 |
| 03 | Nausea | 4 |
I've tried using the the Top 1 incorporated into the query, the ROW_Number, Partition, and everything else Google has thrown at me but I get either too restricted of results (I'm getting around 30 rows but I actually went through the excel (I'm trying to do some QA here) and I should have just under 400 rows. I think that when I do these functions I'm missing something and it's grouping either all Pt_ids and just picking 1 row for all the Events for that Pt_id or it's doing that with the Event - and no matter what I try it won't give me one row per patient, per event, with the highest grade for that event and patient.
Although I've used SQL throughout the years, it's never been my primary function so your assistance is greatly appreciated!
Isn't this enough with use of GROUP BY with MAX() ?
SELECT Pt_id, Event, MAX(Grade)
FROM table t
GROUP BY Pt_id, Event;
If the table has more column other than only 3 columns, then use ROW_NUMBER() with TIES clause :
SELECT TOP (1) WITH TIES t.*
FROM table t
ORDER BY ROW_NUMBER() OVER (PARTITION BY Pt_id, Event ORDER BY Grade DESC);
use row_number window function
with cte as
(
select *,
row_number() over(partition by Pt_id ,Event by order by Grade desc) rn
from your_table
)select * from cte where rn=1
You have to use order by Grade descfor getting max value
I have been battling through this query/query design for sometime now and I thought it's time to ask the experts! Here's my table results:
ID | Status | date |
---------------------------------
05 | Returned | 20/6/2018 |
03 | Sent | 12/5/2018 |
01 | Pending | 07/6/2018 |
01 | Engaged | 11/4/2018 |
03 | Contacted | 16/4/2018 |
05 | Surveyed | 04/3/2017 |
05 | No Contact | 05/3/2017 |
How do I get it to return top/newest value for each ID:
ID | Status | date |
---------------------------------
05 | Returned | 20/6/2018 |
03 | Sent | 12/5/2018 |
01 | Pending | 07/6/2018 |
I've tried group by, TOP 1, Distinct and results still not what I wanted. Also, displaying the results by top 5% is won't do either as the ID can be more than just 3 types.
My QUERY below:
INSERT INTO TmpAllcomsEmployee ( StatusID, EmployeeID, CommunicationDate )
SELECT DISTINCT CommunicationLog.StatusID, TmpAllcomsEmployee.EmployeeID,
Max(CommunicationLog.CommunicationDate) AS MaxOfCommunicationDate
FROM CommunicationLog RIGHT JOIN TmpAllcomsEmployee ON
CommunicationLog.EmployeeID = TmpAllcomsEmployee.EmployeeID
GROUP BY CommunicationLog.StatusID, TmpAllcomsEmployee.EmployeeID
ORDER BY Max(CommunicationLog.CommunicationDate) DESC;
One method is a correlated subquery:
select cl.*
from CommunicationLog as cl
where cl.date = (select max(cl2.date)
from CommunicationLog as cl2
where cl2.EmployeeID = cl.EmployeeID
);
This gets the most recent record for each employee in CommunicationLog. You can join in the other table if you really need it. It does not seem unnecessary unless you are using it for filtering.
I have two tables, Countries and Sums:
+------+---------+
| code | country |
+------+---------+
| 01 | France |
| 02 | Germany |
| 02 | Austria |
| 03 | Belgium |
| 04 | Belgium |
| 04 | Spain |
| 05 | Italy |
+------+---------+
+------+-----+
| code | sum |
+------+-----+
| 01 | 500 |
| 02 | 400 |
| 03 | 300 |
| 04 | 200 |
+------+-----+
I want to create a table code-sum-country. It's very easy of course, but I need to have exactly the same number of rows as in the table Sums.
+------+-----+---------+
| code | sum | country |
+------+-----+---------+
| 01 | 500 | France |
| 02 | 400 | Austria |
| 02 | 400 | Germany |
| 03 | 300 | Belgium |
| 04 | 200 | Spain |
| 04 | 200 | Belgium |
+------+-----+---------+
I want to have in the above table unique code values. So I need to remove some of them, it doesn't matter which one. My goal is to have only one row with the same code. For example the row
| 04 | 200 | Spain |
can remain or be deleted.
How can I do that?
Try this:
DELETE
FROM code_sum_country
WHERE code in
(SELECT code
FROM code_sum_country
GROUP BY code
HAVING COUNT (code) > 1)
AND country NOT IN
(SELECT MIN(country)
FROM code_sum_country
GROUP BY code
HAVING COUNT (code) > 1)
This will retain the country whose name is minimum in alphabetical order.
Change MIN(country) to MAX(country) if you want to retain the maximal ones.
Hope it helps :)
If you want to query sums and get one arbitrary country, you can use a correlated subquery:
select s.*,
(select top 1 c.country
from countries as c
where s.code = c.code
) as country
from sums as s;
I understand your question in such a way that the result is actually about code and sum, and an example of country is desired. Probably to give more meaning to your results and which country is actually returned is irrelevant... I therefore suggest a group by and a max or min on code, and max or min on country. See beneath.
PS: possibly it could be more usefull to define a different kind of name that is more meaningfull than country. But that is kind of bluntly to say without more info :).
select
min(code) as code
,[sum]
,min(country) as country
from TableAA
group by sum