Oracle get rows that exactly matches the list of values - sql

I have a details table and I want to get the record that exactly matches the list of values in another table.
Here is a scenario:
OrderDetailTable
OrderID ItemID
1 1
1 2
1 3
1 4
2 1
2 2
2 4
3 1
3 2
3 3
4 1
4 2
OrderedTable
ItemID
1
2
Now I want to get the OrderID that has the exact ItemID matches with OrderedTable ItemID. In the above scenario OrderID 1 is valid since ItemID 1,2,3 is exactly matched with OrderedTable ItemID.
I used the join but it did not work. It gave me both OrderID 1,2 . How do I do it any ideas??

Try this:
SELECT OrderID
FROM OrderDetailTable JOIN OrderedTable USING (ItemID)
GROUP BY OrderID
HAVING COUNT(DISTINCT ItemID) = (SELECT COUNT(DISTINCT ItemID) FROM OrderedTable)
The idea, in a nutshell, is as follows:
Count how many OrderDetailTable rows match OrderedTable by ItemID,
and then compare that to the total number of ItemIDs from OrderedTable.
If these two numbers are equal, the given OrderID "contains" all ItemIDs. If the one is smaller than the other, there is at least one ItemID not contained in the given OrderID.
Depending on your primary keys, the DISTINCT may not be necessary (though it doesn't hurt).

try
SELECT * FROM OrderDetailTable WHERE OrderID NOT IN
(
SELECT A.OrderID FROM
(
SELECT
Y.OrderID
, OT.ItemID
, (SELECT Z.ItemID
FROM OrderDetailTable Z
WHERE Z.ItemID = OT.ItemID AND Z.OrderID = Y.OrderID
) I
FROM OrderDetailTable Y, OrderedTable OT
) A
WHERE A.I IS NULL);
EDIT - as per request the better syntax:
SELECT * FROM
OrderDetailTable Z WHERE Z.ORDERID NOT IN
(
SELECT O1 FROM
(SELECT Y.ORDERID O1, YY.ORDERID O2 FROM
OrderDetailTable Y CROSS JOIN OrderedTable OT
LEFT OUTER JOIN OrderDetailTable YY ON
YY.ORDERID = Y.ORDERID AND YY.ITEMID = OT.ITEMID ) ZZ WHERE ZZ.O2 IS NULL);

Related

Snowflake: Repeating rows based on column value

How to repeat rows based on column value in snowflake using sql.
I tried a few methods but not working such as dual and connect by.
I have two columns: Id and Quantity.
For each ID, there are different values of Quantity.
So if you have a count, you can use a generator:
with ten_rows as (
select row_number() over (order by null) as rn
from table(generator(ROWCOUNT=>10))
), data(id, count) as (
select * from values
(1,2),
(2,4)
)
SELECT
d.*
,r.rn
from data as d
join ten_rows as r
on d.count >= r.rn
order by 1,3;
ID
COUNT
RN
1
2
1
1
2
2
2
4
1
2
4
2
2
4
3
2
4
4
Ok let's start by generating some data. We will create 10 rows, with a QTY. The QTY will be randomly chosen as 1 or 2.
Next we want to duplicate the rows with a QTY of 2 and leave the QTY =1 as they are.
Obviously you can change all parameters above to suit your needs - this solution works super fast and in my opinion way better than table generation.
Simply stack SPLIT_TO_TABLE(), REPEAT() with a LATERAL() join and voila.
WITH TEN_ROWS AS (SELECT ROW_NUMBER()OVER(ORDER BY NULL)SOME_ID,UNIFORM(1,2,RANDOM())QTY FROM TABLE(GENERATOR(ROWCOUNT=>10)))
SELECT
TEN_ROWS.*
FROM
TEN_ROWS,LATERAL SPLIT_TO_TABLE(REPEAT('hire me $10/hour',QTY-1),'hire me $10/hour')ALTERNATIVE_APPROACH;

Group parents with same children

EDIT: This is way harder to explain that I though, constatly editing based on comments. Thank you all for taking interest.
I have a table like this
ID Type ParentID
1 ChildTypeA 1
2 ChildTypeB 1
3 ChildTypeC 1
4 ChildTypeD 1
5 ChildTypeA 2
6 ChildTypeB 2
7 ChildTypeC 2
8 ChildTypeA 3
9 ChildTypeB 3
10 ChildTypeC 3
11 ChildTypeD 3
12 ChildTypeA 4
13 ChildTypeB 4
14 ChildTypeC 4
and I want to group parents that have same children - meaning same number of children of same type.
From parent point of view, there is a finite set of possible configurations (max 10).
If any parent has same set of children (by ChildType), I want to group them together (in what I call a configuration).
ChildTypeA-D = ConfigA
ChildTypeA-C = ConfigB
ChildTypeA, B, E, F = ConfigX
etc.
The output I need is parents grouped by Configurations.
Config Group ParentID
ConfigA 1
ConfigA 3
ConfigB 2
ConfigB 4
I have no idea where to even begin.
I named your table t. Please try if this is what you are looking for.
It's show matched and unmatched.
It's looking for parentids with the same number of rows (t1.cnt = t2.cnt) and that all the rows are matched (having COUNT(*) = t1.cnt).
You can try it here
;with t1 as (select parentid, type, id, count(*) over (partition by parentid order by parentid) cnt from t),
t3 as
(
select t1.parentid parentid1, t2.parentid parentid2, count(*) cn, t1.cnt cnt1, t2.cnt cnt2, ROW_NUMBER () over (order by t1.parentid) rn
from t1 join t1 as t2 on t1.type = t2.type and t1.parentid <> t2.parentid and t1.cnt = t2.cnt
group by t1.parentid, t2.parentid, t1.cnt, t2.cnt
having COUNT(*) = t1.cnt
),
notFound as (
select t1.parentid, ROW_NUMBER() over(order by t1.parentid) rn
from t1
where not exists (select 1 from t3 where t1.parentid = t3.parentid1)
group by t1.parentid
)
select 'Config'+char((select min(rn)+64 from t3 as t4 where t3.parentid1 in (t4.parentid1 , t4.parentid2))) config, t3.parentid1
from t3
union all
select 'Config'+char((select max(rn)+64+notFound.rn from t3)) config, notFound.parentid
from notFound
OUTPUT
config parentid1
ConfigA 1
ConfigA 3
ConfigB 2
ConfigB 4
If id 14 was ChildTypeZ then parentid 2 and 4 wouldn't match. This would be the output:
config parentid1
ConfigA 1
ConfigA 3
ConfigC 2
ConfigD 4
I have happen to have similar task. The data I'm working with is a bit bigger scale so I had to find an effective approach to this. Basically I've found 2 working approaches.
One is pure SQL - here's a core query. Basically it gives you smallest ParentID with same collection of children, which you can then use as a group id (you can also enumerate it with row_number). As a small note - I'm using cte here, but in real world I'd suggest to put grouped parents into temporary table and add indexes on the table as well.
;with cte_parents as (
-- You can also use different statistics to narrow the search
select
[ParentID],
count(*) as cnt,
min([Type]) as min_Type,
max([Type]) as max_Type
from Table1
group by
[ParentID]
)
select
h1.ParentID,
k.ParentID as GroupID
from cte_parents as h1
outer apply (
select top 1
h2.[ParentID]
from cte_parents as h2
where
h2.cnt = h1.cnt and
h2.min_Type = h1.min_Type and
h2.max_Type = h1.max_Type and
not exists (
select *
from (select tt.[Type] from Table1 as tt where tt.[ParentID] = h2.[ParentID]) as tt1
full join (select tt.[Type] from Table1 as tt where tt.[ParentID] = h1.[ParentID]) as tt2 on
tt2.[Type] = tt1.[Type]
where
tt1.[Type] is null or tt2.[Type] is null
)
order by
h2.[ParentID]
) as k
ParentID GroupID
----------- --------------
1 1
2 2
3 1
4 2
Another one is a bit trickier and you have to be careful when using it. But surprisingly, it works not so bad. The idea is to concatenate children into big string and then group by these strings. You can use any available concatenation method (xml trick or clr if you have SQL Server 2017). The important part is that you have to use ordered concatenation so every string will represent your group precisely. I have created a special CLR function (dbo.f_ConcatAsc) for this.
;with cte1 as (
select
ParentID,
dbo.f_ConcatAsc([Type], ',') as group_data
from Table1
group by
ParentID
), cte2 as (
select
dbo.f_ConcatAsc(ParentID, ',') as parent_data,
group_data,
row_number() over(order by group_data) as rn
from cte1
group by
group_data
)
select
cast(p.value as int) as ParentID,
c.rn as GroupID,
c.group_data
from cte2 as c
cross apply string_split(c.parent_data, ',') as p
ParentID GroupID group_data
----------- -------------------- --------------------------------------------------
2 1 ChildTypeA,ChildTypeB,ChildTypeC
4 1 ChildTypeA,ChildTypeB,ChildTypeC
1 2 ChildTypeA,ChildTypeB,ChildTypeC,ChildTypeD
3 2 ChildTypeA,ChildTypeB,ChildTypeC,ChildTypeD

Find rows in a table based on the existance of two different rows in a 1:N-related table

Say I have a table Clients, with a field ClientID, and that client has orders that are loaded in another table Orders, with foreign key ClientID to link both.
A client can have many orders (1:N), but orders have different types, described by the field TypeID.
Now, I want to select the clients that have orders of a number of types. For instance, the clients that have orders of type 1 and 2 (both, not one or the other).
How do I build this query? I'm really at lost here.
EDIT: Assume I'm on SQL Server.
This is query upon the assumption that TypeId can be either 1 or 2. This will return ClientId that have both a Type1 and Type2 no matter how many of them.
Select ClientId, COUNT(distinct TypeId) as cnt
from tblOrders o
group by ClientId
Having COUNT(distinct TypeId) >= 2
COUNT(distinct TypeId) is how this really works. It will count the distinct number of TypeId's for a particular ClientId. If you had say 5 different Types, then change the condition in the Having Clause to 5
This is a small sample DataSet
ClientId TypeId
1 1
1 2
1 2
2 2
2 1
3 1
3 1
Here is the resulting Query, it will exclude client 3 because it only has orders with Type1
Result Set
ClientId cnt
1 2
2 2
If you have many different TypeId's, but only want to check Type1 and Type2 put those Id's in a where clause
where TypeId in (1,2)
Here's one solution:
select * from clients c
where exists (select 1 from orders o where typeid = 1 and o.clientid = c.clientid)
and exists (select 1 from orders o where typeid = 2 and o.clientid = c.clientid)
and exists (select 1 from orders o where typeid = 3 and o.clientid = c.clientid)
-- additional types ...
You can use INTERSECT which will give the intersection of the resultsets.

Populate a sql table with duplicate data except for one column

I have a sql table :
Levels
LevelId Min Product
1 x 1
2 y 1
3 z 1
4 a 1
I need to duplicate the same data into the database by changing only the product Id from 1 2,3.... 40
example
LevelId Min Product
1 x 2
2 y 2
3 z 2
4 a 2
I could do something like
INSERT INTO dbo.Levels SELECT top 4 * fROM dbo.Levels
but that would just copy paste the data.
Is there a way I can copy the data and paste it changing only the Product value?
You're most of the way there - you just need to take one more logical step:
INSERT INTO dbo.Levels (LevelID, Min, Product)
SELECT LevelID, Min, 2 FROM dbo.Levels WHERE Product = 1
...will duplicate your rows with a different product ID.
Also consider that WHERE Product = 1 is going to be more reliable than TOP 4. Once you have more than four rows in the table, you will not be able to guarantee that TOP 4 will return the same four rows unless you also add an ORDER BY to the select, however WHERE Product = ... will always return the same rows, and will continue to work even if you add an extra row with a product ID of 1 (where as you'd have to consider changing TOP 4 to TOP 5, and so on if extra rows are added).
You can generate the product id's and then load them in:
with cte as (
select 2 as n
union all
select n + 1
from cte
where n < 40
)
INSERT INTO dbo.Levels(`min`, product)
SELECT `min`, cte.n as product
fROM dbo.Levels l cross join
cte
where l.productId = 1;
This assumes that the LevelId is an identity column, that auto-increments on insert. If not:
with cte as (
select 2 as n
union all
select n + 1
from cte
where n < 40
)
INSERT INTO dbo.Levels(levelid, `min`, product)
SELECT l.levelid+(cte.n-1)*4, `min`, cte.n as product
fROM dbo.Levels l cross join
cte
where l.productId = 1;
INSERT INTO dbo.Levels (LevelId, Min, Product)
SELECT TOP 4
LevelId,
Min,
2
FROM dbo.Levels
You can include expressions in the SELECT statement, either hard-coded values or something like Product + 1 or anything else.
I expect you probably wouldn't want to insert the LevelId though, but left that there to match your sample. If you don't want that just remove it from the INSERT and SELECT sections.
You could use a CROSS JOIN against a numbers table, for example.
WITH
L0 AS(SELECT 1 AS C UNION ALL SELECT 1 AS O), -- 2 rows
L1 AS(SELECT 1 AS C FROM L0 AS A CROSS JOIN L0 AS B), -- 4 rows
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS N FROM L1)
SELECT
lvl.[LevelID],
lvl.[Min],
num.[N]
FROM dbo.[Levels] lvl
CROSS JOIN Nums num
This would duplicate 4 times.

Consolidate records

I want to consolidate a set of records
(id) / (referencedid)
1 10
1 11
2 11
2 10
3 10
3 11
3 12
The result of query should be
1 10
1 11
3 10
3 11
3 12
So, since id=1 and id=2 has same set of corresponding referenceids {10,11} they would be consolidated. But id=3 s corresponding referenceids are not the same, hence wouldnt be consolidated.
What would be good way to get this done?
Select id, referenceid
From MyTable
Where Id In (
Select Min( Z.Id ) As Id
From (
Select Z1.id, Group_Concat( Z1.referenceid ) As signature
From (
Select id, referenceid
From MyTable
Order By id, referenceid
) As Z1
Group By Z1.id
) As Z
Group By Z.Signature
)
-- generate count of elements for each distinct id
with Counts as (
select
id,
count(1) as ReferenceCount
from
tblReferences R
group by
R.id
)
-- generate every pairing of two different id's, along with
-- their counts, and how many are equivalent between the two
,Pairings as (
select
R1.id as id1
,R2.id as id2
,C1.ReferenceCount as count1
,C2.ReferenceCount as count2
,sum(case when R1.referenceid = R2.referenceid then 1 else 0 end) as samecount
from
tblReferences R1 join Counts C1 on R1.id = C1.id
cross join
tblReferences R2 join Counts C2 on R2.id = C2.id
where
R1.id < R2.id
group by
R1.id, C1.ReferenceCount, R2.id, C2.ReferenceCount
)
-- generate the list of ids that are safe to remove by picking
-- out any id's that have the same number of matches, and same
-- size of list, which means their reference lists are identical.
-- since id2 > id, we can safely remove id2 as a copy of id, and
-- the smallest id of which all id2 > id are copies will be left
,RemovableIds as (
select
distinct id2 as id
from
Pairings P
where
P.count1 = P.count2 and P.count1 = P.samecount
)
-- validate the results by just selecting to see which id's
-- will be removed. can also include id in the query above
-- to see which id was identified as the copy
select id from RemovableIds R
-- comment out `select` above and uncomment `delete` below to
-- remove the records after verifying they are correct!
--delete from tblReferences where id in (select id from RemovableIds) R