Select only the persons that have both values - sql

I have a table with 2 columns:
ClientID | PhoneType
1 | 4
1 | 4
1 | 5
2 | 4
3 | 5
I'm trying to wright a query to only show the client who have both phone types.
So output should only show client ID 1
As you notice, client 1 has two type '4' PhoneTypes, so I'm trying not to use a GroupBy/HavingCount of method to narrow the result down. Which were the only examples I could find.

Something like...
SELECT x.ClientID
FROM (
SELECT DISTINCT ClientID, PhoneType FROM [Table A]
) AS x
GROUP BY x.ClientID
HAVING COUNT(x.PhoneType) = 2
DISTINCT is used to remove duplicates from the COUNT in case the client has 2 or more phones of the same type.

I don't have access to a SQL Server currently, but it seems like you should be able to use the following, which is a little simpler than Gavin's answer:
SELECT ClientID
FROM [Table A]
GROUP BY ClientID
HAVING COUNT(DISTINCT PhoneType) = 2
Not entirely sure that count distinct works in HAVING, but I don't see why it wouldn't...

Related

Oracle SQL query to count "children" in current query set

I have got an SQL query in Oracle with a multilevel subquery for generating my website navigation in the database. This query has a multilevel subquery because for each user I have to check whether they have the right to access this part of the navigation. The result looks kind of like the following:
ID | ID_PARENT | NAME | LINK
------------------------------------------
1 Main ~/
2 1 Sub1 ~/Sub1
3 1 Sub2 ~/Sub2
4 2 Sub1.1 ~/Sub1.1
5 2 Sub1.2 ~/Sub1.2
6 2 Sub1.3 ~/Sub1.3
The ID_PARENT column refers to the ID column of another row in the same table.
Now what I need is a query that, for each row, gives me the amount of rows in the current query set (because there exist other navigation entries that some users do not have the rights to, and I want to avoid making the same subquery twice) that have the current ID as ID_PARENT, so basically counts the children. With the example above the result I need should look like the following:
ID | ID_PARENT | NAME | LINK | CHILDREN
---------------------------------------------------------
1 Main ~/ 2
2 1 Sub1 ~/Sub1 3
3 1 Sub2 ~/Sub2 0
4 2 Sub1.1 ~/Sub1.1 0
5 2 Sub1.2 ~/Sub1.2 0
6 2 Sub1.3 ~/Sub1.3 0
I have tried a fair share of SQL queries, but none of them get me the result I need. Can anybody help me with this?
You can count() separately the record for your ID_PARENT and then join it with your main query. Something like this:
SELECT A.*, COALESCE(B.RC ,0) AS CHILDREN_NUMBER
FROM YOURTABLE A
LEFT JOIN ( SELECT ID_PARENT,COUNT(*) AS RC FROM YOURTABLE GROUP BY ID_PARENT) B ON A.ID = B.ID_PARENT;
Ouput:
ID ID_PARENT NAME LINK CHILDREN_NUMBER
1 NULL Main / 2
2 1 SUB1 /Sub1 3
3 1 SUB2 /Sub2 0
4 2 SUB1.1 /Sub1.1 0
5 2 SUB1.2 /Sub1.2 0
6 2 SUB1.3 /Sub1.3 0
For example
with q(ID, ID_PARENT, NAME, LINK) as (
-- original query
)
select ID, ID_PARENT, NAME, LINK
,(select count(*) from q q2 where q2.ID_PARENT = q.ID) CHILDREN
from q
Try like this, this is same as above answer by etsa.
select
n.id,n.parent_id,n.name,n.link,coalesce(b.children,0)
from navigation n
left join (select
parent_id as parent,count(id) as children
from navigation group by parent_id) b
on n.id=b.parent;

Get duplicate on single column after distinct across multiple columns in SQL

I have a table that looks like this:
name | id
-----------
A 1
A 1
B 2
C 1
D 3
D 3
F 2
I want to return id's 1 and 2 because they are duplicate on names. I don't want to return 3, because it is distinct for D 3.
Basically, I'm thinking of doing a query to first get a distinct pairing, so the above reduces to
name | id
-----------
A 1
B 2
C 1
D 3
F 2
And then doing a duplicate find on the id column. However, I'm struggling to find the correct syntax to construct that query.
You should be able to get the result you want by using a GROUP BY along with a HAVING clause that counts the distinct names. The HAVING clause will filter for those ids that have more than one distinct name:
select id
from Table1
group by id
having count(distinct name) > 1
Here is a demo

In SQL, find duplicates in one column with unique values for another column

So I have a table of aliases linked to record ids. I need to find duplicate aliases with unique record ids. To explain better:
ID Alias Record ID
1 000123 4
2 000123 4
3 000234 4
4 000123 6
5 000345 6
6 000345 7
The result of a query on this table should be something to the effect of
000123 4 6
000345 6 7
Indicating that both record 4 and 6 have an alias of 000123 and both record 6 and 7 have an alias of 000345.
I was looking into using GROUP BY but if I group by alias then I can't select record id and if I group by both alias and record id it will only return the first two rows in this example where both columns are duplicates. The only solution I've found, and it's a terrible one that crashed my server, is to do two different selects for all the data and then join them
ON [T_1].[ALIAS] = [T_2].[ALIAS] AND NOT [T_1].[RECORD_ID] = [T_2].[RECORD_ID]
Are there any solutions out there that would work better? As in, not crash my server when run on a few hundred thousand records?
It looks as if you have two requirements:
Identify all aliases that have more than one record id, and
List the record ids for these aliases horizontally.
The first is a lot easier to do than the second. Here's some SQL that ought to get you where you want with the first:
WITH A -- Get a list of unique combinations of Alias and [Record ID]
AS (
SELECT Distinct
Alias
, [Record ID]
FROM T1
)
, B -- Get a list of all those Alias values that have more than one [Record ID] associated
AS (
SELECT Alias
FROM A
GROUP BY
Alias
HAVING COUNT(*) > 1
)
SELECT A.Alias
, A.[Record ID]
FROM A
JOIN B
ON A.Alias = B.Alias
Now, as for the second. If you're satisfied with the data in this form:
Alias Record ID
000123 4
000123 6
000345 6
000345 7
... you can stop there. Otherwise, things get tricky.
The PIVOT command will not necessarily help you, because it's trying to solve a different problem than the one you have.
I am assuming that you can't necessarily predict how many duplicate Record ID values you have per Alias, and thus don't know how many columns you'll need.
If you have only two, then displaying each of them in a column becomes a relatively trivial exercise. If you have more, I'd urge you to consider whether the destination for these records (a report? A web page? Excel?) might be able to do a better job of displaying them horizontally than SQL Server can do in returning them arranged horizontally.
Perhaps what you want is just the min() and max() of RecordId:
select Alias, min(RecordID), max(RecordId)
from yourTable t
group by Alias
having min(RecordId) <> max(RecordId)
You can also count the number of distinct values, using count(distinct):
select Alias, count(distinct RecordId) as NumRecordIds, min(RecordID), max(RecordId)
from yourTable t
group by Alias
having count(DISTINCT RecordID) > 1;
This will give all repeated values:
select Alias, count(RecordId) as NumRecordIds,
from yourTable t
group by Alias
having count(RecordId) <> count(distinct RecordId);
I agree with Ann L's answer but would like to show how you can use window functions with CTE's as you may prefer the readability.
(Re: how to pivot horizontally, I again agree with Ann)
create temporary table things (
id serial primary key,
alias varchar,
record_id int
)
insert into things (alias, record_id) values
('000123', 4),
('000123', 4),
('000234', 4),
('000123', 6),
('000345', 6),
('000345', 7);
with
things_with_distinct_aliases_and_record_ids as (
select distinct on (alias, record_id)
id,
alias,
record_id
from things
),
things_with_unique_record_id_counts_per_alias as (
select *,
COUNT(*) OVER(PARTITION BY alias) as unique_record_ids_count
from things_with_distinct_aliases_and_record_ids
)
select * from things_with_unique_record_id_counts_per_alias
where unique_record_ids_count > 1
The first CTE gets all the unique alias/record id combinations. E.g.
id | alias | record_id
----+--------+-----------
1 | 000123 | 4
4 | 000123 | 6
3 | 000234 | 4
5 | 000345 | 6
6 | 000345 | 7
The second CTE simply creates a new column for the above and adds the count of record ids for each alias. This allows you to filter only those aliases which have more than one record id associated with them.
id | alias | record_id | unique_record_ids_count
----+--------+-----------+-------------------------
1 | 000123 | 4 | 2
4 | 000123 | 6 | 2
3 | 000234 | 4 | 1
5 | 000345 | 6 | 2
6 | 000345 | 7 | 2
SELECT A.CitationId,B.CitationId, A.CitationName, A.LoaderID, A.PrimaryReferenceLoaderID,B.SecondaryReference1LoaderID, A.SecondaryReference1LoaderID, A.SecondaryReference2LoaderID,
A.SecondaryReference3LoaderID, A.SecondaryReference4LoaderID, A.CreatedOn, A.LastUpdatedOn
FROM CitationMaster A, CitationMaster B
WHERE A.PrimaryReferenceLoaderID= B.SecondaryReference1LoaderID and Isnull(A.PrimaryReferenceLoaderID,'') != '' and Isnull(B.SecondaryReference1LoaderID,'') !=''

Is there a way to get data from 2 tables without creating a Cartesian product?

In our database a customer can have any number of drivers, any number of vehicles, any number of storage locations, any number of buildings at those locations, any number of comments, and so on. I need a query that returns all of the customer's information and right now the query is something like:
SELECT *
FROM Customer c
INNER JOIN Driver d ON c.ID = d.CustomerID
INNER JOIN Vehicle v ON c.ID = v.CustomerID
The more that a customer has the bigger the result gets, and it grows exponentially because a cartesian product is being created here. 3 drivers, 3 vechiles creates 9 rows, and this is a very small example compared to what our real data is like. We actually have 10 different tables that can hold as many rows per customer as they want. The norm is 2-7 rows at least per table per customer. we have had as many as 60,000,000+ rows returned (6 items each in 10 different tables, 6^10 = 60,466,176) and for our purposes 6 rows total would have given us all the data we needed if we could just stick the 6 rows in each table together.
so in the smaller example, if 1 customer had 2 vehicles and 3 drivers and another customer had 2 vehicles and 1 drivers i would want a result set that looked like:
CustomerID | DriverID | VehicleID
1 | 1 | 1
1 (or NULL) | 2 | 2
1 (or NULL) | NULL | 3
2 | 3 | 4
2 (or NULL) | 4 | NULL
Instead our query that joins every table together on CustomerID looks like this:
CustomerID | DriverID | VehicleID
1 | 1 | 1
1 | 1 | 2
1 | 1 | 3
1 | 2 | 1
1 | 2 | 2
1 | 2 | 3
2 | 3 | 4
2 | 4 | 4
Really, what I want to do is just:
SELECT * FROM Driver
SELECT * FROM Vehicle
Because all we are doing with the data is looping through the rows and formatting the information in a document. All drivers are listed, then all vehicles are listed. It makes no sense to do this crazy huge join when we don't have to, but it's just an arbitrary requirement that it must return all the data in 1 result set from a stubborn superior who refuses to listen to reason. Since the columns are different a UNION isn't possible. i'm just hoping there's a way to stick them together horizontally instead of vertically.
Also, I'm using Microsoft SQL Server.
It's an ugly hack, but you know your proper solution is just as you state:
SELECT * FROM Driver
SELECT * FROM Vehicle
Instead you could use a union query and blank out the columns from the other tables, just start it with a query that sets the type and names of the columns, with a false coldition so it doesn't return a row:
SELECT 1 AS DriverID, "" AS DriverName, 1 AS VehicleID, "" AS VehicleName WHERE 1=0
UNION SELECT DriverID, DriverName, NULL, NULL FROM Driver
UNION SELECT NULL, NULL, VehicleID, VehicleName FROM Driver
Really, really bad code! Keep working on your superior to allow a better solution.
Here's how I'm doing it. Instead of:
SELECT *
FROM Customer c
INNER JOIN Driver d ON c.ID = d.CustomerID
INNER JOIN Vehicle v ON c.ID = v.CustomerID
I'm doing:
WITH CustomerCTE AS
(
SELECT 1 ROW_NUM, ID
FROM Customer
),
DriverCTE AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY ID) ROW_NUM, *
FROM Driver
),
VehicleCTE AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY ID) ROW_NUM, *
FROM Vehicle
)
SELECT *
FROM CustomerCTE c
FULL OUTER JOIN DriverCTE d ON c.ID = d.CustomerID AND c.ROW_NUM = d.ROW_NUM
FULL OUTER JOIN VehicleCTE v ON d.CustomerID = v.CustomerID AND d.ROW_NUM = v.ROW_NUM
ORDER BY
CASE WHEN c.ID IS NOT NULL THEN c.ID ELSE
CASE WHEN d.CustomerID IS NOT NULL THEN d.CustomerID ELSE
v.CustomerID
END
END,
CASE WHEN c.ROW_NUM IS NOT NULL THEN c.ROW_NUM ELSE
CASE WHEN d.ROW_NUM IS NOT NULL THEN d.ROW_NUM ELSE
v.ROW_NUM
END
END
Now if a customer has 3 drivers and 3 vehicles i get 3 rows instead of 9 rows. It makes it look like each driver is associated to 1 of the 3 vehicles, but it's actually not. Again, this is bad design, but it is necessary to cut down on the number of rows returned with the unreasonable restrictions I was given.
It looks like more work than webturner's answer, but in my real case where I have to join 10 different tables with over 500 columns its a lot less work to do it this way than to explicitly name all 500 columns and fill in all of the remaining columns from each table with NULL.
Though, this may not be of much use to most people. In most cases if you're doing something like this you probably need to rethink your design, but there may be some cases where you have no choice.

Difficulty in creating update totals query on same table

Consider the following table:
ID nonUniqueID value total
--------------------------
1 12345 5 x
2 12345 10 x
3 789 20 x
4 789 5 x
I need to make a query something like this (psuedo SQL), which will work within Access 2007:
UPDATE table
SET total = SUM(value)
WHERE nonUniqueID IS SAME;
The result should be as follows:
ID nonUniqueID value total
--------------------------
1 12345 5 15
2 12345 10 15
3 789 20 25
4 789 5 25
I've tried group bys, but I got odd results that quite frankly, I could not interpret. Does anybody know how I could achieve something like this?
Not sure if this works in Access or not, but give it a try:
update table t1
inner join (
select nonUniqueID, sum(value) as SumValue
from table
group by nonUniqueID
) t2 on t1.nonUniqueID = t2.nonUniqueID
set t1.total = t2.SumValue
Update: Based on this question, it looks like it is not going to work. But give it a shot! If it doesn't, you can use the approach suggested in that question.
Another possible option:
update t
set total = (select SUM(value) from table where nonUniqueID = t.nonUniqueID)
from table t