Select MAX and RIGHT OUTER JOIN - sql

I have platform to extract data from sql tables and so far all queries were generated by simple drag and drop tool. Now I am trying to change query manually, but it's not working as expected...
Can you take a look?
Query delivered by generator:
SELECT
repo.MAT.MAT_A_COD,
inventory.INV.MRP_RQMT_DT,
SUM(inventory.INV.MRP_AVL_QTY)
FROM
repo.MAT RIGHT OUTER JOIN inventory.INV ON (inventory.INV.MRP_MAT_A_FK=repo.MAT.MAT_A_PK)
WHERE
( inventory.INV.MRP_COMPANY_COD IN ('01','02') )
GROUP BY
1,
2
Results:
Material A | 2020.01.01 | 100
Material A | 2020.01.02 | 200
Material A | 2020.01.03 | 300
Material B | 2020.01.01 | 10
Material B | 2020.01.02 | 0
What I am looking for: only values for the latest date for each material.
Material A | 2020.01.03 | 300
Material B | 2020.01.02 | 0
I tried with MAX(inventory.INV.MRP_RQMT_DT), but no success. Any help is appreciated!

You can try the below -
SELECT
repo.MAT.MAT_A_COD,
inventory.INV.MRP_RQMT_DT,
SUM(inventory.INV.MRP_AVL_QTY)
FROM
repo.MAT RIGHT OUTER JOIN inventory.INV ON inventory.INV.MRP_MAT_A_FK=repo.MAT.MAT_A_PK
WHERE
inventory.INV.MRP_COMPANY_COD IN ('01','02') and inventory.INV.MRP_RQMT_DT=(select max(inventory.INV.MRP_RQMT_DT) from inventory.INV inv1 where inventory.INV.MRP_MAT_A_FK=inv1.MRP_MAT_A_FK)
GROUP BY 1, 2

You did not specify the database engine, but the RANK windows function works in many major ones (I will use T-SQL syntax).
SELECT * FROM (
SELECT
repo.MAT.MAT_A_COD,
inventory.INV.MRP_RQMT_DT,
SUM(inventory.INV.MRP_AVL_QTY),
RANK () OVER (PARTITION BY repo.MAT.MAT_A_COD ORDER BY inventory.INV.MRP_RQMT_DT) rn
FROM repo.MAT RIGHT OUTER JOIN inventory.INV ON (inventory.INV.MRP_MAT_A_FK=repo.MAT.MAT_A_PK)
WHERE inventory.INV.MRP_COMPANY_COD IN ('01','02')
GROUP BY 1, 2
)
WHERE rn = 1

You can use window functions:
SELECT m.MAT_A_COD, i.MRP_RQMT_DT,
SUM(i.MRP_AVL_QTY)
FROM repo.MAT LEFT JOIN
(SELECT i.*,
MAX(MRP_RQMT_DT) OVER (PARTITION BY MRP_MAT_A_FK ORDER BY DESC) as max_MRP_RQMT_DT
FROM inventory.INV i
) i
ON i.MRP_MAT_A_FK = r.MAT_A_PK AND
i.MRP_RQMT_DT = i.max_MRP_RQMT_DT
WHERE i.MRP_COMPANY_COD IN ('01', '02')
GROUP BY 1, 2;
Note other changes to the query:
Table aliases make the query easier to write and to read.
An outer join does not seem necessary at all. But if you do use one, it is probably on the MAT table, not the inventory table.
If you use an outer join, you should try to take the columns from the table where you are keeping all the rows -- the first table in a LEFT JOIN. I don't recommend RIGHT JOINs in general.

Related

Select rows in left join which depend on sum of a field in the other table?

Im trying to write a SQL left outer join query where the left rows are selected based on the sum of a field in rows in the other (right) table. The other table has an id field that links back to the left table and there is a one-to-many relationship between left and right tables. The tables (simplified to relevant fields only) look like this:
left_table:
+--------+
| id |
| amount |
+--------+
right_table:
+-------------------+
| id |
| amount |
| left_table_row_id |
+-------------------+
Basically the right table's rows' amount fields have fractions of the amounts in the left table and are associated back to the left_table, so several right_table rows might be linked to a single left_table row.
Im trying to select only left_table rows where left_table.id=right_table_id and where the sum of the amounts in the right_table's rows with linked id are equal to left_table.amount. We can't use aggregate in a WHERE clause and I had no luck with using HAVING. I hope that makes sense.
You can filter with a correlated subquery:
select l.*
from left_table l
where l.amount = (select sum(r.amount) from right_table r where r.id = l.id)
This should be possible with the following query:
with agg as
(
select left_table_row_id,sum(amount) as amount
from right_table
group by left_table_row_id
)
select *
from left_table lt
where exists (select 1 from agg where lt.id=agg.left_table_row_id and lt.amount = agg.amount)

How to join records from a group by

I don't know if the title is as descriptive as I wanted but I'll try to explain with real examples of what I want.
In my table 'Details' I have
Date | ProductId | Total
-------------------------------------
17/05/20 | 16788 | 62
--------------------------------------
19/05/20 | 3789 | 15
So I want the result be something like that:
17/05/20 - 16788 - 62
17/05/20 - 3789 - NULL (or 0)
19/05/20 - 16788 - NULL (or 0)
19/05/20 - 3789 - 15
I started doing RIGHT JOIN with a GROUP BY of the Dates, but didn't work.
I run out of ideas, can someone help me?
Thanks in advance
You can generate the rows with a cross join and then bring in the values using left join:
SELECT d.date,
p.productid,
t.total
FROM (
SELECT DISTINCT DATE
FROM details
) d
CROSS JOIN (
SELECT DISTINCT productid
FROM details
) p
LEFT JOIN details t
ON t.date = d.date
AND t.productid = p.productid
ORDER BY
d.date,
p.productid DESC;

SQL- Creating an Inner JOIN for Two Columns inside Same Table

I am attempting to work out a practice problem from my book.
The problem goes like this:
Find all the vendors who have invoices that have not been paid yet.
(Hint: the invoice_total will be different than the payment_total).
Rewrite the above query in a total of 3 ways:
Using equijoins, using INNER JOIN and using NATURAL JOIN.
I completed the first step by doing,
SELECT DISTINCT VENDOR_ID
FROM INVOICES
WHERE Invoice_Total != payment_total;
However, when I try to do the inner joins, I keep getting errors.
Both Invoice_Total and Payment_Total are columns inside of the same "INVOICES" table.
How would I be able to show the discrepancies whilst pulling the vendor ID's?
This is a picture of the practice database that I am working with.
It seems silly to inner join a table to itself to solve this particular problem (there are plenty of good reasons to self-join, but this isn't one of them), but I suppose from a "practice problem" standpoint it's reasonable.
I would think here it would be best to pre-aggregate the invoices before the join to cut down on the processing time (unless there is an index in place to help the join):
SELECT t1.vendor_id
FROM (SELECT vendor_id, sum(invoice_total) sum_invoice_total FROM INVOICES GROUP BY vendor_id) t1
INNER JOIN (SELECT vendor_id, sum(payment_total) sum_payment_total FROM INVOICES GROUP BY vendor_id) t2
ON t1.vendor_id = t2.vendor_id
WHERE
t1.sum_invoice_total != t2.sum_payment_total
There is a chance this could break down though if it's possible for a vendor to overpay for an invoice. Consider:
+------------+-----------+---------------+---------------+
| invoice_id | vendor_id | invoice_total | payment_total |
+------------+-----------+---------------+---------------+
| 1 | a | 10 | 20 |
| 2 | a | 10 | 0 |
+------------+-----------+---------------+---------------+
Without pre-aggregating (again this makes no sense, but it will work):
SELECT DISTINCT t1.vendor_id
FROM invoices t1
INNER JOIN invoices t2
ON t1.invoice_id = t2.invoice_id
WHERE
t1.invoice_total != t2.payment_total
This is nearly identical to your original query, but adds in a superfluous inner join. I'm just guessing at your primary key as invoice_id here. Edit as needed.

Full outer join not bringing all results SQL-Server

I have an issue trying to retrieve all results from a join. I have set up a similar scenario in SQL fiddle and it works but in SQL Server it doesn't. I want to bring results for everything if they're either invoiced or shipped.
The result i am getting in SQL-SERVER is
| No | Order1 | Shipdate | No | Order1 | InvDate |
|-----|--------|----------|--------|--------|----------|
| 111 | 222 | 17-01-18 | 111 | 222 | 24-01-18 |
| 222 | 333 | 18-01-18 | 222 | 333 | 24-01-18 |
Even if the change the join to full outer, right join i still get this result.
I would have thought if i use full outer it will bring all the results back regardless of matches but it doesnt.
What am i missing to give me the full outer result? Thanks
sql fiddle - http://sqlfiddle.com/#!18/89943/1
This is your query:
SELECT S.No, s.Order1, s.Shipdate, i.No, i.Order1, i.InvDate
FROM Ship S LEFT JOIN
Invoice I
ON s.No=i.No AND s.Order1 = i.Order1
WHERE S.Person = 1;
Changing the LEFT JOIN to FULL JOIN doesn't change anything. The WHERE clause turns the FULL JOIN into a LEFT JOIN, because non-matching rows on that table have NULL values and fail the WHERE condition.
The OUTER JOIN query can be setup as follows:
SELECT
S.No, s.Order1, s.Shipdate,
i.No, i.Order1, i.InvDate
FROM Ship S
FULL JOIN Invoice I
ON s.No = i.No AND
s.Order1=i.Order1
-- WHERE S.Person = 1
and produces output
But by adding following filtering criteria to WHERE clause, the OUTER JOIN will produce exactly the same result with LEFT OUTER JOIN
WHERE S.Person = 1
Left Join
SELECT
S.No, s.Order1, s.Shipdate,
i.No, i.Order1, i.InvDate
FROM Ship S
LEFT JOIN Invoice I
ON s.No = i.No AND
s.Order1=i.Order1
WHERE S.Person = 1
In this sense, the Full Outer Join will give any rows that meet the ON clause criteria (S.[No]=I.[No] AND S.Order1=I.Order1) then any rows that don't meet the ON clause criteria but do meet the WHERE criteria.
So it is necessary to include WHERE I.Person=1 as well as WHERE S.Person=1 but qualified by the OR Clause for those rows that have not been shipped yet or have been shipped but not invoiced (if any).
SELECT S.[No],S.Order1,S.Shipdate,I.No,I.Order1,I.InvDate
FROM Ship S FULL OUTER JOIN Invoice I
ON S.[No]=I.[No] AND S.Order1=I.Order1
WHERE S.Person=1 OR I.Person=1
I am assuming you are looking only for Person 1's data.
You can use cross join to get the result for this.
Code
SELECT S.No, s.Order1, s.Shipdate, i.No, i.Order1, i.InvDate
FROM Ship S CROSS JOIN
Invoice I
WHERE S.Person = 1;

How to include US states with zero counts() in Oracle SQL?

I'm struggling to craft a query for an Oracle db that will return "0" for each US state that is not represented in the results of this query:
SELECT TBL_STATES.STATE_ABBR as State, count(tbl_stations.station_state) AS Observations
FROM TBL_STATES
LEFT JOIN TBL_STATIONS ON TBL_STATES.STATE_ABBR = TBL_STATIONS.STATION_STATE
LEFT JOIN TBL_OBSERVATIONS ON TBL_STATIONS.STATION_ID = TBL_OBSERVATIONS.STATION_ID
WHERE EXTRACT(year FROM TBL_OBSERVATIONS.OBSERVATION_DATE)='2015'
AND EXTRACT(month FROM TBL_OBSERVATIONS.OBSERVATION_DATE)='8'
GROUP BY STATE_ABBR
ORDER BY STATE_ABBR
The query currently returns a count for each state in which an observation has been made, as shown here:
STATE | OBSERVATIONS
AZ | 131
CA | 30
CO | 9
FL | 6
...and so on.
What I'd like to see is a count for every entry in TBL_STATES (which contains records for all 50 states + DC & PR):
STATE | OBSERVATIONS
AK | 0
AL | 0
AR | 0
AZ | 131
CA | 30
CO | 9
CT | 0
DC | 0
DE | 0
FL | 6
...etc.
I've also attempted variations of NVL(count(TBL_STATES.STATE_ABBR),0) without success.
What the heck am I missing here?
Just move the date conditions to the ON clause:
SELECT TBL_STATES.STATE_ABBR as State, count(o.station_id) AS Observations
FROM TBL_STATES LEFT JOIN
TBL_STATIONS
ON TBL_STATES.STATE_ABBR = TBL_STATIONS.STATION_STATE LEFT JOIN
TBL_OBSERVATIONS
ON TBL_STATIONS.STATION_ID = TBL_OBSERVATIONS.STATION_ID AND
EXTRACT(year FROM TBL_OBSERVATIONS.OBSERVATION_DATE) = 2015
EXTRACT(month FROM TBL_OBSERVATIONS.OBSERVATION_DATE) = 8
GROUP BY STATE_ABBR
ORDER BY STATE_ABBR;
Because you seem to want to count observations, I also fixed the COUNT() to be from the observations table. This is important when the left join does not find a match.
I am a big fan of using table aliases. Also, direct date comparisons can be more efficient than extract(). So, I would recommend:
SELECT st.STATE_ABBR as State, count(o.STATION_ID) AS Observations
FROM TBL_STATES st LEFT JOIN
TBL_STATIONS sta
ON st.STATE_ABBR = sta.STATION_STATE LEFT JOIN
TBL_OBSERVATIONS o
ON sta.STATION_ID = o.STATION_ID AND
o.OBSERVATION_DATE >= DATE '2015-08-01' AND
o.OBSERVATION_DATE < DATE '2015-09-01'
GROUP BY st.STATE_ABBR
ORDER BY st.STATE_ABBR;
Change the WHERE clause to AND moving the criteria to the join. The where clause is negating the left join making it an inner.
we may also have to use a sub query to limit the results first then join... or perhaps you need to be counting the occurrences of observation_date instead of station_state, since year isn't in tbl_stations...
SELECT TBL_STATES.STATE_ABBR as State, count(b.ovservation_date) AS Observations
FROM TBL_STATES
LEFT JOIN TBL_STATIONS ON TBL_STATES.STATE_ABBR = TBL_STATIONS.STATION_STATE
LEFT JOIN (SELECT * FROM TBL_OBSERVATIONS
WHERE EXTRACT(year FROM TBL_OBSERVATIONS.OBSERVATION_DATE)='2015'
AND EXTRACT(month FROM TBL_OBSERVATIONS.OBSERVATION_DATE)='8') B
ON TBL_STATIONS.STATION_ID = B.STATION_ID
GROUP BY STATE_ABBR
ORDER BY STATE_ABBR
Logically the left joins are returning NULL counts for those states... but then the where clause is ELIMINATING the records because there is no observation date thus NULL <> 2015, and the records are being excluded, negating the left join.