Chaining results from multiple tables using SQL - mysql

I have a set of tables with following structures
**EntityFields**
fid | pid
1 | 1
2 | 1
3 | 2
4 | 2
5 | 1
**Language**
id | type | value
1 | Entity | FirstEntity
2 | Entity | SecondEntity
1 | Field | Name
2 | Field | Age
3 | Field | Name
4 | Field | Age
5 | Field | Location
Now as you may have understood, the first table gives the EntityField assignment to each Entity. The second table gives out the names for those IDs. What I want to output is something like the following
1 | FirstEntity / Name (i.e. a concat of the Entity and the EntityField name)
2 | FirstEntity / Age
3 | FirstEntity / Location
4 | SecondEntity / Name
5 | SecondEntity / Age
Is this possible?
Thank you for the answers, unfortunately the table structure is something that I cannot change. The table structure it self belongs to another data directory system which is quite flexible and which I am using to pull out data. I know that without providing the necessary background, this table structure looks quite weird, but it is something that works quite well (except in this scenario).
I will try out the examples here and will let you know.

For your current table structure, I think the following will work
SELECT EntityFields.fid, CONCAT(L1.value, ' / ' L2.value)
FROM EntityFields INNER JOIN Language as L1 ON EntityFields.pid=L1.id and L1.type='Entity'
INNER JOIN Language as L2 ON EntityFields.fid=L2.id and L2.type='Field'
ORDER BY EntityFields.fid
However, this query could be made much easier by having a better table structure. For example, with the following structure:
**EntityFields**
fid | pid | uid
1 | 1 | 1
2 | 1 | 2
1 | 2 | 3
2 | 2 | 4
3 | 1 | 5
**Entities**
id | value
1 | FirstEntity
2 | SecondEntity
**Fields**
id | value
1 | Name
2 | Age
3 | Location
you can use the somewhat simpler query:
SELECT uid, CONCAT(Entities.value, Fields.value)
FROM EntityFields INNER JOIN Entities ON EntityFields.pid=Entities.id
INNER JOIN Fields ON EntityFields.fid=Fields.id
ORDER BY uid

Well, I have no idea what you're trying to accomplish here. The fact that you label some records "Entity" and others "Field" and then try to connect them to each other makes it look to me like you are mixing two totally different things in the same table. Why not have an Entity table and a Field table?
You could get the results you seem to want by writing
select fid, le.value, lf.value
from entittyfields e
join language le on e.pid=le.id and type='Entity'
join language lf on e.fid=lf.id and type='Field'
order by fid
But I think you'd be wise to rethink your table design. Perhaps you could explain what you're trying to accomplish.

SELECT ef.fid AS id
, COALESCE(e.value, '-', ef.pid, ' / ', f.value)
AS entity_field
FROM EntityFields ef
JOIN Language AS e
ON e.id = ef.id
AND e.type = 'Entity'
JOIN Language AS f
ON f.id = ef.id
AND f.type = 'Field'
ORDER BY ef.pid
, ef.fid

If I understand your question, which I don't think I do, this is simple. It appears to be a set of very poorly designed tables (Language doing more than one thing, for example). And it appears that the Language table has two types of records: a) The Entity records, which have type='Entity' and b) Field records, which have type='Field'.
At any rate, the way I would approach it is to treat the Language table as if it were two tables:
select ef.fid, Entities.value, Fields.value
from entityfields ef
inner join language Entities
on Entities.id = ef.id
and Entities.type = 'Entity'
inner join language Fields
on Fields.id = ef.id
and Fields.Type = 'Field'
order by 2, 3
First stab, anyway. That should help you get the answer.

Related

mySQL - need help creating a query that references several tables in different ways

I'm struggling with creating a query to fetch data from several tables.
These are the tables:-
I want to query the base table, along with the others, to end up with these rows:-
So, the base table make_ID value needs to reference the makes table to populate the make column in the results. This bit I've managed to do with this query:-
SELECT code, make, Model_ID, time
FROM makes
INNER JOIN base
ON makes.ID = base.make_ID;
But the model column I'm finding tricky, as I need to get the result from make, and use that to choose the right table to get the model. So, looking at the first row, I would need to take the make result, which is brillo, and then use this to reference the brillo table using the id from model_ID, to get finepad.
How can I expand my query to do this? Any insight would be greatly appreciated.
as Jerry said you need a table make_models like this
+---------------+---------+----------+------------+
| make_model_id | make_id | model_id | model |
+---------------+---------+----------+------------+
| 1 | 1 | 1 | finepad |
| 2 | 1 | 2 | harshpad |
| 3 | 2 | 1 | toothbrush |
| 4 | 2 | 2 | toothpaste |
| 5 | 3 | 1 | ovenchips |
| 6 | 3 | 2 | porkpie |
+---------------+---------+----------+------------+
^^ unique identifier
So in your base table instead of referencing model_id you can reference make_model_id which is unique so you can do your join the same way you did with makes table
SELECT code, make, Model_ID, time
FROM makes
INNER JOIN base
ON makes.ID = base.make_ID
INNER JOIN make_models
ON base.make_id = make_models.make_id
AND base.model_id = make_models.model_id;
OR:
INNER JOIN make_models
ON base.make_models_id = make_models.make_models_id ;
If you can't change your model you can create a subquery to build it on the fly. See how I assign the make_id based on id from the makes table
SELECT base.code, makes.make, make_models.model, base.time
FROM base
JOIN make
ON base.make_id = makes.id
JOIN ( SELECT 1 as make_id, model_id, model
FROM brillo
UNION ALL
SELECT 2 as make_id, model_id, model
FROM colgate
UNION ALL
SELECT 3 as make_id, model_id, model
FROM mccaine
) as make_models
ON base.make_id = make_models.make_id
AND base.model_id = make_models.model_id

Removing Records with String Contained in Other Records using 3 tables and Joins

I previously got a great answer (thank you #Paul Spiegel) on removing records from a table whose string was contained at the end of another record. For example, removing 'Farm' when 'Animal Farm' existed) and grouped by a Client Field.
The problem is, in fact, a little more complex and spans three tables, I'd hoped I could extend the logic easily but it turns out to also be challenging (for me). Instead of one table with Client and Term, I have three tables:
Terms
Clients
Look-up-Table (LUT) where I store pairs of TermID and ClientID
I have made some progress since initially posting this question so where I stand is I made the Joins and resultant Select return the fields I want to delete from the Look-up-Table (LUT):
http://sqlfiddle.com/#!9/479c72/45
The final select being:
Select Distinct(C.Title),T2.Term From LUT L
Inner Join Terms T
On L.TermID=T.ID
Inner Join Terms T2
On T.Term Like Concat('% ', T2.Term)
Inner Join Clients C
On C.ID=L.ClientID;
I am in the process of trying to turn this into a Delete with little success.
Append this to your query:
Inner Join LUT L2
On L2.ClientID = L.ClientID
And L2.TermID = T2.ID
That will ensure, that the clients do match and you will get the following result:
| ClientID | TermID | ID | Term | ID | Term | ID | Title | ClientID | TermID |
|----------|--------|----|---------------|----|-----------|----|-------|----------|--------|
| 1 | 2 | 2 | Small Dog | 1 | Dog | 1 | Bob | 1 | 1 |
| 2 | 5 | 5 | Big Black Dog | 3 | Black Dog | 2 | Alice | 2 | 3 |
To delete the corresponding rows from the LUT table, replace Select * with Delete L2.
But deleting the terms is more tricky. Since it's a many-to-many relation, the term may belong to multiple clients. So you can't just delete them. You will need to cleanup up the table in a second statement. That can be done with the following statement:
Delete T
From Terms T
Left Join LUT L
On L.TermID = T.ID
Where L.TermID Is Null
Demo: http://sqlfiddle.com/#!9/b17659/1
Note that in this case the term Medium Dog will also be deleted, since it doesn't belong to any client.

MySQL IN() Operator not working

How to use IN() Operator not working it's.
Those table are example and look the same as the real database I have.I don't have the permitting to add tables or change
Those are the tables:
students
+------+------+
| id | name |
+------+------+
| 1 | ali |
| 2 | man |
| 3 | sos |
+------+------+
Classes
+------+---------+
| c_id | students|
+------+---------+
| 1 | 1,2,3,4 |
| 2 | 88,33,55|
| 3 | 45,23,72|
+------+---------+
When I use this query it return me only the student with id =1
because "id IN (students)" return 1 when the first value are equal.
select name,c_id from students,classes where id IN (students);
when I get the list out on PHP than add it. it work fine.But, this solution need a loop and cost many queries.
select name,c_id from students,classes where id IN (1,2,3,4);
FIND_IN_SET()
the same happened, it's only return 1 but if the value on other position it return 0.
The IN operator works just fine, where it's applicable for what it does.
First, consider restructuring your data to be normalized, and avoid storing values as comma separated lists.
Second, if you absolutely have to deal with columns containing comma separated lists of values, MySQL provides the FIND_IN_SET() function.
FOLLOWUP
Ditch the old-school comma syntax for the join operation, and use the JOIN keyword instead. And relocate the join predicates from the WHERE clause to the ON clause. Fully qualify column references, eg.
SELECT s.name
, c.c_id
FROM students s
JOIN classes c
ON FIND_IN_SET(s.student_id,c.students)
ORDER BY s.name, c.c_id
To reiterate, storing a "comma separated list" in a column is an anti-pattern; it flies against relational theory and normalization, and disregards the best practices around relational databases. O
One might argue for improved performance, but this pattern doesn't improve performance; rather it adds unnecessary complexity in query and DML operations.
You need three tables.
One table students, one table classes, and then one table, say, students_to_classes containing something like
c_id | student_id
1 | 1
1 | 2
1 | 3
1 | 4
2 | 88
and so on.
Then you can query
select c_id from students_to_classes where student_id in (1,2,3,4)
Google "n:m relationship" for background on this.
EDIT
I know you're not specifically asking for another table structure, but this is a way of having a data type (a single number) that works with IN. Please believe me that this is the right way to do it, the reason you run into trouble with something as simple as IN is that you're using a non-standard approach, which, for such a standard problem, is typically not a good idea.
That's not how the function IN is supposed to work. You use IN when you have a list of possible matches like:
instead of:
WHERE id=1 or id=2 or id=3 or id=4
you use:
WHERE id IN (1,2,3,4)
Anyhow, your logic is not correct. The relation of Class and Student is Many-to-Many, thus a third table is needed. Let's call it studend_class, where you can store the students of each class.
student
+------+------+
| id | name |
+------+------+
| 1 | ali |
| 2 | man |
| 3 | sos |
+------+------+
class
+------+---------+
| id | name |
+------+---------+
| 1 | math |
| 2 | english |
| 3 | science |
+------+---------+
student_class
+------------+-------------+
| class_id | student_id |
+------------+-------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 3 | 3 |
+--------------+-----------+
In the example above all students are in math class and ali is also in science class.
Finally, if you whant to know which students are in what class, let's say Math, you can use:
SELECT s.id, s.name, c.name
FROM student s
INNER JOIN student_class sc ON sc.student_id=s.id
INNER JOIN class c ON sc.class_id = c.id
WHERE c.name="math";

Replace nested select query by self join

I recently asked a question here concerning an SQL query: Trouble wrapping head around complex SQL delete query
I now understand that what I'm trying to do is too complex to pull off with a single query or even multiple queries without some way to keep results in between. Therefore I decided to create a bash script (the end result will be something to do with a cronjob so bash is the most straightforward choice).
Consider the following table:
AssociatedClient:
+-----------+-----------------+
| Client_id | Registration_id |
+-----------+-----------------+
| 2 | 2 |
| 3 | 2 |
| 3 | 4 |
| 4 | 5 |
| 3 | 6 |
| 5 | 6 |
| 3 | 8 |
| 8 | 9 |
| 7 | 10 |
+-----------------------------+
What I want to do is select all Registration_ids where the Client_id is in the list of Client_ids associated with a specific Registration_id.
Although I'm pretty noob with SQL, I found this query relatively easy:
SELECT `Registration_id` FROM `AssociatedClient` ac1
WHERE ac1.`Client_id` IN
(SELECT `Client_id` FROM `AssociatedClient` ac2
WHERE ac2.`Registration_id` = $reg_id);
where $reg_id is just a bash variable.
This works but I would like to see it done with a self join, because it looks nicer, especially within a bash script where a lot of character clutter occurs. I'm afraid my SQL skills just don't reach that far.
If I've understood correctly, you should just be able to do a simple self join like so:
SELECT ac1.registration_id
FROM associatedclient ac1
JOIN associatedclient ac2 ON ac2.client_id = ac1.client_id
WHERE ac2.registration_id = $reg_id
So what you are doing is scanning the table once, joining it to itself where the client_id matches. Then you are restricting the joined rows to ones where the 2nd version of the table has a specific id, leaving you with the different permutations of the join on the 1st table, and then just picking the registration_id from those rows.
So, given the example of a variable value of 6, try running the following statement:
SELECT
ac1.client_id AS client_id_1
, ac1.registration_id AS reg_id_1
, ac2.client_id AS client_id_2
, ac2.registration_id AS reg_id_2
FROM associatedclient ac1
JOIN associatedclient ac2 ON ac1.client_id = ac2.client_id
and you'll notice the full set of joins. Then try adding the WHERE restriction and notice which rows come back. Then finally just pick the column you want.
You can check out a SQLFiddle I set up which tests it with a value of 6

How can I use rows in a lookup table as columns in a MySQL query?

I'm trying to build a MySQL query that uses the rows in a lookup table as the columns in my result set.
LookupTable
id | AnalysisString
1 | color
2 | size
3 | weight
4 | speed
ScoreTable
id | lookupID | score | customerID
1 | 1 | A | 1
2 | 2 | C | 1
3 | 4 | B | 1
4 | 2 | A | 2
5 | 3 | A | 2
6 | 1 | A | 3
7 | 2 | F | 3
I'd like a query that would use the relevant lookupTable rows as columns in a query so that I can get a result like this:
customerID | color | size | weight | speed
1 A C D
2 A A
3 A F
The kicker of the problem is that there may be additional rows added to the LookupTable and the query should be dynamic and not have the Lookup IDs hardcoded. That is, this will work:
SELECT st.customerID,
(SELECT st1.score FROM ScoreTable st1 WHERE lookupID=1 AND st.customerID = st1.customerID) AS color,
(SELECT st1.score FROM ScoreTable st1 WHERE lookupID=2 AND st.customerID = st1.customerID) AS size,
(SELECT st1.score FROM ScoreTable st1 WHERE lookupID=3 AND st.customerID = st1.customerID) AS weight,
(SELECT st1.score FROM ScoreTable st1 WHERE lookupID=4 AND st.customerID = st1.customerID) AS speed
FROM ScoreTable st
GROUP BY st.customerID
Until there is a fifth row added to the LookupTable . . .
Perhaps I'm breaking the whole relational model and will have to resolve this in the backend PHP code?
Thanks for pointers/guidance.
tom
You have architected an EAV database. Prepare for a lot of pain when it comes to maintainability, efficiency and correctness. "This is one of the design anomalies in data modeling." (http://decipherinfosys.wordpress.com/2007/01/29/name-value-pair-design/)
The best solution would be to redesign the database into something more normal.
What you are trying to do is generally referred to as a cross-tabulation, or cross-tab, query. Some DBMSs support cross-tabs directly, but MySQL isn't one of them, AFAIK (there's a blog entry here depicting the arduous process of simulating the effect).
Two options come to mind for dealing with this:
Don't cross-tab at all. Instead, sort the output by row id, then AnalysisString, and generate the tabular output in your programming language.
Generate code on-the-fly in your programming langauge to emit the appropriate query.
Follow the blog I mention above to implement a server-side solution.
Also consider #Marek's answer, which suggests that you might be better off restructuring your schema. The advice is not a given, however. Sometimes, a key-value model is appropriate for the problem at hand.