I'm having trouble trying to calculate a running total from within a CASE statement.
I have two tables #report and #question and two variables #countCurrent and #countSuggested.
Based off the numbers in the #report table compared against either a static value or a value from the #question table I need to increment either #countCurrent or #countSuggested.
Here is what I have so far but instead of getting some combination of 5 in either column, I am only getting 0/1. I think it is part of the JOIN but I can't see what.
declare #MinSuccessRate float,
#countCurrent int,
#countSuggested int
declare #report table
(
intID int identity(1,1),
intReportID int,
intParticipantID int,
acceptable float,
optimum float
)
insert #report
select 1,1,.25,.75 union all
select 1,2,.45,.75 union all
select 1,3,.35,.75 union all
select 1,4,.55,.75 union all
select 1,5,.65,.75
declare #question table
(
intID int identity(1,1),
intParticipantID int,
answer float
)
insert #question
select 1,35 union all
select 1,55 union all
select 1,65 union all
select 1,75 union all
select 1,85
SET #MinSuccessRate=0.75
SET #countCurrent=0
SET #countSuggested=0
UPDATE #report
SET #countCurrent=
CASE WHEN acceptable>=#MinSuccessRate
THEN #countCurrent+1
ELSE 0
END,
#countSuggested=
CASE WHEN optimum*100 >=q.answer
THEN #countSuggested+1
ELSE 0
END
FROM #report pr
INNER JOIN #question q
ON pr.intParticipantID=q.intParticipantID
WHERE pr.intReportID=1
select #countCurrent [Current],#countSuggested [Suggested]
Thanks in advance!
In a multiple table UPDATE, each target record can be updated at most once (regardless of how many times is it returned by the join).
However, you don't need UPDATE here at all:
SELECT #countCurrent =
SUM
(
CASE
WHEN acceptable >= #MinSuccessRate
THEN
1
ELSE
0
END
),
#countSuggested =
SUM
(
CASE
WHEN optimum * 100 >= q.answer
THEN
1
ELSE
0
END
)
FROM #report pr
JOIN #question q
ON q.intParticipantID = pr.intParticipantID
WHERE pr.intReportID = 1
You may check this http://www.1keydata.com/sql/sql-running-totals.html
Related
This is obviously wrong, but what would be the correct way to average the SUM of 3 columns and exclude the 0's?
SELECT (
AVG(NULLIF(`dices`.`Die1`,0)) +
AVG(NULLIF(`dices`.`Die2`,0)) +
AVG(NULLIF(`dices`.`Die3`,0))
) /3 as avgAllDice
FROM (
SELECT `Die1`,`Die2`,`Die3` FROM `GameLog`
WHERE PlayerId = "12345"
) dices
Thanks.
If I was keeping the inline view query (it's not clear why it's needed). I'd probably do something like this:
SELECT AVG( NULLIF( CASE d.i
WHEN 1 THEN dices.`Die1`
WHEN 2 THEN dices.`Die2`
WHEN 3 THEN dices.`Die3`
END
,0)
) AS `avgAllDice`
FROM ( SELECT gl.`Die1`
, gl.`Die2`
, gl.`Die3`
FROM `GameLog` gl
WHERE gl.playerId = '12345'
) dices
CROSS
JOIN ( SELECT 1 AS i UNION ALL SELECT 2 UNION ALL SELECT 3 ) d
The trick is the cross join operation, giving me three rows for each row returned from dices, and an expression that picks out values of Die1, Die2 and Die3 on each of three rows, respectively.
To exclude values of 0, we replace 0 with with NULL (since AVG doesn't include NULL values.)
Now with all of the non-zero DieN values stacked into a single column, we can just use the AVG function.
Another way to do it would be to get the numerator and denominator for each of Die1, Die2, Die3.... and then total up the numerators, total up the denominators, and then divide the total numerator by the total denominator.
This will should give an equivalent result.
SELECT ( IFNULL(t.n_die1,0) + IFNULL(t.n_die2,0) + IFNULL(t.n_die3,0) )
/ ( t.d_die1 + t.d_die2 + t.d_die3 )
AS avgAllDice
FROM ( SELECT SUM( NULLIF(gl.die1,0)) AS n_die1
, COUNT(NULLIF(gl.die1,0)) AS d_die1
, SUM( NULLIF(gl.die2,0)) AS n_die2
, COUNT(NULLIF(gl.die2,0)) AS d_die2
, SUM( NULLIF(gl.die3,0)) AS n_die3
, COUNT(NULLIF(gl.die3,0)) AS d_die3
FROM `GameLog` gl
WHERE gl.playerid = '12345'
) t
(I didn't work out what gets returned in the edge and corner cases... no matching rows in GameLog, all values of Die1, Die2 and Die3 are zero, etc., for either query. The results might be slightly different, returning a zero instead of NULL, divide by zero edge case, etc.)
FOLLOWUP
I ran a quick test of both queries.
CREATE DATABASE d20170228 ;
USE d20170228 ;
CREATE TABLE GameLog
( playerid VARCHAR(5) DEFAULT '12345'
, die1 TINYINT
, die2 TINYINT
, die3 TINYINT
);
INSERT INTO GameLog (die1,die2,die3)
VALUES (3,0,0),(2,1,0),(4,3,3),(3,3,3),(0,0,0),(4,4,4),(5,4,0),(0,0,2)
;
SELECT (3+2+1+4+3+3+3+3+3+4+4+4+5+4+2)/15 AS manual_avg
manual_avg is coming out 3.2.
Both queries are also returning 3.2
If you want to eliminate zeroes and NULLs, you can simply SELECT from the filtered master set multiple times, doing a UNION ALL on the results, then averaging against that.
SELECT AVG(`allDice`.`DieResult`)
FROM (
SELECT `Die1` AS `DieResult` FROM `GameLog` WHERE COALESCE(`Die1`, 0) <> 0 AND PlayerId = '12345'
UNION ALL
SELECT `Die2` FROM `GameLog` WHERE COALESCE(`Die2`, 0) <> 0 AND PlayerId = '12345'
UNION ALL
SELECT `Die3` FROM `GameLog` WHERE COALESCE(`Die3`, 0) <> 0 AND PlayerId = '12345'
) AS `allDice`
There's no need to overthink this one, it's not too difficult a problem
I need to find the most occurrences in a 10yr age range that can be Age 2 to 22, 15 to 25, 10 to 20, etc. in a table with name & age
I've created the SQL that returns the average age:
SELECT age, count(age)
FROM member
GROUP BY age
ORDER BY COUNT(age) DESC
LIMIT 1
Thanks for your help!
Create another table ages to hold the age ranges you are interested in with a field for age_lower, age_upper and a display name age_range such as '2 to 22'
Join the tables with a WHERE clause that puts the age between the lower and upper ranges.
SELECT `age_range`, COUNT(`age`) AS age_count
FROM `member` INNER JOIN `ages`
ON age BETWEEN age_lower AND age_upper
GROUP BY age_range
ORDER BY COUNT(`age`) DESC, `age_range` ASC
SQL Fiddle
This might solve the problem. The only thing I added was a table to hold values 1..x where x is your bucket count. The #T can easily be replaced with your MySQL table name. The results are all possible sets the age falls in, for each age. Then count of how many equal sets.
--IGNORE BUILDING TEST DATA IN SQL SERVER
DECLARE #T TABLE(member INT,age INT)
DECLARE #X INT
SET #X=1
WHILE(#X<=100) BEGIN
INSERT INTO #T SELECT #X, CAST(RAND() * 100 AS INT)
SET #X=#X+1
END
DECLARE #MinAge INT=1
DECLARE #MaxAge INT=100
--YOUR SET TABLE. TO MAKE LIFE EASY YOU NEED A TABLE OF 1..X
DECLARE #SET TABLE (Value INT)
DECLARE #SET_COUNT INT =10
DECLARE #LOOP INT=1
WHILE(#LOOP<=#SET_COUNT) BEGIN
INSERT #SET SELECT #LOOP
SET #LOOP=#LOOP+1
END
SELECT
MinAge,
MaxAge,
SetCount=COUNT(CountFlag)
FROM
(
SELECT
MinAge=AgeMinusSetCount,
MaxAge=AgePlusSetCount,
CountFlag=1
FROM
(
SELECT DISTINCT
ThisAge,
AgeMinusSetCount=(AgeMinusSetCount-1) + Value,
AgePlusSetCount=CASE WHEN (AgeMinusSetCount-1) + Value + #SET_COUNT > #MaxAge THEN #MaxAge ELSE (AgeMinusSetCount-1) + Value + #SET_COUNT END
FROM
(
SELECT
ThisAge=age,
AgeMinusSetCount=CASE WHEN (age - #SET_COUNT) < #MinAge THEN #MinAge ELSE (age) - #SET_COUNT END
FROM
#T
)RANGES
LEFT OUTER JOIN (SELECT Value FROM #SET) AS FanLeft ON 1=1
)AS DETAIL
)AS Summary
GROUP BY
MinAge,
MaxAge
ORDER BY
COUNT(CountFlag) DESC
Is there a way to define an identity column on another column? What I want to accomplish is a table that holds positions of an order and these orders can be put there anytime. So it could be that there are already lets say three positions in the table and it would look somewhat like this:
OrderNumber | OrderPosition
10001 1
10001 2
10001 3
And now I want to add another position without calculating the right value for the OrderPosition column. This is because I want to write new positions for multiple orders into the table and would like to avoid cursoring over the individual orders. I would prefer a solution wher OrderPosition is an identity column that is reseeded based on the OrderNumber column. So that If i add an order position for a new order it would start with 1 and if I add another position for order 10001 it would continue with 4.
Write a Scalar Function that returns the MAX(OrderPosition) based on OrderNumber. Then reference that function in the insert statement of orders
your requirement will not work for identity column.
You need to create custom logic to get from the normal columns and on combination based new no will generate.. like (read comments, only choose one logic)
declare #t table(OrderNumber int, OrderPosition int)
insert into #t values (10001, 1),(10001, 2),(10001, 3),(10001, 4)
select * from #t
--now insert new record with old orderno
declare #seq int = 1
declare #ordernumberNew int = 10001
--Eigher you can use :- insert to more understand
if( exists(select orderposition from #t where OrderNumber = #ordernumberNew ))
begin
set #seq = (select max(OrderPosition) + 1 from #t where OrderNumber = #ordernumberNew )
end
insert into #t values (#ordernumberNew , #seq )
select * from #t
--or another twist of above statement, insert directly as
insert into #t
values
(
#ordernumberNew,
case when exists (select orderposition from #t where OrderNumber = #ordernumberNew )
then (select max(OrderPosition) + 1 from #t where OrderNumber = #ordernumberNew )
else 1 end
)
select * from #t
--Now enter the not exist order no
set #ordernumberNew = 10006
insert into #t
values
(
#ordernumberNew,
case when exists (select orderposition from #t where OrderNumber = #ordernumberNew )
then (select max(OrderPosition) + 1 from #t where OrderNumber = #ordernumberNew )
else 1 end
)
select * from #t
I want to remove all the non numeric characters from the column. I have bulk data in my database.
Currently I am using method as describe in below link:
http://venerableagents.wordpress.com/2011/01/29/mysql-numeric-functions/
The problem is that its taking too much time for preocessing.
For 1 million of row current logic takes 1 hour to process the data.
please help me..
Thank You,
Ronak
I assume you're doing something like:
update myTable set foo = NumericOnly(foo);
I don't know how much better you can do than that.
One thing that might help a bit, though. In that NumericOnly function, they're doing extra work. I'd remove the SET idx = LENGTH(val)+1; line, since all that will do is start checking the end of the string (the parts we've already checked) again. A string with 5 leading non-numerics would be checked, in full, 5 times.
Removing the line would leave:
DROP FUNCTION IF EXISTS NumericOnly;
CREATE FUNCTION NumericOnly (val VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE idx INT DEFAULT 0;
IF ISNULL(val) THEN RETURN NULL; END IF;
IF LENGTH(val) = 0 THEN RETURN ""; END IF;
SET idx = LENGTH(val);
WHILE idx > 0 DO
IF IsNumeric(SUBSTRING(val,idx,1)) = 0 THEN
SET val = REPLACE(val,SUBSTRING(val,idx,1),"");
END IF;
SET idx = idx - 1;
END WHILE;
RETURN val;
END;
Here's another spin on things...
DEMO: http://sqlfiddle.com/#!2/0c96e/21
First, create yourself a numbers table
CREATE TABLE numbers (
number int NOT NULL PRIMARY KEY
);
INSERT INTO numbers (number)
SELECT n0 + n1 + n2 + n3 + n4 + n5
FROM (SELECT 0 AS n0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) AS z0
CROSS
JOIN (SELECT 0 AS n1 UNION SELECT 4 UNION SELECT 8 UNION SELECT 12) AS z1
CROSS
JOIN (SELECT 0 AS n2 UNION SELECT 16 UNION SELECT 32 UNION SELECT 48) AS z2
CROSS
JOIN (SELECT 0 AS n3 UNION SELECT 64 UNION SELECT 128 UNION SELECT 192) AS z3
CROSS
JOIN (SELECT 0 AS n4 UNION SELECT 256 UNION SELECT 512 UNION SELECT 768) AS z4
CROSS
JOIN (SELECT 0 AS n5 UNION SELECT 1024 UNION SELECT 2048 UNION SELECT 3072) AS z5
ORDER
BY 1;
Here's some sample data to play with
CREATE TABLE your_table (
foo varchar(50)
);
INSERT INTO your_table (foo)
VALUES ('124nhasfonasf13')
, ('NONE')
, ('r937')
, ('o9o9')
, ('n444n4n455n')
, ('blah');
Then here's a query to give you just the numbers. Should be more efficient as it is SET based instead of iterative like your function example...
SELECT foo
, Group_Concat(c ORDER BY position SEPARATOR '')
FROM (
SELECT vals.foo
, numbers.number As position
, SubString(vals.foo, numbers.number, 1) As c
FROM (
SELECT foo
, Length(foo) As lngth
FROM your_table
WHERE foo REGEXP '[0-9]'
) As vals
INNER
JOIN numbers
ON numbers.number BETWEEN 1 AND vals.lngth
) As x
WHERE c REGEXP '[0-9]'
GROUP
BY foo
I have a column TokenId list in my SQL table "Tokendata".
The TokenId has values say 1,2,3,4,6,7,8,10,11
Here the above tokenIds are occupied. When I query this table, it should give me the next free tokenId, in this case it should be 5.
If TokenId has values say "1,2,3,4,5,6,7,8,10,11,13,20", the query should return me 9.
Can anyone help me with a SQL query for this?
Note: updated to account for cases where 1 is missing, as per the comment.
SELECT
t.TokenId + 1
FROM (
SELECT TokenId
FROM Tokendata
UNION ALL
SELECT 0
) t
LEFT JOIN Tokendata t2 ON t.TokenId = t2.TokenId - 1
WHERE t2.TokenId IS NULL
ORDER BY t.TokenId
LIMIT 1
Here is the answer to my question. But it may not be the optimistic solution. Optimized solutions are welcome
DECLARE #TempTable TABLE
(
TableID int PRIMARY KEY IDENTITY,
TokenId int
)
Insert Into #TempTable
Select tt.TokenId from XLBDataPoint tt where tt.TokenId= 1
DECLARE #RowCount INT
SET #RowCount = (SELECT COUNT(*) FROM #TempTable )
(SELECT
Top 1 t.TokenId + #RowCount
FROM TokenData t
Where t.TokenId+ #RowCount NOT in
(select t2.TokenId FROM TokenData t2)