SQL - Want to provide unique rank for same set of values? - sql

I need help with determining the last changed price by dates for which I am trying to generate a Unique-Identifier column, so I can apply partition to this new column and derive additional logic in my programming.
Can you please help me to derive the Unique-Identifier column?
Date | OrderID | Price | Seq_no |Unique-Indentifier
1/24/2015 | 568956 | 300 | 1 | 1
1/20/2015 | 568956 | 350 | 1 | 2
1/20/2015 | 568956 | 375 | 2 | 3
1/20/2015 | 568956 | 400 | 3 | 4
1/17/2015 | 568956 | 400 | 1 | 4
1/14/2015 | 568956 | 500 | 1 | 5
1/11/2015 | 568956 | 500 | 1 | 5
1/9/2015 | 568956 | 400 | 1 | 6
1/7/2015 | 568956 | 400 | 1 | 6
1/24/2015 | 568957 | 600 | 1 | 7
1/20/2015 | 568957 | 600 | 1 | 7
1/17/2015 | 568957 | 700 | 1 | 8
1/14/2015 | 568957 | 800 | 1 | 9
1/11/2015 | 568957 | 800 | 1 | 9
1/9/2015 | 568957 | 700 | 1 | 10
1/7/2015 | 568957 | 700 | 1 | 10
I can’t apply partition on Price column. Reason: For OrderID '568956' the same price 400 was set in two different dates. I wanted to isolate these two sets. If I simply use partition on Price Column then I will get all four rows as one set. So I need to put some identifier to differentiate these rows and apply partition on my new column 'UniqueIdentifier'.
Set 1:
1/20/2015 568956 400 4
1/17/2015 568956 400 4
Set 2:
1/9/2015 568956 400 6
1/7/2015 568956 400 6
If I apply partition I get the result as one set - Which I am not expecting.
Set 1:
1/20/2015 568956 400 4
1/17/2015 568956 400 4
1/9/2015 568956 400 4
1/7/2015 568956 400 4

In your select statement do something like this:
SELECT
DISTINCT
ROW_NUMBER() OVER(PARTITION BY Date,OrderID,Price ORDER BY Date DESC) AS RowNum
,Date
,OrderID
,Price
You may have to mess around with the PARTITION BY section depending on how your select statement is working but when I used this it returns a unique row number for each value.
I'm not sure if you will be able to ORDER BY that Date value accurately so you may have to convert it into a DATETIME

You need to identify the groups, and then assign the sequential number. One method is a difference of row numbers. I think this is the logic:
select t.*,
dense_rank() over (partition by orderid order by grp, price) as newcol
from (select t.*,
(row_number() over partition by orderid order by date, seq_no) -
row_number() over partition by orderid, price order by date, seq_no)
) as grp
from t
) t

Related

How to SELECT records for the latest date in MSSQL

From the below table i want to write a select statement where i can select the price of the items for the
latest date.
Item | Price | Date
------|----------|--------
1001 | 10 | 26-5-2019
1001 | 11 | 15-02-2020
1001 | 9 | 28-08-2020
1002 | 5 | 1/7/2019
1002 | 3 | 8/11/2019
1002 | 4 | 5/5/2020
1003 | 6 | 26-05-2019
1003 | 7 | 1/2/2020
1003 | 5 | 15-09-2020
Result should be as below:
Item | Price | Date
------|----------|--------
1001 | 9 | 28-08-2020
1002 | 4 | 5/5/2020
1003 | 5 | 15-09-2020
Despite the fact that the table is unreadable and you haven't posted anything about what you have tried so far, I will try to help you...
You can get the price via Window Functions - in this case row_number. You should try something like the following:
SELECT x.*
FROM (SELECT Item, Price, [Date], ROW_NUMBER() OVER (PARTITION BY Item ORDER BY [Date] DESC) AS rn) x
WHERE x.rn = 1

Subtract constant across database tables

I need to subtract a value, found in a different table, from values across different rows.
For example, the tables I have are:
ProductID | Warehouse | Locator | qtyOnHand
-------------------------------------------
100 | A | 123 | 12
100 | A | 124 | 12
100 | A | 124 | 8
101 | A | 126 | 6
101 | B | 127 | 12
ProductID | Sold
----------------
100 | 26
101 | 16
Result:
ProductID | Warehouse | Locator | qtyOnHand | available
-------------------------------------------------------
100 | A | 123 | 12 | 0
100 | A | 123 | 12 | 0
100 | A | 124 | 8 | 6
101 | A | 126 | 6 | 0
101 | B | 127 | 12 | 12
The value should only be subtracted from those in warehouse A.
Im using postgresql. Any help is much appreciated!
If I understand correctly, you want to compare the overall stock to the cumulative amounts in the first table. The rows in the first table appear to be ordered from largest to smallest. Note: This is an interpretation and not 100% consistent with the data in the question.
Use JOIN to bring the data together and then cumulative sums and arithmetic:
select t1.*,
(case when running_qoh < t2.sold then 0
when running_qoh - qtyOnHand < t2.sold then (running_qoh - t2.sold)
else qtyOnHand
end) as available
from (select t1.*,
sum(qtyOnHand) over (partition by productID order by qtyOnHand desc) as running_qoh
from table1 t1
) t1 join
table2 t2
using (ProductID)

count total items, sold items (in another table reference by id) and grouped by serial number

I have a table of items in the shop, an item may have different entries with same serial number (sn) (but different ids) if the same item was bought again later on with different price (price here is how much did a single item cost the shop)
id | sn | amount | price
----+------+--------+-------
1 | AP01 | 100 | 7
2 | AP01 | 50 | 8
3 | X2P0 | 200 | 12
4 | X2P0 | 30 | 18
5 | STT0 | 20 | 20
6 | PLX1 | 200 | 10
and a table of transactions
id | item_id | price
----+---------+-------
1 | 1 | 10
2 | 1 | 9
3 | 1 | 10
4 | 2 | 11
5 | 3 | 15
6 | 3 | 15
7 | 3 | 15
8 | 4 | 18
9 | 5 | 22
10 | 5 | 22
11 | 5 | 22
12 | 5 | 22
and transaction.item_id references items(id)
I want to group items by serial number (sn), get their sum(amount) and avg(price), and join it with a sold column that counts number of transactions with referenced id
I did the first with
select i.sn, sum(i.amount), avg(i.price) from items i group by i.sn;
sn | sum | avg
------+-----+---------------------
STT0 | 20 | 20.0000000000000000
PLX1 | 200 | 10.0000000000000000
AP01 | 150 | 7.5000000000000000
X2P0 | 230 | 15.0000000000000000
Then when I tried to join it with transactions I got strange results
select i.sn, sum(i.amount), avg(i.price) avg_cost, count(t.item_id) sold, sum(t.price) profit from items i left join transactions t on (i.id=t.item_id) group by i.sn;
sn | sum | avg_cost | sold | profit
------+-----+---------------------+------+--------
STT0 | 80 | 20.0000000000000000 | 4 | 88
PLX1 | 200 | 10.0000000000000000 | 0 | (null)
AP01 | 350 | 7.2500000000000000 | 4 | 40
X2P0 | 630 | 13.5000000000000000 | 4 | 63
As you can see, only the sold and profit columns show correct results, the sum and avg show different results than the expected
I can't separate the statements because I am not sure how can I add the count to the sn group which has the item_id as its id?
select
j.sn,
j.sum,
j.avg,
count(item_id)
from (
select
i.sn,
sum(i.amount),
avg(i.price)
from items i
group by i.sn
) j
left join transactions t
on (j.id???=t.item_id);
There are multiple matches in both tables, so the join multiplies the rows (and eventually produces wron results). I would recommend pre-joining, then aggregating:
select
sn,
sum(amount) total_amount,
avg(price) avg_price,
sum(no_transactions) no_transactions
from (
select
i.*,
(
select count(*)
from transactions t
where t.item_id = i.id
) no_transactions
from items i
) t
group by sn

How to select the latest date for each group by number?

I've been stuck on this question for a while, and I was wondering if the community would be able to direct me in the right direction?
I have some tag IDs that needs to be grouped, with exceptions (column: deleted) that need to be retained in the results. After which, for each grouped tag ID, I need to select the one with the latest date. How can I do this? An example below:
ID | TAG_ID | DATE | DELETED
1 | 300 | 05/01/20 | null
2 | 300 | 03/01/20 | 04/01/20
3 | 400 | 06/01/20 | null
4 | 400 | 05/01/20 | null
5 | 400 | 04/01/20 | null
6 | 500 | 03/01/20 | null
7 | 500 | 02/01/20 | null
I am trying to reach this outcome:
ID | TAG_ID | DATE | DELETED
1 | 300 | 05/01/20 | null
2 | 300 | 03/01/20 | 04/01/20
3 | 400 | 06/01/20 | null
6 | 500 | 03/01/20 | null
So, firstly if there is a date in the "DELETED" column, I would like the row to be present. Secondly, for each unique tag ID, I would like the row with the latest "DATE" to be present.
Hopefully this question is clear. Would appreciate your feedback and help! A big thanks in advance.
Your results seem to be something like this:
select t.*
from (select t.*,
row_number() over (partition by tag_id, deleted order by date desc) as seqnum
from t
) t
where seqnum = 1 or deleted is not null;
This takes one row where deleted is null -- the most recent row. It also keeps each row where deleted is not null.
You need 2 conditions combined with OR in the WHERE clause:
the 1st is deleted is not null, or
the 2nd that there isn't any other row with the same tag_id and date later than the current row's date, meaning that the current row's date is the latest:
select t.* from tablename t
where t.deleted is not null
or not exists (
select 1 from tablename
where tag_id = t.tag_id and date > t.date
)
See the demo.
Results:
| id | tag_id | date | deleted |
| --- | ------ | ---------- | -------- |
| 1 | 300 | 2020-05-01 | |
| 2 | 300 | 2020-03-01 | 04/01/20 |
| 3 | 400 | 2020-06-01 | |
| 6 | 500 | 2020-03-01 | |

Select dynamic couples of lines in SQL (PostgreSQL)

My objective is to make dynamic group of lines (of product by TYPE & COLOR in fact)
I don't know if it's possible just with one select query.
But : I want to create group of lines (A PRODUCT is a TYPE and a COLOR) as per the number_per_group column and I want to do this grouping depending on the date order (Order By DATE)
A single product with a NB_PER_GROUP number 2 is exclude from the final result.
Table :
-----------------------------------------------
NUM | TYPE | COLOR | NB_PER_GROUP | DATE
-----------------------------------------------
0 | 1 | 1 | 2 | ...
1 | 1 | 1 | 2 |
2 | 1 | 2 | 2 |
3 | 1 | 2 | 2 |
4 | 1 | 1 | 2 |
5 | 1 | 1 | 2 |
6 | 4 | 1 | 3 |
7 | 1 | 1 | 2 |
8 | 4 | 1 | 3 |
9 | 4 | 1 | 3 |
10 | 5 | 1 | 2 |
Results :
------------------------
GROUP_NUMBER | NUM |
------------------------
0 | 0 |
0 | 1 |
~~~~~~~~~~~~~~~~~~~~~~~~
1 | 2 |
1 | 3 |
~~~~~~~~~~~~~~~~~~~~~~~~
2 | 4 |
2 | 5 |
~~~~~~~~~~~~~~~~~~~~~~~~
3 | 6 |
3 | 8 |
3 | 9 |
If you have another way to solve this problem, I will accept it.
What about something like this?
select max(gn.group_number) group_number, ip.num
from products ip
join (
select date, type, color, row_number() over (order by date) - 1 group_number
from (
select op.num, op.type, op.color, op.nb_per_group, op.date, (row_number() over (partition by op.type, op.color order by op.date) - 1) % nb_per_group group_order
from products op
) sq
where sq.group_order = 0
) gn
on ip.type = gn.type
and ip.color = gn.color
and ip.date >= gn.date
group by ip.num
order by group_number, ip.num
This may only work if your nb_per_group values are the same for each combination of type and color. It may also require unique dates, but that could probably be worked around if required.
The innermost subquery partitions the rows by type and color, orders them by date, then calculates the row numbers modulo nb_per_group; this forms a 0-based count for the group that resets to 0 each time nb_per_group is exceeded.
The next-level subquery finds all of the 0 values we mapped in the lower subquery and assigns group numbers to them.
Finally, the outermost query ties each row in the products table to a group number, calculated as the highest group number that split off before this product's date.