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

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.

Related

How to get Null values for more than 1 transaction for an ID IN SQL? Working with two Fact tables

I am trying to get results by joining two tables using a unique key.
SUBJECT Table
KEY
OCCURS
Value
YMPRQ72
1
21
YMPRQJX
1
7
CONSUMER TABLE
KEY
TYPE
ITEM_ID
AMT
YMPRQJX
No Charge
1106
0
YMPRQ72
Deluxe
20883
40
YMPRQJX
Basic
20889
20
YMPRQ72
No Charge
1106
0
RESULTS
KEY
TYPE
ITEM_ID
AMT
OCCURS
VALUE
YMPRQJX
No Charge
1106
0
1
7
YMPRQ72
Deluxe
20883
40
1
21
YMPRQJX
Basic
20889
20
1
7
YMPRQ72
No Charge
1106
0
1
21
You can see in the results for each Key OCCURS should come only once and same goes with the Value. I want to have total of Subjects as 2 because of two unique values but the unique values are matching with multiple values in TYPE.
DESIRED / EXPECTED RESULTS
KEY
TYPE
ITEM_ID
AMT
OCCURS
VALUE
YMPRQJX
No Charge
1106
0
-
-
YMPRQ72
Deluxe
20883
40
1
21
YMPRQJX
Basic
20889
20
-
-
YMPRQ72
No Charge
1106
0
1
21
CODE I am using for ORACLE database is:
SELECT c.KEY, c.TYPE, c.ITEM_ID, c.AMT, s.OCCURS, s.VALUE
FROM CONSUMER c LEFT JOIN SUBJECT s ON c.KEY = s.KEY
You can use ROW_NUMBER() to enumerate the rows and then a CASE expression to put the values on only one row per KEY. It is not clear which row you want, just that the last row for the key has the value:
SELECT C.KEY, C.TYPE, C.ITEM_ID, C.AMT,
(CASE WHEN seqnum = 1 THEN S.OCCURS END) as OCCURS,
(CASE WHEN seqnum = 1 THEN S.VALUE END) as VALUE
FROM (SELECT C.*,
ROW_NUMBERI() OVER (PARTITION BY C.KEY ORDER BY NULL) as seqnum
FROM CONSUMER C
) C LEFT JOIN
SUBJECT
ON C.KEY = S.KEY
ORDER BY C.KEY, seqnum DESC;
If you want to fetch the values with respect to only "KEY", then it will definitely return all the records on that KEY, because of two unique values but the unique values are matching with multiple values in TYPE as you told, try using join instead of left join:
SELECT CONSUMER.KEY, CONSUMER.TYPE, CONSUMER.ITEM_ID, CONSUMER.AMT,
SUBJECT.OCCURS, SUBJECT.VALUE
FROM CONSUMER
JOIN SUBJECT ON CONSUMER.KEY = SUBJECT.KEY;
If you want to don't let the KEY repeated and results for each KEY OCCURS should come only once, it could be done by defining the TYPE in WHERE statement as:
SELECT CONSUMER.KEY, CONSUMER.TYPE, CONSUMER.ITEM_ID, CONSUMER.AMT,
SUBJECT.OCCURS, SUBJECT.VALUE
FROM CONSUMER
JOIN SUBJECT ON CONSUMER.KEY = SUBJECT.KEY
WHERE CONSUMER.TYPE = "No Charge";

SQL query (Postgres) how to answer that?

I have a table with company id's (non unique) and some attribute (let's call it status id), status can be between 1 to 18 (many to many the row id is what unique)
now I need to get results of companies who only have rows with 1 and 18, if they have any number as well (let's say 3) then this company should not be returned.
The data is stored as row id, some meta data, company id and one status id, the example below is AFTER I ran a group by query.
So as an example if I do group by and string agg, I am getting these values:
Company ID Status
1 1,9,12,18
2 12,13,18
3 1
4 8
5 18
So in this case I need to return only 3 and 5.
You should fix your data model. Here are some reasons:
Storing numbers in strings is BAD.
Storing multiple values in a string is BAD.
SQL has poor string processing capabilities.
Postgres offers many ways to store multiple values -- a junction table, arrays, and JSON come to mind.
For your particular problem, how about an explicit comparison?
where status in ('1', '18', '1,18', '18,1')
You can group by companyid and set 2 conditions in the having clause:
select companyid
from tablename
group by companyid
having
sum((status in (1, 18))::int) > 0
and
sum((status not in (1, 18))::int) = 0
Or with EXCEPT:
select companyid from tablename
except
select companyid from tablename
where status not in (1, 18)
See the demo.
Results:
> | companyid |
> | --------: |
> | 3 |
> | 5 |
You can utilize group by and having. ie:
select *
from myTable
where statusId in (1,18)
and companyId in (select companyId
from myTable
group by companyId
having count(distinct statusId) = 1);
EDIT: If you meant to include those who have 1,18 and 18,1 too, then you could use array_agg instead:
select *
from t t1
inner join
(select companyId, array_agg(statusId) as statuses
from t
group by companyId
) t2 on t1.companyid = t2.companyid
where array[1,18] #> t2.statuses;
EDIT: If you meant to get back only companyIds without the rest of columns and data:
select companyId
from t
group by companyId
having array[1,18] #> array_agg(statusId);
DbFiddle Demo

Checking for (and Deleting) Complex Object Duplicates in SQL Server

So I need to duplicate check a complex object, and then cascade delete dupes from all associated tables and I'm wondering if I can do it efficiently in SQL Server, or if I should go about it in my code. Structurally I have the following tables.
Claim
ClaimCaseSubTypes (mapping table for many to many relationship)
ClaimDiagnosticCodes (ditto)
ClaimTreatmentCodes (ditto)
Basically a Claim is only a duplicate if it is matching on 8 fields in itself AND has the same relationships in all the mapping tables.
For Example, the following records would be indicated as duplicates
Claim
Id CreateDate Other Fields
1 1/1/2015 matched
2 6/1/2015 matched
ClaimCaseSubTypes
ClaimId SubTypeId
1 34
1 64
2 34
2 64
ClaimDiagnosticCodes
ClaimId DiagnosticCodeId
1 1
2 1
ClaimTreatmentCodes
ClaimId TreatmentCodeId
1 5
1 6
2 6
2 5
And in this case I would want to keep 1 and delete 2 from the Claim table as well as any rows in the mapping tables with ClaimId of 2
This is the kind of problem that window functions are for:
;WITH cte AS (
SELECT c.ID,
ROW_NUMBER() OVER (PARTITION BY field1, field2, field3, ... ORDER BY c.CreateDate) As ClaimOrder
FROM Claim c
INNER JOIN other tables...
)
UPDATE Claim
SET IsDuplicate = IIF(cte.ClaimOrder = 1, 0, 1)
FROM Claim c
INNER JOIN cte ON c.ID = cte.ID
The fields that you include in the PARTITION BY indicates what fields need to be identical for two claims to be considered matched. The ORDER BY tell SQL Server assign the earliest claim the order of 1. Everything that doesn't have the order of 1 is a duplicate of something else.

DB2 SQL filter query result by evaluating an ID which has two types of entries

After many attempts I have failed at this and hoping someone can help. The query returns every entry a user makes when items are made in the factory against and order number. For example
Order Number Entry type Quantity
3000 1 1000
3000 1 500
3000 2 300
3000 2 100
4000 2 1000
5000 1 1000
What I want to the query do is to return filter the results like this
If the order number has an entry type 1 and 2 return the row which is type 1 only
otherwise just return row whatever the type is for that order number.
So the above would end up:
Order Number Entry type Quantity
3000 1 1000
3000 1 500
4000 2 1000
5000 1 1000
Currently my query (DB2, in very basic terms looks like this ) and was correct until a change request came through!
Select * from bookings where type=1 or type=2
thanks!
select * from bookings
left outer join (
select order_number,
max(case when type=1 then 1 else 0 end) +
max(case when type=2 then 1 else 0 end) as type_1_and_2
from bookings
group by order_number
) has_1_and_2 on
type_1_and_2 = 2
has_1_and_2.order_number = bookings.order_number
where
bookings.type = 1 or
has_1_and_2.order_number is null
Find all the orders that have both type 1 and type 2, and then join it.
If the row matched the join, only return it if it is type 1
If the row did not match the join (has_type_2.order_number is null) return it no matter what the type is.
A "common table expression" [CTE] can often simplify your logic. You can think of it as a way to break a complex problem into conceptual steps. In the example below, you can think of g as the name of the result set of the CTE, which will then be joined to
WITH g as
( SELECT order_number, min(type) as low_type
FROM bookings
GROUP BY order_number
)
SELECT b.*
FROM g
JOIN bookings b ON g.order_number = b.order_number
AND g.low_type = b.type
The JOIN ON conditions will work so that if both types are present then low_type will be 1, and only that type of record will be chosen. If there is only one type it will be identical to low_type.
This should work fine as long as 1 and 2 are the only types allowed in the bookings table. If not then you can simply add a WHERE clause in the CTE and in the outer SELECT.

How do I use a join to query two tables and get all rows from one, and related rows from the other?

Simplified for example, I have two tables, groups and items.
items (
id,
groupId,
title
)
groups (
id,
groupTitle,
externalURL
)
The regular query I'm goes something like this:
SELECT
i.`id`,
i.`title`,
g.`id` as 'groupId',
g.`groupTitle`,
g.`externalURL`
FROM
items i INNER JOIN groups g ON (i.`groupId` = g.`id`)
However I need to modify this now, because all the groups which specify an externalURL will not have any corresponding records in the items table (since they're stored externally). Is it possible to do some sort of join so that the output looks kinda like this:
items:
id title groupId
----------------------
1 Item 1 1
2 Item 2 1
groups
id groupTitle externalURL
-------------------------------
1 Group 1 NULL
2 Group 2 something
3 Group 3 NULL
Query output:
id title groupId groupTitle externalURL
---------------------------------------------------
1 Item 1 1 Group 1 NULL
2 Item 2 1 Group 1 NULL
NULL NULL 2 Group 2 something
-- note that group 3 didn't show up because it had no items OR externalURL
Is that possible in one SQL query?
This is exactly what an outer join is for: return all the rows from one table, whether or not there is a matching row in the other table. In those cases, return NULL for all the columns of the other table.
The other condition you can take care of in the WHERE clause.
SELECT
i.`id`,
i.`title`,
g.`id` as 'groupId',
g.`groupTitle`,
g.`externalURL`
FROM
items i RIGHT OUTER JOIN groups g ON (i.`groupId` = g.`id`)
WHERE i.`id` IS NOT NULL OR g.`externalURL` IS NOT NULL;
Only if both i.id and g.externalURL are NULL, then the whole row of the joined result set should be excluded.