get the incremental pattern - sql

I have table as below
Item|Year|Price
---------------
C|2010|50
C|2000|40
C|1999|30
A|2010|10
A|2009|15
B|2018|10
B|2017|100
B|2015|750
D|2018|220
D|2017|200
D|2016|185
I want to write a query so that I get only the item name which have price in incremental order every greater year.
The output of above pattern would be
ITEM
----
D
C
(D and C only have incremental price for each higher year)
I tried with self Join but I am not able the required output

I think this query will give you the results you want. It uses a self-join to find all years where an item has a price which is lower than a previous year. Items which have no later years where the price is lower (which will appear as i2.Item=NULL) will have a COUNT(i2.Item)=0:
SELECT i1.Item
FROM Items i1
LEFT JOIN Items i2 ON i2.Item = i1.Item AND i2.Price < i1.Price AND i2.Year > i1.Year
GROUP BY i1.Item
HAVING COUNT(i2.Item) = 0
Output:
Item
C
D
SQLFiddle Demo

You may use lag analytical function as in the following select statement :
select item
from
(
select sign(price - lag(price,1,0) over (order by year)) val,
t.item
from tab t
)
group by item
having avg(val)=1;
ITEM
----
D
C
SQL Fiddle Demo

Related

SQL return limited rows based on agregating sum

I want to return a number of rows from one table whose sum is dependent on a value from a row in another table:
Scenario: Sales order for a qty of particular item. The item is found in a number of Bin locations. The storeman needs to be directed to the oldest material.
I can create a query that will list the Bin, the Qty in the bin and list them in age (oldest to youngest) - all good so far, but say the order is for 100 units and there are 50 or so units in each bin and there are 40 bins, then I don't want to list all the bins, just the oldest two - just enough to be able to fulfill the order.
How do I do that?
Just some more info as requested
DB = MS SQL 2016
Sample Data:
The following is the data for a particular item showing the Bin, the qty in that bin and ageing date:
Bin#, Qty, Date
1,40,2018-05-15
3,45,2018-05-15
8,45,2018-02-10
12,45,2017-11-11
13,45,2018-02-10
15,45,2017-09-02
18,20,2017-09-02
The sales order is for 100 of these items, We want to pick FIFO (First-In-First-Out), so the results I want to return are:
18,20,2017-09-02
15,45,2017-09-02
12,45,2017-11-11
These three bins contain a total of 110 units so that is enough to satisfy the Sales Order. Note that order is Date, then Qty
The actual query is currently:
select
[OrderHed].[OrderNum] as [OrderHed_OrderNum],
[OrderRel].[OrderLine] as [OrderRel_OrderLine],
[Part].[PartNum] as [Part_PartNum],
[Part].[PartDescription] as [Part_PartDescription],
[OrderRel].[OurReqQty] as [OrderRel_OurReqQty],
[PartBin].[BinNum] as [PartBin_BinNum],
[PartBin].[OnhandQty] as [PartBin_OnhandQty],
[PartLot].[FirstRefDate] as [PartLot_FirstRefDate]
from Erp.OrderHed as OrderHed
inner join Erp.OrderDtl as OrderDtl on
OrderHed.Company = OrderDtl.Company
and OrderHed.OrderNum = OrderDtl.OrderNum
inner join Erp.OrderRel as OrderRel on
OrderDtl.Company = OrderRel.Company
and OrderDtl.OrderNum = OrderRel.OrderNum
and OrderDtl.OrderLine = OrderRel.OrderLine
and ( OrderRel.OpenRelease = True )
left outer join Erp.PartBin as PartBin on
OrderRel.Company = PartBin.Company
and OrderRel.WarehouseCode = PartBin.WarehouseCode
and ( not PartBin.BinNum like 'Q' )
inner join Erp.Part as Part on
OrderDtl.Company = Part.Company
and OrderDtl.PartNum = Part.PartNum
right outer join Erp.Part as Part
and
PartBin.Company = Part.Company
and PartBin.PartNum = Part.PartNum
inner join Erp.PartLot as PartLot on
PartBin.Company = PartLot.Company
and PartBin.PartNum = PartLot.PartNum
and PartBin.LotNum = PartLot.LotNum
where (OrderHed.OrderNum = #SalesOrder)
order by OrderDtl.OrderLine, PartLot.FirstRefDate, PartBin.OnhandQty
You can select the bin where their date is less then or equal the minimum date for which the sum of the quantity of all bins with a date less than or equal is greater then or equal your target quantity (e.g. 50).
SELECT *
FROM bin b
WHERE b.date <= (SELECT min(bb.date)
FROM bin bb
WHERE (SELECT sum(bbb.qty)
FROM bin bbb
WHERE bbb.date <= bb.date) >= 50)
ORDER BY b.date,
b.bin#;
This approach however can include more bins than necessary. If there are more bins from the youngest date, than they are needed to just satisfy the target quantity, all of them will be included anyhow. So the person who picks the items for the order would have to chose from these bins. But at least the FIFO rule is kept that way and the person has to count the items anyway and cannot just blindly pick from the returned bins.
SQL Fiddle (Note, that I added bin 20 to demonstrate the above mentioned problem.)
The problem I mentioned about 1. can be circumvented if you give all the bins a number ordered by the date. Then there will be no duplicate values as with the date. You can introduce this number by using ROW_NUMBER() in a CTE. Then select from the CTE with the same logic as in 1. but applied on the row number instead of the date.
WITH cte
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY b.date) row#,
b.*
FROM bin b
)
SELECT *
FROM cte c
WHERE c.row# <= (SELECT min(cc.row#)
FROM cte cc
WHERE (SELECT sum(ccc.qty)
FROM cte ccc
WHERE ccc.row# <= cc.row#) >= 50)
ORDER BY c.date,
c.bin#;
SQL Fiddle (Note, that I added bin 20 again to demonstrate, that the problem mentioned in 1. is tackled.)
Both methods however won't necessarily yield the "optimal" set of bins. For example, there might be a set of bins, with the right dates, that exactly hold the amount of items ordered but this set is only returned by chance. There might also be a set of bins with a cardinality less that the one of the returned set.

Using a stored procedure in Teradata to build a summarial history table

I am using Terdata SQL Assistant connected to an enterprise DW. I have written the query below to show an inventory of outstanding items as of a specific point in time. The table referenced loads and stores new records as changes are made to their state by load date (and does not delete historical records). The output of my query is 1 row for the specified date. Can I create a stored procedure or recursive query of some sort to build a history of these summary rows (with 1 new row per day)? I have not used such functions in the past; links to pertinent previously answered questions or suggestions on how I could get on the right track in researching other possible solutions are totally fine if applicable; just trying to bridge this gap in my knowledge.
SELECT
'2017-10-02' as Dt
,COUNT(DISTINCT A.RECORD_NBR) as Pending_Records
,SUM(A.PAY_AMT) AS Total_Pending_Payments
FROM DB.RECORD_HISTORY A
INNER JOIN
(SELECT MAX(LOAD_DT) AS LOAD_DT
,RECORD_NBR
FROM DB.RECORD_HISTORY
WHERE LOAD_DT <= '2017-10-02'
GROUP BY RECORD_NBR
) B
ON A.RECORD_NBR = B.RECORD_NBR
AND A.LOAD_DT = B.LOAD_DT
WHERE
A.RECORD_ORDER =1 AND Final_DT Is Null
GROUP BY Dt
ORDER BY 1 desc
Here is my interpretation of your query:
For the most recent load_dt (up until 2017-10-02) for record_order #1,
return
1) the number of different pending records
2) the total amount of pending payments
Is this correct? If you're looking for this info, but one row for each "Load_Dt", you just need to remove that INNER JOIN:
SELECT
load_Dt,
COUNT(DISTINCT record_nbr) AS Pending_Records,
SUM(pay_amt) AS Total_Pending_Payments
FROM DB.record_history
WHERE record_order = 1
AND final_Dt IS NULL
GROUP BY load_Dt
ORDER BY 1 DESC
If you want to get the summary info per record_order, just add record_order as a grouping column:
SELECT
load_Dt,
record_order,
COUNT(DISTINCT record_nbr) AS Pending_Records,
SUM(pay_amt) AS Total_Pending_Payments
FROM DB.record_history
WHERE final_Dt IS NULL
GROUP BY load_Dt, record_order
ORDER BY 1,2 DESC
If you want to get one row per day (if there are calendar days with no corresponding "load_dt" days), then you can SELECT from the sys_calendar.calendar view and LEFT JOIN the query above on the "load_dt" field:
SELECT cal.calendar_date, src.Pending_Records, src.Total_Pending_Payments
FROM sys_calendar.calendar cal
LEFT JOIN (
SELECT
load_Dt,
COUNT(DISTINCT record_nbr) AS Pending_Records,
SUM(pay_amt) AS Total_Pending_Payments
FROM DB.record_history
WHERE record_order = 1
AND final_Dt IS NULL
GROUP BY load_Dt
) src ON cal.calendar_date = src.load_Dt
WHERE cal.calendar_date BETWEEN <start_date> AND <end_date>
ORDER BY 1 DESC
I don't have access to a TD system, so you may get syntax errors. Let me know if that works or you're looking for something else.

Not getting desired output while doing calculation in sql query result

I have one query result as the following format.
Price Quarter
80 Q1
40 Q2
I need to calculate %Value and need to display 'NA' for Q1 and 2 needs to display for Q2
Desired Result is
Price Quarter %Value
80 Q1 NA
40 Q2 2=(80/40)
How will I get the desired result?
Hmmm. Your results are very specific. The following might be what you want:
select price, quarter,
(case when quarter = 'Q1' then NULL
else value * 1.0 / sum(value) over ()
end) as col
from q;
Any question of the form compute function of values in two adjacent rows (in some order) is answered by joining the table to itself, where the join is based on the "previous" value. See my running totals example for an explanation.
Your code will look something like this. The data you provide isn't sufficient to give a complete answer (no year, no identifying key) but I hope this will get you started.
select Q.price, Q.quarter, Q.value/P.value as '%value'
from (
select a.quarter, a.value, max(b.quarter) as prior
from T as a left join T as b
on a.quarter > b.quarter
group by a.price, a.quarter
) as Q
join T as P -- prior
on Q.prior = P.quarter

Selecting the rows with min value for a field, where the rest of fields differ

I am trying to write SQL (Access 2010) to select parts which have a minimum price from a table where the parts can repeat, as some of the other fields are different.
The table that looks like this:
Dist Part Num Ven Part Num Dist Desc Price
DD7777QED 7777QED DD Product A 10
IM7777QED 7777QED IM This is Product A 12
SY7777QED 7777QED SY Product A Desc 15
DD8888QED 8888QED DD Product B 15
IM8888QED 8888QED IM This is Product B 10
SY8888QED 8888QED SY Product B Desc 12
IM999ABC 999ABC IM Product C Desc 15
I am trying to extract all details for each row that has the min price for that Ven Part Num that repeats. In essence all details for the supplier's row that has the cheapest price for that Vendor Part Number.
The result from the above sample data should be this:
Dist Part Num Ven Part Num Dist Desc Price
DD7777QED 7777QED DD Product A 10
IM8888QED 8888QED IM This is Product A 10
IM999ABC 999ABC IM Product A Desc 15
Thanks
EDIT: Thank you jurgen d for your answer, although I think you meant to use Ven Part Num (instead of Dist Part Num). I have ammended to this query now which almost works to what I want:
SELECT T1.*
FROM My_Table T1
INNER JOIN
(
SELECT [Ven Part Num], MIN(Price) AS MPrice
FROM My_Table
GROUP BY [Ven Part Num]
) T2 ON T1.[Ven Part Num] = T2.[Ven Part Num] AND T1.Price = T2.MPrice
Challenge now is that if two Dist have the same MIN price for the same Ven Part Num, then the resulting extract contains 2 rows for that Ven Part Num, but I want just one, either will do. I tried TOP 1 but it runs and brings up only one row as result of the whole query. I have 40K rows I am expecting! How do I extract only one of these two rows in the final report?
Thanks again!
select t1.*
from your_table t1
inner join
(
select [Dist Part Num], min(price) as mprice
from your_table
group by [Dist Part Num]
) t2 on t1.[Dist Part Num] = t2.[Dist Part Num] and t1.price = t2.mprice

Variant use of the GROUP BY clause in TSQL

Imagine the following schema and sample data (SQL Server 2008):
OriginatingObject
----------------------------------------------
ID
1
2
3
ValueSet
----------------------------------------------
ID OriginatingObjectID DateStamp
1 1 2009-05-21 10:41:43
2 1 2009-05-22 12:11:51
3 1 2009-05-22 12:13:25
4 2 2009-05-21 10:42:40
5 2 2009-05-20 02:21:34
6 1 2009-05-21 23:41:43
7 3 2009-05-26 14:56:01
Value
----------------------------------------------
ID ValueSetID Value
1 1 28
etc (a set of rows for each related ValueSet)
I need to obtain the ID of the most recent ValueSet record for each OriginatingObject. Do not assume that the higher the ID of a record, the more recent it is.
I am not sure how to use GROUP BY properly in order to make sure the set of results grouped together to form each aggregate row includes the ID of the row with the highest DateStamp value for that grouping. Do I need to use a subquery or is there a better way?
You can do it with a correlated subquery or using IN with multiple columns and a GROUP-BY.
Please note, simple GROUP-BY can only bring you to the list of OriginatingIDs and Timestamps. In order to pull the relevant ValueSet IDs, the cleanest solution is use a subquery.
Multiple-column IN with GROUP-BY (probably faster):
SELECT O.ID, V.ID
FROM Originating AS O, ValueSet AS V
WHERE O.ID = V.OriginatingID
AND
(V.OriginatingID, V.DateStamp) IN
(
SELECT OriginatingID, Max(DateStamp)
FROM ValueSet
GROUP BY OriginatingID
)
Correlated Subquery:
SELECT O.ID, V.ID
FROM Originating AS O, ValueSet AS V
WHERE O.ID = V.OriginatingID
AND
V.DateStamp =
(
SELECT Max(DateStamp)
FROM ValueSet V2
WHERE V2.OriginatingID = O.ID
)
SELECT OriginatingObjectID, id
FROM (
SELECT id, OriginatingObjectID, RANK() OVER(PARTITION BY OriginatingObjectID
ORDER BY DateStamp DESC) as ranking
FROM ValueSet)
WHERE ranking = 1;
This can be done with a correlated sub-query. No GROUP-BY necessary.
SELECT
vs.ID,
vs.OriginatingObjectID,
vs.DateStamp,
v.Value
FROM
ValueSet vs
INNER JOIN Value v ON v.ValueSetID = vs.ID
WHERE
NOT EXISTS (
SELECT 1
FROM ValueSet
WHERE OriginatingObjectID = vs.OriginatingObjectID
AND DateStamp > vs.DateStamp
)
This works only if there can not be two equal DateStamps for a OriginatingObjectID in the ValueSet table.