Suppose I have a table
order id
item
1
A
1
B
5
A
3
B
I need to write a select query to get following output:
order id
item
order
1
A
1
5
A
2
1
B
1
3
B
2
Is there a way to achieve this?
You can use the ROW_NUMBER() rank function and write query like this:
create table orders
(
order_id int,
item nvarchar(max)
)
insert into orders values(1,'A');
insert into orders values(1,'B');
insert into orders values(5,'A');
insert into orders values(3,'B');
SELECT order_id, item, ROW_NUMBER() OVER ( PARTITION BY item ORDER BY item ASC, order_id ASC) as [order]
FROM ORDERS
order by item asc, order_id asc
Check the ranking functions: https://www.sqlshack.com/overview-of-sql-rank-functions/
Related
I'm working with a table in the following format
Order_ID
Product_Name
1
A
1
B
2
B
2
C
3
A
3
C
3
B
I need to query the data to output like this
Order_ID
Product_1
Product_2
Product_3
Etc.
1
A
B
2
B
C
3
A
C
B
Basically, I need to show all, or at least several, product_names for a given order_id as separate columns. I found a few answers that suggested using self joins to do this, but that doesn't seem practical when I need to show more than two products.
This is in BigQuery.
This general operation is called pivoting.
Depending on your database and its SQL dialect, there will be different optimal approaches for this. Snowflake and BigQuery, for example, have a PIVOT table operation that does specifically this.
On BigQuery:
with numbered as (
select
*,
'Product_' || row_number() over (
partition by OrderID order by ProductName asc
) as n
from my_table
)
select
*
from numbered pivot(
max(ProductName)
for n in ('Product_1', 'Product_2', 'Product_3')
)
group by Order_ID
If your dialect doesn't support this, but does support window functions like row_number, then you can manually do something like this to support a pre-defined number of products:
with numbered as (
select *, row_number() over (partition by OrderID order by ProductName asc) as n
from my_table
)
select
OrderID,
max(case when n = 1 then ProductName end) as Product_1,
max(case when n = 2 then ProductName end) as Product_2,
...
from numbered
group by Order_ID
.. I need to show all, or at least several, product_names for a given order_id as separate columns
Consider below
select * from (
select *, row_number() over(partition by Order_ID) index
from your_table
)
pivot (any_value(Product_Name) Product for index in (1,2,3,4,5))
if applied to sample data in your question - output is
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
Say I have a table which has a column named ItemCode, it has fixed format xxx-xxxx where x is [0-9], for example a possible value of ItemCode is 097-1234
Now I would like to select the largest ItemCode which starts with 987 AND the last ItemCode starts with 123, so I am trying to do something like (This is wrong)
SELECT TOP 1 ItemCode From Table
WHERE ItemCode like '987%' OR ItemCode like '123%'
ORDER BY 1 DESC
So how can I write a SQL which can select the last ItemCode of each criteria? Is there any general method which can extend to select top N rows on M such criterias?
(assuming there exists data fulfills the criteria, here 2 rows should be returned: largest ItemCode starts with 987 and largest ItemCode starts with 123)
Another option without UNION, you could use TOP 1 WITH TIES and ROW_NUMBER() OVER() like this
SELECT TOP 1 WITH TIES *
From YourTable
WHERE ItemCode like '987%' OR ItemCode like '123%'
ORDER BY ROW_NUMBER() OVER(PARTITION BY LEFT(ItemCode,3) ORDER BY Itemcode DESC)
You can use ROW_NUMBER() in a CTE for a more generalised form:
;With Ordered as (
select
*,
ROW_NUMBER() OVER (
PARTITION BY SUBSTRING(ItemCode,1,3)
ORDER BY ItemCode desc) as rn
from
Table
where
ItemCode like '987%' or
ItemCode like '123%'
)
select *
from Ordered
where rn = 1
As I alluded to in the comments, if possible I'd change the structure so that the ItemCode elements are separately stored, which would make for a simpler internal query form that could also more easily benefit from indexes. E.g. something like:
;With Ordered as (
select
*,
ROW_NUMBER() OVER (
PARTITION BY ItemCode_Prefix
ORDER BY ItemCode_Suffix desc) as rn
from
Table
where
ItemCode_Prefix in (987,123)
)
select *
from Ordered
where rn = 1
Thanks to #jarlh, I used UNION to achieve what I need.
If anyone has a more general method which may easier to be extended to more criteria, please post an answer and I will accept it. Cheers.
SELECT * FROM(
SELECT TOP 1 ItemCode FROM Table
WHERE ItemCode LIKE '987%'
ORDER BY 1 DESC
) AS A
UNION
SELECT * FROM(
SELECT TOP 1 ItemCode FROM Table
WHERE ItemCode LIKE '123%'
ORDER BY 1 DESC
) AS B
Complete solution based on ROW_NUMBER() function:
use tempdb;
go
-- Test data
create table #test_data
(ItemCode char(8) not null);
insert into #test_data
values
('097-1234'),
('097-1243'),
('097-7890'),
('012-1234'),
('912-1234'),
('123-1234'), -- second max for '987,123'
('123-1234'),
('123-0001'),
('123-0932'),
('987-1234'),
('987-5643'),
('987-7890'), -- first max for '987,123'
('000-7890');
go
-- Test data
-- Code
create proc dbo.top_n_from_m
#criterias varchar(max)
as
set nocount on;
declare #crs table
(id int not null identity (1, 1) primary key,
string char(3) not null);
insert into #crs (string)
select value
from string_split(#criterias, ',')
select t.ItemCode
from
(select t.ItemCode,
c.id,
row_id = row_number() over (partition by c.id order by t.ItemCode desc)
from #test_data as t
join #crs as c on t.ItemCode like c.string + '-%') as t
where t.row_id = 1
order by t.id
go
-- Code
-- Test
execute dbo.top_n_from_m #criterias = '987,123'
select ItemCode
from #test_data
order by ItemCode
-- Test
-- Clear
drop table #test_data;
drop proc dbo.top_n_from_m;
-- Clear
How can I display maximum OrderId for a CustomerId with many columns?
I have a table with following columns:
CustomerId, OrderId, Status, OrderType, CustomerType
A customer with Same customer id could have many order ids(1,2,3..) I want to be able to display the max Order id with the rest of the customers in a sql view. how can I achieve this?
Sample Data:
CustomerId OrderId OrderType
145042 1 A
110204 1 C
145042 2 D
162438 1 B
110204 2 B
103603 1 C
115559 1 D
115559 2 A
110204 3 A
I'd use a common table expression and ROW_NUMBER:
;With Ordered as (
select *,
ROW_NUMBER() OVER (PARTITION BY CustomerID
ORDER BY OrderId desc) as rn
from [Unnamed table from the question]
)
select * from Ordered where rn = 1
select * from table_name
where orderid in
(select max(orderid) from table_name group by customerid)
One way to do this is with not exists:
select t.*
from table t
where not exists (select 1
from table t2
where t2.CustomerId = t.CustomerId and
t2.OrderId > t.OrderId
);
This is saying: "get me all rows from t where there is no higher order id for the customer."
In PostgreSQL:
I have a Table that has 3 columns:
CustomerNum, OrderNum, OrderDate.
There may(or may not) be many orders for each customer per date range. What I am needing is the last OrderNum for each Customer that lies in the date range that is supplied.
What I have been doing is getting a ResultSet of the customers and querying each one separately, but this is taking too much time.
Is there any way of using a sub-select to select out the customers, then get the last OrderNum for each Customer?
On postgres you can also use the non-standard DISTINCT ON clause:
SELECT DISTINCT ON (CustomerNum) CustomerNum, OrderNum, OrderDate
FROM Orders
WHERE OrderDate BETWEEN 'yesterday' AND 'today'
ORDER BY CustomerNum, OrderDate DESC;
See http://www.postgresql.org/docs/current/static/sql-select.html#SQL-DISTINCT
select customernum, max(ordernum)
from table
where orderdate between '...' and '...'
group by customernum
that's all.
SELECT t1.CustomerNum, t1.OrderNum As LastOrderNum, t1.LastOrderDate
FROM table1 As t1
WHERE t1.OrderDate = (SELECT MAX(t2.OrderDate)
FROM table1 t2
WHERE t1.CustomerNum = t2.CustomerNum
AND t2.OrderDate BETWEEN date1 AND date2)
AND t1.OrderDate BETWEEN date1 AND date2
Not sure about your Customer table's structure or relationships, but this should work:
SELECT Customer.Num, (
SELECT OrderNum FROM Orders WHERE CustomerNum = Customer.Num AND OrderDate BETWEEN :start AND :end ORDER BY OrderNum DESC LIMIT 1
) AS LastOrderNum
FROM Customer
If by last order number you mean the largest order number then you can just use your select as the predicate for customer num, group the results and select the maximum:
SELECT CustomerNum, MAX(OrderNum) AS LastOrderNum
FROM Orders
WHERE
CustomerNum IN (SELECT CustomerNum FROM ...)
AND
OrderDate BETWEEN :first_date AND :last_date
GROUP BY CustomerNum
If the last order number isn't necessarily the largest order number then you'll need to either find the largest order date for each customer and join it together with the rest of the orders to find the corresponding number(s):
SELECT O.CustomerNum, O.OrderNum AS LastOrderNum
FROM
(SELECT CustomerNum, MAX(OrderDate) AS OrderDate
FROM Orders
WHERE
OrderDate BETWEEN :first_date AND :last_date
AND
CustomerNum IN (SELECT CustomerNum FROM ...)
GROUP BY CustomerNum
) AS CustLatest
INNER JOIN
Orders AS O USING (CustomerNum, OrderDate);
-- generate some data
DROP TABLE tmp.orders;
CREATE TABLE tmp.orders
( id INTEGER NOT NULL
, odate DATE NOT NULL
, payload VARCHAR
)
;
ALTER TABLE tmp.orders ADD PRIMARY KEY (id,odate);
INSERT INTO tmp.orders(id,odate,payload) VALUES
(1, '2011-10-04' , 'one' )
, (1, '2011-10-24' , 'two' )
, (1, '2011-10-25' , 'three' )
, (1, '2011-10-26' , 'four' )
, (2, '2011-10-23' , 'five' )
, (2, '2011-10-24' , 'six' )
;
-- CTE to the rescue ...
WITH sel AS (
SELECT * FROM tmp.orders
WHERE odate BETWEEN '2011-10-23' AND '2011-10-24'
)
SELECT * FROM sel s0
WHERE NOT EXISTS (
SELECT * FROM sel sx
WHERE sx.id = s0.id
AND sx.odate > s0.odate
)
;
result:
DROP TABLE
CREATE TABLE
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "orders_pkey" for table "orders"
ALTER TABLE
INSERT 0 6
id | odate | payload
----+------------+---------
1 | 2011-10-24 | two
2 | 2011-10-24 | six
(2 rows)