Recursive query in DB2 to get all items in the chain - sql

I have to retrieve all clients linked via loans by giving only one as input. Example I have a table data as
TABLEA
LOAN_ID CLIENT_ID
1 7
1 8
2 7
4 8
4 9
4 10
5 9
5 11
13 2
14 3
If I have given only input as CLIENT_ID=7 then the query has to select all the columns from above table except last two column because client_id 7 has 1,2 LOAN_ID and in 1 the CLIENT_ID 8 has loan_id=4 and in this loan CLIENT_id 9 has again 5 as loan_id.
can we write a sql query for this without stored procedure in DB2 ?

Here is the answer to your question using recursive CTE query:
WITH links AS
( SELECT
loan_id,
client_id as c1,
client_id as c2, 0 as distance
FROM
myTable
-- recursion
UNION ALL
SELECT
t.loan_id,
l.c1 as c1,
tt.client_id as c2,
distance = distance + 1
FROM
links l INNER JOIN
myTable t ON l.c2 = t.client_id
AND l.loan_id != t.loan_id INNER JOIN
myTable tt ON t.loan_id = tt.loan_id
AND t.client_id != tt.client_id
)
SELECT * FROM myTable t
WHERE EXISTS
(SELECT * FROM links
WHERE c2 = t.client_id and c1 = 7);
http://sqlfiddle.com/#!3/8394d/16
I have left distance inside the query to make it easier to understand.

Related

How to generate loop kind of behaviour in SQL query to fetch multiple queries & compare results?

I am unable to generate a looping kind of behaviour in a SQL query.
I am having two tables:
Table A
Id Brand Prod_Id Alt_Prod_Id
1 A 2 5
2 B 3 9
3 C 5 9
Table B
Id Prod_Id Rate
1 2 5
2 3 9
2 5 7
2 9 9
Rate in Table B needs to be looked up for each brands Prod_ID & Alt_Prod_Id & select the least value between 2 found value
The expected result / output is:
Brand Min_Prod_Val
A 5
B 9
C 7
Can this be done in a query?
Thanks!
You could join tableb twice (once for prod_id, another for alt_prod_id), and then select the smallest rate:
select
a.brand,
least(b1.rate, b2.rate) min_prod_val
from tablea a
inner join tableb b1 on b1.prod_id = a.prod_id
inner join tableb b2 on b2.prod_id = a.alt_prod_id
It is unclear which database you are using. If that's SQL Server: it does not support least(), so you need a case expression:
case when b1.rate < b2.rate then b1.rate else b2.rate end min_prod_val
You can use a single join and GROUP BY the brand:
SELECT a.Brand,
MIN( b.rate ) AS min_prod_val
FROM TableA A
INNER JOIN TableB b
ON ( b.prod_id IN ( a.prod_id, a.alt_prod_id ) )
GROUP BY a.Brand
Or you can use a correlated sub-query:
SELECT a.Brand,
(
SELECT MIN( rate )
FROM TableB b
WHERE b.prod_id IN ( a.prod_id, a.alt_prod_id )
) AS min_prod_val
FROM TableA A
db<>fiddle

SQL: How to join two columns in a specific way?

I am working with an Oracle Database and I am new to SQL in general.
I have a table with data and month columns. After filtering the data I have just a few rows left. But I want to get two columns: 1-st column with 12 months listed (1,2,3,4,5,6,7,8,9,10,11,12) and second column with values from original data (if exist) or zeroes.
F.e.: Original data:
MONTH VALUE
9 96
What I want:
MONTH VALUE
1 0
2 0
3 0
4 0
5 0
6 0
7 0
8 0
9 96
10 0
11 0
12 0
I have already tried to use join and union all functions but it didn't work out.
First generate a sequence of 12 months number then use left join
select monthNo, coalesce(Value,0) as value from
(
SELECT 1 MonthNo
FROM dual
CONNECT BY LEVEL <= 12
)A left join originaltable b on A.monthNo=b.month
is this what are you looking for?
WITH tab AS(SELECT LEVEL AS m , null as value FROM DUAL CONNECT BY LEVEL <= 12)
, tab2 AS(SELECT 9 as m, 96 as VALUE FROM DUAL)
select t1.m
,coalesce(t2.value,0) as value
from tab t1
left join tab2 t2 on t1.m = t2.m
order by 1
Bro enjoy...
select months.month ,original_data.VALUE
from original_data
Right JOIN (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) months(month) on
months.month = original_data.MONTH
order by months.month --optional

Calculation of points if there are 2 student with same rank in sql server

Rank_Table
ID Rank
1 1
2 1
3 3
4 3
5 5
Price
No Points
1 10
2 9
3 8
4 7
5 6
Expected Output
ID Rank Points
1 1 9.5
2 1 9.5
3 3 7.5
4 3 7.5
5 5 6
2nd rank is not present so 1st and 2nd points are sum together and distributed among the number of the student
for eg : (10+9) / 2 = 9.5
When I join the 2 table like
select *
from Rank_table a join
Price b
on a.ID = b.No
I am getting the output as
ID Rank No Points
1 1 1 10
2 1 2 9
3 3 3 8
4 3 4 7
5 5 5 6
This seems to be quite a simple requirement, simply using AVG and an OVER clause.
CREATE TABLE [Rank] (ID int, [Rank] int)
CREATE TABLE Price ([No] int, Points int);
GO
INSERT INTO [Rank]
VALUES
(1,1),
(2,1),
(3,3),
(4,3),
(5,5);
INSERT INTO Price
VALUES
(1,10),
(2,9),
(3,8),
(4,7),
(5,6);
GO
SELECT R.ID,
R.[Rank],
AVG(CONVERT(decimal(2,0),P.Points)) OVER (PARTITION BY R.[Rank]) AS Points
FROM [Rank] R
JOIN Price P ON R.ID = P.[No];
GO
DROP TABLE [Rank];
DROP TABLE Price;
You need simple JOIN :
select rn.*,
avg(convert(decimal(10,0), p.Points)) over (partition by rn.rnk) as points
from Rank_Table rn inner join
Price p
on p.id = rn.No;
SELECT *,
AA.pts AS POINTS
FROM rank_table R
INNER JOIN (SELECT rank,
Sum(points) / Count(*) AS PTS
FROM rank_table a
JOIN price b
ON a.id = b.no
GROUP BY rank)AA
ON ( R.rank = AA.rank )
You can calculate AVG at rank level and then join back to Rank_Table like in this working demo
select R.*,T.points from Rank_table R
JOIN
(
select rank, points=avg(cast(points as decimal(10,2)))
from Rank_table a join
Price b
on a.ID = b.No
group by rank
)T
on T.rank=R.rank
Hmmm. You seem to want a non-equijoin based on the "next" rank as well as the rank in each row. Unfortunately, SQL Server 2008 doesn't support lead(), but you can use apply:
select rt.id, rt.rank, avg(p.price * 1.0) as points
from rank_table rt outer apply
(select min(rt2.rank) as next_rank
from rank_table rt2
where rt2.rank > rt.rank
) rt2 left join
price p
on p.no >= rt.rank >= p.no and
(p.no < rt2.next_rank or rt2.next_rank is null)
group by rt.id, rt.rank;

SQL query to group based on sum

I have a simple table with values that I want to chunk/partition into distinct groups based on the sum of those values (up to a certain limit group sum total).
e.g.,. imagine a table like the following:
Key Value
-----------
A 1
B 4
C 2
D 2
E 5
F 1
And I would like to group into sets such that no one grouping's sum will exceed some given value (say, 5).
The result would be something like:
Group Key Value
-------------------
1 A 1
B 4
--------
Total: 5
2 C 2
D 2
--------
Total: 4
3 E 5
--------
Total: 5
4 F 1
--------
Total: 1
Is such a query possible?
While I am inclined to agree with the comments that this is best done outside of SQL, here is some SQL which would seem to do roughly what you're asking:
with mytable AS (
select 'A' AS [Key], 1 AS [Value] UNION ALL
select 'B', 4 UNION ALL
select 'C', 2 UNION ALL
select 'D', 2 UNION ALL
select 'E', 5 UNION ALL
select 'F', 1
)
, Sums AS (
select T1.[Key] AS T1K
, T2.[Key] AS T2K
, (SELECT SUM([Value])
FROM mytable T3
WHERE T3.[Key] <= T2.[Key]
AND T3.[Key] >= T1.[Key]) AS TheSum
from mytable T1
inner join mytable T2
on T2.[Key] >= T1.[Key]
)
select S1.T1K AS StartKey
, S1.T2K AS EndKey
, S1.TheSum
from Sums S1
left join Sums S2
on (S1.T1K >= S2.T1K and S1.T2K <= S2.T2K)
and S2.TheSum > S1.TheSum
and S2.TheSum <= 5
where S1.TheSum <= 5
AND S2.T1K IS NULL
When I ran this code on SQL Server 2008 I got the following results:
StartKey EndKey Sum
A B 5
C D 4
E E 5
F F 1
It should be straightforward to construct the required groups from these results.
If you want to have only two members or less in each set, you can use the following query:
Select
A.[Key] as K1 ,
B.[Key] as K2 ,
isnull(A.value,0) as V1 ,
isnull(B.value,0) as V2 ,
(A.value+B.value)as Total
from Table_1 as A left join Table_1 as B
on A.value+B.value<=5 and A.[Key]<>B.[Key]
For finding sets having more members, you can continue to use joins.

What is the SQL for 'next' and 'previous' in a table?

I have a table of items, each of which has a date associated with it. If I have the date associated with one item, how do I query the database with SQL to get the 'previous' and 'subsequent' items in the table?
It is not possible to simply add (or subtract) a value, as the dates do not have a regular gap between them.
One possible application would be 'previous/next' links in a photo album or blog web application, where the underlying data is in a SQL table.
I think there are two possible cases:
Firstly where each date is unique:
Sample data:
1,3,8,19,67,45
What query (or queries) would give 3 and 19 when supplied 8 as the parameter? (or the rows 3,8,19). Note that there are not always three rows to be returned - at the ends of the sequence one would be missing.
Secondly, if there is a separate unique key to order the elements by, what is the query to return the set 'surrounding' a date? The order expected is by date then key.
Sample data:
(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8
What query for '8' returns the set:
2:3,3:8,4:8,16:8,5:19
or what query generates the table:
key date prev-key next-key
1 1 null 2
2 3 1 3
3 8 2 4
4 8 3 16
5 19 16 10
10 19 5 11
11 67 10 15
15 45 11 null
16 8 4 5
The table order is not important - just the next-key and prev-key fields.
Both TheSoftwareJedi and Cade Roux have solutions that work for the data sets I posted last night. For the second question, both seem to fail for this dataset:
(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8
The order expected is by date then key, so one expected result might be:
2:3,3:8,4:8,16:8,5:19
and another:
key date prev-key next-key
1 1 null 2
2 3 1 3
3 8 2 4
4 8 3 16
5 19 16 10
10 19 5 11
11 67 10 15
15 45 11 null
16 8 4 5
The table order is not important - just the next-key and prev-key fields.
Select max(element) From Data Where Element < 8
Union
Select min(element) From Data Where Element > 8
But generally it is more usefull to think of sql for set oriented operations rather than iterative operation.
Self-joins.
For the table:
/*
CREATE TABLE [dbo].[stackoverflow_203302](
[val] [int] NOT NULL
) ON [PRIMARY]
*/
With parameter #val
SELECT cur.val, MAX(prv.val) AS prv_val, MIN(nxt.val) AS nxt_val
FROM stackoverflow_203302 AS cur
LEFT JOIN stackoverflow_203302 AS prv
ON cur.val > prv.val
LEFT JOIN stackoverflow_203302 AS nxt
ON cur.val < nxt.val
WHERE cur.val = #val
GROUP BY cur.val
You could make this a stored procedure with output parameters or just join this as a correlated subquery to the data you are pulling.
Without the parameter, for your data the result would be:
val prv_val nxt_val
----------- ----------- -----------
1 NULL 3
3 1 8
8 3 19
19 8 45
45 19 67
67 45 NULL
For the modified example, you use this as a correlated subquery:
/*
CREATE TABLE [dbo].[stackoverflow_203302](
[ky] [int] NOT NULL,
[val] [int] NOT NULL,
CONSTRAINT [PK_stackoverflow_203302] PRIMARY KEY CLUSTERED (
[ky] ASC
)
)
*/
SELECT cur.ky AS cur_ky
,cur.val AS cur_val
,prv.ky AS prv_ky
,prv.val AS prv_val
,nxt.ky AS nxt_ky
,nxt.val as nxt_val
FROM (
SELECT cur.ky, MAX(prv.ky) AS prv_ky, MIN(nxt.ky) AS nxt_ky
FROM stackoverflow_203302 AS cur
LEFT JOIN stackoverflow_203302 AS prv
ON cur.ky > prv.ky
LEFT JOIN stackoverflow_203302 AS nxt
ON cur.ky < nxt.ky
GROUP BY cur.ky
) AS ordering
INNER JOIN stackoverflow_203302 as cur
ON cur.ky = ordering.ky
LEFT JOIN stackoverflow_203302 as prv
ON prv.ky = ordering.prv_ky
LEFT JOIN stackoverflow_203302 as nxt
ON nxt.ky = ordering.nxt_ky
With the output as expected:
cur_ky cur_val prv_ky prv_val nxt_ky nxt_val
----------- ----------- ----------- ----------- ----------- -----------
1 1 NULL NULL 2 3
2 3 1 1 3 8
3 8 2 3 4 19
4 19 3 8 5 67
5 67 4 19 6 45
6 45 5 67 NULL NULL
In SQL Server, I prefer to make the subquery a Common table Expression. This makes the code seem more linear, less nested and easier to follow if there are a lot of nestings (also, less repetition is required on some re-joins).
Firstly, this should work (the ORDER BY is important):
select min(a)
from theTable
where a > 8
select max(a)
from theTable
where a < 8
For the second question that I begged you to ask...:
select *
from theTable
where date = 8
union all
select *
from theTable
where key = (select min(key)
from theTable
where key > (select max(key)
from theTable
where date = 8)
)
union all
select *
from theTable
where key = (select max(key)
from theTable
where key < (select min(key)
from theTable
where date = 8)
)
order by key
SELECT 'next' AS direction, MIN(date_field) AS date_key
FROM table_name
WHERE date_field > current_date
GROUP BY 1 -- necessity for group by varies from DBMS to DBMS in this context
UNION
SELECT 'prev' AS direction, MAX(date_field) AS date_key
FROM table_name
WHERE date_field < current_date
GROUP BY 1
ORDER BY 1 DESC;
Produces:
direction date_key
--------- --------
prev 3
next 19
My own attempt at the set solution, based on TheSoftwareJedi.
First question:
select date from test where date = 8
union all
select max(date) from test where date < 8
union all
select min(date) from test where date > 8
order by date;
Second question:
While debugging this, I used the data set:
(key:date) 1:1,2:3,3:8,4:8,5:19,10:19,11:67,15:45,16:8,17:3,18:1
to give this result:
select * from test2 where date = 8
union all
select * from (select * from test2
where date = (select max(date) from test2
where date < 8))
where key = (select max(key) from test2
where date = (select max(date) from test2
where date < 8))
union all
select * from (select * from test2
where date = (select min(date) from test2
where date > 8))
where key = (select min(key) from test2
where date = (select min(date) from test2
where date > 8))
order by date,key;
In both cases the final order by clause is strictly speaking optional.
If your RDBMS supports LAG and LEAD, this is straightforward (Oracle, PostgreSQL, SQL Server 2012)
These allow to choose the row either side of any given row in a single query
Try this...
SELECT TOP 3 * FROM YourTable
WHERE Col >= (SELECT MAX(Col) FROM YourTable b WHERE Col < #Parameter)
ORDER BY Col