compare numbers - sql

so here is the deal, and all google search i could find has different scenarios, but nothing matching my situation :-).
this will be coded in VB.net, but since its sql syntax i can easily build it into vb later.
so i have a table from which i will grab a row with numbers:
select n1, n2, n3, n4, n5 from TempTable
essentially this will come back with something like the 3, 8, 12, 5, 33
i now have a 2nd table with the same columns (n1...n5)
i need to find which rows in the 2nd table which have more or equal than 2 numbers matching from the 1st set of numbers (3 or 8 or 12 or 5 or 33).
so if the 2nd table would look like this:
4, 3, 57, 33, 1
5, 6, 87, 21, 44
65, 3, 12, 7, 8
then the query should return 2 (1st and last row)
i hope that makes sense :-)
Thank you for your help!

I would first normalize the 2 tables, so that there is only an ID and the value (and maybe the position if that may be relevant). Let's call them A2 and B2:
SELECT ID, 1 AS Pos, N1 AS Value FROM A
UNION ALL
SELECT ID, 2 AS Pos, N2 AS Value FROM A
UNION ALL
SELECT ID, 3 AS Pos, N3 AS Value FROM A
UNION ALL
SELECT ID, 4 AS Pos, N4 AS Value FROM A
UNION ALL
SELECT ID, 5 AS Pos, N5 AS Value FROM A
Then get only the records with 2 or more matches:
SELECT B2.ID, COUNT(*) AS Matches FROM A2
INNER JOIN B2 ON A2.Value = B2.Value
GROUP BY B2.ID
HAVING COUNT(*) >= 2
And finally count them:
SELECT COUNT(*) FROM ...
In a single query that could look like this (but I recommend you make views for A2 and B2):
WITH A2 AS (
-- Normalize table 1
SELECT ID, 1 AS Pos, N1 AS Value FROM A
UNION ALL
SELECT ID, 2 AS Pos, N2 AS Value FROM A
UNION ALL
SELECT ID, 3 AS Pos, N3 AS Value FROM A
UNION ALL
SELECT ID, 4 AS Pos, N4 AS Value FROM A
UNION ALL
SELECT ID, 5 AS Pos, N5 AS Value FROM A
), B2 AS (
-- Normalize table 2
SELECT ID, 1 AS Pos, N1 AS Value FROM B
UNION ALL
SELECT ID, 2 AS Pos, N2 AS Value FROM B
UNION ALL
SELECT ID, 3 AS Pos, N3 AS Value FROM B
UNION ALL
SELECT ID, 4 AS Pos, N4 AS Value FROM B
UNION ALL
SELECT ID, 5 AS Pos, N5 AS Value FROM B
)
-- Count them
SELECT COUNT(*) FROM (
-- Get only the ones with 2 or more matches
SELECT B2.ID, COUNT(*) AS Matches FROM A2
INNER JOIN B2 ON A2.Value = B2.Value
GROUP BY B2.ID
HAVING COUNT(*) >= 2
) AS T

Related

Return distinct rows based on only one column in oracle sql

I want to return an n number of distinct rows. The distinct rows should be based on one column (SN) only.
I have the query below which is expected to return 4 rows where the serial number is greater than 2 and no rows with similar SN column values are returned.
Table
SN letter value
1 test 25
1 bread 26
3 alpha 43
4 beta 23
4 gamma 5
5 omega 60
6 omega 60
Expected Result
SN letter value
3 alpha 43
4 beta 23
5 omega 60
6 omega 60
This is the query I have. This does not work correctly, it returns the duplicates because it filters disctinct values by all the columns combined instead of just the single column, SN.
SELECT * FROM (SELECT a.*, row_number() over(order by SN) rowRank
FROM (SELECT distinct SN, letter, value from table where SN > 2 order by SN) a)
WHERE rowRank BETWEEN 1 AND 4}"
You do not need to use DISTINCT before trying to filter out your results. You can modify the ORDER BY clause of the row_rank analytic function if you need to modify which duplicate of a SN should be returned. Right now it is returning the first LETTER value alphabetically since that matches your example result.
Query
WITH
some_table (sn, letter, VALUE)
AS
(SELECT 1, 'test', 25 FROM DUAL
UNION ALL
SELECT 1, 'bread', 26 FROM DUAL
UNION ALL
SELECT 3, 'alpha', 43 FROM DUAL
UNION ALL
SELECT 4, 'beta', 23 FROM DUAL
UNION ALL
SELECT 4, 'gamma', 5 FROM DUAL
UNION ALL
SELECT 5, 'omega', 60 FROM DUAL
UNION ALL
SELECT 6, 'omega', 60 FROM DUAL)
--Above is to set up the sample data. Use the query below with your real table
SELECT sn, letter, VALUE
FROM (SELECT sn,
letter,
VALUE,
ROW_NUMBER () OVER (PARTITION BY sn ORDER BY letter) AS row_rank
FROM some_table
WHERE sn > 2)
WHERE row_rank = 1
ORDER BY sn
FETCH FIRST 4 ROWS ONLY;
Result
SN LETTER VALUE
_____ _________ ________
3 alpha 43
4 beta 23
5 omega 60
6 omega 60
SELECT * FROM
(
SELECT
t.*
,ROW_NUMBER() OVER (PARTITION BY sn ORDER BY value ) rn
FROM
t
WHERE sn > 2
) t1
WHERE t1.rn = 1
ORDER BY sn;

Is there a way to do a wildcard with a GROUP BY in bigquery?

Suppose I have two tables: tbl_a contains an id field "id" and a whole bunch of other fields "fa", "fb", ... "fz". (with all ids unique)
tbl_b contains two fields "id" and "value" - where there could be multiple values for the same id. I want to make a table with the same fields as in tbl_a, but with an additional field giving the average "value". This can be done as:
SELECT
tbl_a.id AS id
ANY_VALUE(tbl_a.fa) AS fa
...
ANY_VALUE(tbl_a.fz) AS fz
AVG(tbl_b.value) AS avg_value
FROM
tbl_a JOIN tbl_b ON tbl_a.id AS tbl_b.id
GROUP BY tbl_a.id
My question is:
Is there a way to write this query without having to explicitly write in every field that you want to transfer from the old to new table?
If there were no GROUP BY then this could be done using a wildcard, but I don't see how to do it where there is a GROUP BY, since the wildcard would effectively have to be within the ANY_VALUE().
Below is for BigQuery Standard SQL
#standardSQL
SELECT ANY_VALUE(a).*, AVG(value) avg_value
FROM `project.dataset.table_a` a
LEFT JOIN `project.dataset.table_b` b
USING(id)
GROUP BY a.id
You can test, play with above using dummy data as in below example
#standardSQL
WITH `project.dataset.table_a` AS (
SELECT 1 id, 11 fa, 12 fb, 13 fc UNION ALL
SELECT 2, 21, 22, 23 UNION ALL
SELECT 3, 31, 32, 33
), `project.dataset.table_b` AS (
SELECT 1 id, 1 value UNION ALL
SELECT 1, 2 UNION ALL
SELECT 1, 3 UNION ALL
SELECT 2, 4 UNION ALL
SELECT 2, 5 UNION ALL
SELECT 2, 6 UNION ALL
SELECT 3, 7
)
SELECT ANY_VALUE(a).*, AVG(value) avg_value
FROM `project.dataset.table_a` a
LEFT JOIN `project.dataset.table_b` b
USING(id)
GROUP BY a.id
-- ORDER BY a.id
with result
Row id fa fb fc avg_value
1 1 11 12 13 2.0
2 2 21 22 23 5.0
3 3 31 32 33 7.0

finding consecutive numbers

ok - so i searched the internet for this, and none of the examples i found are exactly like mine.
i have a table with 5 columns and thousands of rows.
i need to find consecutive numbers within each row. i need to end up with 3 queries for the situations shown below
n1 n2 n3 n4 n5
=======================
1 3 4 6 9 = should result in 1 (when checking for pairs)
1 3 4 5 9 = should result in 1 (when checking for triplets)
1 2 5 8 9 = should result in 1 (when checking for double pairs)
This is what i have to move the columns into rows, but i am not sure how to check this now.
select n1 from (
select n1 from myTable where Id = 1
union all select n2 from myTable where Id = 1
union all select n3 from myTable where Id = 1
union all select n4 from myTable where Id = 1
union all select n5 from myTable where Id = 1
) t
order by n1
Thank you for all your help!
#TimBiegeleise, update :
so i found this on google for Gaps & Islands:
SELECT ID, StartSeqNo=MIN(SeqNo), EndSeqNo=MAX(SeqNo)
FROM (
SELECT ID, SeqNo
,rn=SeqNo-ROW_NUMBER() OVER (PARTITION BY ID ORDER BY SeqNo)
FROM dbo.GapsIslands) a
GROUP BY ID, rn;
this is my updated query converting the columns to rows (but it requires 2 statements, i much rather have 1) and implementing the island part - but i don't understand how that give me the result what i need (see above). below i show the original row data and the result.
select n1, IDENTITY (INT, 1, 1) AS ID
into #test
from (
select n1 from myTable where Id = 8
union all select n2 from myTable where Id = 8
union all select n3 from myTable where Id = 8
union all select n4 from myTable where Id = 8
union all select n5 from myTable where Id = 8
) as t
order by n1
SELECT ID, StartSeqNo=MIN(n1), EndSeqNo=MAX(n1)
FROM (
SELECT ID, n1
,rn=n1-ROW_NUMBER() OVER (PARTITION BY ID ORDER BY n1)
FROM #test) a
GROUP BY ID, rn
drop table #test
original row - should return 1 (when checking for "pair"/consecutive numbers
n1 n2 n3 n4 n5
=======================
31 27 28 36 12
the result i get with the above query:
StartSeqNo EndSeqNo
1 12 12
2 27 27
3 28 28
4 31 31
5 36 36
help :-) !
ok, i got it. this query returns a value of 1 for the above stated row
select COUNT(*) as pairs
from (
SELECT StartSeqNo=MIN(n1), EndSeqNo=MAX(n1)
FROM (
SELECT n1, rn=n1-ROW_NUMBER() OVER (ORDER BY n1)
from (
select n1 from myTable where Id = 8
union all select n2 from myTable where Id = 8
union all select n3 from myTable where Id = 8
union all select n4 from myTable where Id = 8
union all select n5 from myTable where Id = 8
) t
) x
GROUP BY rn
) z
where StartSeqNo+1 = EndSeqNo

Conditional column value, Select

I got 2 tables "Records" and "Char". With 1 -> N relation
I need to make a select, with a subquery/join where the value to present on the join column is a fixed string like "Multiple Chars" or the content Char.char_val
Let me illustrate:
Records:
R_ID | Name Char: C_ID | R_ID | Char_Val
1 A 1 3 c1
2 B 2 1 c2
3 C 3 1 c3
4 2 c3
Expected Result:
R_ID | Name | Char_Val
1 A Multiple Records
2 B c3
3 C c1
I guess my query would be something like:
Select r.R_ID, r.Name, (conditional select) Char_Val
From Records r, Char c
where r.R_ID = c.R_ID
Suggestions for the (conditional select)?
You can use a case statement and aggregation to get a fixed string:
case when count(c.c_id) > 1 then 'Multiple Records' else max(c.char_val) end
and you need to group by r_id and name:
select r.r_id, r.name,
case when count(c.c_id) > 1 then 'Multiple Records'
else max(c.char_val) end as char_val
from records r
join char c on r.r_id = c.r_id
group by r.r_id, r.name
order by r.r_id;
I've also switched to use ANSI joins instead of the old syntax (as #Thorsten suggested).
This is a demo using CTE to generate your data, giving them slightly different names because char is a reserved word:
with t_records (r_id, name) as (
select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 3, 'C' from dual
),
t_char (c_id, r_id, char_val) as (
select 1, 3, 'c1' from dual
union all select 2, 1, 'c2' from dual
union all select 3, 1, 'c3' from dual
union all select 4, 2, 'c3' from dual
)
select r.r_id, r.name,
case when count(c.c_id) > 1 then 'Multiple Records'
else max(c.char_val) end as char_val
from t_records r
join t_char c on r.r_id = c.r_id
group by r.r_id, r.name
order by r.r_id;
R_ID N CHAR_VAL
---------- - ----------------
1 A Multiple Records
2 B c3
3 C c1
Group by r_id. Either MIN = MAX or you want 'Multiple Records':
select r_id, r.name, c.char_vals
from
(
select
r_id,
case when min(char_val) = max(char_val) then min(char_val) else 'Multiple Records' end
as char_vals
from char
group by r_id
) c
join records r using(r_id)
order by r_id;
Following query gives the result (with Char_val separated by comma) you expected:
Select r.R_ID, r.Name, listagg(c.char_val,',') WITHIN GROUP(ORDER BY c.char_val) AS Char_Val
From Records r, Char c
where r.R_ID = c.R_ID
GROUP BY r.R_ID, r.Name

Sql query to print values starting from column A till column B

New to SQL so looking for help
I'm trying to write a query which would print values starting from column A till the column B excluding the value present in column 'ANS' of second table.
Like here are the two tables X and Y
Table1
A FROM TO
a 6 9
b 3 6
c 0 3
d 2 3
Table2
A ANS
a 7
b 5
c 1
And I want the output as
A ANS
a 6
a 8
a 9
b 3
b 4
b 6
c 0
c 2
c 3
d 2
d 3
I've tried to write something like this but it doesn't work
WITH y(n) AS
(SELECT 1 AS n
FROM dual
UNION ALL
SELECT n + 1 AS n
FROM y, table1 T
WHERE n <= T.TO AND n>= T.FROM )
SELECT * FROM y;
Which prints 5000+ rows (that's why I am not attaching output)
Thanks in advance
After you get all the numbers between from and to with a recursive cte, left join on the generated table and get only those numbers which don't exist in table2 using not exists.
--Get the maximum value of `to` column and generate all numbers between 0 and that value
WITH maxto(maxt) as (SELECT MAX(TO) FROM TABLE1)
,y(n) AS
(SELECT 0 AS n FROM dual
UNION ALL
SELECT n + 1 AS n FROM y WHERE n < (SELECT maxt FROM maxto))
SELECT * FROM
(SELECT t1.a, y.n
FROM y
LEFT JOIN table1 t1 on y.n between t1.from and t1.to
WHERE t1.a IS NOT NULL) x
WHERE NOT EXISTS (SELECT 1 FROM table2 WHERE x.a = a and x.n = ans)
ORDER BY 1,2
Sample demo
WITH y(n) AS
(SELECT level - 1 FROM dual connect by level <= select max(TO- FROM) +2 from table1)
SELECT t1.a, t1.from + y.n FROM table1 t1
JOIN y on 1 = 1
left JOIN table2 on y.n + t1.FROM = t2.ANS and t2.a = t1.a
where y.n < t1.TO-t1.FROM
and t2.ANS is null;
You can use a "hierarchical query" and a MINUS operation and avoid joins altogether. MINUS is easy to understand if you are somewhat familiar with set theory. Generating numbers using hierarchical queries is somewhat unnatural (and may only be available in Oracle, I don't know any other db products), but it is used very often and it works very fast.
I changed the column names to from_n and to_n; I don't remember if "from" and/or "to" are reserved words in Oracle, but why take the risk.
with
table1 ( a, from_n, to_n ) as (
select 'a', 6, 9 from dual union all
select 'b', 3, 6 from dual union all
select 'c', 0, 3 from dual union all
select 'd', 2, 3 from dual
),
table2 ( a, ans ) as (
select 'a', 7 from dual union all
select 'b', 5 from dual union all
select 'c', 1 from dual
)
-- everything above this point is for testing only and can be removed
-- solution (SQL query) begins below
select a, from_n + level - 1 as ans
from table1
connect by level <= 1 + to_n - from_n
and prior a = a
and prior sys_guid() is not null
minus
select a, ans
from table2
;
Output:
A ANS
- ----------
a 6
a 8
a 9
b 3
b 4
b 6
c 0
c 2
c 3
d 2
d 3
11 rows selected