Mysql count with case when statement - mysql

Consider:
SELECT(count(c.id),
case when(count(c.id) = 0)
then 'loser'
when(count(c.id) BETWEEN 1 AND 4)
then 'almostaloser'
when(count(c.id) >= 5)
then 'notaloser'
end as status,
...
When all is said and done, the query as a whole produces a set of results that look similar to this:
Count | status
--------|-------------
2 | almostaloser //total count is between 2 and 4
--------|-------------
0 | loser // loser because total count = 0
--------|-------------
3 | almostaloser //again, total count between 2 and 4
--------|-------------
What I would like to achieve:
a method to reatain the information from the above table, but add a third column that will give a total count of each status, something like
select count(c.id)
case when(count(c.id) = 0 )
then loser as status AND count how many of the total count does this apply to
results would look similar to:
Count | status |total_of each status |
--------|-------------|---------------------|
2 | almostaloser| 2 |
--------|-------------|---------------------|
0 | loser | 1 |
--------|-------------|---------------------|
3 | almostaloser| 2 |
--------|-------------|----------------------
I've been told this could be achieved using a derived table, but i've not yet been able to get them both, only one or the other.

This can be achieved with this query (you must place your original query as subquery in two places):
SELECT t1.*, t2.total_of_each_status
FROM (
-- put here your query --
) t1
INNER JOIN (
SELECT status, count(*) AS total_of_each_status
FROM (
-- put here your query --
) t2
GROUP BY status
) t2 ON t2.status = t1.status

Related

Count rows where value in row is also in previous row

I want to get a count where the contents of a value in one row is also in the previous row.
Row | Item1 | Item2 | Item 3 |
1 | Dog | Cat | Rat
2 | Bird | Cat | Horse
3 | Horse | Dog | Rat
4 | Bird | Cat | Horse
5 | Horse | Bird | Cat
Row 2 would increase the count of Cat because Cat is in row 1 and 2
Row 3 would increase the count of Horse because Horse is also in Row 2
Row 4 would increase the count of Horse because Horse is also in Row 3
Row 5 would increase the count of Horse AND Cat because both of those appear in row 4.
There can be a max of 100 items or SKU's and I can index on any or all fields. At any given time there's probably between 1000 and 2000 rows.
I can't even wrap my head around where to begin with this query other than "SELECT * FROM table WHERE"
First, create table with all available unique values of SKU:
CREATE TABLE results(
id VARCHAR(255) NOT NULL PRIMARY KEY
);
-- All fields should be listed here one-by-one.
INSERT IGNORE INTO results (select Item1 from example);
INSERT IGNORE INTO results (select Item2 from example);
INSERT IGNORE INTO results (select Item3 from example);
Previous row could be obtained by left join primary table again with itself, i.e. LEFT JOIN example AS previous ON previous.id + 1 = example.id.
After that we've to check that each unique result exists in example table within current row and in previous row and finally get this:
SELECT
r.*,
SUM(
CASE WHEN r.id IN (
prv.Item1, prv.Item2, prv.Item3 -- All fields should be listed here.
) THEN 1 ELSE 0 END
) AS total
FROM
results AS r
LEFT JOIN
example AS cur ON r.id IN (
cur.Item1, cur.Item2, cur.Item3 -- All fields should be listed here.
)
LEFT JOIN
example AS prv ON prv.id + 1 = cur.id
GROUP BY
r.id
ORDER BY
cur.id
;
See working example http://www.sqlfiddle.com/#!9/7ebd85/1/0
This can be done with window functions (available in MySQL 8.0).
An option is to unpivot the resultset, and then use lag() to check the previous record. Assuming that ids are always increasing by 1, you can do:
select
item,
sum(case when id = lag_id + 1 then 1 else 0 end) cnt_consecutive
from (
select
t.*,
lag(id) over(partition by item order by id) lag_id
from (
select id, item1 item from mytable
union all select id, item2 from mytable
union all select id, item3 from mytable
) t
) t
group by item
order by item
If you don't have an incremented column, you can generate one with dense_rank():
select
item,
sum(case when new_id = lag_new_id + 1 then 1 else 0 end) cnt_consecutive
from (
select
t.*,
lag(new_id) over(partition by item order by new_id) lag_new_id
from (
select
t.*,
dense_rank() over(order by id) new_id
from (
select id, item1 item from mytable
union all select id, item2 from mytable
union all select id, item3 from mytable
) t
) t
) t
group by item
order by item
In this DB Fiddle, both queries return:
item | cnt_consecutive
:---- | --------------:
Bird | 1
Cat | 2
Dog | 0
Horse | 3
Rat | 0
I see #frost-nzcr4 suggestion is very good and I was doing my own version quite similar to that yesterday. However, the approach I'm doing is a bit different because I didn't create a table specifically to store the unique value. Instead, I was doing similarly like #GMB UNION sub-query and it end up to be something like this :
SELECT B.row, A.allitem,
SUM(CASE WHEN A.allitem IN (C.Item1, C.Item2, C.Item3) THEN 1
ELSE 0 END) AS total
FROM
-- this sub-query will be dynamic and UNION will eliminate any duplicate
(SELECT item1 AS allitem FROM mytable UNION
SELECT item2 FROM mytable UNION
SELECT item3 FROM mytable) AS A
LEFT JOIN mytable AS B ON A.allitem IN (B.Item1, B.Item2, B.Item3)
LEFT JOIN mytable AS C ON C.row + 1 = B.row
GROUP BY A.allitem
ORDER BY B.row;
Fiddle here : https://www.db-fiddle.com/f/bUUEsaeyPpAMfR2bK1VpBb/2
As you can see this is exactly similar query to frost's suggestion with only minor modification. In the sub-query allitem value will be updated as long as there are new values inserted so you don't need to keep inserting new unique data into a separate table.
Also, this query would normally get this is incompatible with sql_mode=only_full_group_by error on MySQL v5.7 above unless you remove the sql_mode.

table or column not found in mysql join with selects and cases

So I have this bit of mysql that I'm trying to work out. My goal is to insert the count of a grouping into the primary records to tell me how many of each status is within the related table for the record, so the result might look like this:
| id | name | count1 | count2 |
------------------------------------
| 1 | primary 1 | 5 | 3 |
| 1 | primary 2 | 2 | 7 |
select * from primaryTable
left join (
select
case
when relationTable.relation_status_id = 1
then count(*)
END as count1,
case
when relationTable.relation_status_id = 2
then count(*)
END as count2
) relationTable
on relationTable.primary_id = primaryTable.id
I tried using a subquery to do it, which worked, but requires a select per count, which I'm trying to avoid.
Adding a group by to the subquery resulted in an error that more than one row was being returned.
In the subquery, rather than aggregate COUNT()s inside CASE, you may more easily use SUM() to add up the result of a boolean comparison (0 or 1) to return a result resembling a count.
SELECT
primaryTable.*,
count1,
count2
FROM
primaryTable
JOIN (
SELECT
primary_id,
-- Sum the results of a boolean comparison
SUM(relation_status_id = 1) AS count1,
SUM(relation_status_id = 2) AS count2
FROM relationTable
-- Group in the subquery
GROUP BY primary_id
-- Join the subquery to the main table by primary_id
) counts ON primaryTable.primary_id = counts.primary_id
Note that because MySQL treats the booleans the same as 0 or 1, the comparison relation_status_id = 1 returns 1 or 0. The syntax above isn't supported in every RDBMS. To be more portable, you would need to use a CASE inside SUM() to explicitly return an integer 1 or 0.
SUM(CASE WHEN relation_status_id = 1 THEN 1 ELSE 0 END) AS count1,
SUM(CASE WHEN relation_status_id = 2 THEN 1 ELSE 0 END) AS count2
Your original attempt has some syntax problems. Chiefly, it has no FROM clause, which is causing MySQL to think it should be treated as a scalar value and then complain that it returns more than one row.

Excluding results of nested SQL query

I have a table of winners vs losers (TABLE1) e.g.
+----+--------+-------+
| ID | Winner | Loser |
+----+--------+-------+
| 1 | 2 | 3 |
| 2 | 1 | 2 |
+----+--------+-------+
In the most recent game between Item 1 and Item 2, 1 won (ID 2). For this example, I'll refer to this as Current Winner and Current Loser.
I'm trying to build a query that works our inferences from past results.
e.g. if 2>3, and 1>2. Then I need to record a value for 1>3
The query I'm building would find multiple inferred losers against the current winner.
The ideal query would return an array of "losers", which I can loop through and record in the table as inferred results. In this case "3".
The table would be updated to:
+----+--------+-------+
| ID | Winner | Loser |
+----+--------+-------+
| 1 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 1 | 3 |
+----+--------+-------+
And if the query was run again, it would return nothing.
The process I have so far is:
Look up everything the Current Loser, has previously beaten (Previous losers to Current loser)
Check the table to see if any of the Previous Losers to Current Loser, has played the current winner, ever.
Any previous loser that has, should be removed
To get the list of things the Current Loser has beaten i use:
select * from TABLE1 where winner = 2
Then for the second bullet point, I've got two nested queries:
select * from TABLE1 where winner = 1 and loser = (select loser from rp_poss where winner = 2)
select * from TABLE1 where loser = 1 and winner = (select loser from rp_poss where winner = 2)
I really can't work out how to put these together, to remove the rows I don't want. Can somebody let me know what is best, and most efficient query for this for a example, a nested query, some kind of join? Pea brain is really struggling with this.
Thanks in advance
You can do it this way, by explicitly looking for certain records (a match between the two items) and counting to see if there are zero of them.
CURRENTLOSER and CURRENTWINNER are placeholders for variables or whatever.
select previous.loser
from table1 previous
where previous.winner=CURRENTLOSER and (
select count(*)
from table1 ancient
where (ancient.winner=CURRENTWINNER and ancient.loser=previous.loser) or
(ancient.loser=CURRENTWINNER and ancient.winner=previous.loser)
) = 0
Aliasing tables ("from table1 ancient") will help get the algorithm clear in your head.
This will get you one row for every person and competitor, and the last result with that competitor: (ie. if person 1 goes up against person 2 and loses, and then goes up against that person again and wins, this query will show person 1 with competitor 2 WIN, and person 2 with competitor 1 LOSE). It shows the LATEST result for each competitor, relative to the person.
http://sqlfiddle.com/#!2/823d3f/6/0
select x.person,
case when x.person <> t.winner then t.winner else t.loser end as competitor,
case when x.person = t.winner then 'WIN' else 'LOSE' end as result
from (select x.winner as person, max(y.id) as id
from (select winner from table1 union select loser from table1) x
join table1 y
on x.winner = y.winner
or x.winner = y.loser
group by x.winner) x
join table1 t
on x.person = t.winner
or x.person = t.loser
where x.id = t.id
The query below will insert inferred losers for the most recent match between 1 and 2 the first time it's run. The second time it won't insert any new rows.
Initially the not exists subquery had where id < current.id to remove previous losers, however, since inferred games are inserted with 'future' ids (i.e. 3 in your example), if you ran the query again, it would reinsert the rows, so I changed it to where id <> current.id, which means it will also exclude 'future' losers.
insert into mytable (winner, loser)
select current.winner, previous.loser
from (select id, winner, loser
from mytable where
(winner = 1 and loser = 2)
or (winner = 2 and loser = 1)
order by id desc limit 1) current
join mytable previous
on previous.winner = current.loser
and previous.id < current.id
where not exists (select 1 from mytable
where id <> current.id
and ((winner = current.winner and loser = previous.loser)
or (winner = previous.loser and loser = current.winner)))

Return NULL for missing values in an IN list

I have a table like this:
id | val
---------
1 | abc
2 | def
5 | xyz
6 | foo
8 | bar
and a query like
SELECT id, val FROM tab WHERE id IN (1,2,3,4,5)
which returns
id | val
---------
1 | abc
2 | def
5 | xyz
Is there a way to make it return NULLs on missing ids, that is
id | val
---------
1 | abc
2 | def
3 | NULL
4 | NULL
5 | xyz
I guess there should be a tricky LEFT JOIN with itself, but can't wrap my head around it.
EDIT: I see people are thinking I want to "fill the gaps" in a sequence, but actually what I want is to substitute NULL for the missing values from the IN list. For example, this
SELECT id, val FROM tab WHERE id IN (1,100,8,200)
should return
id | val
---------
1 | abc
100 | NULL
8 | bar
200 | NULL
Also, the order doesn't matter much.
EDIT2: Just adding a couple of related links:
How to select multiple rows filled with constants?
Is it possible to have a tableless select with multiple rows?
You could use this trick:
SELECT v.id, t.val
FROM
(SELECT 1 AS id
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5) v
LEFT JOIN tab t
ON v.id = t.id
Please see fiddle here.
Yes, you can. But that will be tricky since there are no sequences in MySQL.
I assume you want just any selection, so it's:
SELECT
*
FROM
(SELECT
(two_1.id + two_2.id + two_4.id +
two_8.id + two_16.id) AS id
FROM
(SELECT 0 AS id UNION ALL SELECT 1 AS id) AS two_1
CROSS JOIN (SELECT 0 id UNION ALL SELECT 2 id) AS two_2
CROSS JOIN (SELECT 0 id UNION ALL SELECT 4 id) AS two_4
CROSS JOIN (SELECT 0 id UNION ALL SELECT 8 id) AS two_8
CROSS JOIN (SELECT 0 id UNION ALL SELECT 16 id) AS two_16
) AS sequence
LEFT JOIN
t
ON sequence.id=t.id
WHERE
sequence.id IN (1,2,3,4,5);
(check the fiddle)
It will work as combination of powers of 2 to generate consecutive table of numbers. Your values are passed to WHERE clause, so you can substitute there any set of values.
I would recommend you to use application for this case - because it will be faster. It may have some sense if you want to use this row set somewhere else (i.e. in some other queries) - but if not, it's a work for your application.
If you'll need higher values, add more rows to sequence generator, like in this fiddle.

select rows based on next row in mysql

I have a table like this :
Type | Time
1 | 234234
2 | 234235
1 | 234238
3 | 234239
4 | 234240
1 | 234242
2 | 234245
I want to count number of all those rows where type=1 and next row's type=2.
For ex : The result here is 2.
I don't know how to put where clause on next row.
You should be able to implement user defined variables to get the total:
select count(*) Total
from
(
select type,
#row:=(case when #prev=1 and type=2 then 'Y' else 'N' end) as Seq,
#prev:=type
from yourtable, (SELECT #row:=null, #prev:=null) r
order by time, type
) src
where Seq = 'Y'
See SQL Fiddle with Demo