Create 2 columns based on value in existing column - mysql

I have the following table. I would like to add 2 new columns with a select query that will show the total based on the flag type.
Table:
tt | company | count | flag
--------------------------------------------
123 | adeco | 5 | 1
123 | mic | 4 | 2
333 | manpower | 88 | 2
444 | linar | 2 | 2
555 | dlank | 3 | 1
Desired:
tt | company | total | flag | total_flag1 | total_flag2
-------------------------------------------------------------------
123 | adeco | 5 | 1 | 5 | 0
123 | mic | 4 | 2 | 0 | 4
333 | manpower | 88 | 2 | 0 | 88
444 | linar | 2 | 2 | 0 | 2
555 | dlank | 3 | 1 | 3 | 0

By your desired result, you should use case when or if syntax to to this:
select
yourtable.*,
case when flag = 1 then `count` else 0 end as total_flag1,
case when flag = 2 then `count` else 0 end as total_flag2
from yourtable
Or
select
yourtable.*,
if(flag = 1, `count`, 0) as total_flag1,
if(flag = 2, `count`, 0) as total_flag2
from yourtable

I think you can do what you want using correlated subqueries or join:
select t.*, tsum.total_flag1, tsum.total_flag2
from t join
(select t.tt,
sum(case when flag = 1 then total else 0 end) as total_flag1,
sum(case when flag = 2 then total else 0 end) as total_flag2
from t
group by t.tt
) tsum
on t.tt = tsum.tt;

Related

More efficient way to include empty rows than LEFT JOIN on same table in mysql?

Consider the following table:
____________________________________
| my_table |
| ID | val_1 | val_2 | val_3 | val_4 |
| 1 | 1 | 1 | 2 | 0 |
| 2 | 1 | 2 | 8 | 1 |
| 3 | 1 | 2 | 9 | 1 |
| 4 | 1 | 2 | 10 | 1 |
| 5 | 1 | 3 | 6 | 1 |
| 6 | 1 | 4 | 8 | 1 |
| 7 | 2 | 1 | 14 | 1 |
| 8 | 2 | 2 | 1 | 0 |
| 8 | 2 | 2 | 8 | 1 |
| 8 | 2 | 3 | 2 | 0 |
------------------------------------
I need sum(val_3) for every combination of val_1,val_2 where val_4=1, or 0 if there is no val_4=1 for the given val_1,val_2
This query gets the proper sum grouping, but does not include a 0 value for empty combinations:
select val_1,val_2,sum(val_3) from my_table where val_4 = 1 group by val_1,val_2
I can get the proper result with a combination of LEFT JOIN on the same table and IFNULL(<condition>,0)
The first query averages .22 seconds on my dataset. The LEFT JOIN / IFNULL query averages 0.98 seconds. Is there a query structure that will include the 0 values and perform closer to the 0.22s time of the first query? My script may run this query a few thousand times per call.
This is actually a subquery to an outer query that calculates the stdev_samp() of val_3, so I do need to include 0 values.
We can do conditional aggregation without using any Derived Tables (subquery), or Left Join.
Query
SELECT
val_1,
val_2,
SUM(CASE WHEN val_4 = 1 THEN val_3
ELSE 0
END) AS sum
FROM my_table
GROUP BY val_1, val_2;
Result
| val_1 | val_2 | sum |
| ----- | ----- | --- |
| 1 | 1 | 0 |
| 1 | 2 | 27 |
| 1 | 3 | 6 |
| 1 | 4 | 8 |
| 2 | 1 | 14 |
| 2 | 2 | 8 |
| 2 | 3 | 0 |
View on DB Fiddle
Without your obvious data to test and compare to, I offer the following. The inner query goes through the list ONCE and does a sum as both the val_4 = 1 AND when = 0 as respective individual column results with the grouping. Once this is done, all combinations are already resolved with the group by. So now the outer query gets the Val 1 & 2, but a case-when for the final. If there is a value in the SumWhen1 > 0, we know there WAS a value with a 1. If no value, then it returns the sumWhen0 results.
SELECT
pq.Val_1,
pq.Val_2,
CASE when PQ.SumWhen1 > 0 then SumWhen1 else SumWhen0 end FinalSum
from
( select
val_1,
val_2,
sum( CASE when val_4 = 1 then val_2 else 0 end ) SumWhen1,
sum( CASE when val_4 = 0 then val_2 else 0 end ) SumWhen0,
from
my_table
where
val_4 = 1
group by
val_1,
val_2 ) PQ
Now, if your data can contain negatives, I would just add to the inner query the following and use this column as the basis to confirm a val_4 had any records.
sum( CASE when val_4 = 1 then 1 else 0 end ) Val1Records,

How to sub total column value's in pivot table (MySQL) with group by clause

I have a pivot table with the respective fields.
district
block
MaleCount
FemaleCount
above are a column name. So, basically, I have grouped all those fileds by district and block-wise. For that, I have written the following query in MySQL.
select
ds.ds_name as district,
bs.bl_name as block,
case
when sd.gender_id = 1 then count(gender_id)
else 0
end as MaleCount,
case
when sd.gender_id = then count(gender_id)
else 0
end as FemalCount,
from studet data sd
inner join district ds on ds.district_id = sd.ds_id
inner join block bs on bs.block_id = sd.bs_id
group by bs.block_name, ds.district_name;
which gives the following result
district | block | FemalCount | MaleCount
xyz | abc | 4 | 5
| cvz | 5 | 9
ytz | tyz | 7 | 3
| awe |3 |8
The main thing is, I want the following kind of the result set.
district | block | FemalCount | MaleCount
xyz | abc | 4 | 5
| cvz | 5 | 9
total | | 9 | 14
ytz | tyz | 7 | 3
| awe | 3 | 8
total | | 10 | 11
which includes a subtotal row for Malecount and FemaleCount with a group by block and district as mention above. How could I achieve this in MySQL query?
You could add a union all (with high values for the block name to push it to the end on the order by) to calculate the totals by district so given
+----------+-----------+-------+-------+
| sudentid | gender_id | ds_id | bs_id |
+----------+-----------+-------+-------+
| 101 | 2 | 3 | 2 |
| 103 | 2 | 3 | 2 |
| 112 | 1 | 3 | 3 |
| 116 | 1 | 3 | 3 |
| 117 | 1 | 3 | 3 |
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 1 |
| 3 | 1 | 1 | 1 |
+----------+-----------+-------+-------+
8 rows in set (0.00 sec)
DROP TABLE IF EXISTS DISTRICT,BLOCK;
CREATE TABLE DISTRICT (ID INT, name varchar(10));
create table block(id int, name varchar(10));
insert into district values (1,'aaa'),(2,'bbb'),(3,'ccc'),(4,'ddd');
insert into block values (1,'E'),(2,'F'),(3,'G');
select
case when block = 'zzz' then 'Total'
else district
end as district,
case when block = 'zzz' then ''
else block
end as block,
malecount,femalecount
from
(
select
ds.name as district,
bs.name as block,
sum(case when gender_id = 1 then 1 else 0 end) Malecount,
sum(case when gender_id = 2 then 1 else 0 end) Femalecount
from student sd
inner join district ds on ds.id = sd.ds_id
inner join block bs on bs.id = sd.bs_id
group by ds.name, bs.name
union all
select district,'zzz', malecount,femalecount
from
(
select ds.name as district,
sum(case when gender_id = 1 then 1 else 0 end) Malecount,
sum(case when gender_id = 2 then 1 else 0 end) Femalecount
from student sd
inner join district ds on ds.id = sd.ds_id
group by ds.name
) s
) t
order by t.district,t.block
;
Result
+----------+-------+-----------+-------------+
| district | block | malecount | femalecount |
+----------+-------+-----------+-------------+
| aaa | E | 3 | 0 |
| Total | | 3 | 0 |
| ccc | F | 0 | 2 |
| ccc | G | 3 | 0 |
| Total | | 3 | 2 |
+----------+-------+-----------+-------------+
5 rows in set (0.00 sec)
Note the slightly less verbose conditional aggregation (sum(case when...)

subtract multiple dates within one id

I'm trying to accomplish something in mySQL, which is difficult due to the lack of a "lag" function and because of all the examples I've read regarding date differences, since in all those examples there's always one ID to one date. In here, I am trying to do the date differences within an ID, and when the date difference is over 60, then returning an indicator of 1, else 0.
Not sure what the best way is to go about this. Would it be some combination of using row_number() with the date? The snag is doing this within multiple IDs, as a lot of things I read don't cover that. Any direction would be helpful.
Thanks!
ID | Service Date | Date Difference | Indicator
1 | 1/22/2016 | 0 | 1
1 | 3/26/2016 | 64 | 1
1 | 5/25/2016 | 60 | 0
1 | 9/15/2016 | 113 | 1
2 | 8/1/2016 | 0 | 1
3 | 1/26/2016 | 0 | 1
3 | 3/9/2016 | 43 | 0
3 | 4/30/2016 | 52 | 0
4 | 8/9/2016 | 0 | 1
5 | 11/19/2016 | 0 | 1
6 | 10/14/2016 | 0 | 1
7 | 1/31/2016 | 0 | 1
7 | 8/11/2016 | 193 | 1
You can use variables, but this is tricky. For this to work reliably, all the variables need to be assigned in a single expression:
select t.*, datediff(prev_date, date) as diff,
(case when datediff(prev_date, date) < 60 then 0 else 1 end) as indicator
from (select t.*,
(case when #id = id
then (case when (#prev := #d) = NULL then 'never' -- intentional
when (#d := date) = NULL then 'never' -- intentional
else #prev
end)
when (#d := date) = NULL then 'never' -- intentional
else NULL
end) as prev_date
from t cross join
(select #id := -1, #d := '') params
order by id, date
) t
create view id_and_date as
select id, service_date from your table;
create view id_and_date_and_prior as
select
a.id, a.service_date,
coalesce(
(select max(b.service_date) from id_and_date b
where b.id = a.id and b.service_date < a.service_date),
a.service_date)
as prior_date
from id_and_date a
select a.id, a.service_date, a.prior_date
date_diff(a.service_date, a.prior_date) as diff,
case when date_diff(a.service_date, a.prior_date) > 60
then 1 else 0 end
as indicator
from id_and_date_and_prior a
Posting to simplify and correct the function calls from the answer provided by #tpdi. Please accept/upvote their answer, as this was pretty much copied from it.
Changes:
date_diff to DATEDIFF
removed create view calls in favor of subquery of t
assigned variable to the diff value
indicator of 0 on initial value as 0 diff to 1
replaced when case in favor of IF
SELECT
c.id,
c.service_date,
#diff := DATEDIFF(c.service_date, c.prior_date) AS diff,
IF(#diff = 0 || #diff > 60, 1, 0) AS indicator
FROM (
SELECT
a.id,
a.service_date,
COALESCE(
(SELECT MAX(b.service_date)
FROM t AS b
WHERE b.id = a.id
AND b.service_date < a.service_date),
a.service_date
) AS prior_date
FROM t AS a
) AS c;
Will result in:
| id | service_date | diff | indicator |
| 1 | 2016-01-22 | 0 | 1 |
| 1 | 2016-03-26 | 64 | 1 |
| 1 | 2016-05-25 | 60 | 0 |
| 1 | 2016-09-15 | 113 | 1 |
| 2 | 2016-08-01 | 0 | 1 |
| 3 | 2016-01-26 | 0 | 1 |
| 3 | 2016-03-09 | 43 | 0 |
| 3 | 2016-04-30 | 52 | 0 |
| 4 | 2016-08-09 | 0 | 1 |
| 5 | 2016-11-19 | 0 | 1 |
| 6 | 2016-10-14 | 0 | 1 |
| 7 | 2016-01-31 | 0 | 1 |
| 7 | 2016-08-11 | 193 | 1 |

Update innerquery result

I have a query and a result as follows.
In the database NULL and 0 represent the same meaning.
Now I want a counter based on Null+0 or 1
Eg:in the following example I want the result like this:
IsVirtual Category counter
NULL+0 3 343+8 = (351 is Total)
Query
select * from
(
Select IsVirtual, Category, count(*) as counter
from [Hardware]
group by IsVirtual, Category
) innercat
Output
+-----------+----------+---------+
| IsVirtual | Category | counter |
+-----------+----------+---------+
| NULL | 3 | 343 |
| 0 | 3 | 8 |
| 1 | 2 | 1 |
| 0 | 1 | 1 |
| NULL | 6 | 119 |
| 0 | 4 | 1 |
| NULL | 1 | 70 |
| 0 | 5 | 9 |
| NULL | 4 | 54 |
| 0 | 2 | 2 |
| NULL | 5 | 41 |
| NULL | 2 | 112 |
| 1 | 1 | 5 |
+-----------+----------+---------+
I think you want this :
SELECT COALESCE(IsVirtual, 0) as [IsVirtual],
Category,
Count(*) as [Counter]
FROM yourtable
GROUP BY COALESCE(IsVirtual, 0),Category
This will give you expected result without using subquery.
try with this
select * from (
Select CASE ISNULL(IsVirtual,0)
WHEN 0 Then 'NULL + 0'
ELSE IsVirtual
END AS IsVirtual, Category, count(*) as counter from [Hardware] group by ISNULL(IsVirtual,0), Category
)innercat
You can also do the same thing by using MAX function
This might help you.
SELECT
max(IsVirtual) as IsVirtual,
Category,
Count(*) as Counter
FROM
yourtable
GROUP BY
Category

MySQL - Count rows in main table with some condition and equals specific values

I have two table and first named table1:
ID | Name | Type | isActive | isDeleted |
-----------------------------------------------
1 | item 1 | 4 | 1 | 0 |
2 | item 2 | 2 | 1 | 0 |
3 | item 3 | 1 | 1 | 1 |
4 | item 4 | 1 | 1 | 0 |
5 | item 5 | 1 | 1 | 0 |
6 | item 6 | 3 | 1 | 0 |
7 | item 7 | 1 | 1 | 0 |
8 | item 8 | 2 | 1 | 0 |
9 | item 9 | 1 | 1 | 0 |
10 | item 10 | 1 | 1 | 0 |
AND second named table1_meta:
ID | table1_id | options | value
------------------------------------
1 | 1 | dont_ask | 1
2 | 2 | dont_ask | 1
3 | 5 | dont_ask | 1
4 | 6 | dont_ask | 1
5 | 8 | alwasys_ask| 1
6 | 9 | alwasys_ask| 1
7 | 1 | is_flagged | 1
8 | 2 | is_flagged | 0
9 | 3 | is_flagged | 0
10 | 4 | is_flagged | 0
11 | 5 | is_flagged | 0
12 | 6 | is_flagged | 1
13 | 7 | is_flagged | 0
14 | 8 | is_flagged | 0
15 | 9 | is_flagged | 0
16 | 10 | is_flagged | 0
I'm trying to count rows in table1 where certain specific criteria is met, some of these conditionals.
The WHERE condition must contain these criteria:
table1.type = 1 and table1.isActive = 1 and table1.isDeleted = 0 and table1_meta.options = 'is_flagged' and table1_meta.value = 0
and this:
table1_meta.options = 'dont_ask' and table1_meta.value = 1
and this:
table1_meta.options = 'always_ask' and table1_meta.value = 1
so, how can I do that?
SQLFiddle link: http://sqlfiddle.com/#!2/2eb27b
Thanks.
I assume you are trying to count rows in the first table. Here is one way using subqueries:
select count(*)
from table1 t1
where t1.type = 1 and t1.isActive = 1 and t1.IsDeleted = 0 and
exists (select 1
from table1_meta tm
where t1.id = tm.table1_id and tm.options = 'is_flagged' and tm.value = 0
) and
not exists (select 1
from table1_meta tm
where t1.id = tm.table1_id and
tm.options = 'dont_ask' and tm.value = 1
) and
exists (select 1
from table1_meta tm
where t1.id = tm.table1_id and
tm.options = 'always_ask' and tm.value = 1
);
This has a separate subquery for each condition on the meta table.
I think I found the answer of my question.
the query is:
SELECT Count(*) AS total FROM
(SELECT Count(*)
FROM table1 t1,
table1_meta t1meta
WHERE t1.type = 1
AND t1.isactive = 1
AND t1.isdeleted = 0
AND t1meta.options = 'is_flagged'
AND t1meta.value = 0
AND t1.id NOT IN (SELECT table1_id
FROM table1_meta tm
WHERE tm.options = 'dont_ask'
AND tm.value = 1)
UNION
SELECT Count(*)
FROM table1_meta tm,
table1 t1
WHERE t1.id = tm.table1_id
AND tm.options = 'always_ask'
AND tm.value = 1) x
Thanks anyway, Gordon.