Update grouped records with sequence in oracle - sql

I have following structure of data in Oracle table
COL1 COL2 COL2 GRP_ID
A A B 1
A A B 1
A A C 2
A A B 1
A D E 3
A D E 3
F G H 4
F G H 4
Basically each unique combination of col1, col2 and col3 has different value in GRP_ID column.
I need to replace value in GRP_ID column with database sequence value such that (assuming next value of sequence is 235678):
COL1 COL2 COL2 GRP_ID
A A B 235678
A A B 235678
A A C 235679
A A B 235678
A D E 235680
A D E 235680
F G H 235681
F G H 235681
There are millions of records in the table so I do not want to go through a loop. Reason to use database sequence is that the number provided by sequence will be exposed to customer and therefore it should not repeat when next communication is sent to customer.
Is there a way to do this through SQL?
Thanks!

Don't know if is the fastest way but it works.
CREATE FUNCTION NEXT
RETURN NUMBER IS
v_nextval NUMBER;
BEGIN
v_nextval := NEW_SEQUENCE.nextval;
RETURN(v_nextval);
END;
/
UPDATE EXAMPLE
SET EXAMPLE.GroupID =
(
SELECT G.GroupID FROM
(
SELECT B.Column1, B.Column2, B.Column3, MY_SCHEMA.NEXT() AS GroupID
FROM EXAMPLE B
GROUP BY B.Column1, B.Column2, B.Column3
) G
Where G.Column1 = EXAMPLE.Column1 AND G.Column2 = EXAMPLE.Column2
AND G.Column3 = EXAMPLE.Column3);
SELECT *
FROM EXAMPLE
Basically you have to do a distinct or group by to get all the different groups that you have and use the sequence in a function or you will get the sequence number is not allowed here error from oracle and then do an update.
See complete example on sqlfiddle
http://sqlfiddle.com/#!4/bf261/8

How about:
begin transaction
Get next sequence in a temp var
Disable sequence
update tablename set grp_ID = grp_ID + tmpVar-1
Select into tempVar max(grp_ID) from tableName
Re-seed sequence based value now in tempvar+1
Re-enable sequence
commit and end transaction
But i'm confused by something when you add additional records later, they can't simply do sequence.nextval because it's only needed when col1,col2,col3 doesn't already exist, otherwise it should use the group_ID for that unique combination... just seems odd, doable but odd.

Related

Generating the same random number per column values

I have a table which in which I would like to generate random numbers in a specific format (e.g. TEST-10256). For which I have been using the below:
concat('TEST-' , uniform(10000, 99000, RANDOM()))
However I now want to update a table with these random numbers based on two columns, so the desired outcome would be this:
I am not sure how to keep the same random value per previously matching it on the same values in ROW1 & ROW2.
Based on query from #Lukasz, we can use update statement.
-- Original
select * from test;
ROW1
ROW2
A
12
A
12
B
5
B
5
C
1
C
1
D
10
-- Update statement
update test t set t.row2 = t1.new_col
from (select row1, row2,
CONCAT('TEST-' , uniform(10000, 99000, RANDOM())) new_col
from (select distinct row1, row2 from test)
)t1
where t1.row1 = t.row1
and t1.row2 = t.row2;
-- After update
select * from test;
ROW1
ROW2
A
TEST-37642
A
TEST-37642
B
TEST-39082
B
TEST-39082
C
TEST-50195
C
TEST-50195
D
TEST-14564
Step 1: Generate a temporary table for possible values:
CREATE OR REPLACE TEMPORARY TABLE random_values
AS
SELECT ROW1, ROW2, CONCAT('TEST-' , uniform(10000, 99000, RANDOM())) AS new_value
FROM (SELECT DISTINCT ROW1, ROW2 FROM <table_name>) AS sub;
Step 2: Join using the newly generated and materialized values:
SELECT t.ROW1, rv.new_value AS ROW2
FROM <table_name> t
JOIN random_values rv
ON t.ROW1 = rv.ROW1
AND t.ROW2 = rv.ROW2;
Output:
Try using the MERGE statement to populate your new field. It allows you to fully run your query then apply its results as an UPDATE.
You can get the syntax here

Select all cell values of a single row table and list it as separate row in SQL

I've a table like this
ID col1 col2 col3 col4
---------------------------------------------------
1 a b c d
2 e f g h
So if I pass the ID 2 it should return all the colum values as separate rows as this
Colum Value
---------------------
ID 2
Col1 e
col2 f
col3 g
col4 h
So all the cells of that single rows been splitted as separate rows.
How can I accomplish this
One way to do it with unpivot and union all.
select 'id' as colu,cast(id as varchar(255)) as val from t where id=2
union all
select colu,val
from t
unpivot (val for colu in (col1,col2,col3,col4)) u
where id=2
You can use cross apply as below:
Select Id as [Column], [Value] from yourcols
cross apply (values(col1), (col2), (col3), (col4)) rws([Value])
where Id = 2
Please refer the below stack over flow link SQL query to split column data into rows
found the question identical as yours.

SSRS report column list sort order

Hi for my ssrs report i am using a matrix to display data row as a two column list and
I am using the following expression in order to group the row;
=ceiling(rownumber(nothing) / 2)
and
the following expression to group column;
=ceiling(rownumber(nothing) mod 2)
similar to https://www.experts-exchange.com/articles/12331/Simple-way-to-show-multi-column-data-in-SSRS-Horizontally-or-Vertically.html
it is working correctly however i would like results to be display alphabetical order going vertical instead of horizontal.
Like.
Record a Record d
Record b Record e
Record c Record f
Instead of
Record a Record b
Record c Record d
Record e Record f
i have order by in my sql query
any suggestions on how to achieve this?
You can modify your sql for this. The hack below could be used if you do not want to use multiple columns.
DECLARE #T TABLE
(
TableID INT,
Value1 NVARCHAR(20),
ShouldBe INT
)
INSERT INTO #T (TableID,Value1,ShouldBe)
VALUES
(1,'A',1),
(2,'B',3),
(3,'C',5),
(4,'D',2),
(5,'E',4),
(6,'F',6),
(7,'A',1),
(8,'B',3),
(9,'C',5),
(10,'D',2),
(11,'E',4),
(12,'F',6),
(13,'A',1)
DECLARE #NumberOfRowsPerPage INT = 3
DECLARE #NumberOfColumns INT = 2
SELECT PageNumber,Value1
FROM
(
SELECT ColumnOrder=ROW_NUMBER() OVER(PARTITION BY PageNumber,ColumnTile ORDER BY TableID),*
FROM
(
SELECT ColumnTile=NTILE(#NumberOfColumns) OVER(PARTITION BY PageNumber ORDER BY TableID),*
FROM
(
SELECT PageNumber=( (DataRowNumber -1) / (#NumberOfRowsPerPage * #NumberOfColumns )) + 1, *
FROM
(
SELECT DataRowNumber=ROW_NUMBER() OVER( ORDER BY TableID) ,*
FROM #T
)AS A
)AS B
)AS C
)AS D
ORDER BY
PageNumber,ColumnOrder,DataRowNumber
The query will produce the following output based on the RowsPerPage and NumberOfColumns.
Page Value
1 A
1 D
1 B
1 E
1 C
1 F
2 A
2 D
2 B
2 E
2 C
2 F
3 A

SQL group data (find data family)

Please help me, I need to find out a SQL solution for grouping data using SQL Server database.
I'm pretty sure that it could be done in one SQL request but I can't see the trick.
Let' see the problem :
I have a two columns table (please see below an example). I just want to add a new column containing a number or a string which indicates the group
BEFORE :
Col1 | Col2
-----+-----
A | B
B | C
D | E
F | G
G | H
I | I
J | U
AFTER TRANSFORMATION :
Col1 | Col2 | Group
-----+------+------
A | B | 1
B | C | 1
D | E | 2
F | G | 3
G | H | 3
I | I | 4
J | U | 5
In other words: A, B, C are in the same group; D and E too; F, G, H in group 3 ....
Do you have any lookup table to get this group mapping?
Or if you just have a logic defined to decide a group, i would recommend to add a UDF which will return group for supplied values.
SELECT Col1,Col2,GetGroupID(Col1,Col2) AS Group
FROM Table
Your UDF will be something like following
CREATE FUNCTION GetGroupID
(
-- Add the parameters for the function here
#Col1 varchar(10),
#Col2 varchar(10)
)
RETURNS int
AS
BEGIN
DECLARE #groupID int
IF (#Col1="A" AND #Co2 = "B") OR (#Col1="B" AND #Co2 = "C")
BEGIN
SET #groupID = 1
END
IF #Col1="D" AND #Co2 = "E"
BEGIN
SET #groupID = 2
END
-- You can write saveral conditions in the same manner.
return #groupID
END
However, in case you have this mapping defined somewhere in another table, let us know the structure of the table and we can then update the query to join with that table instead of using UDF.
Considering the performance of the query, if the amount of data is huge in your table , it is recommended to have these mappings to one fix table and Join that table in query. Using UDF may harm performance if data amount is huge.
There is absolutely no need for a UDF here. Regardless of whether you are looking to update the table with a new column or simply pull out the data with the grouping applied, you will be best off using a set based solution, ie: create and join to a table.
I am assuming here that you don't have messy data, such as a row with Col1 = 'A' and Col2 = 'F'.
If you are able to add new tables permanently you can use the following to create your lookup table:
create table Col1Groups(Col1 nvarchar(10), GroupNum int);
insert into Col1Groups(Col1,GroupNum) values ('A',1),('B',1),('C',1),('D',2),('E',2),('F',3),('G',3),('H',3);
and then join to it:
select t.Col1
,t.Col2
,g.GroupNum
from Table t
inner join Col1Groups g
on t.Col1 = g.Col1
If you can't, you can just create a derived table via a CTE:
with Col1Groups as
(
select Col1
,GroupNum
from (values('A',1),('B',1),('C',1),('D',2),('E',2),('F',3),('G',3),('H',3)) as x(Col1,GroupNum)
)
select t.Col1
,t.Col2
,g.GroupNum
from Table t
inner join Col1Groups g
on t.Col1 = g.Col1
You get the first rows per group with
select col1, col2 from mytable where col1 not in (select col2 from mytable) or col1 = col2;
We can give these rows numbers with
rank() over (order by col1) as grp
Now we must iterate through the rows to find the ones belonging to those first ones, then those belonging to these, etc. A recursive query.
with cte(col1, col2, grp) as
(
select col1, col2, rank() over (order by col1) as grp
from mytable where col1 not in (select col2 from mytable) or col1 = col2
union all
select mytable.col1, mytable.col2, cte.grp
from cte
join mytable on mytable.col1 = cte.col2
where mytable.col1 <> mytable.col2
)
select * from cte
order by grp, col1;
Additional answer for a more flexible approach
Originally you asked for chains A|B -> B|C, F|G -> G|H etc., but in your comment to my other answer you introduced forks like A|B -> B|C, B|D and I've adjusted my answer.
If you want to go one step further and introduce net-like relations such as A|B -> B|C, D|C, we can no longer follow chains forward only (in the example D belongs to the A group, because though A doesn't lead to D directly, it leads to C and D also leads to C. Here is a way to solve this:
Get all letters from the table (no matter whether in col1 or col2). Then for each of them find related letters (again no matter whether in col1 or col2). And for these again find related letters and so on. That will give you complete groups. But duplicates (as D is in the A group, A is in the D group also), which you can get rid of by simply taking the smallest (or greatest) group key per letter. Then join the Groups to the table.
The query:
with cte(col, grp) as
(
select col, rownum as grp from
(select col1 as col from mytable union select col2 from mytable)
union all
select case when mytable.col1 = cte.col then mytable.col2 else mytable.col1 end, cte.grp
from cte
join mytable on cte.col in (mytable.col1, mytable.col2)
where mytable.col1 <> mytable.col2
)
cycle col set is_cycle to 'y' default 'n'
select mytable.col1, mytable.col2, x.grp
from mytable
join (select col, min(grp) as grp from cte group by col) x on x.col = mytable.col1
order by grp, col;

SQL - Select from column A based on values in column B

Lets say I have a table with 2 columns (a, b) with following values:
a b
--- ---
1 5
1 NULL
2 NULL
2 NULL
3 NULL
My desired output:
a
---
2
3
I want to select only those distinct values from column a for which every single occurrence of this value has NULL in column b. Therefore from my desired output, "1" won't come in because there is a "5" in column b even though there is a NULL for the 2nd occurrence of "1".
How can I do this using a TSQL query?
If I understand correctly, you can do this with group by and having:
select a
from t
group by a
having count(b) = 0;
When you use count() with a column name, it counts the number of non-NULL values. Hence, if all values are NULL, then the value will be zero.
It's fairly simple to do:
SELECT A
FROM table1
GROUP BY A
HAVING COUNT(B) = 0
Grouping by A results in all the rows where the value of A is identical to be transferred into a single row in the output. Adding the HAVING clause enables to filter those grouped rows with an aggregate function. COUNT doesn't count NULL values, so when it's 0, there are no other values in B.
Two more ways to do this:
SELECT a
FROM t
EXCEPT
SELECT a
FROM t
WHERE b IS NOT NULL ;
This would use an index on (a, b):
SELECT a
FROM t
GROUP BY a
WHERE MIN(b) IS NOT NULL ;
Try it like this:
DECLARE #tbl TABLE(a INT, b INT);
INSERT INTO #tbl VALUES(1,5),(1,NULL),(2,NULL),(2,NULL),(3,NULL);
--Your test data
SELECT * FROM #tbl;
--And this is what you want - hopefully...
SELECT DISTINCT tbl.a
FROM #tbl AS tbl
WHERE NOT EXISTS(SELECT * FROM #tbl AS x WHERE x.a=tbl.a AND b IS NOT NULL)
To turn your question on it's head, you want the values from column a where there are no non-null values for that value in column b.
select distinct a
from table1 as t1
where 0 = (select count(*)
from table1 as t2
where t1.a = t2.a
and b is not null)
Sample fiddle is here: http://sqlfiddle.com/#!6/5d1b8/1
This should do it:
SELECT DISTINCT a
FROM t
WHERE b IS NULL
AND a NOT IN (SELECT a FROM t WHERE b IS NOT NULL);