Oracle 11g: shuffling VARCHAR2 column to get random mapping - sql

I have something like that (FIDDLE:
CREATE TABLE COMPANY(
id int primary key,
name varchar2(20)
)
I entered sample data:
+------+----------+
| ID | NAME |
+------+----------+
| 1 | John |
| 4 | Albert |
| 6 | Anna |
+------+----------+
I need to have select that returns all names and new mapped name (randomly).
I have achieved it by writing something like this:
with names as (select distinct name from company)
select oldvar, newvar
from (select rownum as id, name as oldvar from names) o,
(select rownum as id,
name as newvar
from (select name from names order by dbms_random.value)) n
where o.id = n.id
(Note that: I DON'T AND I CAN'T use COMPANY.ID )
Can anyone optimalize this SQL statement? Maybe it can be done in better, more Oracle specific way ?

You could use row_number to create a random pair:
with names1 as
(
select row_number() over (order by dbms_random.value) as rn
, name
from Company
)
, names2 as
(
select row_number() over (order by dbms_random.value) as rn
, name
from Company
)
select n1.name as name1
, n2.name as name2
from names1 n1
join names2 n2
on n1.rn = n2.rn

Related

Merging two tables with no unique keys without row number

I have two tables
Table A:
Name
----
Andy
Greg
Table B:
Value
-----
1
2
I want to merge these two tables into one:
Table C:
Result
------
Andy 1
Greg 2
Note:-
without changing the order. I cannot use row numbers as I am using Apache Calcite and it doesn't support that right now.
Is this possible?
WITH X AS
(
SELECT * FROM
(
SELECT NAME AS Val1,
(SELECT Count(*) from #TableA a1 WHERE a1.Name < a2.Name) AS myRowNumber1 FROM #TableA a2
)a1
INNER JOIN
(
SELECT Id AS Val2,
(SELECT Count(*) from #TableB a1 WHERE a1.Id < a2.Id) AS myRowNumber2 FROM #TableB a2
)b1
ON a1.myRowNumber1=b1.myRowNumber2
)
SELECT Val1 +' '+ Val2 AS Result FROM X
You can use Count(*) instead of Row_Number()
OutPut:-
Result
---------
Andy 1
Greg 2
Create a new column as an identifier
SELECT *, ROW_NUMBER() OVER(PARTITION BY NAME ORDER BY NAME) AS id
INTO #NAMES
FROM TABLE1
SELECT *, ROW_NUMBER() OVER(PARTITION BY VALUE ORDER BY VALUE) AS id
INTO #VALUES_TABLE
FROM TABLE2
And then join by the row number that will be called id
SELECT *
FROM #NAMES t1
LEFT JOIN #VALUES_TABLE t2
ON t1.id = t2.id
Calcite does not have a function exactly like Oracle's ROWNUM pseudocolumn but it does have the standard SQL window function ROW_NUMBER(). You can use it as follows:
create table a as select * from (values ('Andy'), ('Greg')) as t (name);
create table b as select * from (values (1), (2)) as t (v);
select *
from (select name, row_number() over () as id from a)
join (select v, row_number() over () as id from b)
using (id);
+----+------+---+
| ID | NAME | V |
+----+------+---+
| 1 | Andy | 1 |
| 2 | Greg | 2 |
+----+------+---+
(2 rows)
If you want deterministic order, you can change over () to, say, over (order by name desc).

Get custom Distinct Values from column table

Table data and expected data as below
I wanted to select the distinct ID and Name, irrespective of branch. But I want the branch name to be displayed.
If branch need not be displayed I can use substring. Can the result be achieved using CTE.
As you don't appear to care about which 'branch' needs to be returned, you can simply use a row_number within a CTE to return just one result per ID value:
declare #t table(ID int,Name varchar(20));
insert into #t values
(10,'Manoj (CS)')
,(10,'Manoj (IS)')
,(20,'Ajay (CS)')
,(20,'AJAY (IS)')
,(30,'Sunjay(EC)')
,(40,'Lina(IS)')
,(40,'Lina(CS)')
,(40,'Lina(EC)')
,(50,'Mary(IS)')
,(50,'Mary(EC)');
with d as
(
select ID
,Name
,row_number() over (partition by ID order by Name) as rn
from #t
)
select ID
,Name
from d
where rn = 1;
Output:
+----+------------+
| ID | Name |
+----+------------+
| 10 | Manoj (CS) |
| 20 | Ajay (CS) |
| 30 | Sunjay(EC) |
| 40 | Lina(CS) |
| 50 | Mary(EC) |
+----+------------+
If you do have a preference for the (CS) branch however, you would need to alter the row_number slightly:
with d as
(
select ID
,Name
,row_number() over (partition by ID
order by case when right(Name,4) = '(CS)'
then 1
else 2
end
,Name
) as rn
from #t
)
select ID
,Name
from d
where rn = 1;
You can use row_number() function with TIES :
select top (1) with ties *
from table t
order by row_number() over (partition by id order by name);
As mentioned in the comments: Change your datamodel.
If you must live with the table as is, all you want is: all student IDs, each with one branhc/subject name arbitrarily picked. This can be achieved with a simple aggregation:
select id, min(name) from mytable group by id;

Getting distinct result with Oracle SQL

I have the following data structure
ID | REFID | NAME
1 | 100 | A
2 | 101 | B
3 | 101 | C
With
SELECT DISTINCT REFID, ID, NAME
FROM my_table
ORDER BY ID
I would like to have the following result:
1 | 100 | A
2 | 101 | B
Colum NAME and ID should contain the MIN or FIRST value.
But actually I get stuck at using MIN/FIRST here.
I welcome every tipps :-)
select id,
refid,
name
from (select id,
refid,
name,
row_number() over(partition by refid order by name) as rn
from my_table)
where rn = 1
order by id
You can use a subquery to do this.
WITH Q AS
( SELECT MIN(NAME) AS NAME, REFID FROM T GROUP BY REFID )
SELECT T.ID, T.REFID, T.NAME
FROM T
JOIN Q
ON (T.NAME = Q.NAME)
Also, note that SQL tables have no order. So there's no "First" value.

Using the same table twice in a FROM clause

I have a table that looks like this:
1 | 'Frank' | 'A'
2 | 'Frank' | 'B'
3 | 'Tom' | 'A'
4 | 'Tom' | 'B'
And I want an output that looks like this:
Frank | A | B
Tom | A | B
I've come up with this:
SELECT N.Name, N.Surname, M.Surname
FROM NAMES N, NAMES M
WHERE N.Surname = 'B' AND M.Surname = 'A'
GROUP BY N.Name;
This seems to work, but I don't know if it is a good practise to use the same table twice in the FROM clause, or if the performance will be affected by this on large tables.
Is there a simpler solution?
Having more than one table behind from is old-fashioned: you can make your join more readable with the standard on syntax:
from Names n
join Names m
on n.name = m.name
A self-join with a restrictive condition isn't too expensive. And Name is a pretty restrictive condition: only 2 records will share the name.
You could write it a little bit more efficient in a database that supports row_number(). That allows you to run the query without a join:
select Name
, max(case when rn = 1 then Surname end) as Surname1
, max(case when rn = 2 then Surname end) as Surname2
, max(case when rn = 3 then Surname end) as Surname3
from (
select row_number() over(
partition by Name
order by Surname) as rn
, Name
, Surname
from YourTable
) SubQueryAlias
group by
Name

can this be done in one sql query?

table indexed on the field name
for given value of name "name1" give me that row as well as N rows before and N rows after (alphabetically)
Did it in two select statements replace the number 5 with whatever you want you N to be and change the table name and this will do it. Also replace the asterisk with correct column names. Let me know if you have any problems with this.
select * from
(
Select *
,row_number() over (order by firstname desc) as 'rowNumber'
from attendees
) as temp
where rowNumber between
(
select rownumber-1
from
(
Select *, row_number() over (order by firstname desc) as 'rowNumber'
from attendees
) as temp
where firstname = 'name1') AND (
select rownumber+1
from
(
Select *, row_number() over (order by firstname desc) as 'rowNumber'
from attendees
) as temp
where firstname = 'name1')
The following gets you the row with name = 'name4', the two rows before that, and the two rows after that.
drop table t;
create table t(
name varchar(20)
,primary key(name)
);
insert into t(name) values('name1');
insert into t(name) values('name2');
insert into t(name) values('name3');
insert into t(name) values('name4');
insert into t(name) values('name5');
insert into t(name) values('name6');
insert into t(name) values('name7');
commit;
(select name from t where name = 'name4')
union all
(select name from t where name > 'name4' order by name asc limit 2)
union all
(select name from t where name < 'name4' order by name desc limit 2);
+-------+
| name |
+-------+
| name1 |
| name2 |
| name4 |
| name5 |
| name6 |
+-------+
Edit:
Added descending order by as pointed out by cyberkiwi (otherwise I would have gotten the "first" 2 items on the wrong end).