isnull values not in the table to 0 - mysql

TableA contains data from id's 55,66,68,70.
My Query:
select id, count(id) as total from TableA where id in (10,22,43,55,66) group by id
Output:
id total
55 2
66 5
Desired Output:
id total
10 0
22 0
43 0
55 2
66 5
Is there a way I can rewrite this query and have an isnull to 0 for the values in the IN statement that are not in the table? For example, 10,22, and 43 are not in tableA but are in the IN statement, so I want the total for those values to be 0
UPDATE: I forgot to mention the values from the IN statement are passed to the query as in array from the client application. So the values are constantly different so I cant hard code anything

You can't do this with in. You can do this with a left join:
select t.id, count(a.id)
from (select 10 as id union all select 22 union all select 43 union all
select 55 union all select 66
) t left join
tableA a
on a.id = t.id
group by t.id;

CREATE a Table Variable and then use that
DECLARE #Ids TABLE (id int, PRIMARY KEY(id))
INSERT #Ids(id) VALUES (10),(22),(43),(55),(66)
SELECT i.id, ISNULL(COUNT(id),0)
FROM #ids i
LEFT JOIN TableA ta ON ta.id = i.id
GROUP BY i.id

If your ID's are within a reasonable range then you can create a recursive call to build a table containing all possible id values and the IN clause will filter it down to only the values you want. Then join that result set with the original table to get your count:
WITH AllNumbers AS (
SELECT 0 AS x
UNION ALL
SELECT x+1 FROM AllNumbers WHERE x < 100
)
SELECT an.x, COUNT(id)
FROM
TableA ta
RIGHT JOIN AllNumbers an ON ta.id = an.x
WHERE an.x IN (10,22,43,55,66)
GROUP BY an.x
OPTION (maxrecursion 100)
You can adjust the '100' values to a higher number but it creates a call stack within SQL that will hit a limit at some point.
Alternatively, if you have an existing table that contains all possible ids then join to that instead of 'AllNumbers' and eliminate the recursion.
Otherwise there may be some regex options to pivot the set into rows and then use the same join statement above.

Related

MySQL max value in row

I am facing a problem with MySQL query which is a variant of "Id for row with max value". I am either getting error or incorrect result for all my trials.
Here is the table structure
Row_id
Group_id
Grp_col1
Grp_col2
Field_for_aggregate_func
Another_field_for_row
For all rows with a particular group_id, I want to group by fields Grp_col1, Grp_col2 then get max value of Field_for_aggregate_func and then corresponding value of Another_field_for_row.
Query I have tried is like below
SELECT c.*
FROM mytable as c left outer join mytable as c1
on (
c.group_id=c1.group_id and
c.Grp_col1 = c1.Grp_col1 and
c.Grp_col2 = c1.Grp_col2 and
c.Field_for_aggregate_func > c1.Field_for_aggregate_func
)
where c.group_id=2
Among alternative solutions for this problem I want a high performance solution as this will be used for large set of data.
EDIT: Here is the sample set of row and expected answer
Group_ID Grp_col1 Grp_col2 Field_for_aggregate_func Another_field_for_row
2 -- N 12/31/2015 35
2 -- N 1/31/2016 15 select 15 from group for max value 1/31/2016
2 -- Y 12/31/2015 5
2 -- Y 1/1/2016 15
2 -- Y 1/2/2016 25
2 -- Y 1/3/2016 30 select 30 from group for max value 1/3/2016
You can use a sub-query to find the maximums, then join that with the original table, along the lines of:
select m1.group_id, m1.grp_col1, m1.grp_col2, m1.another_field_for_row, max_value
from mytable m1, (
select group_id, grp_col1, grp_col2, max(field_for_aggregate_func) as max_value
from mytable
group by group_id, grp_col1, grp_col2) as m2
where m1.group_id=m2.group_id
and m1.grp_col1=m2.grp_col1
and m1.grp_col2=m2.grp_col2
and m1.field_for_aggregate_func=m2.max_value;
Watch out for when there is more than one max_value for the given grouping. You'll get multiple rows for that grouping. Fiddle here.
Try this.
See Fiddle demo here
http://sqlfiddle.com/#!9/9a3c26/8
Select t1.* from table1 t1 inner join
(
Select a.group_id,a.grp_col2,
A.Field_for_aggregate_func,
count(*) as rnum from table1 a
Inner join table1 b
On a.group_id=b.group_id
And a.grp_col2=b.grp_col2
And a.Field_for_aggregate_func
<=b.Field_for_aggregate_func
Group by a.group_id,
a.grp_col2,
a.Field_for_aggregate_func) t2
On t1.group_id=t2.group_id
And t1.grp_col2=t2.grp_col2
And t1.Field_for_aggregate_func
=t2.Field_for_aggregate_func
And t2.rnum=1
Here first I am assigning a rownumber in descending order based on date. The selecting all the records for that date.

MySQL - how to select query values which doesn't have a specific data

I'm having some troubles (thinking about it) doing a select from this table.
tb_details:
id status det_id
1 5 22
2 1 22
3 0 22
4 5 25
5 1 25
6 5 27
7 1 27
8 5 32
9 1 32
10 0 32
How can i make a select query to show just the det_id values which doesn't have a 0 in the table, maybe something like this:
det_id
25
27
One approach (out of several workable approaches) is to use an anti-join pattern.
SELECT t.det_id
FROM this_table t
LEFT
JOIN ( SELECT r.det_id
FROM this_table r
WHERE r.status = 0
GROUP BY r.det_id
) s
ON s.det_id = t.det_id
WHERE s.det_id IS NULL
GROUP BY t.det_id
Let's unpack that a bit.
The inline view (aliased as s) returns a distinct list of det_id values for which a status=0 row does exist in this_table.
The LEFT JOIN operation returns all det_id values from this_table t, along with the matching det_id from s. If a match is not found, the "left outerness" of the join means that all rows from t will be returned, whether a match is found or not.
The "trick" is the predicate in the WHERE clause, testing whether the value of the column returned from s is NULL or not. The predicate effectively excludes any rows from t which had a matching row found in s.
So, all that remains to return is rows from t that didn't have a match in s.
We add a GROUP BY t.det_id (or we could add the DISTINCT keyword), to return a list of distinct det_id values.
This isn't the only approach. You could also use a NOT EXISTS predicate ...
SELECT t.det_id
FROM this_table t
WHERE NOT EXISTS
( SELECT 1
FROM this_table r
WHERE r.det_id = t.det_id
AND r.status = 0
)
GROUP BY t.det_id
(This differs slightly, in that a row with a NULL value for det_id could be returned, where the previous query would not return it. That first query could be tweaked to make it return the same result as this.)
You could also use a NOT IN, taking care that the subquery does not return any NULL values for det_id.
SELECT t.det_id
FROM this_table t
WHERE t.det_id NOT IN
( SELECT r.det_id
FROM this_table r
WHERE r.status = 0
AND r.det_id IS NOT NULL
GROUP BY r.det_id
)
GROUP BY t.det_id
There are several statements what will return the specified resultset.
Another and simpler way of doing this is:-
SELECT DISTINCT det_id FROM TB
WHERE det_id NOT IN
(SELECT det_id RFOM TB WHERE status = 0);
If you want to only have the det_id where no other colum is 0 you should write
SELECT det_id FROM TABLE WHERE
Col1 <> 0
AND
Col2 <> 0
and so on...
If you want only 1 result per type add
GROUP BY det_id
at the end of the query.

Force MySQL to return a result for all option within IN

I have a simple MySQL statement:
SELECT q1, COUNT(q1) FROM results WHERE q1 IN ('1','2','3');
Currently there are only results for 1 and 3 - results are:
1 = 6
3 = 7
But what I need is for MySQL to bring back a result for 1,2 and 3 even though 2 has no data, as this:
1 = 6
2 = 0
3 = 7
Any ideas?
This is tricky because no rows match your value (2), they cannot be counted.
I would solve this by creating a temp table containing the list of values I want counts for:
CREATE TEMPORARY TABLE q ( q1 INT PRIMARY KEY );
INSERT INTO q (q1) VALUES (1), (2), (3);
Then do an OUTER JOIN to your results table:
SELECT q.q1, COALESCE(COUNT(*), 0) AS count
FROM q LEFT OUTER JOIN results USING (q1)
GROUP BY q.q1;
This way each value will be part of the final result set, even if it has no matching rows.
Re comment from #Mike Christensen:
MySQL doesn't support CTE's, in spite of it being requested as far back as 2006: http://bugs.mysql.com/bug.php?id=16244
You could do the same thing with a derived table:
SELECT q.q1, COALESCE(COUNT(*), 0) AS count
FROM (SELECT 1 AS q1 UNION ALL SELECT 2 UNION ALL SELECT 3) AS q
LEFT OUTER JOIN results USING (q1)
GROUP BY q.q1;
But this creates a temp table anyway.
A SQL query doesn't really have a way to refer to the values in your IN clause. I think you'd have to break this down into one query for each value. Something like:
SELECT 1 as q1, COUNT(1) FROM results WHERE q1 = '1'
UNION ALL
SELECT 2 as q1, COUNT(1) FROM results WHERE q1 = '2'
UNION ALL
SELECT 3 as q1, COUNT(1) FROM results WHERE q1 = '3'
Fiddle
Note: If there are a lot of values in your IN clause, you might be better off to write your code in a way where missing values are assumed to have zero.
In general, you cannot query something that does not exists. So, you must create data for it. Use union to add those missing data values.
select q1, COUNT(*)
from results
where q1 in ('1','2','3')
group by q1
union
select q1, 0
from (
select '1' as q1
union
select '2'
union
select '3'
) as q
where q1 not in (
select q1
from results
)

select min value of range [0,44) not in a column

I have a table with an int valued column, which has values between 0 and 43 (both included).
I would like a query that returns the min value of the range [0,44) which is not in the table.
For example:
if the table contains: 3,5, 14. The query should return 0
if the table contains: 0,1, 14. The query should return 2
if the table contains: 0,3, 14. The query should return 1
If the table contains all values, the query should return empty.
How can I achieve that?
Since the value you want is either 0 or 1 greater than a value that exists in the table, you can just do;
SELECT MIN(value)
FROM (SELECT 0 value UNION SELECT value+1 FROM MyTable) a
WHERE value < 44 AND value NOT IN (SELECT value FROM MyTable)
An SQLfiddle to test with.
One way would be to create another table that contains the integers in [0,43] and then left join that and look for NULLs, the NULLs will tell you what values are missing.
Suppose you have:
create table numbers (n int not null);
and this table contains the integers from 0 to 43 (inclusive). If your table is t and has a column n which holds the numbers of interest, then:
select n.n
from numbers n left join t on n.n = t.n
where t.n is null
order by n.n
limit 1
should give you the result you're after.
This is a fairly common SQL technique when you're working with a sequence. The most common use is probably calendar tables.
One approach is to generate a set of 44 rows with integer values, and then perform an anti-join against the distinct set of values from the table, and the grab the mininum value.
SELECT MIN(r.val) AS min_val
FROM ( SELECT 0 AS val UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
-- ...
SELECT 44
) r
LEFT
JOIN ( SELECT t.int_valued_col
FROM mytable t
WHERE t.int_valued_col >= 0
AND t.int_valued_col <= 43
GROUP BY t.int_valued_col
) v
ON v.int_valued_col = r.col
WHERE v.int_valued_col IS NULL
A little bit hacky and MySQL-specific:
SELECT NULLIF(MAX(IF(val=#min, #min:=(val+1), #min)), #max) as min_empty
FROM (
SELECT DISTINCT val
FROM table1
-- WHERE val BETWEEN 0 AND 43
ORDER BY val) as vals, (SELECT #min:=0, #max:=44) as init;

Adding one extra row to the result of MySQL select query

I have a MySQL table like this
id Name count
1 ABC 1
2 CDF 3
3 FGH 4
using simply select query I get the values as
1 ABC 1
2 CDF 3
3 FGH 4
How I can get the result like this
1 ABC 1
2 CDF 3
3 FGH 4
4 NULL 0
You can see Last row. When Records are finished an extra row in this format
last_id+1, Null ,0 should be added. You can see above. Even I have no such row in my original table. There may be N rows not fixed 3,4
The answer is very simple
select (select max(id) from mytable)+1 as id, NULL as Name, 0 as count union all select id,Name,count from mytable;
This looks a little messy but it should work.
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM
(
SELECT 1 as ID
UNION
SELECT 2 as ID
UNION
SELECT 3 as ID
UNION
SELECT 4 as ID
) a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID IN (1,2,3,4)
UPDATE 1
You could simply generate a table that have 1 column preferably with name (ID) that has records maybe up 10,000 or more. Then you could simply join it with your table that has the original record. For Example, assuming that you have a table named DummyRecord with 1 column and has 10,000 rows on it
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM DummyRecord a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID >= 1 AND
a.ID <= 4
that's it. Or if you want to have from 10 to 100, then you could use this condition
...
WHERE a.ID >= 10 AND
a.ID <= 100
To clarify this is how one can append an extra row to the result set
select * from table union select 123 as id,'abc' as name
results
id | name
------------
*** | ***
*** | ***
123 | abc
Simply use mysql ROLLUP.
SELECT * FROM your_table
GROUP BY Name WITH ROLLUP;
select
x.id,
t.name,
ifnull(t.count, 0) as count
from
(SELECT 1 AS id
-- Part of the query below, you will need to generate dynamically,
-- just as you would otherwise need to generate 'in (1,2,3,4)'
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
) x
LEFT JOIN YourTable t
ON t.id = x.id
If the id does not exist in the table you're selecting from, you'll need to LEFT JOIN against a list of every id you want returned - this way, it will return the null values for ones that don't exist and the true values for those that do.
I would suggest creating a numbers table that is a single-columned table filled with numbers:
CREATE TABLE `numbers` (
id int(11) unsigned NOT NULL
);
And then inserting a large amount of numbers, starting at 1 and going up to what you think the highest id you'll ever see plus a thousand or so. Maybe go from 1 to 1000000 to be on the safe side. Regardless, you just need to make sure it's more-than-high enough to cover any possible id you'll run into.
After that, your query can look like:
SELECT n.id, a.*
FROM
`numbers` n
LEFT JOIN table t
ON t.id = n.id
WHERE n.id IN (1,2,3,4);
This solution will allow for a dynamically growing list of ids without the need for a sub-query with a list of unions; though, the other solutions provided will equally work for a small known list too (and could also be dynamically generated).