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) = ''
Related
I have something like in table
mysql> select uuid , short-uuid FROM sampleUUID WHERE identifier ="test123";
+--------------------------------------+-------------+
| uuid | short-uuid |
+--------------------------------------+-------------+
| 11d52ebd-1404-115d-903e-8033863ee848 | 8033863ee848 |
| 22b6f783-aeaf-1195-97ef-a6d8c47261b1 | 8033863ee848 |
| 33c51085-ccd8-1119-ac37-332510a16e1b | 332510a16e1b |
+--------------------------------------+-------------+
I would be needing a result like (grouped all in single row, single value w.r.t uuid and short-uuid being same)
| uuidDetails
+----------------------------------------------------------------------------------------------------------------+-------------+
| 11d52ebd-1404-115d-903e-8033863ee848,22b6f783-aeaf-1195-97ef-a6d8c47261b1|8033863ee848&&33c51085-ccd8-1119-ac37-332510a16e1b| 332510a16e1b |
+----------------------------------------------------------------------------------------------------------------+-------------+
(basically grouping uuid and short uuid in a single row from multiple rows and columns)
I know this can be achieved by select GROUP_CONCAT(uuid)FROM sampleUUID WHERE identifier ="test123" group by short-uuid;
but i don't wanna use group by here because that give multiple rows, i would need all in one row .
I have tried with below stuffs but failed to get the the results in single row
select ANY_VALUE(CONCAT_WS( '||',CONCAT_WS('|',GROUP_CONCAT(uuid) SEPARATOR ','),short-uuid)) )as uuidDetails from sampleUUID
where identifier ="test123";
this resulted like below with not appending short-uuid properly (there is only 1 short uuid appended here,Actually it needs to be grouped first 2 uuids with 1 short(because same short-uuid) uuid and 3rd uuid with other short uuid)
| uuidDetails
+----------------------------------------------------------------------------------------------------------------+-------------+
| 11d52ebd-1404-115d-903e-8033863ee848,22b6f783-aeaf-1195-97ef-a6d8c47261b1,33c51085-ccd8-1119-ac37-332510a16e1b| 332510a16e1b |
+----------------------------------------------------------------------------------------------------------------+-------------+
which is not i expected
Any help here will be appreciated . Thank you
Use nested queries.
SELECT GROUP_CONCAT(result ORDER BY result SEPARATOR '&&') AS uuidDetails
FROM (
SELECT CONCAT(GROUP_CONCAT(uuid ORDER BY uuid SEPARATOR ','), '|', short_uid) AS result
FROM sampleUUID
WHERE identifier = 'test123'
GROUP BY short_uid
) AS x
NOTE: If there is no requirement for ordering of the UUID values, we can use ORDER BY inside the GROUP_CONCAT aggregates to make the result more deterministic, so the query will return just one of a number of possible results given the same data e.g. return aa,bb|1&&cc|3 rather than bb,aa|1&&cc|3 or cc|3&&aa,bb|1 or cc|3&&bb,aa|1.
i have two table, table 1 as below
session status SN error_code
3f24197394c521349206a11 Fail ACNW6676 40774,500005
7385122219fa35c37942511 Fail ACNW6676 40555,40187,500004,500005
412afc12a33601011721415 Fail ACNW6676 100001,500005
9213232191116c821a59f86 Fail ACNW6676 500005
and table 2 as below
error
0001
500005
40774
if I want to table 2 (error) check against table 1 (error_code) if match 3 times will return 1.
for example,
500005 from table 2 match to table 1 (error_code) 3 times it display the error code and the number of matched.
and error from table 1 500005 and 40774 cannot sum the number of match. mean every single value from table 2 is separated
any idea for this?
result wanted:-
error count
500005 4
40774 1
if the count 3 and above return 1
The trick here is the join between tables table1 & table2.
Using LIKE with wildcards will not work.
example: this will not work as it will wrongly join 100001,500005 with 0001.
...from table1 join table2 ON table1.error_code LIKE concat('%', table2.error, '%')
It is necessary instead to check that table2.error is contained in table1.error_code as a whole word. This can be expressed using regular expressions.
The regular expression where \b signifies a word-boundary
\bword\b
will match the text
this sentence has a word in it
but will not match the text
this sentence is made with words
mysql uses these markers instead to signify word boundaries
[[:<:]], [[:>:]]
Thus we construct our query as such
SELECT
error
, count(*) count
FROM table1
JOIN table2
ON error_code REGEXP CONCAT('[[:<:]]', error, '[[:>:]]')
GROUP BY 1;
which should yield the rows:
+--------+----------+
| error | count(*) |
+--------+----------+
| 40774 | 1 |
| 500005 | 4 |
+--------+----------+
First of all, you must be sure to organize your data!
If you're doing this in sql, you should write "40774,500005":
as a string, and so, treat this data
separate in other field, (as the numbers of items can vary, I'd recommend another table)
After doing that, it will only do the sql search.
Example:
SELECT error, count(*) count FROM table1 JOIN table2 GROUP BY 1;
I have a user table containing a column(say interests) with comma separated interest ids as a value.
e.g.
user interests
A 12,13,15
B 10,11,12,15
C 9,13
D 10,12
Now, I have a string with comma separated values as "13,15".
I want to fetch the users who has the interest 13,15 from above table means it should return the user A, B & C as user A contains both interest(13,15), user B matched the interest with 15 & user matched the interest with 13.
what will be the SQL as I have a lots of users in my table.
It can be done with regexp as #1000111 said, but with more complicated regexp. Look at this, for example:
(^|,)(13|15)(,|$)
This will not match 13 from 135, or 1 from 13 and so on. For example, for number 13 this will match next strings:
1,13,2
13,1,2
1,13
13,2
13
But will not match these
1,135,2
131,2
1,113
And this is query:
SET #search = '13,15';
SELECT *
FROM test
WHERE interests REGEXP CONCAT('(^|,)(', REPLACE(#search, ',', '|'), ')(,|$)')
If you want to get the result based on loose matching then you can follow this query:
Loose matching means interests like 135,151 would also appear while searching for '13,15'.
SET #inputInterest := "13,15";
SELECT
*
FROM userinterests
WHERE interests REGEXP REPLACE(#inputInterest,',','|');
For the given data you will get an output like below:
| ID | user | interests |
|----|------|-------------|
| 1 | A | 12,13,15 |
| 2 | B | 10,11,12,15 |
| 3 | C | 9,13 |
SQL FIDDLE DEMO
EDIT:
If you want result based on having at least one of the interests exactly then you can use regex as #Andrew mentioned in this answer:
Here's I've modified my query based on his insight:
SET #inputInterest := "13,15";
SELECT
*
FROM userinterests
WHERE interests REGEXP CONCAT('(^|,)(', REPLACE(#inputInterest, ',', '|'), ')(,|$)')
SEE DEMO OF IT
Note:
You need to replace the #inputInterest variable by your input string.
Suggestion:
Is storing a delimited list in a database column really that bad?
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
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