mysql - find the number of occurrences from two columns - mysql

question example :
source | target
apple | dog
dog | cat
door | cat
dog | apple
cat | dog
result :
apple dog 2
dog cat 2
door cat 1
Here is my question, as an example:
I am trying to count the apple and dog occurrence from source and target. The
count is 2, that is; apple dog and dog cat.
In the same way; dog cat and cat dog, they occur 2 times.
How can I do this with mysql ?
the data will be very large, so this is just a simple example.

Assuming Source and Target are joined with an ID I would do this as:
SELECT
FirstValue,
SecondValue,
COUNT(*) As MyCount
FROM
(SELECT
SourceTable.Value FirstValue,
TargetTable.Value SecondValue
FROM
SourceTable
INNER JOIN TargetTable ON SourceTable.IDValue = TargetTable.IDValue
UNION ALL
SELECT
TargetTable.Value FirstValue,
SourceTable.Value SecondValue
FROM
TargetTable
INNER JOIN SourceTable ON TargetTable.IDValue = SourceTable.IDValue)
GROUP BY
FirstValue,
SecondValue
Reading the question again I'm unsure if these are two columns in the same table. If they are then the query can be simplified to:
SELECT
FirstValue,
SecondValue,
COUNT(*) As MyCount
FROM
(SELECT
SourceColumn FirstValue,
TargetColumn SecondValue
FROM
MyTable
UNION ALL
SELECT
TargetColumn FirstValue,
SourceColumn SecondValue
FROM
MyTable)
GROUP BY
FirstValue,
SecondValue

As I see, your issue is: to count your values independent of their order in your columns. So, pair <'foo', 'bar'> should be counted as <'bar', 'foo'>. For that you may use:
SELECT
*,
COUNT(*)
FROM
test
GROUP BY
LEAST(source, target),
GREATEST(source, target)
Note, that:
Mixing non-group columns with group function will work in MySQL only. It's an extension, so server is free to chose any row.

DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(source VARCHAR(12) NOT NULL
,target VARCHAR(12) NOT NULL
,PRIMARY KEY(source,target)
);
INSERT INTO my_table VALUES
('apple','dog'),
('dog','cat'),
('door','cat'),
('dog','apple'),
('cat','dog');
SELECT * FROM my_table;
+--------+--------+
| source | target |
+--------+--------+
| apple | dog |
| cat | dog |
| dog | apple |
| dog | cat |
| door | cat |
+--------+--------+
SELECT GREATEST(source,target),LEAST(source,target),COUNT(*) FROM my_table GROUP BY GREATEST(source,target),LEAST(source,target);
+-------------------------+----------------------+----------+
| GREATEST(source,target) | LEAST(source,target) | COUNT(*) |
+-------------------------+----------------------+----------+
| dog | apple | 2 |
| dog | cat | 2 |
| door | cat | 1 |
+-------------------------+----------------------+----------+

Related

Select from related tables

So in my data base i have 2 tables related:
The fields are id, name, price and a int so i know if they a all sold or not
fruits
|IDfruit| name | price | sold |
| 1 |orange | 5 | 0
| 2 |apple | 10 | 0
| 3 |grape | 15 | 1
| 4 |lemon | 7 | 1
primary key is IDfruit
images
|IDimage| url | idfruit_image
| 1 | image1.png | 1
| 2 | image2.png | 1
| 3 | image3.png | 2
| 4 | image4.png | 3
| 5 | image5.png | 4
| 6 | image6.png | 4
| 7 | image7.png | 4
IDimage is primary key and idfruit_image is a foreign key that references IDfruit
The result i want is all fruits and the FIRST image of each fruit.
So what i've done is
select fruits.*, url , idfruit_image
from fruits,images
where IDfruit = idfruit_image;
This return all fruits and all images of each fruit, but i would like just one image of each fruit, how can i achieve this?
And what if i want everything from all sold fruits and just the first image of each one
Use GROUP BY to get one row per fruit, and an aggregation function to select one of the images.
SELECT f.*, MAX(url) AS url
FROM fruits AS f
LEFT JOIN images AS i ON f.idfruit = i.idfruit_image
GROUP BY f.idfruit
I understand that you want the first image per fruit, first being defined as: the image with the smallest idimage.
If you just want the url of the image, a correlated subquery should be an acceptable solution:
select
f.*,
(
select i.url
from images i
where i.idfruit_image = f.idfruit
order by i.idimage
limit 1
) url
from fruits f
If you want the whole image record, one option is to join, then filter with a subquery:
select f.*, i.*
from fruits f
inner join images i on i.idimage = (
select min(i1.idimage) from images i1 where i1.idfruit_image = f.idfruit
)
Finally: in MySQL 8.0, you can use row_number() for this:
select *
from (
select
f.*,
i.*,
row_number() over(partition by i.idfruit_image order by i.idimage) as rn
from fruits f
inner join images i on i.idfruit_image = f.idfruit
) t
where rn = 1

WHERE/GROUP By Condition - One Name but multiple values

I have the following table:
Name Product
Bob Car
Bob Apples
Bob Pears
Bob Car
John Apples
John Pears
Whoever has bought a Product Car, I want to keep separate from everyone else. So, I create a flag:
Name Product Flag
Bob Car 1
Bob Apples 0
Bob Pears 0
Bob Car 1
John Apples 0
John Pears 0
But the problem with my flag is that even if I do a where condition and say, show me the consumer WHERE flag !=1, it'll pick Bob. Which is incorrect as Bob owns a car.
I would still like to GROUP by Product.
How do I separate the above table into two groups?
Thanks!
Use below query :-
select name from table where flag!=1
and name not in (select name from table where flag = 1)
group by name
"show me the consumer WHERE flag !=1, it'll pick Bob" that is because you are asking for rows where flag != 1. Instead you'll need something a little more complicated, like:
SELECT DISTINCT Name
FROM tableTable
WHERE Name NOT IN (SELECT Name FROM theTable WHERE Product = 'Car')
alternatively, you can do a LEFT JOIN, which may or may not be faster depending on the amount of data you have and how its values are distributed.
SELECT DISTINCT a.Name
FROM theTable a
LEFT JOIN theTable b ON a.Name = b.Name AND b.Product = 'Car'
WHERE a.Product != 'Car' AND b.Product IS NULL
;
This gets all the rows with products other than cars, and then uses the LEFT JOIN in conjunction with the IS NULL condition to find which did not also have a 'Car' row.
I think you want your table's data displayed, just with "People who bought cars" partitioned (not grouped) separately somehow - this could be done with an ORDER BY OwnsACar clause, for example.
Step 1: Identify the people who have bought cars:
SELECT DISTINCT
Name
FROM
yourTable
WHERE
Product = 'Car'
Step 2: Join on this data to generate a calculated "OwnsACar" column:
SELECT
yourTable.Name,
yourTable.Product,
ISNULL( carowners.Name ) AS OwnsACar
FROM
yourTable
LEFT OUTER JOIN
(
SELECT DISTINCT
Name
FROM
yourTable
WHERE
Product = 'Car'
) AS carowners ON carowners.Name = yourTable.Name
ORDER BY
OwnsACar ASC,
yourTable.Name ASC
You can use these two queries. The additional Flag column is not required.
-- do not have Car
SELECT *
FROM products
WHERE Name not in (SELECT DISTINCT Name
FROM products
WHERE Product='Car');
-- have Car
SELECT *
FROM products
WHERE Name in (SELECT DISTINCT Name
FROM products
WHERE Product='Car');
Illustration:
-- table
SELECT * FROM products;
+------+---------+
| Name | Product |
+------+---------+
| Bob | Car |
| Bob | Apples |
| Bob | Pears |
| Bob | Car |
| John | Apples |
| John | Pears |
+------+---------+
-- query for people that do not have Car
+------+---------+
| Name | Product |
+------+---------+
| John | Apples |
| John | Pears |
+------+---------+
-- query for people having 'Car'
+------+---------+
| Name | Product |
+------+---------+
| Bob | Car |
| Bob | Apples |
| Bob | Pears |
| Bob | Car |
+------+---------+
Try with :
SELECT `t`.`Name`, `t`.`Product`, SUM(`t`.`Flag`) as hasCar
FROM your_table t
GROUP BY `t`.`Name`
HAVING `t`.`hasCar` = 0;
Although you can go without the flag column by going :
SELECT `t`.`Name`, `t`.`Product`, SUM(IF(`t`.`Product` = 'Car', 1, 0)) as hasCar
FROM your_table t
GROUP BY `t`.`Name`
HAVING `t`.`hasCar` = 0;

Update columns counting probability using MySQL

example table:
+--------+--------+-------+------+
| source | target | count | prob |
+--------+--------+-------+------+
| cat | dog | 1 | 0 |
| dog | cat | 1 | 0 |
+--------+--------+-------+------+
I need to let prob = count/ total of count
and I using
update test set prob = count / (select sum(count) from EM);
and it failed ,how I suppose to do ?
MySQL doesn't like referring to the table being updated, in a subquery. To get around it, wrap it in another select to force it to form a temporary table.
update test
set prob = count / (select * from (select sum(count) from test) q )

Split a column into a defined range in MYSQL

I have a table which looks like this:
+-----------------------
| id | first_name
+-----------------------
| AC0089 | John |
| AC0015 | Dan |
| AC0017 | Marry |
| AC0003 | Andy |
| AC0001 | Trent |
| AC0006 | Smith |
+-----------------------
I need a query to split the id in the range of 3 and also display the starting id of that range i.e.
+------------+----------+--------
| startrange | endrange | id
+------------+----------+--------
| 1 | 3 | AC0089
| 4 | 6 | AC0003
+------------+----------+--------
I am pretty new to SQL and trying the below query but I dont think I am near to the correct solution at all ! Here is the query:
select startrange, endrange, id from table inner join (select 1 startRange, 3 endrange union all select 4 startRange, 6 endRange) r group by r.startRange, r.endRange;
It is giving the same id every-time and I am not able to come up with any other solution. How Can I get the required output?
Try this
SET #ct := 0;
select startrange,(startrange + 2) as endrange, seq_no from
(select (c.st - (select count(*) from <table_name>)) as startrange, c.* from
(select (#ct := #ct + 1) as st, b.* from <table_name> as b) c
having startrange mod 3 = 1) as cc;
sorry for formating.
I'm not completely sure what your trying to do but if you're trying to convert a table of ID's into ranges use a case when.
CASE WHEN startrange in(1,2,3) THEN 1
ELSE NULL
END as startrange,
CASE WHEN endrange in(1,2,3) THEN 3
ELSE NULL
END as endrange,
CASE WHEN ID in(1,2,3) THEN id
WHEN ID in(4,5,6) THEN id
ELSE id
END AS ID

Generate unique username from first and last name?

I've got a bunch of users in my database and I want to reset all their usernames to the first letter of their first name, plus their full last name. As you can imagine, there are some dupes. In this scenario, I'd like to add a "2" or "3" or something to the end of the username. How would I write a query to generate a unique username like this?
UPDATE user
SET username=lower(concat(substring(first_name,1,1), last_name), UNIQUETHINGHERE)
CREATE TABLE bar LIKE foo;
INSERT INTO bar (id,user,first,last)
(SELECT f.id,CONCAT(SUBSTRING(f.first,1,1),f.last,
(SELECT COUNT(*) FROM foo f2
WHERE SUBSTRING(f2.first,1,1) = SUBSTRING(f.first,1,1)
AND f2.last = f.last AND f2.id <= f.id
)),f.first,f.last from foo f);
DROP TABLE foo;
RENAME TABLE bar TO foo;
This relies on a primary key id, so for each record inserted into bar, we only count duplicates found in foo with id less than bar.id.
Given foo:
select * from foo;
+----+------+--------+--------+
| id | user | first | last |
+----+------+--------+--------+
| 1 | aaa | Roger | Hill |
| 2 | bbb | Sally | Road |
| 3 | ccc | Fred | Mount |
| 4 | ddd | Darren | Meadow |
| 5 | eee | Sharon | Road |
+----+------+--------+--------+
The above INSERTs into bar, resulting in:
select * from bar;
+----+----------+--------+--------+
| id | user | first | last |
+----+----------+--------+--------+
| 1 | RHill1 | Roger | Hill |
| 2 | SRoad1 | Sally | Road |
| 3 | FMount1 | Fred | Mount |
| 4 | DMeadow1 | Darren | Meadow |
| 5 | SRoad2 | Sharon | Road |
+----+----------+--------+--------+
To remove the "1" from the end of user names,
INSERT INTO bar (id,user,first,last)
(SELECT f3.id,
CONCAT(
SUBSTRING(f3.first,1,1),
f3.last,
CASE f3.cnt WHEN 1 THEN '' ELSE f3.cnt END),
f3.first,
f3.last
FROM (
SELECT
f.id,
f.first,
f.last,
(
SELECT COUNT(*)
FROM foo f2
WHERE SUBSTRING(f2.first,1,1) = SUBSTRING(f.first,1,1)
AND f2.last = f.last AND f2.id <= f.id
) as cnt
FROM foo f) f3)
As a two-parter:
SELECT max(username)
FROM user
WHERE username LIKE concat(lower(concat(substring(first_name,1,1),lastname), '%')
to retrieve the "highest" username for that name combo. Extract the numeric suffix, increment it, then insert back into the database for your new user.
This is racy, of course. Two users with the same first/last names might stomp on each other's usernames, depending on how things work out. You'd definitely want to sprinkle some transaction/locking onto the queries to make sure you don't have any users conflicting.
Nevermind.... I just found the dupes:
select LOWER(CONCAT(SUBSTRING(first_name,1,1),last_name)) as new_login,count(* ) as cnt from wx_user group by new_login having count(* )>1;
And set those ones manually. Was only a handful.
Inspired in the answer of unutbu: there is no need to create an extra table neither several queries:
UPDATE USER a
LEFT JOIN (
SELECT USR_ID,
REPLACE(
CONCAT(
SUBSTRING(f.`USR_FIRSTNAME`,1,1),
f.`USR_LASTNAME`,
(
(SELECT IF(COUNT(*) > 1, COUNT(*), '')
FROM USER f2
WHERE SUBSTRING(f2.`USR_FIRSTNAME`,1,1) =
SUBSTRING(f.`USR_FIRSTNAME`,1,1)
AND f2.`USR_LASTNAME` = f.`USR_LASTNAME`
AND f2.`USR_ID` <= f.`USR_ID`)
)
),
' ',
'') as login
FROM USER f) b
ON a.USR_ID = b.USR_ID
SET a.USR_NICKNAME = b.login