Oracle SQL, how to select * having distinct columns - sql

I want to have a query something like this (this doesn't work!)
select * from foo where rownum < 10 having distinct bar
Meaning I want to select all columns from ten random rows with distinct values in column bar. How to do this in Oracle?
Here is an example. I have the following data
| item | rate |
-------------------
| a | 50 |
| a | 12 |
| a | 26 |
| b | 12 |
| b | 15 |
| b | 45 |
| b | 10 |
| c | 5 |
| c | 15 |
And result would be for example
| item no | rate |
------------------
| a | 12 | --from (26 , 12 , 50)
| b | 45 | --from (12 ,15 , 45 , 10)
| c | 5 | --from (5 , 15)
Aways having distinct item no

SQL Fiddle
Oracle 11g R2 Schema Setup:
Generate a table with 12 items A - L each with rates 0 - 4:
CREATE TABLE items ( item, rate ) AS
SELECT CHR( 64 + CEIL( LEVEL / 5 ) ),
MOD( LEVEL - 1, 5 )
FROM DUAL
CONNECT BY LEVEL <= 60;
Query 1:
SELECT item,
rate
FROM (
SELECT i.*,
-- Give the rates for each item a unique index assigned in a random order
ROW_NUMBER() OVER ( PARTITION BY item ORDER BY DBMS_RANDOM.VALUE ) AS rn
FROM items i
ORDER BY DBMS_RANDOM.VALUE -- Order all the rows randomly
)
WHERE rn = 1 -- Only get the first row for each item
AND ROWNUM <= 10 -- Only get the first 10 items.
Results:
| ITEM | RATE |
|------|------|
| A | 0 |
| K | 2 |
| G | 4 |
| C | 1 |
| E | 0 |
| H | 0 |
| F | 2 |
| D | 3 |
| L | 4 |
| I | 1 |

I mention table create and query for distinct and top 10 rows;
(Ref SqlFiddle)
create table foo(item varchar(20), rate int);
insert into foo values('a',50);
insert into foo values('a',12);
insert into foo values('a',26);
insert into foo values('b',12);
insert into foo values('b',15);
insert into foo values('b',45);
insert into foo values('b',10);
insert into foo values('c',5);
insert into foo values('c',15);
--Here first get the distinct item and then filter row number wise rows:
select item, rate from (
select item, rate, ROW_NUMBER() over(PARTITION BY item ORDER BY rate desc)
row_num from foo
) where row_num=1;

Related

Get some values from the table by selecting

I have a table:
| id | Number |Address
| -----| ------------|-----------
| 1 | 0 | NULL
| 1 | 1 | NULL
| 1 | 2 | 50
| 1 | 3 | NULL
| 2 | 0 | 10
| 3 | 1 | 30
| 3 | 2 | 20
| 3 | 3 | 20
| 4 | 0 | 75
| 4 | 1 | 22
| 4 | 2 | 30
| 5 | 0 | NULL
I need to get: the NUMBER of the last ADDRESS change for each ID.
I wrote this select:
select dh.id, dh.number from table dh where dh =
(select max(min(t.history)) from table t where t.id = dh.id group by t.address)
But this select not correctly handling the case when the address first changed, and then changed to the previous value. For example id=1: group by return:
| Number |
| -------- |
| NULL |
| 50 |
I have been thinking about this select for several days, and I will be happy to receive any help.
You can do this using row_number() -- twice:
select t.id, min(number)
from (select t.*,
row_number() over (partition by id order by number desc) as seqnum1,
row_number() over (partition by id, address order by number desc) as seqnum2
from t
) t
where seqnum1 = seqnum2
group by id;
What this does is enumerate the rows by number in descending order:
Once per id.
Once per id and address.
These values are the same only when the value is 1, which is the most recent address in the data. Then aggregation pulls back the earliest row in this group.
I answered my question myself, if anyone needs it, my solution:
select * from table dh1 where dh1.number = (
select max(x.number)
from (
select
dh2.id, dh2.number, dh2.address, lag(dh2.address) over(order by dh2.number asc) as prev
from table dh2 where dh1.id=dh2.id
) x
where NVL(x.address, 0) <> NVL(x.prev, 0)
);

How to delete the rows with three same data columns and one different data column

I have a table "MARK_TABLE" as below.
How can I delete the rows with same "STUDENT", "COURSE" and "SCORE" values?
| ID | STUDENT | COURSE | SCORE |
|----|---------|--------|-------|
| 1 | 1 | 1 | 60 |
| 3 | 1 | 2 | 81 |
| 4 | 1 | 3 | 81 |
| 9 | 2 | 1 | 80 |
| 10 | 1 | 1 | 60 |
| 11 | 2 | 1 | 80 |
Now I already filtered the data I want to KEEP, but without the "ID"...
SELECT student, course, score FROM mark_table
INTERSECT
SELECT student, course, score FROM mark_table
The output:
| STUDENT | COURSE | SCORE |
|---------|--------|-------|
| 1 | 1 | 60 |
| 1 | 2 | 81 |
| 1 | 3 | 81 |
| 2 | 1 | 80 |
Use the following query to delete the desired rows:
DELETE FROM MARK_TABLE M
WHERE
EXISTS (
SELECT
1
FROM
MARK_TABLE M_IN
WHERE
M.STUDENT = M_IN.STUDENT
AND M.COURSE = M_IN.COURSE
AND M.SCORE = M_IN.SCORE
AND M.ID < M_IN.ID
)
OUTPUT
db<>fiddle demo
Cheers!!
use distinct
SELECT distinct student, course, score FROM mark_table
Assuming you don't just want to select the unique data you want to keep (you mention you've already done this), you can proceed as follows:
Create a temporary table to hold the data you want to keep
Insert the data you want to keep into the temporary table
Empty the source table
Re-Insert the data you want to keep into the source table.
select * from
(
select row_number() over (partition by student,course,score order by score)
rn,student,course,score from mark_table
) t
where rn=1
Use CTE with RowNumber
create table #MARK_TABLE (ID int, STUDENT int, COURSE int, SCORE int)
insert into #MARK_TABLE
values
(1,1,1,60),
(3,1,2,81),
(4,1,3,81),
(9,2,1,80),
(10,1,1,60),
(11,2,1,80)
;with cteDeleteID as(
Select id, row_number() over (partition by student,course,score order by score) [row_number] from #MARK_TABLE
)
delete from #MARK_TABLE where id in
(
select id from cteDeleteID where [row_number] != 1
)
select * from #MARK_TABLE
drop table #MARK_TABLE

Convert tuple value to column names

Got something like:
+-------+------+-------+
| count | id | grade |
+-------+------+-------+
| 1 | 0 | A |
| 2 | 0 | B |
| 1 | 1 | F |
| 3 | 1 | D |
| 5 | 2 | B |
| 1 | 2 | C |
I need:
+-----+---+----+---+---+---+
| id | A | B | C | D | F |
+-----+---+----+---+---+---+
| 0 | 1 | 2 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 1 | 1 |
| 2 | 0 | 5 | 1 | 0 | 0 |
I don't know if I can even do this. I can group by id but how would you read the count value for each grade column?
CREATE TABLE #MyTable(_count INT,id INT , grade VARCHAR(10))
INSERT INTO #MyTable( _count ,id , grade )
SELECT 1,0,'A' UNION ALL
SELECT 2,0,'B' UNION ALL
SELECT 1,1,'F' UNION ALL
SELECT 3,1,'D' UNION ALL
SELECT 5,2,'B' UNION ALL
SELECT 1,2,'C'
SELECT *
FROM
(
SELECT _count ,id ,grade
FROM #MyTable
)A
PIVOT
(
MAX(_count) FOR grade IN ([A],[B],[C],[D],[F])
)P
You need a "pivot" table or "cross-tabulation". You can use a combination of aggregation and CASE statements, or, more elegantly the crosstab() function provided by the additional module tablefunc. All basics here:
PostgreSQL Crosstab Query
Since not all keys in grade have values, you need the 2-parameter form. Like this:
SELECT * FROM crosstab(
'SELECT id, grade, count FROM table ORDER BY 1,2'
, $$SELECT unnest('{A,B,C,D,F}'::text[])$$
) ct(id text, "A" int, "B" int, "C" int, "D" int, "F" int);

Update table with ordered values

i need to update a table ordering by price and reassigning the ordered price.
The price and values are grouped by idcategory. Here is an example:
| ID | idcategory | price | value |
| 1 | 1 | 10 | 3 |
| 2 | 1 | 12 | 30 |
| 3 | 1 | 43 | 9 |
| 4 | 1 | 32 | 23 |
| 5 | 2 | 38 | 13 |
| 6 | 2 | 8 | 26 |
| 7 | 2 | 3 | 34 |
| 8 | 2 | 10 | 12 |
. .. .. .. .. .. .. ... ... .. .. .. ..
I need to reorder the table grouping by idcategory reassigning the ordered value to the ordered price like this:
| ID | idcategory | price | value |
| 1 | 1 | 10 | 3 |
| 2 | 1 | 12 | 9 |
| 3 | 1 | 32 | 23 |
| 4 | 1 | 43 | 30 |
| 5 | 2 | 3 | 12 |
| 6 | 2 | 8 | 13 |
| 7 | 2 | 10 | 26 |
| 8 | 2 | 38 | 34 |
.. .. .. .. .. .. .. .. .. ... ..
database is a postgres 9.2.
any idea will be appreciated.
Thanks you and Happy new Year !!!
this is the updated working solution based on GarethD suggestion:
WITH OrderedValues AS
( SELECT Value,
Price,
idcategory,
ROW_NUMBER() OVER(PARTITION BY idcategory ORDER BY Value) AS ValueNum,
ROW_NUMBER() OVER(PARTITION BY idcategory ORDER BY Price) AS PriceNum
FROM T
), OrderedIDs AS
( SELECT ID,
idcategory,
ROW_NUMBER() OVER(PARTITION BY idcategory ORDER BY ID) AS RowNum
FROM T
), NewValues AS
( SELECT i.ID,
v.Value,
p.Price
FROM OrderedIDs i
INNER JOIN OrderedValues v
ON i.RowNum = v.ValueNum
AND i.idcategory = v.idcategory
INNER JOIN OrderedValues p
ON i.RowNum = p.PriceNum
AND i.idcategory = p.idcategory
)
UPDATE T
SET Price = v.Price,
Value = v.Value
FROM NewValues v
WHERE v.ID = T.ID;
SELECT *
FROM T;
You first need to rank your both your IDs (OrderedIDs), and your Price/Value combination (OrderedValues). Then you can matched the corresponding ranks (NewValues), and update your table accordingly:
WITH OrderedValues AS
( SELECT Value,
Price,
idcategory,
ROW_NUMBER() OVER(PARTITION BY idcategory ORDER BY Value, Price) AS RowNum
FROM T
), OrderedIDs AS
( SELECT ID,
idcategory,
ROW_NUMBER() OVER(PARTITION BY idcategory ORDER BY ID) AS RowNum
FROM T
), NewValues AS
( SELECT i.ID,
v.Value,
v.Price
FROM OrderedIDs i
INNER JOIN OrderedValues v
ON i.RowNum = v.RowNum
AND i.idcategory = v.idcategory
)
UPDATE T
SET Price = v.Price,
Value = v.Value
FROM NewValues v
WHERE v.ID = T.ID;
Example on SQL Fiddle

Sub-sub-selects and grouping: Get name column from the row containing the max value of a group

I have two tables: States, and Items.
States:
+----+------+-------+----------+
| id | name | state | priority |
+----+------+-------+----------+
| 1 | AA | 10 | 1 |
| 2 | AB | 10 | 2 |
| 3 | AC | 10 | 3 |
| 4 | BA | 20 | 1 |
| 5 | BB | 20 | 5 |
| 6 | BC | 20 | 10 |
| 7 | BD | 20 | 50 |
+----+------+-------+----------+
Items:
+----+--------+-------+
| id | item | state |
+----+--------+-------+
| 1 | Blue | 10 |
| 2 | Red | 20 |
| 3 | Green | 20 |
| 4 | Yellow | 10 |
| 5 | Brown | 10 |
+----+--------+-------+
The priority column is not used in the Items table, but complicates getting the data I need, as shown below.
What I want is a list of the rows in the Items table, replacing the state.id value in each row with the name of the highest priority state.
Results would look like this:
+----+--------+-------+
| id | item | state |
+----+--------+-------+
| 1 | Blue | AC |
| 2 | Red | BD |
| 3 | Green | BD |
| 4 | Yellow | AC |
| 5 | Brown | AC |
+----+--------+-------+
Here's the tiny monster I've come up with. Is this the best way, or can I be more efficient / less verbose? (Sub-sub-selects make my palms itch. :-P )
SELECT *
FROM
Items AS itm
INNER JOIN (SELECT sta.name, sta.state
FROM (SELECT state, MAX(priority) [highest]
FROM States
GROUP BY state) AS pri
INNER JOIN States AS sta
ON sta.state = pri.state
AND sta.priority = pri.highest) AS nam
ON item.state = name.state
Update: I'm using MS-SQL 2005 and MS-SQL 2008R2
You did not post your version of SQL-Server. Assuming you are on 2005 or later you can use the ROW_NUMBER() function together with a cross apply like this:
CREATE TABLE dbo.States(id INT, name NVARCHAR(25), state INT, priority INT);
INSERT INTO dbo.States
VALUES
( 1 ,'AA', 10 , 1 ),
( 2 ,'AB', 10 , 2 ),
( 3 ,'AC', 10 , 3 ),
( 4 ,'BA', 20 , 1 ),
( 5 ,'BB', 20 , 5 ),
( 6 ,'BC', 20 , 10 ),
( 7 ,'BD', 20 , 50 );
CREATE TABLE dbo.Items( id INT ,item NVARCHAR(25), state INT );
INSERT INTO dbo.Items
VALUES
( 1 ,'Blue', 10 ),
( 2 ,'Red', 20 ),
( 3 ,'Green', 20 ),
( 4 ,'Yellow', 10 ),
( 5 ,'Brown', 10 );
SELECT i.id,
i.item,
s.name,
s.priority
FROM dbo.Items i
CROSS APPLY (
SELECT *,ROW_NUMBER()OVER(ORDER BY priority DESC) rn FROM dbo.States si WHERE si.state = i.state
)s
WHERE s.rn = 1;
The cross apply works like a join but allows to reference columns on the left side in the right side as you can see in the where clause. The ROW_NUMBER() function numbers all rows in the states table that match the current state value in reverse priority order so that the row with the highest priority always gets the number 1. The final where clause is filtering out just those rows.
EDIT:
I just started a blog series about joins: A Join A Day
The Cross Apply will be topic of day 8 (12/8/2012).