SQL count occurrence by column - mysql

After some research I haven't found what I need and I thought I'll ask here. I'm currently trying to develop a advanced search mode for a application and I'm stuck with my task. Maybe you can help me. So imagine I have the following table:
ID | Name | Surname
1 | John | Mim
2 | Johnny | Crazy
3 | Mike | Something
4 | Milk | Milk
5 | Peter | IDontknow
6 | Mitch | SomeName
Then in my frontend, there's one input field. The input of that field will go trough the query in that way:
SELECT name, surname FROM people WHERE name LIKE 'input%' OR surname LIKE 'input%'
Now lets say my input is "Mi", so I'll have 3 columns match in the "name" column, and 2 in the surname. And that's what I'm looking for.
A count which ouputs the following:
Column | Count
Name | 3
Surname | 2
Is there a way to achieve this in only one query?
What I've tried so far:
I actually created the table above on my localhost in my database and tried different queries. Tried with SELECT count(name), count(surname), but that would output 3 for both counts. So I'm not even sure if that's possible in only one query.

use union all
SELECT 'name' as col, count(name) as cnt FROM people WHERE name LIKE 'input%'
union all
SELECT 'surname', count(surname) FROM people WHERE surname LIKE 'input%'

make customize group using case when
SELECT (case when name LIKE 'input%' then 'name'
else 'surname' end) as Column, count(*) as cnt
FROM people WHERE name LIKE 'input%' OR surname LIKE 'input%'
group by Column

Try this:
SELECT "Name" as Column, count(*) as Count FROM people WHERE name LIKE 'mi%'
UNION
SELECT "Surname" as Column, count(*) as Count FROM people WHERE surname LIKE 'mi%'

In Mysql booleans are evaluated as 1 or 0, so you can do this:
select 'Name' Column, sum(name LIKE 'input%') Count from people
union all
select 'Surname', sum(surname LIKE 'input%') from people
For Mysql 8.0+ you can avoid the double scan of the table with a CTE:
with cte as (
select
sum(name LIKE 'input%') namecounter,
sum(surname LIKE 'input%') surnamecounter
from people
)
select 'Name' Column, namecounter Count from cte
union all
select 'Surname', surnamecounter from cte

The solution without UNION[ ALL] of the people table:
SELECT
CASE cj.x WHEN 1 THEN 'Name' ELSE 'Surname' END AS `Column`,
CASE cj.x
WHEN 1 THEN COUNT(CASE WHEN Name LIKE concat(#input, '%') THEN 1 end)
ELSE COUNT(CASE WHEN Surname LIKE concat(#input, '%') THEN 1 END)
END `Count`
FROM people CROSS JOIN (SELECT 1 AS x UNION ALL SELECT 2) AS cj
WHERE Name LIKE concat(#input, '%') OR Surname LIKE concat(#input, '%')
GROUP BY cj.x;
Output for the Mi input:
| Column | Count |
+---------+-------+
| Name | 3 |
| Surname | 2 |
Test it online with SQL Fiddle.

Related

How to fill (join with other table) null values if any from another table?

I have two similar tables, they have the same columns, but different data, both matching different criterions. I want to join Table A to Table B, where some of the values are null.
I tried to look up similar questions but they are not describing my case as far as i can tell.
As an example:
Table A looks like
| id | name | age | gender |
1 Jhon 2 Male
2 Will null null
Table B looks like
| id | name | age | gender |
1 Jhon null null
2 Will 3 Male
What i would like to make is like
| id | name | age | gender |
1 Jhon 2 Male
2 Will 3 Male
I was trying to left join it, but the result is not as expected. My thought maybe i need to inner join it, then left join maybe, but it is a bit blurry yet.
I'm kinda new to joins, so every thought is appreciated.
Thanks in advance.
You can try to use UNION ALL in the subquery to do MAX
SELECT id ,name,MAX(age) age ,MAX(gender) gender
FROM (
SELECT id ,name , age , gender
FROM A
UNION ALL
SELECT id ,name , age , gender
TABLE B
) t1
GROUP BY id ,name
If your A and B tables schema are the same I would suggest you use only one table and use a flag to split those two part.
Did you try this?
select id, name,
coalesce(a.age, b.age) as age,
coalesce(a.gender, b.gender) as gender
from a join
b
using (id, name);
The issue is less the type of join then in how you combine the values from the two tables in the select.

How Do I convert this table from row to column?

I have a table that contains id and country name, and I need to convert them so the id with more than 1 country will display in 1 row.I have been searching in this forum for over an hour and found nothing.
I tried if using the pivot function can help me to achieve the result i wanted, but I feel like using pivot does not work on my case here.
This is a mini version of the table I have. The number of distinct value in the field "country" will be over 100, so I can just say something like when county = '..' as this will be to repetitive.
enter code here
+----+--------+
| id | country|
+----+--------+
| 1 | US |
| 1 | UK |
| 2 | JP |
+----+--------+
Desired outcome I am looking for:
enter code here
+----+-----------+-----------+
| id | country_1 | country_2 |
+----+-----------+-----------+
| 1 | US | UK |
| 2 | JP | null |
+----+-----------+-----------+
I found this question which is similar but it is the opposite of what I am trying to achieve.
MySQL statement to pivot table without using pivot function or a union
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
update:
Thank you so much for all of your helps. I may not have used the queries of yours to solve my problem - as of the fact that the syntax is a bit diff running in snowflake. However, I got the insights I need from all of you.
here is my solution:
enter code here
select t1.id,
max(iff(t1.row_number = 1, t1.country ,null)) as country_1,
max(iff(t1.row_number = 2, t1.country ,null)) as country_2,
max(iff(t1.row_number = 3, t1.country, null)) as country_3
from
(
select id, country, row_number() over (partition by id order by id ) as
row_number
from table
) t1
group by t1.id
Whereas you could do it with "pivoting", what will happen when you have 3 countries? Or 4? Or 17?
May I suggest this:
SELECT id,
GROUP_CONCAT(country)
FROM tbl
GROUP BY id;
You will get something like:
1 US,UK
2 JP
use aggregation
select id, max(case when id=1 then country end ) as country_1,
max(case when id=2 then country end ) as country_2
from tbale group by id
As you comment on #Rick answer you have max 3 country for each id then you can use this
select
id,
(select country from test where test.id=t.id limit 0,1)as country_1,
(select country from test where test.id=t.id limit 1,1)as country_2,
(select country from test where test.id=t.id limit 2,1)as country_3
from test as t
group by id;
DEMO
You can try this following script with RowNumber generated per id. As you confirmed there are maximum 3 country per id, we can easily generate your desired result set by handling RowNumber 1,2 & 3
SELECT ID,
MAX(CASE WHEN RowNumber = 1 THEN country ELSE NULL END) Country_1,
MAX(CASE WHEN RowNumber = 2 THEN country ELSE NULL END) Country_2,
MAX(CASE WHEN RowNumber = 3 THEN country ELSE NULL END) Country_3
FROM
(
SELECT id,
country,
#row_num :=IF(#prev_value = concat_ws('',id),#row_num+1,1)AS RowNumber
,#prev_value := concat_ws('',id)
FROM tbale
ORDER BY id
)A
GROUP BY id
There's no "dynamic" PIVOT in SQL. You need to specify the list of columns when writing the query. Your options are:
If you know the number of columns in advance, then #ZaynulAbadinTuhin solution is the easier. It seems, however, this is not your case.
If you don't know the number of columns in advance and you want them all concatenated in a single column, then #Rick James solution is the best.
Otherwise, you can still use some kind of dynamic SQL in your app or in a stored procedure that will build the SQL query at runtime, based on the existing values of the table. But this solution would require much more programming. It's not a single/simple SQL query anymore. See Rick James's Pivoting in MySQL stored procedure.

Group by names desc - get last entered values for a grouped name

I have an table like that:
id | name | v (lvl)
11 | Jane | 6
12 | John | 5
13 | Jane | 6
14 | John | 5
15 | Jane | 7
16 | Jane | 5
In my autocomplete form now id like to group the names but get the last value (value with biggest id). In the example above would be
Jane | 5
I tried with combinations like distinct, group by, order by. But im always get
Jane | 6
or grouped like this and reversed:
Jane | 6
Jane | 7
Jane | 5
I would need something like this:
SELECT name,lvl FROM
(
SELECT DISTINCT name, lvl FROM pora WHERE name LIKE 'Jane' ORDER BY lvl DESC
)
GROUP BY name
EDIT: I won't get the highest lvl, i want get the lvl of the highest id, grouped by name. Thats all. My example above would be the best explanation what i like to get.
In the inner query i change the order to DESC for all and in the outer i group it by names. But i get an error for this.
EDIT 2 I finally did at my own. The correct solution (i was already close):
SELECT a.name, a.lvl FROM
(
SELECT DISTINCT name, lvl FROM pora WHERE name LIKE 'Jane' ORDER BY id DESC
)as a
GROUP BY name
LIKE without % is just =
SELECT *
FROM yourTable
WHERE name = 'Jane'
ORDER BY id DESC
LIMIT 1
But because you mention autocomplete functionality you should use:
WHERE name LIKE 'Jane%'
To have the latest, you need to have a field dateAdded which stores the date you ran the insert command.
Following which, you use MAX(dateAdded) to get the latest ID (since, as you mentioned, it may decrease as well)
UPDATE:
if ID doesn't decrease, you can always use MAX(ID)
SELECT MAX(id), v from tablename where name = 'Jane'
UPDATE:
This has been tested:
SELECT ID, v from tableName where ID = (SELECT MAX(ID) as ID from tableName where name like '%Jane%')
Try the following query (h/t #lamak)
WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY name
ORDER BY [id] DESC)
FROM poro
)
SELECT *
FROM CTE
WHERE RN = 1

SQL - select rows that have the same value in two columns

The solution to the topic is evading me.
I have a table looking like (beyond other fields that have nothing to do with my question):
NAME,CARDNUMBER,MEMBERTYPE
Now, I want a view that shows rows where the cardnumber AND membertype is identical. Both of these fields are integers. Name is VARCHAR. Name is not unique, and duplicate cardnumber, membertype should show for the same name, as well.
I.e. if the following was the table:
JOHN | 324 | 2
PETER | 642 | 1
MARK | 324 | 2
DIANNA | 753 | 2
SPIDERMAN | 642 | 1
JAMIE FOXX | 235 | 6
I would want:
JOHN | 324 | 2
MARK | 324 | 2
PETER | 642 | 1
SPIDERMAN | 642 | 1
this could just be sorted by cardnumber to make it useful to humans.
What's the most efficient way of doing this?
What's the most efficient way of doing this?
I believe a JOIN will be more efficient than EXISTS
SELECT t1.* FROM myTable t1
JOIN (
SELECT cardnumber, membertype
FROM myTable
GROUP BY cardnumber, membertype
HAVING COUNT(*) > 1
) t2 ON t1.cardnumber = t2.cardnumber AND t1.membertype = t2.membertype
Query plan: http://www.sqlfiddle.com/#!2/0abe3/1
You can use exists for this:
select *
from yourtable y
where exists (
select 1
from yourtable y2
where y.name <> y2.name
and y.cardnumber = y2.cardnumber
and y.membertype = y2.membertype)
SQL Fiddle Demo
Since you mentioned names can be duplicated, and that a duplicate name still means is a different person and should show up in the result set, we need to use a GROUP BY HAVING COUNT(*) > 1 in order to truly detect dupes. Then join this back to the main table to get your full result list.
Also since from your comments, it sounds like you are wrapping this into a view, you'll need to separate out the subquery.
CREATE VIEW DUP_CARDS
AS
SELECT CARDNUMBER, MEMBERTYPE
FROM mytable t2
GROUP BY CARDNUMBER, MEMBERTYPE
HAVING COUNT(*) > 1
CREATE VIEW DUP_ROWS
AS
SELECT t1.*
FROM mytable AS t1
INNER JOIN DUP_CARDS AS DUP
ON (T1.CARDNUMBER = DUP.CARDNUMBER AND T1.MEMBERTYPE = DUP.MEMBERTYPE )
SQL Fiddle Example
If you just need to know the valuepairs of the 3 fields that are not unique then you could simply do:
SELECT concat(NAME, "|", CARDNUMBER, "|", MEMBERTYPE) AS myIdentifier,
COUNT(*) AS count
FROM myTable
GROUP BY myIdentifier
HAVING count > 1
This will give you all the different pairs of NAME, CARDNUMBER and MEMBERTYPE that are used more than once with a count (how many times they are duplicated). This doesnt give you back the entries, you would have to do that in a second step.

MySQL: Finding repeated names in my User table

I want to find all users whose name appears at least twice in my User table. 'email' is a unique field, but the combination of 'firstName' and 'lastName' is not necessarily unique.
So far I have come up with the following query, which is very slow, and I am not even sure it is correct. Please let me know a better way to rewrite this.
SELECT CONCAT(u2.firstName, u2.lastName) AS fullName
FROM cpnc_User u2
WHERE CONCAT(u2.firstName, u2.lastName) IN (
SELECT CONCAT(u2.firstName, u2.lastName) AS fullNm
FROM cpnc_User u1
GROUP BY fullNm
HAVING COUNT(*) > 1
)
Also, note that the above returns the list of names that appear at least twice (I think so, anyway), but what I really want is the complete list of all user 'id' fields for these names. So each name, since it appears at least twice, will be associated with at least two primary key 'id' fields.
Thanks for any help!
Jonah
SELECT u.*
FROM cpnc_User u JOIN
(
SELECT firstName, lastName
FROM cpnc_User
GROUP BY firstName, lastName
HAVING COUNT(*) > 1
) X on X.firstName = u.firstName AND x.lastName = u.lastName
ORDER BY u.firstName, u.lastName
There is no need to make up a concatenated field, just use the 2 fields separately
SELECT u.id, u.firstName, u.lastName
FROM cpnc_User u, (
SELECT uc.firstName, uc.lastName
FROM cpnc_User uc
GROUP BY uc.firstName, uc.lastName
HAVING count(*) > 1
) u2
WHERE (
u.firstName = u2.firstName
AND u.lastName = u2.lastName
)
To experiment I created a simple table with two columns a user id, and a name. I inserted a bunch of records, including some duplicates. Then ran this query:
SELECT
count(id) AS count,
group_concat(id) as IDs
FROM
test
GROUP BY
`name`
ORDER BY
count DESC
It should give you results like this:
+-------+----------+
| count | IDs |
+-------+----------+
| 4 | 7,15,4,1 |
| 2 | 2,8 |
| 2 | 6,13 |
| 2 | 14,9 |
| 1 | 11 |
| 1 | 10 |
| 1 | 3 |
| 1 | 5 |
| 1 | 17 |
| 1 | 12 |
| 1 | 16 |
+-------+----------+
You'll need to filter out the later results using something else.
SELECT u.id
, CONCAT(u.firstName, ' ', u.lastName) AS fullname
FROM cpnc_User u
JOIN
( SELECT min(id) AS minid
, firstName
, lastName
FROM cpnc_User
GROUP BY firstName, lastName
HAVING COUNT(*) > 1
) AS grp
ON u.firstName = grp.firstName
AND u.lastName = grp.lastName
ORDER BY grp.minid
, u.id
The ORDER BY grp.minid ensures that users with same first and last name stay grouped together in the output.
OK, you are doing a concatenation, then doing a compare on this, which essentially means that the DB is going to have to do something to every single row of the database.
How about a slightly different approach, you are holding surname and first name separately. So first select all those instances where surname appears > 1 time in your database. Now this has cut your population down dramatically.
Now you can do a compare on the first name to find out where the matches are.