Create a repost of latest and second latest value - sql

I have products audits table looks like this
id
product_id
column_updated
value
timestamp
1
product_1
name
Big Shoes.
"18 September 2022 6:42:50 PM GMT+05:30"
2.
product_1
name
Green Shoes
"18 September 2022 6:42:43 PM GMT+05:30"
3.
product_1
name
Big Green Shoes
"18 September 2022 6:43:43 PM GMT+05:30"
I want to show report of latest change happened on column
in form like below
product_id
column_updated
latest_value
previous_value
product_1
name
Green Shoes
Big Green Shoes
I have prepared a query to fetch last 2 record but not sure how I can merge them to form a view like this?
my query is
select product_id, column_updated, value
from audits
where product_id = 'product_1'
and column_updated = 'name'
order by timestamp desc
limit 2;
Please suggest any approach for this, Thanks in advance!

You need to use LEADa swindow function to hget the latest and previous value
WITH CTE as
(select product_id,
column_updated,
value as latest_value,
lead(value) over (
partition by product_id,column_updated order by timestamp desc
) as previous_value,
ROW_NUMBER() over (
partition by product_id,column_updated order by timestamp desc
) rn
from audits
where product_id = 'product_1'
and column_updated = 'name')
SELECT product_id, column_updated,latest_value,previous_value FROM CTE WHERE rn = 1
product_id
column_updated
latest_value
previous_value
product_1
name
Big Green Shoes
Big Shoes.
SELECT 1
fiddle

This problem can be solved by combination of window functions and CTE:
with data as (
select
product_id,
column_updated,
value,
lag(value) over (partition by product_id, column_updated order by updated_at asc) prev_value,
row_number() over (partition by product_id, column_updated order by updated_at desc) rn,
updated_at
from log
) select
product_id,
column_updated,
value,
prev_value,
updated_at
from data
where rn = 1;
online sql editor
where lag give us previous value and row_number give ability to filter only last change

You don't need both row_number and lag like the other answers. You can do it with just row_number. Give it a row number and then join back with the prior value having row number 2.
WITH rownumbered AS (
SELECT product_id, column_updated, value, updated_at
ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY updated_at DESC) rn,
FROM log
)
SELECT d.product_id, d,column_updated, d.value, p.value as prevous_value
FROM rownumbered d
JOIN rownumbered p ON d.product_id = p.product_id and p.rn = 2
WHERE d.rn = 1;
You may want to group by product_id and name -- if that is the case it looks like this:
WITH rownumbered AS (
SELECT product_id, column_updated, value, updated_at
ROW_NUMBER() OVER (PARTITION BY product_id, column_updated ORDER BY updated_at DESC) rn,
FROM log
)
SELECT d.product_id, d,column_updated, d.value, p.value as prevous_value
FROM rownumbered d
JOIN rownumbered p ON d.product_id = p.product_id
and d.column_updated = p.column_updated
and p.rn = 2
WHERE d.rn = 1;

can you use lag to get the previous value? https://www.postgresqltutorial.com/postgresql-window-function/postgresql-lag-function/
select product_id,
lag(column_updated) over (
partition by product_id order by timestamp desc
) as column_updated,
column_updated as latest_value
from ...

Related

Get last and first record using rank()

I need to get first and last record (ordered by Date column) from table for certain SSID. It is not a problem if there is more records with same max or min date. All I need is union all.
I am getting last record having max(date) with:
with c as (
select *, rnk = rank() over (partition by Date order by Date ASC)
from table
where SSID = '00921834800'
)
select top 1 Date, City, Title
from c
order by Date desc
How to I get first record (min(Date)) as well (same thing only with order by Date asc) with single select and without using ranking again?
I'm using MSSQL 2017.
; with c as (
select *,
rnk = rank() over (partition by Date order by Date ASC),
rnk2 = rank() over (partition by Date order by Date desc)
from table
where SSID= '00921834800'
)
select Date,
City,
Title
from c
where rnk = 1 or rnk2 = 1
order by Date desc
I would use the following query:
select * from (select top 1 with ties * from t where ssid = '00921834800' order by date) as a
union all
select * from (select top 1 with ties * from t where ssid = '00921834800' order by date desc) as b
One other solution is :
with
c as
(
select *,
rank() over (partition by Date order by Date ASC) AS RNK,
count() OVER (partition by Date) AS CNT
from table
where SSID= '00921834800')
select Date, City, Title
from c
WHERE RNK = 1
OR CNT = RNK
order by Date desc

Select the last exchange value for each currency. SQL

Let's consider we have table of currencies. The task is to derive the last update of a price for each currency (name).
My endeavor:
SELECT name, date, price
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY name
ORDER BY date DESC
) as RN
FROM currency
) X where RN = 1;
My solution virtually cover everything, except two dollar's fields:
| USD | 2006-03-04 | 8 | and | USD | 2007-03-04 | 8 |
Technically, I comprehend why does it happen. I define the RN = 1, which selects first row for each currency (considering that the dates for each currency are in descending order). In the problem, described above the last update occurs 2006-03-04, and generally its order is second.
However, I have no idea, how to formulate a request to choose the MIN of the dates in the last subgroup.
If you have an idea how to do that - I'ill be very thankful!
I think you want the rows where the currency changes. If that interpretation is correct, use lag():
select c.*
from (select c.*, lag(name) over (order by date) as prev_name
from currency c
) c
where prev_name is null or prev_name <> name;
Note that standard SQL has a way to simplify the where clause using a NULL safe comparison:
where prev_name is distinct from name
However, not all databases support this (or similar) syntax.
EDIT:
I think my above interpretation is incorrect. You want the second to last row because the price does not change. So:
select c.*
from (select c.*, lag(price) over (partition by name order by date) as prev_price
from currency c
) c
where prev_price is null or prev_price <> price;
Then to get one row per currency:
select c.*
from (select c.*,
row_number() over (partition by name order by date desc) as seqnum
from (select c.*, lag(price) over (partition by name order by date) as prev_price
from currency c
) c
where prev_price is null or prev_price <> price
) c
where seqnum = 1
EDIT II:
In Postgres, the last query is more simply written as:
select distinct on (name) c.*
from (select c.*, lag(price) over (partition by name order by date) as prev_price
from currency c
) c
where prev_price is null or prev_price <> price
order by name, date desc

Selecting City from Customer ID in SQL

Customer have ordered from different cities. Thus we have multiple cities against same customer_id. I want to display that city against customer id which has occurred maximum number of times , in case where customer has ordered same number of orders from multiple cities that city should be selected from where he has placed last order. I have tried something like
SELECT customer_id,delivery_city,COUNT(DISTINCT delivery_city)
FROM analytics.f_order
GROUP BY customer_id,delivery_city
HAVING COUNT(DISTINCT delivery_city) > 1
WITH cte as (
SELECT customer_id,
delivery_city,
COUNT(delivery_city) as city_count,
MAX(order_date) as last_order
FROM analytics.f_order
GROUP BY customer_id, delivery_city
), ranking as (
SELECT *, row_number() over (partition by customer_id
order by city_count DESC, last_order DESC) as rn
FROM cte
)
SELECT *
FROM ranking
WHERE rn = 1
select customer_id,
delivery_city,
amount
from
(
select t.*,
rank() over (partition by customer_id order by amount asc) as rank
from(
SELECT customer_id,
delivery_city,
COUNT(DISTINCT delivery_city) as amount
FROM analytics.f_order
GROUP BY customer_id,delivery_city
) t
)
where rank = 1

Sql query to fetch second latest entry from a table

I have a table with below mentioned columns. I want to fetch the previous status of customer. Once customer id can have multiple entries
Customer_id status start_date end_date Active
1 Member 01-JAN-18 04-FEB-18 N
1 Explorist 05-FEB-18 30-APR-18 N
1 Globalist 01-MAY-18 31-DEC-99 Y
Desired output
Customer _id Previous_status end_date
1 Explorist 30-APR-18
Please try below query using QUALIFY keyword and ROW_NUMBER():
SELECT a.* from table a
QUALIFY ROW_NUMBER OVER(PARTITION BY customer_id order by start_date desc) = 2
Below query should work.
SELECT * from (
SELECT a.*,
ROW_NUMBER() over (partition by customer_id order by start_date desc) rn
from table a )
where rn =2
You can use below query and I guess that is very simple and that worked for me,
select * from customer order by end_date desc limit 1,1
Consider this question: Select Nth Row From A Table In Oracle
In your case, that would be:
select * from (select a.*, rownum rnum from (select * from <your table name>
order by <start_date or end_date> desc) a where rownum <= 2) where rnum >= 2;
If you are using Oracle DataBase then try below query using ROW_NUMBER() function:Let's consider the table name is customer
SELECT TEMP.CUSTOMER_ID
,TEMP.STATUS
,TEMP.START_DATE
,TEMP.END_DATE
,TEMP.ACTIVE
FROM(
SELECT ROW_NUMBER() OVER (PARTITION BY CUSTOMER_ID ORDER BY CUSTOMER_ID ASC,START_DATE DESC) AS "ROW_NUM"
,CUSTOMER_ID
,STATUS
,START_DATE
,END_DATE
,ACTIVE
FROM CUSTOMER) TEMP
WHERE TEMP."ROW_NUM" = 2;

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.