So I have this table
table: "tbl_hash"
------------------------------
| id | hash1 | hash2 | hash3 |
------------------------------
| 1 | a | b | c |
| 2 | a | b | c |
| 3 | a | g | d |
| 4 | a | g | d |
| 5 | a | g | d |
------------------------------
I only want to group them by hash1, hash2, and hash3. count them and only return the count which is higher by 2.
So I have this query to get the values I wanted:
select CONCAT(hash1, hash2, hash3) as hashes, COUNT(*) as count from `tbl_hash` group by hashes having `count` > 2 limit 5
^^ the query above works perfectly..
But what If I wanted to get the data and count for each row? Expected output:
--------------------------------------
| id | hash1 | hash2 | hash3 | count |
--------------------------------------
| 1 | a | b | c | 2 |
| 2 | a | b | c | 2 |
| 3 | a | g | d | 3 |
| 4 | a | g | d | 3 |
| 5 | a | g | d | 3 |
--------------------------------------
I'm also planning on converting those to a query builder using DB::table...
You may join your original table to a subquery which finds the counts for each group:
SELECT t1.*, t2.cnt
FROM tbl_hash t1
INNER JOIN
(
SELECT hash1, hash2, hash3, COUNT(*) AS cnt
FROM tbl_hash
GROUP BY hash1, hash2, hash3
HAVING COUNT(*) > 2
) t2
ON t1.hash1 = t2.hash1 AND
t1.hash2 = t2.hash2 AND
t1.hash3 = t2.hash3;
Note that what I wrote above would completely filter off any original records belonging to a hash1/hash2/hash3 group which did not have a count greater than 2. If you instead want all records, with the count, then remove the HAVING clause.
As a side note, in databases which support analytic functions, such as SQL Server and Oracle, we could write a much less verbose query using COUNT as an analytic function. At some point, mainstream versions of MySQL will also support this. But for now, we are stuck with doing a join.
Related
**Im trying to get the difference between two tables sums but i keep getting the wrong outcome **
Table1 Table2
| product | quantity | | product | quantity |
| -------- | -------- | | -------- | -------- |
| a | 7 | | a | 2 |
| b | 8 | | b | 4 |
| c | 9 | | c | 1 |
| c | 7 | | c | 3 |
| a | 3 | | a | 2 |
| b | 4 | | b | 3 |
I tried this queury but i got the wrong values:
select table1.product, sum(table1.quantity) - sum(table2.quantity) as difference
from table1
join table2 on table1.product = table2.product
group by table1.product,table2.product;
Expected Outcome
Table1
product
difference
a
6
b
5
c
12
UNION ALL the tables (where you take -quantity for table2.) GROUP BY the result.
select product, sum(quantity)
from
(
select product, quantity from table1
union all
select product, -quantity from table2
) dt
group by product
As requested - some comments:
As a general advice, it's safer to GROUP BY (in subqueries) before joining - since a JOIN can result in multiple rows for a value.
Also, to include a product only found in one of the tables, an outer join would have been needed.
I have some table like this
table request_buys
| id | invoice | user_id |
| -- | ----------------- | ------- |
| 3 | 20220405/01104298 | 1 |
table traces
| id | request_buy_id | status_id | created_at |
| -- | -------------- | --------- | ------------------- |
| 37 | 3 | 1 | 2022-03-27 14:12:25 |
| 38 | 3 | 2 | 2022-03-28 14:12:25 |
| 39 | 3 | 3 | 2022-03-29 14:12:25 |
| 40 | 3 | 4 | 2022-03-30 14:12:25 |
| 41 | 3 | 5 | 2022-03-31 14:12:25 |
| 42 | 3 | 6 | 2022-04-01 14:12:25 |
table statuses
| id | nama |
| -- | ----------------- |
| 1 | Order Placed |
| 2 | Order Paid |
| 3 | Accepted |
| 4 | Picked by Courier |
| 5 | In Transit |
| 6 | Delivered |
| 7 | Rated |
| 8 | Rejected |
| 9 | Canceled |
and then i try to design query like below
select
request_buys.invoice,
MAX(traces.id) as traces_id,
MAX(statuses.nama) as statuses_nama
from
`request_buys`
inner join `traces` on `request_buys`.`id` = `traces`.`request_buy_id`
inner join `statuses` on `traces`.`status_id` = `statuses`.`id`
where
`user_id` = 1
group by
request_buys.id
and produces output like the following
output
| invoice | traces_id | statuses_nama |
| ----------------- | --------- | ----------------- |
| 20220405/01104298 | 42 | Picked by Courier |
and the output i expect should be like in the table below
expect
| invoice | traces_id | statuses_nama |
| ----------------- | --------- | ----------------- |
| 20220405/01104298 | 42 | Delivered |
I understand my error is in MAX(statuses.nama) which I should change like removing MAX() in statuses.nama
But i just get error like this "SELECT list is not in GROUP BY clause and contains nonaggregated ... this is incompatible with sql_mode=only_full_group_by"
then I tried some to clear the value "ONLY_FULL_GROUP_BY" with a query like the following
SET sql_mode=(SELECT REPLACE(##sql_mode,'ONLY_FULL_GROUP_BY',''))
and the result is like this
output
| invoice | traces_id | statuses_nama |
| ----------------- | --------- | ----------------- |
| 20220405/01104298 | 42 | Order Placed |
and I'm really stuck at this
and how to make trace_id.status_id from the "GROUP BY" result based on request_buys.id still have a relationship with statuses.id
Your problem lies with your misuse of the MAX(statuses.nama) expression. Based on your expected output,you intend to get the statuses.nama which matches the MAX(traces.id), NOT the MAX(statuses.nama) value which returns the highest value in terms of alphabetic order. In this case, the initial letter 'P' > 'D' . I have tweaked your code a bit and tried it on workbench,supposing there are more than one invoice for a particular user.(e.g insert into request_buys values (4,'20230405/01104298',1); insert into traces values (43,4,7,'2022-04-01 14:12:25');) It works as intended.
select invoice, t.id as traces_id, s.nama as statuses_name from request_buys r
join traces t on r.id=t.request_buy_id
join statuses s on t.status_id=s.id
join
(select traces.request_buy_id, MAX(traces.id) as traces_id
from `request_buys`
inner join `traces` on `request_buys`.`id` = `traces`.`request_buy_id`
where
`user_id` = 1
group by
traces.request_buy_id ) join_t
on t.request_buy_id=join_t.request_buy_id and t.id=join_t.traces_id
;
If I'm understanding correctly, you're trying to retrieve the most recent status for each invoice. Using MAX(nama) won't return that result, because it just picks the maximum status name alphabetically.
Assuming you're using MySQL 8.x, use ROW_NUMBER() to sort and rank the statuses for each invoice, by the most recent date first. Then grab the latest one using where rowNum = 1
WITH cte AS (
SELECT rb.id AS request_buy_id
, rb.invoice
, t.id AS traces_id
, s.nama AS statuses_nama
, ROW_NUMBER() OVER(PARTITION BY rb.id ORDER BY t.created_at DESC) AS RowNum
FROM request_buys rb
INNER JOIN traces t ON rb.id = t.request_buy_id
INNER JOIN statuses s ON t.status_id = s.id
WHERE user_id = 1
)
SELECT *
FROM cte
WHERE RowNum = 1
;
Result:
request_buy_id
invoice
traces_id
statuses_nama
RowNum
3
20220405/01104298
42
Delivered
1
db<>fiddle here
so what I am trying to do is having 3 tables (pictures, collections, and bridge) with the following columns:
Collections Table:
| id | name |
------------------
| 1 | coll1 |
| 2 | coll2 |
------------------
Pictures Table: (timestamps are unix timestamps)
| id | name | timestamp |
-------------------------
| 5 | Pic5 | 1 |
| 6 | Pic6 | 19 |
| 7 | Pic7 | 3 |
| 8 | Pic8 | 892 |
| 9 | Pic9 | 4 |
-------------------------
Bridge Table:
| id | collection | picture |
-----------------------------
| 1 | 1 | 5 |
| 2 | 1 | 6 |
| 3 | 1 | 7 |
| 4 | 1 | 8 |
| 5 | 2 | 5 |
| 6 | 2 | 9 |
| 7 | 2 | 7 |
-----------------------------
And the result should look like this:
| collection_name | picture_count | newest_picture |
----------------------------------------------------
| coll1 | 4 | 8 |
| coll2 | 3 | 9 |
----------------------------------------------------
newest_picture should always be the picture with the heighest timestamp in that collection and I also want to sort the result by it. picture_count is obviously the count of picture in that collection.
Can this be done in a single statement with table joins and if yes:
how can I do this the best way?
A simple method uses correlated subqueries:
select c.*,
(select count(*)
from bridge b
where b.collection = c.id
) as pic_count,
(select p.id
from bridge b join
pictures p
on b.picture = b.id
where b.collection = c.id
order by p.timestamp desc
limit 1
) as most_recent_picture
from collections c;
A more common approach would use window functions:
select c.id, c.name, count(bp.collection), bp.most_recent_picture
from collections c left join
(select b.*,
first_value(p.id) over (partition by b.collection order by p.timestamp desc) as most_recent_picture
from bridge b join
pictures p
on b.picture = p.id
) bp
on bp.collection = c.id
group by c.id, c.name, bp.most_recent_picture;
I have 3 tables like this
SecretAgents
| id | name |
|----|------|
| 1 | A |
| 2 | B |
Victims
| id | name | agent_id |
|----|------|----------|
| 1 | Z | 1 |
| 2 | Y | 1 |
| 3 | X | 2 |
Data
| id | keys | values | victim_id | form_id |
|----|------|--------|-----------|---------|
| 1 | a1 | x | 1 | 1 |
| 2 | a2 | xx | 1 | 2 |
| 3 | a3 | xxx | 2 | 1 |
| 4 | a5 | xxx | 1 | 1 |
I have to get the count of forms(here victim_id and form_id are composite primary keys) and the count of victims for each agent.
I have tried this for any 2 tables with left joins and group by but I am not able to achieve the same together. If anyone can be generous enough to offer a pointer/solution, that would be super awesome..
EDIT 1: The query
This is definitely not the right query but anyways
SELECT count(DISTINCT v.id) as victimcount, `sa`.`username`, `sa`.`id`, count(DISTINCT d.form_id) as submissions
FROM `SecretAgents` as `sa`
LEFT JOIN `Victims` as `v` ON `v`.`agent_id`=`sa`.`id`
LEFT JOIN `Data` as `d` ON `d`.`victim_id`=`v`.`id`
GROUP BY `v`.`agent_id`
ORDER BY `sa`.`id` ASC
The victimcount is correct but the submissions count becomes wrong. Tried lots of other things too but this is the most relevant...
Thanks
I believe you can count the forms-per-agent like so:
SELECT COUNT(*) as form_count, a.id as id, a.name as agent
FROM Data d
LEFT JOIN Victims v ON v.id = d.victim_id
LEFT JOIN SecretAgents a on v.agent_id = a.id
GROUP BY a.id;
To count the victims, just leave off the Data table.
I have three tables "names", "groups", "tasks"
"names"
| nid | name |
| 1 | John |
| 2 | Jim |
| 3 | Jerry |
"groups"
| gid | nid |
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
"tasks"
| tid | gid |
| 1 | 2 |
| 2 | 1 |
I want to get a list of names, that belong to a task, like this:
| tid | names |
| 1 | Jim,Jerry |
| 2 | John,Jim |
I successfully tried:
SELECT t.tid,n.name
FROM tasks t
LEFT JOIN groups g ON g.gid=t.gid
LEFT JOIN names n ON n.nid=g.nid
This works so far, but the names are in single outputs so I tried to use GROUP_CONCAT(), but this does not work. I just get the first task with the wrong names!?!?
SELECT t.tid,GROUP_CONCAT(n.name)
FROM tasks t
LEFT JOIN groups g ON g.gid=t.gid
LEFT JOIN names n ON n.nid=g.nid
I have never used GROUP_CONCAT() before. It works well, when using it in a single SELECT without joins. A short explanation what I did wrong, would be nice.
When using group_concat you need to group your results.
Add group by t.id at the end of your query.