query returning multidimensional grouped arrays - mysql

In the hypothetical scenario where I have a couple of database tables, in a one to many relationship. I am trying to find a nice query function so that I can return each row in table A with a nested array for all the linked rows from table B.
SELECT a.id AS id,a.name as name,b.id AS b_id,b.name AS b_name FROM a,b WHERE a.id=b.eid;
will return only one result
SELECT
a.id AS id,
a.name as name,
GROUP_CONCAT(b.id) AS b_id,
GROUP_CONCAT(b.name) AS b_name
FROM a,b
WHERE a.id = b.eid;
whereas this returns what I want but I'm after an array not a string (there might be commas in b.name)
What am I missing?

You can use separator different from comma:
GROUP_CONCAT(b.name SEPARATOR ';') AS b_name

It isn't possible to return anything other than a MySQL datatype, so the short answer is no you cannot return a array (nested or otherwise) of results from a subquery. You'll also see some pretty drastic performance hits trying to build string concatenated values from the foreign, the sub-string order will be implicit and, as you've alluded to, you'll have problems selecting a suitable delimiter.
It almost certainly going to be more efficient use a simple JOIN to create a single result set. Any duplicates on the "one" side of the relationship should be handled programmatically (which is preferable, in any sense, to handling string splitting on multiple concatenated fields).

Related

How to get record where find_in_set from a comma saperate string.[MySql]

I have a comma separate string like "1,2,3" and a column in table is also contain comma separate value like "1,2,4,5,3" how to get all records that match any value to any value.
for example
id---category
1---1,2,4,5
2---1,2,3,6
3---2,3,5
If I search for string "1,2,3" then I should get record the category contains 1 or 2 or 3 or 1,2 or 1,3 or 2,3 or 1,2,3. It should not return the duplicate value where as we can group them.
Is it possible to get all record with a single query.
Try this:
select * from table where category IN (1,2,3);
You should not be storing lists of ids in strings. Here are reasons why:
Values should be stored using the correct type. int <> string.
SQL has lousy string processing functions.
Foreign keys should be properly declared.
SQL will not be able to optimize these queries.
SQL has a great way to store lists. It is called a table not a string.
But, sometimes you are stuck with someone else really, really, really, really, really bad data modeling decisions. You can do something, using regular expressions:
where category regexp replace($string, ',', '|')
or perhaps more accurately:
where concat(',', category, ',') regexp concat(',', replace($string, ',', ',|,'), ',')

Union query to combine results of 3 tables

I am relatively new to coding so please have patience.
I am trying to combine data from 3 tables. I have managed to get some data back but it isn't what i need. Please see my example below.
select oid, rrnhs, idnam, idfnam, dte1, ta
as 'access type' from person
left join
(select fk_oid, min(dte), dte1, ta
from
((Select fk_oid,min(accessdate) as dte, accessdate1 as dte1, accesstype as ta
from vascularpdaccess
where isnull(accesstype)=false group by fk_oid)
union
(Select fk_oid, min(hpdate) as dte, hpdate as dte1, HPACCE as ta
from hdtreatment
where isnull(hptype)=false group by fk_oid)) as bla
group by fk_oid) as access
on person.oid=access.fk_oid
where person.rrnhs in (1000010000, 2000020000, 3000030000)
My understanding with a union is that the columns have to be of the same data type but i have two problems. The first is that accesstype and hpacce combine in to a the same column as expected, but i dont want to actually see the hpacce data (dont know if this is even possible).
Secondly, the idea of the query is to pull back a patients 'accesstype' date at the first date of hpdate.
I dont know if this even makes sens to you guys but hoping someone can help..y'all are usually pretty nifty!
Thanks in advance!
Mikey
All queries need to have the same number of columns in the SELECT statement. It looks like you first query has the max number of columns, so you will need to "pad" the other to have the same number of columns. You can use NULL as col to create the column with all null values.
To answer the question (I think) you were asking... for a UNION or UNION ALL set operation, you are correct: the number of columns and the datatypes of the columns returned must match.
But it is possible to return a literal as an expression in the SELECT list. For example, if you don't want to return the value of HPACCE column, you can replace that with a literal or a NULL. (If that column is character datatype (we can't tell from the information provided in the question), you could use (for example) a literal empty string '' AS ta in place of HPACCE AS ta.
SELECT fk_oid
, MIN(HPDATE) AS dte
, hpdate AS dte1
, NULL AS ta
-- -------------------- ^^^^
FROM hdtreatment
Some other notes:
The predicate ISNULL(foo)=FALSE can be more simply expressed as foo IS NOT NULL.
The UNION set operator will remove duplicate rows. If that's not necessary, you could use a UNION ALL set operator.
The subsequent GROUP BY fk_oid operation on the inline view bla is going to collapse rows; but it's indeterminate which row the values from dte1 and ta will be from. (i.e. there is no guarantee those values will be from the row that had the "minimum" value of dte.) Other databases will throw an exception/error with this statement, along the lines of "non-aggregate in SELECT list not in GROUP BY". But this is allowed (without error or warning) by a MySQL specific extension to GROUP BY behavior. (We can get MySQL to behave like other databases and throw an error of we specify a value for sql_mode that includes ONLY_FULL_GROUP_BY (?).)
The predicate on the outer query doesn't get pushed down into the inline view bla. The view bla is going to materialized for every fk_oid, and that could be a performance issue on large sets.
Also, qualifying all column references would make the statement easier to read. And, that will also insulate the statement from throwing an "ambiguous column" error in the future, when a column named (e.g.) ta or dte1 is added to the person table.

Query for multiple conditions in MySQL

I want to be able to query for multiple statements when I have a table that connects the id's from two other tables.
My three tables
destination:
id_destination, name_destination
keyword:
id_keyword, name_keyword
destination_keyword:
id_keyword, id_destination
Where the last one connects ids from the destination- and the keyword table, in order to associate destination with keywords.
A query to get the destination based on keyword would then look like
SELECT destination.name_destination FROM destination
NATURAL JOIN destination_keyword
NATURAL JOIN keyword
WHERE keyword.name_keyword like _keyword_
Is it possible to query for multiple keywords, let's say I wanted to get the destinations that matches all or some of the keywords in the list sunny, ocean, fishing and order by number of matches. How would I move forward? Should I restructure my tables? I am sort of new to SQL and would very much like some input.
Order your table joins starting with keyword and use a count on the number of time the destination is joined:
select
d.id_destination,
d.name_destination,
count(d.id_destination) as matches
from keyword k
join destination_keyword dk on dk.keyword = k.keyword
join destination d on d.id_destination = dk.id_destination
where name_keyword in ('sunny', 'ocean', 'fishing')
group by 1, 2
order by 3 desc
This query assumes that name_keyword values are single words like "sunny".
Using natural joins is not a good idea, because if the table structures change such that two naturally joined tables get altered to have columns the same name added, suddenly your query will stop working. Also by explicitly declaring the join condition, readers of your code will immediately understand how the tables are jones, and can modify it to add non-key conditions as required.
Requiring that only key columns share the same name is also restrictive, because it requires unnatural column names like "name_keyword" instead of simply "name" - the suffix "_keyword" is redundant and adds no value and exists only because your have to have it because you are using natural joins.
Natural joins save hardly any typing (and often cause more typing over all) and impose limitations on join types and names and are brittle.
They are to be avoided.
You can try something like the following:
SELECT dest.name_destination, count(*) FROM destination dest, destination_keyword dest_key, keyword key
WHERE key.id_keyword = dest_key.id_keyword
AND dest_key.id_destination = dest.id_destination
AND key.name_keyword IN ('sunny', 'ocean', 'fishing')
GROUP BY dest.name_destination
ORDER BY count(*), dest.name_destination
Haven't tested it, but if it is not correct it should show you the way to accomplish it.
You can do multiple LIKE statements:
Column LIKE 'value1' OR Column LIKE 'value2' OR ...
Or you could do a regular expression match:
Column LIKE 'something|somtthing|whatever'
The trick to ordering by number of matches has to do with understanding the GROUP BY clause and the ORDER BY clause. You either want one count for everything, or you want one count per something. So for the first case you just use the COUNT function by itself. In the second case you use the GROUP BY clause to "group" somethings/categories that you want counted. ORDER BY should be pretty straight forward.
I think based on the information you have provided your table structure is fine.
Hope this helps.
DISCLAIMER: My syntax isn't accurate.

MySQL IN() clause multiple returns

I have a special data environment where I need to be returned data in a certain way to populate a table.
This is my current query:
SELECT
bs_id,
IF(bs_board = 0, 'All Boards', (SELECT b_name FROM certboards WHERE b_id IN (REPLACE(bs_board, ';', ',')))) AS board
FROM boardsubs
As you can see I have an if statement then a special subselect.
The reason I have this is that the field bs_board is a varchar field containing multiple row IDs like so:
1;2;6;17
So, the query like it is works fine, but it only returns the first matched b_name. I need it to return all matches. For instance in this was 1;2 it should return two boards Board 1 and Board 2 in the same column. Later I can deal with adding a <br> in between each result.
But the problem I am dealing with is that it has to come back in a single column both name, or all names since the field can contain as many as the original editor selected.
This will not work the way you're thinking it will work.
Let's say bs_board is '1;2;3'
In your query, REPLACE(bs_board, ';', ',') will resolve to '1,2,3', which is a single literal string. This makes your final subquery:
SELECT b_name FROM certboards WHERE b_id IN ('1,2,3')
which is equivalent to:
SELECT b_name FROM certboards WHERE b_id = '1,2,3'
The most correct solution to the problem is to normalize your database. Your current system or storing multiple values in a single field is exactly what you should never do with an RDBMS, and this is exactly why. The database is not designed to handle this kind of field. You should have a separate table with one row for each bs_board, and then JOIN the tables.
There are no good solutions to this problem. It's a fundamental schema design flaw. The easiest way around it is to fix it with application logic. First you run:
SELECT bs_id, bs_board FROM boardsubs
From there you parse the bs_board field in your application logic and build the actual query you want to run:
SELECT bs_id,
IF(bs_board = 0, 'All Boards', (SELECT b_name FROM certboards WHERE b_id IN (<InsertedStringHere>) AS board
FROM boardsubs
There are other ways around the problem, but you will have problems with sorting order, matching, and numerous other problems. The best solution is to add a table and move this multi-valued field to that table.
The b_id IN (REPLACE(bs_board, ';', ',')) will result in b_id IN ('1,2,6,7') which is different from b_id IN (1,2,6,7) which is what you are looking for.
To make it work either parse the string before doing the query, or use prepared statements.

Mysql Joins - How to know which row is retrieved from which table...?

Consider i am using join on three tables to get a desired result-set...
Now in that result set, is there anyway that i can find out which row comes from which table...?
Update :
I see that i have phrased the question rather wrongly.... As pointed in one of the answers below, a result-set returned by join may will contain a row made up columns from multiple talbes...
So the question should actually be "Consider i am using union on three tables to get a desired result-set..."
You can add a table identifier column to each:
select 'A' as table_name, col1, col2 from A
union
select 'B' as table_name, col1, col2 from B
union
...
This returns a single result set which is handled by your application as any ordinary select statement:
while ( rows available ) {
row = fetchrow
if row.table_name == 'A' then
do special handling for table A
else if row.table_name == 'B' then
do special handling for table B
else if ...
}
The actual syntax is dependent on the language you are using, but most procedural languages follow the scheme above.
If you are asking this question, then your database is probably not structured correctly. (correctly being a subjective term).
A proper SQL query on a normalized database should not depend, nor be concerned with, where the data comes from.
Each row would be a combination of all tables, with null values being inserted in columns for left/right/outer joins which do not match the joining criteria. You could perhaps test if a column (from a particular table) is null, and derive from that that the non-null values must originate from the opposite table(s).
Then again, if you were actually performing an UNION, as Marcelo suggested, you would have to look at ancillary columns to determine the source of the data, as that information is lost in the combination.