I want to compute running row totals across a table, however the totals must start over for new IDs
https://imgur.com/a/YgQmYQA
My code:
set #csum := 0;
select ID, name, marks, (#rt := #rt + marks) as Running_total from students order by ID;
The output returns the totals however doesn't break or start over for new IDs
Bro try this... It is tested on MSSQL..
select ID, name, marks,
marks + isnull(SUM(marks) OVER ( PARTITION BY ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) ,0) as Running_total
from students
You need to partition your running total by ID. A running total always needs an order of some column, by ordering on which you want to calculate the running total. Assuming running total under each ID is based on ORDER of marks,
Approach 1: It can be written in a simple query if your DBMS supports Analytical Functions
SELECT ID
,name
,marks
,Running_total = SUM(marks) OVER (PARTITION BY ID ORDER BY marks ASC)
FROM students
Approach 2: You can make use of OUTER APPLY if your database version / DBMS itself does not support Analytical Functions
SELECT S.ID
,S.name
,S.marks
,Running_total = OA.runningtotalmarks
FROM students S
OUTER APPLY (
SELECT runningtotalmarks = SUM(SI.marks)
FROM students SI
WHERE SI.ID = S.ID
AND SI.marks <= S.marks
) OA;
Note:- The above queries have been tested MS SQL Server.
Related
Lets say I have a table with the following rows/values:
I need a way to select the values in amount but only once if they're duplicated. So from this example I'd want to select A,B and C the amount once. The SQL result should look like this then:
Use LAG() function and compare previous amount with current row amount for name.
-- MySQL (v5.8)
SELECT t.name
, CASE WHEN t.amount = t.prev_val THEN '' ELSE amount END amount
FROM (SELECT *
, LAG(amount) OVER (PARTITION BY name ORDER BY name) prev_val
FROM test) t
Please check from url https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=8c1af9afcadf2849a85ad045df7ed580
You can handle situation like these with different function depending on what you need:
Case1 : If you have same values per name:
select distinct name, amount from [table name]
Case2 : You have duplicates with different values for each name and you want to pick the one with the highest value. Use min() if you need the minimum one to show up.
select name, max(amount) from [table name] group by 1
Case 3: The one you need with blanks for the rest of the duplications.
Row number will create rows based on values in amount and since the values are the same it will create it incrementally and you can then use IF to create a new column where rank_ > 1 then blanks. This will also cover the case where you would like to select just the minimum value and then have blanks for the rest of the name values
With ranking as (
select
*,
ROW_NUMBER() OVER(PARTITION BY NAME ORDER BY AMOUNT) AS RANK_
from [table]
)
SELECT
*,
IF(RANK_ > 1,"",AMOUNT) AS NEW_AMOUNT
FROM ranking
Case 4: You need to select maximum and put the other names as blank
You will just adjust the order by clause of ROW_NUMBER() to DESC. This will put the rank 1 to the highest amount per name and for the rest, the blank will be filled
With ranking as (
select
*,
ROW_NUMBER() OVER(PARTITION BY NAME ORDER BY AMOUNT DESC) AS RANK_
from [table]
)
SELECT
*,
IF(RANK_ > 1,"",AMOUNT) AS NEW_AMOUNT
FROM ranking
If you are using mysql 8 you can use row_number for this:
with x as (
select *, row_number() over(partition by name order by amount) rn
from t
)
select name, case when rn=1 then amount else '' end amount
from x
See example Fiddle
The other answers are missing a really important point: A SQL table returns an unordered set unless there is an explicit order by.
The data that you have provides has rows that are exact duplicates. For this reason, I think the best approach uses row_number() and an order by in the outer query:
select name, (case when seqnum = 1 then amount end) as amount
from (select t.*,
row_number() over (partition by name, amount) as seqnum
from t
) t
order by name, seqnum;
Note that MySQL does not require an order by argument for row_number().
More commonly, though, you would have some other column (say a date or id) that would be used for ordering. I should also emphasize that this type of formatting is often handled at the application layer and not in the database.
I'm having a problem with grouping specific columns into one. When I use GROUP BY, the last row always gets selected when it should be the first row.
The main query is:
SELECT cpme_id,
medicine_main_tbl.med_id,
Concat(med_name, ' (', med_dosage, ') ', med_type) AS Medicine,
med_purpose,
med_quantity,
med_expiredate
FROM medicine_main_tbl
JOIN medicine_inventory_tbl
ON medicine_main_tbl.med_id = medicine_inventory_tbl.med_id
WHERE Coalesce(med_quantity, 0) != 0
AND Abs(Datediff(med_expiredate, Now()))
ORDER BY med_expiredate;
SELECT without GROUP BY
If I GROUP BY using any duplicate column value (in this case, I used med_id):
SELECT with GROUP BY
I'm trying to get this output
Expected Output
The output should only be the first two from the first query. Obviously, I cannot use LIMIT.
Since you are using MariaDB, I recommend using ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY med_id ORDER BY med_expireDate) rn
FROM yourTable
)
SELECT cpme_id, med_id, Medicine, med_purpose, med_quantity, med_expireDate
FROM cte
WHERE rn = 1;
This assumes that the "first" row for a given medicine is the one having the earliest expire date. This was the only interpretation of your data which agreed with the expected output.
I have this project that it ranks different items by their scores, the ranking is okay but it shows gaps when there is a tied score.
Here is the query:
SELECT bgycode, scc_bgyscoretotal, FIND_IN_SET( scc_bgyscoretotal, (
SELECT GROUP_CONCAT(DISTINCT scc_bgyscoretotal
ORDER BY scc_bgyscoretotal DESC ) FROM bgyprofile)
) AS rank
FROM bgyprofile
and it outputs like this:
any way to fix this?
Thanks in advance sorry for the bad english
You basically need Dense_Rank() like functionality (available in MySQL version >= 8.0). In older versions of MySQL, it can be emulated using Session Variables.
In a Derived table, determine ranking of a scc_bgyscoretotal (highest value having rank 1 and so on). Firstly, get unique values of scc_bgyscoretotal, and then determine their ranking using Session Variables.
Now, simply join these Derived table to the main table bgyprofile using scc_bgyscoretotal.
Try the following:
SELECT t2.bgycode,
t2.scc_bgyscoretotal,
dt2.`rank`
FROM bgyprofile AS t2
JOIN
(
SELECT dt1.scc_bgyscoretotal,
#rank_no := #rank_no + 1 AS `rank`
FROM
(
SELECT t1.scc_bgyscoretotal
FROM bgyprofile AS t1
GROUP BY t1.scc_bgyscoretotal
ORDER BY t1.scc_bgyscoretotal DESC
) AS dt1
CROSS JOIN (SELECT #rank_no := 0) AS init1
) AS dt2 ON dt2.scc_bgyscoretotal = t2.scc_bgyscoretotal
I have a table with 3 fields:
id
note
created_at
Is there a way in the SQL language especially Postgres that I can select the value of the last note without having to LIMIT 1?
Normal query:
select note from table order by created_at desc limit 1
I'm interested in something avoiding the limit since I'll need it as a subquery.
Simple version with EXISTS semi-join:
SELECT note FROM tbl t
WHERE NOT EXISTS
(SELECT 1 FROM tbl t1 WHERE t1.created_at > t.created_at);
"Find a note where no other note was created later."
This shares the weakness of #Hogan's version that it can return multiple rows if created_at is not UNIQUE - like #Ollie already pointed out. Unlike #Hogan's query (max() is only defined for simple types) this one can be improved easily:
Compare row types
SELECT note FROM tbl t
WHERE NOT EXISTS
(SELECT 1 FROM tbl t1
WHERE (t1.created_at, t1.id) > (t.created_at, t.id));
Assuming you want the greatest id in case of a tie with created_at, and id is the primary key, therefore unique. This works in PostgreSQL and MySQL.
SQL Fiddle.
Window function
The same can be achieved with a window function in PostgreSQL:
SELECT note
FROM (
SELECT note, row_number() OVER (ORDER BY created_at DESC, id DESC) AS rn
FROM tbl t
) x
WHERE rn = 1;
MySQL lacks support for window functions. You can substitute with a variable like this:
SELECT note
FROM (
SELECT note, #rownum := #rownum + 1 AS rn
FROM tbl t
,(SELECT #rownum := 0) r
ORDER BY created_at DESC, id DESC
) x
WHERE rn = 1;
(SELECT #rownum := 0) r initializes the variable with 0 without an explicit SET command.
SQL Fiddle.
If your id column is an autoincrementing primary key field, it's pretty easy. This assumes the latest note has the highest id. (That might not be true; only you know that!)
select *
from note
where id = (select max(id) from note)
It's here: http://sqlfiddle.com/#!2/7478a/1/0 for MySQL and here http://sqlfiddle.com/#!1/6597d/1/0 for postgreSQL. Same SQL.
If your id column isn't set up so the latest note has the highest id, but still is a primary key (that is, still has unique values in each row), it's a little harder. We have to disambiguate identical dates; we'll do this by choosing, arbitrarily, the highest id.
select *
from note
where id = (
select max(id)
from note where created_at =
(select max(created_at)
from note
)
)
Here's an example: http://sqlfiddle.com/#!2/1f802/4/0 for MySQL.
Here it is for postgreSQL (the SQL is the same, yay!) http://sqlfiddle.com/#!1/bca8c/1/0
Another possibility: maybe you want both notes shown together in one row if they were both created at the same exact time. Again, only you know that.
select group_concat(note separator '; ')
from note
where created_at = (select max(created_at) from note)
In postgreSQL 9+, it's
select string_agg(note, '; ')
from note
where created_at = (select max(created_at) from note)
If you do have the possibility for duplicate created_at times and duplicate id values, and you don't want the group_concat effect, you are unfortunately stuck with LIMIT.
I'm not 100% on Postgres (actually never used it) but you can get the same effect with something like this - if the created_at is unique ... (or with any column which is unique):
SELECT note FROM table WHERE created_at = (
SELECT MAX(created_at) FROM table
)
I may not know how to answer on this platform but what I have suggested is working
SELECT * FROM table GROUP BY field ORDER BY max(field) DESC;
You can get the last value of the field without limiting, usually in JOINED query we get the last update time with no limiting of output like this way, such as last message time without limiting it.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Combine multiple results in a subquery into a single comma-separated value
Concat groups in SQL Server
I want to be able to get the duplication's removed
SELECT Count(Data) as Cnt, Id
FROM [db].[dbo].[View_myView]
Group By Data
HAVING Count(Data) > 1
In MySQL it was as simple as this:
SELECT Count(Data), group_concat(Id)
FROM View_myView
Group By Data
Having Cnt > 1
Does anyone know of a solution? Examples are a plus!
In SQL Server as of version 2005 and newer, you can use a CTE (Common Table Expression) with the ROW_NUMBER function to eliminate duplicates:
;WITH LastPerUser AS
(
SELECT
ID, UserID, ClassID, SchoolID, Created,
ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY Created DESC) AS 'RowNum'
FROM dbo.YourTable
)
SELECT
ID, UserID, ClassID, SchoolID, Created,
FROM LastPerUser
WHERE RowNum = 1
This CTE "partitions" your data by UserID, and for each partition, the ROW_NUMBER function hands out sequential numbers, starting at 1 and ordered by Created DESC - so the latest row gets RowNum = 1 (for each UserID) which is what I select from the CTE in the SELECT statement after it.
Using the same CTE, you can also easily delete duplicates:
;WITH LastPerUser AS
(
SELECT
ID, UserID, ClassID, SchoolID, Created,
ROW_NUMBER() OVER(PARTITION BY UserID ORDER BY Created DESC) AS 'RowNum'
FROM dbo.YourTable
)
DELETE FROM dbo.YourTable t
FROM LastPerUser cte
WHERE t.ID = cte.ID AND cte.RowNum > 1
Same principle applies: you "group" (or partition) your data by some criteria, you consecutively number all the rows for each data partition, and those with values larger than 1 for the "partitioned row number" are weeded out by the DELETE.
Just use distinct to remove duplicates. It sounds like you were using group_concat to join duplicates without actually wanting to use its value. In that case, MySQL also has a distinct you could have been using:
SELECT DISTINCT Count(Data) as Cnt, Id
FROM [db].[dbo].[View_myView]
GROUP BY Id
HAVING Count(Data) > 1
Also, you can't group by something you use in an aggregate function; I think you mean to group by id. I corrected it in the example above.