Using random value as join condition - sql

I am generating some test-data and use dbms_random. I encountered some strange behavior when using dbms_random in the condition of the JOIN, that I can not explain:
------------------------# test-data (ids 1 .. 3)
With x As (
Select Rownum id From dual
Connect By Rownum <= 3
)
------------------------# end of test-data
Select x.id,
x2.id id2
From x
Join x x2 On ( x2.id = Floor(dbms_random.value(1, 4)) )
Floor(dbms_random.value(1, 4) ) returns a random number out of (1,2,3), so I would have expected all rows of x to be joined with a random row of x2, or maybe always the same random row of x2 in case the random number is evaluated only once.
When trying several times, I get results like that, though:
(1) ID ID2 (2) ID ID2 (3)
---- ---- ---- ---- no rows selected.
1 2 1 3
1 3 2 3
2 2 3 3
2 3
3 2
3 3
What am I missing?
EDIT:
SELECT ROWNUM, FLOOR(dbms_random.VALUE (1, 4))
FROM dual CONNECT BY ROWNUM <= 3
would get the result in this case, but why does the original query behave like that?

To generate three rows with one predictable value and one random value, try this:
SQL> with x as (
2 select rownum id from dual
3 connect by rownum <= 3
4 )
5 , y as (
6 select floor(dbms_random.value(1, 4)) floor_val
7 from dual
8 )
9 select x.id,
10 y.floor_val
11 from x
12 cross join y
13 /
ID FLOOR_VAL
---------- ----------
1 2
2 3
3 2
SQL
edit
Why did your original query return an inconsistent set of rows?
Well, without the random bit in the ON clause your query was basically a CROSS JOIN of X against X - it would have returned nine rows (at least it would have if the syntax had allowed it). Each of those nine rows executes a call to DBMS_RANDOM.VALUE(). Only when the random value matches the current value of X2.ID is the row included in the result set. Consequently the query can return 0-9 rows, randomly.
Your solution is obviously simpler - I didn't refactor enough :)

Related

Create multiple rows based on 1 column

I currently have a table with a quantity in it.
ID Code Quantity
1 A 1
2 B 3
3 C 2
4 D 1
Is there anyway to write a sql statement that would get me
ID Code Quantity
1 A 1
2 B 1
2 B 1
2 B 1
3 C 1
3 C 1
4 D 1
I need to break out the quantity and have that many number of rows
Thanks
Here's one option using a numbers table to join to:
with numberstable as (
select 1 AS Number
union all
select Number + 1 from numberstable where Number<100
)
select t.id, t.code, 1
from yourtable t
join numberstable n on t.quantity >= n.number
order by t.id
Online Demo
Please note, depending on which database you are using, this may not be the correct approach to creating the numbers table. This works in most databases supporting common table expressions. But the key to the answer is the join and the on criteria.
One way would be to generate an array with X elements (where X is the quantity). So for rows
ID Code Quantity
1 A 1
2 B 3
3 C 2
you would get
ID Code Quantity ArrayVar
1 A 1 [1]
2 B 3 [1,2,3]
3 C 2 [2]
using a sequence function (e.g, in PrestoDB, sequence(start, stop) -> array(bigint))
Then, unnest the array, so for each ID, you get a X rows, and set the quantity to 1. Not sure what SQL distribution you're using, but this should work!
You can use connect by statement to cross join tables in order to get your desired output.
check my solution it works pretty robust.
select
"ID",
"Code",
1 QUANTITY
from Table1, table(cast(multiset
(select level from dual
connect by level <= Table1."Quantity") as sys.OdciNumberList));

How to update table with concatenation

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

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.

how to get the even and odd column separately with separate column by query

I have an input:
id
1
2
3
4
5
6
7
8
9
10
I want get even and odd columns separately by columns in specified output like this
id col
1 2
3 4
5 6
7 8
9 10
here id and col are separate columns id contains the odd number and col contains the even number for specified input
SELECT MIN(id) as id, MAX(id) as col
FROM YourTable
GROUP BY FLOOR((id+1)/2)
For IDs 1 and 2, (id+1)/2 are 2/2 = 1 and 3/2 = 1.5, respectively, and FLOOR then returns 1 for both of them. Similarly, for 3 and 4, this is 2, and so on. So it groups all the input rows into pairs based on this formula. Then it uses MIN and MAX within each group to get the lower and higher IDs of the pairs.
Joined the table on itself
select *
from yourTable tA
left join yourTable tb on tA.id = (tB.id - 1)
where tA.id % 2 <> 0
If you use SQL you can try:
SELECT CASE WHEN column % 2 = 1
THEN column
ELSE null
END AS odds,
CASE WHEN column % 2 = 2
THEN column
ELSE null
END AS even
FROM yourtable
but not exactl as you ask
To show odd:
Select * from MEN where (RowID % 2) = 1
To show even:
Select * from MEN where (RowID % 2) = 0
Now, just join those two result sets and that's it.
Source

SQL conditional select for value in the same table group

We have a table like this:
id mid mult tr result
----------------------------
1 1 1,35 0 1
2 1 5,85 0 2
3 1 4 1 X
50 2 1,3 1 1
51 2 7 0 2
52 2 4 0 X
99 3 2,45 0 1
100 3 2,2 0 2
101 3 3,3 1 X
105 4 2,3 0 1
106 4 2,4 0 2
107 4 3,2 1 X
111 5 3 1 1
112 5 1,9 0 2
113 5 3,25 0 X
What we need is a query that will bring us a table which will count how many times before, for a specific mid(match), have the same mults(multipliers) for result = 1 and result = x have occurred and group them so that we can count them. Something like
select mult(where result = 1), mult(where result = x), count(1)
The result will be like this
mult_1 mult_x count
------------------------------
1,35 4 33
1,3 4 112
The above states that we have seen 33 matches where the mult for result = 1 was 1,35 AND mult for result = x was 4. Also, we found 112 matches where multi for result = 1 was 1,3 AND for result = x mult was 4 (results are not dependant on the first table).
I find it rather complex myself, but hopefully I made sense. I use SQL Server 2008, so any method is more than welcome.
Here is my (final) solution in action: http://www.sqlfiddle.com/#!3/3a516/7
There are a number of assumptions I'm making here.
I'm assuming that every mid value is going to have at most 1 result value of '1' and 1 result of '2'.
I'm assuming that if you want to specify a mid and have the query run off of that.
This assumption turned out to be wrong.
I'm assuming that you want to include the data for the mid you specify.
This assumption turned out to be wrong, it was every match.
I'm assuming that you want to include every mid (or match) before the mid you specify.
This assumption turned out to be wrong, it was every match.
I'm assuming that for any mult_1 and mult_x combination you find in this way, you want the count of how many times that combination occurs, even if it occurs after the match(mid) that you specified.
This assumption turned out to be wrong, it was just for every match.
If all of these assumptions sound right, then this might create the result you're looking for:
DECLARE #Mid INT = 2
;WITH MatchResults AS
(
SELECT
Mid
, [1] AS MultWithResult1
, [X] AS MultWithResultX
FROM
(
SELECT
Mid
, mult
, result
FROM Matches
WHERE result IN ('1', 'X')
) Base
PIVOT
(
MAX(mult)
FOR result
IN
(
[1]
, [X]
)
) Pivoted
)
SELECT
mult.MultWithResult1 AS mult_1
, mult.MultWithResultX AS mult_x
, COUNT(*) AS [count]
FROM MatchResults mult
GROUP BY mult.MultWithResult1
, mult.MultWithResultX
EDIT: I have edited it based on the response to my answer to what I think he means.
Use this query:
select mult_1, mult_x, count() as count
from Matches M
inner join (select distinct M1.mult as mult_1, MX.mult as mult_x from
matches as M1, matches as MX
where M1.result=1 and MX.result=x) M1x
on (M.mult=M1x.mult_1 and M.result=1) or (M.mult=M1x.mult_x and result=x)
group by mult_1, mult_x
EDIT:
I'm supposing you wnat to get the count of all possible combinations of multipliers of matches with result=1 and matches with result = x.
If this is the case, M1x gives all this possible combinations. And you join all the possible matchea with have any of those combinations, and count them, grouping by the possible set of combinations defined by M1x.