group data based on a condition and then sequentially ranking - sql

The table has two columns, the ID is the PK and the other column is Type, sample data looks like the following.
| ID | Type |
| 1 | 0 |
| 2 | 0 |
| 3 | 1 |
| 4 | 1 |
| 5 | 0 |
| 6 | 1 |
I was trying to group the sample data into the following desired output.
1. The desired output is ordered by ID.
2. The Category is calculated based on the following logic,continuous records with the same type will be assigned the same category, and records with different types will increase category by 1.
| ID | Type |Category |
| 1 | 0 | 1 |
| 2 | 0 | 1 |
| 3 | 1 | 2 |
| 4 | 1 | 2 |
| 5 | 0 | 3 |
| 6 | 1 | 4 |
Is it able to do it in SQL server 2008 and 2012?
I found this (http://explainextended.com/2011/02/18/things-sql-needs-series/) might be able to resolve it.
Thanks in advance

It is possible but a bit weird since the value for Category depends on previous data entries.
Look at ROW_NUMBER() and DENSE_RANK() functions, which will do part of the numbering for you. I'd probably select a derived table ordering how you want and with row_number() to sequence them, join that back onto itself and compare rows with their previous one to see if Type matches and Id = Id-1, then re-rank based on that.

You might try (all in sql with the mapping in the sql query)
select PK, ID, Category
from (select PK, ID, "1" as 'Category' from Table1 where ID = 0 and PK < 3;
UNION
select PK, ID, "2" as 'Category' from Table1 where ID = 1 and PK < 5
UNION
select PK, ID, "3" as 'Category' from Table1 where PK = 5
UNION
select PK, ID, "4" as 'Category' from Table1 where PK = 6
}
order by PK

may this you are looking for
DECLARE #amount TABLE (ID INT,TYPE INT)
INSERT INTo #amount (ID,TYPE)VALUES (1,0)
INSERT INTo #amount (ID,TYPE)VALUES (2,0)
INSERT INTo #amount (ID,TYPE)VALUES (3,1)
INSERT INTo #amount (ID,TYPE)VALUES (4,1)
INSERT INTo #amount (ID,TYPE)VALUES (5,0)
INSERT INTo #amount (ID,TYPE)VALUES (6,1)
INSERT INTo #amount (ID,TYPE)VALUES (7,0)
select ID,TYPE,DENSE_RANK()OVER (PARTITION BY TYPE ORDER BY ID ASC ) AS RN from #amount

Related

Counting columns if certain Id

I have a table tblTitles that I am attempting to run a select query on. I would like to select a count based reports are there with IdState and do a count on how many of those titles belong to IsOnSaleCountId which would be if that column has an id of 1
Here is an example of the table:
+---------+----------+-------------------+-----------------+
| IdState | RegionId | Title | IsOnSaleId |
+---------+----------+-------------------+-----------------+
| 22 | 1 | Online Shopping | 0 |
| 22 | 1 | Retail Shopping | 1 |
| 22 | 1 | Pick Up | 0 |
| | | | |
+---------+----------+-------------------+-----------------+
My expected outcome should read that IdState of 22 has 3 reports and 1 report is onSale due to the 1 integer in the second row. Which would look similar to this:
+---------+-------------+---------------+
| IdState | ReportCount | IsOnSaleCount |
+---------+-------------+---------------+
| 22 | 3 | 1 |
+---------+-------------+---------------+
I am having issues when doing a select statement with this count. The IsOnSaleCount is identical to the ReportCount number which they should not be.
I believe this is the case due to my line of code of case when count(i.IsOnSaleId) > 0 THEN count(1) Else 0 End as IsOnSaleCount
Is this something that I can do in a SELECT query?
Here is an example of my query :
select
i.IdState,
count(i.RegionId) as ReportCount,
case when count(i.IsOnSaleId) > 0 THEN count(1) Else 0 End as IsOnSaleCount,
0 as EnterpriseReportCount,
i.IdReportCollection_PK_PrimaryCollection
from IBIS_Local.dbo.tblindustry i
If you want the count:
count(i.IsOnSaleId) as IsOnSaleCount,
If you just want a 0/1 flag, you could do:
sign(count(i.IsOnSaleId)) as IsOnSaleCount,
IF OBJECT_ID('stack.report') IS NOT NULL DROP TABLE stack.report
CREATE TABLE stack.report ( IdState TINYINT, RegionID TINYINT, Title VARCHAR(50), IsOnSaleId INT)
INSERT INTO stack.report
VALUES
(22,1,'Online Shopping', 0)
, (22,1,'Retail Shopping', 1)
, (22,1,'Pick Up', 0)
SELECT *, CONVERT(TINYINT, IsOnSaleId) isonsaleint FROM stack.report
SELECT IdState, COUNT(*) ReportCount, SUM(IsOnSaleId) OnSaleCount
FROM stack.report
GROUP BY IdState
ORDER BY IdState
Result
IdState | ReportCount | OnSaleCount
22 | 3 | 1
The SUM works if IsOnSaleId is an INT, SMALLINT or TINYINT. If IsOnSalesId datatype is BIT (commonly used for flags), then you will need to convert to one of the int types like this SUM(CONVERT(INT, IsOnSaleId))

How to delete the rows with three same data columns and one different data column

I have a table "MARK_TABLE" as below.
How can I delete the rows with same "STUDENT", "COURSE" and "SCORE" values?
| ID | STUDENT | COURSE | SCORE |
|----|---------|--------|-------|
| 1 | 1 | 1 | 60 |
| 3 | 1 | 2 | 81 |
| 4 | 1 | 3 | 81 |
| 9 | 2 | 1 | 80 |
| 10 | 1 | 1 | 60 |
| 11 | 2 | 1 | 80 |
Now I already filtered the data I want to KEEP, but without the "ID"...
SELECT student, course, score FROM mark_table
INTERSECT
SELECT student, course, score FROM mark_table
The output:
| STUDENT | COURSE | SCORE |
|---------|--------|-------|
| 1 | 1 | 60 |
| 1 | 2 | 81 |
| 1 | 3 | 81 |
| 2 | 1 | 80 |
Use the following query to delete the desired rows:
DELETE FROM MARK_TABLE M
WHERE
EXISTS (
SELECT
1
FROM
MARK_TABLE M_IN
WHERE
M.STUDENT = M_IN.STUDENT
AND M.COURSE = M_IN.COURSE
AND M.SCORE = M_IN.SCORE
AND M.ID < M_IN.ID
)
OUTPUT
db<>fiddle demo
Cheers!!
use distinct
SELECT distinct student, course, score FROM mark_table
Assuming you don't just want to select the unique data you want to keep (you mention you've already done this), you can proceed as follows:
Create a temporary table to hold the data you want to keep
Insert the data you want to keep into the temporary table
Empty the source table
Re-Insert the data you want to keep into the source table.
select * from
(
select row_number() over (partition by student,course,score order by score)
rn,student,course,score from mark_table
) t
where rn=1
Use CTE with RowNumber
create table #MARK_TABLE (ID int, STUDENT int, COURSE int, SCORE int)
insert into #MARK_TABLE
values
(1,1,1,60),
(3,1,2,81),
(4,1,3,81),
(9,2,1,80),
(10,1,1,60),
(11,2,1,80)
;with cteDeleteID as(
Select id, row_number() over (partition by student,course,score order by score) [row_number] from #MARK_TABLE
)
delete from #MARK_TABLE where id in
(
select id from cteDeleteID where [row_number] != 1
)
select * from #MARK_TABLE
drop table #MARK_TABLE

T-SQL: Best way to replace NULL with most recent non-null value?

Assume I have this table:
+----+-------+
| id | value |
+----+-------+
| 1 | 5 |
| 2 | 4 |
| 3 | 1 |
| 4 | NULL |
| 5 | NULL |
| 6 | 14 |
| 7 | NULL |
| 8 | 0 |
| 9 | 3 |
| 10 | NULL |
+----+-------+
I want to write a query that will replace any NULL value with the last value in the table that was not null in that column.
I want this result:
+----+-------+
| id | value |
+----+-------+
| 1 | 5 |
| 2 | 4 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 14 |
| 7 | 14 |
| 8 | 0 |
| 9 | 3 |
| 10 | 3 |
+----+-------+
If no previous value existed, then NULL is OK. Ideally, this should be able to work even with an ORDER BY. So for example, if I ORDER BY [id] DESC:
+----+-------+
| id | value |
+----+-------+
| 10 | NULL |
| 9 | 3 |
| 8 | 0 |
| 7 | 0 |
| 6 | 14 |
| 5 | 14 |
| 4 | 14 |
| 3 | 1 |
| 2 | 4 |
| 1 | 5 |
+----+-------+
Or even better if I ORDER BY [value] DESC:
+----+-------+
| id | value |
+----+-------+
| 6 | 14 |
| 1 | 5 |
| 2 | 4 |
| 9 | 3 |
| 3 | 1 |
| 8 | 0 |
| 4 | 0 |
| 5 | 0 |
| 7 | 0 |
| 10 | 0 |
+----+-------+
I think this might involve some kind of analytic function - somehow partitioning over the value column - but I'm not sure where to look.
You can use a running sum to set groups and use max to fill in the null values.
select id,max(value) over(partition by grp) as value
from (select id,value,sum(case when value is not null then 1 else 0 end) over(order by id) as grp
from tbl
) t
Change the over() clause to order by value desc to get the second result in the question.
The best way has been covered by Itzik Ben-Gan here:The Last non NULL Puzzle
Below is a solution which for 10 million rows and completes around in 20 seconds on my system
SELECT
id,
value1,
CAST(
SUBSTRING(
MAX(CAST(id AS binary(4)) + CAST(value1 AS binary(4)))
OVER (ORDER BY id
ROWS UNBOUNDED PRECEDING),
5, 4)
AS int) AS lastval
FROM dbo.T1;
This solution assumes your id column is indexed
You can also try using correlated subquery
select id,
case when value is not null then value else
(select top 1 value from table
where id < t.id and value is not null order by id desc) end value
from table t
Result :
id value
1 5
2 4
3 1
4 1
5 1
6 14
7 14
8 0
9 3
10 3
If the NULLs are scattered I use a WHILE loop to fill them in
However if the NULLs are in longer consecutive strings there are faster ways to do it.
So here's one approach:
First find a record that we want to update. It has NULL in this record and no NULL in the prior record
SELECT C.VALUE, N.ID
FROM TABLE C
INNER JOIN TABLE N
ON C.ID + 1 = N.ID
WHERE C.VALUE IS NOT NULL
AND N.VALUE IS NULL;
Use that to update: (bit hazy on this syntax but you get the idea)
UPDATE N
SET VALUE = C.Value
FROM TABLE C
INNER JOIN TABLE N
ON C.ID + 1 = N.ID
WHERE C.VALUE IS NOT NULL
AND N.VALUE IS NULL;
.. now just keep doing it till you run out of rows
-- This is needed to set ##ROWCOUNT to non zero
SELECT 1;
WHILE ##ROWCOUNT <> 0
BEGIN
UPDATE N
SET VALUE = C.Value
FROM TABLE C
INNER JOIN TABLE N
ON C.ID + 1 = N.ID
WHERE C.VALUE IS NOT NULL
AND N.VALUE IS NULL;
END
The other way is to use a similiar query to get a range of id's to update. This works much faster if your NULLS are usually against consecutive id's
Here is the one simple approach using OUTER APPLY
CREATE TABLE #table(id INT, value INT)
INSERT INTO #table VALUES
(1,5),
(2,4),
(3,1),
(4,NULL),
(5,NULL),
(6,14),
(7,NULL),
(8,0),
(9,3),
(10,NULL)
SELECT t.id, ISNULL(t.value, t3.value) value
FROM #table t
OUTER APPLY(SELECT id FROM #table WHERE id = t.id AND VALUE IS NULL) t2
OUTER APPLY(SELECT TOP 1 value
FROM #table WHERE id <= t2.id AND VALUE IS NOT NULL ORDER BY id DESC) t3
OUTPUT:
id VALUE
---------
1 5
2 4
3 1
4 1
5 1
6 14
7 14
8 0
9 3
10 3
Using this sample data:
if object_id('tempdb..#t1') is not null drop table #t1;
create table #t1 (id int primary key, [value] int null);
insert #t1 values(1,5),(2,4),(3,1),(4,NULL),(5,NULL),(6,14),(7,NULL),(8,0),(9,3),(10,NULL);
I came up with:
with x(id, [value], grouper) as (
select *, row_number() over (order by id)-sum(iif([value] is null,1,0)) over (order by id)
from #t1)
select id, min([value]) over (partition by grouper)
from x;
I noticed, however, that Vamsi Prabhala beat me to it... My solution is identical to what he posted. (arghhhh!). So I thought I'd try a recursive solution. Here's a pretty efficient use of a recursive cte (provided that ID is indexed):
with sorted as (select *, seqid = row_number() over (order by id) from #t1),
firstRecord as (select top(1) * from #t1 order by id),
prev as
(
select t.id, t.[value], lastid = 1, lastvalue = null
from sorted t
where t.id = 1
union all
select t2.id, t2.[value], lastid+1, isnull(prev.[value],lastvalue)
from sorted t2
join prev on t2.id = prev.lastid+1
)
select id, [value]=isnull([value],lastvalue)--, *
from prev;
Normally I don't like recursive cte's (rCte for short) but in this case it offered an elegant solution and was faster than using the window aggregate function (sum over, min over...). Note the execution plans, the rcte on the bottom. The rCTE get's it done with two index seeks, one of which is for just one row. Unlike the window aggregate solution, the rcte does not require a sort. Running this with statistics io on; the rcte produces much less IO.
All this said, don't use either of these solutions, What the TheGameiswar posted will perform the best by far. His solution on a properly indexed id column would be lightening fast.
Following UPDATE statement can be used, please test it before use
update #table
set value = newvalue
from (
select
s.id, s.value,
(select top 1 t.value from #table t where t.id <= s.id and t.value is not null order by t.id desc) as newvalue
from #table S
) u
where #table.id = u.id and #table.value is null
stop worrying..here's the answer for you :)
SELECT *
INTO #TempIsNOtNull
FROM YourTable
WHERE value IS NOT NULL
SELECT *
INTO #TempIsNull
FROM YourTable
WHERE value IS NULL
UPDATE YourTable
SEt YourTable.value = UpdateDtls.value
FROM YourTable
JOIN (
SELECT OuterTab1.id,
#TempIsNOtNull.value
FROM #TempIsNull OuterTab1
CROSS JOIN #TempIsNOtNull
WHERE OuterTab1.id - #TempIsNOtNull.id > 0
AND (OuterTab1.id - #TempIsNOtNull.id) = ( SELECT TOP 1
OuterTab1.id - #TempIsNOtNull.id
FROM #TempIsNull InnerTab
CROSS JOIN #TempIsNOtNull
WHERE OuterTab1.id - #TempIsNOtNull.id > 0
AND OuterTab1.id = InnerTab.id
ORDER BY (OuterTab1.id - #TempIsNOtNull.id) ASC) ) AS UpdateDtls
ON (YourTable.id = UpdateDtls.id)

Querying data groups with total row before starting next group?

I need to query some data in the below format in SQL Server:
Id Group Price
1 A 10
2 A 20
Sum 30
1 B 6
2 B 4
Sum 10
1 C 100
2 C 200
Sum 300
I was thinking to do it in the follwoing steps:
Query one group
In other query do sum
Use Union operator to combine this result set
Do step 1-3 for all groups and finally return all sub sets of data using union.
Is there a better way to do this ? May be using some out of box feature ? Please advise.
Edit:
As per suggestions and code sample I tried this code:
Select
Case
when id is null then 'SUM'
else CAST(id as Varchar(10)) end as ID,
Case when [group] is null then 'ALL' else CAST([group] as Varchar(50)) end as [group]
,Price from
(
SELECT Id, [Group],BGAApplicationID,Section, SUM(PrimaryTotalArea) AS price
FROM vwFacilityDetails
where bgaapplicationid=1102
GROUP BY Id, [Group],BGAApplicationID,Section WITH ROLLUP
) a
And Even this code as well:
Select Id, [Group],BGAApplicationID,Section, SUM(PrimaryTotalArea) AS price
From vwFacilityDetails
Where Not ([group] Is Null And id Is Null And BGAApplicationId is null and section is null) and BGAApplicationId=1102
Group By Id, [Group],BGAApplicationID,Section
With Rollup
In results it groups up the data but for every record it shows it 3 times (in both above codes) like:
2879 Existing Facilities Whole School 25.00
2879 Existing Facilities Whole School 25.00
2879 Existing Facilities Whole School 25.00
2879 ALL 25.00
I guess there is some issue in my query, please guide me here as well.
Thanks
SQL Server introduced GROUPING SETS which is what you should be looking to use.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
Create Table vwFacilityDetails (
id int not null,
[group] char(1) not null,
PrimaryTotalArea int not null,
Section int,
bgaapplicationid int
);
Insert Into vwFacilityDetails (id, [group], Section,bgaapplicationid,PrimaryTotalArea) values
(1, 'A', 1,1102,2),
(1, 'A', 1,1102,1),
(1, 'A', 1,1102,7),
(2, 'A', 1,1102,20),
(1, 'B', 1,1102,6),
(2, 'B', 1,1102,4),
(1, 'C', 1,1102,100),
(2, 'C', 1,1102,200);
Query 1:
SELECT CASE WHEN Id is null then 'SUM'
ELSE Right(Id,10) end Id,
[Group],BGAApplicationID,Section,
SUM(PrimaryTotalArea) price
FROM vwFacilityDetails
where bgaapplicationid=1102
GROUP BY GROUPING SETS (
(Id,[Group],BGAApplicationID,Section),
([Group])
)
ORDER BY [GROUP],
ID;
Results:
| ID | GROUP | BGAAPPLICATIONID | SECTION | PRICE |
----------------------------------------------------
| 1 | A | 1102 | 1 | 10 |
| 2 | A | 1102 | 1 | 20 |
| SUM | A | (null) | (null) | 30 |
| 1 | B | 1102 | 1 | 6 |
| 2 | B | 1102 | 1 | 4 |
| SUM | B | (null) | (null) | 10 |
| 1 | C | 1102 | 1 | 100 |
| 2 | C | 1102 | 1 | 200 |
| SUM | C | (null) | (null) | 300 |
Select
id,
[Group],
SUM(price) AS price
From
Test
Group By
[group],
id
With
Rollup
http://sqlfiddle.com/#!3/080cd/8
Select Case when id is null then 'SUM' else CAST(id as Varchar(10)) end as ID
,Case when [group] is null then 'ALL' else CAST([group] as Varchar(10)) end as [group]
,Price from
(
SELECT id, [group], SUM(price) AS Price
FROM IG
GROUP BY [GROUP],ID WITH ROLLUP
) a

Create New Table From Other Table After Grouping

How can I insert to a table a value from "grouping" other table?
That means I have 2 table with different structure.
The table ORDRE with existed DATA
Table ORDRE:
ORDRE ID | CODE_DEST |
-------------------------
1 | a |
2 | b |
3 | c |
4 | a |
5 | a |
6 | b |
7 | g |
I want to INSERT the value FROM Table ORDRE INTO TABLE VOIT:
ID_VOIT | ORDRE ID | CODE_DEST |
---------------------------------------
1 | 1 | a |
1 | 4 | a |
1 | 5 | a |
2 | 2 | b |
2 | 6 | b |
3 | 3 | c |
4 | 7 | g |
This is my best guess on what you need using only the info available.
declare #Ordre table
(
ordre_id int,
code_dest char(1)
)
declare #Voit table
(
id_voit int,
ordre_id int,
code_dest char(1)
)
insert into #Ordre values
(1,'a'),
(2,'b'),
(3,'c'),
(4,'a'),
(5,'a'),
(6,'b'),
(7,'g')
insert into #Voit
select id_voit, ordre_id, rsOrdre.code_dest
from #Ordre rsOrdre
inner join
(
select code_dest, ROW_NUMBER() over (order by code_dest) as id_voit
from #Ordre
group by code_dest
) rsVoit on rsVoit.code_dest = rsOrdre.code_dest
order by id_voit, ordre_id
select * from #Voit
Working Example.
For the specific data you give as an example, this works:
insert into VOIT
select
case code_dest
when 'a' then 1
when 'b' then 2
when 'c' then 3
when 'g' then 4
else 0
end, orderId, code_dest from ORDRE order by code_dest, orderId
But it kind of sucks because it requires hard-coding in a huge case statement.
Test is here - https://data.stackexchange.com/stackoverflow/q/119442/
What I like more is moving the VOIT ID / Code_Dest associations to a new table, so then you could do an inner join instead.
insert into VOIT
select voit_id, orderId, t.code_dest
from ORDRE t
join Voit_CodeDest t2 on t.code_dest = t2.code_dest
order by code_dest, orderId
Working example of that here - https://data.stackexchange.com/stackoverflow/q/119443/