Subtracting two rows from the same table based on date and hour - sql

I have two tables I would like to join.
Table A
Date | Hour | Direction | Qty
2018-11-20 1 DE/UK 2
2018-11-20 2 DE/UK 6
Table B
Date | Hour | Area | Price
2018-11-20 1 DE 5
2018-11-20 2 DE 4
2018-11-20 1 UK 3
2018-11-20 2 UK 9
I want to join them like this:
Table C
Date | Hour | Direction | Qty | AreaFrom | AreaTo | PriceFrom | PriceTo | Profit
2018-11-20 1 DE/UK 2 DE UK 5 3 3-5 = -2
2018-11-20 2 DE/UK 6 DE UK 4 9 5
I have tried by CROSS Join and other kinds of joins, but have not been able to make it work.
Other related questions I have looked at so far:
SQL subtract two rows based on date and another column
Selecting two rows from the same table

It might not be as complex as Adam makes out. If the TableA had a "directionFrom" and a "directionTo" columns you'd probably have worked this out by yourself. So let's cut up the direction field into two columns:
SELECT
a.[Date], a.[Hour], a.Direction, a.Qty,
f.Area as AreaFrom, t.Area as AreaTo,
f.Price as priceFrom, t.Price as PriceTo,
t.Price-f.Price as profit
FROM
TableA a
INNER JOIN TableB f
ON
a.[Date] = f.[Date] and
a.Hour = b.[Hour] and
LEFT(a.Direction, 2) = f.Area --take the left two as the area from
INNER JOIN TableB t
ON
a.[Date] = t.[Date] and
a.Hour = t.[Hour] and
RIGHT(a.Direction, 2) = t.Area --take the right two as the area to
If you ever have areas with more than two letter codes, you'll have to SUBSTRING on the CHARINDEX of the / instead

Related

SQL select with three tables and foreign keys

I have three tables :
field:
f_id
f_start
f_end
1
10
20
2
15
25
3
5
10
person :
p_id
p_name
1
Roger
2
John
3
Alicia
affect :
id
fk_field
fk_person
1
2
1
2
1
2
3
3
3
And I would like to select the dates and the names associated to. Like this
p_name
f_start
f_end
Roger
15
25
John
10
20
Alicia
5
10
I'm new to SQL and I don't know if i have to use JOIN or not... Thanks
You must join all 3 tables on their related columns:
SELECT p.p_name, f.f_start, f.f_end
FROM person p
INNER JOIN affect a ON a.fk_person = p.p_id
INNER JOIN field f ON f.f_id = a.fk_field;
Depending on your requirement you may need LEFT instead of INNER joins, but for this sample data the INNER joins will do.

Finding Minimum Values with Multiple Joins

I am using StandardSQL in BigQuery and have 7 tables with 10 to 75 columns each and thousands of rows. For simplicity I will only use the relevant tables and columns for what I am trying to accomplish.
Table 1
Item
Desc
12341
abcd
23451
bcda
34561
cdab
45671
dabc
Table 2
SubItem
Location
ON_OFF
OnHand
OnOrder
12345
1
ON
3
5
12345
2
ON
4
2
12345
3
ON
2
4
12346
1
ON
7
7
12346
2
ON
1
4
12346
3
ON
8
7
23451
1
OFF
1
1
23451
2
OFF
3
2
34567
1
ON
6
0
34567
2
ON
1
5
34568
1
ON
2
0
34568
2
ON
3
10
45671
2
ON
5
1
Table 3
Item
SubItem
12341
12346
23451
23451
34561
34567
34561
34568
Current Result
Item
Desc
ON_OFF
OH
OO
12341
abcd
ON
9
11
12341
abcd
ON
16
18
23451
bcda
OFF
4
3
34561
cdab
ON
7
5
34561
cdab
ON
5
10
45671
dabc
ON
5
1
Desired Result
Item
Desc
ON_OFF
OH
OO
12341
abcd
ON
9
18
23451
bcda
OFF
4
3
34561
cdab
ON
5
5
45671
dabc
ON
5
1
I am looking for the Minimum OH and Minimum OO Value for each item and as in the case of Item 45671, that does not correspond with the same SubItem number.
Current code providing me with the Current Result table is:
Select
Table1.Item,
Table1.Desc,
Table2.ON_OFF,
Table2.OH,
Table2.OO
From Table1
Left Join Table3
On Table1.Item = Table3.Item
Left Join
(Select SubItem, ON_OFF, Sum(OnHand) As OH, Sum(OnOrder) As OO
From Table 2
Group by 1,2)
ON Table3.SubItem = Table2.SubItem;
Looking for ideas as I am still fairly new to SQL and the current actual code ties 7 tables with various joins to build a final table with 45 columns and thousands of rows. I have looked at using RowNumber() and Partition By, but I am not sure where it would go. Was also thinking separating the OO and OH into two joins might help.
Any suggestions welcome! Thank you!
Looking at your sample data, It seems that You can use group by and aggregate function min as follows:
Select Table1.Item,
Table1.Desc,
Table2.ON_OFF,
Min(Table2.OH),
Min(Table2.OO)
From Table1
Left Join Table3
On Table1.Item = Table3.Item
Left Join (Select SubItem,
ON_OFF,
Sum(OnHand) As OH,
Sum(OnOrder) As OO
From Table2
Group by 1,2) Table2
ON Table3.SubItem = Table2.SubItem
Group by Table1.Item, Table1.Desc, Table2.ON_OFF;
I worked with it after getting some much needed sleep...
I came up with the below and it is working!
SELECT T1.Item,
T1.Desc,
T3_1.ONOFF,
T3_1.OH,
T3_2.OO
FROM Table1 T1
Left Join
(Select Item, SubItem,T2_1.O_O as ONOFF, T2_1.OH1 as OH,
ROW_NUMBER() OVER(PARTITION BY Item ORDER BY T2_1.OH1) as rn
FROM T2_1
Left Join
(Select SubItem, SUM(IFNULL(OnHand,0)) AS OH1,
FROM Table2
GROUP BY 1) T2_1
ON T2_1.SubItem = T3_1.SubItem) T3_1
On T1.Item = T3_1.Item
Left Join
(Select Item, SubItem, T2_2.OO1 as OO,
ROW_NUMBER() OVER(PARTITION BY Item ORDER BY T2_2.OO1) as rn2
FROM T2_2
Left Join
(Select SubItem, SUM(IFNULL(OnOrder,0)) AS OO1,
FROM Table2
GROUP BY 1) T2_2
ON T2_2.SubItem = T3_2.SubItem) T3_2
On T1.Item = T3_2.Item
Where rn = 1 and rn2 = 1;
Thanks!

Left join to a table where values do not exist (and are not NULLs)

EDIT (SOLVED): A cross join. One of those joins you never use until you need it. Thanks for the help
Left table: TS , single field with values [1,2,...,365].
Right table: PAYMENT with three fields (ID, TS, AMT)
For each ID, I want to see 365 records from a left join of TS on PAYMENT.
The problem is that "no value" is not the same as a NULL.
If PAYMENT.TS does not exist for a certain value (e.g. PAYMENT.TS=4), then there is no value to join on and the left join does not return a row #4.
I tried using NOT IN / NOT EXISTS as a condition, but this only treats the case where the right table has explicit NULLS and not the case where no value exists.
How can I proceed? Thanks!
(This is a DB2 system)
SELECT * FROM TS LEFT JOIN PAYMENT ON TS = PAYMENT.TS
TS TABLE:
| TS |
----------
1
2
...
365
PAYMENTS TABLE:
| ID | TS | PMT |
-----------------------------
1 1 70
1 2 20
1 5 10
2 3 200
EXPECTED RESULT:
| ID | TS | PMT |
-----------------------------
1 1 70
1 2 20
1 3
1 4
1 5 10
... ...
1 365
2 1
2 2
2 3 200
... ...
2 365
ACTUAL RESULT:
| ID | TS | PMT |
-----------------------------
1 1 70
1 2 20
1 5 10
2 3 200
You need to generate all the rows you want using a cross join and then use left join:
SELECT i.id, ts.ts. p.amt
FROM (SELECT DISTINCT ID FROM PAYMENT) i CROSS JOIN
TS LEFT JOIN
PAYMENT p
ON ts.TS = p.TS AND p.id = i.id;
This will return 365 rows for each id.
You have to join them matching the two common columns in each table. Preferably by the keys(foreign and primary).
Let's say TS table has this one column called 'NUMBERS' and its type is int. The table PAYMENT has the column ID, type of int also. Which means they may have common values. Thus, if you want to join two tables and get the common ones where the PAYMENT.ID exists in TS.NUMBERS then you should do:
SELECT * FROM TS LEFT JOIN PAYMENT ON TS.NUMBERS = PAYMENT.ID
I hope I've been clear.
Note: Also do not forget that if a column or more has the same name in both tables, you have to clarify from which table you want that column for instance if also PAYMENT table had the column named as NUMBERS, then:
SELECT PAYMENT.ID, TS.NUMBERS FROM TS LEFT JOIN PAYMENT ON TS.NUMBERS = PAYMENT.ID

SQL count 2 equal columns and select other columns

I have a two separate tables, one with vacancies, and one with applications to those vacancies. I want to select a new table which selects from the vacancies table with a number of other columns from that table, and another column that calculates how many applications there are for those vacancies. So my vacancy table looks like this:
ID Active StartDate JobID JobTypeID HoursPerWeek
1 1 2017-02-28 2 CE 0
2 1 2017-02-15 4 CE 40
3 1 2017-02-14 1 CE 40
4 1 2017-02-28 1 CE 48
My applications table looks like this:
ID VacancyID Forename Surname EmailAddress TelephoneNumber
1 1 John Smith jsmith#gmail.com 447777777777
2 2 John Smith jsmith#gmail.com 447748772641
3 2 John Smith jsmith#gmail.com 447777777777
4 2 John Smith jsmith#gmail.com 447700123456
5 4 John Smith jsmith#gmail.com 447400123569
6 4 John Smith jsmith#gmail.com 447400126547
7 4 John Smith jsmith#gmail.com 447555123654
I want a table that looks like this:
ID Active StartDate JobID HoursPerWeek NumberOfApplicants
1 1 2017-02-28 2 0 1
2 1 2017-02-15 4 40 3
3 1 2017-02-14 1 40 0
4 1 2017-02-28 1 48 3
How can I select that table using joins and count the number of applicants where the VacancyID is equal to the ID of the first vacancy table? I have tried:
select Vacancy.ID, VacancyID, count(*) as NumberOfApplications from VacancyApplication
join Vacancy on Vacancy.ID=VacancyID
group by VacancyID, Vacancy.ID
This obviously doesn't select all the other columns and it also does not select ID 3 because there are 0 applications for that - I want ID 3 to be there with a value of 0 as well as all the other columns. How do I do this? I've tried various forms of grouping and selecting but I'm quite new to SQL so I'm not really sure how this can be done.
Use RIGHT JOIN instead of INNER JOIN and count the vacancyid column from vacancyapplication table. For the non matching records you will get count as 0
SELECT v.id, v.Active, v.StartDate, v.JobID, v.HoursPerWeek
Count(va.vacancyid) AS NumberOfApplications
FROM vacancyapplication va
RIGHT JOIN vacancy v
ON v.id = va.vacancyid
GROUP BY v.id, v.Active, v.StartDate, v.JobID, v.HoursPerWeek
Start using Alias names, it makes the query more readable
Hoping, i understood your problem correctly. Please try below query
select Vacancy.ID, VacancyID, count(*) as NumberOfApplications from VacancyApplication
left join Vacancy on Vacancy.ID=VacancyID
group by VacancyID, Vacancy.ID
You can use count as a window function using the OVER clause, thus eliminating he need for group by:
SELECT v.ID,
v.Active,
v.StartDate,
v.JobID,
v.JobTypeID,
COUNT(va.ID) OVER(PARTITION BY v.ID) HoursPerWeek
FROM Vacancy v
LEFT JOIN vacancyapplication va ON(v.ID = va.VacancyID)
Use left join and table aliases:
select v.ID, count(va.VacancyID) as NumberOfApplications
from Vacancy v join
VacancyApplication va
on v.ID = va.VacancyID
group by v.ID;
You seem to want all the columns. You could include them in the group by. However, a correlated subquery or outer apply is simpler:
select v.*, va.cnt
from vacancy v outer apply
(select count(*) as cnt
from VacancyApplication va
where v.ID = va.VacancyID
) va;
This is probably more efficient anyway, especially if you have an index on VacancyApplication(VacancyID).

How to join 2 tables with no common column in sql

eg
Names
id | name
1 abc
2 efg
Area
id | areaName
3 area1
4 area2
The query should return
id | name | areaid
1 abc 3
1 abc 4
2 efg 3
2 efg 4
This should give the ecpected result:
select a.id, a.name, b.id from names,area
When you want to join all records from table A to all records from table B (i.e. get a Cartesian product, you can use CROSS JOIN:
SELECT Names.id, Names.name, Area.areaid
FROM Names
CROSS JOIN Area
ORDER BY Names.id, Area.areaid