Database structure with two 1-n connections.
User table
==========
user_id
Attribute table
===============
attribute_id
user_id
attribute_name
Attribute_Value table
=====================
attribute_value_id
attribute_id
attribute_value
Is there a way that I can receive the data in the following row style:
user_id | firstname | lastname
---------------------------
1 | Simon | Smith
2 | John | Doe
Where name is the first attribute_name entry from the Attribute table and lastname the second.
Since you know the names of the values that you are looking for, a join will do the trick.
select
u.user_id,
a1.attribute_value as firstname,
a2.attribute_value as lastname
from User u
inner join Attribute a1
on u.user_id = a1.user_id
and a1.attribute_name = 'name'
inner join Attribute_Value v1
on a1.attribute_id = v1.attribute_id
inner join Attribute a2
on u.user_id = a2.user_id
and a2.attribute_name = 'lastname'
inner join Attribute_Value v2
on a2.attribute_id = v2.attribute_id
or so (did not run this). Use left joins if not everybody has a first or last name.
The only way you can do it with a dynamic number of columns is to use dynamic sql- use one sql statement to generate another by string concatenation.
Edit: I am not sure this is possible in mysql since there's not pivot command. Let me know if you want to see an example query from MS Sql I wrote. It is a similar data model, but there's just a lot more fields / joins.
Also I want to point out that you have a flaw in your design if attribute names wind up not being unique.
Related
I'm new to SQL. I have a simple problem with getting the results from two different tables.
I have two tables in a database. The first table has a column with an id reference, which corresponds to rows in the second table. What SELECT do I need to perform to get a result such that the ids are repalced by all of the values in the second table. To visualize the tables I am discussing:
TABLE_USERS
===========
id username group
-- -------- -----
1 jim A
2 alice A
3 brandon B
TABLE_GROUPS
============
id groupname members
-- --------- -------
A designer 134
B photographer 39
DESIRED_SELECTION
=================
id username group
-- -------- -----
1 jim designer
2 alice designer
3 brandon photographer
Thanks!
You do, in fact, want to JOIN the two tables:
SELECT * FROM
TABLE_USERS LEFT JOIN TABLE_GROUPS
ON TABLE_USERS.group = TABLE_GROUPS.id
The trick of joining tables is to find the values that must match in the two tables, and use the on to tell SQL to match them. This table has a ID column to let you do that = you will join the table, ON, and then list the values that need to be equal.
If you do not want all of the columns in both tables, you can simply list only the columns you need in your final query. This means that instead of Select *, you list the columns you want. As shown below, if a column appears with the same name in both tables, you need to prepend the table name, so that SQL know which value you want.
SELECT TABLE_USERS.ID, Username, Groupname
FROM TABLE_USERS
LEFT JOIN TABLE_GROUPS
ON TABLE_USERS.group = TABLE_GROUPS.id
You want a JOIN:
SELECT
u.id,
username,
groupname
FROM
TABLE_USERS AS u
LEFT JOIN TABLE_GROUPS AS g
ON u.group = g.id
Edit: this isn't to be a dynamic output, the output view structure is fixed.
I am trying to create a SQL Server view that shows a single fixed column row for each user, and flattens out an associated one to many table into that row.
Although the associated table has a one to many relationship, the output table structure is limited to 4 elememts form that table.
My table structure is like so:
User (Id, FirstName, LastName)
Assessment (Id, Date, Location, User_Id)
Topics (Id, Topic, Assessment_Id)
Where the Assessment is joined to the User by the User_Id (One 2 One), and the Topics are joined to the Assessment by the Assessment_Id.
So, if I have three topics for an assessment, I'd want the view to look something like:
User_Id | FirstName | LastName | Date | Location | Topic1 | Topic2 | Topic3 | Topic4 |
1 | dave | toby | 2/2/11 | In situ | apples | pears | lemons | NULL |
My current SQL looks like this:
SELECT User.Id, User.FirstName, User.LastName, Assessment.Date, Assessment.Location, Topic.Topic
FROM User LEFT OUTER JOIN
Assessment INNER JOIN
Topic ON Assessment.Id = Topic.Assessment_Id ON
User.Id = Assessment.User_Id
But this returns a row for each concern - it doesn't compress them to one line. I've played with a few different joins, but haven't been able to get the behaviour I want.
Is it possible to do this in a view?
What do I need to do to make it happen??
Thanks!
There is no such JOIN. SQL has a fixed column output: so you can't add arbritrary numbers of columns. It doesn't matter if it's a view, direct or in a stored procedure.
There are 2 main options
concatenate the many rows into one column which is a popular questions here on SO. One random solution using XML PATH
use dynamic SQL to add a column per row in a stored procedure.
Note: PIVOT is fixed column output too
Edit: for a maximum of 4 child rows
SELECT
P.col1, P.col2,
C1.col1 AS Topic1,
C2.col1 AS Topic2,
C3.col1 AS Topic2,
C4.col1 AS Topic4
FROM
Parent P
LEFT JOIN
Child C1 ON P.Key = C1.FKey AND C1.ID = 1
LEFT JOIN
Child C2 ON P.Key = C2.FKey AND C2.ID = 2
LEFT JOIN
Child C3 ON P.Key = C3.FKey AND C3.ID = 3
LEFT JOIN
Child C4 ON P.Key = C4.FKey AND C4.ID = 4
You can use PIVOT too but I prefer the simpler self joins.
Take a look at PIVOT table functionality - e.g. http://www.help-sql.info/27/9/610208.html and http://blog.sqlauthority.com/2008/05/22/sql-server-pivot-table-example/
Although you will need to know the AssessmentId's before you can write the PIVOT
What the simplest way to sub-query a variable number of rows into fields of the parent query?
PeopleTBL
NameID int - unique
Name varchar
Data: 1,joe
2,frank
3,sam
HobbyTBL
HobbyID int - unique
HobbyName varchar
Data: 1,skiing
2,swimming
HobbiesTBL
NameID int
HobbyID int
Data: 1,1
2,1
2,2
The app defines 0-2 Hobbies per NameID.
What the simplest way to query the Hobbies into fields retrieved with "Select * from PeopleTBL"
Result desired based on above data:
NameID Name Hobby1 Hobby2
1 joe skiing
2 frank skiing swimming
3 sam
I'm not sure if I understand correctly, but if you want to fetch all the hobbies for a person in one row, the following query might be useful (MySQL):
SELECT NameID, Name, GROUP_CONCAT(HobbyName) AS Hobbies
FROM PeopleTBL
JOIN HobbiesTBL USING NameID
JOIN HobbyTBL USING HobbyID
Hobbies column will contain all hobbies of a person separated by ,.
See documentation for GROUP_CONCAT for details.
I don't know what engine are you using, so I've provided an example with MySQL (I don't know what other sql engines support this).
Select P.NameId, P.Name
, Min( Case When H2.HobbyId = 1 Then H.HobbyName End ) As Hobby1
, Min( Case When H2.HobbyId = 2 Then H.HobbyName End ) As Hobby2
From HobbyTbl As H
Join HobbiesTbl As H2
On H2.HobbyId = H.HobbyId
Join PeopleTbl As P
On P.NameId = H2.NameId
Group By P.NameId, P.Name
What you are seeking is called a crosstab query. As long as the columns are static, you can use the above solution. However, if you want to dynamic build the columns, you need to build the SQL statement in middle-tier code or use a reporting tool.
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.
I feel like an idiot asking this...
Table 1: users
id serial
person integer
username char(32)
Table 2:persons
id serial
name char(16)
Can I run a query that returns the name field in persons by providing the username in users?
users
1 | 1 | larry123
persons
1 | larry
2 | curly
SQL?
select name from persons where users.person=persons.id and users.username='larry123';
with the desired return of
larry
I have been doing it with two passes until now and think maybe a nested select using a join is what I need
1 | larry
It sounds like you're asking how to do a join in SQL:
SELECT
name
FROM
users JOIN persons ON (users.person = persons.id)
WHERE
users.username = 'larry123';
that is almost the query you wrote. All you were missing was the join clause. You could also do that join like this:
SELECT name
FROM users, persons
WHERE
users.person = persons.id
AND users.username = 'larry123';
I suggest finding a well-written introduction to SQL.