Running a query using multiple junction tables - mysql

I have a table listing case studies and another table that list outcomes. A case study can have multiple outcomes so I created a junction table.
I want to run a SQL that will show the case once and each of the outcomes so far I have
SELECT caseSummaries.caseTitle, caseSummaries.caseSynopsis, RESULTS.resultText
FROM JNCT_RESULT_CASESUMMARY
JOIN caseSummaries ON JNCT_RESULT_CASESUMMARY.caseSummary_FK = caseSummaries.caseID
JOIN RESULTS ON JNCT_RESULT_CASESUMMARY.result_FK = RESULTS.result_ID
GROUP BY caseSummaries.caseID;
which gives me one row and only the first outcome of three. How can I show the others in the same row? Will I have to create temporary tables and how is that done? So far I have used a LEFT JOIN but I still get one row. If I don't use GROUP BY I get the caseSummaries.caseTitle repeated thrice and the outcome for each listed. I want to get the case summary once and each outcome appear in a new column.
Thanks,
C
Assume from the question I have two tables
Case studies with three fields:
ID
Title
Synopsis
and another table containing Outcomes:
[bullet]
Apology
Compensation
Policy change
There is a many to many relationship and my SQL needs to show the outcomes for each case study like this:
Case 1 | Title | Synopsis | Apology|Compensation|Policy change
Case 2 |Title | Synopsis | Apology|NULL|Policy change
assuming the Case 2 only has 2 outcomes.
At the moment without the GROUP BY or SELECT DISTINCT I get
Case 1 | Title | Synopsis | Apology
Case 1 | Title | Synopsis |Compensation
Case 1 | Title | Synopsis |Policy change
Case 2 | Title | Synopsis | Apology
Case 2 | Title | Synopsis |Policy change

The group_concat function should do what you need:
SELECT caseSummaries.caseTitle,
caseSummaries.caseSynopsis,
GROUP_CONCAT(RESULTS.resultText)
FROM JNCT_RESULT_CASESUMMARY
JOIN caseSummaries ON JNCT_RESULT_CASESUMMARY.caseSummary_FK = caseSummaries.caseID
JOIN RESULTS ON JNCT_RESULT_CASESUMMARY.result_FK = RESULTS.result_ID
GROUP BY caseSummaries.caseTitle, caseSummaries.caseSynopsis;

Related

ER_NON_UNIQ_ERROR and how to design tables correctly

I have come across this problem and I've tried to solve it few days now.
Let's say I have following tables
properties
-----------------------------------------
| id | address | building_material |
-----------------------------------------
| 1 | Street 1 | 1 |
-----------------------------------------
| 2 | Street 2 | 2 |
-----------------------------------------
building_materials
-----------------------------
| id | building_material |
-----------------------------
| 1 | Wood |
-----------------------------
| 2 | Stone |
-----------------------------
Now. I would like to provide an API where you could send a request and ask for every property that has building material of wood. Like this:
myapi.com/properties?building_material=Wood
So I would like to query database like this (I want to return the string value of building_material not the numeric value):
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE building_material = "Wood"
But this will give me an error
Column 'building_material' in where clause is ambiguous
Also if I want to get property with id of 1.
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE id = 1
Column 'id' in where clause is ambiguous
I understand that the error means that I have same column name in two tables and I don't specify which id I want like p.id.
Problem is I don't know how many query parametes API user is going to send and I would like to avoid looping through them and changing id to p.id and building_material to bm.building_material. Also I don't want that user has to send request to the API like this
myapi.com/properties?bm.building_material=Wood
I've thought about changing the properties table building_material to fk_building_material and changing properties table id to property_id.
I just don't like the idea that on client side I would then have to refer property's building material as fk_building_material. Is this a valid method to solve this problem or what would be the correct way of designing these tables?
The query mentions two tables, so all the columns in both tables are "on the table" for use anywhere in the query.
In one table building_material is an "id" for linking to the other table; in the other table, it is a string. While this is possible, it is confusing to the reader. And to the parser. To resolve the confusion, you must qualify building_material with which one you want; that is done with a table alias (or table) in front (as you did in all other places).
There are two ids are all ambiguous. But this is the "convention" used by table designers. So, it is OK for an id in one table to be different than the id in the other table. (p.id refers to one thing in one table; bm.id refers to another in another table.)
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE bm.building_material = "Wood" -- Note "bm."

Joined query missing records

I have a database that contains a people table and another table with names for those people. For each person, there is at least one record in the names table, with one of those being set as the 'person_default_name_id' for that person, but other variations of that name in different languages. The idea is that the user who looks up the table will have a preferred language set (e.g. English, Spanish, Russian) and a preferred script set, which is based on their preferred language (e.g. if their preferred language is English or Spanish, the script would be "Latin", while if the preferred language is Russian, the script would be "Cyrillic"). It's a little complex and I'm wanting to display a list of names, but only display one name per person, and that one chosen name should be shown according to the best-fit for the user's chosen language and script.
The code below is what I'm trying:
SELECT
people.person_id,
names.name
FROM
`people`
LEFT JOIN
`names` ON names.person_id=people.person_id
LEFT JOIN
`languages` ON names.language_id = languages.language_id
LEFT JOIN
`language_scripts` ON languages.language_id = language_scripts.language_id
WHERE
(
/* 1st preference - display the default name for the person IF the default name's language writing system matches the user's writing system */
(people.person_default_name_id=names.name_id AND language_scripts.script_id = :user_script_id)
OR
/* 2nd preference - display the alternative name in the user's chosen language if an alternative name exists in that language */
names.language_id = :user_language_id
OR
/* 3rd preference - display the alternative name in the user's chosen writing system if an alternative name exists in that writing system */
language_scripts.script_id = :user_script_id
)
GROUP BY
people.person_id
ORDER BY
names.name ASC
Example data is below:
Table: people
person_id | person_default_name_id
------------------------------------
1 | 2
Table: names
name_id | name | person_id | language_id
--------------------------------------------
1 | George | 1 | 1
2 | Jorge | 1 | 2
3 | Джордж | 1 | 3
Table: languages
language_id | language
------------------------
1 | English
2 | Spanish
3 | Russian
Table: language_scripts
language_script_id | language_id | script_id
----------------------------------------------
1 | 1 | 1
2 | 2 | 1
3 | 3 | 2
Table: scripts
script_id | script
----------------------
1 | Latin
2 | Cyrillic
I'm finding that some of the expected records are not coming through. I'm guessing that there are improvements I could make to my query, but my skills are not quite advanced enough to know the best path. Can anyone see what I'm doing wrong?
I would suggest you put your where clause conditions in your select statement and return a "score" for each record. Remove it entirely from your where clause and it may give you insight into why you have missing records if they are returned with a 0 score.
Case when condition Then 5
when condition then 4
Etc...
else 0
End case
Once you have your results scored, you can order by your score descending and take the first one per person. Or add additional outer queries to only return the rows having the max score per person.
Apologies for answering from my phone.

export phpList subscribers via sql in mysql database

For some reason, I am unable to export a table of subscribers from my phpList (ver. 3.0.6) admin pages. I've searched on the web, and several others have had this problem but no workarounds have been posted. As a workaround, I would like to query the mySQL database directly to retrieve a similar table of subscribers. But I need help with the SQL command. Note that I don't want to export or backup the mySQL database, I want to query it in the same way that the "export subscribers" button is supposed to do in the phpList admin pages.
In brief, I have two tables to query. The first table, user contains an ID and email for every subscriber. For example:
id | email
1 | e1#gmail.com
2 | e2#gmail.com
The second table, user_attribute contains a userid, attributeid, and value. Note in the example below that userid 1 has values for all three possible attributes, while userid's 2 and 3 are either missing one or more of the three attributeid's, or have blank values for some.
userid | attributeid | value
1 | 1 | 1
1 | 2 | 4
1 | 3 | 6
2 | 1 | 3
2 | 3 |
3 | 1 | 4
I would like to execute a SQL statement that would produce a row of output for each id/email that would look like this (using id 3 as an example):
id | email | attribute1 | attribute2 | attribute3
3 | e3#gmail.com | 4 | "" | "" |
Can someone suggest SQL query language that could accomplish this task?
A related query I would like to run is to find all id/email that do not have a value for attribute3. In the example above, this would be id's 2 and 3. Note that id 3 does not even have a blank value for attributeid3, it is simply missing.
Any help would be appreciated.
John
I know this is a very old post, but I just had to do the same thing. Here's the query I used. Note that you'll need to modify the query based on the custom attributes you have setup. You can see I had name, city and state as shown in the AS clauses below. You'll need to map those to the attribute id. Also, the state has a table of state names that I linked to. I excluded blacklisted (unsubscribed), more than 2 bounces and unconfirmed users.
SELECT
users.email,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=1
) AS name,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=3
) AS city,
(SELECT st.name
FROM `phplist_user_user_attribute` attrs
LEFT JOIN `phplist_listattr_state` st
ON attrs.value = st.id
WHERE
attrs.userid = users.id and
attributeid=4
) AS state
FROM
`phplist_user_user` users
WHERE
users.blacklisted=0 and
users.bouncecount<3 and
users.confirmed=1
;
I hope someone finds this helpful.

MySQL query get column value similar to given

Sorry if my question seems unclear, I'll try to explain.
I have a column in a row, for example /1/3/5/8/42/239/, let's say I would like to find a similar one where there is as many corresponding "ids" as possible.
Example:
| My Column |
#1 | /1/3/7/2/4/ |
#2 | /1/5/7/2/4/ |
#3 | /1/3/6/8/4/ |
Now, by running the query on #1 I would like to get row #2 as it's the most similar. Is there any way to do it or it's just my fantasy? Thanks for your time.
EDIT:
As suggested I'm expanding my question. This column represents favourite artist of an user from a music site. I'm searching them like thisMyColumn LIKE '%/ID/%' and remove by replacing /ID/ with /
Since you did not provice really much info about your data I have to fill the gaps with my guesses.
So you have a users table
users table
-----------
id
name
other_stuff
And you like to store which artists are favorites of a user. So you must have an artists table
artists table
-------------
id
name
other_stuff
And to relate you can add another table called favorites
favorites table
---------------
user_id
artist_id
In that table you add a record for every artist that a user likes.
Example data
users
id | name
1 | tom
2 | john
artists
id | name
1 | michael jackson
2 | madonna
3 | deep purple
favorites
user_id | artist_id
1 | 1
1 | 3
2 | 2
To select the favorites of user tom for instance you can do
select a.name
from artists a
join favorites f on f.artist_id = a.id
join users u on f.user_id = u.id
where u.name = 'tom'
And if you add proper indexing to your table then this is really fast!
Problem is you're storing this in a really, really awkward way.
I'm guessing you have to deal with an arbitrary number of values. You have two options:
Store the multiple ID's in a blob object in JSON format. While MySQL doesn't have JSON functions built in, there are user defined functions that will extract values for you, etc.
See: http://blog.ulf-wendel.de/2013/mysql-5-7-sql-functions-for-json-udf/
Alternatively, switch to PostGres
Add as many columns to your table as the maximum number of ID's you expect to have. So if /1/3/7/2/4/8/ is the longest entry, have 6 columns in your table. Reason this is bad: you'll have sparse columns that'll unnecessarily slow your tables.
I'm sure you could write some horrific regex to accomplish the task, but I caution on using complex regex's on enormous tables.

MySQL excluding results on a JOIN

I'm working on a restaurant CMS app. I have a many-to-many relationship between 2 tables, menu_sections and menu_items. The relationship is maintained with a table in between called menu_relationships.
As an example let's say the menu section called Snacks (menu_section_id = 1) contains a menu item called Pretzels (menu_item_id = 1) and the menu section called Desserts (menu_section_id = 2) contains a menu item called Ice Cream (menu_item_id = 2), but Ice Cream is also contained within another menu section called Kids Food (menu_section_id = 3). So there would be 3 rows in the menu_relationships table to map out these 3 relationships. The relationship table would look like this:
---------------------------------------
| menu_section_id | menu_item_id |
|=====================================|
| 1 | 1 |
|-------------------------------------|
| 2 | 2 |
|-------------------------------------|
| 3 | 2 |
---------------------------------------
So far so good.
I want to generate a result set that will return the names of all menu items except for menu items with a given menu_section_id. So to return the menu item names, I have a join on the menu_items table. Here's the SQL:
SELECT menu_section_id, menu_items.menu_item_id, menu_item_name
FROM menu_relationships
JOIN menu_items
ON menu_items.menu_item_id = menu_relationships.menu_item_id
WHERE menu_section_id != 2
The result set which will give me a row for each relationship that doesn't contain a given menu_section_id. With the example data I would be getting 2 rows back from the relationship table:
-----------------------------------------------------------
| menu_section_id | menu_item_id | menu_item_name |
|======================================|==================|
| 1 | 1 | Pretzels |
|--------------------------------------|------------------|
| 3 | 2 | Ice Cream |
-----------------------------------------------------------
But what I want is to exclude the menu item altogether from the result set, if it has ANY relationship to the specified menu_section_id. In other words, in the case of this example , I only want to return rows for menu items that have no relationship mappings at all to a menu_section_id of 2, I only want to return the Pretzels row.
I've tried various things with GROUP BY and HAVING using the bit_xor() aggregate function, but so far no luck at all in getting what I want.
I probably could have taken less time to explain that but I wanted it to be a clear as I can make it. I hope it is. Can anyone help?
This is a wonderful case for the use of LEFT OUTER JOIN because it includes all rows from your left-hand table and matches where it can, returning NULL for any non-match.
Building on Mark Breyer's sample query from above, see this example:
SELECT R.menu_section_id, I.menu_item_id, I.menu_item_name
FROM menu_items AS I
LEFT OUTER JOIN menu_relationships R on (R.menu_item_id=I.menu_item_id) AND (R.menu_section_id = 2)
The mysql optimizer may actually rewrite this as a subquery - i'm not an optimization expert by any means - I'd take a look at the way your indexes are built and see if this type of join makes sense for your schema. I'd also test to see if it's actually faster because it's actually less semantic.
There are many ways to do this. Here is one example using WHERE ... NOT IN (...):
SELECT
R.menu_section_id,
I.menu_item_id,
I.menu_item_name
FROM menu_items AS I
JOIN menu_relationships AS R
ON R.menu_item_id = I.menu_item_id
WHERE I.menu_item_id NOT IN
(
SELECT menu_item_id
FROM menu_relationships
WHERE menu_section_id = 2
)
I would use a subquery for this, getting me every menu_item_id which has the menu_section_id 2 and then using NOT IN. Here you go:
SELECT menu_section_id, menu_items.menu_item_id, menu_item_name
FROM menu_relationships
JOIN menu_items
ON menu_items.menu_item_id = menu_relationships.menu_item_id
WHERE menu_relationships.menu_item_id NOT IN (
SELECT menu_item_id
FROM menu_relationships
WHERE menu_section_id = 2
);
I was going to suggest a subquery a well, except that I wanted to mention that subqueries can dramatically affect performance on your site. You may want to consider options for caching to avoid serious load time hangups due to things like this.
In most cases you'll be ok, but if you're only showing us part of the issue and just not mentioning the irrelevant details then you could very well be building a site where you run 100 of these queries on a page, for example, because someone mentioned it here without mentioning the compounded overhead things like this can result in...
Like I said though, you'll probably be fine. Just don't do a subquery within a subquery unless you want to restart your server.