Loop in the same table on ids found with an id - sql

I have a simple table:
Main_ID
Sub_ID1
Sub_ID2
ID1
ID2
ID3
ID2
ID4
ID5
ID3
ID7
ID12
Where a product in Main_ID is made with products in Sub_ID1 and Sub_ID2.
I would like, for a given id, have all the products (sub_ids) necessary for its realisation.
For example: For the id ID1, I will have ID2, ID3, ID4, ID5, ID7, ID12.
(ID1 is made with ID2 and ID3, but ID2 is made with ID4 and ID5, and ID3 is made with ID7 and ID12, etc.)
I've tried some left join, but I miss something I guess.
SELECT t1.Main_ID
FROM my_table t1
INNER JOIN my_table t2 ON t2.Sub_ID1 t1.Main_ID OR t2.Sub_ID2 t1.Main_ID
WHERE t1.Main_ID LIKE 'ID1'

You can do this in a reliable way using a recursive query.
Although you need to do a preprocessing step first: "Sub_ID1" and "Sub_ID2" should be found within one single Sub_ID column, using the UNION ALL operator. We're calling the output of this step as "crafted table".
Then you can apply recursion defining these two steps:
base step, select the "main_id" record you want to inspect
recursive step, joining base-step table with the crafted table on matching <"Main_ID", "SubID">
WITH RECURSIVE one_subid AS (
SELECT Main_ID, Sub_ID1 AS Sub_ID FROM tab
UNION ALL
SELECT Main_ID, Sub_ID2 AS Sub_ID FROM tab
), cte_rec AS (
SELECT Main_ID, Sub_ID FROM one_subid WHERE Main_ID = 'ID1'
UNION ALL
SELECT t2.Sub_ID, t1.Sub_ID
FROM one_subid t1
INNER JOIN cte_rec t2 ON t1.Main_ID = t2.Sub_ID
)
SELECT Sub_ID FROM cte_rec
Output for 'Main_ID = ID1':
sub_id
ID2
ID3
ID4
ID7
ID5
ID12
Check the demo here.

Related

How to get the count of the modal value in PostgreSQL?

I have calculated the modal value of a column in a JOIN.
mode() WITHIN GROUP (ORDER BY col) AS modal_col
I would also like the frequency of the modal value. i.e. how often does this value appear?
I have tried to simply nest this in the count function, but postgres does not allow this.
count(mode() WITHIN GROUP (ORDER BY col))
ERROR: aggregate function calls cannot be nested
I have also tried:
row_number() OVER (PARTITION BY id ORDER BY count(*)
I have also tried the RANK() function, but these simply give me the row numbers
I would like a simple count of the occurrence of modal value.
Input
id
col
id1
a
id1
a
id1
b
id2
a
id2
a
id3
a
id3
b
id3
c
id3
c
id3
c
id3
c
Output
id
col_mode
mode_count
id1
a
2
id2
a
3
id3
c
4
EDIT
SELECT DISTINCT ON (t1.id)
t1.id,
mode() WITHIN GROUP (ORDER BY t2.col) AS modal_col,
count(*) OVER(PARTITION BY t1.id, t2.col) AS mode_count
FROM schema.foo t2
JOIN schema.bar t1
ON t2.id2 = t1.id2
ORDER BY t1.id, count(*) OVER(PARTITION BY t1.id, t2.col) DESC
;
Thanks to Danny for the pointer.
I tried the above and postgres errors and requires that I group by both t1.id AND t2.col. Do I need to create an intermediary scratch table as I do not to want to group by both columns, just t1.id?
select distinct on (id)
id
,col as col_mode
,count(*) over(partition by id, col) as mode_count
from t
order by id, count(*) over(partition by id, col) desc
id
col_mode
mode_count
id1
a
2
id2
a
2
id3
c
4
Here's another answer using mode() within group
select id
,mode() WITHIN GROUP (order by col) as col_mode
,max(mode_count) as mode_count
from
(
select *
,count(*) over(partition by id, col) as mode_count
from t
) t
group by id
Fiddle

SQL: exclude rows that are duplicates according to some column arrangement

In my problem, I have these rows e.g.:
id1 |name1| id2 | name2| c
1 10994,Apple,22265,Banana,103
2 22265,Banana,10994,Apple,103
3 20945,Coconut,20391,Date,101
4 20391,Date,20945,Coconut,101
They show pair-wise combinations of ids and names, together with another column c.
I consider row 1+2, and 3+4 as duplicates, considering the pairings of ids and names. Rows 1+2, or 3+4 show basically the same information.
I had no luck removing the duplicates with grouping, because id1 + id2, or name1 + name2 respectively are distinct columns.
Can I add something to my SQL query that removes these 'duplicates', so that only rows 1+3 are output?
One approach here would to be aggregate by the c column and then use a least/greatest trick to tease apart the duplicates. Here is a general solution which should work on any version of SQL:
SELECT DISTINCT c, CASE WHEN id1 < id2 THEN id1 ELSE id2 END AS id1,
CASE WHEN id1 < id2 THEN id2 ELSE id1 END AS id2,
CASE WHEN name1 < name2 THEN name1 ELSE name2 END AS name1,
CASE WHEN name1 < name2 THEN name2 ELSE name1 END AS name2
FROM yourTable;
Here is a working demo.
Note that on databases which have a LEAST and GREATEST function, the above can be simplified to:
SELECT DISTINCT c, LEAST(id1, id2) AS id1,
GREATEST(id1, id2) AS id2,
LEAST(name1, name2) AS name1,
GREATEST(name1, name2) AS name2
FROM yourTable;
You could try using not exists. You have not provided any existing query but something like:
select *
from Tablename t
where not exists (
select * from Tablename t2
where t2.id2 = t.id1
);

How can I select a different column from each row?

I have a table which has three columns with three records. How can I select first value of the first column, second value of the second column, third value of the third column?
table
============
test_tab
id1 id2 id2
=== === ====
100 400 700
200 500 800
300 600 900
Required output like :
100 500 900
How can I achieve this by using Oracle SQL or PL/SQL?
First of all, How you would identify which row is the first and which one is second?
Oracle does not guarantee the order of the records, it must be ordered using order by clause explicitly else it will be, we can say random order (The default query output)
With your test data and result, You can use the following query:
Note: I am considering the third column as ID3 and ordering the rows using ID1
SELECT
MAX(CASE RN
WHEN 1 THEN ID1
END) AS ID1,
MAX(CASE RN
WHEN 2 THEN ID2
END) AS ID2,
MAX(CASE RN
WHEN 3 THEN ID3
END) AS ID3
FROM
(
SELECT
ID1,
ID2,
ID3,
ROW_NUMBER() OVER(
ORDER BY
ID1
) RN
FROM
TEST_TAB
);
Cheers!!
You will need a means of establishing what "first" means.
In Oracle, here is one way to address the example from your question:
create table test_tab(
id1 integer,
id2 integer,
id3 integer
);
insert into test_tab values(100,400,700);
insert into test_tab values(200,500,800);
insert into test_tab values(300,600,900);
commit;
select sum(decode(pos, 1, id1, null)) id1,
sum(decode(pos, 2, id2, null)) id2,
sum(decode(pos, 3, id3, null)) id3
from(
-- this subquery produces a rank column for the whole dataset
-- with an explicit order
select id1, id2, id3, rank() over (order by id1, id2, id3) pos from TEST_TAB
);
In this implementation, the subquery is used to establish an ordering of the rows, adding a new pos column based on the rank() function.
The sum(decode(pos, 3, id3, null)) construct is an Oracle idiom for picking one specific row (row 3 in this case) while ignoring the others.
Basically, for your three rows, the decode will result in NULL for any row but the one with the specified number, so the expression for id3 will only have a non-null value for the third row, hence the sum over the group will be equal to id3 in row 3.
There are many ways to do it, this is just one, and you will likely need to make some adjustments to this implementation for it to work properly in your real code.

reducing oracle table entity

I have a table like this:
I want a query that make my table like this:
You can use aggregation:
select max(id1) as id1, max(id2) as id2, max(id3) as id3
from . . .
I would prefer to use SUM() here, because you might have negative numbers in a given column:
select sum(id1) as id1, sum(id2) as id2, sum(id3) as id3
from yourTable
If we use MAX(), then it would fail, if for example there were a -2 in one of the columns. In that case, the max would return zero.
I'm not sure that solution is optimal for executing, but:
select (select id1 from table where id1 <> 0) id1,
(select id2 from table where id2 <> 0) id2,
(select id3 from table where id3 <> 0) id3
from dual;

sql server 2008 order by issue

I am trying to order by columns from another table, heres an example of the error i am getting
DECLARE #UserID1 varchar(31), #UserID2 varchar(31), #UserID3 varchar(31), #UserID4 varchar(31)
--getting data from table
SELECT #UserID1 = ID1, #UserID2 = ID2, #UserID3 = ID3, #UserID4 = ID4
FROM USER_INFO
WHERE idref = #id
--select some data from another table with the values we got from USER_INFO table
SELECT age, name, location
FROM USER_DATA
WHERE NameId in(#UserID1, #UserID2, #UserID3, #UserID4)
ORDER BY #UserID1, #UserID2, #UserID3, #UserID4 -- this errors
The returned error is
The SELECT item identified by the ORDER BY number 1 contains a
variable as part of the expression identifying a column position.
Variables are only allowed when ordering by an expression referencing
a column name.
I am trying to order by ID1, ID2, ID3, ID4 FROM USER_INFO in ascending order
You can do it in this way:
SELECT age, name, location
FROM USER_DATA
WHERE NameId in(#UserID1, #UserID2, #UserID3, #UserID4)
ORDER BY CASE NameId
WHEN #UserID1 THEN 1
WHEN #UserID2 THEN 2
WHEN #UserID3 THEN 3
WHEN #UserID4 THEN 4
END
This will put records with NameId matching #UserID1 first, then records with NameId matching #UserID2, then #UserID3, and so on.
SELECT age, name, location
FROM USER_DATA UD
INNER JOIN USER_INFO UI
ON (UD.NameId=UI.ID1 OR UD.NameId=UI.ID2 OR UD.NameId=UI.ID3 OR UD.NameId=UI.ID4)
WHERE UI.idref = #id
AND NameId in(ID1, ID2, ID3, ID4)
ORDER BY ID1, ID2, ID3, ID4
OR
SELECT age, name, location
FROM USER_DATA UD
, USER_INFO UI
WHERE UI.idref = #id
AND NameId in(ID1, ID2, ID3, ID4)
ORDER BY ID1, ID2, ID3, ID4