SQL Query to create a view with different grouping logic - mysql

I have a table similar to below
Insurance ID
Created By
Closed By
1
User A
User A
2
User A
User C
3
User B
User C
4
User B
User C
5
User B
User C
From this table, I am trying to create a View as below
UserName
Total Created
Total Closed
User A
2
1
User B
3
0
User C
0
4
I am not able to figure out how to group the table to achieve this view. Any help would be greatly appreciated

Here's one option:
Sample data (you have it already, so you don't type that):
SQL> with test (insurance_id, created_by, closed_by) as
2 (select 1, 'user a', 'user a' from dual union all
3 select 2, 'user a', 'user c' from dual union all
4 select 3, 'user b', 'user c' from dual union all
5 select 4, 'user b', 'user c' from dual union all
6 select 5, 'user b', 'user c' from dual
7 ),
Query begins here:
8 all_users as
9 (select created_by username from test
10 union
11 select closed_by from test
12 )
13 select u.username,
14 sum(case when t.created_by = u.username then 1 else 0 end) total_created,
15 sum(case when t.closed_by = u.username then 1 else 0 end) total_closed
16 from all_users u cross join test t
17 group by u.username
18 order by u.username;
USERNA TOTAL_CREATED TOTAL_CLOSED
------ ------------- ------------
user a 2 1
user b 3 0
user c 0 4
SQL>

I would be inclined to have a separate Users table and only have an integer of UserId in the main table. CROSS APPLY should avoid reading the same table twice.
SELECT X.UserName
,SUM(CASE WHEN X.Activity = 'Created' THEN 1 ELSE 0 END) AS TotalCreated
,SUM(CASE WHEN X.Activity = 'Closed' THEN 1 ELSE 0 END) AS TotalClosed
FROM YourTable T
CROSS APPLY
(
VALUES (T.CreatedBy, 'Created')
,(T.ClosedBy, 'Closed')
) X (UserName, Activity)
GROUP BY X.UserName
ORDER BY UserName;

create table sometable (user_id, created_by,closed_by)
as
select 1, 'user a', 'user a' from dual union all
select 2, 'user a', 'user c' from dual union all
select 3, 'user b', 'user c' from dual union all
select 4, 'user b', 'user c' from dual union all
select 5, 'user b', 'user c' from dual;
SELECT *
FROM sometable
UNPIVOT ( username
FOR col IN ( created_by
, closed_by
)
)
PIVOT ( COUNT (user_id)
FOR col IN ( 'CREATED_BY' AS total_created
, 'CLOSED_BY' AS total_closed
)
)
ORDER BY username
;
USERNAME TOTAL_CREATED TOTAL_CLOSED
user a 2 1
user b 3 0
user c 0 4

Related

Php randomized with an % chance

I need something where I really dont know how to make it.
Here is an example:
user 1 = 3
user 2 = 1
user 3 = 6
What I want with this, is that user 1, has 30% chance, user 2 has 10% and user 3 60%. But those numbers can also be 0.01 instead of 6. What now want, is that it gets randomised but also have the % of chances. I dont really know how to explain it. That it then draws an number with 60% chance for user 3, 10 chance for user 2 and 30% chance for user 1. But, this last can be as long as possible. How to do this? Sorry, I am really bad in explaining.
Thanks!
What you want can be described as a "weighted probability distribution" or to be technical a "discrete distribution" and a "Categorical distribution":
A categorical distribution is a discrete probability distribution that describes the possible results of a random variable that can take on one of K possible elementary events, with the probability of each elementary event separately specified.
-- Wikipedia
Given you have a random variable with uniform distribution in the range 0 to 1 you can build any distribution you want by using the Inverse Method.
Your first step is to normalize the distribution. That means to make sure that the area of below the curve equals one (that is, that your weight do not sum more than 100%). For a discrete distribution, it means to make sure that sum of the weights is equal to one. [Which is the same as taking the values as a vector and calculate the unit vector in the same direction], just take the each value and divide it by the sum of the values.
Therefore, you go from this:
(original)
user 1 = 3
user 2 = 1
user 3 = 6
To this:
sum = 3 + 1 + 6 = 10
(normalized)
user 1 = 3 / 10 = 0.3
user 2 = 1 / 10 = 0.1
user 3 = 6 / 10 = 0.6
Next, get the cumulative distribution. That is, for each value you do not want its (normalized) weight but the weight of it plus all previous ones.
Therefore, you go from this
(normalized)
user 1 = 0.3
user 2 = 0.1
user 3 = 0.6
To this:
(cumulative)
user 1 = 0.3
user 2 = 0.1 + 0.3 = 0.4
user 3 = 0.6 + 0.3 + 0.1 = 1
Finally, you get your random variable with uniform distribution in the range 0 to 1 and check below which value it falls:
$r = (float)rand()/(float)getrandmax();
if ($r <= 0.3) return "user 1"; // user 1 = 0.3
else if ($r <= 0.4) return "user 2"; // user 2 = 0.4
else return "user 3"; // user 3 = 1
Note: The range is inclusive because PHP is weird.
Ok, all in one go, in (ugly) PHP:
$p = ['user 1' => 3, 'user 2' => 1, 'user 3' => 6];
$s = array_sum($p);
$n = array_map(function($i) use ($p, $s){return $i/$s;},$p);
$a = []; $t = 0;
foreach($n as $k => $i) {$t += $i; $a[$k] = $t;}
$r = (float)rand()/(float)getrandmax();
foreach($a as $k => $i) { if ($r <= $i) return $k; }
Try online.
Let us reimplement in MySQL because reasons.
First we need a table with the input, for example:
SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`
Then we sum the values
SELECT sum(chance) FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input
Then we normalize
SELECT id, chance / sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s
Then we cumulate
SELECT id, chance / sum as sum, (#tmp := #tmp + chance / sum) as csum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s CROSS JOIN (SELECT #tmp := 0) cheat
Then we pick
SELECT id from (
SELECT id, chance / sum as sum, (#tmp := #tmp + chance / sum) as csum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s CROSS JOIN (SELECT #tmp := 0) cheat) a
CROSS JOIN (SELECT RAND() as r) random
WHERE csum > r
LIMIT 1
Try online.
Edit: This answer is based on the title "PHP...." not the tag "mysql"
Each user has n tickets. In your case you have 10 tickets and therefore need a random number between 0 and 9. An unelegant solution could be:
<?php
$tickets = array();
$number_of_tickets = 0;
foreach($users as $user) {
for($i = 0; i < $user->tickets; $i++) {
$tickets[] = $user->id;
$tickets++;
}
}
$lucky_draw = rand(0, $number_of_tickets);
$winner = tickets[$lucky_draw] //ID of the user
print("And the winner is...." . $winner);

Distinct on row level

It seemed so easy.
I am getting following table by using COALESCE. I need to perform distinct on row level.
1 1 5 5 5 (null)
2 2 2 2 25 25
3 7 35 35 35 35
That's what I am looking for.
1 5 null
2 25
3 7 35
Here's a Demo on http://sqlfiddle.com/#!3/e945b/5/0
This is the only way I can think of doing it.
Do not currently have enough time to explain its operation, so please post questions in comments;
WITH DataCTE (RowID, a, b, c, d, e, f) AS
(
SELECT 1, 1, 1, 5, 5, 5, NULL UNION ALL
SELECT 2, 2, 2, 2, 2, 25, 25 UNION ALL
SELECT 3, 3, 7, 35, 35, 35, 35
)
,UnPivotted AS
(
SELECT DC.RowID
,CA.Distinctcol
,OrdinalCol = ROW_NUMBER() OVER (PARTITION BY DC.RowID ORDER BY CA.Distinctcol)
FROM DataCTE DC
CROSS
APPLY (
SELECT Distinctcol
FROM
(
SELECT Distinctcol = a UNION
SELECT b UNION
SELECT c UNION
SELECT d UNION
SELECT e UNION
SELECT f
)DT
WHERE Distinctcol IS NOT NULL
) CA(Distinctcol)
)
SELECT RowID
,Col1 = MAX(CASE WHEN OrdinalCol = 1 THEN Distinctcol ELSE NULL END)
,Col2 = MAX(CASE WHEN OrdinalCol = 2 THEN Distinctcol ELSE NULL END)
,Col3 = MAX(CASE WHEN OrdinalCol = 3 THEN Distinctcol ELSE NULL END)
,Col4 = MAX(CASE WHEN OrdinalCol = 4 THEN Distinctcol ELSE NULL END)
,Col5 = MAX(CASE WHEN OrdinalCol = 5 THEN Distinctcol ELSE NULL END)
FROM UnPivotted
GROUP BY RowID

In MySQL how to rewrite a query using a case statement?

I have a MySQL table:
create table tbl (
amount int
);
insert into tbl (amount) values (1);
insert into tbl (amount) values (2);
insert into tbl (amount) values (3);
insert into tbl (amount) values (4);
My goal is a report of how many values are in the following buckets, by using a case statment.
Bucket A: values 0-1
Bucket B: values 2-5
Bucket C: values 6-9
First lets try a simple query:
select "Bucket A" as Bucket, count(amount) "Count"
from tbl
where amount in (0,1)
union
select "Bucket B" as Bucket, count(amount) "Count"
from tbl
where amount in (2,3,4,5)
union
select "Bucket C" as Bucket, count(amount) "Count"
from tbl
where amount in (6,7,8,9);
Result:
+----------+-------+
| Bucket | Count |
+----------+-------+
| Bucket A | 1 |
| Bucket B | 3 |
| Bucket C | 0 |
+----------+-------+
Results are perfect, but I want a case statement.
So I try this:
select
sum(case when amount in (0,1) then 1 else 0 end) as "Bucket A",
sum(case when amount in (2,3,4,5) then 1 else 0 end) as "Bucket B",
sum(case when amount in (6,7,8,9) then 1 else 0 end) as "Bucket C"
from tbl;
Result:
+----------+----------+----------+
| Bucket A | Bucket B | Bucket C |
+----------+----------+----------+
| 1 | 3 | 0 |
+----------+----------+----------+
Values are correct, and great that I have a case statement, but problem is the values got pivoted.
How can I
1. use a case statement
2. have no pivot?
You can do this using aggregation:
select (case when amount in (0, 1) then 'Bucket A'
when amount in (2, 3,4, 5) then 'Bucket B'
when amount in (6, 7, 8, 9) then 'Bucket C'
end) as bucket, count(*) as `count`
from tbl
where amount in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
group by (case when amount in (0, 1) then 'Bucket A'
when amount in (2,3,4,5) then 'Bucket B'
when amount in (6,7,8,9) then 'Bucket C'
end);
EDIT:
Digital Chris makes a very good point. This can be solved by using left outer join:
select (case when tbl.amount in (0, 1) then 'Bucket A'
when tbl.amount in (2, 3,4, 5) then 'Bucket B'
when tbl.amount in (6, 7, 8, 9) then 'Bucket C'
end) as bucket, count(tbl.amount) as `count`
from (select 0 as amount union all
select 2 as amount union all
select 6 as amount
) throwaway left outer join
tbl
on throwaway.amount = tbl.amount
where tbl.amount in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
group by (case when tbl.amount in (0, 1) then 'Bucket A'
when tbl.amount in (2,3,4,5) then 'Bucket B'
when tbl.amount in (6,7,8,9) then 'Bucket C'
end);
Or, perhaps more clearly, by using the original query as a subquery:
select buckets.bucket, coalesce(`count`, 0) as `count`
from (select 'Bucket A' as bucket union all
select 'Bucket B' union all
select 'Bucket C'
) buckets left outer join
(select (case when amount in (0, 1) then 'Bucket A'
when amount in (2, 3,4, 5) then 'Bucket B'
when amount in (6, 7, 8, 9) then 'Bucket C'
end) as bucket, count(*) as `count`
from tbl
where amount in (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
group by (case when amount in (0, 1) then 'Bucket A'
when amount in (2,3,4,5) then 'Bucket B'
when amount in (6,7,8,9) then 'Bucket C'
end)
) g
on buckets.bucket = g.bucket;
select
"Bucket A" as "Bucket", sum(case when amount in (0,1) then 1 else 0 end) as "Count" from tbl
UNION
select "Bucket B", sum(case when amount in (2,3,4,5) then 1 else 0 end) from tbl
UNION
select "Bucket C", sum(case when amount in (6,7,8,9) then 1 else 0 end) from tbl;
Like this? sqlfiddle
SELECT "Bucket A" AS Bucket ,
(SELECT SUM(CASE WHEN amount IN (0,1) THEN 1 ELSE 0 END) FROM tbl) AS "COUNT"
UNION
SELECT "Bucket B" AS Bucket ,
(SELECT SUM(CASE WHEN amount IN (2,3,4,5) THEN 1 ELSE 0 END) FROM tbl) AS "COUNT"
UNION
SELECT "Bucket C" AS Bucket ,
(SELECT SUM(CASE WHEN amount IN (6,7,8,9) THEN 1 ELSE 0 END) FROM tbl) AS "COUNT"
sqlfiddle demo
Use a manufactured list of bucket names, then left join to the table:
select concat('Bucket ', b) bucket, count(amount) count
from (select 'A' as b union select 'B' union select 'C') a
left join tbl on b =
case when amount in (0, 1) then 'A'
when amount in (2,3,4,5) then 'B'
when amount in (6,7,8,9) then 'C' end
group by 1
This will produce a row with a zero count when no rows for the bucket are found.
See SQLFiddle

MySQL Count frequency of records

Table:
laterecords
-----------
studentid - varchar
latetime - datetime
reason - varchar
students
--------
studentid - varchar -- Primary
class - varchar
I would like to do a query to show the following:
Sample Report
Class No of Students late 1 times 2 times 3 times 4 times 5 & more
Class A 3 1 0 2 0 0
Class B 1 0 1 0 0 0
My query below can show the first column results:
SELECT count(Distinct studentid), class FROM laterecords, students
WHERE students.studenid=laterecords.studentid AND
GROUP BY class
I can only think of getting the results for each column and store them into php arrays. Then echo them to table in HTML.
Is there any better SQL way to do the above? How to do up the mysql query ?
Try this:
SELECT
a.class,
COUNT(b.studentid) AS 'No of Students late',
SUM(b.onetime) AS '1 times',
SUM(b.twotime) AS '2 times',
SUM(b.threetime) AS '3 times',
SUM(b.fourtime) AS '4 times',
SUM(b.fiveormore) AS '5 & more'
FROM
students a
LEFT JOIN
(
SELECT
aa.studentid,
IF(COUNT(*) = 1, 1, 0) AS onetime,
IF(COUNT(*) = 2, 1, 0) AS twotime,
IF(COUNT(*) = 3, 1, 0) AS threetime,
IF(COUNT(*) = 4, 1, 0) AS fourtime,
IF(COUNT(*) >= 5, 1, 0) AS fiveormore
FROM
students aa
INNER JOIN
laterecords bb ON aa.studentid = bb.studentid
GROUP BY
aa.studentid
) b ON a.studentid = b.studentid
GROUP BY
a.class
How about :
SELECT numlates, `class`, count(numlates)
FROM
(SELECT count(laterecords.studentid) AS numlates, `class`, laterecords.studentid
FROM laterecords,
students
WHERE students.studentid=laterecords.studentid
GROUP BY laterecords.studentid, `class`) aliastbl
GROUP BY `class`, numlates

Sql Server 2008 Select From table with AND style conditions in related tables

Given a model like this
ProductFacets contains the following data:
ProductId, FacetTypeId
1, 1
1, 2
2, 1
2, 3
3, 4
3, 5
4, 1
4, 2
I'd like to be able to select all Products which have a FacetTypeId of 1 AND 2.
The result set should contain ProductIds 1 and 4
This will return rows for products that have only facet types 1 and 2, and only those facets.
SELECT ProductId,
COUNT(*) AS FacetCountByProduct,
SUM(CASE WHEN FacetTypeId in (1, 2) THEN 1 ELSE 0 END) AS FacetCountSelectedFacets
FROM ProductFacets
GROUP BY ProductId
HAVING COUNT(*) = 2
and SUM(CASE WHEN FacetTypeId in (1, 2) THEN 1 ELSE 0 END) = 2
;
SELECT * FROM Product PROD WHERE PROD.ProductId IN(
SELECT P.ProductId pId FROM ProductFacets AS P
WHERE P.FacetTypeId = 1
AND EXISTS
(
SELECT *
FROM ProductFacets AS P1
WHERE P1.FacetTypeid = 2
AND P1.ProductId = pId
)
AND NOT EXISTS
(
SELECT *
FROM ProductFacets AS P2
WHERE P2.FacetTypeid NOT IN (1,2)
AND P2.ProductId = pId
)
)
There must be a better way to solve this, but it's the only one i can come up with
Just thought of a way to do this:
select distinct ProductId from ProductFacets
where ProductId in (select ProductId from ProductFacets where FacetTypeId = 1)
and ProductId in (select ProductId from ProductFacets where FacetTypeId = 2)