SELECT and CONCAT in MySQL - mysql

I'm using MySQL and trying to use CONCAT_WS() function with SELECT.
I tried:
SELECT id, A_12, CONCAT_WS('A_', '12') as testA from TABLE_A
I expected something like
id | A_12 | testA |
-------------------
1 | 20 | 20 |
The value is testA should be same as the value in A_12.
However, what I'm getting is
id | A_12 | testA |
-------------------
1 | 20 | 12 |
The '12' in testA column is simply coming from latter string of the CONCAT_WS() function CONCAT_WS('A_','12').
Any help would be appreciated.
======EDIT======:
Sorry I didn't clearly state my question and purpose in the beginning. I have 12 columns A_1, A_2, ... , A_12 in TABLE_A. More specifically, Table_A looks like this:
id | A_1 | A_2 | ... | A_12|
---------------------------
1 | 4 | 5 | ... | 20 |
2 | 1 | 4 | ... | 50 |
3 | 2 | 5 | ... | 70 |
I also have another table TABLE_B that looks something like this:
id | value
----------
1 | 12
2 | 5
3 | 3
I'm trying to create a stored function that...
select the corresponding value from TABLE_B
from TABLE_A, pull info under the column A_ + the value from Table_B
for every id.
So I have
SELECT id, CONCAT_WS('A_', stored-value-from-TABLE_B) as testA from TABLE_A
To make sure if the code is running as I expect, I ran
SELECT id, A_12, CONCAT_WS('A_', '12') as testA from TABLE_A
since the value for id=1 in Table_B is 12.
However, what I'm getting is 12 in testA column for every id.

You can join the tables on their ids and pick the proper A_? with the function ELT() where you have to enumerate all 12 columns:
SELECT b.*,
ELT(b.value, a.A_1, a.A_2, ..., a.A_12) testA
FROM TABLE_B b INNER JOIN TABLE_A a
ON b.id = a.id;
See a simplified demo.

well to get this result just need
SELECT id, A_12, A_12 as testA from TABLE_A

You seem to think that a select-list is bound to be a list of column names, therefore a string function like CONCAT_WS() would produce a string like A_12 and that string must be interpreted as a column identifier, so the result of that query would use the value of the column named by that string.
But that's not how an SQL select-list works.
A select-list is a list of expressions. You could use a simple column name, and the result would be the value in that column. Or you could use another expression, in this case a string function, and the result will be the string returned by that function — not the column that coincidentally has a name matching that string.
As mentioned in the comment above, identifiers are fixed in an SQL query. You cannot make a string expression and have the value of that expression be interpreted as an identifier in the same query. To make a dynamic reference to an identifier, you need to format it in your SQL syntax before you prepare the query.
You also misunderstand what CONCAT_WS('A_', '12') does. It concatenates its 2nd argument and further arguments, with a separator between them of the 1st argument. A typical usage would be:
CONCAT_WS(', ', col1, col2, col3, ...)`
This returns a list of comma-separated words from the values of several columns: "value1, value2, value3".
So in your case, you concatenated a single value "12" but the separator "A_" does not appear because there is only one value in the list.

First, you don't want CONCAT_WS(), you want CONCAT() - Concat with separator inserts whatever your first argument is between all the others, and since you only have one other argument, it never gets used - for example CONCAT_WS('A_', '12', '13') would give you 12A_13. CONCAT('A_', '12') gives you A_12 but as a string, not as a column name.
After correcting to CONCAT and evaluating, your select will look like this SELECT id, A_12, 'A_12' as testA from TABLE_A; Notice the quotes around A_12.
This is because concat functions return a string and can't be used to build a column name in a select string the way you want. It's possible to do so, but is complicated - you would have to build your entire query string in a string variable then execute it as a prepared statement:
SET #QueryString = CONCAT('SELECT id, A_12, ', CONCAT('A_', '12'), ' as testA from TABLE_A;');
PREPARE stmnt FROM #QueryString;
EXECUTE stmnt;
The nested CONCAT() is unnecessary, since concat can take any number of arguments so you get it simplified to this:
SET #QueryString = CONCAT('SELECT id, A_12, ', 'A_', '12', ' as testA from TABLE_A;');
PREPARE stmnt FROM #QueryString;
EXECUTE stmnt;
And your #QueryString will be SELECT id, A_12, A_12 as testA from TABLE_A;
This can be pretty dangerous if anything in that string comes from user input in a system. If this is connected to any application, combine the strings using whatever concatenation your server side application language uses then execute that as a query.
DB Fiddle testing (SQLFiddle seems to have removed support for selects in prepared statements)

Related

MySQL - query to get specific result

I have started learning MySQL and I'm stuck on a case.
I have the following table:
id | value
1 | abc
1 | def
2 |
2 |
3 | pqr
3 |
4 |
4 | xyz
5 |
Please note the empty values beside numeric int denotes empty strings.
Problem statement: I need to get those ids which if formed into a group would only have empty strings attached to it. Example here would be ids: 2, 5
Explanation: id = 2 appears twice both with empty strings and hence included. id = 5 appears once and have one empty string, thus included. id = 3 is not included since one of its instance has got non-empty value i.e. "pqr"
I am stuck with the query:
SELECT * FROM t1 GROUP BY id;
But this gives a wrong result.
Could you please help me out? What should be the query to get ids = 2, 5. I am sorry for the table formatting.
SELECT DISTINCT t1.id
FROM t1
LEFT JOIN t1 t1d ON t1d.id = t1.id AND t1d.value <> ''
WHERE t1d.id IS NULL
without GROUP BY and HAVING = one happy database!
You can achieve the expected outcome with conditional counting compared to counting of all rows within a group:
select id from t1
group by id
having count(*)=count(if(`value`='',1,null))
count(*) returns the number of records with the corresponding id. count(if(value='',1,null)) return the number of such records, where the value field is an empty string.
Using below query you will get your desired output.
select id
from test_empty
group by id
having TRIM(TRAILING ',' FROM group_concat(value))=""
By group_concat(value) output will concatenated value by comma for all the ids.
By using TRIM(TRAILING ',' FROM group_concat(value)) trailing comma can be removed
By using having we can place condition on group by that only id with all blank value will be retrieved.
An empty string will always be "less than" any non-empty string, so this should do the trick:
select id from t1
group by id
having max(value) = ''

Sum and percentage on json array elements

My table is like this:
create table alphabet_soup(
id numeric,
index json bigint
);
my data looks like this:
(id, json) looks like this: (1, '{('key':1,'value':"A"),('key':2,'value':"C"),('key':3,'value':"C")...(600,"B")}')
How do I sum across the json for number of A and number of B and do % of the occurence of A or B? I have about 6 different types of values (ABCDEF), but for simplicity I am just looking for a comparison of 3 values.
I am trying to find something to help me calculate the % of occurrence of a value from a key value pair in json. I am using postgres 9.4. I am new to both json and postgres, and I am landing on the same json functions manual page of postgres over and over.
I have managed to find a sum, but how to calculate the % in a nested select and display the key and values in increasing order of occurence like follows:
value | occurence | %
====================================
A | 300 | 50
B | 198 | 33
C | 102 | 17
The script I am using for the sum is :
select id, index->'key'::key as key
sum(case when (1,index::json->'1')::text = (1,index::json->'2')::text
then 1
else 0
end)/count(id) as res
from
alphabet_soup
group by id;
limit 10;
I get an output as follows:
column "alphabet_soup.id" must appear in the group by clause or be used in an aggregate function.
Thanks for the comment Patrick. Sorry I forgot to add I am using postgres 9.4
The easiest way to do this is to expand the json document into a regular row set using the json_each_text() function. Every single json document then becomes a set of rows and you can then apply aggregate function as you would on any other row set. However, you need to use the function as a row source (section 7.2.1.4) (since it returns a set of rows) and then select the value field which has the category of interest. Note that the function uses a field of the table, through an implicit LATERAL join (section 7.2.1.5).
SELECT id, value
FROM alphabet_soup, json_each_text("index");
which yields something like:
test=# SELECT id, value FROM alphabet_soup, json_each_text("index");
id | value
----+-------
1 | A
1 | C
1 | C
1 | B
To this you can apply regular aggregate functions over the appropriate windows to get the result you are looking for:
SELECT DISTINCT id, value,
count(value) OVER (PARTITION BY id, value) AS occurrence,
count(value) OVER (PARTITION BY id, value) * 100.0 /
count(id) OVER (PARTITION BY id) AS percentage
FROM (
SELECT id, value
FROM alphabet_soup, json_each_text("index") ) sub
ORDER BY id, value;
Which gives a result like:
id | value | occurrence | percentage
----+-------+------------+---------------------
1 | A | 1 | 25.0000000000000000
1 | B | 1 | 25.0000000000000000
1 | C | 2 | 50.0000000000000000
This will work for any number of categories (ABCDEF) and any number of ids.
# Patrick, it was an accident. I am new to stackoverflow. I did not realize how ti works. I was fiddling around and I found the answer to the question I asked in addition to the first one. Sorry about that!
For fun, I added some more to the code to make the % compare of the result set:
With q1 as
(SELECT DISTINCT id, value,
count(value) OVER (PARTITION BY id, value) AS occurrence,
count(value) OVER (PARTITION BY id, value) * 100.0 / count(id) OVER(PARTITION BY id) AS percentage
FROM ( SELECT id, value FROM alphabet_soup, json_each_text("index") ) sub
ORDER BY id, value) Select distinct id, value, least(percentage) from q1
Where (least(percentage))>20 Order by id, value;
The output for this is:
id | value | least
----+-------+--------
1 | B | 33
1 | C | 50

MySQL joining tables on fields with different type

Suppose I have 2 InnoDB tables A and B.
Table A has a column named Acountry of type INT
Table B has a column named Bcountry of type VARCHAR
Some records in table A have in column Acountry values "356"
Some records in table B have in column Bcountry values "356,Italy"
How is it possible that the following join works perfectly:
(I mean I get rows where Acountry or Bcountry starts with 356)
SELECT A.Field1 , A.Field2 , B.Field3 , B.Field4
FROM A
JOIN B ON A.Acountry=B.Bcountry
despite the fact that the 2 columns have different values
and are of different type
Any hints ?
Is there any setting for "loose" joining ?
P.S.
I found this link http://bugs.mysql.com/bug.php?id=3777
where it states that:
"This is expected behavior.
The arguments (string and number) are compared as floating-point numbers"
???
Strings in MySQL which begin with numbers will be cast as numbers up to the first non-numeric character. So the cast operation results in only the integer at the front:
> SELECT CAST('356,Italy' AS INT);
+--------------------------+
| CAST('356,Italy' AS INT) |
+--------------------------+
| 356 |
+--------------------------+
(Note: casting as DECIMAL will produce the same result)
But a similar string which has a non-numeric character first will cast to 0:
> SELECT CAST('xx356,Italy' AS INT);
+--------------------------------+
| CAST('xx356,Italy' AS INT) |
+--------------------------------+
| 0 |
+--------------------------------+
I would consider this to be an unreliable behavior to perform a join on, even if it is unlikely to be changed in future MySQL versions. It would be much better to produce a consistent or more directly comparable value between those common columns.
Fix the data if possible:
First and foremost, if you are in any position to change this table structure such that B has consistent data, that is the real solution. And doing that would also allow you to make the data types of A.ACountry and B.BCountry identical (both as INT types) which further allows you to define a proper FOREIGN KEY constraint.
Join with what you have using string operations:
But a JOIN's ON condition can be any arbitrary expression, and MySQL offers a SUBSTRING_INDEX() function to return a substring before a delimiter. You should be able to join successfully using that:
SELECT
A.*,
B.Field3,
B.Field4
FROM
A
-- Join on the first group of characters before `,` in BCountry
INNER JOIN B ON A.ACountry = SUBSTRING_INDEX(BCountry, ',', 1)
This works because:
> SELECT SUBSTRING_INDEX('356,Italy', ',', 1);
+--------------------------------------+
| SUBSTRING_INDEX('356,Italy', ',', 1) |
+--------------------------------------+
| 356 |
+--------------------------------------+
And without the trailing string the same result:
> SELECT SUBSTRING_INDEX('356', ',', 1);
+--------------------------------+
| SUBSTRING_INDEX('356', ',', 1) |
+--------------------------------+
| 356 |
+--------------------------------+
Note: The string operation is likely to degrade performance of this join. Fixing the source data is again the better solution.

MySQL select column name and value as a field

I have a mysql table that looks something like this:
id | PO | DAP | MEDIA
---|----|-------|------
1 | 2 | 34 | 64
2 | 6 | 53 | 23
I would like to be able to query get multiple rows, one for each column. E.g:
SELECT column_name as column, column_value as value FROM my_table;
Which would give me:
PO=2,DAP=34,MEDIA=54,PO=6,DAP=53,MEDIA=23
What would I need to use to formulate a query like this?
You have to first CONCAT the data of each specified field and apply GROUP_CONCAT ON the result.
Query
SELECT GROUP_CONCAT(temp_col) FROM
(
SELECT 1 as 'temp_id',
CONCAT(
CONCAT('PO=', PO),
',',
CONCAT('DAP=', DAP),
',',
CONCAT('MEDIA=', MEDIA)
) AS 'temp_col'
FROM test
) temp
GROUP BY temp_id
Check Out SQLFIDDLE
Not exactly sure what you mean. But this is traditionally done in this manner
SELECT * FROM my_table;
You'll get your array like this
array(0=>array('PO'=>2,'DAP'=>34,'MEDIA'=54), 1=>array('PO'=>6, 'DAP'=>53, 'MEDIA'=> 23))
.. like so.

Find values across SQL SELECT statements so that the concatenation of any 2 values from each SELECT statement is below a certain max length?

I have a complex problem that I am not quite sure how to address.
I am defining two subset of values from the same table that match certain criteria.
statement 1:
SELECT value FROM Values WHERE category = a
statement 2:
SELECT value FROM Values WHERE category = b
But I also have an additional constraint which is that any value from the statement 1 concatenated with any value of statement 2 should result in a string whose length should be equal to or smaller than a given max string length.
What kind of query can I write to do this?
Thanks in advance for your help.
Lothaire
Edit:
My data looks like:
+---+---------+------------+----------+
|id | routeId |category | value |
+---+---------+------------+----------+
| 1 | 1 |origin | Paris |
| 2 | 1 |destination | New York |
| 3 | 2 |origin | Paris |
| 4 | 2 |destination | Berlin |
+------+--------------+---------------+
And I'd like to receive a list of routeId's for routes where origin.destination is less than n characters.
Now that I am looking at this problem more carefully, I see that my initial approach was incorrect, as I wrote that any value from the first select statement concatenated with any value from the second select statement should have a length under a certain character limit.
In fact, the problem is more complex, because the routeId for the origin value and the destination value should be the same for the pair of value whose concatenation should be under a max length.
Try:
SELECT a.routeID
FROM tbl a
JOIN tbl b ON a.routeID = b.routeID AND b.category = 'destination'
WHERE a.category = 'origin' AND
CHAR_LENGTH(CONCAT(a.value, b.value)) <= 5
Where tbl is the name of your table, and 5 is the maximum length of the concatenation of the two statements. This compares the concatenation of each routeID's origin value to its destination value. If it's longer than five characters, then the routeID is filtered out.
Or perhaps this is what you want:
Comparing the concatenation of the origin value of each routeID to EVERY destination value in the table:
SELECT a.routeID
FROM tbl a
CROSS JOIN (SELECT value FROM tbl WHERE category='destination') b
WHERE a.category = 'origin'
GROUP BY a.routeID
HAVING MAX(CHAR_LENGTH(CONCAT(a.value, b.value))) <= 5
You may try this query:
SELECT t1.value AS v1, t2.value AS v2, LEFT(CONCAT(t1.value, t2.value), 20) AS my_text
FROM `values` t1, `values` t2
WHERE t1.category=a AND t2.category=b
I used a maximum length of 20 characters, change it as you need.
Since VALUES is a reserved word in MySQL, it must be enclosed in backticks to avoid syntax errors.
Other query, after reading your comment:
SELECT t1.value AS v1, t2.value AS v2, CONCAT(t1.value, t2.value) AS my_text
FROM `values` t1, `values` t2
WHERE t1.category=a AND t2.category=b
AND CHAR_LENGTH(CONCAT(t1.value, t2.value))<=20
This query will return records whose concatenation of both values is shorter or equal to 20 characters.
Documentation: LENGTH and CHAR_LENGTH