Can you include totals and detail rows in a single mySQL query? - mysql

None of the questions on file seems to be about my precise problem.
Is it possible to sequence detail lines based on the number of detail lines for that order you have in a single query?
Consider the following simplified table:
Order number Article number
------------ --------------
123 1
123 2
123 3
234 1
234 2
345 1
456 1
456 2
456 3
456 4
456 5
The number of detail lines for each order would be
Order number Number of lines
------------ ---------------
123 3
234 2
345 1
456 5
Is it possible to select the order number, article number in descending order by total number of detail lines for each detail line? In other words the desired results are
Order number Article number
------------ --------------
456 1
456 2
456 3
456 4
456 5
123 1
123 2
123 3
234 1
234 2
345 1
I can do it with multiple queries and temporary tables or extra columns. Neither simple SELECTS, SELF JOINs nor UNIONs appear to give me the results I want. Is it possible to do with a single query?

This query should work as it's getting the count from the join and then ordering it by the count.
select t1.orderNumber, t1.articleNumber from myTable t1
inner join
(
select orderNumber, count(articleNumber) as count from myTable
group by orderNumber
) t2
on t1.orderNumber = t2.orderNumber
order by t2.count desc, t1.orderNumber, t1.articleNumber
To better expain it:
We are first selecting all the data, then we are inner joining a table that has the count for each order number, once we have this we can then order it by count DESC so we get the order number with the highest count on top and we can then add additional sorting in the Order By

In the interests of future readers (when MySQL 8.x is production ready and it supports window functions) you can avoid the intermediate subquery and join by using COUNT() OVER(partition by ...) like this:
select t1.orderNumber, t1.articleNumber f
from myTable t1
order by
count(*) over(partition by t1.orderNumber) DESC
, t1.orderNumber
, t1.articleNumber
;
orderNumber | f
----------: | -:
456 | 1
456 | 2
456 | 3
456 | 4
456 | 5
123 | 1
123 | 2
123 | 3
234 | 1
234 | 2
345 | 1
ps: The example above done in MariaDB 10.2 to show MySQL(ish) syntax at work (& it's standards complaint SQL anyway).
dbfiddle here

If I understand the problem correctly, following query should work:
select t1.order_number, t1.Article_number
from t1
inner join (select order_number,count(*) as cnt from t1 group by order_number) as t2
on t1.order_number = t2.order_number
order by t2.cnt desc,t1.order_number,t1.Article_number;
Hope it helps!

Related

MySQL - select distinct value from two column

I have a table with the following structure:
IdM|IdS
-------
1 | 2
1 | 3
1 | 4
2 | 1
2 | 3
2 | 4
3 | 1
3 | 2
3 | 3
3 | 4
How could I make a select statement on this table, which will return some rows of this table, where in each row, a specific id appears only one, indifferent on which column it is specified?
For the above result set, I would like a query that would return:
-------
1 | 2
3 | 4
-------
To give another example, if you would omit the first row in the original dataset:
IdM|IdS
-------
1 | 3
1 | 4
2 | 1
2 | 3
2 | 4
3 | 1
3 | 2
3 | 3
3 | 4
the result set should be:
-------
1 | 3
2 | 4
-------
That's actually an interesting problem. If I follow you correctly, you want to iterate through the dataset and only retain rows where both values were never seen before. You could use a recursive query:
with recursive
data as (
select idm, ids, row_number() over(order by idm, ids) rn
from mytable
where idm <> ids
),
cte as (
select idm, ids, rn, 1 as to_keep , concat(idm, ',', ids) visited from data where rn = 1
union all
select d.idm, d.ids, d.rn,
(not find_in_set(d.idm, c.visited) and not find_in_set(d.ids, c.visited)),
case when (not find_in_set(d.idm, c.visited) and not find_in_set(d.ids, c.visited))
then concat_ws(',', c.visited, d.idm, d.ids)
else c.visited
end
from cte c
inner join data d on d.rn = c.rn + 1
)
select idm, ids from cte where to_keep
The first CTE enumerates the rows ordered by both columns. Then the recursive query walks the resultset, checks if both values are new, and sets a flag accordingly of the columns. Flagged numbers are retained to be used for filtering in the following iteration.
Demo on DB Fiddle
Note that, given your requirement, not all values may appear in the resultset. Consider the following dataset:
idm ids
+-----+---
1 2
1 3
1 4
Your logic will only return the first row.

Can I count multiple columns with Group By?

I have a table with these columns:
s, s2, s3
1, 2, 3
4
1, 3
4, 2,
2, 1
3, 4
4
I want to know how many times the unique values in column s appears in the columns s, s2 and s3.
So far I have:
$query = "SELECT s, COUNT(*) as count FROM table GROUP BY s";
This will give me:
1 - count 2
2 - count 1
3 - count 1
4 - count 3
But I want to count the column s2 and s3 also so the outcome will be:
1 - count 3
2 - count 3
3 - count 3
4 - count 4
Any idea how I must edit the query so I can count the columns s, s2 and s3 group by the values of column s?
Kind regards,
Arie
You need a UNION ALL for all the columns and then count them:
select
t.s, count(*) counter
from (
select s from tablename union all
select s2 from tablename union all
select s3 from tablename
) t
where t.s is not null
group by t.s
See the demo.
Results:
| s | counter |
| --- | ------- |
| 1 | 3 |
| 2 | 3 |
| 3 | 3 |
| 4 | 4 |
If in the columns s2 and s3 there are values that do not exist in the column s and you want them excluded, then instead of:
where t.s is not null
use
where t.s in (select s from tablename)
#forpas answer is a good one. However, two things you should consider.
Due to the use of union the query will become slower as the data size increases.
If the input is as following:
s, s2, s3
1, 2, 3
4
1, 3
4, 2,
2, 1
3, 4
4 5
The result of the provided query will be:
| s | counter |
| --- | ------- |
| 1 | 3 |
| 2 | 3 |
| 3 | 3 |
| 4 | 4 |
| 5 | 1 |
whereas it should remain the same as 5 is not present into the s column.
In order to resolve both of the above issues, I propose the approach to use JOIN instead of UNION:
SELECT t3.s, IF(t3.s = t4.s3, cnt1 + 2, cnt1 + 1) as counter FROM
(SELECT *, count(*) AS cnt1 FROM
(SELECT s from table) AS t1
LEFT JOIN
(SELECT s2 FROM table) AS t2
ON t1.s = t2.s2 GROUP BY t1.s
) AS t3
LEFT JOIN
(SELECT s3 FROM table) AS t4
ON t3.s = t4.s3
ORDER BY t3.s
The query might look a bit lengthy and complicated but it is really simple when you look into the logic.
Step 1
What I have done here is to make a left join from s column to s2 and counted results for that so it will give you 1 lesser number than how many numbers are present in total as it will make relation left to right.
Step 2
Then I have made a left join from s to s3, and only increase the count of step 1 by 1 if the relation is found.
Step 3
Ultimately I have increased the count by 1 so that we can convert the number of relations to the number of the enities.
I hope it makes sense

Query to find the duplicates between the name and number in table

SELECT count(*), lower(name), number
FROM tbl
GROUP BY lower(name), number
HAVING count(*) > 1;
input tb1
slno name number
1 aaa 111
2 Aaa 111
3 abb 221
4 Abb 121
5 cca 131
6 cca 141
7 abc 222
8 cse 222
This query can just find the duplicates in the number and names which are same but it wont be able find the duplicates in the 3 and 4th row!!!
SELECT count(*), lower(name)
FROM tbl
GROUP BY lower(name)
HAVING count(lower(name)) > 1
this query can find all the duplicates in name!!! it works perfectly
SELECT count(*), number
FROM tbl
GROUP BY number
HAVING count(number) > 1
this query can find all the duplicates in number!!! it works perfectly
I want a query which can find all the duplicates in both name and number whether the name consists of lower case and upper case
output
name number count
2 111 aaa
2 --- abb
2 --- cca
2 222 ---
Updated question
"Get duplicate on both number and name" ... "name and number as different column"
Rows can be counted twice here!
SELECT lower(name), NULL AS number, count(*) AS ct
FROM tbl
GROUP BY lower(name)
HAVING count(*) > 1
UNION ALL
SELECT NULL, number, count(*) AS ct
FROM tbl
GROUP BY number
HAVING count(*) > 1;
-> sqlfiddle
Original question
The problem is that the query groups by
GROUP BY lower(name), number
As row 3 and 4 have a different number, they are not the same for this query.
If you want to ignore different numbers for this query, try something like:
SELECT lower(name)
, count(*) AS ct
FROM tbl
GROUP BY lower(name)
HAVING count(*) > 1;
With a little work we can show counts for both name and number in one column:
select NameOrNumber, count(*) as Count
from (
select name as NameOrNumber from tb1
union all
select number from tb1
) a
group by NameOrNumber
having count(NameOrNumber) > 1
SQL Fiddle Example #1
Output #1:
| NAMEORNUMBER | COUNT |
------------------------
| 111 | 2 |
| aaa | 2 |
| abb | 2 |
| cca | 2 |
If you want the output in separate columns, you can do something like this:
select distinct if(t1.name = t2.name, t1.name, null) as DUPLICATE_Name,
if(t1.number = t2.number, t1.number, null) as DUPLICATE_Number
from tb1 t1
inner join tb1 t2 on (t1.name = t2.name or t1.number = t2.number)
and t1.slno <> t2.slno
SQL Fiddle Example #2
Output #2:
| DUPLICATE_NAME | DUPLICATE_NUMBER |
-------------------------------------
| Aaa | 111 |
| Abb | (null) |
| cca | (null) |

Pivoting data in MySQL.

I'm sure this sort of question has come up before but I've been searching and can't find anything similar to what I need.
Edit: so after some reading this looks like it falls under pivots and uses group concat. If anybody has any insight id really appreciate it.
I have 3 tables (unnecessary fields & data stripped out for simplicity):
Students
id name
------------------
1 John
2 Jane
Tests
id name
------------------
1 Test1
2 Test2
Results
id test_id student_id result
--------------------------------------
1 1 1 90
2 1 2 70
3 2 1 50
4 2 2 95
What I want is to be able to produce a table like this:
Name Average Test1 Test2
-----------------------------------
John 70 90 50
Jane 92.5 70 95
I know how to get the average, and I'm sure I could do this with an ugly set of loops and php logic but I'd like to get the most efficient solution. Any help is greatly appreciated.
SELECT
s.name,
avg(r.result) AS average,
t1.result AS test1,
t2.result AS test2
FROM
students s,
results r,
results t1,
results t2
WHERE
r.student_id = s.id AND
t1.test_id = 1 AND
t1.student_id = s.id AND
t2.test_id = 2 AND
t2.student_id = s.id
GROUP BY s.id;
+------+---------+-------+-------+
| name | average | test1 | test2 |
+------+---------+-------+-------+
| John | 70 | 90 | 50 |
| Jane | 82.5 | 70 | 95 |
+------+---------+-------+-------+
Edit: You can't really make the test columns dynamically based on the contents of the tests table. You can pick specific values to use as columns though.
You'll want to look at the AVG function.
Try This:
mysql> Select s.Name as Name, avg(r.result) as Average
from result as r join Student as s
on r.id=s.id group by (r.id)
you will have something like this:
Name Average
-----------------
John 70
Jane 92.5
You can't put a row as a column, imagine that John have 100000 times the test1, it will no be possible to put it as column.

mysql query: get only those name_id which has 4 and 6 term_id attached to it

I have a table and data like this
id | term_id | name_id
1 | 4 | 1
2 | 6 | 1
3 | 5 | 2
4 | 6 | 2
3 | 4 | 3
4 | 6 | 3
I want a query so that I can get only those name_id which has 4 and 6 term_id attached to it ... if i query 4,6,2 it should not display me anything because no named_id is attached to all three of them or like 4,5 it should not display anything because non has same 4 and 5
SELECT Name_ID
FROM Table1 t
WHERE term_id IN (4,6)
GROUP BY NAME_ID
HAVING COUNT(*) = 2
This is it (if i understand what you want):
SELECT t1.name_id
FROM table1 t1
INNER JOIN table1 t2 ON t1.name_id=t2.name_id
WHERE (t1.term_id = 4 AND t2.term_id=6) OR (t1.term_id = 6 AND t2.term_id=4)
GROUP BY name_id;
When i run this query i get
+---------+
| name_id |
+---------+
| 1 |
| 3 |
+---------+
SELECT DISTINCT name_id
FROM t
WHERE
term_id = 4
AND
name_id IN (SELECT name_id FROM t WHERE term_id = 6)
select
name_id
from
YourTable
where
term_id = 4 or term_id = 6
group by
name_id
having
count(*) = 2
To clarify what is happening, you need to understand the "GROUP BY" and "HAVING" context. Group by forces the SQL query to group as many qualified records by the columns it is identifying as the "Group By" clause. This just happens to be the same single column in the query.
Next, the HAVING clause is based on the final results of the group after all records are pre-qualified and included. In this case, it is looking at the COUNT(*) of records that qualified = 2.
Since you were concerned with a given Name_ID being associated with BOTH Terms of 4 AND 6, we want any names where their ID was found in BOTH... Hence the OR condition of the WHERE clause. We want any record where a person was associated with 4 OR 6. If at the result, only ONE record was found (only a 4 or 6), their COUNT() value would be equal to 1 and thus discarded from the result set... Only those that qualify with BOTH terms would have a count() = 2 and thus included in the final results...
To see otherwise what it may look like, try this query...
select
name_id,
count(*) as TotalTermsPerName
from
YourTable
group by
name_id
You'll get all name ID with a minimum of 1 term, and others that could have 10 terms or more based on your data. Since the original query was only considering the terms of 4 AND 6, the maximum count that COULD be reached would be a max of 2.
Hope this helps, instead of just a query answer and not understanding the basis of HOW things work.