SQL : How to calculate limited rows and reset the counter - mysql

I am dealing with an issue and need some expert advice on to achieve the problem, my sql query generates output with two columns, 1st column displays id (for e.g. abc-123 in following table) and next column displays corresponding result to the id stored in db which is pass or fail.
I need to implement, when resolution is pass it should display success attempt, in following example, abc-123 failed 1st time however def-456 passed in next attempt thus success rate is 50%, now counter should reset and go to next row where there is pass thus it should show 100%, again when code hits pass counter resets then goes next and displays 33% bec there are two fail and one pass at the end, how it can be achieved in sql? (id and resolution are column names)
**date** **id resolution**
6/6/2012 abc-123 fail 50%
6/7/2012 abc-456 pass
6/8/2012 abc-789 pass 100%
6/9/2012 abc-799 fail 33%
6/10/2012 abc-800 fail
6/1/2012 abc-900 pass
Thanks

SELECT
*
FROM
table
INNER JOIN
(
SELECT
MIN(g.id) AS first_id,
MAX(g.id) AS last_id,
COUNT(*) AS group_size
FROM
table AS p
INNER JOIN
table AS g
ON g.id > COALESCE(
(SELECT MAX(id) FROM table WHERE id < p.id AND resolution = 'pass'),
''
)
AND g.id <= p.id
WHERE
p.resolution = 'pass'
GROUP BY
p.id
)
AS groups
ON table.id >= groups.first_id
AND table.id <= groups.last_id

There's more than one way to do it:
SELECT st.*,
#prev:=#counter + 1,
#counter:= CASE
WHEN st.resolution = 'pass'
THEN 0
ELSE #counter + 1
END c,
CASE WHEN #counter = 0
THEN CONCAT(FORMAT(100/#prev, 2), '%')
ELSE '-'
END res
FROM so_test st, (SELECT #counter:=0) sc
Here's proof of concept.

Related

Trying to use multiple AS, but I'm getting: Subquery returns more than 1 row

I'm using 3 tables from my database which I read data for my rank (top15) table. I'm trying to fill one 'tr' with only one query (using multiple Aliases), but I'm stuck here:
My last try was:
SELECT DISTINCT(mapname),
(SELECT his_time FROM primekz_records
WHERE primekz_records.id=$player_id AND his_aa = 10 AND tp > 0) AS nub10,
(SELECT his_time FROM primekz_records
WHERE primekz_records.id=$player_id AND his_aa = 10 AND tp = 0) AS pro10
FROM primekz_records
JOIN primekz_players ON primekz_records.id=primekz_players.id
JOIN primekz_maps ON primekz_maps.mid=primekz_records.mid
WHERE primekz_players.id=$player_id
Tables are structured:
primekz_players( id, steamid, name ...)
primekz_maps( mid, mapname )
primekz_records( id, mid, his_time, his_aa, tp, ... ) <-- this means one ID(player) can be max 4 times for one mid (map), variations are: his_aa (10/100), tp (0/more)
If I try with only one alias I get this result, which is totally wrong (see Noob100 column).
https://i.snag.gy/tHpUK8.jpg
Does it have something to do with ROW_NUMBER() + 4x AS ?

MySQL Ignoring Outliers

I have to present some data to work colleagues and i am having issues analysing it in MySQL.
I have 1 table called 'payments'. Each payment has columns for:
Client (our client e.g. a bank)
Amount_gbp (the GBP equivalent of the value of the transaction)
Currency
Origin_country
Client_type (individual or company)
I have written pretty simple queries like:
SELECT
AVG(amount_GBP),
COUNT(client) AS '#Of Results'
FROM payments
WHERE client_type = 'individual'
AND amount_gbp IS NOT NULL
AND currency = 'TRY'
AND country_origin = 'GB'
AND date_time BETWEEN '2017/1/1' AND '2017/9/1'
But what i really need to do is eliminate outliers from the average AND/OR only include results within a number of Standard Deviations from the Mean.
For example, ignore the top/bottom 10 results of 2% of results etc.
AND/OR ignore any results that fall outside of 2 STDEVs from the Mean
Can anyone help?
--- EDITED ANSWER -- TRY AND LET ME KNOW ---
Your best best is to create a TEMPORARY table with the avg and std_dev values and compare against them. Let me know if that is not feasible:
CREATE TEMPORARY TABLE payment_stats AS
SELECT
AVG(p.amount_gbp) as avg_gbp,
STDDEV(amount_gbp) as std_gbp,
(SELECT MIN(srt.amount_gbp) as max_gbp
FROM (SELECT amount_gbp
FROM payments
<... repeat where no p. ...>
ORDER BY amount_gbp DESC
LIMIT <top_numbers to ignore>
) srt
) max_g,
(SELECT MAX(srt.amount_gbp) as min_gbp
FROM (SELECT amount_gbp
FROM payments
<... repeat where no p. ...>
ORDER BY amount_gbp ASC
LIMIT <top_numbers to ignore>
) srt
) min_g
FROM payments
WHERE client_type = 'individual'
AND amount_gbp IS NOT NULL
AND currency = 'TRY'
AND country_origin = 'GB'
AND date_time BETWEEN '2017/1/1' AND '2017/9/1';
You can then compare against the temp table
SELECT
AVG(p.amount_gbp) as avg_gbp,
COUNT(p.client) AS '#Of Results'
FROM payments p
WHERE
p.amount_gbp >= (SELECT (avg_gbp - std_gbp*2)
FROM payment_stats)
AND p.amount_gbp <= (SELECT (avg_gbp + std_gbp*2)
FROM payment_stats)
AND p.amount_gbp > (SELECT min_g FROM payment_stats)
AND p.amount_gbp < (SELECT max_g FROM payment_stats)
AND p.client_type = 'individual'
AND p.amount_gbp IS NOT NULL
AND p.currency = 'TRY'
AND p.country_origin = 'GB'
AND p.date_time BETWEEN '2017/1/1' AND '2017/9/1';
-- Later on
DROP TEMPORARY TABLE payment_stats;
Notice I had to repeat the WHERE condition. Also change *2 to whatever <factor> to what you need!
Still Phew!
Each compare will check a different stat
Let me know if this is better

SELECT a column then INSERT that column again randomly

I've got a table:
player_id|player_name|play_with_id|play_with_name|
I made this table for a game.
Everyone who wants to play can sign up to it.
When they sign up the table stores player_id and player_name
When the period while they can sign up expires I want to assign every player_name to a play_with_name randomly.
So for example.. my structure would like this when they in sign up period:
player_id|player_name|play_with_id|play_with_name|
1 someone1
2 someone2
3 someone3
4 someone4
5 someone5
And this when the period expires:
player_id|player_name|play_with_id|play_with_name|
1 someone1 2 someone2
2 someone2 1 someone1
3 someone3 4 someone4
4 someone4 3 someone3
5 someone5 - -
I can't test this since I don't have a MySQL database handy and SQLFiddle seems to take forever to run anything, but this hopefully gets you there or at least close:
SET #row_num = 0;
SET #last_player_id = 0;
UPDATE P
SET
play_with_id =
CASE
WHEN P.player_id = SQ.player_id THEN SQ.last_player_id
ELSE player_id
END
FROM
Players P
LEFT OUTER JOIN
(
SELECT
#row_num := #row_num + 1 row_num,
#last_player_id last_player_id,
#last_player_id := player_id player_id
FROM
Players
WHERE
MOD(#row_num, 2) = 0
ORDER BY
RAND()
) SQ ON SQ.player_id = P.player_id OR SQ.last_player_id = P.player_id
The code (hopefully) sorts the players randomly then it pairs them based on that order. Every other player in the randomly sorted result is paired with the person right before them.
In MS SQL Server RAND() would only be evaluated once here and wouldn't end up affecting the ORDER BY, but I think that MySQL handles RAND() differently and generates a new value for each row in the result set.
I'm not sure why some client code isn't doing this as opposed to having this operation be done at the database level, but I suppose if you get the strategy for retrieving a randomized row set based on your DB from here, you could then write a stored procedure with a cursor or iterator to loop through the result set of something like:
select player_id, player_name from players order by RAND()
and then loop through the all the table rows to update the play_with_id and play_with_name, where the previously selected player_id <> play_with_id.

filed showing null value when joining table

below is my query
select C.cName,DATE_FORMAT(CT.dTransDate,'%d-%M-%Y') as dTransDate,
(c.nOpBalance+IFNULL(CT.nAmount,0)) AS DrAMount,IFNULL(CTR.nAmount,0) AS
CrAMount,((c.nOpBalance+IFNULL(CT.nAmount,0))-IFNULL(CTR.nAmount,0)) AS
Balance,CT.cTransRefType,CT.cRemarks,cinfo.cCompanyName,cinfo.caddress1,cinfo.cP
honeOffice,cinfo.cMobileNo,cinfo.cEmailID,cinfo.cWebsite from Customer
C LEFT JOIN Client_Transaction CT ON CT.nClientPk = C.nCustomerPk AND
CT.cTransRefType='PAYMENT' AND CT.cClientType='CUSTOMER' AND CT.dTransDate
between '' AND '' LEFT JOIN Client_Transaction CTR ON CTR.nClientPk =
C.nCustomerPk AND CTR.cTransRefType='RECEIPT' AND
CTR.cClientType='CUSTOMER' AND CTR.dTransDate between '2015-05-01' AND
'2015-05-29' LEFT JOIN companyinfo cinfo ON cinfo.cCompanyName like
'%Fal%' Where C.nCustomerPk = 4 Order By dTransDate
it's showing all value but dTransDate ,cTransRefType,cRemarks, showing null.
One obvious thing jumps out at us:
CT.dTransDate BETWEEN '' AND ''
^^ ^^
Another thing that jumps out at us is that there's a semi-Cartesian join between rows from CT and rows from CTR. If 5 rows are returned from CT for a given customer, and 5 rows are returned from CTR, that's going to produce a total of 5*5 = 25 rows. That just doesn't seem like a resultset that you'd really want returned.
Also, if more than one row is returned from cinfo, that's also going to cause another semi-Cartesian join. If there's two rows returned from cinfo, the total number or rows in the resultset will be doubled. It's valid to do that in SQL, but this is an unusual pattern.
The calculation of the balance is also very strange. For each row, the nAmount is added/subtracted from opening balance. On the next row, the same thing, on the original opening balance. There's nothing invalid SQL-wise with doing that, but the result being returned just seems bizarre. (It seems much more likely that you'd want to show a running balance, with each transaction.)
Another thing that jumps out at us is that you are ordering the rows by a string representation of a DATE, with the day as the leading portion. (As long as all the rows have date values in the same year and month, that will probably work, but it just seems bizarre that we wouldn't sort on the DATE value, or a canonical string representation.
I strongly suspect that you want to run a query that's more like this. (This doesn't do a "running balance" calculation. It does return the 'PAYMENT' and 'RECEIPT' rows as individual rows, without producing a semi-Cartesian result.
SELECT c.cName
, DATE_FORMAT(t.dTransDate,'%d-%M-%Y') AS dTransDate
, C.nOpBalance
, IF(t.cTransRefType='PAYMENT',IFNULL(t.nAmount,0),0) AS DrAMount
, IF(t.cTransRefType='RECEIPT',IFNULL(t.nAmount,0),0) AS CrAMount
, t.cTransRefType
, t.cRemarks
, ci.*
FROM Customer c
LEFT
JOIN Client_Transaction t
ON t.nClientPk = c.nCustomerPk
AND t.cClientType = 'CUSTOMER'
AND t.dTransDate >= '2015-05-01'
AND t.dTransDate <= '2015-05-29'
AND t.cTransRefType IN ('PAYMENT','RECEIPT')
CROSS
JOIN ( SELECT cinfo.cCompanyName
, cinfo.caddress1
, cinfo.cPhoneOffice
, cinfo.cMobileNo
, cinfo.cEmailID
, cinfo.cWebsite
FROM companyinfo cinfo
WHERE cinfo.cCompanyName LIKE '%Fal%'
ORDER BY cinfo.cCompanyName
LIMIT 1
) ci
WHERE c.nCustomerPk = 4
ORDER BY t.dTransDate, t.cTransRefTpye, t.id

Need help to make one mysql query to get expected result for my requirement

I am facing few issue to write mysql query in my scope to get result. Actually I am getting appropriate result using this existing query but it is not written appropriate way. Here is my query:
SELECT c.ID, c.chn_name,c.chn_logo,
(SELECT ID FROM tv_showtime WHERE showtime<='2013-02-18 10:28:35' AND status='Enable' AND chn_id=c.ID ORDER BY ID DESC Limit 0,1) as currentshowid,
(SELECT tv_showtime FROM tv_showtime WHERE showtime<='2013-02-18 10:28:35' AND status='Enable' AND chn_id=c.ID ORDER BY ID DESC Limit 0,1) as currentshowtime ,
(SELECT tv_showtime FROM tv_showtime WHERE showtime >'2013-02-18 10:28:35' AND status='Enable' AND chn_id=c.ID ORDER BY ID ASC Limit 0,1) as nextshowtime
FROM tv_channels AS c
WHERE c.status="Enable"
ORDER BY c.chn_name
LIMIT 0,10
Here, there are only two tables named as "tv_channels" and "tv_showtime". I need one record for each channel at a time ( for current time). So here suppose 12 channels and approx 30 (may vary foe each channel) records for each channel and I only need to display channels with current show (More clarification: only channels will be displayed which has current show time and/or next show time.)
Problem: I need more field values from "tv_showtime" to display other required values. And if I will use this way then I have to write more inner select query and it will slow down my website to load. So can you suggest or advise any other way to write this query please?
Database table detail:
tv_channels [ID, chn_name, [other required fields]],
tv_showtime [ID, chn_id, showtime, show_name, hits, last_ip [and few more fields]]
Please let me know if you will need further detail to get this question.
Any help or suggestion will be appreciated. thanks.
As another asked, but you didnt respond to an "end time" for each show, I had to go on the premise that the show time was when it started. That said, how do you determine which is the current show running for a given channel based on CURTIME() (instead of fixed time value).
Get each channel and the MAXIMUM SHOW Time that exists PRIOR TO the current time...
Likewise, how to get the NEXT Show? Get each channel with the MINIMUM SHOW time that STARTS AFTER the current time.
So, if I had the following records for 1 channels and the current time is 2:15pm
Channel ShowTime Show_Name
1 12:30pm Show "X"
1 01:00pm Show "B"
1 01:30pm Show "C"
1 02:00pm Show "D" <- Current Show
1 02:30pm Show "Y" <- Next Show
1 03:00pm Show "Z"
The current show running is the latest one PRIOR to 2:15 (Show "D" starting at 2pm)
and the NEXT Show is first AFTER current time (Show "Y" starting at 2:30pm). The above will work even if the rows are not in sequential order as I am using MIN() and MAX() respectively to get the time.
So, I start with the channel table and do a left-join to each separate pre-aggregate query for detecting the current show and next show times respectively and join on the channel ID which each COULD return at most one record --- provided there IS a record within qualified WHERE CURTIME() consideration.
From THAT, I am re-joining THOSE result sets back to the actual tv schedule table AGAIN, but this time, on the channel AND the time that matched the corresponding current or next time.
So now, I have everything lined up ready to go with respective aliases for content. Now, I just grab the columns I want to present.
Since the joins are all LEFT-JOINs, each side COULD have NULL values, so you might want to adjust the query to prevent nulls using COALESCE(), such as I've sampled...
SELECT
TC.ID,
TC.Chn_Name,
TC.Chn_Logo,
COALESCE( CurShowTimeDetail.ShowTime, 'no time' ) CurShowTime,
COALESCE( CurShowTimeDetail.Show_Name, '' ) CurShowName,
COALESCE( CurShowTimeDetail.Hits, 0 ) CurHits,
COALESCE( NextShowTimeDetail.ShowTime, 'no time' ) NextShowTime,
COALESCE( NextShowTimeDetail.Show_Name, '' ) NextShowName,
COALESCE( NextShowTimeDetail.Hits, 0 ) NextHits
from
TV_Channels TC
LEFT JOIN ( SELECT
ST.chn_id,
MAX( ST.showtime ) CurShowTime
from
tv_showtime ST
where
ST.ShowTime < CURTIME()
group by
ST.chn_id ) CurrentShow
ON TC.ID = CurrentShow.Chn_ID
LEFT JOIN tv_showtime CurShowTimeDetail
ON CurrentShow.Chn_ID = CurShowTimeDetail.Chn_ID
AND CurrentShow.CurShowTime = CurShowTimeDetail.ShowTime
LEFT JOIN ( SELECT
ST.chn_id,
MIN( ST.showtime ) NextShowTime
from
tv_showtime ST
where
ST.ShowTime > CURTIME()
group by
ST.chn_id ) NextShow
ON TC.ID = NextShow.Chn_ID
LEFT JOIN tv_showtime NextShowTimeDetail
ON NextShow.Chn_ID = NextShowTimeDetail.Chn_ID
AND NextShow.NextShowTime = NextShowTimeDetail.ShowTime
To select last (first) records from a table by some order, you may LEFT JOIN the table with itself as any next (previous) element, and add a condition that there is no such element.
SELECT c.ID, c.chn_name, c.chn_logo
, curr_sh.ID AS currentshowid, curr_sh.showtime AS currentshowtime -- Continue with desired columns
, next_sh.showtime AS nextshowtime -- Continue with desired columns
FROM tv_channels AS c
LEFT JOIN tv_showtime AS curr_sh
ON curr_sh.chn_id = c.ID
AND curr_sh.showtime <= '2013-02-18 10:28:35'
AND curr_sh.status='Enable'
LEFT JOIN tv_showtime AS curr_next_sh
ON curr_next_sh.chn_id = curr_sh.chn_id
AND curr_next_sh.showtime > curr_sh.showtime
AND curr_next_sh.showtime <= '2013-02-18 10:28:35'
AND curr_next_sh.status = 'Enable'
LEFT JOIN tv_showtime AS next_sh
ON next_sh.chn_id = c.ID
AND next_sh.showtime > '2013-02-18 10:28:35'
AND next_sh.status='Enable'
LEFT JOIN tv_showtime AS next_prev_sh
ON next_prev_sh.chn_id = next_sh.chn_id
AND next_prev_sh.showtime < next_sh.showtime
AND next_prev_sh.showtime > '2013-02-18 10:28:35'
AND next_prev_sh.status = 'Enable'
WHERE c.status = 'Enable'
AND curr_next_sh.ID IS NULL -- This gives us only the latest current show
AND next_prev_sh.ID IS NULL -- This gives us only the earliest next show
AND (curr_sh.ID IS NOT NULL OR next_sh.ID IS NOT NULL) -- This gives us 'which has current show time and/or next show time'
ORDER BY c.chn_name
LIMIT 0,10
But I'm not sure about performance, and whether this solution is optimal.