MySql Impass - can't move forward - mysql

I currently have this sql statement that I wrote and it works but it's not quite what I want. I've been working on it for hours but can't seem to get any further.
select parent.id as parent_id, parent.subject as parent,s.id,s.subject from (select s.id, s.subject from subjects s where parent_id = 0) parent join subjects s on parent.id = s.parent_id order by parent.subject, s.subject
It's grabbing all the subjects and ordering correctly but I also want to return the parent subject (parent_id = 0) at the top of each grouping. This is because some parents may not have subjects underneath but I still need to return them. Also the ordering is off when I try to do it as I want the parent first then it's child subjects. Hope that makes sense but if not just ask.
Any help would be appreciated.
Thanks
Steve

You're talking about grouping sets of rows by their parent rows.
The only way I know how to do this in MySQL is using the GROUP_CONCAT() function which won't group the subjects by row, but rather create a grouped string.
Here's what you can do:
SELECT
a.id,
a.subject,
GROUP_CONCAT(CONCAT(b.id, ':::', b.subject) ORDER BY b.subject SEPARATOR '|||') AS subjectlist
FROM
subjects a
LEFT JOIN
subjects b ON a.id = b.parent_id
WHERE
a.parent_id = 0
GROUP BY
a.id,
a.subject
ORDER BY
a.subject
So this will give you a result set like:
id | subject | subjectlist
---------------------------------------------------------------------
2 | subj1 | 23:::childsubj1|||28:::childsubj4
3 | subj2 | 18:::childsubj8|||55:::childsubj16
4 | subj3 | NULL
Depending on what language you are using in your application, you may be able to "explode" the subjects string into arrays delimited first by ||| which separates each subject, then ::: which separates that subject's ID and name.
Obviously, the downside of this is you have to make sure that your child subject name does not contain either ||| or ::: or whichever delimiters you decide to use.
Edit: Experimentally, I came up with this alternative solution which may be closer to what you're looking for:
Try:
SELECT
c.subj,
c.id
FROM
(
SELECT
CONCAT('---> ', b.subject) AS subj,
b.id,
CONCAT(a.subject, b.subject) AS orderfactor
FROM
subjects a
INNER JOIN
subjects b ON a.id = b.parent_id
WHERE
a.parent_id = 0
UNION ALL
SELECT
subject AS subj,
id,
subject AS orderfactor
FROM
subjects
WHERE
parent_id = 0
) c
ORDER BY
c.orderfactor
This query should give you a result along the lines of:
subject | id |
----------------------------------------------------------
subj1 | 2 |
---> childsubj1 | 23 |
---> childsubj4 | 28 |
subj2 | 3 |
---> childsubj8 | 18 |
---> childsubj16 | 55 |
subj3 | 4 |
subj4 | 5 |
---> childsubj10 | 79 |

Related

Issue with mysql query that calls column name from another table

I have two tables, one is an index (or map) which helps when other when pulling queries.
SELECT v.*
FROM smv_ v
WHERE (SELECT p.network
FROM providers p
WHERE p.provider_id = v.provider_id) = 'RUU='
AND (SELECT p.va
FROM providers p
WHERE p.provider_id = v.provider_id) = 'MjU='
LIMIT 1;
Because we do not know the name of the column that holds the main data, we need to look it up, using the provider_id which is in both tables, and then query.
I am not getting any errors, but also no data back. I have spent the past hour trying to put this on sqlfiddle, but it kept crashing, so I just wanted to check if my code is really wrong, hence the crashing?
In the above example, I am looking in the providers table for column network, where the provider_id matches, and then use that as the column on smv.
I am sure i have done this before just like this, but after the weekend trying I thought i would ask on here.
Thanks in Advance.
UPDATE
Here is an example of the data:
THis is the providers, this links so no matter what the name of the column on the smv table, we can link them.
+---+---+---------------+---------+-------+--------+-----+-------+--------+
| | A | B | C | D | E | F | G | H |
+---+---+---------------+---------+-------+--------+-----+-------+--------+
| 1 | 1 | Home | network | batch | bs | bp | va | bex |
| 2 | 2 | Recharge | code | id | serial | pin | value | expire |
+---+---+---------------+---------+-------+--------+-----+-------+--------+
In the example above, G will mean in the smv column for recharge we be value. So that is what we would look for in our WHERE clause.
Here is the smv table:
+---+---+-----------+-----------+---+----+---------------------+-----+--+
| | A | B | C | D | E | F | value | va |
+---+---+-----------+-----------+---+----+---------------------+-----+--+
| 1 | 1 | X2 | Home | 4 | 10 | 2016-09-26 15:20:58 | | 7 |
| 2 | 2 | X2 | Recharge | 4 | 11 | 2016-09-26 15:20:58 | 9 | |
+---+---+-----------+-----------+---+----+---------------------+-----+--+
value in the same example as above would be 9, or 'RUU=' decoded.
So we do not know the name of the rows, until the row from smv is called, once we have this, we can look up what column name we need to get the correct information.
Hope this helps.
MORE INFO
At the point of triggering, we do not know what the row consists of the right data because some many of the fields would be empty. The map is there to help we query the right column, to get the right row (smv grows over time depending on whats uploaded.)
1) SELECT p.va FROM providers p WHERE p.network = 'Recharge' ;
2) SELECT s.* FROM smv s, providers p WHERE p.network = 'Recharge';
1) gives me the correct column I need to look up and query smv, using the above examples it would come back with "value". So I need to now look up, within the smv table, C = Recharge, and value = '9'. This should bring me back row 2 of the smv table.
So individually both 1 and 2 queries work, but I need them put together so the query is done on the database server.
Hope this gives more insight
Even More Info
From reading other posts, which are not really doing what I need, i have come up with this:
SELECT s.*
FROM (SELECT
(SELECT p.va
FROM dh_smv_providers p
WHERE p.provider_name = 'vodaphone'
LIMIT 1) AS net,
(SELECT p.bex
FROM dh_smv_providers p
WHERE p.provider_name = 'vodaphone'
LIMIT 1) AS bex
FROM dh_smv_providers) AS val, dh_smv_ s
WHERE s.provider_id = 'vodaphone' AND net = '20'
ORDER BY from_base64(val.bex) DESC;
The above comes back blank, but if i replace net, in the WHERE clause with a column I know exists, I do get the results expected:
SELECT s.*
FROM (SELECT
(SELECT p.va
FROM dh_smv_providers p
WHERE p.provider_name = 'vodaphone'
LIMIT 1) AS net,
(SELECT p.bex
FROM dh_smv_providers p
WHERE p.provider_name = 'vodaphone'
LIMIT 1) AS bex
FROM dh_smv_providers) AS val, dh_smv_ s
WHERE s.provider_id = 'vodaphone' AND value = '20'
ORDER BY from_base64(val.bex) DESC;
So what I am doing wrong, which is net, not showing the value derived from the subquery "value" ?
Thanks
SELECT
v.*,
p.network, p.va
FROM
smv_ v
INNER JOIN
providers p ON p.provider_id = v.provider_id
WHERE
p.network = 'RUU=' AND p.va = 'MjU='
LIMIT 1;
The tables talk to each other via the JOIN syntax. This completely circumvents the need (and limitations) of sub-selects.
The INNER JOIN means that only fully successful matches are returned, you may need to adjust this type of join for your situation but the SQL will return a row of all v columns where p.va = MjU and p.network = RUU and p.provider_id = v.provider_id.
What I was trying to explain in comments is that subqueries do not have any knowledge of their outer query:
SELECT *
FROM a
WHERE (SELECT * FROM b WHERE a)
AND (SELECT * FROM c WHERE a OR b)
This layout (as you have in your question) is that b knows nothing about a because the b query is executed first, then the c query, then finally the a query. So your original query is looking for WHERE p.provider_id = v.provider_id but v has not yet been defined so the result is false.

How to replace the IDs with their corresponding values in SQL?

I working on 4 tables, 1 main table and 3 small tables. The main table is called 'document' and it is referencing and ID from each of the other 3 small tables which are: importance, nature, access_level.
e.g.
(Table1) document
document_id | title | file_name | importance_id | nature_id | access_level_id
-----------------------------------------------------------------------------
1 | Food | food.docx | 1 | 1 | 1
(Table2) importance
importance_id | name
--------------------
1 | High
(Table3) nature
nature_id | name
----------------
1 | General
(Table4) nature
access_level_id | name
----------------------
1 | Public
What I wanted to do is showing all the documents (using SELECT) with their IDs replaced by their corresponding names.. like this:
document_id | title | file_name | importance | nature | access_level
-----------------------------------------------------------------------------
1 | Food | food.docx | High | General | Public
I tried to use this query but it didn't work quite well:
SELECT `document_id`, `title`, `file_name`, `date_of_archiving`, `duration_of_life`, importance.name as importance, nature.name as nature, access_level.name as access_level
FROM document, importance, nature, access_level
WHERE importance.importance_id = document.document_id
AND nature.nature_id = document.nature_id
AND access_level.access_level_id = document.access_level_id;
SO how can I make that happen?
Thanks in advance :)
Use JOINS rather than outdated syntax you are using. It is clearer and easier to debug. And to save typing use table aliases. As follows:
SELECT
document_id,
title,
file_name,
date_of_archiving,
duration_of_life,
i.NAME AS [importance],
n.name as [nature],
al.name as [access_level]
FROM
DOCUMENT D
JOIN importance i ON i.Importance_id = D.importance_id
JOIN nature n ON n.nature_id = D.nature_id
JOIN access_level al on al.access_level_id = D.access_level_id
Use inner join if you want the rows return no null values
SELECT
document_id,
title,
file_name,
date_of_archiving,
duration_of_life,
i.NAME AS [importance],
n.name as [nature],
al.name as [access_level]
FROM
DOCUMENT D
INNER JOIN importance i ON i.Importance_id = D.importance_id
INNER JOIN nature n ON n.nature_id = D.nature_id
INNER JOIN access_level al on al.access_level_id = D.access_level_id
Use this Code:
SELECT
Document_id,
Title,
File_name,
i.name AS Importance,
n.name AS Nature,
s.name AS Access_level
FROM Document d
INNER JOIN importance i ON i.importance_id = d.importance_id
INNER JOIN nature n ON n.nature_id = d.nature_id
INNER JOIN nature s ON s.access_level_id = d.access_level_id

MySQL subquery from same table

I have a database with table xxx_facileforms_forms, xxx_facileforms_records and xxx_facileforms_subrecords.
Column headers for xxx_facileforms_subrecords:
id | record | element | title | neame | type | value
As far as filtering records with element = '101' ..query returns proper records, but when i add subquery to filete aditional element = '4871' from same table - 0 records returned.
SELECT
F.id AS form_id,
R.id AS record_id,
PV.value AS prim_val,
COUNT(PV.value) AS count
FROM
xxx_facileforms_forms AS F
INNER JOIN xxx_facileforms_records AS R ON F.id = R.form
INNER JOIN xxx_facileforms_subrecords AS PV ON R.id = PV.record AND PV.element = '101'
WHERE R.id IN (SELECT record FROM xxx_facileforms_records WHERE record = R.id AND element = '4871')
GROUP BY PV.value
Does this looks right?
Thank You!
EDIT
Thank you for support and ideas! Yes, I left lot of un guessing. Sorry. Some input/output table data might help make it more clear.
_facileforms_form:
id | formname
---+---------
1 | myform
_facileforms_records:
id | form | submitted
----+------+--------------------
163 | 1 | 2014-06-12 14:18:00
164 | 1 | 2014-06-12 14:19:00
165 | 1 | 2014-06-12 14:20:00
_facileforms_subrecords:
id | record | element | title | name|type | value
-----+--------+---------+--------+-------------+--------
5821 | 163 | 101 | ticket | radio group | flight
5822 | 163 | 4871 | status | select list | canceled
5823 | 164 | 101 | ticket | radio group | flight
5824 | 165 | 101 | ticket | radio group | flight
5825 | 165 | 4871 | status | select list | canceled
Successful query result:
form_id | record_id | prim_val | count
1 | 163 | flight | 2
So i have to return value data (& sum those records) from those records where _subrecord element - 4871 is present (in this case 163 and 165).
And again Thank You!
Thank You for support and ideas! Yes i left lot of un guessing.. sorry . So may be some input/output table data might help.
_facileforms_form:
headers -> id | formname
1 | myform
_facileforms_records:
headers -> id | form | submitted
163 | 1 | 2014-06-12 14:18:00
164 | 1 | 2014-06-12 14:19:00
165 | 1 | 2014-06-12 14:20:00
_facileforms_subrecords
headers -> id | record | element | title | name | type | value
5821 | 163 | 101 | ticket | radio group| flight
5822 | 163 | 4871 | status | select list | canceled
5823 | 164 | 101 | ticket | radio group | flight
5824 | 165 | 101 | ticket | radio group | flight
5825 | 165 | 4871 | status | select list | canceled
Succesful Query result:
headers -> form_id | record_id | prim_val | count
1 | 163 | flight | 2
So i have to return value data (& sum those records) from those records where _subrecord element - 4871 is present (in this case 163 and 165).
And again Thank You!
No, it doesn't look quite right. There's a predicate "R.id IN (subquery)" but that subquery itself has a reference to R.id; it's a correlated subquery. Looks like something is doubled up there. (We're assuming here that id is a UNIQUE or PRIMARY key in each table.)
The subquery references an identifier element... the only other reference we see to that identifier is from the _subrecords table (we don't see any reference to that column in _records table... if there's no element column in _records, then that's a reference to the element column in PV, and that predicate in the subquery will never be true at the same time the PV.element='101' predicate is true.
Kudos for qualifying the column references with a table alias, that makes the query (and the EXPLAIN output) much easier to read; the reader doesn't need to go digging around in the table definitions to figure out which table does and doesn't contain which columns. But please take that pattern to the next step, and qualify all column references in the query, including column references in the subqueries.
Since the reference to element isn't qualified, we're left to guess whether the _records table contains a column named element.
If the goal is to return only the rows from R with element='4871', we could just do...
WHERE R.element='4871'
But, given that you've gone to the bother of using a subquery, I suspect that's not really what you want.
It's possible you're trying to return all rows from R for a _form, but only for the _form where there's at least one associated _record with element='4871'. We could get that result returned with either an IN (subquery) or an EXISTS (correlated_ subquery) predicate, or an anti-join pattern. I'd give examples of those query patterns; I could take some guesses at the specification, but I would only be guessing at what you actually want to return.
But I'm guessing that's not really what you want. I suspect that _records doesn't actually contain a column named element.
The query is already restricting the rows returned from PV with those that have element='101'.)
This is a case where some example data and the example output would help explain the actual specification; and that would be a basis for developing the required SQL.
FOLLOWUP
I'm just guessing... maybe what you want is something pretty simple. Maybe you want to return rows that have element value of either '101' or '4913'.
The IN comparison operator is a convenient of way of expressing the OR condition, that a column be equal to a value in a list:
SELECT F.id AS form_id
, R.id AS record_id
, PV.value AS prim_val
, COUNT(PV.value) AS count
FROM xxx_facileforms_forms F
JOIN xxx_facileforms_records R
ON R.form = F.id
JOIN xxx_facileforms_subrecords PV
ON PV.record = R.id
AND PV.element IN ('101','4193')
GROUP BY PV.value
NOTE: This query (like the OP query) is using a non-standard MySQL extension to GROUP BY, which allows non-aggregate expressions (e.g. bare columns) to be returned in the SELECT list.
The values returned for the non-aggregate expressions (in this case, F.id and R.id) will be a values from a row included in the "group". But because there can be multiple rows, and different values on those rows, it's not deterministic which of values will be returned. (Other databases would reject this statement, unless we wrapped those columns in an aggregate function, such as MIN() or MAX().)
FOLLOWUP
I noticed that you added information about the question into an answer... this information would better be added to the question as an EDIT, since it's not an answer to the question. I took the liberty of copying that, and reformatting.
The example makes it much more clear what you are trying to accomplish.
I think the easiest to understand is to use EXISTS predicate, to check whether a row meeting some criteria "exists" or not, and exclude rows where such a row does not exist. This will use a correlated subquery of the _subrecords table, to which check for the existence of a matching row:
SELECT f.id AS form_id
, r.id AS record_id
, pv.value AS prim_val
, COUNT(pv.value) AS count
FROM xxx_facileforms_forms f
JOIN xxx_facileforms_records r
ON r.form = f.id
JOIN xxx_facileforms_subrecords pv
ON pv.record = r.id
AND pv.element = '101'
-- only include rows where there's also a related 4193 subrecord
WHERE EXISTS ( SELECT 1
FROM xxx_facileforms_subrecords sx
WHERE sx.element = '4193'
AND sx.record = r.id
)
--
GROUP BY pv.value
(I'm thinking this is where OP was headed with the idea that a subquery was required.)
Given that there's a GROUP BY in the query, we could actually accomplish an equivalent result with a regular join operation, to a second reference to the _subrecords table.
A join operation is often more efficient than using an EXISTS predicate.
(Note that the existing GROUP BY clause will eliminate any "duplicates" that might otherwise be introduced by a JOIN operation, so this will return an equivalent result.)
SELECT f.id AS form_id
, r.id AS record_id
, pv.value AS prim_val
, COUNT(pv.value) AS count
FROM xxx_facileforms_forms f
JOIN xxx_facileforms_records r
ON r.form = f.id
JOIN xxx_facileforms_subrecords pv
ON pv.record = r.id
AND pv.element = '101'
-- only include rows where there's also a related 4193 subrecord
JOIN xxx_facileforms_subrecords sx
ON sx.record = r.id
AND sx.element = '4193'
--
GROUP BY pv.value

How can I get connected values of two times the same foreign key?

I currently have a situation that could easily be solved with 3 SQL queries, but I wonder if it can be done in one query.
I have the following tables:
symbol similarity
------- ------------
id | name | latex id | base_symbol_id | similar_symbol_id
I want to SELECT so that my result looks like this:
query_result
------------
similarity_id | base_formula_id | base_formula_name | base_formula_latex | similar_formula_id | similar_formula_name | similar_formula_latex
Failed tries
I usual solve similar tasks with JOIN. But this time, the SELECT depends on another attribute I select ... I don't know how to do this. Here is my try (which of course failed):
SELECT `base_symbol_id`, `similar_symbol_id`, `latex`
FROM `similarity`
JOIN `symbol` ON ((`symbol`.`id` = `base_symbol_id`) OR (`symbol`.`id` = `similar_symbol_id`))
gives
base_symbol_id | simlar_symbol_id | latex
10 | 11 | \alpha
10 | 11 | a
select sim.id
,base.id
,base.name
,base.latex
,similar.id
,similar.name
,similar.latex
from similarity as sim
join symbol as base on base.id=sim.base_symbol_id
join symbol as similar on similar.id=sim.similar_symbol_id
Using the given table structure, and making up some random sample inputs in an SQL Fiddle session, the following query would work as you desired:
SELECT T.id as similarity_id,
S1.id as base_formula_id, S1.name as base_formula_name, S1.latex as base_formula_latex,
S2.id as similar_formula_id, S2.name as similar_formula_name, S2.latex as similar_formula_latex
FROM similarity T
LEFT OUTER JOIN symbol S1 ON (T.base_symbol_id = S1.id)
LEFT OUTER JOIN symbol S2 ON (T.similar_symbol_id = S2.id)

Mysql JOIN query MAX value

This is example of the problem i am having. The query should return rows paul and rick because they have the highest rating of the child rows. Instead the query is returning dave and owen, my guess is because they are the first child rows. I am grouping by position and using MAX(child.rating) but the query isn't working like i want it to be. In the real table i have alot of columns that is why i use child.* in the select clause.
mytable
id | name | parentid| position| rating |
1 | mike | 1 | 1 | 6 |
2 | dave | 1 | 2 | 5 |
3 | paul | 1 | 2 | 7 |
4 | john | 1 | 2 | 3 |
5 | mike | 5 | 1 | 8 |
6 | owen | 5 | 2 | 2 |
7 | rick | 5 | 2 | 9 |
8 | jaye | 5 | 2 | 3 |
$getquery = mysql_query("SELECT MAX(child.rating),child.* FROM mytable child
LEFT JOIN mytable parent on parent.parentid=child.parentid
WHERE parent.name LIKE '%mike%' GROUP BY child.position,child.parentid");
while($row=mysql_fetch_assoc($getquery)) {
$id = $row['id'];
$name = $row['name'];
$parentid = $row['parentid'];
if($id==$parentid) {
continue;
}
echo "<p>Name: $name </p>";
}
You can use a subquery in from clause to first figure out what is the maximum rating for each parent and then get the children with that rating:
select *
from mytable c
join
(select parentid, max(rating) as 'maxrating'
from mytable m
group by parentid) as q on c.parentid=q.parentid and c.rating = q.maxrating;
Funny thing, I've just realized what you're looking for. Here is the final query:
select t1.* from mytable t1
left join mytable t2
on t1.parentid = t2.parentid and t1.rating < t2.rating
join mytable parents
on parents.id = t1.parentid
where t2.rating is null and parents.name like '%mike%'
And here is a working example
This is the way mysql's group by works, and is actually working correctly.
There are two way around it, either a subquery or joins that get the top most child, and you probably want to reorder the way your tables are
Here's the join method (if i'm understanding your data correctly):
SELECT child.*
FROM mytable parent
LEFT JOIN mytable child ON parent.parentid=child.parentid
LEFT JOIN mytable child2 ON child.parentid=child2.parentid AND child2.rating > child.rating AND child2.parentid IS NULL
WHERE parent.name LIKE '%mike%' AND parent.position = 1 AND child.position <> 1
This makes the assumption that parants always have a position of 1, and children do not. You may need to also add another bit to the child2 join to remove the possibility of parents having a higher rating than the children?
The 2nd join makes sure there are no other children with a higher rating for each parent.
This must be what you're trying to do (although I'm unsure if're really comparing child's parentid with parent's parentid):
SELECT child.* FROM mytable child
INNER JOIN mytable parent on parent.parentid=child.parentid
LEFT JOIN mytable child2 ON (child2.parentid = parent.parentid AND child2.position = child.position AND child2.rating > child.rating)
WHERE parent.name LIKE '%mike%' AND child2.parentid IS NULL
GROUP BY child.position, child.parentid
HAVING `better` = 0;
Another option would be to use a subquery, but you should check which works faster:
SELECT child.*
FROM (
SELECT MAX(child.rating) maxrating, child.parentid, child.position FROM mytable child
INNER JOIN mytable parent on parent.parentid=child.parentid
WHERE parent.name LIKE '%mike%'
GROUP BY child.position,child.parentid
) h
INNER JOIN mytable child ON (child.parentid = h.parentid AND child.position = h.position AND child.rating = h.maxrating)
performance may be very different on tables of different sizes.
If I haven't got your point right, I still suggest you use INNER JOINs instead of OUTERs if you don't need anything for which there's nothing to join. INNER JOINs are usually way faster.
I actually think second one will work faster on larger tables.
add:
ORDER BY child.rating DESC