SQL count(*) only returns one row - mysql

I'm a beginner in mysql. The following codes look stupid but it is why my codes cannot work. I thought it would give me n rows. However, it only gave me one row. Suppose there are n rows in the table seat, and there are two fields including id, student.
I understand count(*) will return one number. I thought that, for each row, sql will check whether id equals total number of rows. But it didn't.
select id = count(*) as id, student
from seat
The following codes did what I wanted. Could anyone explain what makes them give different results?
select id = count2 as id, student
from seat, (select count(*) as count2 from seat) seat2

One way:
set #num := (select count(*) from seat);
select
id
, student
, case when id = #num then 'special' else 'normal' end as x
from seat
Another way:
select
id
, student
, case when id = x.y then 'special' else 'normal' end as x
from seat
cross join (select count(*) as y from seat) as x
;
NB: In both examples you need the subquery to return just one value in one row.

Given
+----+----------+
| id | lastname |
+----+----------+
| 1 | aaa |
| 2 | bbb |
| 3 | ccc |
+----+----------+
3 rows in set (0.00 sec)
Your first query is an instruction to mysql to count across the entire set
MariaDB [sandbox]> select id = count(*) as id, lastname
-> from users;
+------+----------+
| id | lastname |
+------+----------+
| 0 | aaa |
+------+----------+
1 row in set (0.00 sec)
Clearly this does not return the last id or the correct count. The correct count would be returned by this
select id, lastname , count(*)
from users;
+------+----------+----------+
| id | lastname | count(*) |
+------+----------+----------+
| 1 | aaa | 3 |
+------+----------+----------+
1 row in set (0.00 sec)
And the id and lastname are indeterminate.
Your second query returns a cartesian product
select id = count2, lastname, count2
-> from users, (select count(*) as count2 from users) seat2
-> ;
+-------------+----------+--------+
| id = count2 | lastname | count2 |
+-------------+----------+--------+
| 0 | aaa | 3 |
| 0 | bbb | 3 |
| 1 | ccc | 3 |
+-------------+----------+--------+
3 rows in set (0.00 sec)
Which again does not identify the id which matches the count..
Assuming the id is a number and increments in some fashion a way which does find the last id is
MariaDB [sandbox]> select id,lastname
-> from users
-> where id = (select count(*) from users);
+----+----------+
| id | lastname |
+----+----------+
| 3 | ccc |
+----+----------+
1 row in set (0.00 sec)
But this is dangerous - if id is auto_increment then the number of rows may not match the id because of the way auto_increment is treated (it can be overridden, insert update on duplicate key etc.)
A safer way is to
MariaDB [sandbox]> select id,lastname
-> from users
-> where id = (select max(id) from users);
+----+----------+
| id | lastname |
+----+----------+
| 3 | ccc |
+----+----------+
1 row in set (0.00 sec)

I think you should try something like this :
select id = (select count(*) from seat) as id, student
from seat
Basically your previous request with this statement in the FROM clause (select count(*) as count2 from seat) gives you only row so it can't compare to all of your rows because you put it in your FROM clause two tables seat and seat2 without the same number of rows.
I think you don't need to use two tables.

I thought it [count(*)] would give me n rows. However, it only gave me one row.
Nope. You are using COUNT(*) with no GROUP BY. That makes your query an aggregation query. An aggregation query with no GROUP BY always returns one row -- even when there are no rows in the table.
Your query would be invalid in almost any other database (and in the default configuration of future versions of MySQL) because you have an aggregation query and the column id is not in the GROUP BY nor an argument to an aggregation function.
In MySQL, probably the simplest way to do what you intend uses a subquery:
select id = (select count(*) from seat) as id, student
from seat
I have no idea why you would want to call the boolean result id, but that is what your original query expresses.
Conclusion: You need to practice aggregation queries and look up what GROUP BY does.

Related

In MySQL, select one row out of multiple rows based on condition, otherwise select first row and exclude the rest

I am interested in MySQL of writing a query that looks through a list consisting of IDs and locations. Each ID represents a unique person who can be tied to multiple locations.
I have the following table to simplify things:
+----+----------+
| ID | Location |
+----+----------+
| 1 | Bldg#1 |
| 1 | Bldg#2 |
| 2 | Bldg#3 |
+----+----------+
I am looking to deduplicate the above table to only end up with ONE row per ID, but I would also like to add a conditional that preferences Bldg#1 for any given ID. In other words, given a table of multiple rows with potentially the same ID and multiple locations, I would like to write a query that outputs 1 row per ID, and if any of the rows associated with that ID also have a location of Bldg#1, I want to keep that row and drop the rest. Otherwise, I just want to keep one arbitrary location row for that ID.
For the above table, I would like the following as output:
+----+----------+
| ID | Location |
+----+----------+
| 1 | Bldg#1 |
| 2 | Bldg#3 |
+----+----------+
You can group by id and use conditional aggregation:
select id,
case
when max(location = 'Bldg#1') then 'Bldg#1'
else any_value(location)
end location
from tablename
group by id
See the demo.
Results:
| id | location |
| --- | -------- |
| 1 | Bldg#1 |
| 2 | Bldg#3 |
You can use row_number() with a case expression:
select id, location
from (select t.*,
row_number() over (partition by id
order by (case location when 'Bldg#1' then 1 when 'Bldg#2' then 2 when 'Bldg#3' then 3 else 4 end)
) as seqnum
from t
) t
where seqnum = 1;
This does not assume any particular ordering -- such as alphabetical ordering.
Is this you looking for?
Exmaple:
Query:
DROP TABLE IF EXISTS #TEST
CREATE TABLE #TEST (ID INT, Location NVARCHAR(10))
INSERT INTO #TEST
SELECT 1,'Bldg#1'
UNION
SELECT 1,'Bldg#2'
UNION
SELECT 2,'Bldg#3'
SELECT ID,Location FROM (
SELECT ID,Location, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID) AS RNM
FROM #TEST) T
WHERE RNM = 1
inner query will make sure the location is in order so that Bldg#1 is always the first for each id, so then the outer group by will pick the first record
SELECT * FROM
(
SELECT id, location
FROM location
ORDER BY id, location ASC
)a
GROUP BY id

How to compute occurence probability of rows inside a SQL (MySQL) table using a single query?

I have a table containing some similar rows representing objects for a game. I use this table as a way to select objects randomly. Of course, I ignore the size of the table. My problem is that I would like to have a single query that returns the probability to select every object and I don't know how to proceed.
I can get the total number of objects I have in my table:
select count(id) from objects_desert_tb;
Which returns
+-----------+
| count(id) |
+-----------+
| 81 |
+-----------+
1 row in set (0.00 sec)
and I have a query that return the number of occurence of every object in the table:
select name, (count(name)) from objects_desert_tb group by name;
which gives:
+-------------------+---------------+
| name | (count(name)) |
+-------------------+---------------+
| carrots | 5 |
| metal_scraps | 14 |
| plastic_tarpaulin | 8 |
| rocks_and_stones | 30 |
| wood_scraps | 24 |
+-------------------+---------------+
5 rows in set (0.00 sec)
Computing the probability for every object just consist in doing (count(name)) divided by the total number of rows in the table. For example with the row carrots, just compute 5/81, from the two queries given above. I would like a single query that would return:
+-------------------+---------------+
| carrots | 5/81 = 0.06172839
| metal_scraps | 0.1728...
| plastic_tarpaulin | 0.09876...
| rocks_and_stones | 0.37...
| wood_scraps | 0.29...
+-------------------+---------------+
Is there a way to use the size of the table as a variable inside a SQL query? Maybe by nesting several queries?
Cross join your queries:
select c.name, c.counter / t.total probability
from (
select name, count(name) counter
from objects_desert_tb
group by name
) c cross join (
select count(id) total
from objects_desert_tb
) t
In MySQL 8+, you would just use window functions:
select name, count(*) as cnt,
count(*) / sum(count(*)) over () as ratio
from objects_desert_tb
group by name;

MySQL Select all values from 1st column that have specific value in 2nd column

I have a mysql table with 2 columns.
+---------+-----------+
| Barcode | StationID |
+---------+-----------+
| 89411 | 1 |
| 89411 | 2 |
| 89411 | 3 |
| 89412 | 1 |
| 89413 | 1 |
+---------+-----------+
I would like to select all valus from Barcode column which have StationID = 1 and do NOT have a StationID different than 1.
As shown in the picture Barcode 89411 appears three times with different StationID and should be excluded from the result.
Can you help me make a query?
Another approach is to use an EXISTS query:
SELECT t1.*
FROM yourTable t1
WHERE
t1.StationID = 1 AND
NOT EXISTS (SELECT 1 FROM yourTable t2
WHERE t1.Barcode = t2.Barcode AND t2.StationID <> 1);
Demo
Use aggregation function GROUP_CONCAT, and use HAVING clause to filter out those barcodes, which has only one StationID, that is '1':
SELECT barcode, GROUP_CONCAT(DISTINCT StationID) AS stations
FROM table_name
GROUP BY barcode
HAVING stations = '1';
Try this: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=641d334c5f9e57bbdde07e4f24365f88
select barcode from tablename
group by barcode
having sum(case when sanctionid=1 then 0 else 1 end)=0
output:
barcode
89412
89413

Using multiple JOIN's and GROUP BY - ORDER BY not working as expected

SELECT msg.msgFrom,
mem.memberID,
mem.memberFirstName,
mem.memberLastName,
msg.msgJobID,
msg.msgMessage,
msg.msgRead,
job.jobDescription
FROM messages msg JOIN members mem ON msg.msgFrom = mem.memberID
JOIN jobs job on msg.msgJobID = job.jobID
WHERE msgTo = ?
GROUP BY msgJobID
ORDER BY msg.msgRead DESC
The ORDER BY in my MySQL statement doesn't seem to be working. Just wondering if someone could help me out.
Thanks
You are using grouping for no reason here. GROUP BY is used for aggregation (SUM,MAX,MIN,etc).
You cannot order by the field you are using as a result of your unnecessary grouping.
Also, define your join types (good practice)
Try this instead:
SELECT msg.msgFrom,
mem.memberID,
mem.memberFirstName,
mem.memberLastName,
msg.msgJobID,
msg.msgMessage,
msg.msgRead,
job.jobDescription
FROM messages msg
INNER JOIN members mem
ON msg.msgFrom = mem.memberID
INNER JOIN jobs job
on msg.msgJobID = job.jobID
WHERE msgTo = ?
ORDER BY msgJobID, msg.msgRead DESC -- Use 2x order by's
Just to illustrate the effect of a group by,
given
MariaDB [sandbox]> select * from person;
+-----------+-------+
| person_id | name |
+-----------+-------+
| 1 | Test1 |
| 2 | Test2 |
| 3 | Test3 |
+-----------+-------+
3 rows in set (0.00 sec)
MariaDB [sandbox]> select * from personroles;
+-----------+---------+
| person_id | role_id |
+-----------+---------+
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 2 | 1 |
| 3 | 1 |
+-----------+---------+
5 rows in set (0.00 sec)
a group by on role_id returns 3 rows (instead of six) and the other columns in the select statement are not guaranteed to be the same in any 2 executions of the code.
so for example
MariaDB [sandbox]> select p.person_id,p.name,pr.role_id
-> from person p
-> join personroles pr on pr.person_id = p.person_id
-> group by pr.role_id ;
+-----------+-------+---------+
| person_id | name | role_id |
+-----------+-------+---------+
| 1 | Test1 | 1 |
| 1 | Test1 | 2 |
| 2 | Test2 | 3 |
+-----------+-------+---------+
3 rows in set (0.00 sec)
I suspect you just need an order by as JohnHC suggests.
As per the documentation:
[...] If the [selected] columns are not functionally dependent on GROUP BY
columns [...] the server is free to choose any value from each group, so
unless they are the same, the values chosen are indeterminate, which
is probably not what you want. Furthermore, the selection of values
from each group cannot be influenced by adding an ORDER BY clause.
Result set sorting occurs after values have been chosen, and ORDER BY
does not affect which value within each group the server chooses.
I'd recommend enabling ONLY_FULL_GROUP_BY on your server so your development is ANSI compliant. It will save you time in the future, as both your queries and your sql skills will become more portable over different dbms implementations.

Count all records that does not exist to other table - SQL Query

I have two(2) tables and I'm trying to count all records from Table1 and Table1_delta were pagename from Table1_delta is not yet listed into Table1. Incase pagename from Table1_delta is listed to Table1, status must be 1 so that it will be included in count result.
Sample table structure:
Table1
+-----------+--------+
| pagename | status |
+-----------+--------+
| pagename1 | 2 |
| pagename2 | 1 |
+-----------+--------+
Table1_delta
+-----------+
| pagename |
+-----------+
| pagename1 |
| pagename2 |
| pagename3 |
| pagename4 |
+-----------+
The table sample should return "3".
pagename3 and pagename4 is not listed in Table1(that returns 2) and pagename2 from Table1 has an status = 1(that returns 1). In total there are 3 pagenames from Table1_delta that are not listed in Table1 and record from Table1 where status = 1. I'm wondering on how will be the query of this? I'm using MySQL v5.6.17. Thanks!
Here is an alternative solution using joins:
SELECT COUNT(*)
FROM Table1_delta t1 LEFT JOIN Table1 t2
ON t1.pagename = t2.pagename
WHERE t2.status IS NULL OR t2.status = 1
Here is what the temporary table from the above query looks like:
+-----------+--------+
| pagename | status |
+-----------+--------+
| pagename1 | 2 | # this row is NOT counted
| pagename2 | 1 | # +1 this row has status = 1 and is counted
| pagename3 | null | # +1 this row has status = null and is counted
| pagename4 | null | # +1 this row is also null and is counted
+-----------+--------+
Check out the link below for a running demo.
SQLFiddle
Try using joins
Select count(Table1_delta.pagename) from Table1_delta
INNER JOIN Table1 ON
Table1_delta.pagename != Table1 .pagename
AND Table1.status != 1
If I've understood correctly:
SELECT COUNT(*) FROM Table1_Delta
WHERE pagename NOT IN
(SELECT pagename FROM Table1 WHERE status = 1)
Update
As requested in the comments, here's what this query does:
First, the subquery: SELECT pagename FROM Table1 WHERE status = 1, retrieves the pagename field from those Table1 records where status is 1.
So in the example case, it'll return a single row, containing pagename2.
Then the main query counts all the records in Table1_Delta (SELECT COUNT(*) FROM Table1_Delta) whose Pagename does not contain (WHERE Pagename NOT IN (<subquery>)) those values returned from the subquery.
So this would match 3 entries (pagename1, pagename3, pagename4), and that's the count you get
Historically, using sub-queries is considered slower than using joins, but frankly, RDBMS's have come a long way optimizing queries, and for simple cases like this, it would be "probably" (I haven't measured) faster. It actually depends on the real case and DB... but the SQL code is much more self-explanatory than joins IMO. Your mileage may vary.