How to query values by line with a join? - sql

given the two tables and values:
Tables
People- Columns: [ID]PK Name
Field - Columns: [ID FieldName]PK FieldValue
Values
People
ID Name
1 Mary
2 John
3 Tony
Field
ID FieldName FieldValue
1 Age 20
1 Country USA
2 Age 21
2 Country USA
3 Age 20
3 Country USA
I would like a query that gives me the names of the people from USA AND have 20 years old. The result should be Mary and Tony.
SELECT name FROM people p
INNER JOIN field f ON f.ID = f.ID
WHERE
(FieldName = 'Age' AND FieldValue= '20') OR
(FieldName = 'Country' AND FieldValue= 'USA'));
The problem with that query is the "OR" between the where clauses. I will get the 3 people, instead of only Mary and Tony. If I use an AND, none result is return.
Do you have some tips?
Thanks in advance :)

What about this?
SELECT name
FROM people p
JOIN field fAge ON p.ID = fAge.ID
AND fAge.FieldName = 'Age'
JOIN field fCountry ON p.ID = fCountry.ID
AND fCountry.FieldName = 'Country'
WHERE fAge.FieldValue = '20'
AND fCountry.FieldValue = 'USA'
Thereby treating the Country and Age data essentially as separate tables. As it seems that the field table is used to store different types of data.
It might be useful to have different database Views on this table for each type of data. This will further simplify the above query.

What you've done here, unfortunately, is implemented the Entity-Attribute-Value (EAV) antipattern. (Antipattern is a fancy word for "something that's usually a bad idea.") It's generally counter to SQL best practices to have a "Field" table like yours, which is why it has an antipattern named after it. "Entity" refers to your ID column (or, by association, a Person record), "Attribute" corresponds to FieldName, and "Value" to FieldValue.
For more information, take a look at this excellent set of slides (this should link directly to the section on EAV; if not, skip to slide 16).
The solution is to refactor your database so that Age and Country are fields in your People table. Then a far simpler query will be possible: SELECT name FROM people WHERE Age = 20 AND Country = 'USA';

You don't say whether you considered and rejected the "standard" design that would Name, Age, and Country in a single table. That would allow substantially easier querying and also type and range protection for the Age values as well as FK/PK protection for the Country values.
If you do have a positive reason to use the Key/Value approach, then you have three choices for your query:
Join the Key/Value table in the query one time for each term you're searching for (see Philip's answer, above).
Use WHERE EXISTS conditions:
SELECT Name FROM People WHERE
EXISTS (SELECT * FROM Field WHERE ID = People.ID AND FieldName = 'Country' AND Fieldvalue = 'USA')
AND EXISTS (SELECT * FROM Field WHERE ID = People.ID AND FieldName = 'Age' AND Fieldvalue = '20')
Use some kind of INTERSECT or SUBTRACT operator if supported in your dialect of SQL.

Related

How can I have UNION respect column aliases?

Sorry for the bad title, I couldn't think of anything better. Feel free to edit.
I have to work with a db table that uses one column to store different types of information (last name if person, company name if company). A nightmare, I know, but it's what it is.
To distinguish the meaning, there is another column with an integer that specifies the type of what's in the name column.
The schema of this table looks as follows (simplified):
ID int
dtype int
name varchar(50)
So, a sample could look like this:
ID dtype name
---------------------------
1 0 Smith
2 0 Trump
3 1 ABC Ltd.
4 1 XYZ Ltd.
I'm trying to normalize this using the following T-SQL code:
WITH companies AS
(
SELECT ID, name AS company
FROM nametable WHERE dtype=1
),
people AS
(
SELECT ID, name AS person
FROM nametable WHERE dtype=0
),
SELECT * FROM companies UNION ALL SELECT * FROM people;
What I hoped to get is a new table with the schema:
ID
dtype
company
person
Or, in table view:
ID dtype person company
------------------------------------------
1 0 Smith
2 0 Trump
3 1 ABC Ltd.
4 1 XYZ Ltd.
Instead, the field is now just called person instead of name but it's still just one field for 2 types of information.
I understand I could just create a new table and insert each partial result into it but it seems there should be a simpler way. Any advice appreciated.
It seems you need case when which helps you
select ID, dtype,case when dtype=0 then name end AS company,
case when dtype=1 then name end AS person
FROM nametable
The CASE statement goes through conditions and return a value when condition is met, from your sample input and output its clear you want to create type wise new column ,so i used case Statement
You don't need to use UNION for this at all. A better approach would be using a bit of aggregation.
SELECT ID,
MAX(CASE WHEN dtype = 0 THEN [name] END) AS company
MAX(CASE WHEN dtype = 1 THEN [name] END) AS person
FROM nametable
GROUP BY ID;
UNION (ALL) doesn't "care" for aliases though. It combines the datasets it receives into 1. All the datasets must have the same definition and the dataset returned will have the same definition. If the datasets have different aliases for columns, the aliases supplied in the first dataset will be used. UNION doesn't detect that the datasets have different names for the columns and therefore return the different names as different columns; that's not what a UNION does.
Edit: well this will give the OP the data they want, however, there's no need for the aggregation. I was honestly expected ID's to be a shared resource; because that's normally the only time you have such horrid tables. The fact that it isn't just makes this table even more confused...

Need query to list rows where two columns are relatively the same with caveat in Postgresql

Most compare two or more columns for the same value between two tables. I need to compare two columns in a table where relative to another column with 3 owner values used. This will be 3 different first names.
I have seen table compares, but this is in one table and data is separated already by a column value. The table is thousands of rows. Owner column is only three different names.
So Joe, Sam and Jim have rows of names. When any of these two people have a common first and last name listed in the row, they will be listed as output with other column data like location and zip. So if Joe and Sam have two common names, the output will list:
Owner,firstname,lastname,location,zip
Joe,Bob,Smith,Dallas,37377
Sam,Bob,Smith,Dallas,37377
Jim had no Bob,Smith,Dallas,37377 so it is not listed in the group with Joe and Sam. In summation, the results will be a few hundred lines of these 3 owners grouped where a common name is found. I will need to use percentages in the query in case some one uses Bobby or Smiths. Therefore a name like Smithson will show up but I will adjust.
I had this all typed out and the page sees it as code so I apologize I had to abbreviate.
The most straightforward thing to use is a self-join, though an EXISTS may be quicker.
First, the self-join:
--note DISTINCT here because if Joe, Bob, and Sam all have records with the name we don't want duplicates
select DISTINCT f.owner, f.firstname, f.lastname, f.location, f.zip
from tablename f
join tablename s on f.firstname = s.firstname and f.lastname = s.lastname and f.owner <> s.owner
Then, with an EXISTS:
select f.owner, f.firstname, f.lastname, f.location, f.zip
from tablename f
WHERE EXISTS(select 1 from tablename s WHERE s.firstname = f.firstname and s.lastname = f.lastname and s.owner <> f.owner)
Of course, if instead of equality you want 'Smith' and 'smithson' to match, you can replace the equality comparison with something like: (f.firstname ilike (s.firstname||'%') OR s.firstname ilike (f.firstname||'%')) you can use that (or any other comparison)

User to location mapping with country state and city in the same table

I have a user table that has among others the fields CityId, StateId, CountryId. I was wondering if it was a good idea to store them[City, State, Country] in separate tables and put their respective ids in the User table or put all the three entities in one table.
While the former is conventional, I am concerned about the extra tables to join and so would want to store all these three different location types in one table like so
RowId - unique row id
LocationType - 1 for City, 2 for state, etc
ActualLocation - Can be a city name if the locationType is 1 and so on..
RowId LocationType ActualLocation
1 1 Waltham
2 1 Yokohama
3 2 Delaware
4 2 Wyoming
5 3 US
6 3 Japan
the problem is I am only able to get the city name for all three fields using a join like this
select L.ActualLocation as CityName,
L.ActualLocation as StateName,
L.ActualLocation as CountryName
from UserTable U,
AllLocations L
WHERE
(L.ID = U.City and L.LocationType= 1)
AND
(L.ID = U.State and L.LocationType = 2)
What worked best for us was to have a country table (totally separate table, which can store other country related information, a state table (ditto), and then the city table with ID's to the other tables.
CREATE TABLE Country (CountryID int, Name varchar(50))
CERATE TABLE State (StateID int, CountryID int, Name varchar(50))
CREATE TABLE City (CityID int, StateID int, Name varchar(50))
This way you can enforce referential integrity using standard database functions and add additional information about each entity without having a bunch of blank columns or 'special' values.
You actually need to select from your location table three times - so you will still have the joins:
select L1.ActualLocation as CityName,
L2.ActualLocation as StateName,
L3.ActualLocation as CountryName
from UserTable U,
AllLocations L1,
AllLocations L2,
AllLocations L3
WHERE
(L1.ID = U.City and L1.LocationType= 1)
AND
(L2.ID = U.State and L2.LocationType = 2)
AND
(L3.ID = U.Country and L3.LocationType = 3)
HOWEVER
Depending what you want to do with this, you might want to think about the model... You probably want a separate table that would contain the location "Springfield Missouri" and "Springfield Illinois" - depending how "well" you want to manage this data, you would need to manage the states and countries as separate inter-related reference data (see, for example, ISO 3361 part 2). Most likely overkill for you though, and it might be easiest just to store the text of the location with the user - not "pure" modeling, but much simplified for simple needs... just pulling the "word" out into a separate table doesn't really give you much other than complex queries

sql query to get data from two tables when one column name is same in both tables

I have a table
custd
name email no
kuldeep kldthakur#gmail.com 99
and second table
pkd
list weight type address name
p1 100 formal delhi kuldeep
Now I want to search the detail by name from the database and the detail should be come from the both table like :-
name email no list weight type address
kuldeep kldthakur#gmail.com 99 p1 100 formal delhi
Please tell the how I can solve this prob. with which query I'm using sql server.
Well, you just need a join. It looks like you have a foreign key on name, so this should work:
select * from custd c
join pkd p on c.name = p.name
This should do:
select pkd.name
,custd.email
,custd.no
,pkd.list
,pkd.weight
,pkd.type
,pkd.address
from custd join pkd on pkd.name=custd.name
But we aware that if name is not a unique key, you can get lots of incorrect results.

SQL select replace integer with string

Goal is to replace a integer value that is returned in a SQL query with the char value that the number represents. For example:
A table attribute labeled ‘Sport’ is defined as a integer value between 1-4. 1 = Basketball, 2 = Hockey, etc. Below is the database table and then the desired output.
Database Table:
Player Team Sport
--------------------------
Bob Blue 1
Roy Red 3
Sarah Pink 4
Desired Outputs:
Player Team Sport
------------------------------
Bob Blue Basketball
Roy Red Soccer
Sarah Pink Kickball
What is best practice to translate these integer values for String values? Use SQL to translate the values prior to passing to program? Use scripting language to change the value within the program? Change database design?
The database should hold the values and you should perform a join to another table which has that data in it.
So you should have a table which has say a list of people
ID Name FavSport
1 Alex 4
2 Gnats 2
And then another table which has a list of the sports
ID Sport
1 Basketball
2 Football
3 Soccer
4 Kickball
Then you would do a join between these tables
select people.name, sports.sport
from people, sports
where people.favsport = sports.ID
which would give you back
Name Sport
Alex Kickball
Gnat Football
You could also use a case statement eg. just using the people table from above you could write something like
select name,
case
when favsport = 1 then 'Basketball'
when favsport = 2 then 'Football'
when favsport = 3 then 'Soccer'
else 'Kickball'
end as "Sport"
from people
But that is certainly not best practice.
MySQL has a CASE statement. The following works in SQL Server:
SELECT
CASE MyColumnName
WHEN 1 THEN 'First'
WHEN 2 THEN 'Second'
WHEN 3 THEN 'Third'
ELSE 'Other'
END
In oracle you can use the DECODE function which would provide a solution where the design of the database is beyond your control.
Directly from the oracle documentation:
Example: This example decodes the value warehouse_id. If warehouse_id is 1, then the function returns 'Southlake'; if warehouse_id is 2, then it returns 'San Francisco'; and so forth. If warehouse_id is not 1, 2, 3, or 4, then the function returns 'Non domestic'.
SELECT product_id,
DECODE (warehouse_id, 1, 'Southlake',
2, 'San Francisco',
3, 'New Jersey',
4, 'Seattle',
'Non domestic') "Location"
FROM inventories
WHERE product_id < 1775
ORDER BY product_id, "Location";
The CASE expression could help. However, it may be even faster to have a small table with an int primary key and a name string such as
1 baseball
2 football
etc, and JOIN it appropriately in the query.
Do you think it would be helpful to store these relationships between integers and strings in the database itself? As long as you have to store these relationships, it makes sense to store it close to your data (in the database) instead of in your code where it can get lost. If you use this solution, this would make the integer a foreign key to values in another table. You store integers in another table, say sports, with sport_id and sport, and join them as part of your query.
Instead of SELECT * FROM my_table you would SELECT * from my_table and use the appropriate join. If not every row in your main column has a corresponding sport, you could use a left join, otherwise selecting from both tables and using = in the where clause is probably sufficient.
definitely have the DB hold the string values. I am not a DB expert by any means, but I would recommend that you create a table that holds the strings and their corresponding integer values. From there, you can define a relationship between the two tables and then do a JOIN in the select to pull the string version of the integer.
tblSport Columns
------------
SportID int (PK, eg. 12)
SportName varchar (eg. "Tennis")
tblFriend Columns
------------
FriendID int (PK)
FriendName (eg. "Joe")
LikesSportID (eg. 12)
In this example, you can get the following result from the query below:
SELECT FriendName, SportName
FROM tblFriend
INNER JOIN tblSport
ON tblFriend.LikesSportID = tblSport.SportID
Man, it's late - I hope I got that right. by the way, you should read up on the different types of Joins - this is the simplest example of one.