Getting the lastest entry grouped by ID - sql

I have a table with stock for products. The problem is that every time there is a stock change, the new value is stored, together with the new Quantity. Example:
ProductID | Quantity | LastUpdate
1 123 2019.01.01
2 234 2019.01.01
1 444 2019.01.02
2 222 2019.01.02
I therefore need to get the latest stock update for every Product and return this:
ProductID | Quantity
1 444
2 222
The following SQL works, but is slow.
SELECT ProductID, Quantity
FROM (
SELECT ProductID, Quantity
FROM Stock
WHERE LastUpdate
IN (SELECT MAX(LastUpdate) FROM Stock GROUP BY ProductID)
)
Since the query is slow and supposed to be left joined into another query, I really would like some input on how to do this better.
Is there another way?

Use analytic functions. row_number can be used in this case.
SELECT ProductID, Quantity
FROM (SELECT ProductID, Quantity, row_number() over(partition by ProductID order by LstUpdte desc) as rnum
FROM Stock
) s
WHERE RNUM = 1
Or with first_value.
SELECT DISTINCT ProductID, FIRST_VALUE(Quantity) OVER(partition by ProductID order by LstUpdte desc) as quantuity
FROM Stock

Just another option is using WITH TIES in concert with Row_Number()
Full Disclosure: Vamsi's answer will be a nudge more performant.
Example
Select Top 1 with ties *
From YourTable
Order by Row_Number() over (Partition By ProductID Order by LastUpdate Desc)
Returns
ProductID Quantity LastUpdate
1 444 2019-01-02
2 222 2019-01-02

So you Could use a CTE(Common Table Expression)
Base Data:
SELECT 1 AS ProductID
,123 AS Quantity
,'2019-01-01' as LastUpdate
INTO #table
UNION
SELECT 2 AS ProductID
,234 AS Quantity
,'2019-01-01' as LastUpdate
UNION
SELECT 1 AS ProductID
,444 AS Quantity
,'2019-01-02' as LastUpdate
UNION
SELECT 2 AS ProductID
,222 AS Quantity
,'2019-01-02' as LastUpdate
Here is the code using a Common Table Expression.
WITH CTE (ProductID, Quantity, LastUpdate, Rnk)
AS
(
SELECT ProductID
,Quantity
,LastUpdate
,ROW_NUMBER() OVER(PARTITION BY ProductID ORDER BY LastUpdate DESC) AS Rnk
FROM #table
)
SELECT ProductID, Quantity, LastUpdate
FROM CTE
WHERE rnk = 1
Returns
You could then Join the CTE to whatever table you need.

row_number() function might be the most efficient, but the big slow down in your query is the use of the IN statement when used on a subquery, it's a little bit of a tricky one but a join is faster. This query should get what you want and be much faster.
SELECT
a.ProductID
,a.Quantity
FROM stock as a
INNER JOIN (
SELECT
ProductID
,MAX(LastUpdate) as LastUpdate
FROM stock
GROUP BY ProductID
) b
ON a.ProductID = b.ProductId AND
a.LastUpdate = b.LastUpdate

Related

SQL Server : SELECT Highest Price and add qty's from table

I have the following table TableAllProds:
ProdName ManuPartNo Price Qty Supplier
--------------------------------------------------
Part1 R10001 100.00 2 Supp1
Part2 R10002 500.00 2 Supp2
Part3 R30023 50.00 1 Supp3
Part2again R10002 100.00 5 Supp4
Part2Again R10002 300.00 10 Supp5
Part1again R10001 200.00 5 Supp3
I have a select statement to bring me back the highest price which works fine if there are duplicate products from different suppliers.
SELECT
ProdName, ManuPartNo, Price, Qty, Supplier
FROM
(SELECT
dbo.TableAllProds.*,
ROW_NUMBER() OVER (PARTITION BY ManuPartNo ORDER BY Price ASC) AS RN
FROM
dbo.TableAllProds) AS t
WHERE
RN = 1
ORDER BY
ManuPartNo
However I would also like to total all of the qty's for all suppliers Example for ManuPartNo - R10001 I would Like to return R10001 - 200.00 - 7(qty) and the supplier of the highest Price if possible.
Not sure how to google this, I can either return the highest/Lowest price easily and also return a sum of the qty for each part but am not sure about how to perform both queries at once.
Thanks for any help.
You can use SUM as a windowed function:
SELECT ProdName, ManuPartNo, Price, Qty, TotalQty, Supplier
FROM ( SELECT *,
ROW_NUMBER() OVER(PARTITION BY ManuPartNo ORDER BY Price ASC) AS RN,
SUM(Qty) OVER(PARTITION BY ManuPartNo) AS TotalQty,
FROM dbo.TableAllProds) AS t
WHERE RN = 1
ORDER BY ManuPartNo;
This seems to be what you want... uncomment the where clause if you only want that supplier.
declare #TableAllProds table (ProdName varchar(16), ManuPartNo varchar(16), Price decimal (5,2), Qty int, Supplier varchar(16))
insert into #TableAllProds
values
('Part1','R10001',100.00,2,'Supp1'),
('Part2','R10002',500.00,2,'Supp2'),
('Part3','R30023',50.00,1,'Supp3'),
('Part2again','R10002',100.00,5,'Supp4'),
('Part2Again','R10002',300.00,10,'Supp5'),
('Part1again','R10001',200.00,5,'Supp3')
;WITH CTE AS(
SELECT
ProdName,
ManuPartNo,
Price,
Supplier,
sum(Qty) over (partition by ManuPartNo) TotalOverAllSuppliers,
case when Price = max(price) over (partition by ManuPartNo) then Supplier end HighestPricedSupplier
FROM
#TableAllProds)
select
*
from cte
--where HighestPricedSupplier is not null
SELECT a.ManuPartNo, a.Price, a.QTY, b.Supplier
FROM (SELECT t1.ManuPartNo, MAX(t1.Price) AS Price, SUM(t1.Qty) AS QTY
FROM dbo.alltableprods t1
GROUP BY t1.ManuPartNo) a
JOIN (SELECT t2.ManuPartNo, t2.price, T2.Supplier,
ROW_NUMBER() OVER (PARTITION BY t2.ManuPartNo ORDER BY t2.price desc)
AS RN
FROM dbo.alltableprods t2
GROUP BY t2.ManuPartNo, t2.Price, t2.Supplier) b ON a.ManuPartNo =
b.ManuPartNo
WHERE b.RN = 1
Using this will return
R10001 200.00 7 Supp3
R10002 500.00 17 Supp2
R30023 50.00 1 Supp3
I have a question though. Is it possible for there to be more than one supplier that has the same part at the same price? If so then this will still work however it will just grab whatever applicable supplier it finds first.
You can query using row_number as below:
Select * from (
Select *, RowN = Row_Number() over(Partition by ManuPartNo order by Price desc), SmQty = Sum(Qty) over(Partition by ManuPartNo) from dbo.TableAllProds ) a
where a.RowN = 1

SQL Select Group By Min() - but select other

I want to select the ID of the Table Products with the lowest Price Grouped By Product.
ID Product Price
1 123 10
2 123 11
3 234 20
4 234 21
Which by logic would look like this:
SELECT
ID,
Min(Price)
FROM
Products
GROUP BY
Product
But I don't want to select the Price itself, just the ID.
Resulting in
1
3
EDIT: The DBMSes used are Firebird and Filemaker
You didn't specify your DBMS, so this is ANSI standard SQL:
select id
from (
select id,
row_number() over (partition by product order by price) as rn
from orders
) t
where rn = 1
order by id;
If your DBMS doesn't support window functions, you can do that with joining against a derived table:
select o.id
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price;
Note that this will return a slightly different result then the first solution: if the minimum price for a product occurs more then once, all those IDs will be returned. The first solution will only return one of them. If you don't want that, you need to group again in the outer query:
select min(o.id)
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price
group by o.product;
SELECT ID
FROM Products as A
where price = ( select Min(Price)
from Products as B
where B.Product = A.Product )
GROUP BY id
This will show the ID, which in this case is 3.

Delete where one column contains duplicates

consider the below:
ProductID Supplier
--------- --------
111 Microsoft
112 Microsoft
222 Apple Mac
222 Apple
223 Apple
In this example product 222 is repeated because the supplier is known as two names in the data supplied.
I have data like this for thousands of products. How can I delete the duplicate products or select individual results - something like a self join with SELECT TOP 1 or something like that?
Thanks!
I think you want to do the following:
select t.*
from (select t.*,
row_number() over (partition by product_id order by (select NULL)) as seqnum
from t
) t
where seqnum = 1
This selects an arbitrary row for each product.
To delete all rows but one, you can use the same idea:
with todelete (
(select t.*,
row_number() over (partition by product_id order by (select NULL)) as seqnum
from t
)
delete from to_delete where seqnum > 1
DELETE a
FROM tableName a
LEFT JOIN
(
SELECT Supplier, MIN(ProductID) min_ID
FROM tableName
GROUP BY Supplier
) b ON a.supplier = b.supplier AND
a.ProductID = b.min_ID
WHERE b.Supplier IS NULL
SQLFiddle Demo
or if you want to delete productID which has more than onbe product
WITH cte
AS
(
SELECT ProductID, Supplier,
ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY Supplier) rn
FROM tableName
)
DELETE FROM cte WHERE rn > 1
SQLFiddle Demo
;WITH Products_CTE AS
(
SELECT ProductID, Supplier,
ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY <some value>) as rn
FROM PRODUCTS
)
SELECT *
FROM Products_CTE
WHERE rn = 1
The some value is going to be the key that determines which version of Supplier you keep. If you want the first instance of the supplier, you could use the DateAdded column, if it exists.

T-Sql find duplicate row values

I want to write a stored procedure.
In that stored procedure, I want to find duplicate row values from a table, and calculate sum operation on these rows to the same table.
Let's say, I have a CustomerSales table;
ID SalesRepresentative Customer Quantity
1 Michael CustA 55
2 Michael CustA 10
and I need to turn table to...
ID SalesRepresentative Customer Quantity
1 Michael CustA 65
2 Michael CustA 0
When I find SalesRepresentative and Customer duplicates at the same time, I want to sum all Quantity values of these rows and assign to the first row of a table, and others will be '0'.
Could you help me.
To aggregate duplicates into one row:
SELECT min(ID) AS ID, SalesRepresentative, Customer
,sum(Quantity) AS Quantity
FROM CustomerSales
GROUP BY SalesRepresentative, Customer
ORDER BY min(ID)
Or, if you actually want those extra rows with 0 as Quantity in the result:
SELECT ID, SalesRepresentative, Customer
,CASE
WHEN (count(*) OVER (PARTITION BY SalesRepresentative,Customer)) = 1
THEN Quantity
WHEN (row_number() OVER (PARTITION BY SalesRepresentative,Customer
ORDER BY ID)) = 1
THEN sum(Quantity) OVER (PARTITION BY SalesRepresentative,Customer)
ELSE 0
END AS Quantity
FROM CustomerSales
ORDER BY ID
This makes heavy use of window functions.
Alternative version without window functions:
SELECT min(ID) AS ID, SalesRepresentative, Customer, sum(Quantity) AS Quantity
FROM CustomerSales
GROUP BY SalesRepresentative, Customer
UNION ALL
SELECT ID, SalesRepresentative, Customer, 0 AS Quantity
FROM CustomerSales c
GROUP BY SalesRepresentative, Customer
LEFT JOIN (
SELECT min(ID) AS ID
FROM CustomerSales
GROUP BY SalesRepresentative, Customer
) x ON (x.ID = c.ID)
WHERE x.ID IS NULL
ORDER BY ID

SQL Group By Question

I have a table which tracks views of products.
TrackId ProductId CreatedOn
1 1 01/01/2011
2 4 01/01/2011
3 4 01/01/2011
4 10 01/01/2011
What I want to do is return a dataset which doesn't have two ProductIds next to each other. I.E from the above data set I would want to return:
TrackId ProductId CreatedOn
1 1 01/01/2011
2 4 01/01/2011
4 10 01/01/2011
I can't use distinct as far as I am aware as this is row based?
Help appreciated.
Generate a row number sequence per ProductID, take the first
;WITH cte AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY TrackID) AS rn
FROM
MyProductTable
)
SELECT
TrackId ProductId CreatedOn
FROM
cte
WHERE
rn = 1
Edit:
If you want to use an aggregate, you need a separate subquery first to ensure consistent results. A straight MIN won't work.
This is based on my comment to the question
"not having productid in two adjacent rows. Adjacent is defined by next/previous Trackid"
SELECT
M.*
FROM
myProductTable M
JOIN
( --gets the lowest TrackID for a ProductID
SELECT ProductID, MIN(TrackID) AS MinTrackID
FROM myProductTable
GROUP BY ProductID
) M2 ON M.ProductID= M2.ProductID AND M.TrackID= M2.MinTrackID
select min(TrackId), ProductId, CreatedOn
from YourTable
group by ProductId, CreatedOn;
You can GroupBy on the TrackID and ProductID and do a Min of the CreatedOn if the date is not important.
SELECT TrackID ,ProductID ,MIN(CreatedOn)
FROM [table]
GROUP BY TrackID ,ProductID
If the date is the same you can group by all three
SELECT TrackID ,ProductID ,CreatedOn
FROM [table]
GROUP BY TrackID ,ProductID ,CreatedOn