Join tables based on dates with check - sql

I have two tables in PostgreSQL:
Demans_for_parts:
demandid partid demanddate quantity
40 125 01.01.17 10
41 125 05.01.17 30
42 123 20.06.17 10
Orders_for_parts:
orderid partid orderdate quantity
1 125 07.01.17 15
54 125 10.06.17 25
14 122 05.01.17 30
Basicly Demans_for_parts says what to buy and Orders_for_parts says what we bought. We can buy parts which do not list on Demans_for_parts.
I need a report which shows me all parts in Demans_for_parts and how many weeks past since the most recent matching row in Orders_for_parts. note quantity field is irrelevent here,
The expected result is (if more than one row per part show the oldes):
partid demanddate weeks_since_recent_order
125 01.01.17 2 (last order is on 10.06.17)
123 20.06.17 Unhandled

I think the tricky part is getting one row per table. But that is easy using distinct on. Then you need to calculate the months. You can use age() for this purpose:
select dp.partid, dp.date,
(extract(year from age(dp.date, op.date))*12 +
extract(month from age(dp.date, op.date))
) as months
from (select distinct on (dp.partid) dp.*
from demans_for_parts dp
order by dp.partid, dp.date desc
) dp left join
(select distinct on (op.partid) op.*
from Orders_for_parts op
order by op.partid, op.date desc
) op
on dp.partid = op.partid;

smth like?
with o as (
select distinct partid, max(orderdate) over (partition by partid)
from Orders_for_parts
)
, p as (
select distinct partid, min(demanddate) over (partition by partid)
from Demans_for_parts
)
select p.partid, min as demanddate, date_part('day',o.max - p.min)/7
from p
left outer join o on (p.partid = o.partid)
;

Related

SQL subqueries using SELECTs only

I need to write a query with subqueries using SELECT and aggregation functions only, e.g.:
select distinct m_name
from MANUFACT
where m_id in (select TOP 1 m_id
from PRODUCT
where p_id = (select p_id
from PRODUCT
where p_desc = 'Bronze Sculpture'));
The question is about query similar to this one, but using SUM(). The data I have:
Table SPERSON:
sp_id | sp_name
---------------
10 | Jones
39 | Matsu
23 | Atsuma
Table SALE:
sp_id | qty
-----------
10 | 20
23 | 30
10 | 10
39 | 20
etc.
The task is to return the sp_name s whose total number of products is <= 75.
The teacher says we're not allowed to use join, but I doubt whether is any way not to use it.
This is what I have so far:
select sp_name
from SPERSON
where sp_id in (select sp_id from SALE
where qty in (select sum(qty) group by sp_id));
Anyway, I only got the 'Each GROUP BY expression must contain at least one column that is not an outer reference' error, but can't really get the thing.
You can use correlated subquery :
SELECT q.sp_name
FROM( SELECT sp_name,
(SELECT SUM(qty) FROM sale s WHERE s.sp_id = p.sp_id ) AS qty
FROM SPERSON p
GROUP BY sp_name
) q
GROUP BY q.sp_name
HAVING SUM(q.qty) <= 75
Mostly, using correlated subqueries, which may contains a reference to the outer query and so produces different results for each row of the outer query, is not suggested. But I suggested to use it as an alternative method depending on your case for not being permitted to use JOIN. Btw, it is more straightforward to use JOIN .
You can try to approach a problem from different direction.
Create a query to calculate total quantity grouped by sp_id
SELECT s.sp_id, SUM(s.qty)
FROM SALE s
GROUP BY s.sp_id
Filter persons id which has quantity less or equal to 75
SELECT s.sp_id, SUM(s.qty)
FROM SALE s
GROUP BY s.sp_id
HAVING SUM(s.qty) <= 75
Because joins not allowed, "inject" name as a subquery
SELECT
(SELECT p.sp_name FROM SPERSON p WHERE p.sp_id = s.sp_id) AS name
FROM SALE s
GROUP BY s.sp_id
HAVING SUM(s.qty) <= 75

How to display only few columns of a table based on the data

Consider the table below
Name partno. sch_date WO# owed panels
aa 1234 08/22/2017 121 22 26
aa 1234 08/22/2017 222 22 27
aa 1234 08/22/2017 242 22 27
aa 1234 08/29/2017 152 20 24
aa 1234 08/29/2017 167 20 24
aa 1234 08/29/2017 202 20 26`
Is it possible to display the data in such way that when the number of panels is greater than owed, then i don't won't to dispaly the other partno. schedule on the same date(sch_date).
Expected Result
Name partno. sch_date WO# owed panels
aa 1234 08/22/2017 121 22 26
aa 1234 08/29/2017 152 20 24
Cross apply may help here. (note you can see why I asked about order in my earlier comment as the ORDER of records in a table is not guaranteed! we need to know in what you want the records evaluated! Date isn't enough (unless it has a time compoent not displayed that is different!)
WORKING example on Rextester: http://rextester.com/CAUK18185
Many assumptions made:
When owned is > panels you don't need to see the record.
You want to see the the lowest WO# when owed is < panels and suppress all other records including ones where owed > panels.
If there are no records for a date, name and partno that have owed < panels, you want to see no records.
If these assumptions are incorrect, please provide a better sample set and expected results to test these types of situations and explain what you want to have happen.
SELECT Distinct B.*
FROM tblName Z
CROSS APPLY (SELECT TOP 1 A.*
FROM tblName A
WHERE A.owed < A.panels
and Z.Name = A.Name
and Z.[partno.] = a.[partno.]
and Z.sch_date = a.sch_date
ORDER by A.name, A.[partno.], A.sch_date, A.[wo#]) B
For each record in A run a query which returns the lowest wo# for a name, partno and sch_date when the owed < panels.
UPDATED:
I see in a comment you want to keep records if owed > panels... if it's encountered first.... but what if it's not encountered first?
http://rextester.com/NXS51018
--First we get all the records w/ a owed < panels per group and assign the earliest row (that having the lowest WO) a RN of 1. then we return that set.
cte as (
Select A.*, row_number() over (Partition by Name, [partno.], sch_date ORDER BY [WO#]) RN
from tblName A
where owed < panels)
Select * from cte where RN =1
UNION ALL
--We then union in the records where owed >=panels and their WO# < the wo# from the CTE.
SELECT Z.*, 0 as rn FROM tblName Z where owed >=panels
and exists (Select * from cte
where Z.name = CTE.name
and Z.[partno.] = cte.[partno.]
and Z.sch_date = cte.sch_date
and CTE.[WO#] > Z.[WO#]) --Now this line may not be needed, depending on if you want all or just some of the WO#'s when owed >=panels.
ORDER BY name, [partno.], Sch_date, [Wo#]
After last comment update:
cte as (
Select A.*, row_number() over (Partition by Name, [partno.], sch_date ORDER BY [WO#]) RN
from tblName A
where owed < panels),
cte2 as (Select * from cte where RN =1
UNION ALL
SELECT Z.*, 0 as rn FROM tblName Z where owed >=panels
and exists (Select * from cte
where Z.name = CTE.name
and Z.[partno.] = cte.[partno.]
and Z.sch_date = cte.sch_date
and CTE.[WO#] > Z.[WO#]))
Select * into SOQ#45619304 from CTE2; --This line creates the table based on the 2nd cte results.
Select * from SOQ#45619304;
You can try this -
SELECT Name, partno., sch_date, WO#, owed, panels
FROM YOUR_TABLE
WHERE panels < owed
UNION ALL
SELECT Name, partno., sch_date, MIN(WO#), owed, MIN(panels)
FROM YOUR_TABLE
WHERE panels > owed
GROUP BY Name, partno., sch_date, owed
ORDER BY Name

SQL Server Amount Split

I have below 2 tables in SQL Server database.
Customer Main Expense Table
ReportID CustomerID TotalExpenseAmount
1000 1 200
1001 2 600
Attendee Table
ReportID AttendeeName
1000 Mark
1000 Sam
1000 Joe
There is no amount at attendee level. I have need to manually calculate individual attendee amount as mentioned below. (i.e split TotalExpenseAmount based on number of attendees and ensure individual split figures round to 2 decimals and sums up to the TotalExpenseAmount exactly)
The final report should look like:
ReportID CustID AttendeeName TotalAmount AttendeeAmount
1000 1 Mark 200 66.66
1000 1 Sam 200 66.66
1000 1 Joe 200 66.68
The final report will have about 1,50,000 records. If you notice the attendee amount I have rounded the last one in such a way that the totals match to 200. What is the best way to write an efficient SQL query in this scenario?
You can do this using window functions:
select ReportID, CustID, AttendeeName, TotalAmount,
(case when seqnum = 1
then TotalAmount - perAttendee * (cnt - 1)
else perAttendee
end) as AttendeeAmount
from (select a.ReportID, a.CustID, a.AttendeeName, e.TotalAmount,
row_number() over (partition by reportId order by AttendeeName) as seqnum,
count(*) over (partition by reportId) as cnt,
cast(TotalAmount * 1.0 / count(*) over (partition by reportId) as decimal(10, 2)) as perAttendee
from attendee a join
expense e
on a.ReportID = e.ReportID
) ae;
The perAttendee amount is calculated in the subquery. This is rounded down by using cast() (only because floor() doesn't accept a decimal places argument). For one of the rows, the amount is the total minus the sum of all the other attendees.
Doing something similar to #Gordon's answer but using a CTE instead.
with CTECount AS (
select a.ReportId, a.AttendeeName,
ROW_NUMBER() OVER (PARTITION BY A.ReportId ORDER BY A.AttendeeName) [RowNum],
COUNT(A.AttendeeName) OVER (PARTITION BY A.ReportId) [AttendeeCount],
CAST(c.TotalExpenseAmount / (COUNT(A.AttendeeName) OVER (PARTITION BY A.ReportId)) AS DECIMAL(10,2)) [PerAmount]
FROM #Customer C INNER JOIN #Attendee A ON A.ReportId = C.ReportID
)
SELECT CT.ReportID, CT.CustomerId, AT.AttendeeName,
CASE WHEN CC.RowNum = 1 THEN CT.TotalExpenseAmount - CC.PerAmount * (CC.AttendeeCount - 1)
ELSE CC.PerAmount END [AttendeeAmount]
FROM #Customer CT INNER JOIN #Attendee AT
ON CT.ReportID = AT.ReportId
INNER JOIN CTECount CC
ON CC.ReportId = CT.ReportID AND CC.AttendeeName = AT.AttendeeName
I like the CTE because it allows me to separate the different aspects of the query. The cool thing that #Gordon used was the Case statement and the inner calculation to have the lines total correctly.

returning only one row for each value of a column along with other values in different columns

I am working on a query on a SQL table which has several columns along with several rows of data and the query returns one row for each unique first and second columns based on the criteria given in the query.
For Example, I have the following table CC
product term bid offer bidcp offercp
AA sep14 20 10 x y
AA Sep14 15 9 p q
BA Sep14 30 15 as ps
XY Sep14 25 15 r t
XY Oct14 30 20 t r
XY Oct14 25 22 p q
When I run the query on the above table it should return the following data
product term bid offer bidcp offercp
AA sep14 20 9 x q(coming from a record which has lowest offer)
BA Sep14 30 15 as ps
XY Sep14 25 15 r t
XY Oct14 30 20 t r
When I executed the following query it grouped the data in CC even by bidcp and offercp and returned almost all the rows as both offercp and bidcp are unique in one or the other way but I just wanted bidcp and offercp to be where bid and offer are coming from assuming pair of both bid and offer are unique for each product and term
select product,term,max(bid) as bid,min(offer) as offer,bidcp,offercp from canadiancrudes where product like '%/%' group by product,term,bidcp,offercp
But, when I removed bidcp and offercp from groupby clause it threw me an obvious error
Column 'CC.BidCP' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Is there a better way to fix it?
In that case, you need 2 CTEs:
WITH o AS (
SELECT product,term,offer,offercp, ROW_NUMBER() OVER (PARTITION BY product, term ORDER BY offer ASC) AS rn
FROM canadiancrudes where product like '%/%'
)
, b AS (
SELECT product,term,bid,bidcp, ROW_NUMBER() OVER (PARTITION BY product, term ORDER BY bid DESC) AS rn
FROM canadiancrudes where product like '%/%'
)
SELECT o.product,o.term,b.bid,o.offer,b.bidcp,o.offercp
FROM o
INNER JOIN b
ON o.product=b.product
AND o.term=b.term
WHERE o.rn=1
AND b.rn=1
Use a CTE to get the min, max value -
WITH MaxMin_CTE AS (
SELECT product,term,max(bid) as bid,min(offer) AS Offer
FROM CC
GROUP BY product,term)
SELECT * from CC
INNER JOIN MaxMin_CTE ON CC.product = MaxMin_CTE .product
AND CC.bid= MaxMin_CTE.bid AND CC.Offer = MaxMin_CTE.offer
AND CC.Term = MaxMin_CTE.Term
Heres the SQL fiddle -
http://sqlfiddle.com/#!6/a6588/2

How can I SELECT the max row in a table SQL?

I have a little problem.
My table is:
Bill Product ID Units Sold
----|-----------|------------
1 | 10 | 25
1 | 20 | 30
2 | 30 | 11
3 | 40 | 40
3 | 20 | 20
I want to SELECT the product which has sold the most units; in this sample case, it should be the product with ID 20, showing 50 units.
I have tried this:
SELECT
SUM(pv."Units sold")
FROM
"Products" pv
GROUP BY
pv.Product ID;
But this shows all the products, how can I select only the product with the most units sold?
Leaving aside for the moment the possibility of having multiple products with the same number of units sold, you can always sort your results by the sum, highest first, and take the first row:
SELECT pv."Product ID", SUM(pv."Units sold")
FROM "Products" pv
GROUP BY pv."Product ID"
ORDER BY SUM(pv."Units sold") DESC
LIMIT 1
I'm not quite sure whether the double-quote syntax for column and table names will work - exact syntax will depend on your specific RDBMS.
Now, if you do want to get multiple rows when more than one product has the same sum, then the SQL will become a bit more complicated:
SELECT pv.`Product ID`, SUM(pv.`Units sold`)
FROM `Products` pv
GROUP BY pv.`Product ID`
HAVING SUM(pv.`Units sold`) = (
select max(sums)
from (
SELECT SUM(pv2.`Units sold`) as "sums"
FROM `Products` pv2
GROUP BY pv2.`Product ID`
) as subq
)
Here's the sqlfiddle
SELECT SUM(pv."Units sold") as `sum`
FROM "Products" pv
group by pv.Product ID
ORDER BY sum DESC
LIMIT 1
limit 1 + order by
The Best and effective way to this is Max function
Here's The General Syntax of Max function
SELECT MAX(ID) AS id
FROM Products;
and in your Case
SELECT MAX(Units Sold) from products
Here is the Complete Reference to MIN and MAX functions in Query
Click Here