I have a simple table like this:
group | name | price
1 | john |
2 | mike |
3 | paul |
1 | sean |
4 | jack |
2 | brad |
5 | mick |
1 | bill |
4 | chad |
I have two different price values where 100EUR is for a first member of a group and 50EUR is for all additional members of that same group.
Detailed explanation. If a group has only one member, that member gets a price of 100EUR. If a group has multiple members, the first member gets a price of 100EUR, and all additional members of that same group get a price of 50EUR. There can be unlimited number of groups that will be added additionally.
The result should be like this:
group | name | price
1 | john | 100
2 | mike | 100
3 | paul | 100
1 | sean | 50
4 | jack | 100
2 | brad | 50
5 | mick | 100
1 | bill | 50
4 | chad | 50
I'd need a query which would be able to INSERT/UPDATE all missing price fields whenever I manually run it.
Thank you in advance for looking into that matter.
After a lot of trial and error I found a perfect fully functional solution, based on daviid's clever method. The issue with mysql is that by it's structure won't update tables with select methods as subquery. However, self-join (join or inner join) methods can be used instead in this case. I also had to add auto-incremental id to that table, so the final table structure is:
id | group_id | name | price
1 | 1 | john |
2 | 2 | mike |
3 | 3 | paul |
4 | 1 | sean |
5 | 4 | jack |
6 | 2 | brad |
7 | 5 | mick |
8 | 1 | bill |
9 | 4 | chad |
---
SET SQL_SAFE_UPDATES=0;
UPDATE table_name
SET price = 50;
UPDATE table_name AS a
JOIN
( SELECT id
FROM table_name
GROUP BY group_id
HAVING COUNT(*) >= 1
) AS b
ON a.id = b.id
SET a.price = 100;
Thanks also to Cody and Barmar for usable hints...
A partial answer: you can GROUP BY your "group" field and tack on a HAVING COUNT(group) > 1 to determine if that group has more than 1 member.
That is, to see all groups with more than one member it would look like:
SELECT
group
FROM table
GROUP BY group
HAVING COUNT(group) > 1
That will just tell you which groups have multiple members. Without another way to ensure ordering you cannot tell which member is "first" in their group and thus should be priced at 100 and all others priced at 50.
The following queries are not tested and might contain syntax errors. But they are good enough to understand the principle. There are many possible ways to achieve your result.
Here is my take: I would make use of one query to UPDATE the price on every row and set it to 50 whether it is the first group member or not. >table_name<, of course, needs to be changed to the name of your mentioned table.
UPDATE >table_name<
SET price = 50;
Then I would take care of each individual group and the respective first member by running the following query. Adapt the query to each group by changing the >groupId<.
UPDATE >table_name<
SET price = 100
WHERE id = (
SELECT id
FROM >table_name<
WHERE group = >groupId<
ORDER BY id
LIMIT 1
);
Take a look a the nested query: It queries the table for all members of only one group, orders them in ascending order and only returns an id per member. By applying LIMIT to the query, the result will just be the first group member's id. The resulting id can then be used in the other query to update the price and set it to 100.
But be careful: If you insert/delete (new) members with an id that is not just counting up, this query might select a "new first member".
Related
Maybe this question is super specific but I can't really find a way to do it.
A bit of background. I'm studying, and In order to learn this I'm playing around counting stats from our Wednesday football matches with my friends.
Now in order to make data as normalized as possible and calculate everything programmatically, I have a table called matches and a table called goals.
Matches
+----+------------+
| id | date |
+----+------------+
| 1 | 05-01-2022 |
+----+------------+
| 2 | 12-01-2022 |
+----+------------+
Goals
+----+--------------+
| id | match | team |
+----+--------------+
| 1 | 1 | A |
+----+--------------+
| 2 | 1 | B |
+----+--------------+
| 3 | 1 | B |
+----+--------------+
| 4 | 2 | A |
+----+--------------+
| 5 | 2 | A |
+----+--------------+
So my objective here is to count the goals for each team in each match, compare both values to determine the winner and come up with a result like this:
Results
+-------+------------+
| match | result |
+-------+------------+
| 1 | B |
+-------+------------+
| 2 | A |
+-------+------------+
I'm currently doing this fairly straightforward using laravel on my app, but I'm trying to get it done through SQL queries only.
I also know that this would be much easier, if i simply added a goals_team_a, goals_team_b, result columns to my Matches table. If this is the actual correct solution is super easy to add, I just though at some point goals may differ from the score because of a typo or something and this is the way to be extra sure. Probably an overkill
The current structure of matches and goals is definitely flawed. If a given match id has no goals scored by either side there is no record of who played. By simply adding home and away teams to the match you have a more meaningful match entity and it becomes simple to query for the result.
Matches
id
kick_off
home_team_id
away_team_id
1
2022-01-05 10:00:00
1
2
2
2022-01-12 14:30:00
2
1
Teams
id
name
1
A
2
B
Additionally, if your goals table is as simple as suggested, it serves no purpose but I suspect this was simplification for the sake of providing an example. Assuming there is a player and time goal scored as part of the goal table, it has obvious value.
Goals
id
match_id
team_id
player_id
time_scored
1
1
1
1
10:01:23
2
1
2
2
10:17:38
3
1
2
2
11:02:44
4
2
1
1
15:42:53
5
2
1
1
15:47:18
With the teams identified in the match you can then use something like -
select m.id as match_id,
case
when count(distinct hg.id) > count(distinct ag.id) then ht.name
when count(distinct hg.id) < count(distinct ag.id) then at.name
else 'draw'
end as `result`
from matches m
join teams ht on m.home_team_id = ht.id
join teams at on m.away_team_id = at.id
left join goals hg on m.home_team_id = hg.team_id and m.id = hg.match_id
left join goals ag on m.away_team_id = ag.team_id and m.id = ag.match_id
group by m.id
to get -
match
result
1
B
2
A
Here's a db<>fiddle to play around with.
We are looking to return rows of a query as groups and displaying all entries of the group in the sort order. Randomly based on the set_id... and then in order by the sort_id.
So, randomly it will show:
Carl,
Phil,
Wendy,
Tina,
Rick,
Joe
or
Tina,
Rick,
Joe,
Carl,
Phil,
Wendy
This query is always showing Tina/Rick/Joe first
SELECT * FROM products ORDER BY set_id, rand()
Any help would be appreciated
+---------+--------+-------+----------+
| id | set_id | name | sort_id |
+---------+--------+-------+----------+
| 1 | AA |Rick | 2 |
| 2 | BB |Carl | 1 |
| 3 | AA |Joe | 3 |
| 4 | AA |Tina | 1 |
| 5 | BB |Phil | 2 |
| 6 | BB |Wendy | 3 |
+---------+--------+-------+----------+
if you need a random comma separated name list this will do the trick.
This will keep the groups and the correct sorting within the group.
Query
SELECT
GROUP_CONCAT(Table_names_rand.names) as names
FROM (
SELECT
*
FROM (
SELECT
GROUP_CONCAT(name ORDER BY sort_id) as names
FROM
Table1
GROUP BY
set_id
)
AS Table1_names
ORDER BY
RAND()
)
AS Table_names_rand
Result
| names |
|-------------------------------|
| Carl,Phil,Wendy,Tina,Rick,Joe |
or
| names |
|-------------------------------|
| Tina,Rick,Joe,Carl,Phil,Wendy |
demo http://www.sqlfiddle.com/#!9/487ac9/9
if you need random names as records output.
Query
SELECT
Table1.name
FROM
Table1
CROSS JOIN (
SELECT
GROUP_CONCAT(Table_names_rand.names) as names
FROM (
SELECT
*
FROM (
SELECT
GROUP_CONCAT(name ORDER BY sort_id) as names
FROM
Table1
GROUP BY
set_id
)
AS Table1_names
ORDER BY
RAND()
)
AS Table_names_rand
)
AS Table_names_rand
ORDER BY
FIND_IN_SET(name, Table_names_rand.names)
Result
| name |
|-------|
| Carl |
| Phil |
| Wendy |
| Tina |
| Rick |
| Joe |
or
| name |
|-------|
| Tina |
| Rick |
| Joe |
| Carl |
| Phil |
| Wendy |
demo http://www.sqlfiddle.com/#!9/487ac9/28
If we strip away the randomness of the gorup ordering, your query would look like this:
SELECT
*
FROM
products
ORDER BY
set_id,
sort_id;
The ordering by set_id is necessary to "group" the results, without really grouping them. You do not want to group them, because then the rows with the same group would be aggregated, meaning that only one row per group would be put out.
Since you only want to randomize the groups, you need to write another query that assigns a random number to each group, like the one below:
SELECT
set_id,
RAND() as 'rnd'
FROM
products
GROUP BY
set_id
The GROUP BY clause makes sure, that each group is only selected once. The resultset will look like this:
| set_id | priority |
+--------+---------+
| AA | 0.21 |
| BB | 0.1 |
With that result we can then randomize the output, by combining both queries with a JOIN on the set_id field. This will add the randomly generated number from the second query to the result set of the first query and therefore extend the static set_id with the randomized, but still for all group members equal, rnd:
SELECT
products.*
FROM
products
JOIN (
SELECT
set_id,
RAND() as 'rnd'
FROM
products
GROUP BY
set_id
) as rnd ON rnd.set_id = products.set_id
ORDER BY
rnd.rnd,
products.set_id,
products.sort_id;
Keep in mind, that it is important to still group on products.set_id, because it may be possible that two groups get the same random number assigned. If the result would not be ordered by products.set_id those groups members would then be merged.
I got a query and I want wort sort the result depending on another database.
This is my query:
SELECT a.`name`
FROM `article` AS a
LEFT JOIN `container` AS b
ON b.`id` = a.`container`
WHERE a.`container` = '1'
ORDER BY b.`sort` ASC;
This works fine, but it always sorts the result ascending. Bute I want ist to be sorted depending on the value in the container table.
Article table
id | container | name | gender
---|---------------------|---------
1 | 1 | Michael | male
2 | 1 | Joe | male
3 | 2 | Karen | female
4 | 2 | Andrew | male
5 | 3 | Jessica | female
Container table
id | sort | order
---|--------|----
1 | name | asc
2 | name | desc
3 | gender | asc
4 | name | asc
5 | gender | desc
How can I change my query to get the order from the container table?
I would expect this result:
id | container | name | gender
---|---------------------|---------
2 | 1 | Joe | male
1 | 1 | Michael | male
You can't do this in one query.
A given SQL query must be fixed at the time you prepare the query, which is before the execution begins. You can't make the behavior change depending on what data it discovers while executing.
You'll have to read the sort direction in one query, and then format a dynamic SQL query with the values you discover.
SELECT `sort`, `order` FROM container WHERE id = 1
Fetch these data into application variables like $sort_column, $direction. Then use them:
$sql = "
SELECT a.`name`
FROM `article` AS a
WHERE a.`container` = '1'
ORDER BY {$sort_column} {$direction}";
...prepare $sql as a query...
...execute query...
(I'm assuming PHP syntax for the variables, but you can do something similar in any language.)
Does something like this work?
ORDER BY CASE WHEN b.`order` = 'ASC' b.`sort` ELSE -b.`sort` END;
If not please provide some sample data and the expected result for that data so we can understand your requirements better.
I have products with different rankings. The products may be members of a supergroup (like Cream).
product_id | supergroup | rank | other_info
1 | Cream | 3 | Eric
2 | Zep | 1 | Jimmy
3 | Zep | 4 | Jon Paul
4 | Cream | 3 | Jack
5 | Cream | 4 | Ginger
6 | Who | 4 | Roger
7 | Who | 5 | John
8 | Who | 3 | Pete
I want to get the max product rank from each group, along with other info for that product id. Ranks are not meant for intragroup ranks. They are ranks that work across all products in the system. So more than one product may have the same rank, even in the same group.
EDIT: fixed "other_info". I had some gibberish there. Also added a row. Results should be from highest rank to lowest. But they also should only include the highest ranking product_id from each supergroup, along with matching other_info.
product_id | supergroup | rank | other_info
2 | Zep | 1 | Jimmy
8 | Who | 3 | Pete
1 | Cream | 3 | Eric
Can I do that with a simple query? The existing system's query already involves a GROUP BY statement on the supergroup, and no aggregators in the SELECT. That results in a random, but coherent row from within the group. What is the simplest way to modify the query to get a complete row, but always of the highest-ranked member of each super group.
If there is no way, what about this: Is this possible without GROUP BY?
SELECT t.*
FROM your_table t
JOIN (
SELECT MIN(product_id) as product_id #if there are multiple products with the same (min) rank in the same supergroup - get the one with lowest product_id
FROM your_table tt
JOIN (
SELECT supergroup, MIN(rank) as min_rank
FROM your_table
GROUP BY supergroup
) mr ON mr.supergroup = tt.supergroup AND mr.min_rank = tt.rank
GROUP BY tt.supergroup, tt.rank
) as mid ON mid.id.product_id = t.product_id
ORDER BY whatever_you_need_to
You need an index on (supergroup,rank) for this to run efficiently.
I have 2 tables (SALESMAN, SOLD), where the SALES table records what cars were sold each day. At night a job runs that must increase the SOLD count in the SALESMAN table. For example, here are two tables:
SALESMAN SALES
+-------------+-----------+------+ +------------+---------+
| SALESMANID | NAME | SOLD | | SALESMANID | VEHICLE |
| 1 | Bob | 1 | | 1 | GM |
| 2 | Charlie | 7 | | 1 | Chrys |
| 3 | Dave | 0 | | 1 | GM |
+-------------+-----------+------+ | 3 | Dodge |
| 3 | GM |
| 2 | Hummer |
+------------+---------+
After the UPDATE has run, Bob's sold count will increase to 4, Charlie's sold count will increase to 8, and Dave's sold count will increase to 2. I'm trying to create something like:
UPDATE SALESMAN SET SOLD=SOLD+(
SELECT COUNT(*)
FROM SALES
WHERE SALESMAN.SALESMANID = SALES.SALESMANID
)
Is this the right way to solve the problem?
I found a similar question here: (Updating one SQL table based on data in another table) but it's not clear if it will selectively updates table A in their example, or all records in table A.
UPDATE: I fixed the typo above but it still doesn't work. 0 rows affected when I run the query.
yes its right your query just change this
WHERE SALESAN.SALESMANID
to
WHERE SALESMAN.SALESMANID
your demo
i dont know why you didnt try it your self before asking a question.
INSERT INTO SALESMAN (SALESMANID, SOLD) (SELECT SALEMANID, COUNT(*) as c FROM SOLD GROUP BY SALEMANID) ON DUPLICATE KEY UPDATE SOLD = c
You may need to name the select and use .c
If the sales table is deleted after this nightly process runs, then this should work
Update m Set
Sold = sold +
(Select Count(*) From Sales
Where SalesmanId = m.SalesmanId)
From Salesman m
UPDATE SALESMAN a,
(SELECT SALESMANID, COUNT(*) SALE_COUNT
FROM SALES
group by SALESMANID) b
set a.SOLD=a.SOLD+ b.SALE_COUNT
WHERE a.SALESMANID = b.SALESMANID;
see SQL Fiddle