Please help me design a sql query for this problem - sql

For a particular name i want to fetch other names who have lived in three or more cities lived by this person.

this is what you should do:
redesign your database to have a city table:
city(id int, name varchar)
and a user table:
user(id int, name varchar, ...)
and a user_city table:
user_city(user_id int, city_id int)
that alone will eliminate the limit of 10 cities per user.
to find the cities lived in by a user:
select city_id form user_city where user_id = ?
now how you would find users that lives in 3 or more cities from that list?
one way to do it would be to count the number of cities from the list each user lived in, something like:
select user_id,count(*) n
from user_city
where city_id in (select city_id
from user_city
where user_id = ?)
group by user_id having n >= 3;
I didn't really test this, but it should work.
you will also have to figure out how to index those tables.

You'd need binomial(10,3)^2 OR conditions to do your query. Thats 14 400. You do not want to do that.

You need to redesign your table instaed of
name , city1 , city2 , city3 ,city4 , city5 ,city6 , city7 , city8 , city9 city10
it should be more like
Person, City, rank
------------------
name , city1 ,1
name , city2 ,2
name , city3 ,3
name , city4 ,4
name , city5 ,5
name , city6 ,6
name , city7 ,7
name , city8 ,8
name , city9 ,9
name , city10,10
and take TomTom's advice and learn about data normalization!

Respecting your request to not redesign the database
My untried idea, no way to test it right now
Make a view (name, city) by unioning select name, c1, select name, c2 etc...
Then:
select m2.name from myview m1
inner join myview m2 on m1.city = m2.city
where m1.name = #Name AND m2.Name!=#Name
group by m2.name
having count(m2.name) > 2

Yeah.
You send the table back to whoever designed it with a comment to learn hwo to design tables. First normal form, normalization.
Once the table follows SQL rules, the query is pretty easy.

Try something like this:
SELECT PersonName,COUNT(*) AS CountOf
FROM (SELECT PersonName,city1 FROM PersonCities WHERE city1 IS NOT NULL
UNION SELECT PersonName,city2 FROM PersonCities WHERE city2 IS NOT NULL
UNION SELECT PersonName,city3 FROM PersonCities WHERE city3 IS NOT NULL
UNION SELECT PersonName,city4 FROM PersonCities WHERE city4 IS NOT NULL
UNION SELECT PersonName,city5 FROM PersonCities WHERE city5 IS NOT NULL
...
) dt
WHERE dt.city1 IN (SELECT city1 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city1 IS NOT NULL
UNION SELECT city2 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city2 IS NOT NULL
UNION SELECT city3 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city3 IS NOT NULL
UNION SELECT city4 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city4 IS NOT NULL
UNION SELECT city5 FROM PersonCities WHERE PersonName=..SearchPerson.. AND city5 IS NOT NULL
...
)
AND PersonName!=#SearchPerson
GROUP BY PersonName
HAVING COUNT(*)>=3
I don't have mysql, so here it is running using SQL Server:
DECLARE #PersonCities table(PersonName varchar(10), city1 varchar(10), city2 varchar(10), city3 varchar(10), city4 varchar(10), city5 varchar(10))
INSERT INTO #PersonCities VALUES ('Joe','AAA','BBB','CCC', NULL, NULL)
INSERT INTO #PersonCities VALUES ('Pat','BBB','DDD','EEE','FFF','GGG')
INSERT INTO #PersonCities VALUES ('Sam','FFF','BBB', NULL, NULL, NULL)
INSERT INTO #PersonCities VALUES ('Ron','HHH','DDD','EEE','FFF', NULL)
INSERT INTO #PersonCities VALUES ('Don','FFF','ZZZ','QQQ', NULL, NULL)
DECLARE #SearchPerson varchar(10)
SET #SearchPerson='Pat'
SELECT PersonName,COUNT(*) AS CountOf
FROM (SELECT PersonName,city1 FROM #PersonCities WHERE city1 IS NOT NULL
UNION SELECT PersonName,city2 FROM #PersonCities WHERE city2 IS NOT NULL
UNION SELECT PersonName,city3 FROM #PersonCities WHERE city3 IS NOT NULL
UNION SELECT PersonName,city4 FROM #PersonCities WHERE city4 IS NOT NULL
UNION SELECT PersonName,city5 FROM #PersonCities WHERE city5 IS NOT NULL
) dt
WHERE dt.city1 IN (SELECT city1 FROM #PersonCities WHERE PersonName=#SearchPerson AND city1 IS NOT NULL
UNION SELECT city2 FROM #PersonCities WHERE PersonName=#SearchPerson AND city2 IS NOT NULL
UNION SELECT city3 FROM #PersonCities WHERE PersonName=#SearchPerson AND city3 IS NOT NULL
UNION SELECT city4 FROM #PersonCities WHERE PersonName=#SearchPerson AND city4 IS NOT NULL
UNION SELECT city5 FROM #PersonCities WHERE PersonName=#SearchPerson AND city5 IS NOT NULL
)
AND PersonName!=#SearchPerson
GROUP BY PersonName
HAVING COUNT(*)>=3
OUTPUT:
PersonName
---------- -----------
Ron 3
(1 row(s) affected)

You need to normalize your database.
Doing that you will get the columns
Name, City (optionally CityOrder).
After that you will need to find a way to combine these results into what you need. Doing this you'll need to understand Join, Count and Group by.

Try this:
< table > Person
< fields > PersonId, PersonName |
< table > City
< fields > CityId, CityName |
< table > LivedIn
< fields > LivedInId, PersonId, CityId
Logically you would do the following things for each scenario:
Find the person who has lived in the maximum number of different cities:
Make a list of the PersonId's (all people)
Iterate over that and count the number of cities each person lived in
Find the maximum cities lived in by anyone person
Find the person name related to the personId that had the max cities
Find all people that lived in 3 or more cities as a give person
Let's call the person Bob
Make a list of all cities (the CityIds) that Bob lived in.
Make a list which includes personId, and common cities (maybe a HashMap in Java)
Iterate over the LivedIn table and update the count of how many cities are common
Find all the people who have a count greater than 3
I would do this with a combination of Java and SQL but I am not that good with either so can't give you the code here without having to look a lot of stuff up.

Breaking this data out into three tables to provide a more flexible many-to-many relationship.
person table to store names
city table to store cities
person_city to relate the two (many to many)
To retrieve other people who have lived in 3 or more cities that navin has:
SELECT name FROM (
SELECT
p.name, COUNT(DISTINCT(city_id)) AS lived
FROM person p
JOIN person_city pc ON (pc.person_id = p.person_id)
JOIN city c ON (c.city_id = pc.city_id)
WHERE city_id IN (
SELECT c2.city_id
FROM city c2
JOIN person_city pc2 ON (c2.city_id = pc2.city_id)
JOIN person p2 ON (p2.person_id = pc2.person_id)
WHERE p2.name = 'navin'
)
GROUP BY person_id HAVING lived >= 3
) AS multihome
WHERE name <> 'navin';

Related

implementing a divide query in postgresql

Basically i have these relations, account(account_number,branch_name,balance) and city(branch_name,country). What i want is all the account_numbers of the people who have accounts on all the cities in the city table but for some reason im getting an empty query and i dont get why.
Ps: ive seen some confusion on what the divide means, the divide is the division of 2 sets like in relational algebra, what i want is to divide the account set with the city set, in doing so, it is supposed to return all accounts that have a branch_name in every city in cities
Example:
account table
--------------|-----------|---------
account_number|branch_name|balance
--------------|-----------|---------
'A-000000' |'Downtown' | 3467
'A-000001' |'Downtown' | 1500
'A-000002' |'London' | 1500
'A-000826' |'Manchester'| 9999999
'A-000826' |'Downtown' | 33399
------------------------------------
city
--------------|-----------
branch_name | country
--------------|-----------
'Manchester' | 'UK'
'Downtown' | 'USA'
--------------------------
Account รท city
--------------|-----------|---------
account_number|branch_name|balance
--------------|-----------|---------
'A-000826' |'Manchester'| 9999999
'A-000826' |'Downtown' | 33399
-----------------------------------
'A-000826' is the only account in all of the cities in the city table
Program:
create table account(
account_number char(9),
branch_name varchar(80) not null,
balance numeric(16,4));
create table city(
branch_name varchar(80) not null,
country varchar(80) not null,
check(branch_name != ''));
INSERT INTO city(branch_name,country)
VALUES ('Manchester','UK'),
('Downtown','USA');
INSERT INTO account(account_number, branch_name, balance)
VALUES ('A-000000','Downtown',3467),
('A-000001','Downtown',1500),
('A-000002','London',1500),
('A-000826','Manchester',9999999),
('A-000826','Downtown',33399);
Query:
select *
from account as A
where not exists ( (select branch_name
from account)
except
(select C.branch_name
from city as C
where A.branch_name = C.branch_name)
)
I agree with Tim Biegeleisen's answer.
Your logic in the where not exists was backwards.
If you insist on sticking to relational algebra, then try this, instead:
select *
from account as A
where not exists (
select branch_name
from city
except
select branch_name
from account b
where b.account_number = a.account_number
);
db<>fiddle here
I would use an aggregation approach:
SELECT a.account_number
FROM account a
INNER JOIN city c
ON c.branch_name = a.branch_name
GROUP BY a.account_number
HAVING COUNT(*) = (SELECT COUNT(*) FROM city);

How to split a row in multiple rows SQL Server?

I want to convert a row in sql table to multiple rows in other table.
example: say if i'm having a table 'UserDetail' and he has 2 address's (home, office...) then the table looks like...
I wand the result to be in the second table as shown in the image
We can use Cross Apply
;WITH CTE(UseriD,Address1Line,Address1City,Address1State,Address2Line,Address2City,Address2State )
AS
(
SELECT 1,'Line1','City1','State1','Line2','City2','State2'
)
SELECT UseriD,[Address],City,[State]
FROM CTE
CROSS APPLY ( VALUES (Address1Line,Address1City,Address1State ),
(Address2Line,Address2City,Address2State )
)AS Dt([Address],City,[State])
Result
UseriD Address City State
-----------------------------
1 Line1 City1 State1
1 Line2 City2 State2
Demo:http://rextester.com/KHFUM28227
You could use "union all" to do that like:
select * into newTable
from
(
select UserId, Address1Line as Address, Address1City as City, Address1State as State
from myTable
union all
select UserId, Address2Line as Address, Address2City as City, Address2State as State
from myTable
) tmp
If you use just UNION instead of UNION ALL you would also be removing the duplicates where Address1 and Address2 is same.
You can CROSS JOIN or CROSS APPLY the table to a list of numbers.
Then use IIF or CASE to get the correspondent numbered fields.
Or CROSS APPLY on the address values directly on the table.
Example snippet:
declare #UserTable table (UserId int, Address1Line varchar(30), Address1City varchar(30), Address1State varchar(30), Address2Line varchar(30), Address2City varchar(30), Address2State varchar(30));
insert into #UserTable (UserId, Address1Line, Address1City, Address1State, Address2Line, Address2City, Address2State) values
(1,'Wonder Lane 42','WonderTown', 'WonderState', 'Somewhere 1 B', 'Nowhere', 'Anywhere'),
(2,'Backstreet 69','Los Libros', 'Everland', 'Immortal Cave 777', 'Ghost City', 'The Wild Lands');
-- Cross Join on numbers
select UserId,
case n when 1 then Address1Line when 2 then Address2Line end as [Address],
case n when 1 then Address1City when 2 then Address2City end as [City],
case n when 1 then Address1State when 2 then Address2State end as [State]
from #UserTable u
cross join (values (1),(2)) as nums(n);
-- Cross Apply on Adress values
select UserId, [Address], [City], [State]
from #UserTable Usr
cross apply (values
(1, Address1Line, Address1City, Address1State),
(2, Address2Line, Address2City, Address2State)
) AS Addr(n, [Address], [City], [State]);
Both return:
UserId Address City State
------ ----------------- ---------- --------------
1 Wonder Lane 42 WonderTown WonderState
1 Somewhere 1 B Nowhere Anywhere
2 Backstreet 69 Los Libros Everland
2 Immortal Cave 777 Ghost City The Wild Lands

Find duplicate records based on two columns

I have a table named employee, there are many records in this table. Here is some sample data :
fullname | address | city
-----------------------------
AA address1 City1
AA address3 City1
AA address8 City2
BB address5 City2
BB address2 City1
CC address6 City1
CC address7 City2
DD address4 City1
I want to have a SELECT query in sql server which will show only the duplicate records based on the columns fullname and city. For the given data and considering the condition, only the first two records is duplicate. So my expected output should be like below :
fullname | address | city
-----------------------------
AA address1 City1
AA address3 City1
To get this output, I have this query :
select fullname, city from employee group by fullname, city having count(*)>1
As you can see, it selects two columns only and thus it is giving the following output :
fullname | city
------------------
AA City1
If I re-write the query like below :
select fullname, city, address from employee group by fullname, city, address
having count(*)>1
Unfortunately it is showing no records! Can anybody please help me to write the correct query to get the expected result?
Instead of a grouped COUNT you can use it as a windowed aggregate to access the other columns
SELECT fullname,
address,
city
FROM (SELECT *,
COUNT(*) OVER (PARTITION BY fullname, city) AS cnt
FROM employee) e
WHERE cnt > 1
Agree with above answer.
But If you don't want to use Windows functions which might not work properly on all DBs you can join to itself on city and full name after the group by and having and then get the addresses
Select employee.* from employee
join (select fullname, city from employee group by fullname, city having count(*)>1) q1
on q1.fullname = employee.fullname and q1.city = employee.city
Try the Following Code:
create table ##Employee
(Fullname varchar(25),
Address varchar(25),
City varchar(25))
insert into ##Employee values
( 'AA', 'address1', 'City1')
,( 'AA', 'address3', 'City1')
,( 'AA', 'address8', 'City2')
,( 'BB', 'address5', 'City2')
,( 'BB', 'address2', 'City1')
,( 'CC', 'address6', 'City1')
,( 'CC', 'address7', 'City2')
select E.* from ##Employee E
cross apply(
select Fullname,City,count(Fullname) cnt from ##Employee
group by Fullname,City
having Count(Fullname)>1)x
where E.Fullname=x.Fullname
and E.City=x.City
If you have a unique id or the address is always different, you can try:
select e.*
from employee e
where exists (select 1
from employee e2
where e2.fullname = e.fullname and e2.city = e.city and
e2.address <> e.address -- or id or some other unique column
);
Although I would probably go with the window function approach, you might find that under some circumstances, this is faster (especially if you have an index on employee(fullname, city, address)).
Here you go with the solution:
DECLARE #Employee TABLE
(
Fullname VARCHAR(25),
[Address] VARCHAR(25),
City VARCHAR(25)
)
INSERT INTO #Employee VALUES
('AA', 'address1', 'City1')
,('AA', 'address1', 'City1')
,('AA', 'address3', 'City1')
,('AA', 'address8', 'City2')
,('BB', 'address5', 'City2')
,('BB', 'address2', 'City1')
,('CC', 'address6', 'City1')
,('CC', 'address7', 'City2')
;WITH cte AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY FullName, [Address], [City] ORDER BY Fullname) AS sl,
HashBytes('MD5', FullName + [Address] + [City]) AS RecordId
FROM #Employee AS e
)
SELECT c.FullName,
c.[Address],
c.City
FROM cte AS c
INNER JOIN cte AS c1
ON c.RecordId = c1.RecordId
WHERE c.sl = 2
Result :
FullName Address City
AA address1 City1
AA address1 City1
SELECT Feild1, Feild2, COUNT() FROM table name GROUP BY Feild1, Feild2 HAVING COUNT()>1
This will give you all yours answer

Select same last names but not same names

I have a table with fname|lname|startyear|endyear
Take it that a person with same fname and lname is a unique person.
There can be multiple entries with the same fname|lname.
1)How do i find all the same last names belonging to different people?
Eg
'tom' |'jerry'|1990|1991|
'vlad' |'jerry'|1991|1992|
'tim' |'cook' |1991|1992|
'tim' |'cook' |1992|1993|
Output:
jerry
2)Which people (first and last names) served between 'Mary' 'Jane's two terms?
Eg
'mary' |'jane'|1989|1990|
'tom' |'jerry'|1990|1991|
'vlad' |'jerry'|1991|1992|
'tim' |'cook' |1991|1992|
'tim' |'cook' |1992|1993|
'mary' |'jane'|1993|1994
Output
tom jerry
vlad jerry
tim cook
1) In this below query, the inline view gets you all the unique combination of fname,lname's and its joined with the original table on lname that will give you all the unique lnames but have multilple first names.
SELECT lname
FROM table t1
INNER JOIN
( SELECT fname,lname
FROM table
GROUP BY fname,lname
HAVING COUNT(1) = 1
) t2
ON t1.lname = t2.lname;
2) In this query, the inline view will return the min year and max year of the terms served by Mary Jane and then its cross joined to the original table and the comparison is done on the startyear and endyear which will give you all the fname,lname's who served in between Mary Jane.
SELECT fname,lname
FROM table t1
CROSS JOIN
( SELECT MIN(startyear) AS minstart,MAX(endyear) AS maxend
FROM table
WHERE fname = 'Mary' AND lname = 'Jane'
) t2
WHERE t1.startyear >= t2.minstart AND t1.endyear <= t2.maxstart;

SQL Server : query to update record with latest entry

I have a table that maintains records of employers and employees' data. Something like this
EmployerName EmployerPhone EmployerAddress EmployeeName EmployeePhone EmployeeAddress Date
-------------------------------------------------------------------------------------------------------
John 12345 NewYork Harry 59786 NewYork 12-1-1991
Mac 22345 Bankok John 12345 Delhi 12-3-1991
Smith 54732 Arab Amar 59226 China 21-6-1991
Sarah 12345 Bhutan Mac 22345 NewYork 5-9-1991
Root 85674 NewYork Smith 54732 Japan 2-11-1991
I have another table that will have generic records on the basis of phone number (both employers and employees).
Table structure is as following
Phone Name Address
I want to put latest records according to date from Table1 to Table2 on the basis of phone..
Like this
Phone Name Address
-----------------------
59786 Harry NewYork
22345 Mac NewYork
59226 Amar China
12345 Sarah Bhutan
22345 Mac NewYork
85674 Root NewYork
54732 Smith Arab
I've written many queries but couldn't find anyone resulted as required.
Any kind of help will be appreciated.
For initialize the table without phone duplicates:
INSERT IGNORE INTO Table2 (Phone, Name, Address)
SELECT X.* FROM (
SELECT EmployeeName,EmployeePhone,EmployeeAddress FROM Table1
UNION
SELECT EmployerName,EmployerPhone,EmployerAddress FROM Table1
) X
WHERE NOT EXISTS (SELECT Phone FROM Table2 WHERE Phone=X.Phone)
I think this is what you are looking for if I understand your question correctly. Should work for a once-off
DECLARE #restbl TABLE
(
Name varchar(100),
Phone varchar(20),
Addr varchar(100),
[Date] date,
RecType varchar(100)
)
INSERT INTO #restbl
SELECT EmployerName, EmployerPhone, NULL, MAX([Date]), 'Employer'
FROM #tbl
GROUP BY EmployerName, EmployerPhone
INSERT INTO #restbl
SELECT EmployeeName, EmployeePhone, NULL, MAX([Date]), 'Employee'
FROM #tbl
GROUP BY EmployeeName, EmployeePhone;
WITH LatestData (Name, Phone, [Date])
AS
(
SELECT Name, Phone, MAX([Date])
FROM #restbl
GROUP BY Name, Phone
)
INSERT INTO FinalTable (Name, Phone, [Address])
SELECT DISTINCT ld.Name, ld.Phone, ISNULL(tEmployer.EmployerAddress, tEmployee.EmployeeAddress) AS [Address]
FROM LatestData ld
LEFT JOIN #tbl tEmployer ON ld.Name = tEmployer.EmployerName AND ld.Phone = tEmployer.EmployerPhone AND ld.Date = tEmployer.Date
LEFT JOIN #tbl tEmployee ON ld.Name = tEmployee.EmployeeName AND ld.Phone = tEmployee.EmployeePhone AND ld.Date = tEmployee.Date