I have a table where each rows contains product id (A), price (P) and modification date (D) in YYYYMMDD format.
Here is the table :
WITH temp_table AS (
select 744583 as a, 9.21 as p, 20210706 as d from sysibm.sysdummy1
union all
select 744583 as a, 9.21 as p, 20210630 as d from sysibm.sysdummy1
union all
select 744583 as a, 9.21 as p, 20210628 as d from sysibm.sysdummy1
union all
select 744583 as a, 9.04 as p, 20210604 as d from sysibm.sysdummy1
union all
select 744583 as a, 9.04 as p, 20210201 as d from sysibm.sysdummy1
union all
select 744583 as a, 9.21 as p, 20200407 as d from sysibm.sysdummy1
)
select *
from temp_table
what i have
What i would like to have is when the price changed for the last time. In this example, the third line :
enter image description here
How would you do that ?
Thanks,
One method uses lag() and then ordering:
select t.*
from (select t.*,
lag(p) over (order by d desc) as prev_p
from temp_table t
) t
where prev_p is null or prev_p <> p
order by d desc
fetch first 1 row only;
If you wanted to do this for multiple as at the same time, then there are different approaches. An interesting one uses a difference of row numbers:
select a, p, min(date)
from (select t.*,
row_number() over (partition by a order by date desc) as seqnum,
row_number() over (partition by a, p order by date desc) as seqnum_2
from temp_table t
) t
where seqnum = seqnum_2
group by a, p;
You can investigate why this works. The two row numbers are the same only for the last price for each a.
Related
I need to select the newest row from two tables, two tables have the same schema
Table A and Table B is the same schema, like this:
Table A :
user_id, time_stamp, order_id
1,20190101,100
2,20190103,201
3,20190102,300
5,20180209,99
Table B:
user_id, time_stamp, order_id
1,20190102,101
2,20190101,200
3,20190103,305
4,20190303,900
I want the output is A union B, then select the newer row of a user, order by time_stamp:
output should be:
1,20190102,101
2,20190103,201
3,20190103,305
4,20190303,900
5,20180209,99
How to write this SQL?
You can write as following sample query demo
with unionedTable as (
select * from tableA
union
select * from tableB)
,newerUsersTable as (
select distinct on (u.user_id)u.*
from unionedTable u
order by u.user_id, u.time_stamp desc
)select * from newerUsersTable
The main idea is using FULL OUTER JOIN among two tables, and then using UNION [ALL] for returning data set. So, consider the following SELECT statement with WITH clause :
with a( user_id, time_stamp, order_id ) as
(
select 1,20190101,100 union all
select 2,20190103,201 union all
select 3,20190102,300 union all
select 5,20180209,99
), b( user_id, time_stamp, order_id ) as
(
select 1,20190102,101 union all
select 2,20190101,200 union all
select 3,20190103,305 union all
select 4,20190303,900
), c as
(
select a.user_id as user_id_a, a.time_stamp as time_stamp_a, a.order_id as order_id_a,
b.user_id as user_id_b, b.time_stamp as time_stamp_b, b.order_id as order_id_b
from a full outer join b
on a.user_id = b.user_id
), d as
(
select user_id_a, time_stamp_a, order_id_a
from c
where coalesce(time_stamp_b,time_stamp_a) <= time_stamp_a
union all
select user_id_b, time_stamp_b, order_id_b
from c
where time_stamp_b >= coalesce(time_stamp_a,time_stamp_b)
)
select user_id_a as user_id, time_stamp_a as time_stamp, order_id_a as order_id
from d
order by user_id_a;
user_id time_stamp order_id
1 20190102 101
2 20190103 201
3 20190103 305
4 20190303 900
5 20180209 99
Demo
Use Group by(user_id) to show all user_id
Use max(time_stamp) get the newer row of user
SELECT aa.* from (select * from a union SELECT * from b ) aa
JOIN
(select user_id,max(time_stamp) as new_time
from (select * from a union SELECT * from b ) u
group by u.user_id) bb
on bb.new_time=aa.time_stamp and bb.user_id=aa.user_id
order by aa.user_id;
SQL Fiddle
I would simply do:
select user_id, time_stamp, order_id
from (select ab.*,
row_number() over (partition by user_id order by time_stamp desc) as seqnum
from (select a.* from a union all
select b.* from b
) ab
) ab
where seqnum = 1;
I'm trying to select the last MAX(DateTime) status from the table "Zee" but if the DateTime is the same it returns two lines, and I would like to get only the last one (maybe last inserted?).
here is the query:
SELECT Z."ID" AS ID,Z."A" AS A,Z."B" AS B,Z."C" AS C,Z."D" AS D
FROM ZEE Z
INNER JOIN
(SELECT ID, A, B, MAX(C) AS C
FROM ZEE
GROUP BY A, B) groupedtt
ON Z.A = groupedtt.A
AND Z.B = groupedtt.B
AND Z.C = groupedtt.C
WHERE (
Z.B = 103
OR Z.B = 104
);
and the result:
Thanks,
Regards.
I usually use rank() for such things:
select Z."ID" AS ID,Z."A" AS A,Z."B" AS B,Z."C" AS C,Z."D" AS D
from (select Z.*, rank()over(partition by A,B order by C desc, rownum) r from ZEE Z
)Z where Z.r=1
Use the ROW_NUMBER() analytic function (you will also eliminate the self-join):
SELECT ID, A, B, C, D
FROM (
SELECT ID,
A,
B,
C,
D,
ROW_NUMBER() OVER ( PARTITION BY A, B ORDER BY C DESC ) As rn
FROM ZEE
)
WHERE rn = 1;
I have a table that has millions of records.
So I might have these columns
a, b, c, d
I need to select all the distinct records based on columns a and b.
But I need to select columns a, b, c and d not just a and b.
Can I do this?
edit
Data might be
1,1,frog,green
1,1,frog,brown
2,1,cat,black
2,4,dog,white
so i need;
1,1,frog,green
2,1,cat,black
2,4,dog,white
SQL Server supports Common Table Expression and Window Function. The query below uses ROW_NUMBER() which ranks the record according to group. It sorts by c ASC, d ASC (just play with it).
WITH records
AS
(
SELECT a, b, c, d,
ROW_NUMBER() OVER(PARTITION BY a, b ORDER BY c, d) rn
FROM TableName
)
SELECT a, b, c, d
FROM records
WHERE rn = 1
SQLFiddle Demo
TSQL Ranking Functions
partition by is your man
SELECT a, b, c, d FROM (
SELECT a, b, c, d, ROW_NUMBER() OVER (PARTITION BY a, b ORDER BY a, b) rn
FROM table
) sq
where rn = 1
Please try:
select *
From(
select
row_number() over (partition by a, b order by a, b) RNum,
*
from
YourTable
)x
where RNum=1
Sample
select * From(
select row_number() over (partition by a, b order by a, b) RNum, *
from(
select 1 a, 1 b, 'frog' c, 'green' d union all
select 1 a, 1 b, 'frog' c, 'brown' d union all
select 2 a, 1 b, 'cat' c, 'black' d union all
select 2 a, 4 b, 'dog' c, 'white' d)x
)y
where RNum=1
I have a query like:
SELECT
R.*
FROM
(SELECT A, B,
(SELECT smth from another table) as C,
ROW_NUMBER() OVER (ORDER BY C DESC) AS RowNumber
FROM SomeTable) R
WHERE
RowNumber BETWEEN 10 AND 20
This gives me an error on ORDER BY C DESC.
I understand why this error is caused, so I've thought of adding another SELECT with ORDER BY and only than selecting rows from 10 to 20. But I don't think it's good to have 3 nested SELECT commands.
How else is it possible to select these rows?
A column cannot refer to an alias on same level, you have to table-derive it first, or use CTE.
SELECT
R.* , ROW_NUMBER() OVER (ORDER BY C DESC) AS RowNumber
FROM
(SELECT A, B, (SELECT smth from another table) as C
FROM SomeTable) R
-- WHERE
-- but you still cannot do this
-- RowNumber BETWEEN 10 AND 20
Need to do this:
select S.*
from
(
SELECT
R.* , ROW_NUMBER() OVER (ORDER BY C DESC) AS RowNumber
FROM
(SELECT A, B,
(SELECT smth from another table) as C
FROM SomeTable) R
) as s
where s.RowNumber between 10 and 20
To avoid deep nesting and to make it at least look pleasant, use CTE:
with R as
(
SELECT A, B, (SELECT smth from another table) as C
FROM SomeTable
)
,S AS
(
SELECT R.*, ROW_NUMBER() OVER (ORDER BY C DESC) AS RowNumber
FROM R
)
SELECT S.*
FROM S
WHERE S.RowNumber BETWEEN 1 AND 20
You cannot use aliased columns in the same SELECT, but you can wrap it into another select to make it work:
SELECT R.*
FROM (SELECT ABC.A, ABC.B, ABC.C, ROW_NUMBER() OVER (ORDER BY C DESC) AS RowNumber
FROM (SELECT A, B, (SELECT smth from another table) as C FROM SomeTable) ABC
) R
WHERE R.RowNumber BETWEEN 10 AND 20
Assume we have the following table:
Id A B
1 10 ABC
2 10 ABC
3 10 FFF
4 20 HHH
As result of a "group by A" expression I want to have the value of the B-Column that occurs most often:
select A, mostoften(B) from table group by A;
A mostoften(B)
10 ABC
20 HHH
How do I achieve this in Oracle 10g?
Remark: in the case of a tie (when there are more than one value that occurs most often) it does not matter which value is selected.
select A, B
from (
select A, B, ROW_NUMBER() OVER (PARTITION BY A ORDER BY C_B DESC) as rn
from (
select A, COUNT (B) as C_B, B
from table
group by A, B
) count_table
) order_table
where rn = 1;
You want the Bs with the MAX of COUNT group by A, B.
Old school solution, it took me some time and some cursing :)
select a,b
from ta ta1
group by a,b
having count(*) = (select max(count(*))
from ta ta2
where ta1.a = ta2.a
group by b)
This problem can be clarified by creating a view for the count in each A & B group:
CREATE VIEW MyTableCounts AS
SELECT A, B, COUNT(*) C
FROM MyTable
GROUP BY A, B;
Now we can do a query that finds the row c1 where the count is greatest. That is, no other row that has the same A has a greater count. Therefore if we try to find a row c2 with a greater count, no match is found.
SELECT c1.A, c1.B
FROM MyTableCounts c1
LEFT OUTER JOIN MyTableCounts c2
ON (c1.A = c2.A AND (c1.C < c2.C OR (c1.C = c2.C AND c1.B < c2.B)))
WHERE c2.A IS NULL
ORDER BY c1.A;
To resolve tied counts (c1.C = c2.C), we use the value of B which we know is unique within a given group of A.
try this (works on SQL Server 2005):
declare #yourtable table (rowid int, a int,b char(3))
insert into #yourtable values (1,10,'ABC')
insert into #yourtable values (2,10,'ABC')
insert into #yourtable values (3,10,'FFF')
insert into #yourtable values (4,20,'HHH')
;WITH YourTableCTE AS
(
SELECT
*, ROW_NUMBER() OVER(partition by A ORDER BY A ASC,CountOfB DESC) AS RowRank
FROM (SELECT
A, B, COUNT(B) AS CountOfB
FROM #yourtable
GROUP BY A,B
) dt
)
SELECT
A,B
FROM YourTableCTE
WHERE RowRank=1
EDIT without CTE...
SELECT
A,B
FROM (SELECT
*, ROW_NUMBER() OVER(partition by A ORDER BY A ASC,CountOfB DESC) AS RowRank
FROM (SELECT
A, B, COUNT(B) AS CountOfB
FROM #yourtable
GROUP BY A,B
) dt
) dt2
WHERE RowRank=1