SQL server: Question about query group and auto id - sql

i have a table which has 2 columns like this, picture is input and output:
Explain input: 2 column is 2 person who relation ship together. Exam: A relation with B, C,D,H
Output: i want to merge 2 column , with Column group ID auto and column RelationShip
ID group auto: i tried query: row_number() OVER (ORDER BY [columnA]) n
RelationShip: is group all person relatioship together and group them to 1 ID group. Exam person A,B,C,D,H realation ship together and group id auto is 1
I have a demo in https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=f81ff386b07589654ad133a8e4b30472
In my query, first i turn the data into a single column, second i count letter, next step i don't know query.
I tried recursive CTE but error (perhaps I don't understand clearly about recursive)
I want to solve this problem in sql query.

You can use a recursive CTE to assign each "column" value a number. This starts by created edges in both directions and then following them:
with pairs as (
select columnA, columnB from myTable
union -- on purpose to remove duplicates
select columnB, columnA from myTable
),
cte as (
select distinct columnA, columnA as columnB,
convert(varchar(max), ',' + columnA + ',') as visited,
1 as lev
from pairs
union all
select cte.columnA, p.columnB, concat(visited, p.columnA, ','), lev + 1
from cte join
pairs p
on cte.columnB = p.columnA
where cte.visited not like concat('%,', p.columnB, ',%') and
lev < 5
)
select cte.columnA, min(cte.columnB),
dense_rank() over (order by min(cte.columnB))
from cte
group by cte.columnA;
You can then join this in to assign a group id:
with pairs as (
select columnA, columnB from myTable
union -- on purpose to remove duplicates
select columnB, columnA from myTable
),
cte as (
select distinct columnA, columnA as columnB,
convert(varchar(max), ',' + columnA + ',') as visited,
1 as lev
from pairs
union all
select cte.columnA, p.columnB, concat(visited, p.columnA, ','), lev + 1
from cte join
pairs p
on cte.columnB = p.columnA
where cte.visited not like concat('%,', p.columnB, ',%') and
lev < 5
),
groups as (
select cte.columnA, min(cte.columnB) as min_columnB,
dense_rank() over (order by min(cte.columnB)) as group_id
from cte
group by cte.columnA
)
select t.*, g.group_id
from mytable t join
groups g
on g.columnA = t.columnA;
Here is a db<>fiddle.

Related

How to generate Tree path using traverse CTE

Suppose my data in table like in attached picture without having path column.
i want to generate a column, like "Path" in picture using traverse CTE in sql
Picture example:
Use a recursive CTE to solve this:
WITH recCTE
AS (
SELECT id,
parentid,
id AS original_id,
parentid AS original_parentid,
name as original_name,
1 AS depth,
CAST(name AS VARCHAR(5000)) AS path
FROM yourtable
UNION ALL
SELECT yourtable.id,
yourtable.parentid,
recCTE.original_id,
recCTE.original_parentID,
recCTE.original_name,
recCTE.depth + 1,
CAST(recCTE.path + '-' + yourtable.name as VARCHAR(5000))
FROM recCTE
INNER JOIN yourtable
ON recCTE.parentid = yourtable.id
WHERE depth < 20 /*prevent cycling*/
)
SELECT original_id as id, original_parentid as parentid, original_name as name, depth, path
FROM recCTE t1
WHERE depth = (SELECT max(depth) FROM recCTE WHERE t1.original_id = recCTE.original_id)
sqlfiddle example
That CTE has two parts:
The "Anchor Member" which is the first selection from the table. This defines the output (which columns and column type are in the output).
The "Recursive Member" which selects from the CTE in which it's contained at is performed iteratively until the join fails.
In this example we capture the path by concatenating the path to the name over and over again in the recursive member. We also track Depth (how many recursions have been performed) and track the current id and parentid as well as the original id and original parentid so they can be selected in the final SELECT statement.
try this,
;with cte(id,parentId,name,path,cnt)
AS
(
select id,parentid,name,cast(name as VARCHAR(1024)) as path, 1 as cnt from test_cte
union all
select a.id,a.parentid,a.name,CAST((a.name + '-' +path ) as VARCHAR(1024)), case when a.parentid is null then 0 else cnt + 1 end as cnt from test_cte a join cte c on c.id = a.parentid where c.cnt is not null
)
select id,parentid,name,path from (select id,parentid,name,path, row_number() over(partition by id order by cnt desc) as rank from cte) a where a.rank = 1 order by 1 asc ;

alias table with union all not works

I have ora-00094 (identifier not valid) in a simple query but I can't see why. Could you help me please?
select columnA, 'More than 4000 bytes'
from tableA
union all
select p.columnB, listagg(p.columnC, ',') within group (order by p.columnC)
from (
select distinct b.job_name, a.hostname
from tableB a, emuser.def_job b
) p
group by p.columnB
order by p.columnB desc;
ORDER BY is for ResultSet of whole query. So for ORDER BY there is no columnB here. ResultSet have only column names of first query.
Try this
SELECT columnA, 'More than 4000 bytes' as columnC FROM tableA
UNION ALL
SELECT p.columnB, LISTAGG (p.columnC, ',') WITHIN GROUP (ORDER BY p.columnC)
FROM (SELECT DISTINCT b.job_name, a.hostname
FROM tableB a, emuser.def_job b) p
GROUP BY p.columnB
ORDER BY p.columnA DESC;

Oracle: SQL Dynamic cursor statement

I have a dynamic temporary table like below.
Table name for assumption: TB_EMP_TEMP_TABLE
Column1 | column2 | column3
Emp_NM | EMP_ID |TB_EMP_DTLS
Emp_Adr | EMP_ID |TB_EMP_DTLS
Emp_Sal | EMP_ID |TB_EMP_OTHER
The above data is retrieved as a Cursor(Emp_cursor) and i need to construct a dynamic SQL Query as below based on cursor data.
Expected Output:
SELECT TB_EMP_DTLS.EMP_NM,TB_EMP_DTLS.EMP_Adr,TB_EMP_OTHER.EMP_SAL
FROM TB_EMP_DTLS,TB_EMP_OTHER
WHERE TB_EMP_DTLS.EMP_ID=TB_EMP_OTHER.EMP_ID
I havent worked extensively on PLSQL/Cursor concepts. How the cursor can be looped to get expected output.
If i understand it right, you want column1 values selected from column3 tables joined by column2 columns.
It's not elegant but should work:
select listagg(v, ' ') within group (order by n asc) my_cursor from (
with
tb as (select distinct column3 val from tb_emp_temp_table), --tables
sl as (select distinct column3||'.'||column1 val from tb_emp_temp_table), --selected columns
pr as (select distinct column3||'.'||column2 val from tb_emp_temp_table) --predicates
select 1 n, 'SELECT' v from dual
union
select 2 n, listagg(val, ', ') within group (order by val) v from sl
union
select 3 n, 'FROM' v from dual
union
select 4 n, listagg(val, ', ') within group (order by val) v from tb
union
select 5 n, 'WHERE' v from dual
union
select 6 n, listagg(pra.val||'='||prb.val, ' AND ') within group (order by pra.val) v from pr pra, pr prb where pra.val != prb.val
)

concatenate recursive cross join

I need to concatenate the name in a recursive cross join way. I don't know how to do this, I have tried a CTE using WITH RECURSIVE but no success.
I have a table like this:
group_id | name
---------------
13 | A
13 | B
19 | C
19 | D
31 | E
31 | F
31 | G
Desired output:
combinations
------------
ACE
ACF
ACG
ADE
ADF
ADG
BCE
BCF
BCG
BDE
BDF
BDG
Of course, the results should multiply if I add a 4th (or more) group.
Native Postgresql Syntax:
SqlFiddleDemo
WITH RECURSIVE cte1 AS
(
SELECT *, DENSE_RANK() OVER (ORDER BY group_id) AS rn
FROM mytable
),cte2 AS
(
SELECT
CAST(name AS VARCHAR(4000)) AS name,
rn
FROM cte1
WHERE rn = 1
UNION ALL
SELECT
CAST(CONCAT(c2.name,c1.name) AS VARCHAR(4000)) AS name
,c1.rn
FROM cte1 c1
JOIN cte2 c2
ON c1.rn = c2.rn + 1
)
SELECT name as combinations
FROM cte2
WHERE LENGTH(name) = (SELECT MAX(rn) FROM cte1)
ORDER BY name;
Before:
I hope if you don't mind that I use SQL Server Syntax:
Sample:
CREATE TABLE #mytable(
ID INTEGER NOT NULL
,TYPE VARCHAR(MAX) NOT NULL
);
INSERT INTO #mytable(ID,TYPE) VALUES (13,'A');
INSERT INTO #mytable(ID,TYPE) VALUES (13,'B');
INSERT INTO #mytable(ID,TYPE) VALUES (19,'C');
INSERT INTO #mytable(ID,TYPE) VALUES (19,'D');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'E');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'F');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'G');
Main query:
WITH cte1 AS
(
SELECT *, rn = DENSE_RANK() OVER (ORDER BY ID)
FROM #mytable
),cte2 AS
(
SELECT
TYPE = CAST(TYPE AS VARCHAR(MAX)),
rn
FROM cte1
WHERE rn = 1
UNION ALL
SELECT
[Type] = CAST(CONCAT(c2.TYPE,c1.TYPE) AS VARCHAR(MAX))
,c1.rn
FROM cte1 c1
JOIN cte2 c2
ON c1.rn = c2.rn + 1
)
SELECT *
FROM cte2
WHERE LEN(Type) = (SELECT MAX(rn) FROM cte1)
ORDER BY Type;
LiveDemo
I've assumed that the order of "cross join" is dependent on ascending ID.
cte1 generate DENSE_RANK() because your IDs contain gaps
cte2 recursive part with CONCAT
main query just filter out required length and sort string
The recursive query is a bit simpler in Postgres:
WITH RECURSIVE t AS ( -- to produce gapless group numbers
SELECT dense_rank() OVER (ORDER BY group_id) AS grp, name
FROM tbl
)
, cte AS (
SELECT grp, name
FROM t
WHERE grp = 1
UNION ALL
SELECT t.grp, c.name || t.name
FROM cte c
JOIN t ON t.grp = c.grp + 1
)
SELECT name AS combi
FROM cte
WHERE grp = (SELECT max(grp) FROM t)
ORDER BY 1;
The basic logic is the same as in the SQL Server version provided by #lad2025, I added a couple of minor improvements.
Or you can use a simple version if your maximum number of groups is not too big (can't be very big, really, since the result set grows exponentially). For a maximum of 5 groups:
WITH t AS ( -- to produce gapless group numbers
SELECT dense_rank() OVER (ORDER BY group_id) AS grp, name AS n
FROM tbl
)
SELECT concat(t1.n, t2.n, t3.n, t4.n, t5.n) AS combi
FROM (SELECT n FROM t WHERE grp = 1) t1
LEFT JOIN (SELECT n FROM t WHERE grp = 2) t2 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 3) t3 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 4) t4 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 5) t5 ON true
ORDER BY 1;
Probably faster for few groups. LEFT JOIN .. ON true makes this work even if higher levels are missing. concat() ignores NULL values. Test with EXPLAIN ANALYZE to be sure.
SQL Fiddle showing both.

Select max key from joined keys

I have a table that contains keys that have been changed to a different key. These are laid out like this:
origkey newkey
1 2
2 3
4 5
6 7
7 8
8 9
9 10
What I'm trying to accomplish is a query that takes the origkey and finds the max newkey for each one. In the example above, the results would look like:
origkey maxkey
1 3
4 5
6 10
If I knew the maximum amount of times that the key could have been changed, I would just add that amount of self joins and get it from there. Unfortunately, I don't know how many times it could have changed in the past. Is there a way to keep self joining until it finds a null? The following query will return the changed keys into new columns, but I think I'm going down the wrong road here since this will get the 1 -> 3 change, but not the 6 -> 10 change.
select a.origkey
,a.newkey
,b.newkey newkey1
,c.newkey newkey2
from changedkeys a
Left Outer Join changedkeys b on a.newkey=b.origkey
Left Outer Join changedkeys c on b.newkey=c.origkey
There is a way. It is called a recursive CTE:
with cte as (
select origkey, newkey, 1 as lev
from table1
union all
select cte.origkey, t1.newkey, lev + 1
from cte join
table1 t1
on cte.newkey = t1.origkey
)
select origkey, newkey as newestkey
from (select cte.*, row_number() over (partition by origkey order by lev desc) as seqnum
from cte
) t
where seqnum = 1;
Note that this assumes that there are no cycles in the key definitions, as in the example in your question. If this is a possibility, the recursive CTE can be modified to handle this.
EDIT:
If you have potential cycles in the data, then try this:
with cte as (
select origkey, newkey, 1 as lev, ',' + cast(newkey as varchar(8000)) + ',' as keys
from table1
union all
select cte.origkey, t1.newkey, cte.lev + 1, keys + cast(t1.newkey as varchar(8000)) + ','
from cte join
table1 t1
on cte.newkey = t1.origkey
where ',' + t1.keys + ',' not like '%,' + cast(t1.newkey as varchar(8000)) + '%,'
)
select origkey, newkey as newestkey
from (select cte.*, row_number() over (partition by origkey order by lev desc) as seqnum
from cte
) t
where seqnum = 1;