I'm trying to get the number of rows in a table or column and place that value inside an equation, like this:
UPDATE myTable
SET myCalculatedColumn = COUNT(*) / (#rownum:= 1 + #rownum)
WHERE 0 = (#rownum:=0)
Unfortunately, I get an error 1111 "Invalid use of group function". I've also tried:
SET #c = COUNT(*);
UPDATE myTable
SET myCalculatedColumn = #c / (#rownum:= 1 + #rownum)
WHERE 0 = (#rownum:=0)
But this produces the same error.
How can I place COUNT(*) (or a programmatically equivalent operation) into an equation?
Join with a subquery that gets the count. You can also initialize the #rownum variable there as well.
UPDATE myTable AS t
CROSS JOIN (SELECT COUNT(*) AS count, #rownum := 0 FROM myTable) AS c
SET myCalculatedColumn = count / (#rownum := 1 + #rownum)
If you don't want to do a cross join, you can use the subquery when setting #c. You just have to tell COUNT(*) what table to count from.
SET #c = (SELECT COUNT(*) FROM myTable);
SET #rownum = 0;
UPDATE myTable
SET myCalculatedColumn = #c / (#rownum:= 1 + #rownum);
Note that the order that it assigns to myCalculatedColumn will be arbitrary and unpredictable unless you also have an ORDER BY clause.
Related
I use this code in MySQL to order by 'anotherColumn' and then get the row number of 'myColumn' and then I perform a calculation and set 'myColumn' to the result:
SET #c = (SELECT COUNT(*) FROM myTable); SET #rownum = 0; UPDATE myTable SET myColumn = #c * (#rownum:= 1 + #rownum) ORDER BY anotherColumn DESC LIMIT 100000;
I'm trying to achieve the same thing in Postgresql but am getting a lot of errors. I have:
SET c = (SELECT COUNT(*) FROM myTable); SET rownum = 0; UPDATE myTable SET myColumn = c * (rownum:= 1 + rownum) ORDER BY anotherColumn DESC LIMIT 100000;
.. but it gives me an error at the first parenthesis. If I remove those parenthesis like this:
SET c = SELECT COUNT(*) FROM myTable; SET rownum = 0; UPDATE myTable SET myColumn = c * (rownum:= 1 + rownum) ORDER BY anotherColumn DESC LIMIT 100000;
.. then it gives me an error at the SELECT. If I just set c to equal 0, I get an error way down at the ORDER. Does anyone know how to convert my code from MySQL to PostgreSQL?
This "pattern" in MySQL is typically used to work around the absence of window function.
You don't need variables in Postgres to achieve something like that:
update my_table
set my_column = t.cnt + t.rn
from (
select pk_column,
(select count(*) from my_table) as cnt,
row_number() over (order by another_column) as rn
from my_table
limit 100000
) t
where t.pk_column = my_table.pk_column;
Where pk_column is the primary key column of your table. If you have more than one PK column, you need to use all of them.
I am using a modified version of a query similiar to another question here:Convert SQL Server query to MySQL
Select *
from
(
SELECT tbl.*, #counter := #counter +1 counter
FROM (select #counter:=0) initvar, tbl
Where client_id = 55
ORDER BY ordcolumn
) X
where counter >= (80/100 * #counter);
ORDER BY ordcolumn
tbl.* contains the field 'client_id' and I am attempting to get the top 20% of the records for each client_id in a single statement. Right now if I feed it a single client_id in the where statement it gives me the correct results, however if I feed it multiple client_id's it simply takes the top 20% of the combined recordset instead of doing each client_id individually.
I'm aware of how to do this in most databases, but the logic in MySQL is eluding me. I get the feeling it involves some ranking and partitioning.
Sample data is pretty straight forward.
Client_id rate
1 1
1 2
1 3
(etc to rate = 100)
2 1
2 2
2 3
(etc to rate = 100)
Actual values aren't that clean, but it works.
As an added bonus...there is also a date field associated to these records and 1 to 100 exists for this client for multiple dates. I need to grab the top 20% of records for each client_id, year(date),month(date)
You need to do the enumeration for each client:
SELECT *
FROM (SELECT tbl.*, #counter := #counter +1 counter
(#rn := if(#c = client_id, #rn + 1,
if(#c := client_id, 1, 1)
)
)
FROM (select #c := -1, #rn := 0) initvar CROSS JOIN tbl
ORDER BY client_id, ordcolumn
) t cross join
(SELECT client_id, COUNT(*) as cnt
FROM tbl
GROUP BY client_id
) tt
where rn >= (80/100 * tt.cnt);
ORDER BY ordcolumn;
Using Gordon's answer as a starting point, I think this might be closer to what you need.
SELECT t.*
, (#counter := #counter+1) AS overallRow
, (#clientRow := if(#prevClient = t.client_id, #clientRow + 1,
if(#prevClient := t.client_id, 1, 1) -- This just updates #prevClient without creating an extra field, though it makes it a little harder to read
)
) AS clientRow
-- Alteratively (for everything done in clientRow)
, #clientRow := if(#prevClient = t.client_id, #clientRow + 1, 1) AS clientRow
, #prevClient := t.client_id AS extraField
-- This may be more reliable as well; I not sure if the order
-- of evaluation of IF(,,) is reliable enough to guarantee
-- no side effects in the non-"alternatively" clientRow calculation.
FROM tbl AS t
INNER JOIN (
SELECT client_id, COUNT(*) AS c
FROM tbl
GROUP BY client_id
) AS cc ON tbl.client_id = cc.client_id
INNER JOIN (select #prevClient := -1, #clientRow := 0) AS initvar ON 1 = 1
WHERE t.client_id = 55
HAVING clientRow * 5 < cc.c -- You can use a HAVING without a GROUP BY in MySQL
-- (note that clientRow is derived, so you cannot use it in the `WHERE`)
ORDER BY t.client_id, t.ordcolumn
;
I have a quite complex query to get some data from the database, sort them and rank them accordingly.
Here is the SQL fiddle for it: SQL Fiddle
Now what I want to do is, to add a WHERE statement to this query, so only limited users will be selected (3 users above and 3 users below, the id = 8).
WHERE sort BETWEEN #userpos - 3 AND #userpos + 3
So it should look something like this, but with the first example:
SQL Fiddle
I have already tried to implement this WHERE statement to this query, but I couldn't figure it out where should I add, as I've always received error (that the column cannot be found).
Any suggestion and / or solution for my problem? Should I rewrite the whole query for this?
If I understand correctly, you can do this with a subquery:
SET #userid = 8
SELECT *
FROM (SELECT #pos := #pos + 1 AS sort, points, r.userid, s.active
FROM rank r JOIN
settings s
USING (userid) CROSS JOIN
(SELECT #pos := 0) p
WHERE s.active = 1
ORDER BY points DESC
) list
WHERE userid = #userid;
Note that this eliminates a layer of subqueries that you have. Otherwise, it is quite similar to your query.
EDIT:
The above was based more on the SQL Fiddle than on the question. (Oops.)
To get three rows before and after a given row is possible and just a small tweak, using a trick. The trick is to define another variable with the user pos and then use that variable in the outer query:
SELECT *
FROM (SELECT #pos := #pos + 1 AS sort, points, r.userid, s.active,
if(userid = #userid, #userpos := #pos, 0)
FROM rank r JOIN
settings s
USING (userid) CROSS JOIN
(SELECT #pos := 0, #userpos := 0) p
WHERE s.active = 1
ORDER BY points DESC
) list
WHERE `sort` between #userpos - 3 and #userpos + 3;
Note: MySQL does not guarantee the order of evaluation for variables in the select. The following is a bit safer in terms of order of execution:
SELECT *
FROM (SELECT (case when (#pos := #pos + 1) is NULL then NULL
when (case when (userid = #userid) then #userpos := #pos else 1 end) is null
then NULL
else #pos
end) AS sort, points, r.userid, s.active,
if(userid = #userid, #userpos := #pos)
FROM rank r JOIN
settings s
USING (userid) CROSS JOIN
(SELECT #pos := 0, #userpos := 0) p
WHERE s.active = 1
ORDER BY points DESC
) list
WHERE `sort` between #userpos - 3 and #userpos + 3;
The weird case statements are to ensure statement executions. The is null is to ensure that the when clauses fail, so the assignments are made sequentially.
Couldn't really explain my problem with words, but with an example I can show it clearly:
I have a table like this:
id num val
0 3 10
1 5 12
2 7 12
3 11 15
And I want to go through all the rows, and calculate the increase of the "num", and multiply that difference with the "val" value. And when I calculated all of these, I want to add these results together.
This is the mathematical equation, that I want to run on the table:
Result = (3-0)*10 + (5-3)*12 + (7-5)*12 + (11-7)*15
138 = Result
Thank you.
You can do with mysql variables, but you will still get one record for each entry.
select
#lastTotal := #lastTotal + ( (yt.num - #lastNum) * yt.val ) thisLineTotal,
#lastNum := yt.num as saveForNextRow,
yt.id
from
yourTable yt,
( select #lastTotal := 0,
#lastNum := 0 ) sqlvars
order by
id
This SHOULD give you what you want to confirm the calculations to each record basis.
Now, to get the one record and one column result, you can wrap it such as
select
pq.thisLineTotal
from
(above entire query ) as pq
order by
pq.id DESC
limit 1
Assuming the IDs are consecutive as your sample data suggests, just join the table to itself:
select sum((t1.num-ifnull(t2.num,0))*t1.val) YourValue
from YourTable t1
left join YourTable t2
on t2.id = t1.id - 1;
http://www.sqlfiddle.com/#!2/40b9f/12
This will give you the total. Make sure to order in the order you wish - I have ordered by id
SET #runtot:=0;
SET #prevval:=0;
select max(rt) as total FROM (
SELECT
q.val,
q.num,
(#runtot := #runtot + (q.num- #prevval) * q.val) AS rt,
(#prevval := q.num) AS pv
FROM thetable q
ORDER by ID) tot
If you want to see the details of the calculation, leave out the outer select as so:
SET #runtot:=0;
SET #prevval:=0;
SELECT
q.val,
q.num,
(#runtot := #runtot + (q.num- #prevval) * q.val) AS rt,
(#prevval := q.num) AS pv
FROM thetable q
ORDER by ID
If it is possible to have negative numbers for your column values, using max(rt) won't work for the total. You should then use:
SET #runtot:=0;
SET #prevval:=0;
select #runtot as total FROM (
SELECT
q.val,
q.num,
(#runtot := #runtot + (q.num- #prevval) * q.val) AS rt,
(#prevval := q.num) AS pv
FROM thetable q
ORDER by ID) tot LIMIT 1
Lets say I have these columns
uniqueID|Money|Quantity|MoneyOrder|QuantityOrder
1|23|12||
2|11|9||
3|99|100||
What I want to do is update MoneyOrder and QuantityOrder based on the value of ORDER BY.
So the results would be:
uniqueID|Money|Quantity|MoneyOrder|QuantityOrder
1|23|12|2|1
2|11|90|1|2
3|99|100|3|3
I want the update to operate like an identity column without actually making it an identity column. I know that I could just order by 'x' and the order would be the result but I want to generate a report where you can see the item line by line.
Is something like this possible update mytable set Moneyorder = 'imnotsure' order by MoneyOrder asc ?
SET #rownumber = 0;
update mytable set Moneyorder = (#rownumber:=#rownumber+1)
order by MoneyOrder asc
or to do it in a single query you can try
update mytable target
join
(
select id, (#rownumber := #rownumber + 1) as rownum
from mytable
cross join (select #rownumber := 0) r
order by MoneyOrder asc
) source on target.id = source.id
set Moneyorder = rownum
See answers to this question:
Updating column so that it contains the row position
SET #counter = 0;
UPDATE
my_table
SET MoneyOrder = #counter := #counter + 1
ORDER BY Money;
SET #counter = 0;
UPDATE
my_table
SET QuantityOrder = #counter := #counter + 1
ORDER BY Quantity;