I have a table of items. One of the fields is a category (represented by an enum). Some categories have zero items.
So I did this:
select category, count(*) as total from items group by category;
+------------+-------+
| category | total |
+------------+-------+
| one | 6675 |
+------------+-------+
I want to generate a table like this (where two is the other possible enum value):
+------------+-------+
| category | total |
+------------+-------+
| one | 6675 |
+------------+-------+
| two | 0 |
+------------+-------+
How do I do this with an mysql SQL query?
Enum datatype is generally preferred for those cases where possible options (values) are not too many (prefer <= 10), and you are not going to add new options in future (atleast not very frequently). So, a good use-case for Enum is gender: (m, f, n). In your case, it would be generally better to have a Master table of all possible Categories, instead of using Enum for them. Then it is easier to do a LEFT JOIN from the Master table.
However, as asked by you:
A solution uses the enum type to generate the table, and includes 0
entries
Works for all MySQL/MariaDB versions:
We will need to get the list of all possible Enum values from INFORMATION_SCHEMA.COLUMNS:
SELECT
SUBSTRING(COLUMN_TYPE, 6, CHAR_LENGTH(COLUMN_TYPE) - 6) AS enum_values
FROM
information_schema.COLUMNS
WHERE
TABLE_NAME = 'items' -- your table name
AND
COLUMN_NAME = 'category' -- name of the column
AND
TABLE_SCHEMA = 'your_db' -- name of the database (schema)
But then, this query will give you all the enum values in comma-separated string, like below:
'one','two','three','four'
Now, we will need to convert this string into multiple rows. To achieve that, we can use a Sequence (Number series) table. You can define a permanent table in your database storing integers ranging from 1 to 100 (you may find this table helpful in many other cases as well) (OR, another approach is to use a Derived Table - check this to get an idea: https://stackoverflow.com/a/58052199/2469308).
CREATE TABLE seq (n tinyint(3) UNSIGNED NOT NULL, PRIMARY KEY(n));
INSERT INTO seq (n) VALUES (1), (2), ...... , (99), (100);
Now, we will do a JOIN between "enum values string" and seq table, based on the position of comma, to extract enum values into different rows. Note that instead of just using , (comma) to extract enum values, we would use ',' (to avoid cases when there might be a comma inside the value string). String operations utilizing Substring_Index(), Trim(), Char_Length() etc functions can be used to extract enum values. You can check this answer to get a general idea about this technique:
Schema (View on DB Fiddle)
CREATE TABLE items
(id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
category ENUM('one','two','three','four'),
item_id INT UNSIGNED) ENGINE=InnoDB;
INSERT INTO items (category, item_id)
VALUES ('one', 1),
('two', 2),
('one', 2),
('one', 3);
CREATE TABLE seq (n tinyint(3) UNSIGNED NOT NULL,
PRIMARY KEY(n));
INSERT INTO seq (n) VALUES (1),(2),(3),(4),(5);
Query #1
SELECT Trim(BOTH '\'' FROM Substring_index(Substring_index(e.enum_values,
'\',\'',
seq.n),
'\',\'', -1)) AS cat
FROM (SELECT Substring(column_type, 6, Char_length(column_type) - 6) AS
enum_values
FROM information_schema.columns
WHERE table_name = 'items'
AND column_name = 'category'
AND table_schema = 'test') AS e
JOIN seq
ON ( Char_length(e.enum_values) - Char_length(REPLACE(e.enum_values,
'\',\'',
''))
) / 3 >= seq.n - 1
| cat |
| ----- |
| one |
| two |
| three |
| four |
Now, the hard part is done. All we need to do is do a LEFT JOIN from this subquery (having all category enum values) to your items table, to get Count per category.
The final query follows (View on DB Fiddle):
SELECT all_cat.cat AS category,
Count(i.item_id) AS total
FROM (SELECT Trim(BOTH '\'' FROM Substring_index(
Substring_index(e.enum_values,
'\',\'',
seq.n),
'\',\'', -1)) AS cat
FROM (SELECT Substring(column_type, 6, Char_length(column_type) - 6)
AS
enum_values
FROM information_schema.columns
WHERE table_name = 'items'
AND column_name = 'category'
AND table_schema = 'test') AS e
JOIN seq
ON ( Char_length(e.enum_values) - Char_length(
REPLACE(e.enum_values,
'\',\'',
''))
) / 3 >= seq.n - 1) AS all_cat
LEFT JOIN items AS i
ON i.category = all_cat.cat
GROUP BY all_cat.cat
ORDER BY total DESC;
Result
| category | total |
| -------- | ----- |
| one | 3 |
| two | 1 |
| three | 0 |
| four | 0 |
Here is some fun with MySQL 8.0 and JSON_TABLE():
select c.category, count(i.category) as total
from information_schema.COLUMNS s
join json_table(
replace(replace(replace(trim('enum' from s.COLUMN_TYPE),'(','['),')',']'),'''','"'),
'$[*]' columns (category varchar(50) path '$')
) c
left join items i on i.category = c.category
where s.TABLE_SCHEMA = 'test' -- replace with your db/schema name
and s.TABLE_NAME = 'items'
and s.COLUMN_NAME = 'category'
group by c.category
It converts the ENUM type definition from information_schema to a JSON array, which is then converted by JSON_TABLE() to a table, which you then can use for a LEFT JOIN.
See demo on db-fiddle
Note: The categories should not contain any characters from ()[]'".
But seriously – Just create the categories table. There are more reasons to do that. For example you might want to render a drop-down menu with all possible categories. That would be simple with
select category from categories
I would say that it's basically bad practice to encode your enumerations into the script. Therefore, create a table with the enumerations present (and their relative keys), then it's a simple case of grouping a left joined query...
SELECT
cat.enum_name,
COUNT(data.id) AS total
FROM
category_table cat
LEFT JOIN
data_table data
ON cat.cate_id = data.cat_id
GROUP BY
cat.enum_name
using in-select subquery
select cat.categoryname
(
select count(*) -- count total
from items as i
where i.category = cat.category -- connect
) as totalcount
from cat
order by cat.categoryname
You can make a fictive dataset of the different categories and do a left join with your original table as shown below.
SELECT A.category, count(*) total FROM
(SELECT 'one' as Category
UNION ALL
SELECT 'two' as Category) A
LEFT JOIN items B
ON A.Category=B.Category
GROUP BY B.Category;
If you would prefer to get a list of all the category dynamically, then save them in another table (say All_category_table) then do a join as shown below:
SELECT A.category, count(*) total FROM
(SELECT Category FROM All_category_table) A
LEFT JOIN items B
ON A.Category=B.Category
GROUP BY B.Category;
This answer is applicable for when you do not have another table holding the possible category values.
Let's say you have a table called real_table with a not null & value constrained column category. In this column you know you can theoretically encounter 5 different values: 'CATEGORY_0', 'CATEGORY_1', 'CATEGORY_2', 'CATEGORY_3', 'CATEGORY_4':
CREATE TABLE real_table
(
id VARCHAR(255) NOT NULL
PRIMARY KEY,
category VARCHAR(255) NOT NULL
CONSTRAINT category_in CHECK (
category in ('CATEGORY_0',
'CATEGORY_1',
'CATEGORY_2',
'CATEGORY_3',
'CATEGORY_4')
)
);
But your actual data set in the table does not include any row with value 'CATEGORY_0'. So when you run a query such as:
SELECT real_table.category AS category, COUNT(*) AS cnt
FROM real_table
GROUP BY real_table.category;
you will see, that you get result like this:
category
cnt
CATEGORY_1
150
CATEGORY_2
20
CATEGORY_3
12
CATEGORY_4
1
Hmm, the 'CATEGORY_0' is omitted. Not good.
Since your categories are not backed by another table, then you must create an artificial dataset of the possible categories that looks as below:
SELECT 'CATEGORY_0' AS category_entry
UNION ALL
SELECT 'CATEGORY_1' AS category_entry
UNION ALL
SELECT 'CATEGORY_2' AS category_entry
UNION ALL
SELECT 'CATEGORY_3' AS category_entry
UNION ALL
SELECT 'CATEGORY_4' AS category_entry;
You can use this in your original query as a table to do a right join on:
SELECT all_categories.category_entry AS category,
COUNT(real_table.id) AS cnt -- important to count some non-null value, such as PK of the real_table
FROM real_table
RIGHT JOIN
(SELECT 'CATEGORY_0' AS category_entry -- not present in any row in table 'all_categories'
UNION ALL
SELECT 'CATEGORY_1' AS category_entry
UNION ALL
SELECT 'CATEGORY_2' AS category_entry
UNION ALL
SELECT 'CATEGORY_3' AS category_entry
UNION ALL
SELECT 'CATEGORY_4' AS category_entry) all_categories
ON real_table.category = all_categories.category_entry
GROUP BY all_categories.category_entry;
Now when you run the query, you should get the desired output:
category
cnt
CATEGORY_0
0
CATEGORY_1
150
CATEGORY_2
20
CATEGORY_3
12
CATEGORY_4
1
The 'CATEGORY_0' is now included with zero cnt. Nice.
Now let's say that the category column is not not null constrained and can also possibly include some other unexpected category values (e.g. 'CATEGORY_66'):
CREATE TABLE real_table
(
id VARCHAR(255) NOT NULL
PRIMARY KEY,
category VARCHAR(255) -- nullable and no constraint for valid values
);
We would like to include these null and unexpected category counts in the result set as well.
Then we must prepare the artificial dataset of the possible categories differently:
SELECT DISTINCT all_categories.category_entry
FROM (SELECT 'CATEGORY_0' AS category_entry -- not present in any row in table 'all_categories'
UNION ALL
SELECT 'CATEGORY_1' AS category_entry
UNION ALL
SELECT 'CATEGORY_2' AS category_entry
UNION ALL
SELECT 'CATEGORY_3' AS category_entry
UNION ALL
SELECT 'CATEGORY_4' AS category_entry
UNION ALL
SELECT DISTINCT category
FROM real_table AS category_entry) all_categories;
and use it as before:
SELECT distinct_categories.category_entry AS category,
COUNT(real_table.id) AS cnt -- important to count some non-null value, such as PK of the real_table
FROM real_table
RIGHT JOIN
(SELECT DISTINCT all_categories.category_entry
FROM (SELECT 'CATEGORY_0' AS category_entry -- not present in any row in table 'all_categories'
UNION ALL
SELECT 'CATEGORY_1' AS category_entry
UNION ALL
SELECT 'CATEGORY_2' AS category_entry
UNION ALL
SELECT 'CATEGORY_3' AS category_entry
UNION ALL
SELECT 'CATEGORY_4' AS category_entry
UNION ALL
SELECT DISTINCT category
FROM real_table AS category_entry) all_categories) distinct_categories
ON real_table.category = distinct_categories.category_entry
GROUP BY distinct_categories.category_entry;
Now when you run the query, the output should also include counts for additional categories and null categories
category
cnt
CATEGORY_0
0
CATEGORY_1
150
CATEGORY_2
20
CATEGORY_3
12
CATEGORY_4
1
CATEGORY_66
13
10
Both unexpected 'CATEGORY_66' (with 13 entries) as well as null category (with 10 entries) are now included in the result set
I cannot vouch for the performance of the provided queries - somebody more experienced might weigh in on that?
Related
I have two mysql tables with part numbers and qty's. I want to sum each tables qty sum(qty) ... group by partNumber Then join the two tables on the part number.
Sometimes table A will have part numbers that table b does not and vice versa. Below is an image of what I am expecting.
I've tried something like this, but this returns a row for each table and I want it to return 1 combined row
SELECT *, null as macroQty, sum(qty) as cardinalQty
FROM parts.cardinal where fileinfoid IN
(select cardinalFiles from parts.reports where fileinfoid = 418)
GROUP BY partNumber UNION ALL
SELECT *, sum(qty) as macroQty, null as cardinalQty
FROM parts.macro where fileinfoid IN
(select macroFiles from parts.reports where fileinfoid = 418 )
GROUP BY partNumber
I also tried wrapping it in an outer select and grouping by the part number from the outer select like this, but this results in the second inner select being null always
SELECT * FROM (
SELECT *, null as macroQty, sum(qty) as cardinalQty
FROM parts.cardinal where fileinfoid IN
(select cardinalFiles from parts.reports where fileinfoid = 418)
GROUP BY partNumber UNION ALL
SELECT *, sum(qty) as macroQty, null as cardinalQty
FROM parts.macro where fileinfoid IN
(select macroFiles from parts.reports where fileinfoid = 418 )
GROUP BY partNumber
) combined GROUP BY combined.partNumber
One approach would be to identify unique part numbers across the 2 tables (using a UNION with it's applied distinct) and then use correlated sub queries to get the sums. For example
drop table if exists a,b;
create table a(id int,val int);
create table b(id int,val int);
insert into a values(1,10),(1,10),(3,10),(4,10);
insert into b values (2,10),(4,10),(4,10);
select (select sum(a.val) from a where a.id = s.id) aval,
(select sum(b.val) from b where b.id = s.id) bval,
s.id partno
from
(
select id from a
union select id from b
) s
order by s.id;
+------+------+--------+
| aval | bval | partno |
+------+------+--------+
| 20 | NULL | 1 |
| NULL | 10 | 2 |
| 10 | NULL | 3 |
| 10 | 20 | 4 |
+------+------+--------+
4 rows in set (0.00 sec)
I would phrase this as a join between two subqueries which each find the sum in their respective tables. However, since each table does not necessarily contain all part numbers, and in fact there may be part numbers unique to each table, we will have to use a full outer join approach.
SELECT
t1.partNumber,
t1.cardinalQty,
COALECSE(t2.macroQty, 0) AS macroQty
FROM
(
SELECT partNumber, SUM(qty) AS cardinalQty
FROM cardinal
GROUP BY partNumber
) t1
LEFT JOIN
(
SELECT partNumber, SUM(qty) AS macroQty
FROM macro
GROUP BY partNumber
) t2
ON t1.partNumber = t2.partNumber
UNION ALL
SELECT
t2.partNumber,
0 AS cardinalQty,
t2.macroQty
FROM
(
SELECT partNumber, SUM(qty) AS cardinalQty
FROM cardinal
GROUP BY partNumber
) t1
RIGHT JOIN
(
SELECT partNumber, SUM(qty) AS macroQty
FROM macro
GROUP BY partNumber
) t2
ON t1.partNumber = t2.partNumber
WHERE t1.partNumber IS NULL;
Keep in mind that under normal conditions, in a well designed database, you should rarely encounter a situation which requires using a full outer join. Actually, a full outer join screams out that there is a design problem. In this case, you don't have a single parts table containing all part numbers. That table should exist, so unless you enjoy big ugly queries, you should create a parts table where the partNumber is a primary key.
I use mysql db engine, I wonder is it possible that the data in the table one row transferred to another table, this table would consist of two columns, id and value
each of the transferred value would go into one row and row would look like ID, value, and for as long as it has a value that is transferred to new row maintains the id as long as it has a value that belonged to the id of a row from which it transferred
Initial table looks like
id |country_name |city_1 |city_2 |city_3 |city_4
------------------------------------------------------------------------
1 |Some_country |some_city1 |some_city2 |some_city3 |some_city4
Wanted table looks like
id | city_name
1 | some_city1
1 | some_city2
1 | some_city3
1 | some_city4
Use this for one particular ID
select id, city_name from(
select id, city_1 as city_name from yourTable
union all
select id, city_2 from yourTable
union all
select id, city_3 from yourTable
union all
select id, city_4 from yourTable
) as t where id= yourID
http://sqlfiddle.com/#!9/7ee1f/1
Use this for whole table
select id, city_name from(
select id, city_1 as city_name from yourTable
union all
select id, city_2 from yourTable
union all
select id, city_3 from yourTable
union all
select id, city_4 from yourTable
) as t
order by id
What you are looking for is often referred to as vertical pivoting: you want to pivot something like an array of four city names - hard-wired into the table definition - into four vertical rows.
The solution is a cross join with a temporary table with as many consecutive integers, starting from 1, as you have columns to pivot, in conjunction with a CASE-WHEN expression that makes use of that series of integers.
See here:
WITH foo(id,country_name,city_1,city_2,city_3,city_4) AS (
SELECT 1,'Some_country','some_city1','some_city2','some_city3','some_city4'
)
, four_indexes(idx) AS (
SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
)
SELECT
id AS country_id
, idx AS city_id
, CASE idx
WHEN 1 THEN city_1
WHEN 2 THEN city_2
WHEN 3 THEN city_3
WHEN 4 THEN city_4
ELSE ''
END AS city_name
FROM foo CROSS JOIN four_indexes
;
country_id|city_id|city_name
1| 1|some_city1
1| 3|some_city3
1| 2|some_city2
1| 4|some_city4
Only the other day, I answered a question that was looking for reversing the operation we are performing here: horizontal pivoting.
See here if you're curious ...
How to go about a column with different values in a same row in sql?
Happy Playing -
Marco the Sane
I am trying to use the IN operator to get the count of certain fields in the table.
This is my query:
SELECT order_id, COUNT(*)
FROM remake_error_type
WHERE order_id IN (1, 2, 100)
GROUP BY order_id;
My current output:
| order_id | COUNT(*) |
+----------+----------+
| 1 | 8 |
| 2 | 8 |
My expected output:
| order_id | COUNT(*) |
+----------+----------+
| 1 | 8 |
| 2 | 8 |
| 100 | 0 |
You can write your query this way:
SELECT t.id, COUNT(remake_error_type.order_id)
FROM
(SELECT 1 AS id UNION ALL SELECT 2 UNION ALL SELECT 100) as t
LEFT JOIN remake_error_type
ON t.id = remake_error_type.order_id
GROUP BY
t.id
a LEFT JOIN will return all rows from the subquery on the left, and the COUNT(remake_error_type.order_id) will count all values where the join succeeds.
You can create a temporary table, insert as many order_ids as required, and perform the left join to remake_error_type. At a small number of orders the other answers are sufficient, but if you were doing this for a lot of orders, UNION ALL and sub-queries are inefficient, both to type it up and to execute on the server.
Additionally, this is a very dynamic approach, because you can control easily the values in your temp table by modifying the insert statement.
However, this will only work if the database user has sufficient privileges: at least select, create temporary and drop table.
DROP TABLE IF EXISTS myTempOrders;
CREATE TEMPORARY TABLE myTempOrders (order_id INTEGER, PRIMARY KEY(order_id));
INSERT INTO myTempOrders (order_id) VALUES (1), (2), (100);
SELECT temp.order_id, count(*)
FROM myTempOrders temp
LEFT JOIN remake_error_type ON temp.order_id = remake_error_type.order_id
GROUP BY 1
If the order_id values exist in some table, then it is possible to extract the desired result without creating a temporary table and inserting values into it.
To qualify, the table must
have an auto increment primary key with # rows greater than the maximum sought order_id value
have a starting increment value less than the minimum sought order_id value
have no missing values in the primary key (i.e. no records have been deleted)
if a qualified table exists, then you can run the following query, where you have to replace surrogate with the qualified table name and surrogate_id with the auto-incrementing primary key of the qualified table name
SELECT surrogate.surrogate_id, count(*)
FROM my_qualified_table surrogate
LEFT JOIN remake_error_type ON surrogate.surrogate_id = remake_error_type.order_id
WHERE surrogate.surrogate_id IN (1, 2, 100)
GROUP BY 1
You could use a union for this. No, this does not use the IN operator, but it is an alternative that will give you your expected results. One option is to hardcode the order_id and use conditional aggregation to get the SUM() of rows with that id:
SELECT 1 AS order_id, SUM(order_id = 1) AS numOrders FROM myTable
UNION ALL
SELECT 2 AS order_id, SUM(order_id = 2) AS numOrders FROM myTable
UNION ALL
SELECT 100 AS order_id, SUM(order_id = 100) AS numOrders FROM myTable;
Here is an SQL Fiddle example.
Edit 1 the code is just an example, I do not have suburb data, my real data is inherited and messy and could be fixed by creating code out of the database or creating reference data. The question should have been something like does anyone have a good cell sort function or other solution that can be reused?
Table
CREATE TABLE postcode (
`id` int NOT NULL AUTO_INCREMENT,
`suburbs` varchar(2000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_unicode_ci;
Insert
INSERT INTO postcode (`suburbs`)
VALUES ( 'Zackville;Astor;Mary-town;Jackson' );
Want field to be sorted an result to be
Astor;Jackson;Mary-town;Zackville
All comments have very valid points and you should try to avoid working with delimited values in RDBMS.
That being said, if you're stuck with the existing database and you for some reason want to do it on the database level rather than with client code you can leverage tally(number) table and SUBSTRING_INDEX() to split delimited values into rows, then GROUP_CONCAT() to pivot data back in the ordered manner.
One of the several ways to create a tally table:
CREATE TABLE tally (n int not null primary key);
INSERT INTO tally (n)
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
Number of rows in the tally table should be greater or equal to the maximum possible number of delimited values.
Now to resort delimited values:
UPDATE postcode p JOIN
(
SELECT id, GROUP_CONCAT(suburbs ORDER BY suburbs SEPARATOR ';') suburbs
FROM
(
SELECT t.id, SUBSTRING_INDEX(SUBSTRING_INDEX(t.suburbs, ';', n.n), ';', -1) suburbs
FROM postcode t CROSS JOIN tally n
WHERE n.n <= 1 + (LENGTH(t.suburbs) - LENGTH(REPLACE(t.suburbs, ';', '')))
) q
GROUP BY id
) s
ON p.id = s.id
SET p.suburbs = s.suburbs;
Output:
| ID | SUBURBS |
|----|-----------------------------------|
| 1 | Astor;Jackson;Mary-town;Zackville |
Here is a SQLFiddle demo
I have to combine these two mySQL queries into one. I duplicated a solution and used it on a join table. I am querying a join table that has two columns (labeled "to_" and "from_"). Both 'to_' and 'from_' hold an id number for the same table. I need to combine these queries in such a way that I get results based on: [('from_' + 'to_') > 3], where 'from_' and 'to_' have the same value (i.e., they refer to the same id).
$query = "select * from nodes where nodeID in (
select to_ from joinTable group by to_ having count(*) > 3
)";
...
$query = "select * from nodes where nodeID in (
select from_ from joinTable group by from_ having count(*) > 3
)";
Acknowledgement: I'm using a query based very closely on a solution 'Mr E' helped me with earlier.
You can try (see important notice at the last paragraph regarding to_ and from_ matching requirements):
SELECT X.to_, COUNT(*)
FROM joinTable X, joinTable Y
WHERE
X.to_ = Y.from_
GROUP BY X.to_
HAVING COUNT(*) > 2
Or
SELECT X.to_, COUNT(*)
FROM joinTable X LEFT JOIN joinTable Y ON X.to_ = Y.from_
GROUP BY X.to_
HAVING COUNT(*) > 2
Using Mr E's test data:
CREATE TABLE `foo` (
`id` int(1) DEFAULT NULL,
`to_` varchar(5) DEFAULT NULL,
`from_` varchar(5) DEFAULT NULL
);
INSERT INTO `foo` VALUES (1,'A','B'),(2,'B','A'),(3,'B','C'),(4,'X','C'),(4,'X','B');
It will work, half-way, by issuing:
SELECT X.to_, Y.from_
FROM foo X LEFT JOIN foo Y ON X.to_ = Y.from_
which will then yield:
mysql> SELECT X.to_, Y.from_ /*--, COUNT(*) */
-> FROM foo X LEFT JOIN foo Y ON X.to_ = Y.from_;
+------+-------+
| to_ | from_ |
+------+-------+
| A | A |
| B | B |
| B | B |
| B | B |
| B | B |
| X | NULL |
| X | NULL |
+------+-------+
7 rows in set (0.00 sec)
and by running in full:
mysql> SELECT X.to_, COUNT(*)
-> FROM foo X LEFT JOIN foo Y ON X.to_ = Y.from_
-> GROUP BY X.to_
-> HAVING COUNT(*) > 2;
+------+----------+
| to_ | COUNT(*) |
+------+----------+
| B | 4 |
+------+----------+
Basically, join the table with itself and then generate an N:N list of matching records from both tables where to_ and from_ match (whether or not on the same row), then work with a single column and aggregate its values for the final COUNT(*).
And, most importantly, why have I lowered the number on the HAVING COUNT(*) from 3 to 2? The N:N relationship will issue N1 * N2 records (where N1 is the count of matching records on the first table and N2 on the second). So if the lower bound is three, we can only have over 3 records on these two tables by having one record in one of them and then 3 on the other (in whatever order) or 2 in one and 2 on the other (and then up from there) - otherwise there will be no matches on the to_ and from_ fields and this is something I am not sure about - whether the OP wants only records whose values appear on both fields or if having a COUNT(*) from a single side would suffice. If the latter is the case, however, I don't see any other option apart from separating the queries to deal with each column individually, as some people already have posted since that's an isolated sum we're dealing with. This will be slow if running against large tables.
SELECT * FROM(
SELECT nodeID, to_, count(*) cto_ FROM joinTable jta GROUP BY to_
OUTER JOIN
SELECT from_, count(*) cfrom_ FROM joinTable jtb GROUP BY from_
ON jta.nodeID = jtb.nodeID
) WHERE ((cto_ + cfrom) > 3) as tableA
INNER JOIN
node
ON node.nodeID = tableA.nodeID
I haven't tested to make sure this code compiles and runs but I think that's generally the right direction for the answer to what you want--
first get the count of to's from the to table
then get the count of from's from the from table
finally put the addition in the criteria for the two tables.
As long as the outer join is on same nodeID's it should only have one entry per nodeID, if I understand my code right.
OK, I ran it through a database I had handy, here's actual code that works on my database (change the names for yours of course)
SELECT * FROM (
SELECT * FROM
(
SELECT ticket_id, author_uid, count(*) cto_
FROM strato_ticket GROUP BY author_uid
) as jta
LEFT JOIN
(
SELECT ticket_id as tid, uid, count(*) cfrom_
FROM strato_ticket GROUP BY uid
) as jtb
ON jta.ticket_id = jtb.tid
WHERE ((cto_ + cfrom_) > 3)
) as jt
INNER JOIN strato_invite
ON strato_invite.ticket_id = jt.tid
It's not pretty, but:
select * from nodes where nodeID in (
select x from (
select to_ as x, count(*) as num
from joinTable group by to_
union all
select from_ as x, count(*) as num
from joinTable group by from_
) as temp_table
group by x having sum(num) > 3;
)
Doesn't seem to work for the OP. All I can say is "works on my machine" - here's the data and exact query I used:
CREATE TABLE `foo` (
`id` int(1) DEFAULT NULL,
`to_` varchar(5) DEFAULT NULL,
`from_` varchar(5) DEFAULT NULL
);
INSERT INTO `foo` VALUES (1,'A','B'),(2,'B','A'),(3,'B','C'),(4,'X','C'),(4,'X','B');
The query:
select x from (
select to_ as x, count(*) as num
from foo group by to_
union all
select from_ as x, count(*) as num
from foo group by from_
) as temp_table
group by x having sum(num) > 3;