SQL - how to return rows containing ALL children rows - sql

Given a parent + reference tables where Reference table is as follows
Ref_ID PARENT_ID
-------------------
1 1
2 1
1 2
3 2
1 3
3 3
4 3
2 4
3 4
I'm trying to return all distinct parent rows where ref_id contains both 2 & 3
The query
SELECT *
FROM Parent
WHERE parent_id in (SELECT parent_id from XRefTable where ref_id in (2, 3) )
returns all parent_id 1, 2, 3, 4
WHEREAS the correct result required is to return parent_id 4 which has BOTH ref_id's 2 & 3, others have EITHER 2 OR 3
Any help is appreciated
FYI - there are 4-7 tables in the query (depending on user selections) so performance is a huge factor
SORRY cannot use stored procedures as it has to work on SQL Server CE too

SELECT parent_id
from XRefTable
where ref_id in ( 2, 3 )
group by PARENT_ID
having count(distinct ref_id) = 2

You could do this:
SELECT
ParentReference.Parent_ID
FROM
ParentReference
INNER JOIN ParentReference B ON ParentReference.Parent_ID = B.Parent_ID AND ParentReference.Ref_ID = 2 AND B.Ref_ID = 3

You are trying to do a set-wise comparison. For this, I strongly recommend the group by and having clauses:
select parent_id
from Reference r
group by parent_id
having sum(case when ref_id = 2 then 1 else 0 end) > 0 and
sum(case when ref_id = 3 then 1 else 0 end) > 0
Each component of the having clause is counting one of the fields. The logic requires that both are present.
The reason that I prefer this approach over others is because you can change the logic, using essentially the same structure.
If you have the list in a comma delimited string, the following will work. Perhaps not "elegant" and "relational", but it works:
set #Ref_ids = "1,2,3,4"
select parent_id
from Reference r
where charindex(','+cast(ref_id as varchar(255))+',', '+#ref_ids+',') > 0
group by parent_id
having count(distinct ref_id) = (len(replace(#ref_ids, ',', '')) - len(#ref_ids))+1
This is doing string manipulation to determine whether the ref_id is in the list. The having clause then counts the number of matches, making sure that it is the same size as the list. This will work, assuming there are no spaces in the list and no blank values.

Related

How add more rows when find string in column Oracle

Would it be possible to add more rows base on Keyword string in SQL ?
table A
PID PromotionName
1 OUT_EC_D10_V500K_FamilyCare_PROCO
2 OUT_EC_D5_V50K_Lunchbox_PROCO
3 OUT_EC_D5_V50K_PROCO
table B
promotion_code itm_name quantity
Lunchbox Item name 1 1
FamilyCare Item name 2 1
FamilyCare Item name 3 1
BUY1FREE6 Item name 4 1
HiSummer Item name 5 1
FamilyCare Item name 6 1
Example:
SELECT * FROM A where pid = '1';
Output of the SQL should be -
PID PromotionName Itm_name quantity
1 OUT_EC_D10_V500K_FamilyCare_PROCO
2 FamilyCare Item name 2 1
3 FamilyCare Item name 3 1
4 FamilyCare Item name 6 1
How to find string with keyword 'FamilyCare' in PromotionName of table A base on promotion_code of table B? If it exist it will add more rows in output
Any help with the SQL?
Here is how you can achieve this:
SELECT PID,PromotionName, '' as Itm_name, NULL as quantity
FROM A
WHERE pid = '1'
UNION
SELECT PID, PROMOTION_NAME, Itm_name, quantity
FROM
(SELECT * FROM A inner join B on a.promotionName LIKE '%'||b.promotion_name||'%')
WHERE pid='1'
You have to update your pid in both the places (before and after UNION).
Notice that tables were joined using LIKE operator with % before and after the word. Hence this joins if a part of a string is present in another column.
db<>fiddle link here
An option would be starting to construct a subquery factoring along with joining tables through a.promotionName LIKE '%'||b.promotion_code||'%' condition while filtering by b.promotion_code = 'FamilyCare', then add another query to combine the result sets by UNION ALL, and then enumerate with an id column by ROW_NUMBER() analytic function such as
WITH ab AS
(
SELECT a.*, b.*
FROM a
JOIN b
ON a.promotionName LIKE '%'||b.promotion_code||'%'
WHERE b.promotion_code = 'FamilyCare'
), ab2 AS
(
SELECT promotion_code, itm_name, quantity
FROM ab
UNION ALL
SELECT DISTINCT promotionName, NULL, NULL
FROM ab
)
SELECT ROW_NUMBER() OVER (ORDER BY itm_name NULLS FIRST) AS pid,
a.*
FROM ab2 a
if there's mismatch for the topmost query, then no row will be returned. eg. that query will check for the existence for the literal you provide
Demo

Select query to reference tables based on table Id or Name

Consider I have a table which list all the tables in database along with the referenced tables. In my case, no foreign key reference are used in the tables. Tables references are maintained as below
TableId ReferedInTableId
1 2
1 3
1 4
2 5
2 6
3 7
4 8
4 9
5 -
6 -
7 10
8 -
9 11
10 -
11 -
In this case, I need a query to find the referenced tables based on the input TableId.
For Eg, for TableId 1, the referencedTableId are 2,3,4. But I need to recurse again like these 2,3,4 are again referred in some tables and I need that list too.
At the end,
If the input is TableId 1, It should return 2,3,4,5,6,7,8,9,10,11
If the input is tableId 4, It should return 8,9,11
If the input is tableId 3, it should return 7,10
Please help me in building a SQL select query.
Assuming you have no cycles in the links, you can use a relatively simple recursive CTE:
with cte as (
select t.tableid, t.referencedtableid, 1 as lev
from t
where t.id = 1
union all
select cte.tableid, t.referencedtableid, lev + 1
from cte join
t
on cte.referencedtableid = t.id
)
select referencedtableid
from cte;

Find next unused ID in table SQL which has non continous ranges

I have two tables like the following:
TABLE1:
=======
somid, tobeupdated
1 , null
2 , null
3 , null
10 , null
TABLE2:
=======
rangeofids
2
3
9
10
11
12
13
I have to update TABLE1.tobeupdated (or found its' should be value) based on the following criteria(s):
if TABLE1.somid NOT exists in TABLE2.rangeofids, then the expected result is: tobeupdated = TABLE1.somid
else find the next available (or unused) TABLE2.rangeofids which is larger then TABLE1.somid
So the expected values are:bu
TABLE1:
=======
somid, tobeupdated
1 , 1
2 , 4
3 , 4
10 , 14
I tried hard, but the simplest solution I came up with is creating a temporary table with a full sequence of ids (from 1 to max(rangeofids)+1) MINUS TABLE2.rangeofids so I can found the MIN(TMPTABLE.id) where TMPTABLE.ID > TABLE1.somid.
But isn't there a better solution (without the temp table)?
Note: I can't create procedures/functions, etc, so it must be standard (Oracle 10) SQL.
This is my try.
First we should decide using only table2 what value should return after finding the value there.
select rangeofids,
candidate,
nvl(candidate,lead(candidate ignore nulls) over (order by rangeofids)) as full_candidate
from (
select rangeofids, case when dist=1 then null else rangeofids+1 end as candidate
from (
select rangeofids,
lead(rangeofids) over (order by rangeofids) - rangeofids as dist
from table2
)
);
After this a merge into table1 with the below select will solve the problem:
select someid, nvl(full_candidate, someid)
from table1 a
left join (
--the above query
) b
on a.someid = b.rangeofids;
See SQLFIDDLE.

How to get second parent with recursive query in Common Table

I am using SQL Server 2008. I have a table like this:
UnitId ParentId UnitName
---------------------------
1 0 FirstUnit
2 1 SecondUnit One
3 1 SecondUnit Two
4 3 B
5 2 C
6 4 D
7 6 E
8 5 F
I want to get second parent of the record. For example:
If I choose unit id that equal to 8, It will bring unit id is equal to 2 to me. It needs to be SecondUnit One. or If I choose unit id that equal to 7, It will bring unit id is equal to 3 to me. It needs to be SecondUnit Two.
How can I write a SQL query this way?
It took me a while, but here it is :)
with tmp as (
select unitId, parentId, unitName, 0 as iteration
from t
where unitId = 7
union all
select parent.unitId, parent.parentId, parent.unitName, child.iteration + 1
from tmp child
join t parent on child.parentId = parent.unitId
where parent.parentId != 0
)
select top 1 unitId, parentId, unitName from tmp
order by iteration desc
Here is also a fiddle to play with.
SELECT t.*, tParent1.UnitId [FirstParent], tParent2.UnitId [SecondParent]
FROM Table t
LEFT JOIN Table tParent1 ON t.ParentId = tParent1.UnitId
LEFT JOIN Table tParent2 ON tParent1.ParentId = tParent2.UnitId
WHERE t.UnitId = <Unit ID search here>
AND NOT tParent2.UnitId IS NULL
Edit: And leave out second part of the WHERE clause if you want results returned even if they don't have a second parent.

Returning several rows from a single query, based on a value of a column

Let's say I have this table:
|Fld | Number|
1 5
2 2
And I want to make a select that retrieves as many Fld as the Number field has:
|Fld |
1
1
1
1
1
2
2
How can I achieve this? I was thinking about making a temporary table and instert data based on the Number, but I was wondering if this could be done with a single Select statement.
PS: I'm new to SQL
You can join with a numbers table:
SELECT Fld
FROM yourtable
JOIN Numbers
ON yourtable.Number <= Numbers.Number
A numbers table is just a table with a list of numbers:
Number
1
2
3
etc...
Not an great solution (since you still query your table twice, but maybe you can work from it)
SELECT t1.fld, t1.number
FROM table t1, (
SELECT ROWNUM number FROM dual
CONNECT BY LEVEL <= (SELECT MAX(number) FROM t1)) t2
WHERE t2.number<=t1.number
It generates maximum amount of rows needed and then filters it by each row.
I don't know if your RDBMS version supports it (although I rather suspect it does), but here is a recursive version:
WITH remaining (fld, times) as (SELECT fld, 1
FROM <table>
UNION ALL
SELECT a.fld, a.times + 1
FROM remaining as a
JOIN <table> as b
ON b.fld = a.fld
AND b.number > a.times)
SELECT fld
FROM remaining
ORDER BY fld
Given your source data table, it outputs this (count included for verification):
fld times
=============
1 1
1 2
1 3
1 4
1 5
2 1
2 2