Check succeeding records for special constraint - sql

A mathematical rule must apply to all records with the same name identifier:
x(n) = x(n-1) + y(n-1) where n are the elements sorted by x.
A special case: if y=0, the following value of x does not need to stick to this rule. As an example:
Name X Y
a 0 1
a 1 2
a 2 5 <----- this is invalid because 1 + 2 != 2
b 0 1
b 1 0
b 14 3 <----- this is okay because the preceding element had y = 0
b 16 1 <----- this is invalid because 14 + 3 != 16
The task is to filter the invalid elements.
Without the special case of y=0 I came up with this:
SELECT * FROM TABLE EXCEPT
SELECT NAME, X, Y FROM (
SELECT * FROM TABLE JOIN SELECT NAME AS N, X AS XX, Y AS YY
ON NAME = N WHERE X = 0 OR XX+YY = X)
Does anyone have any suggestion how to handle Y=0?

I have found a solution to your problem. I made an assumption that your table is named 'a'
Here is the code:
SELECT name,
x,
y
FROM
(SELECT a1.name,
a1.x,
a1.y,
(SELECT a3.x + a3.y
FROM a AS a3
WHERE a3.name=a1.name
AND a3.y<>0
AND a3.x =
(SELECT max(a2.x)
FROM a AS a2
WHERE a2.name=a1.name
AND a2.x<a1.x)) AS s1
FROM a AS a1)
WHERE s1 IS NULL
OR x=s1
and the result is:
a 0 1
a 1 2
b 0 1
b 1 0
b 14 3

Related

Count the number of distinct variables in a column for each Code in SQL

I am trying to return the number of Y's N's and R's for each Code in redshift SQL.
Table:
Code Allowed?
A Y
A Y
A N
A Y
A R
A Y
A N
A R
B Y
B N
B Y
Desired Output:
Code Count_Y Count_N Count_R
A 4 2 2
B 2 1 0
You can use conditional aggregation:
select code, sum( (allowed = 'Y')::int ) as num_y,
sum( (allowed = 'N')::int ) as num_n,
sum( (allowed = 'R')::int ) as num_r
from t
group by code;

T/SQL - Trying to convert col to rows

DDL/DML http://www.sqlfiddle.com/#!6/2d69c/1
Desired Output:
Country_ State_ FamilyTag ChildTag Name HouseHold IsReachable Rank_
US CA Family A Child A Ch A1 1 1 1
US CA Family B Child A Ch A1 1 1 2
US CA Family C Child A Ch A1 1 1 3
US CA Family B Child B Ch B1 3 1 1
US CA Family A Child B Ch B1 3 1 2
US CA Family C Child B Ch B1 3 1 3
US CA Family C Child C Ch C1 2 1 1
US CA Family A Child C Ch C1 2 1 3
US CA Family B Child C Ch C1 2 1 2
"Child[n]Tag" field data should go in "ChildTag" field.
"Child[n]Name" field data should go in "Name" field.
"ChildFamilyTag[n]" field data should go in "FamilyTag" field.
"ChildFamilyRank[n]" field data should go in "Rank_" field.
"Child[n]HouseHold" field data should go in "Household" field.
"Child[n]IsReachable" field data should go in "IsReachable" field.
Getting NULLs instead of above desired output...please help
Note:
# of "ChildFamily%" columns can vary (right now, it has 3 sets...but could be 2,3, and etc.
Also, my example shows only one record, there could be more. :)
Thank you
p.s. if you need me to post code here, kindly let me know.
Your query is pretty much along the way there. But you are missing conditions in the case statements. Adding new conditions seems pointless, when some arithmetic on N solves the problem:
SELECT *
FROM (SELECT Country_
, State_
, (CASE WHEN N = 1 THEN 1
WHEN N = 2 THEN 2
WHEN N = 3 THEN 3
WHEN N = 4 THEN 4
WHEN N = 5 THEN 5
WHEN N = 6 THEN 6
WHEN N = 7 THEN 7
WHEN N = 8 THEN 8
WHEN N = 9 THEN 9 END) AS ParentNo
, (CASE WHEN N = 1 THEN [Child1FamilyTag1]
WHEN N = 2 THEN [Child1FamilyTag2]
WHEN N = 3 THEN [Child1FamilyTag3]
WHEN N = 4 THEN [Child2FamilyTag1]
WHEN N = 5 THEN [Child2FamilyTag2]
WHEN N = 6 THEN [Child2FamilyTag3]
WHEN N = 7 THEN [Child3FamilyTag1]
WHEN N = 8 THEN [Child3FamilyTag2]
WHEN N = 9 THEN [Child3FamilyTag3] END) AS FamilyTag
, (CASE WHEN (N + 2) / 3 = 1 THEN [Child1Tag]
WHEN (N + 2) / 3 = 2 THEN [Child2Tag]
WHEN (N + 2) / 3 = 3 THEN [Child3Tag] END) AS ChildTag
, (CASE WHEN (N + 2) / 3 = 1 THEN [Child1Name]
WHEN (N + 2) / 3 = 2 THEN [Child2Name]
WHEN (N + 2) / 3 = 3 THEN [Child3Name] END) AS Name
, (CASE WHEN (N + 2) / 3 = 1 THEN [Child1HouseHold]
WHEN (N + 2) / 3 = 2 THEN [Child2HouseHold]
WHEN (N + 2) / 3 = 3 THEN [Child3HouseHold] END) AS HouseHold
, (CASE WHEN (N + 2) / 3 = 1 THEN [Child1IsReachable]
WHEN (N + 2) / 3 = 2 THEN [Child2IsReachable]
WHEN (N + 2) / 3 = 3 THEN [Child3IsReachable] END) AS IsReachable
, (CASE WHEN (N % 3) = 1 THEN [Child1FamilyRankA]
WHEN (N % 3) = 2 THEN [Child1FamilyRankB]
WHEN (N % 3) = 0 THEN [Child1FamilyRankC]
END) AS Rank
FROM Temp TI CROSS JOIN
(SELECT 1 AS N
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9) N
) T;
Here is the SQL Fiddle.
EDIT (in response to comment):
Your original query is
, (CASE WHEN N = 1 THEN [Child1FamilyRankA]
WHEN N = 2 THEN [Child1FamilyRankB]
WHEN N = 3 THEN [Child1FamilyRankC]
WHEN N = 4 THEN [Child2FamilyRankA]
WHEN N = 2 THEN [Child2FamilyRankB]
WHEN N = 3 THEN [Child2FamilyRankC]
WHEN N = 7 THEN [Child3FamilyRankA]
WHEN N = 2 THEN [Child3FamilyRankB]
WHEN N = 3 THEN [Child3FamilyRankC] END) AS Rank_
I don't know why the values are arranged as they are for this rank_, but you can keep using them. I think I replaced them with something else.

Full path traversed for each ID with cycle Oracle

I have an input data which consists of ID,prev,current and next node (not sorted).
I have to find a path between the first and last page for each ID which covers all the nodes traversed.
for eg : if my input data is like :
first column is ID, second column is prev_node, third column is current node, fourth column is next node.
Prev_node will be empty for starting value and next node will be empty for last value
input
id prev current next
1 a b c
1 a e f
1 a b g
1 a b o
1 b c d
1 b g h
1 b o p
1 c d a
1 c b g
1 d a e
1 e f e
1 e f f
1 f e f
1 f f f
1 f f a
1 f a b
1 g h i
1 h i j
1 h j i
1 i j i
1 i i k
1 i k l
1 j i i
1 k l m
1 l m n
1 l n a
1 m n a
1 n a b
1 o p q
1 p q r
1 q r s
1 r s t
1 s t u
1 t u v
1 u v w
1 v w x
1 w x
1 a b
output should be the path of current node like -
ID current
1 a
1 b
1 c
1 d
1 a
1 e
1 f
1 e
1 f
1 f
1 f
1 a
1 b
1 b
1 g
1 h
1 i
1 j
1 j
1 i
1 i
1 k
1 l
1 m
1 n
1 n
1 a
1 b
1 o
1 p
1 q
1 r
1 s
1 t
1 u
1 v
1 w
1 x
There will be many IDs with similar data here i have shown only one ID(1). Also here i have used alphabets which will actually be 200-500 character long string. I tried the SQL approach with little modification, it works fine if an ID has 100 or below rows but gives string concatenation error for more rows (even after converting the long strings to number). Can anyone please suggest a robust procedure based approach to same. I tried some but it doesn't work for more than 300 rows for a ID.
The error that i sometimes encounter with below code is "result of string concatenation is too long"
my code
create or replace procedure pathing
as
type varr is table of varchar(4000);
visit varr;
t number;
--v varchar2(40);
fp varchar2(1000);
np varchar2(1000);
type stype is record(fp varchar2(1000),np varchar2(1000),t number);
type sinput is table of stype;
iarray sinput;
begin
select id
bulk collect into visit
from table_source
group by id
order by count(1) desc;
delete from table_final;
commit;
for k in visit.first .. visit.last loop
delete from table_temp;
commit;
insert into table_temp
select distinct prev_pg, page_id, next_pg, visit(k)
from table_source
where visit_id = visit(k)
order by prev_pg desc;
commit;
insert into table_final
WITH t_n AS (
SELECT prev_pg, page_id, next_pg, rownum n FROM table_temp
),
t_br AS (
SELECT
prev_pg,
page_id,
'<' || listagg(n, '|<') within GROUP(ORDER BY n) || '|' br,
COUNT(0) cnt
FROM
t_n
GROUP BY
prev_pg, page_id
),
t_mp AS (
SELECT
'|' || listagg(list) within GROUP(ORDER BY NULL) list
FROM (
SELECT REPLACE(br, '<') list FROM t_br WHERE cnt > 1
)
),
t_path(step, page_id, next_pg, used) AS (
SELECT 1, page_id, next_pg, ''
FROM t_n
WHERE prev_pg is null
UNION ALL
SELECT
step + 1,
t_br.page_id,
t_n.next_pg,
CASE
WHEN instr(list, '|' || n || '|') = 0
THEN used
ELSE used || n || '|'
END
FROM
t_mp,
t_path
JOIN t_br
ON next_pg = t_br.page_id AND t_path.page_id = prev_pg
JOIN t_n
ON n = regexp_substr(br, '^(<(' || used || '0)\|)*(<(\d+))?', 1, 1, '', 4)
) cycle step
SET is_cycle TO 'Y' DEFAULT 'N'
SELECT
page_id,
next_pg,
step,
visit(k)
FROM t_path
ORDER BY 1;
commit;
end loop;
end;
Explaining my example further more:- I want full path journey of each ID , in the example i have taken ID 1 as example. For ID 1 we have a set of current, previous and next value. So we need to find the path using these values . For example for id 1 the path starts with 'a' because prev column is empty. then we see the next value of a is b i.e current is a and next is b so we search in all the rows of id 1 for prev value as a and current value as b , at the point we find the same we take the next value of the row and repeat the process. For example here prev a ,current b and next is c so we again search for prev b and current c and so on until we get the full path until we encounter next as null as that would be the last
The solution through Hierarchical query clauses seems to be tricky, but there should be a solution, still as an alternative, use your existing PL/SQL code but change the VARCHAR field to CLOB to avoid "result of string concatenation is too long".
you may use the following query in your procedure to accomplish your goal
select id,current into v_your_output_collection from nodes
where id = :vId
start with prev_node is null
connect by
NOCYCLE
prior next_node= current and prior current = prev_node
that works in Oracle 10g and up

PLSQL or SSRS, How to select having all values in a group?

I have a table like this.
ID NAME VALUE
______________
1 A X
2 A Y
3 A Z
4 B X
5 B Y
6 C X
7 C Z
8 D Z
9 E X
And the query:
SELECT * FROM TABLE1 T WHERE T.VALUE IN (X,Z)
This query gives me
ID NAME VALUE
______________
1 A X
3 A Z
4 B X
6 C X
7 C Z
8 D Z
9 E X
But i want to see all values of names which have all params. So, only A and C have both X and Z values, and my desired result is:
ID NAME VALUE
______________
1 A X
2 A Y
3 A Z
6 C X
7 C Z
How can I get the desired result? No matter with sql or with reporting service. Maybe "GROUP BY ..... HAVING" clause will help, but I'm not sure.
By the way I dont know how many params will be in the list.
I realy appreciate any help.
The standard approach would be something like
SELECT id, name, value
FROM table1 a
WHERE name IN (SELECT name
FROM table1 b
WHERE b.value in (x,y)
GROUP BY name
HAVING COUNT(distinct value) = 2)
That would require that you determine how many values are in the list so that you can use a 2 in the HAVING clause if there are 2 elements, a 5 if there are 5 elements, etc. You could also use analytic functions
SELECT id, name, value
FROM (SELECT id,
name,
value,
count(distinct value) over (partition by name) cnt
FROM table1 t1
WHERE t1.value in (x,y))
WHERE cnt = 2
I prefer to structure these "sets within sets" of queries as an aggregatino. I find this is the most flexible approach:
select t.*
from t
where t.name in (select name
from t
group by name
having sum(case when value = 'X' then 1 else 0 end) > 0 and
sum9case when value = 'Y' then 1 else 0 end) > 0
)
The subquery for the in finds all names that have at least one X value and one Y value. Using the same logic, it is easy to adjust for other conditions (X and Y and Z,; X and Y but not Z and so on). The outer query just returns all the rows instead of the names.

how to combine Y or N or null values

still not found the solution described in update 2
thx for help
ill try to explain my issue with my poor english. hope someone can solve my problem.
i got the following table
A B
1 Y
2 null
3 Y
what result i want?
in dependency of the rank in column A i want to combine column B.
the result in that example is ... no result
the reason is because there is a null in rank 2 and the next and the last rank (=3) has a value (=Y).
next example
A B
1 Y
2 null
3 null
result i want is
A B
1 Y
because the way after is free... means 2 and the last 3 has null
another example
A B
1 null
2 N
3 null
again no result is what i want in this case. because first =1 has null value.
i try now to conclude ... if n(e.g. 2) of column B has value Y or N then the elements bevor (in this 1) must have the value Y or N.
thank you very much. i tried different technics without any success...
UPDATE 1
thank you fast comment
some example dates with expected result
example 1
A B
1 Y
2 N
3 null
4 null
expected result
A B
2 N
example 2
A B
1 N
2 Y
3 N
4 null
expected result
A B
3 N
example 3
A B
1 null
2 Y
3 Y
4 null
expected result
no result
example 4
A B
1 Y
2 Y
3 null
4 Y
expected result
no result
UPDATE 2
forget the basic case
A B
1 Y
2 N
3 Y
expected result
A B
3 N
Establish the highest value of A where B is Y or N, and the lowest value of A where B is null. Provided the first value is lower than the second value you have a valid result set.
select yt.A
, yt.B
from
( select max(case when B is not null then A else null end) as max_b_yn
, min(case when B is null then A else null end) as min_b_null
from your_table ) t1
, your_table yt
where ( t1.min_b_null is null
or t1.max_b_yn < t1.min_b_null )
and yt.A = t1.max_b_yn
/
I think you want the last row before the first NULL. If this is correct, then the following gets what you want:
select t.*
from t join
(select min(A) as NullA from t where B is NULL) t2
on t.A = t2.NullA - 1
Ah, I see. To get the last row with an "N":
select t.*
from t
where t.A in (select max(A) as MaxA
from t join
(select min(A) as NullA from t where B is NULL) t2
on t.A < t2.NullA
where t.B = 'N'
)
OK, I think I have what you need. It grabs the last row that has a value in column B as long as there isn't a row with a NULL that precedes it.
select MAX(A), B
from table_a
where B IS NOT NULL
and table_a.A > (
select MIN(A)
FROM table_a
WHERE B IS NULL
)
Group by B
select top 1 A, B
from #Temp
where B is not null
order by A desc
or
select top 1 A, B
from #Temp
where B = 'N'
order by A desc