SQL groups inside groups - mysql

I have transaction data like this example:
Name | Price
George | 20
George | 20
George | 20
George | 30
Paul | 20
Paul | 20
Paul | 30
Paul | 30
Paul | 35
I need to group by user and sort by the number of transactions in general, but within that group of users also make groups by price ordering by the amount of transactions at that price.
I need this result:
Name | Price | Count
Paul | 20 | 2
Paul | 30 | 2
Paul | 35 | 1
George | 20 | 3
George | 30 | 1
UPDATE
It is in a MySQL 5.5 database. I need to make the query for fluent in Laravel but having it in SQL is a great advance.
Thanks in advance.

SELECT t1.*
FROM ( SELECT name,
price,
COUNT(*) cnt
FROM srctable
GROUP BY name, price ) t1
JOIN ( SELECT name,
COUNT(*) tcnt
FROM srctable
GROUP BY name ) t2 USING (name)
ORDER BY tcnt DESC,
cnt DESC;
fiddle

Here you go. It can be done in MySQL 8.x. The double ordering you want requires the use of a couple of table expressions, as shown below:
select
x.name, x.price, x.cnt
from (
select name, price, count(*) as cnt
from t
group by name, price
) x
join (
select name, row_number() over(order by cnt desc) as ob
from (
select name, count(*) as cnt
from t
group by name
) y
) z on x.name = z.name
order by z.ob, x.cnt desc
Result:
name price cnt
------ ----- ---
Paul 20 2
Paul 30 2
Paul 35 1
George 20 3
George 30 1
For reference, the data script I used is:
create table t (
name varchar(10),
price int
);
insert into t (name, price) values
('George', 20),
('George', 20),
('George', 20),
('George', 30),
('Paul', 20),
('Paul', 20),
('Paul', 30),
('Paul', 30),
('Paul', 35);

Related

Min and Max on a SUM column

I have a table like:
Phrase | qty
phrase_1 | 4
phrase_1 | 1
phrase_1 | 8
phrase_2 | 2
phrase_3 | 3
phrase_3 | 2
What I initially return is:
phrase_1 | 13
phrase_3 | 5
phrase_2 | 2
Using:
SELECT phrase, sum(qty) as total
FROM mytable
GROUP By phrase
ORDER BY total DESC
What I need, and can't figure out, is how to return the min and max with the results.
so I would get:
phrase, qty, min, max
phrase_1 | 13 | 2 | 13
phrase_3 | 5 | 2 | 13
phrase_2 | 2 | 2 | 13
Because I want to run a normalization on the resultset and return a new order based on values between 1 and 0.
Something like (this doesn't work):
SELECT phrase, sum(qty) as total, (total - min(total)/max(total) - min(total)) AS rank
FROM mytable
GROUP By phrase
ORDER BY rank DESC
The above statement is ultimiately what I'm looking to do and not sure if it's possible.
With some subqueries you can achieve your goal, but pretty it will never get
CREATE TABLE mytable (
`Phrase` VARCHAR(8),
`qty` INTEGER
);
INSERT INTO mytable
(`Phrase`, `qty`)
VALUES
('phrase_1', '4'),
('phrase_1', '1'),
('phrase_1', '8'),
('phrase_2', '2'),
('phrase_3', '3'),
('phrase_3', '2');
SELECT phrase,total,(total - mi/ma - mi) AS rank
FROM
(SELECT phrase, sum(qty) as total
FROM mytable
GROUP By phrase
ORDER BY total DESC) t1 CROSS JOIN (SELECT MIN(total) mi,MAX(total) ma
FROM
(SELECT phrase, sum(qty) as total
FROM mytable
GROUP By phrase
ORDER BY total DESC) t1) t2
phrase | total | rank
:------- | ----: | ------:
phrase_1 | 13 | 10.8462
phrase_3 | 5 | 2.8462
phrase_2 | 2 | -0.1538
db<>fiddle here
You want window functions:
SELECT phrase, sum(qty) as total,
MIN(SUM(qty)) OVER () as min_total,
MAX(SUM(qty)) OVER () as max_total
FROM mytable
GROUP By phrase
ORDER BY total DESC
You can use code below :
SELECT phrase, sum(qty) as total,
MIN(SUM(qty)) OVER () as min_total,
MAX(SUM(qty)) OVER () as max_total
into #temp
FROM mytable
GROUP By phrase
ORDER BY total DESC
Select *,(total - min_total/max_total - min_total) AS rank From #temp
Drop Table #temp

Getting Percentage and Total from SELECT GROUP BY Mysql

After multiple searches I couldn't find a fit solution that would work in my case.
I have the following table:
id category entry_date
1 Suggestion 01/01/2019
2 Suggestion 05/01/2019
3 Compliment 05/01/2019
4 Complaint 12/02/2019
5 Suggestion 09/10/2019
6 Compliment 23/11/2019
I need to show the number of each category and the percentage of each (based on the total entries). The 'where' will limit a date range - but I'm assuming that here it doesn't matter. This is my expected result:
Category Totals % of Total Entries
Compliment 2 ˜34%
Complaint 1 16%
Suggestion 3 60%
Here is the query I'm currently using:
SELECT category,
COUNT(*) AS total,
ROUND(COUNT(category)*100 / (SELECT COUNT(*))) AS pct
FROM (Mytable)
WHERE `entry_date` >= '2018-01-01' AND `entry_date` <= '2019-12-31'
GROUP BY category ORDER BY category ASC
With this the pct is relative to each category (so: 200,100,300), and not the total.
I'm using MySQL 5.7. Thank you all!
SELECT count can take time, so it will not be fast on big tables
CREATE TABLE Table1
(`id` int, `category` varchar(10), `entry_date` varchar(10))
;
INSERT INTO Table1
(`id`, `category`, `entry_date`)
VALUES
(1, 'Suggestion', '01/01/2019'),
(2, 'Suggestion', '05/01/2019'),
(3, 'Compliment', '05/01/2019'),
(4, 'Complaint', '12/02/2019'),
(5, 'Suggestion', '09/10/2019'),
(6, 'Compliment', '23/11/2019')
;
SELECT `category`, COUNT(*) AS cnt,
100.0 * COUNT(*) / (SELECT COUNT(*) FROM Table1) AS percent FROM Table1 GROUP BY `category`
category | cnt | percent
:--------- | --: | -------:
Suggestion | 3 | 50.00000
Compliment | 2 | 33.33333
Complaint | 1 | 16.66667
db<>fiddle here
You'd need to do this with window functions in MySQL 8.0:
with cte as (
select category, count(*) over (partition by category) as subtotal,
count(*) over () as total from following_table )
select category, subtotal,
concat(round(subtotal*100/total), '%') as `% of Total Entries`
from cte group by category;
Output:
+------------+----------+--------------------+
| category | subtotal | % of Total Entries |
+------------+----------+--------------------+
| Complaint | 1 | 17% |
| Compliment | 2 | 33% |
| Suggestion | 3 | 50% |
+------------+----------+--------------------+
In MySQL 5.7, do it with two queries. The first to get the total, then the second to use that result to calculate the percentage per group.

How to calculate date difference for group data

I would like to count the date-time difference, when I group the data.
Lets have a look on table content example :
id | session_id | created
1 | 101 | 1/10/2010 9:30:10
2 | 102 | 1/10/2010 9:31:10
3 | 101 | 1/10/2010 9:32:10
4 | 103 | 1/10/2010 9:35:10
5 | 102 | 1/10/2010 9:38:10
6 | 103 | 1/10/2010 9:39:10
Output should be as follow :
session_id | time_count
101 | 2 (minutes)
102 | 7 (minutes)
103 | 4 (minutes)
So here i want to calculate time difference of 'created' field for data which is group by session_id.
Any help would be great. Thanks in advance :)
My Situation :
I have a table with following fields :
id, session_id, platform, event, created
there are 2 event (Start,End)
Now if there are records of Start and End for same session_id then i want the time takes to complete this session.
But if i have 2 Start session, but only 1 End session then i do not want the time difference for 2nd Session because it did not end
SELECT session_id,
DATEDIFF(MINUTE, MAX(created), MIN(created)) AS diff
FROM table
GROUP BY session_id
Try this:
Table Schema:
CREATE TABLE A(id INT, session_id INT,Event VARCHAR(20), created DATETIME);
INSERT INTO A VALUES(1, 101,'Start','2010/10/01 9:30:10')
,(2, 102,'Start' , '2010/10/01 9:31:10')
,(3, 101,'End' , '2010/10/01 9:32:10')
,(4, 103,'Start' , '2010/10/01 9:35:10')
,(5, 102,'End' , '2010/10/01 9:38:10')
,(6, 103,'End' , '2010/10/01 9:39:10')
,(7, 101,'Start' , '2010/10/01 9:39:10')
,(8, 102,'Start' , '2010/10/01 9:39:10')
,(9, 101,'End' , '2010/10/01 9:55:10');
Query:
SELECT D.session_id
,TIMESTAMPDIFF(MINUTE,MIN(D.created), MAX(D.created)) time_count
FROM(
SELECT a.id,a.session_id,a.created
,ROUND(count(*)/2,0) as RN
,count(*) as row_number1
FROM a AS a
JOIN a AS b ON a.session_id = b.session_id AND a.id >= b.id
GROUP BY a.id,a.session_id,a.created
ORDER BY 2,3
)D
GROUP BY D.session_id,D.RN
HAVING COUNT(1)>1
Output:
session_id time_count
101 2
102 7
103 4
101 16
Fiddle:
Check the Output in the #SQL Fiddle
You can try it's sintax :
WITH cte AS (
SELECT
session_id,
DATEDIFF(minute, created, LAG(created, 1)) AS diff,
ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created) rn
FROM yourTable
)
SELECT
session_id,
diff AS time_count
FROM cte
WHERE rn % 2 = 0
;

Selecting the most recent, lowest price from multiple vendors for an inventory item

I’m fairly proficient at SQL, however this question has had myself stumped for quite a while now. In the most basic sense, there are simply two tables:
Items
+----+--------+
| id | title |
+----+--------+
| 1 | socks |
| 2 | banana |
| 3 | watch |
| 4 | box |
| 5 | shoe |
+----+--------+
...and the prices table:
Prices
+---------+-----------+-------+------------+
| item_id | vendor_id | price | created_at |
+---------+-----------+-------+------------+
| 1 | 1 | 5.99 | Today |
| 1 | 2 | 4.99 | Today |
| 2 | 1 | 6.99 | Today |
| 2 | 2 | 6.99 | Today |
| 1 | 1 | 3.99 | Yesterday |
| 1 | 1 | 4.99 | Yesterday |
| 2 | 1 | 6.99 | Yesterday |
| 2 | 2 | 6.99 | Yesterday |
+---------+-----------+-------+------------+
(Please note: created_at is actually a timestamp, the words “Today” and “Yesterday” were provided merely to quickly convey the concept).
My goal is to get a simple result back containing the inventory item associated with the most recent, lowest price, including the reference to the vendor_id who is providing said price.
However, I find the stumbling block appears to be the sheer number of requirements for the statement (or statements) to handle:
Each item has multiple vendors, so we need to determine which price between all the vendors for each item is the lowest
New prices for the items get appended regularly, therefore we only want to consider the most recent price for each item for each vendor
We want to roll all that up into a single result, one item per row which includes the item, price and vendor
It seems simple, but I’ve found this problem to be uncanningly difficult.
As a note, I’m using Postgres, so all the fanciness it provides is available for use (ie: window functions).
Much simpler with DISTINCT ON in Postgres:
Current price per item for each vendor
SELECT DISTINCT ON (p.item_id, p.vendor_id)
i.title, p.price, p.vendor_id
FROM prices p
JOIN items i ON i.id = p.item_id
ORDER BY p.item_id, p.vendor_id, p.created_at DESC;
Optimal vendor for each item
SELECT DISTINCT ON (item_id)
i.title, p.price, p.vendor_id -- add more columns as you need
FROM (
SELECT DISTINCT ON (item_id, vendor_id)
item_id, price, vendor_id -- add more columns as you need
FROM prices p
ORDER BY item_id, vendor_id, created_at DESC
) p
JOIN items i ON i.id = p.item_id
ORDER BY item_id, price;
->SQLfiddle demo
Detailed explanation:
Select first row in each GROUP BY group?
Try this
CREATE TABLE #Prices ( Iid INT, Vid INT, Price Money, Created DateTime)
INSERT INTO #Prices
SELECT 1, 1, 5.99 ,GETDATE() UNION
SELECT 1, 2, 4.99 ,GETDATE() UNION
SELECT 2, 1, 6.99 ,GETDATE() UNION
SELECT 2, 2, 6.99 ,GETDATE() UNION
SELECT 1, 1, 3.99 ,GETDATE()-1 UNION
SELECT 1, 2, 4.99 ,GETDATE()-1 UNION
SELECT 2, 1, 6.99 ,GETDATE()-1 UNION
SELECT 2, 2, 6.99 ,GETDATE()-1
WITH CTE AS
(
SELECT
MyPriority = ROW_NUMBER() OVER ( partition by Iid, Vid ORDER BY Created DESC, Price ASC)
, Iid
, Vid
, price
, Created
FROM #Prices
)
SELECT * FROM CTE WHERE MyPriority = 1
It's also possible to do this with windowed functions, it will work on SQL Server version > 2005:
with cte1 as (
select
*,
row_number() over(partition by vendor_id, item_id order by created_at desc) as row_num
from prices
), cte2 as (
select
*,
row_number() over(partition by item_id order by price asc) as row_num2
from cte1
where row_num = 1
)
select i.title, c.price, c.vendor_id
from cte2 as c
inner join items as i on i.id = c.item_id
where c.row_num2 = 1;
sql fiddle demo(Thanks Erwin)

SQL sum a field grouped by same identifier in two different columns

The table has 4 columns Match, Winning_Player_ID, Losing_Player_ID, Quantity_Points_Exchanged_for_that_match. I would like to in a single query show the total number of points each player won and earned over all matches.
Here is the table
M WIN LOSE QTY
1 100 201 10
2 201 100 05
3 100 201 05
4 302 100 05
For output I would like total it in this way in a single query and cannot figure it out.
ID WIN LOSE
100 15 10
201 05 15
302 05 00
SELECT p.player ID,
(SELECT SUM(QTY) FROM tbl WHERE WIN = p.player) WIN,
(SELECT SUM(QTY) FROM tbl WHERE LOSE = p.player) LOSE
FROM
(SELECT DISTINCT WIN player FROM tbl
UNION
SELECT DISTINCT LOSE FROM tbl) p
No db available to check this out, but perhaps something like this:
SELECT player, SUM(winQty) AS WIN, SUM(loseQty) AS LOSE
FROM ( SELECT win AS player, qty AS winQty, 0 AS loseQty
FROM myTable
UNION ALL
SELECT lose AS player, 0 AS winQty, qty AS loseQty
FROM myTable
) x
GROUP BY player
UPDATE: Changed UNION to UNION ALL
Here's the answer:
CREATE THE TABLE:
CREATE TABLE table_name (
m int,
win int,
lose int,
qty int);
INSERT THE DATA:
INSERT INTO table_name (m, win, lose, qty)
values
(1, 100, 201, 10),
(2, 201, 100, 05),
(3, 100, 201, 05),
(4, 302, 100, 05);
The QUERY:
SELECT id, sum(won), sum(lost) from (
SELECT win as id, SUM(qty) as won, 0 as lost
FROM table_name W GROUP BY win
UNION
SELECT lose as id, 0 as won, SUM(qty) as lost
FROM table_name W GROUP BY lose
) sums
GROUP BY ID
The result:
+------+----------+-----------+
| id | sum(won) | sum(lost) |
+------+----------+-----------+
| 100 | 15 | 10 |
| 201 | 5 | 15 |
| 302 | 5 | 0 |
+------+----------+-----------+