Query to find combinations of accounts sql - sql

I am looking for how to form a query, where I seek to find that the ordering accounts are interacting with the same beneficiary accounts 3 or more times. As I describe below.
Examples:
Account A sends account 1,2,and 3.
Account B sends account 1,2 and 3.
Account C sends account 1,2 and 3.
This is the table called TBL_ACCOUNTS
ordering account
beneficiary account
A
1
B
1
C
1
A
2
B
2
C
2
A
3
B
3
C
3
H
1
K
23
Z
329
W
3
I want to find all those accounts that meet this condition, that the ordering accounts are interacting with the same beneficiary accounts 3 or more times. The result you would expect to get is.
ordering account
beneficiary account
A
1
A
2
A
3
B
1
B
2
B
3
C
1
C
2
C
3
I hope you can guide me which way to go, because I'm a bit lost.

You can create a collection data type:
CREATE TYPE int_list IS TABLE OF INT;
and then you can use:
WITH accounts (ordering_account, beneficiary_account, accounts) AS (
SELECT t.*,
CAST(
COLLECT(beneficiary_account) OVER (PARTITION BY ordering_account)
AS int_list
)
FROM TBL_ACCOUNTS t
)
SELECT ordering_account,
beneficiary_account
FROM accounts a
WHERE EXISTS(
SELECT 1
FROM accounts x
WHERE a.ordering_account <> x.ordering_account
AND CARDINALITY(a.accounts MULTISET INTERSECT x.accounts) >= 3
-- Remove the next line if you want to return all accounts and not just the matched accounts
AND a.beneficiary_account = x.beneficiary_account
);
Which, for the sample data:
CREATE TABLE TBL_ACCOUNTS (ordering_account, beneficiary_account) AS
SELECT 'A', 1 FROM DUAL UNION ALL
SELECT 'B', 1 FROM DUAL UNION ALL
SELECT 'C', 1 FROM DUAL UNION ALL
SELECT 'A', 2 FROM DUAL UNION ALL
SELECT 'B', 2 FROM DUAL UNION ALL
SELECT 'C', 2 FROM DUAL UNION ALL
SELECT 'A', 3 FROM DUAL UNION ALL
SELECT 'B', 3 FROM DUAL UNION ALL
SELECT 'C', 3 FROM DUAL UNION ALL
SELECT 'C', 4 FROM DUAL UNION ALL
SELECT 'H', 1 FROM DUAL UNION ALL
SELECT 'K', 23 FROM DUAL UNION ALL
SELECT 'Z', 329 FROM DUAL UNION ALL
SELECT 'W', 3 FROM DUAL;
Outputs:
ORDERING_ACCOUNT
BENEFICIARY_ACCOUNT
A
1
A
3
A
2
B
1
B
3
B
2
C
1
C
2
C
3
If you want to do it without a collection then:
SELECT ordering_account,
beneficiary_account
FROM TBL_ACCOUNTS a
WHERE EXISTS(
SELECT 1
FROM TBL_ACCOUNTS x
WHERE a.ordering_account <> x.ordering_account
AND a.beneficiary_account = x.beneficiary_account
AND EXISTS(
SELECT 1
FROM TBL_ACCOUNTS l
INNER JOIN TBL_ACCOUNTS r
ON (l.beneficiary_account = r.beneficiary_account)
WHERE l.ordering_account = a.ordering_account
AND r.ordering_account = x.ordering_account
HAVING COUNT(*) >= 3
)
);
or:
SELECT ordering_account,
beneficiary_account
FROM TBL_ACCOUNTS a
WHERE EXISTS(
SELECT 1
FROM TBL_ACCOUNTS l
INNER JOIN TBL_ACCOUNTS r
ON ( l.beneficiary_account = r.beneficiary_account
AND l.ordering_account <> r.ordering_account )
WHERE l.ordering_account = a.ordering_account
GROUP BY r.ordering_account
HAVING COUNT(*) >= 3
AND COUNT(
CASE WHEN r.beneficiary_account = a.beneficiary_account THEN 1 END
) > 0
);
db<>fiddle here

Maybe something like this:
select ordering_account, beneficiary
from TBL_ACCOUNTS
group by ordering_account, beneficiary
having count(*) >= 3
order by ordering_account, beneficiary

SELECT T.ordering_account,T.beneficiary_account
FROM TBL_ACCOUNTS T
JOIN
(
SELECT Z.ordering_account
FROM TBL_ACCOUNTS Z
GROUP BY Z.ordering_account
HAVING COUNT(*)>2
)X ON T.ordering_account=X.ordering_account
ORDER BY T.ordering_account,T.beneficiary_account
or
SELECT X.ordering_account,X.beneficiary_account FROM
(
SELECT T.ordering_account,T.beneficiary_account,
COUNT(*)OVER(PARTITION BY T.ordering_account)XCOL
FROM TBL_ACCOUNTS T
)X WHERE X.XCOL=3
ORDER BY X.ordering_account,X.beneficiary_account

Self-join the table on the beneficiary account. Thus you get all ordering account pairs as often as they share the share3 beneficiary accounts. This means you can group by these pairs then and count.
The following query lists all entries of all ordering accounts for which exists another ordering account sharing at least three beneficiary accounts.
with share3 as
(
select a1.ordering_account as acc1, a2.ordering_account as acc2
from tbl_accounts a1
join tbl_accounts a2 on a2.beneficiary_account = a1.beneficiary_account
and a2.ordering_account > a1.ordering_account
group by a1.ordering_account, a2.ordering_account
having count(*) >= 3
)
select *
from tbl_accounts
where exists
(
select null
from share3
where share3.acc1 = tbl_accounts.ordering_account
or share3.acc2 = tbl_accounts.ordering_account
)
order by ordering_account, beneficiary_account;

I'm not sure I follow what you're asking, but it sounds like you simply need to include an ORDER BY clause.
At the end of your query just include
ORDER BY 'ordering account', 'beneficiary account'
The only thing that could change this is if you use different kinds of SQL that don't like single quotes. You may need to use [],"", or ``.

Related

ORACLE get rows with condition value equals something but not equals to anything else

I have rows that look like .
OrderNo OrderStatus SomeOtherColumn
A 1
A 1
A 3
B 1 X
B 1 Y
C 2
C 3
D 2
I want to return all orders that have only one possible value of orderstatus. For e.g Here order B has only order status 1 SO result should be
B 1 X
B 1 Y
Notes:
Rows can be duplicated with same order status. For e.g. B here.
I am interested in the order having a very peculiar status for e.g. 1 here and not having any other status. So if B had a status of 3 at any point of time it is disqualified.
You can use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t.orderno = t2.orderno and t.OrderStatus = t2.OrderStatus
);
If you just want the orders where this is true, you can use group by and having:
select orderno
from t
group by orderno
having min(OrderStatus) = max(OrderStatus);
If you only want a status of 1 then add max(OrderStatus) = 1 to the having clause.
Here is one way to do it. It does not handle the case where the status can be NULL; if that is possible, you will need to explain how you want it handled.
SQL> create table test_data ( orderno, status, othercol ) as (
2 select 'A', 1, null from dual union all
3 select 'A', 1, null from dual union all
4 select 'A', 3, null from dual union all
5 select 'B', 1, 'X' from dual union all
6 select 'B', 1, 'Y' from dual union all
7 select 'C', 2, null from dual union all
8 select 'C', 3, null from dual union all
9 select 'D', 2, null from dual
10 );
Table created.
SQL> variable input_status number
SQL> exec :input_status := 1
PL/SQL procedure successfully completed.
SQL> column orderno format a8
SQL> column othercol format a8
SQL> select orderno, status, othercol
2 from (
3 select t.*, count(distinct status) over (partition by orderno) as cnt
4 from test_data t
5 )
6 where status = :input_status
7 and cnt = 1
8 ;
ORDERNO STATUS OTHERCOL
-------- ---------- --------
B 1 X
B 1 Y
One way to handle NULL status (if that may happen), if in that case the orderno should be rejected (not included in the output), is to define the cnt differently:
count(case when status != :input_status or status is null then 1 end)
over (partition by orderno) as cnt
and in the outer query change the WHERE clause to a single condition,
where cnt = 0
Count distinct OrderStatus partitioned by OrderNo and show only rows where number equals one:
select OrderNo, OrderStatus, SomeOtherColumn
from ( select t.*, count(distinct orderstatus) over (partition by orderno) cnt
from t )
where cnt = 1
SQLFiddle demo
Just wanted to add something to Gordon's answer, using a stats function:
select orderno
from t
group by orderno
having variance(orderstatus) = 0;

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 return data only if ALL necessary columns are present and not NULL

ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
2 Purchase null
2 Return 5
2 Exchange 1
3 Purchase 34
3 Return 4
3 Exchange 2
4 Purchase 12
4 Exchange 2
Above is sample data. What I want to return is:
ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
3 Purchase 34
3 Return 4
3 Exchange 2
So if a field is null in total or the values of Purchase, Return and Exchange are not all present for that ID, ignore that ID completely. How can I go about doing this?
You can use exists. I think you intend:
select t.*
from t
where exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Purchase' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Exchange' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Return' and t2.total is not null
);
There are ways to "simplify" this:
select t.*
from t
where 3 = (select count(distinct t2.type)
from t t2
where t2.id = t.id and
t2.type in ('Purchase', 'Exchange', 'Return') and
t2.total is not null
);
I would write this as a join, without subqueries:
SELECT pur.id, pur.total AS Purchase, exc.total AS Exchange, ret.total AS Return
FROM MyTable as pur
INNER JOIN MyTable AS exc ON exc.id=pur.id AND exc.type='Exchange'
INNER JOIN MyTable AS ret ON ret.id=pur.id AND ret.type='Return'
WHERE pur.type='Purchase'
The inner join means that if any of the three rows with different values are not found for a given id, then no row is included in the result.
Analytic functions are a good way to solve this kind of problems. The base table is read just once, and no joins (explicit or implicit, as in EXISTS conditions or correlated subqueries) are needed.
In the solution below, we count distinct values of 'Purchase', 'Exchange' and 'Return' for each id while ignoring other values (assuming that is indeed the requirement), and separately count total nulls in the total column for each id. Then it becomes a trivial matter to select just the "desired" rows in an outer query.
with
test_data ( id, type, total ) as (
select 1, 'Purchase', 12 from dual union all
select 1, 'Return' , 2 from dual union all
select 1, 'Exchange', 5 from dual union all
select 2, 'Purchase', null from dual union all
select 2, 'Return' , 5 from dual union all
select 2, 'Exchange', 1 from dual union all
select 3, 'Purchase', 34 from dual union all
select 3, 'Return' , 4 from dual union all
select 3, 'Exchange', 2 from dual union all
select 4, 'Purchase', 12 from dual union all
select 4, 'Exchange', 2 from dual
)
-- end of test data; actual solution (SQL query) begins below this line
select id, type, total
from ( select id, type, total,
count( distinct case when type in ('Purchase', 'Return', 'Exchange')
then type end
) over (partition by id) as ct_type,
count( case when total is null then 1 end
) over (partition by id) as ct_total
from test_data
)
where ct_type = 3 and ct_total = 0
;
Output:
ID TYPE TOTAL
-- -------- -----
1 Exchange 5
1 Purchase 12
1 Return 2
3 Exchange 2
3 Purchase 34
3 Return 4
This also should work fine even if new values are added to type column
select * from t where
ID not in(select ID from t where
t.total is null or t.[Type] is null)

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.

Tricky sql to select distinct user

given the following data/tabel:
User|Value
A 1
A 1
B 3
B 1
A 1
A 3
A 3
B 1
B 1
A 1
B 1
B 1
A 3
A 3
B 1
Here I want to pick out those users who have alternated the Value column more than once.
Eg. Here B is not a problem as it changes only once, A on the other hand changes often and I want a sql select that returns A.
I have not found any examples on how to do this! :(
I think this should work:
SELECT DISTINCT u1.UserName
FROM Users u1
INNER JOIN Users u2 ON u1.UserName = u2.UserName
INNER JOIN Users u3 ON u1.UserName = u3.UserName
WHERE u1.Value <> u2.Value
AND u1.UserID < u2.UserID
AND u2.Value <> u3.Value
AND u2.UserID < u3.UserID
Assuming your table is called "Users", of course :)
declare #t table(user_id char(1), val tinyint)
insert into #t
select 'A', 1
union all
select 'A', 1
union all
select 'B', 3
union all
select 'B', 1
union all
select 'A', 1
union all
select 'A', 3
union all
select 'A', 2
union all
select 'A', 1
union all
select 'B', 1
union all
select 'B', 1
union all
select 'A', 1
select user_id, count(distinct val)
from #t
group by user_id
having count(distinct val) > 2