I am using SUM() to count lines with multiple conditions in the following manner
SELECT date(time),
SUM(CASE WHEN crit1 = 1 AND crit2 NOT LIKE CONCAT('%',table1.id,'%') THEN 1 ELSE 0 END)
FROM table1, table2
GROUP BY date(time)
However what I noticed is that # of items that are counted is a multiple of items in the table1. So if there are 100 items that meet crit1 and crit2 criteria, and there are 5 items in table1, it would give me 500 items summed.
I added and removed items from table1 and it proportionally affected the SUM clause, to verify this.
How can it be summed without double counting for every case in CONCAT, or maybe a better way of counting all together?
Data structure:
table1 table2
id name time crit1 crit2
123 A 2013-05-15 05:00:00 1 456
234 B 2013-05-15 05:00:00 2 789
345 C 2013-05-15 05:00:00 1 678
Note: IDs are unique
Desired output:
2013-05-15 2
I believe you want this:
SELECT date(time), COUNT(crit2)
FROM table2
WHERE
table2.crit1 = 1
AND
table2.crit2 NOT IN (SELECT id FROM table1)
GROUP BY date(time)
Related
I am trying to write a SQL query in MySQL Workbench that will return to me the sums of records I moved to a particular status considering only the latest timestamp for a particular record. I also need to do this without a sub query (or nested select).
Given the below table, I want to know that user with id 1 moved two records to status with id 2. I need to not include in my counts if the same record was moved to two different status ids, but only count the latest status id.
Table
user_id
acted_on_record_id
moved_to_status_id
timestamp
1
1234
2
2022-01-01 19:39:37
1
1234
3
2022-01-02 19:39:37
1
1234
2
2022-01-03 19:39:37
1
5678
2
2022-01-03 19:39:37
Here is the query I have so far:
SELECT t1.user_id, t1.acted_on_record_id,
SUM(DISTINCT IF(t1.moved_to_status_id = 3, 1, 0)) AS pending,
SUM(DISTINCT IF(t1.moved_to_status_id = 2, 1, 0)) AS open,
MAX(t1.timestamp) as timestamp
FROM table1 t1
GROUP BY t1.user_id, t1.acted_on_record_id
This is the result I want:
user_id
acted_on_record_id
pending
open
timestamp
1
1234
0
1
2022-01-03 19:39:37
1
5678
0
1
2022-01-03 19:39:37
However, my query gives me this result:
user_id
acted_on_record_id
pending
open
timestamp
1
1234
1
1
2022-01-03 19:39:37
1
5678
0
1
2022-01-03 19:39:37
It shows a 1 in both pending and 1 in open columns because the SUM IF aggregates are not mutually exclusive or distinct on the acted_on_record_id. Is there a way to have these two aggregates know about each other and only sum the one with the greater timestamp without using a sub query (nested select)?
I eventually figured it out by expanding on a solution here: Retrieving the last record in each group - MySQL
I used a LEFT JOIN to compare the table against itself. This query returned in 1.095 seconds where my prior solution (not posted) using a subquery returned in 15.268 seconds.
SELECT t1.user_id, t1.acted_on_record_id,
SUM(IF(t1.moved_to_status_id = 3, 1, 0)) AS pending,
SUM(IF(t1.moved_to_status_id = 2, 1, 0)) AS open
MAX(t1.timestamp) as timestamp
FROM table1 t1 LEFT JOIN table1 t2
ON (t1.acted_on_record_id = t2.acted_on_record_id AND t1.user_id = t2.user_id AND t1.id < t2.id)
WHERE t2.user_id IS NULL
group by t1.user_id, t1.acted_on_record_id, t1.moved_to_status_id
How to get those entries which have more than 1 records?
If it doesn't make sense... let me explain:
From the below table I want to access the sum of the commission of all rows where type is joining and "they have more than 1 entry with same downmem_id".
I have this query but it doesn't consider more entries scenario...
$search = "SELECT sum(commission) as income FROM `$database`.`$memcom` where type='joining'";
Here's the table:
id mem_id commission downmem_id type time
2 1 3250 2 joining 2019-09-22 13:24:40
3 45 500 2 egbvegr new time
4 32 20 2 vnsjkdv other time
5 23 2222 2 vfdvfvf some other time
6 43 42 3 joining time
7 32 353 5 joining time
8 54 35 5 vsdvsdd time
Here's the expected result: it should be the sum of the id no 2, 7 only
ie. 3250+353=whatever.
It shouldn't include id no 6 because it has only 1 row with the same downmem_id.
Please help me to make this query.
Another approach is two levels of aggregation:
select sum(t.commission) income
from (select sum(case when type = 'joining' then commission end) as commission
from t
group by downmem_id
having count(*) > 1
) t;
The main advantage to this approach is that this more readily supports more complex conditions on the other members of each group -- such as at most one "joining" record or both "joining" records and no more than two "vnsjkdv" records.
Use EXISTS:
select sum(t.commission) income
from tablename t
where t.type = 'joining'
and exists (
select 1 from tablename
where id <> t.id and downmem_id = t.downmem_id
)
See the demo.
Results:
| income |
| ----- |
| 3603 |
You can use subquery that will find all downmem_id having more than one occurrence in the table.
SELECT Sum(commission) AS income
FROM tablename
WHERE type = 'joining'
AND downmem_id IN (SELECT downmem_id
FROM tablename t
GROUP BY downmem_id
HAVING Count(id) > 1);
DEMO
I have a table with the columns : id, status, value.
id status value
-- ------ -----
1 10 100
2 10 100
3 10 60
4 11 20
5 11 15
6 12 100
7 12 50
8 12 50
I would like to get the id and value of the first and second highest valued rows, from each status group. My table should have the following columns:
status, id of the first highest value, first highest value, id of second highest value, second highest value.
I should get:
status 1stID 1stValue 2ndID 2ndValue
------ ----- -------- ----- --------
10 1/2 100 2/1 100
11 4 20 5 15
12 6 100 7/8 50
I tried all kinds of solutions, but I couldn't find a solution for same-value 1st s (two rows with the same value, which happened to be the highest in that status group) or same-value seconds.
For example, in case of two rows sharing the highest value in their status group, this not-so-elegant query will return two rows with the same status, different 1sts and same 2nd:
SELECT 2nds.status, 1sts.id AS "1stID",1sts.value AS "1stValue",
2nds.id AS "2ndID",2nds.value AS "2ndValue"
FROM
(SELECT v.* FROM
(SELECT status, MAX(value) AS "SecMaxValue" FROM table o
WHERE value < (SELECT MAX(value) FROM table
WHERE status = o.status
GROUP BY status) AS m
INNER JOIN table v
ON v.status = m.status AND v.value = m.SecMaxValue) AS 2nds
INNER JOIN
(SELECT v.* FROM
(SELECT status, MAX(value) AS maxValue FROM table
GROUP BY status) AS m
INNER JOIN table v
ON v.status = m.status AND v.value = m.MaxValue) AS 1sts
ON 1sts.status = 2nds.status ;
This query will give me:
status 1stID 1stValue 2ndID 2ndValue
------ ----- -------- ----- --------
10 1 100 3 60
10 2 100 3 60
11 4 20 5 15
12 6 100 7 50
12 6 100 8 50
To conclude, I would like to find a solution in which:
a. if there are two rows with the highest value the query puts the details one of them in the column of the 1st and the details of other in 2nd (no mather which)
b. if there are two rows with the second highst value it puts the highest in its place and one of the seconds in the second place.
Is there a way to change the query above? someone has a nicer solution?
I came across several 1st and 2nd queries but they had the same problem - for example this solution: Finding the highest n values of each group in MySQL. it does not deliver 1st and 2nd in the same row, but the main problem it provides only one of the firsts.
Thanks
After spent a lot of time, finally I found a solution for above problem. Please try it out:
select 1st.status as Status,
SUBSTRING_INDEX(1st.id,'/',1) as 1stID,
1st.value as 1stValue,
(case when locate('/',1st.id) > 0 then SUBSTRING_INDEX(1st.id,'/',-1)
else 2nd.id
end) as 2ndID,
(case when locate('/',1st.id) > 0 then 1st.value
else 2nd.value
end) as 2ndValue
from
(
(select status, SUBSTRING_INDEX(Group_concat(id separator '/'),'/',2) as id,value
from t1
where (status,value) in (select status,value
from t1
group by status
having max(value))
group by status) 1st
inner join
(select status,id,value
from t1
where (status,value) not in (select status,value
from t1
group by status
having max(value))
group by status,value
order by status,value desc) 2nd
on 1st.status = 2nd.status)
group by 1st.status;
Just replace t1 with your tablename and it should work like a charm.
Click here for Updated Demo
If you have any doubt(s), feel free to ask.
Hope it helps!
Having trouble wrapping my head around having an efficient "duplicate entries" select in a single query.
In the below example, duplicate StockNo can exist spanning multiple Date. I want to search StockNo for duplicate entries, and if at least 1 StockNo record is found within the Date current YEAR-MONTH, then I also need to select its partner that could exist in any other YEAR-MONTH. Is this possible?
Example Query:
SELECT * FROM `sales`
WHERE `StockNo` IN
(SELECT `StockNo` FROM `sales` GROUP BY `StockNo` HAVING COUNT(*) > 1)
AND `Date` LIKE '2016-11-%'
ORDER BY `StockNo`, `TransactionID`;
Example Data:
ID | StockNo | Date
1 | 1 | 2016-11-01
2 | 1 | 2016-11-10
3 | 2 | 2016-11-05
4 | 2 | 2016-10-29
5 | 3 | 2016-10-25
6 | 3 | 2016-10-15
With my example query and data, I have 3 pairs of duplicate entries. It's pretty obvious that I will only return 3 records (ID's 1, 2 & 3) due to AND Date LIKE '2016-11-%', however I need to return ID's 1, 2, 3, 4. I want to ignore ID's 5 & 6 because neither of them fall within the current month.
Hope that makes sense. Thanks for any help you can provide.
SELECT StockNo
FROM sales
GROUP BY StockNo
HAVING SUM(CASE WHEN DATE_FORMAT(Date, '%Y-%m') = '2016-11' THEN 1 ELSE 0 END) > 0
If you also want to retrieve the full records for those matching stock numbers in the above query, you can just add a join:
SELECT s1.*
FROM sales s1
INNER JOIN
(
SELECT StockNo
FROM sales
GROUP BY StockNo
HAVING SUM(CASE WHEN DATE_FORMAT(Date, '%Y-%m') = '2016-11' THEN 1 ELSE 0 END) > 0
) s2
ON s1.StockNo = s2.StockNo
Demo here:
SQLFiddle
Thank you very much Tim for pointing me in the right direction. Your answer was close but it still only returned records from the current month and in the end I used the following query:
SELECT s1.* FROM `sales` s1
INNER JOIN
(
SELECT * FROM `sales` GROUP BY `StockNo` HAVING COUNT(`StockNo`) > 1
AND SUM(CASE WHEN DATE_FORMAT(`Date`, '%Y-%m')='2016-11' THEN 1 ELSE 0 END) > 0
) s2
ON s1.StockNo=s2.StockNo
This one had been eluding me for some time.
Is there a way to select only the rows that have an other result than the row previous selected?
In one of my tables I store advertisement data, that’s one row per advertisement. I also store in an other table the prices for rental per dag, week, month, this table contain more than one row per advertisement.
I want to select al the rows from table 2 where there is a change in one of the prices (in the example row 1 and 3 in table 2) in the same query as the data selection. I know that I have to use a GROUP_CONCAT to get one row instead of a 2 row result in this case, but how to get 2 result rows from table 2 and 1 result row in total?
The outcome of the query has to be something like: tre,234,” 12345678911,12,45, 32555678911,12,67 ”
Table 1 (advertisements)
ID_adv data1 data2
1 tre 234
2 ghj 34
3 jk 098
4 jfjk 12
Table 2 (dates)
ID_dates ID_adv timestamp_day price1 price2
1 1 12345678911 12 45
2 1 22345677771 12 45
3 1 32555678911 12 67
4 2 42345671231 34 34
I tried
SELECT
t1.*,
GROUP_CONCAT(t2.date) AS dates
FROM Table1 t1
LEFT JOIN Table2 t2 ON t2.ID_adv = t1.ID_adv
WHERE t1.ID_adv = 3 GROUP BY t1.ID_adv
Can you try this one:
SELECT T3.ID_adv
, T3.data1
, T3.data2
, CAST(GROUP_CONCAT(CONCAT(T3.timestamp_day, ',', T3.price1, ',', T3.price2)) AS CHAR) AS DatePrice
FROM (
SELECT T1.*
, MIN(T2.timestamp_day) AS timestamp_day
, T2.price1
, T2.price2
FROM Table1 T1
LEFT JOIN Table2 T2 ON T2.ID_adv = T1.ID_adv
GROUP BY T1.ID_adv, T2.price1, T2.price2
) T3
GROUP BY T3.ID_adv;
I've tried it on SQL Fiddle.