How to update table with concatenation - sql

I have table like this
create table aaa (id int not null, data varchar(50), numb int);
with data like this
begin
for i in 1..30 loop
insert into aaa
values (i, dbms_random.string('L',1),dbms_random.value(0,10));
end loop;
end;
now im making this
select a.id, a.data, a.numb,
count(*) over (partition by a.numb order by a.data) count,
b.id, b.data,b.numb
from aaa a, aaa b
where a.numb=b.numb
and a.data!=b.data
order by a.data;
and i want to update every row where those numbers are the same but with different letters, and in result i want to have new data with more than one letter (for example in data column- "a c d e"), i just want to create concatenation within. How can i make that? the point is to make something like group by for number but for that grouped column i would like to put additional value.
that is how it looks like in begining
id | data |numb
1 q 1
2 z 8
3 i 7
4 a 2
5 q 4
6 h 1
7 b 9
8 u 9
9 s 4
That i would like to get at end
id | data |numb
1 q h 1
2 z 8
3 i 7
4 a 2
5 q s 4
7 b u 9

Try this
SELECT MIN(id),
LISTAGG(data,' ') WITHIN GROUP(
ORDER BY data
) data,
numb
FROM aaa GROUP BY numb
ORDER BY 1
Demo

This selects 10 random strings 1 to 4 letters long, letters in words may repeat:
select level, dbms_random.string('l', dbms_random.value(1, 4))
from dual connect by level <= 10
This selects 1 to 10 random strings 1 to 26 letters long, letters do not repeat and are sorted:
with aaa(id, data, numb) as (
select level, dbms_random.string('L', 1),
round(dbms_random.value(0, 10))
from dual connect by level <= 30)
select numb, listagg(data) within group (order by data) list
from (select distinct data, numb from aaa)
group by numb

Related

SQL - Return All Possible Combinations of Values in a Column by Means of Two New Columns

I want to return all possible combinations of values in a column by means of two new columns. E.g. my column consists out of the values (A,B,C,D). The possible combinations of those values are (A,B), (A,C), (A,D), (B,C), (B,D), (C,D), (A,B,C), (B,D,C), (D,C,A), (C,A,B) [Remark: I don't want to consider (1) the combintions with just one value, (2) the combination with all values and (3) the combination with no values. Thus I have 2^(n)-n-1-1 combinations for n different values]. I want to list all those combinations in two columns like demonstrated below.
Consider that I start with this column:
Col0
----
A
B
C
D
Out of Col0 I want to produce the 10 combinations using two columns:
Col1 Col2
---- ----
1 A
1 B
2 A
2 C
3 A
3 D
4 B
4 C
5 B
5 D
6 C
6 C
7 A
7 B
7 C
8 B
8 C
8 D
9 C
9 D
9 A
10 D
10 A
10 B
How do I do this in SQL? I use SQLite.
Thank you a lot!
I have a solution, but it requires two changes...
Each item must be given an id (starting from 1)
The output id's may not be sequential
id | datum
----+-------
1 | A
2 | B
3 | C
4 | D
(The output id's I calculate are effectively identifiers for each Permutation, but I don't output permutations you're not interested in...)
group_id | datum
----------+-------
6 | A
6 | B
7 | A
7 | C
8 | A
8 | D
12 | B
12 | C
13 | B
13 | D
18 | C
18 | D
32 | A
32 | B
32 | C
33 | A
33 | B
33 | D
38 | A
38 | C
38 | D
63 | B
63 | C
63 | D
http://dbfiddle.uk/?rdbms=sqlite_3.8&fiddle=87d670ecaba8b735cb3f95fa66cea96b
http://dbfiddle.uk/?rdbms=sqlite_3.8&fiddle=26e4f59874009ef95367d85565563c3c
WITH
cascade AS
(
SELECT
1 AS depth,
NULL AS parent_id,
id,
datum,
id AS datum_id
FROM
sample
UNION ALL
SELECT
parent.depth + 1,
parent.id,
parent.id * (SELECT MAX(id)+1 FROM sample) + child.id - 1,
child.datum,
child.id
FROM
cascade AS parent
INNER JOIN
sample AS child
ON child.id > parent.datum_id
),
travelled AS
(
SELECT
depth AS depth,
parent_id AS parent_id,
id AS group_id,
datum AS datum,
datum_id AS datum_id
FROM
cascade
WHERE
depth NOT IN (1, (SELECT COUNT(*) FROM sample))
UNION ALL
SELECT
parent.depth,
parent.parent_id,
child.group_id,
parent.datum,
parent.datum_id
FROM
travelled AS child
INNER JOIN
cascade AS parent
ON parent.id = child.parent_id
)
SELECT
group_id,
datum
FROM
travelled
ORDER BY
group_id,
datum_id
The first CTE walks all the available combinations (recursively) creating a directed graph. At this stage I don't exclude combinations of one item, or all items, but I do exclude equivalent permutations.
Each node also has a unique identifier calculated for it. There are gaps in these ids, because the calculation would also work for all permutations, even though they're not all included.
Taking any node in that graph and walking up to the final parent node (recursively again) will always give a different combination than if you started from a different node in the graph.
So the second CTE does all of those walks, excluding the combinations of "just one item" and "all items".
The final select just outputs the results in order.
The gaps in the id's are probably avoidable but the maths is too hard for my head at the end of a working day.
The idea is to enumerate the power set, by assigning each value a power of 2, then iterate from 1 to 2^n - 1 , and filter the elements which corresponding bit is set.
-- map each value with a power of 2 : 1, 2, 4, 8, 16
with recursive ELEMENTS(IDX, POW, VAL) as (
-- init with dummy values
values(-1, 0.5, null)
union all
select IDX + 1,
POW * 2,
-- index the ordered values from 0 to N - 1
( select COL0
from DATA d1
where (select count(*) from DATA d2 where d2.COL0 < d1.COL0) = IDX + 1)
from ELEMENTS
where IDX + 1 < (select count(*) from data)
), POWER_SETS(ITER, VAL, POW) as (
select 1, VAL, POW from ELEMENTS where VAL is not null
union all
select ITER + 1, VAL, POW
from POWER_SETS
where ITER < (select SUM(POW) from elements) )
select ITER, VAL from POWER_SETS
-- only if the value's bit is set
where ITER & POW != 0
EDIT: 2nd version, with help from MatBailie. Only one of the CTE is recursive, and singleton subsets are excluded.
WITH RECURSIVE
-- number the values
elements(val, idx) AS (
SELECT d1.col0, (select count(*) from DATA d2 where d2.COL0 < d1.COL0)
FROM DATA d1
),
-- iterate from 3 (1 and 2 are singletons)
-- to 2^n - 1 (subset containing all the elements)
subsets(iter) AS (
VALUES(3)
UNION ALL
SELECT iter + 1
from subsets
WHERE iter < (1 << (SELECT COUNT(*) FROM elements)) - 1
)
SELECT iter AS Col1, val AS Col2
FROM elements
CROSS JOIN subsets
-- the element is present is this subset (the bit is set)
WHERE iter & (1 << idx) != 0
-- exclude singletons (another idea from MatBailie)
AND iter != (iter & -iter)
ORDER BY iter, val
If window functions and CTE is available then you can use the following approach
with data_rn as
(
select d1.col0 col1,
d2.col0 col2,
row_number() over (order by d1.col0) rn
from data d1
inner join data d2 on d1.col0 > d2.col0
)
select rn, col1 from data_rn
union all
select rn, col2 from data_rn
order by rn
dbfiddle demo

SQL Server column Sorting with 2 or more columns being sorted

I have these records in my table:
id sequence question answer letter
1 1 1 + 1 2 B
2 2 2 + 2 4 E
3 3 4 + 4 8 A
4 4 8 + 8 16 D
5 5 16 + 16 32 C
Is there any possibilites to make my table look like this? :
id sequence question answer letter
1 1 1 + 1 8 A
2 2 2 + 2 2 B
3 3 4 + 4 32 C
4 4 8 + 8 16 D
5 5 16 + 16 4 E
I mean the first one is i order by sequence, but i also wanted to get the order of it in letters because if not they will be in same position as therefore the question can be answered easily, I wanted the answer to come with the letter and order by it alphabetically. Please badly need your help :(
--DDL
CREATE TABLE test(id int,sequence int,question varchar(20),answer int, letter varchar(5) );
INSERT INTO test VALUES(1,1,'1+1',2,'B');
INSERT INTO test VALUES(2,2,'2+2',4,'E');
INSERT INTO test VALUES(3,3,'4+4',8,'A');
INSERT INTO test VALUES(4,4,'8+8',16,'D');
INSERT INTO test VALUES(5,5,'16+16',32,'C');
--SQL
SELECT A.id,
A.sequence,
A.question,
B.answer,
B.letter
FROM
(
SELECT id,
sequence,
question,
ROW_NUMBER() OVER ( ORDER BY id ) AS rn
FROM test
) A
INNER JOIN
(
SELECT answer,
letter,
ROW_NUMBER() OVER ( ORDER BY letter ) AS rn
FROM test
) B
ON A.rn = B.rn;
SQL Fiddle :-
http://sqlfiddle.com/#!6/96e09/3

Select unique subsets

I have a table like in example below.
SQL> select * from test;
ID PARENT_ID NAME
1 1 A
2 1 B
3 2 A
4 2 B
5 3 A
6 3 B
7 3 C
8 4 A
What I need is to get all unique subsets of names ((A,B), (A,B,C), (A)) or exclude duplicate subsets. You can see that (A,B) is twice there, one for PARENT_ID=1 and one for 2.
I want to exclude such duplicates:
ID PARENT_ID NAME
1 1 A
2 1 B
5 3 A
6 3 B
7 3 C
8 4 A
You can use DISTINCT to only return different values.
e.g.
SELECT DISTINCT GROUP_CONCAT(NAME SEPARATOR ',') as subsets
FROM TABLE_1
GROUP BY PARENT_ID;
SQL Fiddle
I have used 'group_concat' assuming you are using 'Mysql'. The equivalent function in Oracle is 'listagg()'. you can see it in action here in SQL fiddle
Here is the solution:-
Select a.* from
test a
inner join
(
Select nm, min(parent_id) as p_id
from
(
Select Parent_id, group_concat(NAME) as nm
from test
group by Parent_ID
) a
group by nm
)b
on a.Parent_id=b.p_id
order by parent_id, name

SQL select combine data from multiple rows into a single row

I have a table that looks similar to this:
ID OLD NEW TIME
1 a b 5
1 b c 7
1 c d 45
1 d e 4
2 a b 1
2 b d 8
2 d e 45
3 b c 15
3 c d 14
And I would like to build a report that looks like this (basically for each OLD data point grab the TIME value):
ID TimeForA TimeForB TimeForC TimeForD
1 5 7 45 4
2 1 8 NULL 45
3 NULL 15 14 NULL
I have been able to get all the data into the correct columns, but have not been able to combine each row into a single row for each ID. My current query looks like this (no I don't have every column in place yet, still just testing):
WITH CTE (id, ATime, BTime)
AS
(
select T1.oid, T1.loggedFor, null, T1.time as Atime
from Table1 T1
where T1.OLD = 'a'
union
select T1.oid, T1.loggedFor, T1.time as BTime, null
from Table1 T1
where T1.old = 'b'
)
select ID, ATime, BTime
from CTE
order by ID
Any help appreciated!
Try this:
select id,
sum(if(old = 'a',time,null)) as time_for_a,
sum(if(old = 'b',time,null)) as time_for_b,
sum(if(old = 'c',time,null)) as time_for_c,
sum(if(old = 'd',time,null)) as time_for_d
from test_tbl
group by id
order by id;

SQL: Assembling Non-Overlapping Sets

I have sets of consecutive integers, organized by type, in table1. All values are between 1 and 10, inclusive.
table1:
row_id set_id type min_value max_value
1 1 a 1 3
2 2 a 4 10
3 3 a 6 10
4 4 a 2 5
5 5 b 1 9
6 6 c 1 7
7 7 c 3 10
8 8 d 1 2
9 9 d 3 3
10 10 d 4 5
11 11 d 7 10
In table2, within each type, I want to assemble all possible maximal, non-overlapping sets (though gaps that cannot be filled by any sets of the correct type are okay). Desired output:
table2:
row_id type group_id set_id
1 a 1 1
2 a 1 2
3 a 2 1
4 a 2 3
5 a 3 3
6 a 3 4
7 b 4 5
8 c 5 6
9 c 6 7
10 d 7 8
11 d 7 9
12 d 7 10
13 d 7 11
My current idea is to use the fact that there is a limited number of possible values. Steps:
Find all sets in table1 containing value 1. Copy them into table2.
Find all sets in table1 containing value 2 and not already in table2.
Join the sets from (2) with table1 on type, set_id, and having min_value greater than the group's greatest max_value.
For the sets from (2) that did not join in (3), insert them into table2. These start new groups that may be extended later.
Repeat steps (2) through (4) for values 3 through 10.
I think this will work, but it has a lot of pain-in-the-butt steps, especially for (2)--finding the sets not in table2, and (4)--finding the sets that did not join.
Do you know a faster, more efficient method? My real data has millions of sets, thousands of types, and hundreds of values (though fortunately, as in the example, the values are bounded), so scalability is essential.
I'm using PLSQL Developer with Oracle 10g (not 11g as I stated before--thanks, IT department). Thanks!
For Oracle 10g you can't use recursive CTEs, but with a bit of work you can do something similar with the connect by syntax. First you need to generate a CTE or in-line view which has all the non-overlapping links, which you can do with:
select t1.type, t1.set_id, t1.min_value, t1.max_value,
t2.set_id as next_set_id, t2.min_value as next_min_value,
t2.max_value as next_max_value,
row_number() over (order by t1.type, t1.set_id, t2.set_id) as group_id
from table1 t1
left join table1 t2 on t2.type = t1.type
and t2.min_value > t1.max_value
where not exists (
select 1
from table1 t4
where t4.type = t1.type
and t4.min_value > t1.max_value
and t4.max_value < t2.min_value
)
order by t1.type, group_id, t1.set_id, t2.set_id;
This took a bit of experimentation and it's certainly possible I've missed or lost something about the rules in the process; but that gives you 12 pseudo-rows, and is in my previous answer this allows the two separate chains starting with a/1 to be followed while constraining the d values to a single chain:
TYPE SET_ID MIN_VALUE MAX_VALUE NEXT_SET_ID NEXT_MIN_VALUE NEXT_MAX_VALUE GROUP_ID
---- ------ ---------- ---------- ----------- -------------- -------------- --------
a 1 1 3 2 4 10 1
a 1 1 3 3 6 10 2
a 2 4 10 3
a 3 6 10 4
a 4 2 5 3 6 10 5
b 5 1 9 6
c 6 1 7 7
c 7 3 10 8
d 8 1 2 9 3 3 9
d 9 3 3 10 4 5 10
d 10 4 5 11 7 10 11
d 11 7 10 12
And that can be used as a CTE; querying that with a connect-by loop:
with t as (
... -- same as above query
)
select t1.type,
dense_rank() over (partition by null
order by connect_by_root group_id) as group_id,
t1.set_id
from t t1
connect by type = prior type
and set_id = prior next_set_id
start with not exists (
select 1 from table1 t2
where t2.type = t1.type
and t2.max_value < t1.min_value
)
and not exists (
select 1 from t t3
where t3.type = t1.type
and t3.next_max_value < t1.next_min_value
)
order by t1.type, group_id, t1.min_value;
The dense_rank() makes the group IDs contiguous; not sure if you actually need those at all, or if their sequence matters, so it's optional really. connect_by_root gives the group ID for the start of the chain, so although there were 12 rows and 12 group_id values in the initial query, they don't all appear in the final result.
The connection is via two prior values, type and the next set ID found in the initial query. That creates all the chains, but own its own would also include shorter chains - for d you'd see 8,9,10,11 but also 9,10,11 and 10,11, which you don't want as separate groups. Those are eliminated by the start with conditions, which could maybe be simplified.
That gives:
TYPE GROUP_ID SET_ID
---- -------- ------
a 1 1
a 1 2
a 2 1
a 2 3
a 3 4
a 3 3
b 4 5
c 5 6
c 6 7
d 7 8
d 7 9
d 7 10
d 7 11
SQL Fiddle demo.
If you can identify all the groups and their starting set_id then you can use a recursive approach and do this all in a single statement, rather than needing to populate a table iteratively. However you'd need to benchmark both approaches both for speed/efficiency and resource consumption - whether it will scale for your data volumes and within your system's available resources would need to be verified.
If I understand when you decide to start a new group you can identify them all at once with a query like:
with t as (
select t1.type, t1.set_id, t1.min_value, t1.max_value,
t2.set_id as next_set_id, t2.min_value as next_min_value,
t2.max_value as next_max_value
from table1 t1
left join table1 t2 on t2.type = t1.type and t2.min_value > t1.max_value
where not exists (
select 1
from table1 t3
where t3.type = t1.type
and t3.max_value < t1.min_value
)
)
select t.type, t.set_id, t.min_value, t.max_value,
t.next_set_id, t.next_min_value, t.next_max_value,
row_number() over (order by t.type, t.min_value, t.next_min_value) as grp_id
from t
where not exists (
select 1 from t t2
where t2.type = t.type
and t2.next_max_value < t.next_min_value
)
order by grp_id;
The tricky bit here is getting all three groups for a, specifically the two groups that start with set_id = 1, but only one group for d. The inner select (in the CTE) looks for sets that don't have a lower non-overlapping range via the not exists clause, and outer-joins to the same table to get the next set(s) that don't overlap, which gives you two groups that start with set_id = 1, but also four that start with set_id = 9. The outer select then ignores everything but the lowest non-overlapping with a second not exists clause - but doesn't have to hit the real table again.
So that gives you:
TYPE SET_ID MIN_VALUE MAX_VALUE NEXT_SET_ID NEXT_MIN_VALUE NEXT_MAX_VALUE GRP_ID
---- ------ ---------- ---------- ----------- -------------- -------------- ------
a 1 1 3 2 4 10 1
a 1 1 3 3 6 10 2
a 4 2 5 3 6 10 3
b 5 1 9 4
c 6 1 7 5
c 7 3 10 6
d 8 1 2 9 3 3 7
You can then use that as the anchor member in a recursive subquery factoring clause:
with t as (
...
),
r (type, set_id, min_value, max_value,
next_set_id, next_min_value, next_max_value, grp_id) as (
select t.type, t.set_id, t.min_value, t.max_value,
t.next_set_id, t.next_min_value, t.next_max_value,
row_number() over (order by t.type, t.min_value, t.next_min_value)
from t
where not exists (
select 1 from t t2
where t2.type = t.type
and t2.next_max_value < t.next_min_value
)
...
If you left the r CTE with that and just did sleect * from r you'd get the same seven groups.
The recursive member then uses the next set_id and its range from that query as the next member of each group, and repeats the outer join/not-exists look up to find the next set(s) again; stopping when there is no next non-overlapping set:
...
union all
select r.type, r.next_set_id, r.next_min_value, r.next_max_value,
t.set_id, t.min_value, t.max_value, r.grp_id
from r
left join table1 t
on t.type = r.type
and t.min_value > r.next_max_value
and not exists (
select 1 from table1 t2
where t2.type = r.type
and t2.min_value > r.next_max_value
and t2.max_value < t.min_value
)
where r.next_set_id is not null -- to stop looking when you reach a leaf node
)
...
Finally you have a query based on the recursive CTE to get the columns you want and to specify the order:
...
select r.type, r.grp_id, r.set_id
from r
order by r.type, r.grp_id, r.min_value;
Which gets:
TYPE GRP_ID SET_ID
---- ---------- ----------
a 1 1
a 1 2
a 2 1
a 2 3
a 3 4
a 3 3
b 4 5
c 5 6
c 6 7
d 7 8
d 7 9
d 7 10
d 7 11
SQL Fiddle demo.
If you wanted to you could show the min/max values for each set, and could track and show the min/max value for each group. I've just show then columns from the question though.