MySQL - Counting two things with different conditions - mysql

I want to count two things under different conditions in one query.
SELECT COUNT(*) AS count FROM table_name WHERE name = ?
and
SELECT COUNT(*) as count FROM table_name WHERE address = ? AND port = ?
I need to have a count for rows that have a certain address and certain port, and a SEPARATE count for rows that have a certain name.
I'm aware that I could do
SELECT (COUNT*) as count FROM table_name WHERE (address = ? AND port = ?) OR name = ?
However that is a single count, and I need them to be separate so I can display a more accurate message to the user.
How might I go about doing this? Help would be appreciated!

What about simply:
SELECT
SUM(IF(name = ?, 1, 0)) AS name_count,
SUM(IF(address = ? AND port = ?, 1, 0)) AS addr_count
FROM
table_name

SELECT SUM(CASE WHEN Name = ? THEN 1 ELSE 0 END) as name_match
, SUM(CASE WHEN Address = ? AND Port = ? THEN 1 ELSE 0 END) as address_match
FROM table_name
WHERE (address = ? AND port = ?) OR name = ?

SELECT
COUNT( CASE WHEN n1 = 'J' THEN 1 END ) AS t1,
COUNT( CASE WHEN n2 = 'C' THEN 1 END ) AS t2,
COUNT( CASE WHEN n3 = 'K' THEN 1 END ) AS t3
FROM test
Using COUNT(CASE...), you can get the count of two-column from single table, even when conditions for both are different (eg: Get count of J from n1 column and count of C from n2 column, and so on..)
Table: test
+----+----+----+----+
| id | n1 | n2 | n3 |
|----+----+----+----+
| 1 | J | C | K |
|----+----+----+----+
| 1 | J | C | F |
|----+----+----+----+
| 1 | J | K | C |
|----+----+----+----+
| 1 | K | K | C |
|----+----+----+----+
Result:
+----+----+----+
| t1 | t2 | t3 |
|----+----+----+
| 3 | 2 | 1 |
|----+----+----+

Might be easiest just to do a Union:
SELECT COUNT(*) AS count FROM table_name WHERE name = ?
GROUP BY name
UNION ALL
SELECT COUNT(*) as count FROM table_name WHERE address = ? AND port = ?
GROUP BY address, port

Related

Limit query results based on value of multiple columns

I am using MySQL 5.6 and I have a table structure like below
| user_id | email_1 | email_2 | email_3 |
| 1 | abc#test.com | | |
| 2 | xyz#test.com | | joe#test.com |
| 3 | | test#test.com | bob#joh.com |
| 4 | | | x#y.com |
I want to fetch the first n email addresses from this table.
For example, if I want to fetch the first 5 then only the first 3 rows should return.
This makes certain assumptions about the uniqueness of data, that might not be true...
SELECT DISTINCT x.* FROM my_table x
JOIN
(SELECT user_id, 1 email_id,email_1 email FROM my_table WHERE email_1 IS NOT NULL
UNION ALL
SELECT user_id, 2 email_id,email_2 email FROM my_table WHERE email_2 IS NOT NULL
UNION ALL
SELECT user_id, 3 email_id,email_3 email FROM my_table WHERE email_3 IS NOT NULL
ORDER BY user_id, email_id LIMIT 5
) y
ON y.user_id = x.user_id
AND CASE WHEN y.email_id = 1 THEN y.email = x.email_1
WHEN y.email_id = 2 THEN y.email = x.email_2
WHEN y.email_id = 3 THEN y.email = x.email_3
END;
You want to return as many rows as necessary to get five emails. So you need a running total of the email count.
select user_id, email_1, email_2, email_3
from
(
select
user_id, email_1, email_2, email_3,
coalesce(
sum((email_1 is not null) + (email_2 is not null) + (email_3 is not null))
over (order by user_id rows between unbounded preceding and 1 preceding)
, 0) as cnt_prev
from mytable
) counted
where cnt_prev < 5 -- take the row if the previous row has not reached the count of 5
order by user_id;
You need a current MySQL version for SUM OVER to work.
The counting of the emails uses a MySQL feature: true equals 1 and false equals 0 in MySQL. Thus (email_1 is not null) + (email_2 is not null) + (email_3 is not null) counts the emails in the row.
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ac415e71733699547196ae01cb1caf13

sql query to compare values row wise?

i have a table which contains some data as an example:
+----------+-----+------+
| order_id | poi | povi |
+----------+-----+------+
| 1 | A | a |
| 1 | B | b |
| 1 | C | c |
| 2 | A | a |
| 2 | B | b |
| 2 | C | c |
| 3 | A | a |
| 3 | B | b |
| 4 | C | c |
| 5 | A | a |
| 5 | B | b |
| 6 | C | c |
| 7 | A | a |
| 8 | B | b |
| 9 | C | c |
+----------+-----+------+
i have 3 set of values of poi and povi like {A,a},{B,b},{C,c}
i want to get the order_id which contains all three of them, like in the above case the output should be.(order_id which have poi and povi as {A,a} and {B,b} and {C,c} but the problem is that they are diffrent rows)
+----------+
| order_id |
+----------+
| 1 |
| 2 |
+----------+
any idea?
So many times people just getting started ask similar questions to those already asked and answered, including this common scenario. However, not being able to apply know answers to your scenario doesn't help you wrap your head around what is asked, or how the query works in their own scenario... That said, lets look at yours.
You want all DISTINCT orders that have ALL of the following A/a, B/b, C/c entries. Multiple ways to resolve, but the most common is with a where / group by / having.
Start with something simple, looking for any order that has A/a
select
yt.Order_id
from
YourTable yt
where
( yt.poi = 'A' AND yt.poiv = 'a' )
and you would get order 1, 2, 3, 5 and 7. That is simple...
Now, add in your other criteria
select
yt.Order_id
from
YourTable yt
where
( yt.poi = 'A' AND yt.poiv = 'a' )
OR ( yt.poi = 'B' AND yt.poiv = 'b' )
OR ( yt.poi = 'C' AND yt.poiv = 'c' )
and this will give you all rows, but not what you want, but you should be able to see the where criteria is checking for both parts of POI / POIV with an OR between each possible combination. You obviously can not have one record that has a POI of both "A" and "B", that is why the "OR" between each paired ( AND ) criteria. But again, this gives ALL rows. But it is also qualifying only the pieces. So lets add one next step... a group by via the order, but HAVING clause expecting 3 records...
select
ytA.Order_id
from
YourTable ytA
where
( yt.poi = 'A' AND yt.poiv = 'a' )
OR ( yt.poi = 'B' AND yt.poiv = 'b' )
OR ( yt.poi = 'C' AND yt.poiv = 'c' )
group by
yt.Order_id
HAVING
count(*) = 3
The count(*) is to count how many records qualified the WHERE clause and will only return those records that had 3 entries.
Now, what if someone has multiple orders of A/a, A/a, B/b... This COULD give a false answer returned value, but please confirm these queries to meet your needs.
Although accepted, here is another way I would have written the query... somewhat similar to another post below. The premise of this version of the query is to utilize an index and qualify at least 1 record found before trying to find ALL. In this case, it first qualifies for those with an A/a. If an order does not have that, it does not care about looking for a B/b, C/c. If it DOES, then the join qualifies to the next levels too
select
ytA.Order_id
from
YourTable ytA
JOIN YourTable ytB
on ytA.Order_id = ytB.Order_id
AND ytB.poi = 'B'
AND ytB.poiv = 'b'
JOIN YourTable ytC
on ytB.Order_id = ytC.Order_id
AND ytC.poi = 'C'
AND ytC.poiv = 'c'
where
ytA.poi = 'A'
AND ytA.poiv = 'a'
find the "intersection" of lists, each of which contains one set
select id
from
(select id from mytable where poi = 'A' and povi= 'a') t1
inner join
(select id from mytable where poi = 'B' and povi= 'b') t2
using(id)
inner join
(select id from mytable where poi = 'C' and povi= 'c') t3
using(id)
demo

How to count and compare value of colume in the same table

I need to create procedure which will find the worst user in one table by counting status with 'P' and 'U' calculate ratio then compare it with other users, take that user id and find it in another table and write all user information that are in two tables. And i call that procedure from java application.
Table Rezervacija
id | SifKorisnikPK | Status
1 | 1 | 'P'
2 | 1 | 'U'
3 | 1 | 'U'
4 | 2 | 'U'
5 | 2 | 'P'
6 | 2 | 'P'
7 | 2 | 'P'
8 | 2 | 'P'
9 | 3 | 'U'
10 | 3 | 'U'
11 | 3 | 'P'
12 | 3 | 'P'
13 | 3 | 'P'
14 | 3 | 'P'
So the user with id 2 is worst user because of 4 P's, and one U, so his ratio is 3 P. Then it's should go to Korisnik table and return all the info for user with id 2
I try with this but can't get any return values
CREATE PROCEDURE sp_getBadPremiumUsers
AS
BEGIN
DECLARE #BrLr int
DECLARE #BrDr int
SELECT #BrLr = (SELECT COUNT(*) FROM Rezervacija A
INNER JOIN Rezervacija B
ON A.SifKorisnikPK = B.SifKorisnikPK
WHERE A.Status = 'P')
SELECT #BrDr = (SELECT COUNT(*) FROM Rezervacija A
INNER JOIN Rezervacija B
ON A.SifKorisnikPK = B.SifKorisnikPK
WHERE A.Status = 'U')
SELECT * INTO #PremiKoris FROM Korisnik
INNER JOIN PremiumKorisnik
ON SifKorisnik = SifKorisnikPK
ALTER TABLE #PremiKoris
DROP COLUMN Password
SELECT * FROM #PremiKoris
WHERE #BrLr > #BrDr
DROP TABLE #PremiKoris
END
GO
You can get the worse user using:
select r.SifKorisnikPK
from Rezervacija r
where status = 'P'
group by SifKorisnikPK
order by count(*) desc
limit 1;
You can then use this in a query to get more information:
select k.*
from PremiumKorisnik k join
(select r.SifKorisnikPK
from Rezervacija r
where status = 'P'
group by SifKorisnikPK
order by count(*) desc
limit 1
) r
on r.SifKorisnikPK = k.SifKorisnikPK
This does in one query what you describe you want to do.

How to select data given several rows match?

I have a configuration table and a users table.
users:
| id | name |
|----|----------|
| 0 | Bob |
| 1 | Ted |
| 2 | Sam |
config:
| user_id | name | value |
|---------|----------|-------|
| 0 | a | 11 |
| 0 | b | 2 |
| 0 | c | 54 |
| 1 | a | 5 |
| 1 | b | 3 |
| 1 | c | 0 |
| 2 | a | 1 |
| 2 | b | 74 |
| 2 | c | 54 |
I normalized the configuration this way since the config will be of unknown amount, but I will have to query users based on this config, so it couldn't be stored in a serialized form.
My issue is how do I find users based on multiple rows? For instance:
Select all users with a > 4 and b < 5
This should return Bob and Ted.
Using groups:
SELECT u.name
FROM users u
JOIN config c
ON c.user_id = u.id
GROUP BY u.name
HAVING MAX(c.name = 'a' AND c.value > 4)
AND MAX(c.name = 'b' AND c.value < 5)
Using joins:
SELECT u.name
FROM users u
JOIN config a
ON a.user_id = u.id
AND a.name = 'a'
AND a.value > 4
JOIN config b
ON b.user_id = u.id
AND b.name = 'b'
AND b.value < 5
I prefer the JOIN method, as you can name each JOIN after the property and collect the conditions in the JOIN. You also don't have to worry about the GROUPs which makes it more flexible for aggregates.
A bonus over EXISTS is that you can easily access the properties of the config if you require further joins/calculations.
Try this:
SELECT us.name
FROM USERS us
WHERE EXISTS (SELECT name FROM CONFIG WHERE name='a' AND value>4 AND user_id=us.id)
AND EXISTS (SELECT name FROM CONFIG WHERE name='b' AND value<5 AND user_id=us.id)
Alternatively, you can use two joins:
SELECT us.name
FROM USERS us, CONFIG c1, CONFIG c2
WHERE us.id=c1.user_id
AND c1.name='a'
AND c1.value<4
AND us.id=c2.user_id
AND c2.name='b'
AND c2.value>5
select u.name from users as u inner join config as c on u.id = c.user_id where (c.name = "a" and c.value > 4) or (c.name = "b" and c.value < 5);
you will need two exists statements for this:
SELECT * FROM users u
WHERE EXISTS ( SELECT 1
FROM config
WHERE user_id = u.id
AND name = 'a' AND value > 4)
AND EXISTS ( SELECT 1
FROM config
WHERE user_id = u.id
AND name = 'b' AND value < 5)
the following query achieves the result, as does not rely joining between the tables in any way.
to include more of "name" you could add another OR clause inside the HAVING.
NOTE: This has been tested on the W3Schools WebSQL Interface
SELECT
value,
name,
user_id
FROM config
GROUP BY value, name, user_id
HAVING
(
(value > 4 AND name = 'a')
OR (value < 5 AND name = 'b')
)
After ditching the grouping (thanks Arth)
SELECT
value,
name,
user_id
FROM config
WHERE
(
(value > 4 AND name = 'a')
OR (value < 5 AND name = 'b')
)

SQL Aggregation with SUM, GROUP BY and JOIN (many-to-many)

Here's an example Table layout:
TABLE_A: TABLE_B: TABLE_A_B:
id | a | b | c id | name a_id | b_id
--------------------- --------- -----------
1 | true | X | A 1 | A 1 | 1
2 | true | Z | null 2 | B 1 | 2
3 | false | X | null 3 | C 2 | 2
4 | true | Y | Q 4 | 1
5 | false | null | null 4 | 2
5 | 1
Possible Values:
TABLE_A.a: true, false
TABLE_A.b: X, Y, Z
TABLE_A.c: A, B, C, ... basically arbitrary
TABLE_B.name: A, B, C, ... basically arbitrary
What I want to achieve:
SELECT all rows from TABLE_A
SUM(where a = true),
SUM(where a = false),
SUM(where b = 'X'),
SUM(where b = 'Y'),
SUM(where b = 'Z'),
SUM(where b IS NULL),
and also get the SUMs for all distinct TABLE_A.c values.
and also get the SUMs for all those TABLE_A_B relations.
The result for the example Table above should look like:
aTrue | aFalse | bX | bY | bZ | bNull | cA | cQ | cNull | nameA | nameB | nameC
-------------------------------------------------------------------------------
3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 3 | 3 | 3 | 0
What I've done so far:
SELECT
SUM(CASE WHEN a = true THEN 1 ELSE 0 END) AS aTrue,
SUM(CASE WHEN b = false THEN 1 ELSE 0 END) AS aFalse,
SUM(CASE WHEN b = 'X' THEN 1 ELSE 0 END) AS bX,
...
FROM TABLE_A
What's my problem?
Selecting column TABLE_A.a and TABLE_A.b is easy, because there's a fixed number of possible values.
But I can't figure out how to count the distinct values of TABLE_A.c. And basically the same problem for the JOINed TABLE_B, because the number of values within TABLE_B is unknown and can change over time.
Thanks for your help! :)
EDIT1: New (preferred) SQL result structure:
column | value | sum
----------------------------
TABLE_A.a | true | 3
TABLE_A.a | false | 2
TABLE_A.b | X | 2
TABLE_A.b | Y | 1
TABLE_A.b | Z | 1
TABLE_A.b | null | 1
TABLE_A.c | A | 1
TABLE_A.c | Q | 1
TABLE_A.c | null | 3
TABLE_B.name | A | 3
TABLE_B.name | B | 3
TABLE_B.name | C | 0
From your original request of rows as a simulated pivot. By doing a SUM( logical condition ) basically returns 1 if true, 0 if false. So, since the column "a" is true or false, simple sum of "a" or NOT "a" (for the false counts -- NOT FALSE = TRUE). Similarly, your "b" column, so b='X' = true counted as 1, else 0.
In other sql engines, you might see it as SUM( case/when ).
Now, since your table counts don't rely on each other, they can be separate SUM() into their own sub-alias query references (pqA and pqB for pre-queryA and pre-queryB respectively). Since no group by, they will each result in a single row. With no join will create a Cartesian, but since 1:1 ratio, will only return a single record of all columns you want.
SELECT
pqA.*, pqB.*
from
( SELECT
SUM( ta.a ) aTrue,
SUM( NOT ta.a ) aFalse,
SUM( ta.b = 'X' ) bX,
SUM( ta.b = 'Y' ) bY,
SUM( ta.b = 'Z' ) bZ,
SUM( ta.b is null ) bNULL,
SUM( ta.c = 'A' ) cA,
SUM( ta.c = 'Q' ) cQ,
SUM( ta.c is null ) cNULL,
COUNT( distinct ta.c ) DistC
from
table_a ta ) pqA,
( SELECT
SUM( b.Name = 'A' ) nameA,
SUM( b.Name = 'B' ) nameB,
SUM( b.Name = 'C' ) nameC
from
table_a_b t_ab
join table_b b
ON t_ab.b_id = b.id ) pqB
This option gives your second (preferred) output
SELECT
MAX( 'TABLE_A.a ' ) as Basis,
CASE when a then 'true' else 'false' end Value,
COUNT(*) finalCnt
from
TABLE_A
group by
a
UNION ALL
SELECT
MAX( 'TABLE_A.b ' ) as Basis,
b Value,
COUNT(*) finalCnt
from
TABLE_A
group by
b
UNION ALL
SELECT
MAX( 'TABLE_A.c ' ) as Basis,
c Value,
COUNT(*) finalCnt
from
TABLE_A
group by
c
UNION ALL
SELECT
MAX( 'TABLE_B.name ' ) as Basis,
b.Name Value,
COUNT(*) finalCnt
from
table_a_b t_ab
join table_b b
ON t_ab.b_id = b.id
group by
b.Name
I think You will need to build dynamic query as you don't know possible values for column C in table A. So you can write store procedure where you can get list of distinct value for Column C in one variable and by using "Do WHILE" you can construct your dynamic query.
Please let me know if you need more help in detail
Dynamic SQL