COUNT() with a nested subquery - mysql

Can I count a column with the number of instances of a character in a particular column?
+---+---+
| i | p |
+---+---+
| A | 3 |
| B | 3 |
| C | 0 |
| A | 1 |
| B | 1 |
| C | 3 |
| A | 1 |
| B | 0 |
| C | 0 |
+---+---+
Query:
SELECT i, SUM(p) AS Sp, COUNT(p) AS Cp FROM table
GROUP BY i
Id like to get this:
+---+----+----+-----+-----+-----+
| i | Sp | Cp | x3x | x1x | x0x |
+---+----+----+-----+-----+-----+
| A | 5 | 3 | 1 | 2 | 0 |
| B | 4 | 3 | 1 | 1 | 0 |
| C | 4 | 3 | 1 | 0 | 2 |
+---+----+----+-----+-----+-----+
Essentially I want to COUNT the instances of 3, 0 or 1 in a column where the column is grouped by the id 'i'
I tried this as well as a number of variations, but I can't seem to get it going.
COUNT(P WHERE p='3'), COUNT(P WHERE p='1'), COUNT(P WHERE p='0'),
Is there a means by which I can place a subquery within a COUNT() that I've missed in my research?
I also tried
COUNT(Points='3'), COUNT(='1'), COUNT(Points='0'),

You are close:
select i, sum(points), count(*),
sum(Points = 3), sum(points = 1), sum(Points = 0)
from t
group by i;
One minor difference in this case is that a removed the single quotes around the values. When comparing to a number, don't use single quotes. Only use single quotes for string and date constants.
The more important change is from count() to sum(). count() counts the number of non-NULL values. Well, the boolean expression is true or false -- but not really NULL (unless points is NULL, which is not the case with your data).
MySQL treats boolean values as integers in a numeric context, with 0 for false and 1 for true. So, adding them up counts the number of times that something is true.

Related

Select count of rows matching a condition grouped by increments of Id in MySQL

I have a table that has an autoincremented numeric primary. I'm trying to get a count of rows that match a condition grouped by increments of their primary key. Given the data:
| id | value |
|----|-------|
| 1 | a |
| 2 | b |
| 3 | a |
| 4 | a |
| 5 | b |
| 6 | a |
| 7 | b |
| 8 | a |
| 9 | b |
| 10 | b |
| 11 | a |
| 12 | b |
If I wanted to know how many rows matched value = 'a' for every five rows, the result should be:
| count(0) |
|----------|
| 3 |
| 2 |
| 1 |
I can nest a series of subqueries in the SELECT statement, like such:
SELECT (SELECT count(0)
FROM table
WHERE value = 'a'
AND id > 0
AND id <= 5) AS `1-5`,
(SELECT count(0)
FROM table
WHERE value = 'a'
AND id > 5
AND id <=10) AS `6-10`,
...
But is there a way to do this with a GROUP BY statement or something similar where I don't have to manually write out the increments? If not, is there a more time efficient method than a series of subqueries in the SELECT statement as in the above example?
You could divide the ID by 5 and then ceil the result:
SELECT CONCAT((CEIL(id / 5.0) - 1) * 5, '-', CEIL(id / 5.0) * 5), COUNT(*)
FROM mytable
WHERE value = 'a'
GROUP BY CEIL(id / 5.0)
The following aggregated query should do the trick :
SELECT CEIL(id/5), COUNT(*)
FROM table
WHERE value = 'a'
GROUP BY CEIL(id/5)

MySQL: SUM function applied to a formula contained in field selected by another query

I'm in the need to perform a select SUM() where that is a formula contained into a field selected by another query.
Example:
table_A (the "formula" field contains, in each cell, an arithmetic expression involving columns from table B):
+------------+--------------+------------+
| Product_id | related_prod | formula |
+------------+--------------+------------+
| U1 | C2 | col2-col1 |
| U2 | C3 | col3-col2 |
| U3 | C4 | col3-col1 |
+------------+--------------+------------+
table_B:
+------------+---------+------------+----------+------+------+------+
| Product_id | year_id | company_id | month_id | col1 | col2 | col3 |
+------------+---------+------------+----------+------+------+------+
| C2 | 2017 | 1 | 2 | 100 | 200 | 300 |
| C3 | 2017 | 1 | 2 | 400 | 500 | 600 |
| C4 | 2017 | 1 | 2 | 700 | 800 | 900 |
+------------+---------+------------+----------+------+------+------+
I do, then, the following query:
SELECT
SUM(totals.relaz) as final_sum,
totals.relaz as 'col',
totals.prod as 'prod',
totals.cons as 'cons',
m.company_id, m.month_id, m.year_id, FROM `table_B` m,
( SELECT formula as relaz,
related_prod as prod,
p.product_id as cons FROM table_A p )
AS totals
WHERE m.product_id=totals.prod
GROUP BY m.company_id, m.year_id, m.month_id, m.product_id, totals.cons
After the select I'd do expect that, considering for example the only product 'U1', the corresponding row would be
+-----------+-----------+------+------+------------+----------+---------+
| final_sum | col | prod | cons | company_id | month_id | year_id |
+-----------+-----------+------+------+------------+----------+---------+
| 100 | col2-col1 | C2 | U1 | 1 | 2 | 2017 |
+-----------+-----------+------+------+------------+----------+---------+
Instead, what I get is
+-----------+-----------+------+------+------------+----------+---------+
| final_sum | col | prod | cons | company_id | month_id | year_id |
+-----------+-----------+------+------+------------+----------+---------+
| 0 | col2-col1 | C2 | U1 | 1 | 2 | 2017 |
+-----------+-----------+------+------+------------+----------+---------+
i.e. the final_sum field is always set to 0, despite the 'col' field contains the correct equation.
What am I doing wrong?
Thank you in advance
Alex
You are trying to get sum from a string column (table_A.formula). This will result 0. MySQL/MariaDB will not try to convert the strings to column references and evaluate the formula in the string.
Another thing is that you should list all columns not in aggregate function in GROUP BY.
To get the result you want, use:
SELECT
SUM(CASE
WHEN a.formula = 'col2-col1' THEN b.col2-b.col1
WHEN a.formula = 'col3-col1' THEN b.col3-b.col1
WHEN a.formula = 'col3-col2' THEN b.col3-b.col2
END
) AS final_sum,
a.formula as 'col',
a.related_prod as 'prod',
a.Product_id as 'cons',
b.company_id,
b.month_id,
b.year_id
FROM table_B b
JOIN table_A a on a.related_prod=b.Product_id
GROUP BY a.formula, a.related_prod, a.Product_id, b.company_id, b.month_id, b.year_id
It may possible to build a Stored routine that fetches the string col2-col1 and inserts it (using CONCAT) into a string, then PREPAREs and EXECUTEs the SQL string.
That is, dynamically build the SQL, perhaps like in #slaakso's Answer.
It would be messy.
I have needed something like this; I chose to do eval() in PHP, which was the client language. I use it for evaluating VARIABLES and GLOBAL STATUS. Example: Table_open_cache_misses / Uptime gives the "misses per second", which, if high, indicates the need for increasing the setting table_open_cache.

Summarise multiple columns at once in MySQL

I have some data (~70,000 rows) that is in a similar format to the below.
+-----------+-----+-----+----+-----------+
| ID | A | B | C | Whatever |
+-----------+-----+-----+----+-----------+
| 1banana | 42 | 0 | 2 | Um |
| fhqwhgads | 514 | 6 | 9 | Nevermind |
| 2banana | 69 | 42 | 0 | NULL |
| pears | 18 | 96 | 2 | 8.8 |
| zubat2 | 96 | 2 | 14 | "NULL" |
+-----------+-----+-----+----+-----------+
I want to make an output table that counts how many times each number occurs in any of the three columns, such as:
+--------+---------+---------+---------+-----+
| Number | A count | B count | C count | sum |
+--------+---------+---------+---------+-----+
| 0 | 0 | 1 | 1 | 2 |
| 2 | 0 | 1 | 2 | 3 |
| 6 | 0 | 1 | 0 | 1 |
| 9 | 0 | 0 | 1 | 1 |
| 14 | 0 | 0 | 1 | 1 |
| 18 | 1 | 0 | 0 | 1 |
| 42 | 1 | 1 | 0 | 2 |
| 69 | 1 | 0 | 0 | 1 |
| 96 | 1 | 1 | 0 | 2 |
| 514 | 1 | 0 | 0 | 1 |
+--------+---------+---------+---------+-----+
(In my real-world use, there would be at least 10 times as many rows in the input table than in the query result)
Whether or not the query returns a row of zeros for numbers that aren't anywhere in those 3 columns isn't that important, as is a lack of a distinct sum column (though my preferences are that it does have the sum column and numbers not in any column are excluded).
Currently, I am using the following query to get ungrouped data:
SELECT * #Number, COUNT(DISTINCT A), COUNT(DISTINCT B), COUNT(DISTINCT C)
FROM
( # Generate a list of numbers to try
SELECT #ROW := #ROW + 1 AS `Number`
FROM DataTable t
join (SELECT #ROW := -9) t2
LIMIT 777 # None of the numbers I am interested in should be greater than this
) AS NumberList
INNER JOIN DataTable ON
Number = A
OR Number = B
OR Number = C
#WHERE <filters on DataTable columns to speed things up>
#WHERE NUMBER = 10 # speed things up
#GROUP BY Number
The above query with the commented-out parts of the code left as they are returns a table similar to the data table, but sorted by which number of the entry it matches. I would like to group together all rows starting with the same Number, and have the values in the "data" columns of the query result be the count of how many times the Number occured in the corresponding column of DataTable.
When I uncomment the grouping statements (and delete the * from the SELECT statement), I can get the count of how many rows each Number appeared in (useful for the sum column of the desired output). However, it does not give me the actual totals of how many times the Number matched each data column: I just get three copies of the number of rows where Number was found. How do I get the groupings to be by each actual column instead of the total number of matching rows?
Additionally, you may have noticed that I have some lines with comments regarding speeding things up. This query is slow, so I added a couple filters so testing it runs faster. I would very much like some way to make it run fast so that sending the results of the query from the complete set to a new table is not the only reasonable way to re-use this data, since I would like to have the ability to play around with the filters on DataTable for non-performance reasons. Is there a better way to structure the overall query so that it runs faster?
I think you want to unpivot using union all and then an aggregation:
select number, sum(a) as a, sum(b) as b, sum(c) as c, count(*) as `sum`
from ((select a as number, 1 as a, 0 as b, 0 as c from t
) union all
(select b, 0 as a, 1 as b, 0 as c from t
) union all
(select c, 0 as a, 0 as b, 1 as c from t
)
) abc
group by number
order by number;

How to join tables with SQL query and take number of tied columns?

I'm having BookTable in database (with foregin hey LibID):
| BookID | BookName | BookPrice | LibID |
-------------------------------------------
| 1 | Book_1 | 200 | 1 |
| 2 | Book_2 | 100 | 1 |
| 3 | Book_3 | 300 | 2 |
| 4 | Book_4 | 150 | 4 |
and also LibraryTable:
| LibID | LibName | LibLocation |
-----------------------------------
| 1 | Lib_1 | Loc_1 |
| 2 | Lib_2 | Loc_2 |
| 3 | Lib_3 | Loc_3 |
| 4 | Lib_4 | Loc_4 |
I need to write SQL query that will return be the info about the library and number of books for that library:
| LibID | LibName | NumberOfBooks|
------------------------------------
| 1 | Lib_1 | 2 |
| 2 | Lib_2 | 1 |
| 3 | Lib_3 | 0 |
| 4 | Lib_4 | 1 |
It should be one SQL query, probably with nested queries or joins.. Not sure how the query should look like:
SELECT L.LibID AS LibID, L.LibName AS LibName, COUNT(B) AS NumberOfBooks
FROM LibraryTable L, BookTable B
WHERE L.LibID = B.LibID
Will that work?
No, this query will not work. COUNT aggregates data, so you must explicitely tell the DBMS for which group of data you want the count. In your case this is the library (you want one result record per library).
COUNT's parameter is a column, not a table, so change this to * (i.e. count records) or a certain column (e.g. LibID).
The join syntax you are using is valid, but deprecated. Use explicit joins instead. In your case an outer join would even show libraries that have no books at all, if such is possible.
select l.libid, l.libname, count(b.libid) as numberofbooks
from librarytable l
left outer join booktable b on b.libid = l.libid
group by l.libid;
You could also do all this without a join at all and get the book count in a subquery instead. Then you wouldn't have to aggregate. That's way simpler and more readable in my opinion.
select
l.libid,
l.libname,
(select count(*) booktable b where b.libid = l.libid) as numberofbooks
from librarytable l;
SELECT lt.LibID AS LibID, lt.LibName AS LibName, count(*) AS NumberOfBooks
FROM BookTable AS bt
LEFT JOIN LibraryTable AS lt ON bt.LibID = lt.LibID
GROUP BY bt.LibID

select lower and upper from lower only table

How can I transform a normalized table with family, name and 'lowerbound' number to a result set with family, name, lower and upper bound, where upper bound is defined as min(lowerbound of family) > current lowerbound and if no number like this exists, use a provided number
for example, if this is the schema and data:
create table records(
family varchar(10),
name varchar(10),
lowbound int(4)
);
insert into records
values
('letters', 'a',1),('letters', 'b',3),('letters', 'c',3),('letters', 'd',3),
('letters', 'e',7),('letters', 'f',7),('numbers', '12',1), ('numbers', '15',1), ('numbers', '18',4);
and the provided number is 9, then the result set should be:
| FAMILY | NAME | LOWER | UPPER |
|---------|------|-------|-------|
| letters | a | 1 | 3 |
| letters | b | 3 | 7 |
| letters | c | 3 | 7 |
| letters | d | 3 | 7 |
| letters | e | 7 | 9 |
| letters | f | 7 | 9 |
| numbers | 12 | 1 | 4 |
| numbers | 15 | 1 | 4 |
| numbers | 18 | 4 | 9 |
Try this out:
SELECT r1.family, r1.name, r1.lowbound lower, coalesce(min(r2.lowbound), 9) upper
FROM records r1
LEFT JOIN records r2 ON r1.family = r2.family AND r1.lowbound < r2.lowbound
GROUP BY r1.family, r1.name, r1.lowbound
Fiddle here
I think the easiest way to express this is with a correlated subquery in the select clause:
select r.*,
coalesce((select r2.lowbound
from records r2
where r2.family = r.family and
r2.lowbound > r.lowbound
order by r2.lowbound
limit 1
), 9) as highbound
from records r;
The coalesce() handles the case where there is no value. In that case, your substitution value of 9 is used.
Here is the SQL Fiddle.