Select Rows having values matching in 2 other columns - sql

Table
Id || IdFrom || IdTo
--------------------------
1 || null || 2
2 || 1 || null
3 || null || 5
4 || null || 6
5 || 3 || 9
6 || 4 || 7
7 || 6 || null
8 || null || null
9 || 5 || 10
10 || 9 || null
947 || null || 949
949 || 947 || 952
950 || null || 951
951 || 950 || 952
952 || 951 || null
Need to get all rows or just specifically the Ids that are found between all 3 columns when specifying a given Id. So a SELECT (all ids found in the IdFrom or IdTo and Those IdFrom's or IdTo's are in other IdFrom's or IdTo's)
Results when searching for Id 1 would give results of Ids 1 and 2
Results when searching for Id 2 would give results of Ids 1 and 2
Results when searching for Id 3,5,9, or 10 would give results of Ids 3,5,9, and 10
Results when searching for Id 4,6, or 7 would give results of Ids 4,6,and 7
My current search is an iteration getting IdFrom and IdTo for Id, putting those found Id's into a tmp table and iterating back again searching for matches until no more distinct Ids are found. It works but is extremely ugly and takes longer then probably could...
Came across a query that can get all rows that have matching but not specifying for a particular id
DECLARE #SearchForId int = 1
SELECT
t1.ID,t1.IdFROM,t1.IdTO
FROM SomeTable t1
WHERE
(
EXISTS(SELECT Id FROM SomeTable tblFROM WHERE tblFROM.IdFROM = t1.Id) OR
EXISTS(SELECT Id FROM SomeTable tblTO WHERE IdTO.IDTRANSFEREDTO = t1.Id)
)
AND Id = 1 <<-- this part just gives that id obvious but without it, it gets everything in the entire table
EDIT: added new ids (947-952). The previous selected solution did provide ids 947 and 949 but missing 950,951,952. Tried adding another couple cte's like the previous solution to give all ids 947,949,950,951,952 but only giving 947 and 949. How to get all 5. That solution was much quicker by almost by 25x. Would like to keep it and get remainder id's

You need to do it using two recursive common table expressions.
declare #id int
set #id = 2
;WITH CTE1
AS(
SELECT c.*
FROM tbl c
WHERE c.Id = #id
UNION ALL
SELECT p.*
FROM CTE1 cte1_alias
INNER JOIN tbl p
ON p.IdFrom = cte1_alias.Id
),
CTE2
AS(
SELECT c.*
FROM tbl c
WHERE c.Id = #id
UNION ALL
SELECT p.*
FROM CTE2 cte2_alias
INNER JOIN tbl p
ON p.IdTo = cte2_alias.Id
)
SELECT Id FROM CTE1
Union
SELECT Id FROM CTE2

As I understand ORing the columns to be equal to the given value would be enough:
Declare #prm int = 1
Select Id
From SomeTable
Where Id=#prm Or IdFrom=#prm Or IdTo=#prm
or, for a different flavour, using in among columns
Declare #prm int = 1
Select Id
From SomeTable
Where #prm in (Id, IdFrom, IdTo)

Related

Query select with next id order

I have a table with ID and NextID like this:
MainID || NextID
1 || 2
2 || 3
3 || 5
4 || 6
5 || 4
6 || ...
... || ...
what I want to achieve is select data into like this
MainID || NextID
1 || 2
2 || 3
3 || 5
5 || 4
4 || 6
6 || ...
... || ...
what i've tried is simple query like :
SELECT * FROM 'table' ORDER BY NextID
but of course it didn't meet my needs,
I have an idea to create a temp table and insert with loop but takes too much time to complete :
WHILE #NextID IS NOT NULL
BEGIN
INSERT INTO 'table'(MainID, NextID)
SELECT MainID, NextId
FROM 'table' WHERE MainID=#NextID
END
Can anyone help me?
Thanks
Recursive cte will return rows in the order of nodes visited
with t as (
select f.*, right('00000000'+cast(f.mainId as varchar(max)),9) path
from yourtable f
where MainID=1
union all
select f.*, path + '->' + right('00000000'+cast(f.mainId as varchar(max)),9)
from t
join yourtable f on t.NextID = f.MainID
)
select *
from t
order by path
db<>fiddle
where MainId=1 is an arbitrary start. You may wish also start with
where not exists (select 1 from yourtable f2 where f2.Nextid = f.MainId)
Edit
Added explicit order by
For this particular case you may use right join with some ordering
select t2.*
from some_table t1
right join some_table t2
on t1. main_id = t2.next_id
order by case when t2.next_id is null then 9999999 else t2.main_id + t2.next_id end;
the 999999 in the "order by" part is to place last line (6, null) to the end of the output.
Good luck with adopting the query to your real data

How to union a hardcoded row after each grouped result

After every group / row i want to insert a hardcoded dummy row with a bunch of 'xxxx' to act a separator.
I would like to use oracle sql to do this query. i can execute it using a loop but i don't want to use plsql.
As the others suggest, it is best to do it on the front end.
However, if you have a burning need to be done as a query, here is how.
Here I did not use the rownum function as you have already done. I assume, your data is returned by a query, and you can replace my table with your query.
I made few more assumptions, as you have data with row numbers in it.
[I am not sure what do you mean by not PL/SQL]
Select Case When MOD(rownm, 2) = 0 then ' '
Else to_char((rownm + 1) / 2) End as rownm,
name, total, column1
From
(
select (rownm * 2 - 1) rownm,name, to_char(total) total ,column1 from t
union
SELECT (rownm * 2) rownm,'XXX' name, 'XXX' total, 'The row act .... ' column1 FROM t
) Q
Order by Q.rownm;
and here is the fiddle
Since you're already grouping the data, it might be easier to use GROUPING SETS instead of a UNION.
Grouping sets let you group by multiple sets of columns, including the same set twice to duplicate rows. Then the GROUP_ID function can be used to determine when the fake values should be used. This code will be a bit smaller than a UNION approach, and should be faster since it doesn't need to reference the table multiple times.
select
case when group_id() = 0 then name else '' end name,
case when group_id() = 0 then sum(some_value) else null end total,
case when group_id() = 1 then 'this rows...' else '' end column1
from
(
select 'jack' name, 22 some_value from dual union all
select 'jack' name, 1 some_value from dual union all
select 'john' name, 44 some_value from dual union all
select 'john' name, 1 some_value from dual union all
select 'harry' name, 1 some_value from dual union all
select 'harry' name, 1 some_value from dual
) raw_data
group by grouping sets (name, name)
order by raw_data.name, group_id();
You can use row generator technique (using CONNECT BY) and then use CASE..WHEN as follows:
SQL> SELECT CASE WHEN L.LVL = 1 THEN T.ROWNM END AS ROWNM,
2 CASE WHEN L.LVL = 1 THEN T.NAME
3 ELSE 'XXX' END AS NAME,
4 CASE WHEN L.LVL = 1 THEN TO_CHAR(T.TOTAL)
5 ELSE 'XXX' END AS TOTAL,
6 CASE WHEN L.LVL = 1 THEN T.COLUMN1
7 ELSE 'This row act as separator..' END AS COLUMN1
8 FROM T CROSS JOIN (
9 SELECT LEVEL AS LVL FROM DUAL CONNECT BY LEVEL <= 2
10 ) L ORDER BY T.ROWNM, L.LVL;
ROWNM NAME TOTAL COLUMN1
---------- ---------- ----- ---------------------------
1 Jack 23
XXX XXX This row act as separator..
2 John 45
XXX XXX This row act as separator..
3 harry 2
XXX XXX This row act as separator..
4 roy 45
XXX XXX This row act as separator..
5 Jacob 26
XXX XXX This row act as separator..
10 rows selected.
SQL>

SQL query group by and select the maximum absolute value

"Table1" structure is as shown below:
source table table1
Player_NAME || Player_NUMBER || Client_name || Client_country || Player_country|| Rating
GERALD || A1234 || BENFIELD || IND || IND || 76
GERALD ||A6578 || ROTFIELD || USA || USA || 64
KUMAR || P1234 || LFV || ARG || ARG || -24
KUMAR || P5678 ||JEURASIN || ARG || TUR ||-32
KUMAR || P0101 ||ARGENIA ||ARG ||POL ||-16
ANDREW ||R1234 || GENMAD || GER || GER || 23
I need to select the records from above table “Table1” and copy them to “Table2”.
I need to select the player record from table1 which satisfy the below conditions :
If a player has multiple client_names or multiple client_country, then select the record which has the maximum value of rating . If it is negavie, then take the absolute value of that value. i.e if the rating is -10 and -34, then take the absolute value which is greatest. i. e by taking absolute value it is 10,34 and 34 is greatest one.
For ex: Kumar has 3 diff client names or 3 diff client_country ,so for kumar the record with rating 32 should be selected ,after taking the absolute value of it.
Below is the expected output:
Player_NAME || Player_NUMBER ||Client_name || Client_country ||Player_country|| Rating
GERALD || A1234 || BENFIELD|| IND|| IND|| 76
KUMAR || P5678 || JEURASIN ||ARG ||TUR || -32
ANDREW || R1234 || GENMAD ||GER ||GER || 23
destination table-'table2'
You can try something like this:
INSERT INTO Table2
(
Player_Name,
Player_Number,
Cliet_Name,
Client_country,
Player_country,
Rating
)
SELECT
Player_Name,
Player_Number,
Cliet_Name,
Client_country,
Player_country,
MAX(ABS(Rating)) OVER (PARTITION BY player_Name ORDER BY Cliet_Name,
Client_country) as Rating
FROM
table1
If your DBMS supports Analytical Function you can utilize ROW_NUMBER:
select ... -- all columns but rn
from
(
select ... -- all columns
,row_number()
over (partition by player_name
order by abs(Rating) desc as rn
from table1
) as dt
where rn = 1;
Otherwise use a Correlated Subquery:
select *
from table1 as t1
where abs(rating) =
( select max(abs(rating))
from table1 as t2
where t1.player_name = t2.player_name
)
If you got multiple rows with the same max(abs(rating)) #1. will select one of them randomly, but #2 will select all.
I guess, this query will work:
select
max(abs(Rating))
from Table1
group by Player_NAME
To insert data into Table2, you can do it like so:
INSERT INTO Table2 (
Player_Name,
Player_Number,
Cliet_Name,
Client_country,
Player_country,
Rating
)
SELECT
t1.Player_Name,
t1.Player_Number,
t1.Cliet_Name,
t1.Client_country,
t1.Player_country,
t1.Rating
FROM Table1 t1
INNER JOIN (
SELECT
Player_NAME,
MAX(ABS(Rating)) as Rating
FROM Table1
GROUP BY Player_NAME
) t2 ON t2.Player_NAME = t1.Player_NAME AND ABS(t1.Rating) = t2.Rating

Oracle XE, count and display different combinations of rows based on one column

need help with a complicated query. This is an extract from my table:
USERID SERVICE
1 A
1 B
2 A
3 A
3 B
4 A
4 C
5 A
6 A
7 A
7 B
Ok, I would like the query to return and display all possible combinations that exist in my table with their respective counts based on the SERVICE column. For example first user has A and B service, this is one combination which occurred once. Next user has only service A, this is one more combination which occurred once. Third user has service A and B, this has happened once already and the count for this combination is 2 now, etc. So my output based on this particular input would be a table like this:
A AB AC ABC B BC
3 3 1 0 0 0
So to clarify a bit more, if there are 3 services, then there is 3! possible combinations; 3x2x1=6 and they are A, B, C, AB, AC, BC and ABC. And my table should contain count of users which have these combination of services assigned to them.
I have tried building a matrix using this query and then getting all counts using the CUBE function:
select service_A, service_B, service_C from
(select USERID,
max(case when SERVICE =A then 1 else null end) service_A,
max(case when SERVICE =B then 1 else null end) service_B,
max(case when SERVICE =C then 1 else null end) service_C
from SOME_TABLE)
group by CUBE(service_A, service_B,service_C);
But I don't get the count of all combinations. I need only combinations which happened, so counts 0 are not necessary but it is ok to display them. Thanks.
Don't output it as dynamic columns (it is difficult to do without using PL/SQL and dynamic SQL) but output it as rows instead (if you have a front-end then it can usually translate rows to columns much easier than oracle can):
Oracle Setup:
CREATE TABLE some_table ( USERID, SERVICE ) AS
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 1, 'B' FROM DUAL UNION ALL
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 3, 'A' FROM DUAL UNION ALL
SELECT 3, 'B' FROM DUAL UNION ALL
SELECT 4, 'A' FROM DUAL UNION ALL
SELECT 4, 'C' FROM DUAL UNION ALL
SELECT 5, 'A' FROM DUAL UNION ALL
SELECT 6, 'A' FROM DUAL UNION ALL
SELECT 7, 'A' FROM DUAL UNION ALL
SELECT 7, 'B' FROM DUAL;
Query:
SELECT service,
COUNT( userid ) AS num_users
FROM (
SELECT userid,
LISTAGG( service ) WITHIN GROUP ( ORDER BY service ) AS service
FROM some_table
GROUP BY userid
)
GROUP BY service;
Output:
SERVICE NUM_USERS
------- ----------
AC 1
A 3
AB 3
PL/SQL for dynamic columns:
VARIABLE cur REFCURSOR;
DECLARE
TYPE string_table IS TABLE OF VARCHAR2(4000);
TYPE int_table IS TABLE OF INT;
t_services string_table;
t_counts int_table;
p_sql CLOB;
BEGIN
SELECT service,
COUNT( userid ) AS num_users
BULK COLLECT INTO t_services, t_counts
FROM (
SELECT userid,
CAST( LISTAGG( service ) WITHIN GROUP ( ORDER BY service ) AS VARCHAR2(2) ) AS service
FROM some_table
GROUP BY userid
)
GROUP BY service;
p_sql := EMPTY_CLOB() || 'SELECT ';
p_sql := p_sql || t_counts(1) || ' AS "' || t_services(1) || '"';
FOR i IN 2 .. t_services.COUNT LOOP
p_sql := p_sql || ', ' || t_counts(i) || ' AS "' || t_services(i) || '"';
END LOOP;
p_sql := p_sql || ' FROM DUAL';
OPEN :cur FOR p_sql;
END;
/
PRINT cur;
Output:
AC A AB
--- --- ---
1 3 3

How do you find a missing number in a table field starting from a parameter and incrementing sequentially?

Let's say I have an sql server table:
NumberTaken CompanyName
2 Fred 3 Fred 4 Fred 6 Fred 7 Fred 8 Fred 11 Fred
I need an efficient way to pass in a parameter [StartingNumber] and to count from [StartingNumber] sequentially until I find a number that is missing.
For example notice that 1, 5, 9 and 10 are missing from the table.
If I supplied the parameter [StartingNumber] = 1, it would check to see if 1 exists, if it does it would check to see if 2 exists and so on and so forth so 1 would be returned here.
If [StartNumber] = 6 the function would return 9.
In c# pseudo code it would basically be:
int ctr = [StartingNumber]
while([SELECT NumberTaken FROM tblNumbers Where NumberTaken = ctr] != null)
ctr++;
return ctr;
The problem with that code is that is seems really inefficient if there are thousands of numbers in the table. Also, I can write it in c# code or in a stored procedure whichever is more efficient.
Thanks for the help
A solution using JOIN:
select min(r1.NumberTaken) + 1
from MyTable r1
left outer join MyTable r2 on r2.NumberTaken = r1.NumberTaken + 1
where r1.NumberTaken >= 1 --your starting number
and r2.NumberTaken is null
I called my table Blank, and used the following:
declare #StartOffset int = 2
; With Missing as (
select #StartOffset as N where not exists(select * from Blank where ID = #StartOffset)
), Sequence as (
select #StartOffset as N from Blank where ID = #StartOffset
union all
select b.ID from Blank b inner join Sequence s on b.ID = s.N + 1
)
select COALESCE((select N from Missing),(select MAX(N)+1 from Sequence))
You basically have two cases - either your starting value is missing (so the Missing CTE will contain one row), or it's present, so you count forwards using a recursive CTE (Sequence), and take the max from that and add 1
Edit from comment. Yes, create another CTE at the top that has your filter criteria, then use that in the rest of the query:
declare #StartOffset int = 2
; With BlankFilters as (
select ID from Blank where hasEntered <> 1
), Missing as (
select #StartOffset as N where not exists(select * from BlankFilters where ID = #StartOffset)
), Sequence as (
select #StartOffset as N from BlankFilters where ID = #StartOffset
union all
select b.ID from BlankFilters b inner join Sequence s on b.ID = s.N + 1
)
select COALESCE((select N from Missing),(select MAX(N)+1 from Sequence))
this may now return a row that does exist in the table, but hasEntered=1
Tables:
create table Blank (
ID int not null,
Name varchar(20) not null
)
insert into Blank(ID,Name)
select 2 ,'Fred' union all
select 3 ,'Fred' union all
select 4 ,'Fred' union all
select 6 ,'Fred' union all
select 7 ,'Fred' union all
select 8 ,'Fred' union all
select 11 ,'Fred'
go
Try the set based approach - should be faster
select min(t1.NumberTaken)+1 as "min_missing" from t t1
where not exists (select 1 from t t2
where t1.NumberTaken = t2.NumberTaken+1)
and t1.NumberTaken > #StartingNumber
This is Sybase syntax, so massage for SQL server consumption if needed.
Create a temp table with all numbers from StartingValue to EndValue and LEFT OUTER JOIN to your data table.