SQL find entire row where only 2 columns values - sql

I'm attempting to
select columns Age, Height, House_number, Street
from my_table
where count(combination of House_number, Street)
occurs more than once.
My table looks like this
Age, Height, House_number, Street
15 178 6 Mc Gill Crst
85 166 6 Mc Gill Crst
85 166 195 Mc Gill Crst
18 151 99 Moon Street
52 189 14a Grimm Lane
My desired outcome looks like this
Age, Height, House_number, Street
15 178 6 Mc Gill Crst
85 166 6 Mc Gill Crst
Stuck!

The best way to do this is with window functions, assuming your database supports them:
select columns Age, Height, House_number, Street
from (select t.*, count(*) over (partition by house_number, street) as cnt
from my_table t
) t
where cnt > 1
This is using a windows function (also called analytic function) in Oracle. The expression count(*) over (partition by house_number, street) is counting the number of rows for each house_number and street combination. It is kind of like doing a group by, but it adds the count to each row rather than combining multiple rows into one.
Once you have that, it is easy to simply choose the rows where the value is greater than 1.

Since you haven't mentioned the RDBMS you are using, the query below will amost work on most RDBMS.
SELECT *
FROM tableName
WHERE (House_number, Street) IN
(
SELECT House_number, STREET
FROM tableName
GROUP BY House_number, STREET
HAVING COUNT(*) >= 2
)
SQLFiddle Demo

Sounds like you need a NOT DISTINCT. The following might give you what you need: Multiple NOT distinct

If you do not have windowing function, then you can use a subquery with a JOIN. The subquery gets the list of the house_number and street that have a count of greater than 1, this result is then used to join back to your table:
select t1.age,
t1.height,
t1.house_number,
t1.street
from my_table t1
inner join
(
select house_number, street
from my_table
group by house_number, street
having count(*) > 1
) t2
on t1.house_number = t2.house_number
and t1.street = t2.street
See SQL Fiddle with Demo

Related

Is there a way to display the total count of rows in a separate row?

I have a table that looks like this:
City_Id
City
41
Athena
39
Beijing
35
London
30
Rio de Janeiro
28
Salt Lake City
18
Sochi
7
Sydney
4
Torino
is there a way to display another row in the bottom that will display the total count of rows?
City_Id
City
41
Athena
39
Beijing
35
London
30
Rio de Janeiro
28
Salt Lake City
18
Sochi
7
Sydney
4
Torino
Total
8
You can actually use GROUPING SETS for this. This avoids having to scan the table twice.
However you still have the data-type mismatch problem. You could solve it by casting, but it's probably easier to just swap the columns around
SELECT
CASE WHEN GROUPING(City) = 0 THEN City ELSE 'Total' END AS City,
CASE WHEN GROUPING(City_Id) = 0 THEN City_Id ELSE COUNT(*) END AS City_Id
FROM Table1
GROUP BY GROUPING SETS (
(City_Id, City),
()
)
ORDER BY GROUPING(City_Id);
SQL Fiddle
What this does is generate separate result-sets, unioned together. You can differentiate between a grouped row and a non-grouped row using the GROUPING function.
I would agree with most of the other comments that acquiring a result set count would be more appropriate from the application code (which usually has a mechanism specifically for this purpose).
However...
If you must have a TSQL solution for your question, an option is to return the count in a separate column. This is different than returning it in a separate row, of course. There are pros & cons with each approach.
DROP TABLE IF EXISTS #Cities;
CREATE TABLE #Cities (
City_Id INT,
City VARCHAR(128)
);
INSERT INTO #Cities
VALUES
(41, 'Athena'),
(39, 'Beijing'),
(35, 'London'),
(30, 'Rio de Janeiro'),
(28, 'Salt Lake City'),
(18, 'Sochi'),
(7 , 'Sydney'),
(4 , 'Torino');
SELECT *, COUNT(*) OVER(ORDER BY (SELECT NULL)) AS Total
FROM #Cities;
--Count is properly reflected based on WHERE clause.
SELECT *, COUNT(*) OVER(ORDER BY (SELECT NULL)) AS Total
FROM #Cities
WHERE City LIKE 'S%';
--Be careful with this one--the COUNT(*) may not be what you expected.
SELECT TOP(4) *, COUNT(*) OVER(ORDER BY (SELECT NULL)) AS Total
FROM #Cities;
NOTE: be aware that this approach may not scale (perform) well for large result sets. Be sure to do some testing!
As you know already, it should be done in the presentation layer. But if you just want to know if there is any way, then I would suggest to use UNION ALL
select cast(City_Id as varchar(10)) City_Id, City from Table1
union all
select 'Total' as City_Id, cast(count(*) as varchar(14)) from Table1
Here is the sql fiddle

Count specific duplicates in Oracle

Hi I have problem to count the number of employees (EmpID) with a phone number (PhoneNum) assigned also to some other employee. But only for specific organization (OrgID)
My Oracle tables looks like this:
TABLE OrgEmployees (OrgID, EmpID, ...)
TABLE PhoneNums (ID, EmpID, PhoneNum, ...)
Sample data for the specific organization:
SELECT pn.EmpID, pn.PhoneNum FROM PhoneNums pn
WHERE EmpID IN (SELECT DISTINCT EmpID FROM OrgEmployees oe
WHERE oe.OrgID = 'XY');
EmpID PhoneNum
723 963264
731 963264
973 963276
729 963276
103 963450
725 963450
722 963460
731 963460
722 963462
731 963462
427 995487
295 995487
771 123151
503 123151
721 963265
104 963266
Correct result on above set of data should be 14.
My attempts went like this:
SELECT pn.PhoneNum, count(pn.EmpID) FROM PhoneNums pn
WHERE pn.EmpID IN (SELECT oe.EmpID FROM OrgEmployees oe
WHERE oe.OrgID = 'XY')
GROUP BY pn.PhoneNum
HAVING count (*) > 1
ORDER BY pn.PhoneNum;
But how could I consider if EmpID are the same or not?
Thank you in advance
I think you want count(distinct):
SELECT pn.PhoneNum, COUNT(DISTINCT pn.EmpID)
FROM PhoneNums pn
WHERE pn.EmpID IN (SELECT oe.EmpID
FROM OrgEmployees oe
WHERE oe.OrgID = 'XY'
)
GROUP BY pn.PhoneNum
HAVING COUNT(DISTINCT pn.EmpID) > 1
ORDER BY pn.PhoneNum;
I would be more inclined to write this using JOIN rather than IN:
SELECT pn.PhoneNum, COUNT(DISTINCT pn.EmpID)
FROM PhoneNums pn JOIN
OrgEmployees oe
ON oe.OrgID = 'XY' AND pn.EmpID = oe.EmpID
GROUP BY pn.PhoneNum
HAVING COUNT(DISTINCT pn.EmpID) > 1
ORDER BY pn.PhoneNum;

Combine GROUP BY and LIKE SQL

My objective is to display states that have 20+ of the value in the 2nd column..
Currently I have been able to display states and the values but I need to combine states that are similar and their values (e.g VIC and Vic and vic should equal VIC 68).
I also only want to display States, not their values but the values keep showing. I'm guessing its using LIKE combined with GROUP BY but I can't figure out how.
My current SQL query:
SELECT DEPARTMENT.STATE, COUNT(ACADEMIC.DEPTNUM) FROM ACADEMIC
JOIN DEPARTMENT
ON DEPARTMENT.DEPTNUM=ACADEMIC.DEPTNUM
GROUP BY DEPARTMENT.STATE;
Output:
STATE COUNT(ACADEMIC.DEPTNUM)
----- -----------------------
NSW 82
7
QLD 21
VIC 14
vic 1
WA 42
Tas 1
SA 40
Qld 55
Vic 53
ACT 35
TAS 8
I have no idea how to do this, can anyone help?
SELECT DEPARTMENT.STATE, COUNT(ACADEMIC.DEPTNUM) FROM ACADEMIC
JOIN DEPARTMENT
ON DEPARTMENT.DEPTNUM=ACADEMIC.DEPTNUM
GROUP BY DEPARTMENT.STATE
HAVING COUNT(ACADEMIC.DEPTNUM) >= 20;
Use HAVING to return only rows where the count is 20+.
To take care of different case, do UPPER on all states:
SELECT UPPER(DEPARTMENT.STATE), COUNT(ACADEMIC.DEPTNUM) FROM ACADEMIC
JOIN DEPARTMENT
ON DEPARTMENT.DEPTNUM=ACADEMIC.DEPTNUM
GROUP BY UPPER(DEPARTMENT.STATE)
HAVING COUNT(ACADEMIC.DEPTNUM) >= 20;
I need to combine states that are similar and their values (e.g VIC and Vic and vic should equal VIC 68)
You need to use SUM and GROUP BY(UPPER/LOWER) on your sub-query or simply use UPPER/LOWER in the GROUP BY expression in your original query.
For example,
SQL> with data as(
2 select 'VIC' state, 14 cnt from dual union all
3 select 'vic' state, 1 cnt from dual union all
4 select 'Vic' state, 53 cnt from dual
5 )
6 select upper(state), sum(cnt) count
7 from data
8 group by upper(state);
UPP COUNT
--- ----------
VIC 68
Since you already have the sub-query which gives you the count, all you need to use UPPER/LOWER in GROUP BY, such that the count would now consider similar states:
SELECT UPPER(DEPARTMENT.STATE) AS "STATE"
FROM ACADEMIC
JOIN DEPARTMENT
ON DEPARTMENT.DEPTNUM=ACADEMIC.DEPTNUM
GROUP BY UPPER(DEPARTMENT.STATE)
HAVING COUNT(ACADEMIC.DEPTNUM) >= 20;

Collapse Multiple Records Into a Single Record With Multiple Columns

In a program I'm maintaining we were given a massive (~500 lines) SQL statement by the customer. It is used for generating flat files with fixed length records for transmitting data to another big business. Since its a massive flat file its not relational and the standard normal forms of data are collapsed. So, if you have a record that can have multiple codes associated, in this case upto 19, they all have be written into single line, but seperate fields, in the flat file.
Note: this example is simplified.
The data might look like this, with three tables:
RECORDS
record_id firstname lastname
--------------------------------
123 Bob Schmidt
324 George Washington
325 Ronald Reagan
290 George Clooney
CODE_TABLE
code_id code_cd code_txt
--------------------------------
5 3 President
2 4 Actor
3 7 Plumber
CODES_FOR_RECORDS
record_id code_cd
-------------------
123 7
325 3
290 4
324 3
325 4
123 4
This needs to produce records like:
firstname lastname code1 code2 code3
Bob Schmidt Actor Plumber NULL
George Washington President NULL NULL
Ronald Reagon Actor President NULL
George Clooney Actor NULL NULL
The portion of the current query we were given looks like this, but with 19 code columns instead of the 5:
select
x.record_id,
max(case when x.rankk = 1 then code_txt end) as CodeColumn1,
max(case when x.rankk = 2 then code_txt end) as CodeColumn2,
max(case when x.rankk = 3 then code_txt end) as CodeColumn3,
max(case when x.rankk = 4 then code_txt end) as CodeColumn4,
max(case when x.rankk = 5 then code_txt end) as CodeColumn5,
from
(
select
r.record_id,
ct.code_txt as ctag ,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
from
records as r
codes_for_records as cfr,
code_table as ct
where
r.record_id = cfr.record_id
and ct.code_cd = cfr.code_cd
and cfr.code_cd is not null
and ct.code_txt not like '%V%'
) as x
where
x.record_id is not null
group by
x.record_id
I trimmed down things for simplicties sake, but the actual statment includes an inner query and a join and more where conditions, but that should get the idea across. My brain is telling me there has to be a better way, but I'm not an SQL expert. We are using DB2 v8 if that helps. And the codes have to be in seperate columns, so no coalescing things into a single string. Is there a cleaner solution than this?
Update:
I ended up just refacorting the original query, it sill uses the ugly MAX() business, but overall the query is much more readable due to reworking other parts.
It sounds like what you are looking for is pivoting.
WITH joined_table(firstname, lastname, code_txt, rankk) AS
(
SELECT
r.firstname,
r.lastname,
ct.code_txt,
dense_rank() over (partition by r.record_id order by cfr.code_id) as rankk
FROM
records r
INNER JOIN
codes_for_records cfr
ON r.record_id = cfr.record_id
INNER JOIN
codes_table ct
ON ct.code_cd = cfr.code_cd
),
decoded_table(firstname, lastname,
CodeColumn1, CodeColumn2, CodeColumn3, CodeColumn4, CodeColumn5) AS
(
SELECT
firstname,
lastname,
DECODE(rankk, 1, code_txt),
DECODE(rankk, 2, code_txt),
DECODE(rankk, 3, code_txt),
DECODE(rankk, 4, code_txt),
DECODE(rankk, 5, code_txt)
FROM
joined_table jt
)
SELECT
firstname,
lastname,
MAX(CodeColumn1),
MAX(CodeColumn2),
MAX(CodeColumn3),
MAX(CodeColumn4),
MAX(CodeColumn5)
FROM
decoded_table dt
GROUP BY
firstname,
lastname;
Note that I've never actually done this myself before. I'm relying on the linked document as a reference.
You might need to include the record_id to account for duplicate names.
Edit: Added the GROUP BY.
One of the possible solutions is using of recursive query:
with recursive_view (record_id, rankk, final) as
(
select
record_id,
rankk,
cast (ctag as varchar (100))
from inner_query t1
union all
select
t1.record_id,
t1.rankk,
/* all formatting here */
cast (t2.final || ',' || t1.ctag as varchar (100))
from
inner_query t1,
recursive_view t2
where
t2.rankk < t1.rankk
and t1.record_id = t2.record_id
and locate(t1.ctag, t2.final) = 0
)
select record_id, final from recursive_view;
Can't guarantee that it works, but hope it will be helpful. Another way is using of custom aggregate function.

count without group

I have one table named GUYS(ID,NAME,PHONE) and i need to add a count of how many guys have the same name and at the same time show all of them so i can't group them.
example:
ID NAME PHONE
1 John 335
2 Harry 444
3 James 367
4 John 742
5 John 654
the wanted output should be
ID NAME PHONE COUNT
1 John 335 3
2 Harry 444 1
3 James 367 1
4 John 742 3
5 John 654 3
how could i do that? i only manage to get lot of guys with different counts.
thanks
Update for 8.0+: This answer was written well before MySQL version 8, which introduced window functions with mostly the same syntax as the existing ones in Oracle.
In this new syntax, the solution would be
SELECT
t.name,
t.phone,
COUNT('x') OVER (PARTITION BY t.name) AS namecounter
FROM
Guys t
The answer below still works on newer versions as well, and in this particular case is just as simple, but depending on the circumstances, these window functions are way easier to use.
Older versions: Since MySQL, until version 8, didn't have analytical functions like Oracle, you'd have to resort to a sub-query.
Don't use GROUP BY, use a sub-select to count the number of guys with the same name:
SELECT
t.name,
t.phone,
(SELECT COUNT('x') FROM Guys ct
WHERE ct.name = t.name) as namecounter
FROM
Guys t
You'd think that running a sub-select for every row would be slow, but if you've got proper indexes, MySQL will optimize this query and you'll see that it runs just fine.
In this example, you should have an index on Guys.name. If you have multiple columns in the where clause of the subquery, the query would probably benefit from a single combined index on all of those columns.
Use an aggregate Query:
select g.ID, g.Name, g.Phone, count(*) over ( partition by g.name ) as Count
from
Guys g;
You can still use a GROUP BY for the count, you just need to JOIN it back to your original table to get all the records, like this:
select g.ID, g.Name, g.Phone, gc.Count
from Guys g
inner join (
select Name, count(*) as Count
from Guys
group by Name
) gc on g.Name = gc.Name
In Oracle DB you can use
SELECT ID,NAME,PHONE,(Select COUNT(ID)From GUYS GROUP BY Name)
FROM GUYS ;
DECLARE #tbl table
(ID int,NAME varchar(20), PHONE int)
insert into #tbl
select
1 ,'John', 335
union all
select
2 ,'Harry', 444
union all
select
3 ,'James', 367
union all
select
4 ,'John', 742
union all
select
5 ,'John', 654
SELECT
ID
, Name
, Phone
, count(*) over(partition by Name)
FROM #tbl
ORDER BY ID
select id, name, phone,(select count(name) from users u1 where u1.name=u2.name) count from users u2
try
select column1, count(1) over ()
it should help