I have a table with data below
create table A
(id number,
code varchar2(100));
insert into A values
(1, '20,21,22');
insert into A values
(2, '22,23,24');
commit;
create table code_descriptions
(
code_id number,
code_desc varchar2(100));
insert into code_descriptions values
(20, 'ABC');
insert into code_descriptions values
(21, 'BBC');
insert into code_descriptions values
(22, 'CAP');
insert into code_descriptions values
(23, 'INC');
insert into code_descriptions values
(24, 'ABC');
commit;
I would like to come up with sql to update table A code values with descriptions
Could you please suggest any functions or ideas, to achieve this?
You can tokenize your comma-separated lists:
select id, level as rn, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1) as code_id
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null;
ID RN CODE_ID
---------- ---------- -------
1 1 20
1 2 21
1 3 22
2 1 22
2 2 23
2 3 24
Then use that as a CTE (subquery factoring) and join to the descriptions (I've made it a left-join so you choose how to handle any missing codes):
with cte (id, rn, code_id) as (
select id, level, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1)
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null
)
select cte.id, cte.rn, cte.code_id, coalesce(cd.code_desc, '??') as code_desc
from cte
left join code_descriptions cd on cd.code_id = cte.code_id;
ID RN CODE_ID CODE_DESC
---------- ---------- ------- ---------
1 1 20 ABC
1 2 21 BBC
2 1 22 CAP
1 3 22 CAP
2 2 23 INC
2 3 24 ABC
and then aggregate those back into single comma-separated values:
with cte (id, rn, code_id) as (
select id, level, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1)
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null
)
select cte.id,
listagg(coalesce(cd.code_desc, '??'), ',') within group (order by rn) as code
from cte
left join code_descriptions cd on cd.code_id = cte.code_id
group by cte.id;
ID CODE
---------- ----------------
1 ABC,BBC,CAP
2 CAP,INC,ABC
and then merge or us a correlated update:
update a
set code = (
with cte (id, rn, code_id) as (
select id, level, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1)
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null
)
select listagg(coalesce(cd.code_desc, '??'), ',') within group (order by rn) as code
from cte
left join code_descriptions cd on cd.code_id = cte.code_id
where cte.id = a.id
group by cte.id
);
2 rows updated.
select * from a;
ID CODE
---------- ----------------
1 ABC,BBC,CAP
2 CAP,INC,ABC
or
rollback; -- just to undo previous update
merge into a
using (
with cte (id, rn, code_id) as (
select id, level, regexp_substr(code, '(.*?)(,|$)', 1, level, null, 1)
from a
connect by level <= regexp_count(code, ',') + 1
and id = prior id
and prior dbms_random.value is not null
)
select cte.id, listagg(coalesce(cd.code_desc, '??'), ',') within group (order by rn) as code
from cte
left join code_descriptions cd on cd.code_id = cte.code_id
group by cte.id
) tmp
on (tmp.id = a.id)
when matched then update set a.code = tmp.code;
2 rows merged.
select * from a;
ID CODE
---------- ----------------
1 ABC,BBC,CAP
2 CAP,INC,ABC
db<>fiddle
The simplest solution is if the number of elements in the "code" column of table A is fixed - by directly gluing the subqueries to the code_descriptions table by codes (we cut each code from the enumeration line).
Related
I have data in the below format
id idnew
1 2
3 4
2
4 7
6 8
7
Result Should be something like this
ID should be followed by idnew
1
2
3
4
2
4
7
6
8
7
Thanks in advance
This should maintain the order:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY idnew) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
WHERE idnew IS NOT NULL
) a
ORDER BY RowNumber
I am assuming the id column is NOT NULL-able.
NOTE: If you want to keep the NULL values from the idnew column AND maintain the order, then remove the WHERE clause and ORDER BY id in the second select:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY id) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
) a
ORDER BY RowNumber
This is fully tested, try it here: https://rextester.com/DVZXO21058
Setting up the table as you described:
CREATE TABLE myTable (id INT, idnew INT);
INSERT INTO myTable (id, idnew)
VALUES (1, 2),
(3, 4),
(2, NULL),
(4, 7),
(6, 8),
(7, NULL);
SELECT * FROM myTable;
Here is the query to do the trick:
SELECT mixed_id FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS row_num,
id,
idnew
FROM myTable
) AS x
UNPIVOT
(
mixed_id for item in (id, idnew)
) AS y
WHERE mixed_id IS NOT NULL
ORDER BY row_num, mixed_id;
In order not to further complicate the query, this is taking advantage of 'id' would rank ahead of 'idnew' as a string. I believe string ranking is not the key issue here.
Using Cross Apply
;WITH CTE (id,idnew)
AS
(
SELECT 1,2 UNION ALL
SELECT 3,4 UNION ALL
SELECT 2,NULL UNION ALL
SELECT 4,7 UNION ALL
SELECT 6,8 UNION ALL
SELECT 7,NULL
)
SELECT New
FROM CTE
CROSS APPLY ( VALUES (id),(idnew))AS Dt (New)
WHERE dt.New IS NOT NULL
Result
New
---
1
2
3
4
2
4
7
6
8
7
I have a table with the columns below, and I need to get the values if COD is duplicated, get the non NULL on VALUE column. If is not duplicated, it can get a NULL VALUE. Like the example:
I'm using SQL SERVER.
This is what I get:
COD ID VALUE
28 1 NULL
28 2 Supermarket
29 1 NULL
29 2 School
29 3 NULL
30 1 NULL
This is what I want:
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
What I'm tryin' to do:
;with A as (
(select DISTINCT COD,ID,VALUE from CodId where ID = 2)
UNION
(select DISTINCT COD,ID,NULL from CodId where ID != 2)
)select * from A order by COD
You can try this.
DECLARE #T TABLE (COD INT, ID INT, VALUE VARCHAR(20))
INSERT INTO #T
VALUES(28, 1, NULL),
(28, 2 ,'Supermarket'),
(29, 1 ,NULL),
(29, 2 ,'School'),
(29, 3 ,NULL),
(30, 1 ,NULL)
;WITH CTE AS (
SELECT *, RN= ROW_NUMBER() OVER (PARTITION BY COD ORDER BY VALUE DESC) FROM #T
)
SELECT COD, ID ,VALUE FROM CTE
WHERE RN = 1
Result:
COD ID VALUE
----------- ----------- --------------------
28 2 Supermarket
29 2 School
30 1 NULL
Another option is to use the WITH TIES clause in concert with Row_Number()
Example
Select top 1 with ties *
from YourTable
Order By Row_Number() over (Partition By [COD] order by Value Desc)
Returns
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
I would use GROUP BY and JOIN. If there is no NOT NULL value for a COD than it should be resolved using the OR in JOIN clause.
SELECT your_table.*
FROM your_table
JOIN (
SELECT COD, MAX(value) value
FROM your_table
GROUP BY COD
) gt ON your_table.COD = gt.COD and (your_table.value = gt.value OR gt.value IS NULL)
If you may have more than one non null value for a COD this will work
drop table MyTable
CREATE TABLE MyTable
(
COD INT,
ID INT,
VALUE VARCHAR(20)
)
INSERT INTO MyTable
VALUES (28,1, NULL),
(28,2,'Supermarket'),
(28,3,'School'),
(29,1,NULL),
(29,2,'School'),
(29,3,NULL),
(30,1,NULL);
WITH Dups AS
(SELECT COD FROM MyTable GROUP BY COD HAVING count (*) > 1 )
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
INNER JOIN dups ON MyTable.COD = Dups.COD
WHERE value IS NOT NULL
UNION
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
LEFT JOIN dups ON MyTable.COD = Dups.COD
WHERE dups.cod IS NULL
I need a graceful way to select the max value from a field holding a comma delimited list.
Expected Values:
List_1 | Last
------ | ------
A,B,C | C
B,D,C | D
I'm using the following query and I'm not getting what's expected.
select
list_1,
(
select max(values) WITHIN GROUP (order by 1)
from (
select
regexp_substr(list_1,'[^,]+', 1, level) as values
from dual
connect by regexp_substr(list_1, '[^,]+', 1, level) is not null)
) as last
from my_table
Anyone have any ideas to fix my query?
with
test_data ( id, list_1 ) as (
select 101, 'A,B,C' from dual union all
select 102, 'B,D,C' from dual union all
select 105, null from dual union all
select 122, 'A' from dual union all
select 140, 'A,B,B' from dual
)
-- end of simulated table (for testing purposes only, not part of the solution)
select id, list_1, max(token) as max_value
from ( select id, list_1,
regexp_substr(list_1, '([^,])(,|$)', 1, level, null, 1) as token
from test_data
connect by level <= 1 + regexp_count(list_1, ',')
and prior id = id
and prior sys_guid() is not null
)
group by id, list_1
order by id
;
ID LIST_1_ MAX_VAL
---- ------- -------
101 A,B,C C
102 B,D,C D
105
122 A A
140 A,B,B B
In Oracle 12.1 or higher, this can be re-written using the LATERAL clause:
select d.id, d.list_1, x.max_value
from test_data d,
lateral ( select max(regexp_substr(list_1, '([^,]*)(,|$)',
1, level, null, 1)) as max_value
from test_data x
where x.id = d.id
connect by level <= 1 + regexp_count(list_1, ',')
) x
order by d.id
;
I need your help in SQL
I have a set of records of Cost center ID below.
what I want to do is to segregate/group them by inserting column to distinguish the category.
as you can see all digits start in 7 is belong to the bold digits.
my expected out is on below image also.
You can as the below:
DECLARE #Tbl TABLE (ID INT)
INSERT INTO #Tbl
VALUES
(735121201),
(735120001),
(5442244),
(735141094),
(735141097),
(4008060),
(735117603),
(40100000),
(735142902),
(735151199),
(4010070)
;WITH TableWithRowId
AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY(SELECT NULL)) RowId,
ID
FROM
#Tbl
), TempTable
AS
(
SELECT T.RowId + 1 AS RowId FROM TableWithRowId T
WHERE
LEFT(T.ID, 1) != 7
), ResultTable
AS
(
SELECT
T.RowId ,
T.ID,
DENSE_RANK() OVER (ORDER BY (SELECT TOP 1 A.RowId FROM TempTable A WHERE A.RowId > T.RowId ORDER BY A.RowId)) AS Flag
FROM TableWithRowId T
)
SELECT * FROM ResultTable
Result:
RowId ID Flag
----------- ----------- ----------
1 735121201 1
2 735120001 1
3 5442244 1
4 735141094 2
5 735141097 2
6 4008060 2
7 735117603 3
8 40100000 3
9 735142902 4
10 735151199 4
11 4010070 4
The following query is similer with NEER's
;WITH test_table(CenterID)AS(
SELECT '735121201' UNION ALL
SELECT '735120001' UNION ALL
SELECT '5442244' UNION ALL
SELECT '735141094' UNION ALL
SELECT '735141097' UNION ALL
SELECT '4008060' UNION ALL
SELECT '735117603' UNION ALL
SELECT '40100000' UNION ALL
SELECT '735142902' UNION ALL
SELECT '735151199' UNION ALL
SELECT '4010070'
),t1 AS (
SELECT *,ROW_NUMBER()OVER(ORDER BY(SELECT 1)) AS rn,CASE WHEN LEFT(t.CenterID,1)='7' THEN 1 ELSE 0 END AS isSeven
FROM test_table AS t
),t2 AS(
SELECT t1.*,ROW_NUMBER()OVER(ORDER BY t1.rn) AS toFilter
FROM t1 LEFT JOIN t1 AS pt ON pt.rn=t1.rn-1
WHERE pt.CenterID IS NULL OR (t1.isSeven=1 AND pt.isSeven=0)
)
SELECT t1.CenterID,x.toFilter FROM t1
CROSS APPLY(SELECT TOP 1 t2.toFilter FROM t2 WHERE t2.rn<=t1.rn ORDER BY rn desc) x
CenterID toFilter
--------- --------------------
735121201 1
735120001 1
5442244 1
735141094 2
735141097 2
4008060 2
735117603 3
40100000 3
735142902 4
735151199 4
4010070 4
Using Oracle 10g. I have two tables:
User Parent
-------------
1 (null)
2 1
3 1
4 3
Permission User_ID
-------------------
A 1
B 3
The values in the permissions table get inherited down to the children. I would like to write a single query that could return me something like this:
User Permission
------------------
1 A
2 A
3 A
3 A
3 B
4 A
4 B
Is it possible to formulate such a query using 10g connect .. by syntax to pull in rows from previous levels?
you can achieve the desired result with a connect by (and the function CONNECT_BY_ROOT that returns the column value of the root node):
SQL> WITH users AS (
2 SELECT 1 user_id, (null) PARENT FROM dual
3 UNION ALL SELECT 2, 1 FROM dual
4 UNION ALL SELECT 3, 1 FROM dual
5 UNION ALL SELECT 4, 3 FROM dual
6 ), permissions AS (
7 SELECT 'A' permission, 1 user_id FROM dual
8 UNION ALL SELECT 'B', 3 FROM dual
9 )
10 SELECT lpad('*', 2 * (LEVEL-1), '*')||u.user_id u,
11 u.user_id, connect_by_root(permission) permission
12 FROM users u
13 LEFT JOIN permissions p ON u.user_id = p.user_id
14 CONNECT BY u.PARENT = PRIOR u.user_id
15 START WITH p.permission IS NOT NULL
16 ORDER SIBLINGS BY user_id;
U USER_ID PERMISSION
--------- ------- ----------
3 3 B
**4 4 B
1 1 A
**2 2 A
**3 3 A
****4 4 A
You could take a look at http://www.adp-gmbh.ch/ora/sql/connect_by.html
Kind of black magic, but you can use table-cast-multiset to reference one table from another in WHERE clause:
create table t1(
usr number,
parent number
);
create table t2(
usr number,
perm char(1)
);
insert into t1 values (1,null);
insert into t1 values (2,1);
insert into t1 values (3,1);
insert into t1 values (4,3);
insert into t2 values (1,'A');
insert into t2 values (3,'B');
select t1.usr
, t2.perm
from t1
, table(cast(multiset(
select t.usr
from t1 t
connect by t.usr = prior t.parent
start with t.usr = t1.usr
) as sys.odcinumberlist)) x
, t2
where t2.usr = x.column_value
;
In the subquery x I construct a table of all parents for the given user from t1 (including itself), then join it with permissions for these parents.
Here is a example for just one user id. you can use proc to loop all.
CREATE TABLE a_lnk
(user_id VARCHAR2(5),
parent_id VARCHAR2(5));
CREATE TABLE b_perm
(perm VARCHAR2(5),
user_id VARCHAR2(5));
INSERT INTO a_lnk
SELECT 1, NULL
FROM DUAL;
INSERT INTO a_lnk
SELECT 2, 1
FROM DUAL;
INSERT INTO a_lnk
SELECT 3, 1
FROM DUAL;
INSERT INTO a_lnk
SELECT 4, 3
FROM DUAL;
INSERT INTO b_perm
SELECT 'A', 1
FROM DUAL;
INSERT INTO b_perm
SELECT 'B', 3
FROM DUAL;
-- example for just for user id = 1
--
SELECT c.user_id, c.perm
FROM b_perm c,
(SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id = 1
CONNECT BY PRIOR user_id = parent_id
UNION
SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id IS NULL
CONNECT BY PRIOR user_id = parent_id) d
WHERE c.user_id = d.user_id
UNION
SELECT d.user_id, c.perm
FROM b_perm c,
(SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id = 1
CONNECT BY PRIOR user_id = parent_id
UNION
SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id IS NULL
CONNECT BY PRIOR user_id = parent_id) d
WHERE c.user_id = d.parent_id;