Joining 3 tables pgsql - sql

I have 3 tables as shown below
table 1
________________________________________________
id | effective_date | table_3_id | acc_name |
___|____________________|____________|__________|
112|2012-02-01 12:00:00 | 23 | Over Pay |
___|____________________|____________|__________|
table 2
__________________________________
id | table_1_id | amount |
______|________________|_________|
1 | 112 | 400.00 |
______|________________|_________|
table 3
________________________________________
id | emp_num | first_name | last_name|
____|__________|_____________|__________|
23 | 100004 | John | Doe |
____|__________|_____________|__________|
I have a start date and an end date and also a predefined value for acc_name. What I want to do is, to retrieve the emp_num, first_name, last_name and amount from relevant tables that have a effective_date that falls between start date and end date and also the acc_name should be the predefined value.
For above tables if my start date = 2012-01-30 12:00:00 , end date = 2012-03-01 12:00:00 and acc_name = Over Pay; then below values should be returned.
emp_num = 100004
first_name = John
last_name = Doe
amount = 400.00
How can I do this? I am not sure whether joining all 3 the tables is the best approach here. Can anyone help?

Yes. Use a join.
select emp_num, first_name, last_name, amount
from
table1
inner join table2 on table1.id = table2.table_1_id
inner join table3 on table1.table_3_id = table3.id
where
effective_date between '2012-01-30 12:00:00' and '2012-03-01 12:00'
and
acc_name = 'Over Pay'
There is no 30th of February.

Here, try this one:
SELECT a.emp_num,
a.first_name,
a.last_name,
c.amount
FROM table3 a
INNER JOIN table1 b
on a.id = b.table_3_id
INNER JOIN table2 c
on b.id = c.id
WHERE b.effective_date BETWEEN '2012-01-30 12:00:00' AND '2012-02-29 12:00:00'
AND
b.acc_name = 'Over pay'

Related

How to select records only if a specific condition is met

Table A has customer names, service dates, and diagnosis.
I want to select the customer, the date of service and all diagnosis for that particular day if an oil change was done. If an oil change was not done, I don't want anything at all.
I have tried
select customer, servicedate, diagnosis
from A
where customer in (select customer from A where diagnosis = 'oilchange')
and servicedate in (select servicedate from A where diagnosis = 'oilchange')
Expected Output
First get the dates and customers where there is diagnosis = 'oil change' and join to the table:
select A.customer, A.servicedate, A.diagnosis
from A inner join (
select customer, servicedate
from A
where diagnosis = 'oil change'
) B on B.customer = A.customer and B.servicedate = A.servicedate
Or with EXISTS:
select t.customer, t.servicedate, t.diagnosis
from A t
where exists (
select 1 from A
where customer = t.customer and servicedate = t.servicedate and diagnosis = 'oil change'
)
See the demo.
Results:
> customer | servicedate | diagnosis
> :------- | :------------------ | :----------
> Smith | 01/01/2019 00:00:00 | Spark plugs
> Smith | 01/01/2019 00:00:00 | ValveCG
> Smith | 01/01/2019 00:00:00 | oil change
> Smith | 01/01/2019 00:00:00 | alignment
> John | 06/01/2019 00:00:00 | puncture
> John | 06/01/2019 00:00:00 | oil change
> John | 06/01/2019 00:00:00 | Wipers
> John | 06/01/2019 00:00:00 | engine
Try something like
SELECT all.customer, all.servicedate, all.diagnosis
FROM A all INNER JOIN A oilchange
ON all.customer=oilchange.customer AND oilchange.diagnosis='oilchange'
Try this:
select customer, servicedate, diagnosis
from A as a1, A as a2
where a1.customer = a2.customer and a1.servicedate = a2.servicedate and a2.diagnosis = 'oilchange'
if I understand this correctly
First identify the customers who had an oil change
;with OilChange
AS(
select customer, servicedate
from A
where diagnosis = 'oilchange'
)
--then get the rest of the data
select tbl.customer, tbl.servicedate, tbl.diagnosis
from A tbl
INNER JOIN OilChange OC on OC.Customer = tbl.Customer and OC.Servicedate = tbl.ServiceDate

SQL get available between to dates

I have two tables. These look like this
Table cars
+====+=========+=============+
| ID | mark | description |
+====+=========+=============+
| 1 | 1234 AB | Volvo V70 |
+----+---------+-------------+
| 2 | 4567 CD | VW GOLF |
+====+=========+=============+
Table bookings
+====+========+============+============+
| ID | car_id | date_from | date_to |
+====+========+============+============+
| 1 | 2 | 2018/01/01 | 2018/02/01 |
+----+--------+------------+------------+
| 2 | 1 | 2018/03/01 | 2018/04/01 |
+----+--------+------------+------------+
| 3 | 1 | 2018/05/01 | 2018/06/01 |
+----+--------+------------+------------+
| 4 | 2 | 2018/07/01 | 2018/08/01 |
+====+========+============+============+
I need a SQL query which returns all available cars in a specific time period. That means: If I give the query parameters 2018/01/13 and 2018/01/17, I will only get car number 2 because the other is not available.
Here is my first idea:
select c.id,
c.mark
from cars c
join bookings b on c.id = b.car_id
where to_date('2017/01/13', 'yyyy/mm/dd') not between b.date_from and b.date_to
and to_date('2017/01/17', 'yyyy/mm/dd') not between b.date_from and b.date_to;
This gives me more rows than I expect.
What should be the right query to achieve this?
Edit: I'm on Oracle 12c
This might work
SELECT DISTINCT c.id,
c.mark
FROM cars c
JOIN bookings b ON c.id = b.car_id
WHERE (to_date('2017/01/13', 'yyyy/mm/dd') < b.date_from AND to_date('2017/01/17', 'yyyy/mm/dd') < b.date_to)
OR (to_date('2017/01/13', 'yyyy/mm/dd') < b.date_from AND to_date('2017/01/17', 'yyyy/mm/dd') < b.date_to)
If I have understood the requirement correctly, you are looking for cars that do not have any bookings for the specified date range. Therefore, you should not join to the bookings table, because that will only give you cars with existing bookings for other periods. You are looking for cars where no booking exists, so:
with cars (id, mark, description) as
( select 1, '1234 AB', 'Volvo V70' from dual union all
select 2, '4567 CD', 'VW Golf' from dual )
, bookings (id, car_id, date_from, date_to ) as
( select 1, 2, date '2018-01-01', date '2018-02-01' from dual union all
select 2, 1, date '2018-03-01', date '2018-04-01' from dual union all
select 3, 1, date '2018-05-01', date '2018-06-01' from dual union all
select 4, 2, date '2018-07-01', date '2018-08-01' from dual )
select c.*
from cars c
where not exists
( select 1 from bookings b
where b.car_id = c.id
and ( date '2018-01-13' between b.date_from and b.date_to
or date '2018-01-17' between b.date_from and b.date_to )
);
Result:
ID MARK DESCRIPTION
---------- ------- -----------
1 1234 AB Volvo V70
Car 1 is available, because it only has bookings for March and May, while the Golf is booked for January and July.
Have you tried this:
DECLARE #dateToCheck datetime
SELECT cars.id, cars.mark, cars.description FROM cars
INNER JOIN bookings ON bookings.car_id = car.id
WHERE #dateToCheck BETWEEN booking.date_from AND booking.date_to

SQL Add hours for employees

I have a table for employees signing in and out. They have a date and time field for in and out and an PersonID number that links to the employees name etc.
I need to work out the difference between the 2 dates and times then add them all together for each employee.
select a.*,
b.timein,
b.timeout,
datediff(mi,b.timein,b.timeout) as total_mins
from tbl_people a
left join tbl_register b on a.id=b.personid
Output:
+----+-----------+----------+-------------------------+-------------------------+------------+
| ID | FirstName | LastName | TimeIn | TimeOut | Total_Mins |
+----+-----------+----------+-------------------------+-------------------------+------------+
| 1 | David | Test | 2015-05-12 12:11:00.000 | 2015-05-12 12:13:00.000 | 2 |
| 2 | David | Test | 2015-05-12 12:15:00.000 | 2015-05-12 12:18:00.000 | 3 |
+----+-----------+----------+-------------------------+-------------------------+------------+
This is what im currently getting. I would like it to show one record for each person with the total amount of minutes worked.
Thanks in anticipation!
Basically you have at least 2 options:
Option 1 - Use DISTINCT and SUM with OVER clause:
SELECT DISTINCT a.*,
SUM(DATEDIFF(mi, b.timein, b.timeout)) OVER(PARTITION BY a.id) AS total_mins
FROM tbl_people a
LEFT JOIN tbl_register b ON a.id=b.personid
Option 2 - Use a derived table for the GROUP BY part:
SELECT a.*,
total_mins
from tbl_people a
left join (
SELECT personid,
SUM(DATEDIFF(mi, timein, timeout) AS total_mins
FROM tbl_register
GROUP BY personid
) b ON a.id=b.personid
select
ppl.FirstName + ' ' + ppl.LastName as 'Person',
sum( datediff(mi, reg.timein, reg.timeout)) as 'total_mins'
from
tbl_people ppl
left join tbl_register reg on ppl.id = reg.personid
group by
ppl.FirstName + ' ' + ppl.LastName

SQL Group By Issue with same item ID

I am trying to track the total number of sales a rep has along with the amount of time he was clocked into work.
I have the following two tables:
table1:
employeeID | item | price | timeID
----------------------------------------
1 | 1 | 12.92 | 123
1 | 2 | 10.00 | 123
1 | 2 | 10.00 | 456
table2:
ID | minutes_in_shift
--------------------------
123 | 45
456 | 15
I would join these two queries with the following SQL:
SELECT
t1.employeeID, t1.item, t1.price, t1.shiftID, t2.minutes_in_shift
FROM table1 t1
JOIN table 2 t2 ON (t2.ID = t1.timeID)
Which would return the following table:
employeeID | item | price | timeID | minutes_in_shift
---------------------------------------------------
1 | 1 | 12.92 | 123 | 45
1 | 2 | 10.00 | 123 | 45
1 | 2 | 10.00 | 456 | 15
I would like for the consolidate results, however, to have this outcome:
employeeID | itemsSold | priceTotals | totaltimeworked
-----------------------------------------------------------------
1 | 3 | 32.92 | 60
I could use COUNT and SUM for the items and price but I cannot figure out how to properly show the total time worked in the manner it appears above.
Note: I am only having trouble with calculating the time worked. In shift 123 - employee 1 was working 45 minutes, regardless of how many items he sold.
Any suggestions?
If you wish to use the sample data as they are you will need to extract the shifts and sum the minutes, like this:
with a as (
select employeeID, count(*) itemsSold, sum(price) priceTotals
from Sampletable1
group by employeeID),
b as (
select employeeID, shiftID, max(minutes_in_shift) minutes_in_shift
from Sampletable1
group by employeeID, shiftID),
c as (
select employeeID, sum(minutes_in_shift) totaltimeworked
from b
group by employeeID)
select a.employeeID, a.itemsSold, a.priceTotals, c.totaltimeworked
from a inner join c on a.employeeID = c.employeeID
However, with your existing tables the select statement will be much easier:
with a as (
select employeeID, timeID, count(*) itemsSold, sum(price) priceTotals
from table1
group by employeeID, timeID)
select a.employeeID, sum(a.itemsSold), sum(a.priceTotals), sum(table2.minutes_in_shift) totaltimeworked
from a inner join table2 on a.timeID = table2.ID
group by a.employeeID
I think this query should do what you want:
SELECT t1.employeeID,
count(t1.item) AS itemsSold,
sum(t1.price) AS priceTotals,
sum(DISTINCT t2.minutes_in_shift) AS totaltimeworked
FROM table1 t1
JOIN table2 t2 ON (t2.ID = t1.timeID)
GROUP BY t1.employeeID;
Check on SQL Fiddle

Select distinct records with Min Date from two tables with Left Join

I'm trying to retrieve all distinct AccountId’s as well as the earliest InsertDate for each. Occasionally the AccountId is not known and although the transactions may be distinct I want to bucket all of the ‘-1’s into their own group.
This is what I have attempted so far along with the schemas.
CREATE TABLE #tmpResults (
Trans Varchar(12),
AccountId Varchar(50),
EarlyDate DateTime DEFAULT getdate(), CardType Varchar(16))
insert #tmpResults
select [Trans] = convert(varchar(12),'CashSale')
, [AccountId] = b.AccountId
, [EarlyDate] = min(b.InsertDate)
, case when c.name LIKE '%VISA%' then 'VISA'
when c.name LIKE '%MasterCard%' then 'MasterCard'
when c.name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end as [CardType]
from TransBatch b
left join CardVer_3 c WITH (NOLOCK) ON c.Id = B.BatchId
left join TransBatch b2
on (b.accountid = b2.accountid and (b.InsertDate > b2.InsertDate or b.InsertDate = b2.InsertDate))
and b2.accountid is NULL
group by b.accountid, b.InsertDate,c.name
order by b.accountid DESC
select * from #tmpResults
The table schemas are like so:
**TransBatch**
RecordId |BatchId |InsertDate | AccountId | AccNameHolder
6676 | 11 | 2012-11-01 05:19:04.000 | 12345 | Account1
6677 | 11 | 2012-11-01 05:19:04.000 | 12345 | Account1
6678 | 11 | 2012-11-01 05:19:04.000 | 55555 | Account2
6679 | 11 | 2012-11-01 05:19:04.000 | -1 | NULL
6680 | 12 | 2012-11-02 05:20:04.000 | 12345 | Account1
6681 | 12 | 2012-11-02 05:20:04.000 | 55555 | Account2
6682 | 13 | 2012-11-04 06:20:04.000 | 44444 | Account3
6683 | 14 | 2012-11-05 05:30:04.000 | 44444 | Account3
6684 | 14 | 2012-11-05 05:31:04.000 | -1 | NULL
**CardVer_3**
BatchId |Name
11 |MasterCard
12 |Visa
13 |AMEX
14 |GoCard
This will be an intermediate table, the output is planned to look like the attached.
Gordon, I made some very minor changes to your suggestion and believe I have the correct output: http://www.sqlfiddle.com/#!3/cfbc3/7/0 . Thank you very much. I'm not at all familiar with the windows functions so I'm going to brush up on these.
The code is here:
select 'CashSale' as [Trans],
AccountId,
min(InsertDateTime),
(case when name LIKE '%VISA%' then 'VISA'
when name LIKE '%MasterCard%' then 'MasterCard'
when name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end) as [CardType]
from (select AccountId, InsertDateTime, c.name,
row_number() over (partition by AccountId order by insertDateTime asc) as seqnum
from TransBatch b left join
CardVer_3 c WITH (NOLOCK) ON c.batchId = B.BatchId
) t
where seqnum = 1
group by t.accountid, t.name
The next steps are to dump this into a temp table and try and get the output looking like the attached excel screen.
It sounds like you are trying to get the full record for the minimum insert date time. For this, you want to use windows functions:
select 'CashSale' as Trans,
AccountId,
min(InsertDate),
(case when name LIKE '%VISA%' then 'VISA'
when name LIKE '%MasterCard%' then 'MasterCard'
when name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end) as [CardType]
from (select AccountId, InsertDate, c.name,
row_number() over (partition by AccountId order by insertDate desc) as seqnum
from TransBatch b left join
CardVer_3 c WITH (NOLOCK)
ON c.Id = B.BatchId
) t
where seqnum = 1
I'm taking a guess that "CashSale" means that the credit card did not match. The TransId is then either the recordId or "CashSale".