Can SQL interleave data based on a range limit? - sql

Consider the following data
Table 1
Key Value
--- -----
A 1
B 2
C 3
D 4
E 5
F 6
G 7
H 8
I 9
J 10
Table 2
Q MaxValue
- --------
X 3
Y 6
Z 10
I'm trying to create a join that matches table 1 with table 2 when the values of table 1 are less than or equal to MaxValue in table 2, but only where they are greater than the prior MaxValue, like so
Result
Key Value Q MaxValue
--- ----- - --------
A 1 X 3
B 2 X 3
C 3 X 3
D 4 Y 6
E 5 Y 6
F 6 Y 6
G 7 Z 10
H 8 Z 10
I 9 Z 10
J 10 Z 10
Here you see that while A-F all meet the criteria of being less than or equal to 6 (Y's MaxValue), I want A-C to be matched only to X since they already match X's criteria of being less than 3, X being the 'prior max value'.
So can this be achieved in SQL?

This isn't very pretty but it should work for you:
select Z.[Key], Z.Value, T2.Q, Z.MaxValue from
(
select Y.[Key], Y.Value, Min(MaxValue) as MaxValue from
(
select T1.*, T2.MaxValue from Table1 T1 cross join Table2 T2
) Y
where Y.Value <= Y.MaxValue
group by Y.[Key], Y.Value
) Z
inner join Table2 T2 on Z.MaxValue = T2.MaxValue
select T1.Value, T2.MaxValue from Table1 T1 cross join Table2 T2 gets all possible combinations of Value and MaxValue.
Then I'm grouping it based on Value and Key and including the condition where Y.Value <= Y.MaxValue to exclude combinations where there are overlapping MaxValues for the same Value.
Finally, I'm getting the original Q column from Table2.

Hopefully help
select [Key],
[Value],
(SELECT [Q] FROM [TB2] T2 WHERE [MaxValue] IN (SELECT MIN(MaxValue) FROM [Table1] T2 WHERE T1.[Value]<= T2.[MaxValue])) AS Q,
(SELECT MIN(MaxValue) FROM [TB2] T2 WHERE T1.[Value]<= T2.[MaxValue]) AS MaxValue
FROM [Table2] T1

Related

How to find records where at least a single record among multiple records having same column values matches with other table but one or more aren't?

I have tables
Table_A
Rec_Id
Cust_Id
Act_Cp
1
10
x
2
11
y
2
12
z
3
13
x
3
14
y
3
15
z
4
16
z
Table_B
sf_id
sf_detail
x
good
y
bad
I want to find the records where at least one of the record of a rec_id is present in table_B but one or more aren't.
the condition is act_cp = sf_id
Somehow my expected output will be:
rec_id
cust_id
act_cp
2
12
z
3
15
z
In the above output , I would not need the row
rec_id
cust_id
act_cp
4
16
z
because there is only a single record with rec_id =4
Also,it would be great,if joins are not used since I don't want any data from table_b just need to check. So , the query can use exists, not exists statement
SELECT *
FROM table_a t1
WHERE EXISTS (SELECT SF_ID FROM table_b t2 WHERE t1.act_cp = t2.sf_id)
I tried doing:
SELECT *
FROM Table_A AS t1
WHERE Rec_Id IN
(SELECT Rec_Id FROM Table_B GROUP BY Rec_Id HAVING COUNT(*) > 1)
AND NOT EXISTS
(SELECT Sf_Id FROM Table_B AS t2 WHERE t1.Act_Cp = t2.Sf_Id)
But this will work only for the above data.
If table_A is changed to the below, the query will not work.
Rec_Id
Cust_Id
Act_Cp
1
10
x
2
11
y
2
12
z
3
13
x
3
14
y
3
15
z
4
16
z
5
17
z
5
18
z
5
19
z
I would like to make adjustments that at least a single record from multiple rec_id should match so the output would be similar to the previous one.
rec_id
cust_id
act_cp
2
12
z
3
15
z
What you might need is an outer join along with a window function to count the occurence of the matching whenever grouped by Rec_Id column such as
WITH ab AS
(
SELECT a.*, b.*,
SUM( CASE WHEN sf_id IS NOT NULL THEN 1 ELSE 0 END )
OVER ( PARTITION BY Rec_Id ) AS cnt
FROM a
LEFT JOIN b
ON act_cp = sf_id
)
SELECT Rec_Id,Cust_Id,Act_Cp
FROM ab
WHERE sf_id IS NULL
AND cnt > 0
Demo on MySQL DB
What I understand is that you need to get records that act_cp is not equal to "x" and "y" but "z" only. So remove any rec_id's that have x-y in any row.
First, I would pick rec_id's that include x and y's.
SELECT rec_id, cust_id
FROM table_a
WHERE act_cp IN (SELECT sf_id FROM table_b)
This will return:
Rec_Id
Cust_Id
Act_Cp
1
10
x
2
11
y
2
12
z
3
13
x
3
14
y
3
15
z
Now time to get the remaining row that you requested
SELECT rec_id, cust_id, act_cp
FROM table_a
WHERE rec_id NOT IN
(SELECT rec_id
FROM table_a
WHERE act_cp IN (SELECT sf_id FROM table_b));
I assume this is a long way but you get the results. The less expensive method is to use left joins - I know you stated "no joins" but it might be good for future reference
SELECT a.rec_id, a.cust_id, a.act_cp
FROM table_a a
LEFT JOIN table_b b
ON a.act_cp = b.sf_id
WHERE act_cp IS NULL;
So with this, you still won't return any data from table b and get your requested data.

If there is null in the result of join then take left value [duplicate]

This question already has answers here:
sql replace null value with value with value in other column
(2 answers)
SQL Server : IF null Replace with other column data
(1 answer)
Closed 1 year ago.
My issue is
I have a table1 with data
Num Value
----
1 B
2 С
3 D
4 F
5 G
And another one table2
Num Value
----
1 K
2 Z
6 D
10 F
15 G
In the result I have
Num Value1 Value2
----
1 B K
2 С Z
3 D null
4 F null
5 G null
But how should I join tables to have default value instead of null, for instance the same as it is given to the left
1 B K
2 С Z
3 D D
4 F F
5 G G
What should I write in SQL?
I initially try like
SELECT t1.Num, t1.Value
FROM table1 t1
RIGHT JOIN table2 t2 ON t1.Num = t2.Num
I think you just want coalesce():
select t1.num, t1.value as value1,
coalesce(t2.value, t1.value) as value2
from table1 t1 left join
table2 t2
on t1.num = t2.num;

assign a new value based on combinations

I have these two tables
the first one has id's and a categorical variable 'code'
table1
id code
1 1 F
2 1 B
3 1 J
4 2 D
5 2 B
6 2 F
7 2 G
8 2 C
9 2 D
10 3 G
11 3 G
12 3 G
13 4 B
14 4 F
15 4 C
16 4 D
17 5 C
18 5 A
19 5 G
20 5 D
and table2
has some combinations of the categorical variable 'code' which are assigned a new category 'code3'
code1 code2 code_3
1 C B O
2 B A K
3 A C L
4 E B N
5 A D J
6 D B L
id's in table1 come with multiple codes, the combinations of those codes result in new codes found on table2.
how to I assign the id's in table1 the values in table2 code3 based on the combinations they have?
desired output
would be something like
id code
1 1 F
2 1 B
3 1 J
5 2 L -- added, while a B and D removed
6 2 F
7 2 G
8 2 C
...
You can get the list of new codes to add by doing a self-join and then joining to table2 to find matches:
select t1.id, t2.code3
from table1 t1 join
table1 tt1
on t1.id = tt1.id and
t1.code < t2.code join
table2 t2
on t2.code1 = t1.code and
t2.code2 = tt1.code;
SELECT id, code, NVL (code3, code)
FROM (SELECT id,
code,
hh,
rr,
gg,
code3
FROM ( SELECT id,
code,
hh,
code || hh rr
FROM --here rr is used as foreign key which refer gg ,which can used as primary key of table2
(SELECT id,
code,
LEAD (code, 1, code)
OVER (PARTITION BY id ORDER BY ROWNUM)
hh
FROM table1)
ORDER BY code, hh) e, --hh gives the code of next row of each code of table1
( SELECT code1 || code2 gg, code3
FROM table2
ORDER BY code1, code2) b
WHERE e.rr = b.gg(+))
ORDER BY id; --here left outer join is used to get desired output
-- ORDER BY code,hh and ORDER BY code1,code2 are used to make sure that SUM(D+B)=L AND SUM(B+D)=L

"Cluster" Code Help in SQL

I am relative newcomer to SQL, but have gained many useful ideas through the site. Now I'm stuck on a piece of code that seems simple enough, but for some reason I can't wrap my head around it.
I am trying to create a third column (Column Z) based off of the first two columns below:
Column X Column Y
-------------------
1 a
1 b
1 c
2 a
2 d
2 e
2 f
4 b
5 i
5 c
3 g
3 h
6 j
6 k
6 l
What i need to have happen in Column Z:
For each individual value found in Column Y, note the value of Column X
Likewise, for each individual value in Column X, note the value of Column Y
Then, cluster (RANK/ROW_NUMBER?) these into groups seen below:
Column X Column Y Column Z
-----------------------------
1 a 1
1 b 1
1 c 1
2 a 1
2 d 1
2 e 1
2 f 1
4 b 1
5 i 1
5 c 1
3 g 2
3 h 2
6 j 3
6 k 3
6 l 3
I hope I've been clear enough without over-complicating things. My head has been spinning all morning. Let me know if anyone needs any more info.
Greatly appreciated in advance!
I have faced exactly this problem for some analyses in the past. The only way I could get it to work is by doing a loop, that incrementally adds in the information.
The loop assigns the minimum "x" value within each group as the group id. By your rules, this is guaranteed to be unique. It starts by assigning the current x value to z. It then finds the minimum z along the x and y dimensions. It repeats this process until no records change.
Given your data, the following is an outline of how to do it:
update t set z = x
while 1=1
begin
with toupdate as (
select t.*,
min(z) over (partition by x) as idx,
min(z) over (partition by y) as idy from t
)
update toupdate
set z = (case when idx < idy then idx else idy end)
where z > idx or z > idy;
if (##ROWCOUNT = 0) break;
end;
;with a as
(
select z, dense_rank() over (order by z) newZ from t
)
update a set z = newZ
Maybe not the best way, but it works
SQLFiddle http://sqlfiddle.com/#!3/99532/1
;WITH cte AS (
SELECT *, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS row_nb
FROM #t
)
, c2 AS (
SELECT e1.*
,CASE WHEN EXISTS(SELECT * FROM cte e2 WHERE e1.Y = e2.Y and e2.row_nb < e1.row_nb) THEN 1 ELSE 0 END as ex
FROM cte e1
)
, c3 AS (
SELECT X,1 - SIGN(SUM(ex)) as ex,MAX(row_nb) as max_row_nb
FROM c2
GROUP BY X
)
SELECT
cte.X,cte.Y
,(SELECT SUM(cc3.ex) FROM c3 cc3 where cc3.max_row_nb<= c3.max_row_nb) AS Z
FROM cte
INNER JOIN c3
ON c3.X = cte.X
ORDER BY cte.row_nb
declare #t table (x tinyint, y char(1), z tinyint)
insert #t (x,y) values(1,'a'),(1,'b'),(1,'c'),(2,'a'),(2,'d'),(2,'e'),(2,'c'),
(2,'f'),(4,'b'),(5,'i'),(5,'c'),(3,'g'),(3,'h'),(6,'j'),(6,'k'),(6,'l'),(7,'v')
;with a as
(
select x,parent from
(
select x, min(x) over (partition by y) parent from #t
) a
where x > parent
), b as
(
select x, parent from a
union all
select a.x, b.parent
from a join b on a.parent = b.x
), c as
(
select x, min(parent) parent
from b
group by x
), d as
(
select t.x,t.y, t.z,
dense_rank() over (order by coalesce(c.parent, t.x)) calculatedZ
from #t t
left join c on t.x = c.x
)
select x,y,calculatedZ as z from d
-- if you want to update instead of selecting, replace last line with:
-- update d set z = newz
-- select x,y,z from #t
option (maxrecursion 0)
Result:
x y z
1 a 1
1 b 1
1 c 1
2 a 1
2 d 1
2 e 1
2 c 1
2 f 1
4 b 1
5 i 1
5 c 1
3 g 2
3 h 2
6 j 3
6 k 3
6 l 3
8 j 3
7 v 4

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.