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
Related
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.
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 have a question - I have 2 tables like this :
tblattLogs:
| user_id | attendantLogs |
+---------+------------------+
| 01 | 2017-10-31 08:00 |
| 01 | 2017-10-31 12:00 |
| 01 | 2017-10-31 17:05 |
| 01 | 2017-10-31 17:10 |
| 02 | 2017-10-31 08:10 |
| 02 | 2017-10-31 11:00 |
| 02 | 2017-10-31 17:01 |
| 02 | 2017-10-31 17:05 |
......
tblusers:
| id | name | otherstuff |..
+----+------+------------+
| 01 | Joe | otherstuff |
| 02 | Jean | otherstuff |
...
and I want the following as a result:
| id | name | date | CheckIn | CheckOut |
+----+------+------------+---------+----------+
| 01 | Joe | 2017-10-31 | 08:00 | 17:10 |
| 02 | Jean | 2017-10-31 | 08:10 | 17:05 |
...
And my query looks like:
SELECT DISTINCT
t.[user_id],
MIN(t.attendantLogs) OVER (PARTITION BY [user_id]) AS CheckIn,
MAX(t.attendantLogs) OVER (PARTITION BY [user_id]) AS CheckOut,
n.name AS NAME
FROM
tblattLogs t, tblusers as n
WHERE
t.user_id = n.id
My question is: how do I create a query that shows me correct result as I want?
and I know my query is wrong. I'm literally beginner in SQL. So, please correct me if I am wrong, and please help me to resolve this.
The trick here is to build up a definitive list of all dates as a derived table or cte.
You can then CROSS JOIN the users and dates tables, and then JOIN back to the Attendance data, grouping and using the MIN / MAX aggregates as before.
The LEFT JOIN ensures that there will always be a record for every employee for any day on at least one employee went to work. The attendance will be NULL if that employee didn't check in / out at all.
WITH cteUniqueDates AS
(
SELECT DISTINCT CAST(attendantLogs AS DATE) as AttendanceDate
FROM tblattLogs
)
SELECT u.id, u.name, d.AttendanceDate, min(attendantLogs) AS CheckIn,
max(attendantLogs) AS CheckOut
FROM tblusers u
CROSS JOIN cteUniqueDates d
LEFT OUTER JOIN tblattLogs t
ON u.Id = t.user_id AND CAST(attendantLogs AS DATE) = d.AttendanceDate
GROUP BY u.id, u.name, d.AttendanceDate
ORDER BY d.AttendenceDate, u.id;
SqlFiddle Here
One caveat - this will only work if the employees check in and checkout on the same day. If the employees work overnight, things become more complicated.
One way would be to use group by.DEMO based on data by Stuart
;with cte
as
(
select user_id,
cast(attendant_logs as date) as dt,
min(cast(attendant_logs as time)) as checkin,
max(cast(attendant_logs as time)) as checkout
from
#tblattLogs
group by user_id,
cast(attendant_logs as date)
)
select u.id,u.name,
c.*
from
#tblusers u
join
cte c
on c.user_id=u.id
Here is single SQL Query which could help u to achieve the above result :
SELECT U.ID,
U.name,
CONVERT(DATE, MIN(L.attendantLogs)) [Date],
CONVERT(VARCHAR(8), MIN(L.attendantLogs), 108) [CheckIn],
CONVERT(VARCHAR(8), MAX(L.attendantLogs), 108) [CheckOut]
FROM tblattLogs L
INNER JOIN tblusers U ON U.id = L.USER_ID
GROUP BY U.id,
U.name;
Result :
ID name Date CheckIn CheckOut
01 Joe 2017-10-31 08:00:00 17:10:00
02 Jean 2017-10-31 08:10:00 17:05:00
I've got a table that tracks the relationship between various attributes, and the dates when they occur. I want to look at how the relationships between the fields change for each entity. As part of this analysis I want to move the records where the relationships have ended by a certain date.
If the data looked something like this:
id | FieldOne | FieldTwo | FieldThree | DataDate
---------------------------------------------------
01 | A | A | A | 2000-01-01
01 | A | A | A | 2000-01-02
01 | A | A | A | 2000-01-03
01 | A | A | A | 2000-01-04
01 | A | A | B | 2000-01-05
02 | A | C | C | 2000-01-01
02 | A | C | C | 2000-01-02
02 | A | D | C | 2000-01-03
02 | A | D | C | 2000-01-04
02 | A | D | C | 2000-01-05
03 | C | A | D | 2000-01-01
03 | C | A | D | 2000-01-02
03 | C | A | D | 2000-01-03
03 | C | A | D | 2000-01-04
03 | C | A | D | 2000-01-05
I want to take the "closed" records, and study/analyse them.
So, if my cut-off date was 2000-01-05, then I'd be want remove all the AAA records for patient 01, but not the AAB record, which is still "active." Also, I'd want the ACC records for patient 02, but not the ADC records, and none of patient 03's records. The "active" records should be left in place so that the other processes that gather data can still write subsequent records for the same patients to the same table.
The actual table has nine columns, all of which are varchars, or varying lengths.
At the moment I'm doing it by finding all the ended relationships, then doing an ugly join.
SELECT id, FieldOne, FieldTwo, FieldThree
INTO Closed
FROM DataTable
GROUP BY id, FieldOne, FieldTwo, FieldThree
HAVING MAX( DataDate ) < '2001-01-05'
DELETE d.*
OUTPUT deleted.*
INTO OutputTable
FROM DataTable d
INNER JOIN Closed c
ON d.Id = c.Id
AND (d.FieldOne = c.FieldOne
OR (d.FieldOne IS NULL
AND c.FieldOne IS NULL))
AND (d.FieldTwo = c.FieldTwo
OR (d.FieldTwo IS NULL
AND c.FieldTwo IS NULL))
AND (d.FieldThree = c.FieldThree
OR (d.FieldThree IS NULL
AND c.FieldThree IS NULL))
I feel like there is probably a better (faster, and more elegant) way to do this, but when I use EXISTS it returns all the records, not the subset I want.
What is the best way to achieve this?
This selects rows where the combinations of the first four columns have at least one record on or after the cutoff date:
select dt.*
from datatable dt
where exists (select 1
from datatable dt2
where dt.id = dt2.id and
dt.field1 = dt2.field1 and
dt.field2 = dt2.field2 and
dt.field3 = dt2.field3 and
dt.datadate >= '2001-01-05'
);
If the values can have NULLs, then the comparisons need to take that into account.
reservations table:
id | fk_property_id | arrival | departure
------+----------------+---------------------+---------------------
1 | 1 | 2013-01-11 14:00:00 | 2013-09-07 10:00:00
2 | 1 | 2013-02-12 14:00:00 | 2013-09-07 10:00:00
3 | 1 | 2013-03-29 14:00:00 | 2013-09-07 10:00:00
4 | 2 | 2013-04-29 14:00:00 | 2013-09-07 10:00:00
5 | 2 | 2013-05-29 14:00:00 | 2013-09-07 10:00:00
6 | 3 | 2013-06-29 14:00:00 | 2013-09-07 10:00:00
properties table:
id | title
-----+--------------------------------------------------------
1 | blah blah
2 | blah blah
3 | blah blah
4 | blah blah
5 | blah blah
6 | blah blah
7 | blah blah
8 | blah blah
9 | blah blah
10 | blah blah
I need to list properties available for a certain date range.
For example user enters date-range:
arrival: 2013-06-29 14:00:00
departure: 2013-07-14 10:00:00
and I need to list all not-rented in that period properties. how do I do that?
I take it it should be a left join - but I can't wrap my head around it.
Here is the right condition:
select p.*
from properties p left outer join
reservations r
on p.id = r.fk_property_id and
r.arrival < '2013-07-14 10:00:00' and
r.departure > '2013-06-29 14:00:00'
where r.id is null
Here is the logic. First, the time conditions need to go in the on clause. In the where clause, they conflict with the left outer join.
The logic is that a room is available for all the days when there is no arrival before the later date and no departure after the first date. This takes all the various overlap possibilities into account.
The final where just finds the properties that are available because there are no reservations.
There are a couple of ways:
LEFT JOIN
SELECT p.id, p.title
FROM properties p
LEFT JOIN reservations r
on p.id = r.fk_property_id
where arrival < '2013-06-29 14:00:00'
and departure > '2013-07-14 10:00:00'
and r.id is null
However, since you're essentially performing an anti-semijoin, NOT EXISTS could be better for you:
select p.id, p.title
from properties p
where NOT EXISTS (
select 1
from reservations r
where where arrival > '2013-06-29 14:00:00'
and departure < '2013-07-14 10:00:00'
and p.id = r.fk_property_id);
You would want to use IS NULL with your LEFT OUTER JOIN table. Meaning that nothing was found in the reservations table for that specific property.
SELECT *
FROM properties LEFT OUTER JOIN reservations ON properties.id = reservations.fk_property_id AND a
WHERE arrival > '2013-06-29 14:00:00'
AND departure < '2013-07-14 10:00:00'
AND reservations.arrival IS NULL