Calculate data only for special rows [closed] - sql

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I have table:
ID count OperationID
100 111 1
100 55 2
100 55 3
100 66 4
99 69 1
99 33 2
99 11 14
98 33 1
98 47 2
It is necessary to show data for those 'OperationID' that are in each 'ID'
Ex. output:
ID count OperationID
100 111 1
100 55 2
99 69 1
99 33 2
98 33 1
98 47 2
Maybe join table with itself help?But it does not work ...

You can use it.
DECLARE #MyTable TABLE ( ID INT, [count] INT,OperationID INT)
INSERT INTO #MyTable
VALUES
(100,111,1),
(100,55,2),
(100,55,3),
(100,66,4 ),
(99,69,1),
(99,33,2),
(99,11,14),
(98,33,1),
(98,47,2)
SELECT *
FROM #MyTable
WHERE
OperationID IN
(SELECT
OperationID
FROM #MyTable
GROUP BY OperationID
HAVING COUNT(*) = (SELECT COUNT(DISTINCT ID) FROM #MyTable)
)
Result:
ID count OperationID
----------- ----------- -----------
100 111 1
100 55 2
99 69 1
99 33 2
98 33 1
98 47 2

Ok you can try this, but there are some considerations in your dataset, what about if there is a row that does not have a common OpID for example.
declare #tbl table (ID int,Cnt int, OperationID int)
insert into #tbl
select 100,111,1 union
select 100, 55,2 union
select 100, 55,3 union
select 100, 66,4 union
select 99 , 69,1 union
select 99 , 33,2 union
select 99 , 11,14 union
select 98 , 33,1 union
select 98,47,2
select * from #tbl where OperationID in (
select OperationID from #tbl
where id in (
select a.id from
(
select top 1 id, count (distinct OperationID) as opCnt from #tbl
group by id
order by opCnt
) a
)
)
Result
98 33 1
98 47 2
99 33 2
99 69 1
100 55 2
100 111 1

I am sure this is not the most optimal way.
with cte1 as (
select distinct id
from Operations
),
cte2 as (
select distinct operationID
from Operations
)
, cte3 as (
select *
from cte1 cross join cte2
)
SELECT *
FROM operations
WHERE operationid NOT IN(
SELECT cte3.operationid
FROM operations a
right JOIN cte3 ON cte3.id = a.id
AND cte3.operationid = a.operationid
WHERE a.id IS NULL)

You can try it
DECLARE #table TABLE ( id INT, cnt INT, operationid INT )
INSERT INTO #table VALUES
(100,111 , 1 )
,(100, 55 , 2 )
,(100, 55 , 3 )
,(100, 66 , 4 )
,(99 , 69 , 1 )
,(99 , 33 , 2 )
,(99 , 11 , 14)
,(98 , 33 , 1 )
,(98 , 47 , 2 )
;WITH cte
AS (
SELECT
Count(distinct id) TotalId
FROM
#table
)
,cte1 as
(select
operationid
,Count( distinct id) operationid_By_Id
FROM
#table
Group by
operationid )
SELECT * From
#table c
WHERE EXISTS (
SELECT 1 FROM (
SELECT
*
FROM cte1 A
WHERE EXISTS (SELECT 1 FROM cte b WHERE a.operationid_By_Id = b.TotalId)
) d where d.operationid = c.operationid)
Order by id desc , operationid

Related

T SQL Cte delete where group by is greater than 1

I'm using SQL Server 2016. I have the below table:
SKU Mkt Week Cost Code
ABC 05 1 10 100
ABC 05 2 12 100
DEF 05 3 20 100
DEF 05 3 25 125
XYZ 08 1 10 100
XYZ 08 2 12 100
XZY 08 2 14 125
This is the desired result:
SKU Mkt Week Cost Code
ABC 05 1 10 100
ABC 05 2 12 100
DEF 05 3 25 125
XYZ 08 1 10 100
XZY 08 2 14 125
So if a SKU\Mkt\Week\Cost exist more than once, I want to keep the record where code = 125 and delete the row where the code is 100.
I'm using the below Cte:
;WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER( PARTITION BY SKU, Mkt, Week
ORDER BY SKU, Mkt, Week)
FROM [table]
WHERE code = 100
)
DELETE FROM CTE
WHERE RN > 1
However, the Cte doesnot delete anything -what am I missing?
Based on the query and sample data you have provided, You need to note to this section of the cte inner query:
WHERE code = 100
when this filter applied you have the following data:
SKU Mkt Week Cost Code
ABC 05 1 10 100
ABC 05 2 12 100
DEF 05 3 20 100
which will get the 1 as Row_Number()'s output!, so running the following query will not effect any rows:
DELETE FROM CTE
WHERE RN > 1
To achieve the desired result you need to remove the WHERE section in CTE's inner query.
;WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER( PARTITION BY SKU, Mkt, Week
ORDER BY SKU, Mkt, Week, Cost DESC) --Code/Cost DESC <==== Note this too
FROM [table]
--WHERE code = 100 <========== HERE, I've commented it
)
DELETE FROM CTE
WHERE RN > 1
You need to also add the Cost DESC or Code Desc to Row_Number()'s Order By section.
Ranking function will be evaluated in the select statement , which means the where clause WHERE code = 100 is evaluated before ROW_NUMBER() and so it has already removed the rows with code 125. Use order by Code as well and then apply the code=100 check when deleting from the CTE
;WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER( PARTITION BY SKU, Mkt, Week
ORDER BY SKU, Mkt, Week,Code DESC)
FROM tt1
)
DELETE FROM CTE
WHERE RN > 1
AND CODE = 100
Try below query to get the desired result -
Sample data and Query
Declare #Table table
(SKU varchar(20), Mkt int, [Week] int, Cost int, Code int)
Insert into #Table
values
( 'ABC', 05 , 1, 10 , 100),
( 'ABC' , 05 , 2 , 12 , 100),
('DEF' ,05 , 3 , 20 , 100),
('DEF' ,05 , 3 ,25 , 125),
('XYZ' , 08 , 1 ,10 , 100),
('XYZ' , 08 , 2 ,12 , 100),
('XYZ' , 08, 2 ,14, 125)
;WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER( PARTITION BY SKU, Mkt, Week
ORDER BY SKU, Mkt, Week, code desc)
FROM #Table
)
delete from Cte where RN > 1
Along with moving your Where statement, I believe you also want a second cte to work with the records you are identifying... In the following your first cte identifies the duplicate records while the second cte isolates them so you can perform your delete against those SKUs
Table
Create Table #tbl
(
SKU VarChar(10),
Mkt VarChar(10),
Week Int,
Cost Int,
Code Int
)
Insert Into #tbl Values
('ABC','05',1,10,100),
('ABC','05',2,12,100),
('DEF','05',3,20,100),
('DEF','05',3,25,125),
('XYZ','08',1,10,100),
('XYZ','08',2,12,100),
('XYZ','08',2,14,125)
Query
;WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER( PARTITION BY SKU, Mkt, Week
ORDER BY SKU, Mkt, Week)
FROM #tbl
--WHERE code = 100
)
, cte1 As
(
Select sku from cte where rn > 1
)
DELETE c FROM CTE c inner join cte1 c1 On c.SKU = c1.SKU
WHERE c.Code = 100
Select * From #tbl
Result (Your 'desired result' example removed an XYZ record where the week was not duplicated?)
SKU Mkt Week Cost Code
ABC 05 1 10 100
ABC 05 2 12 100
DEF 05 3 25 125
XYZ 08 1 10 100
XYZ 08 2 12 100
XZY 08 2 14 125
Your CTE statement is only considering rows with code = 100. If you remove it, then CTE will rank based on all rows from the table. Using this, first find out which combination of have multiple rows. Then, among these combinations, identify rows with code = 100 and delete them.
create table #e1
(
SKU varchar(50)
,Mkt varchar(50)
,_Week int
,Cost int
,_code int
)
insert into #e1(SKU, Mkt, _Week, Cost, _code)
select 'ABC', '05', 1, 10, 100 UNION
SELECT 'ABC', '05', 2, 12, 100 union
SELECT 'DEF', '05', 3, 20, 100 UNION
SELECT 'DEF', '05', 3, 25, 125 UNION
SELECT 'XYZ', '08', 1, 10, 100 UNION
SELECT 'XYZ', '08', 2, 12, 100 UNION
SELECT 'XZY', '08', 2, 14, 125
delete s
from
#e1 s
JOIN
(
SELECT SKU, Mkt, _Week
FROM #e1
group by
SKU, Mkt, _Week
having count(1) > 1
) m
ON
s.SKU = m.sku and s.mkt = m.mkt and s._Week = m._Week
WHERE s._code = 100
Create table #tab1 (SKU varchar(50),Mkt varchar(50),[Week] varchar(50),Cost varchar(50),Code varchar(50))
insert into #tab1
select 'ABC','05','1','10','100'
union
select 'ABC','05','2','12','100'
union
select 'DEF','05','3','20','100'
union
select 'DEF','05','3','25','125'
union
select 'XYZ','08','1','10','100'
union
select 'XYZ','08','2','12','100'
union
select 'XYZ','08','2','14','125'
delete t from #tab1 t
inner join (select t1.SKU,t1.Mkt,t1.[Week],t1.Cost as Cost,t1.Code as Code,ROW_NUMBER()over(partition by t1.SKU,t1.Mkt,t1.[Week] order by t1.Cost desc,t1.Code desc ) as rno
from #tab1 t1
) c on c.SKU = t.SKU and c.Mkt = t.Mkt and c.Cost = t.Cost and c.[Week] = t.[Week] and c.Code = t.Code
where c.rno = 2
select * from #tab1
Output:
SKU Mkt Week Cost Code
ABC 05 1 10 100
ABC 05 2 12 100
DEF 05 3 25 125
XYZ 08 1 10 100
XYZ 08 2 14 125

Get Records depend on their sum value

I have a SQL Server table which have records like this
ID | Value
1 | 100
2 | 150
3 | 250
4 | 600
5 | 1550
6 | 50
7 | 300
I need to select random records, but the only condition is that the total sum of this records value achieve a specific number or percentage i define.
let's say i need a total value of 300 or 10%, so here are the chances
1 | 100
2 | 150
6 | 50
or
3 | 250
6 | 50
or
7 | 300
can any one help me to do this.
Think this recursive CTE works, no idea what the performance will be like though once you get past a trivial amount of rows:
DECLARE #Test TABLE
(
ID INT NOT NULL,
VAL INT NOT NULL
);
INSERT INTO #Test
VALUES (1,100),
(2,150),
(3,250),
(4,600),
(5,1550),
(6,50),
(7,300);
DECLARE #SumValue INT = 300,
#Percentage INT = 10;
WITH GetSums
AS
(
SELECT T.ID,
T.Val,
CAST(T.ID AS VARCHAR(MAX)) AS IDs
FROM #Test AS T
UNION ALL
SELECT T1.ID,
T1.Val + GS.Val AS Val,
CAST(T1.ID AS VARCHAR(MAX)) + ',' + GS.IDs AS IDs
FROM #Test AS T1
INNER
JOIN GetSums AS GS
ON T1.ID > GS.ID
)
SELECT GS.IDs,
GS.Val
FROM GetSums AS GS
WHERE (GS.Val = #SumValue OR GS.VAL = (SELECT SUM(Val) FROM #Test AS T) / #Percentage)
OPTION (MAXRECURSION 50);
Similar found here:
find all combination where Total sum is around a number
Try this...we will get the correct answer if the 6th value is 250...
SELECT 1 ID, 100 Value
INTO #Temp_1
UNION ALL SELECT 2 , 150
UNION ALL SELECT 2 , 150
UNION ALL SELECT 3 , 250
UNION ALL SELECT 4 , 600
UNION ALL SELECT 5 , 1550
UNION ALL SELECT 6 , 250
UNION ALL SELECT 7 , 300
CREATE TABLE #Temp_IDs
(
ID Int,
Value Numeric(18,2)
)
DELETE
FROM #Temp_IDs
DECLARE #ID Int,
#Vale Numeric(18,2),
#ContinueYN Char(1)
SET #ContinueYN = 'Y'
IF EXISTS (SELECT TOP 1 1 FROM #Temp_1
WHERE Value <= 300
AND ID NOT IN (SELECT ID FROM #Temp_IDs )
AND Value <= (SELECT 300 - ISNULL( SUM(Value),0) FROM #Temp_IDs)
ORDER BY NEWID())
BEGIN
WHILE (#ContinueYN = 'Y')
BEGIN
SELECT #ID = ID,
#Vale = Value
FROM #Temp_1
WHERE Value <= 300
AND ID NOT IN (SELECT ID FROM #Temp_IDs )
AND Value <= (SELECT 300 - ISNULL( SUM(Value),0) FROM #Temp_IDs)
ORDER BY NEWID()
INSERT INTO #Temp_IDs
SELECT #ID,#Vale
IF (SELECT SUM(Value) FROM #Temp_IDs) = 300
BREAK
ELSE IF #ID IS NULL
BEGIN
DELETE FROM #Temp_IDs
END
SET #ID = NULL
SET #Vale = NULL
END
END
SELECT *
FROM #Temp_IDs
DROP TABLE #Temp_IDs
DROP TABLE #Temp_1

how to get rows with null values and value

Interviewer asked me the following question:
consider the below table :
tpid data
100 1
100 2
100 NULL
101 6
101 5
101 NULL
102 NULL
103 9
103 65
104 NULL
..
..
If the tpid has got any data then display the data and not null value
but if the tpid has got only null then only display null against the id.
The result set should be like this :
tpid data
100 1
100 2
101 6
101 5
102 NULL
103 9
103 65
104 NULL
I wrote the following query but it doesn't give the desired result :
;with cte as
(select tpid,count(data) as num from a group by TPID)
select a.TPID,
(case when cte.num=0 then NULL else a.DATA end)col
from cte
join A on a.TPID=cte.TPID
DECLARE #temp TABLE(tpid int, data int)
INSERT INTO #temp
SELECT 100, 1 UNION
SELECT 100, 2UNION
SELECT 100, NULL UNION
SELECT 101, 6 UNION
SELECT 101, 5 UNION
SELECT 101, NULL UNION
SELECT 102, NULL UNION
SELECT 103, 9 UNION
SELECT 103, 65 UNION
SELECT 104, NULL
SELECT * FROM #temp a WHERE
data IS not NULL OR (select COUNT(1) FROM #temp WHERE tpid=a.tpid)=1
Ans:
tpid data
100 1
100 2
101 5
101 6
102 NULL
103 9
103 65
104 NULL
You can use windowed functions to get what you want:
SELECT tpid, data
FROM (
SELECT tpid, data,
COUNT(*) OVER (PARTITION BY tpid) AS cnt,
COUNT(CASE WHEN [data] IS NULL THEN 1 END)
OVER (PARTITION BY tpid) AS cntNulls
FROM mytable) AS t
WHERE (data IS NOT NULL) OR (cnt = cntNulls)
NULL values are filtered out, unless there is nothing but NULL values in a tpid slice.
Demo here
Alternatively you can use the following query:
SELECT t1.tpid, t1.data
FROM mytable AS t1
LEFT JOIN (
SELECT tpid
FROM mytable
GROUP BY tpid
HAVING COUNT(*) = COUNT(CASE WHEN data IS NULL THEN 1 END)
) AS t2 ON t1.tpid = t2.tpid
WHERE (t1.data IS NOT NULL) OR (t2.tpid IS NOT NULL)
Demo here
select tpid,data from (SELECT tpid, data,
COUNT(*) OVER (PARTITION BY tpid) AS cnt,
COUNT(CASE WHEN [data] IS NULL THEN 1 END)
OVER (PARTITION BY tpid) AS cntNulls
FROM mytable) t
where t.cntNulls = t.cnt or data is not null

sql server : count records

I have a tableA (ID int, Match varchar, tot int)
ID Match Tot
1 123
2 123
3 12
4 12
5 4
6 12
7 8
Now, I want to calculate Tot which is total number of match exists in the table. for example 123 occured twice, 12 exist thrice and so on. Also note that I want the count only at first match. here is the expected result.:
ID Match Tot
1 123 2
2 123
3 12 3
4 12
5 4 1
6 12
7 8 1
Another case:
ID Match Count Tot
1 123 2
2 123 1
3 12 10
4 12 10
5 4 3
6 12 5
7 8 7
Now I want to add the count for the same match. expected result:
ID Match Count Tot
1 123 2 3
2 123 1
3 12 10 25
4 12 10
5 4 3 3
6 12 5
7 8 7 7
Thanks
WITH tableA(ID, Match) AS
(
SELECT 1,123 UNION ALL
SELECT 2,123 UNION ALL
SELECT 3,12 UNION ALL
SELECT 4,12 UNION ALL
SELECT 5,4 UNION ALL
SELECT 6,12 UNION ALL
SELECT 7,8
)
SELECT *,
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Match ORDER BY ID) = 1
THEN COUNT(*) OVER (PARTITION BY Match)
END AS Tot
FROM tableA
ORDER BY ID
SELECT match, COUNT(match ) as Tot
FROM tableA
GROUP BY match
Solution 1:
DECLARE #MyTable TABLE
(
ID INT PRIMARY KEY
,Match VARCHAR(10) NOT NULL
,Tot INT NULL
);
INSERT #MyTable(ID, Match)
SELECT 1, 123
UNION ALL
SELECT 2, 123
UNION ALL
SELECT 3, 12
UNION ALL
SELECT 4, 12
UNION ALL
SELECT 5, 4
UNION ALL
SELECT 6, 12
UNION ALL
SELECT 7, 8;
--SELECT
SELECT *
,CASE
WHEN ROW_NUMBER()OVER(PARTITION BY a.Match ORDER BY a.ID ASC)=1
THEN COUNT(*)OVER(PARTITION BY a.Match)
END TotCalculated
FROM #MyTable a;
--UPDATE
WITH MyCTE
AS
(
SELECT a.Tot
,CASE
WHEN ROW_NUMBER()OVER(PARTITION BY a.Match ORDER BY a.ID ASC)=1
THEN COUNT(*)OVER(PARTITION BY a.Match)
END TotCalculated
FROM #MyTable a
)
UPDATE MyCTE
SET Tot = TotCalculated;
SELECT *
FROM #MyTable;
Solution 2:
UPDATE #MyTable
SET Tot = NULL;
SELECT x.ID, y.Num
FROM
(
SELECT b.Match, MIN(b.ID) ID
FROM #MyTable b
GROUP BY b.Match
) x INNER JOIN
(
SELECT a.Match, COUNT(*) AS Num
FROM #MyTable a
GROUP BY a.Match
) y ON x.Match = y.Match
ORDER BY x.ID
UPDATE #MyTable
SET Tot = t.Num
FROM #MyTable z
INNER JOIN
(
SELECT x.ID, y.Num
FROM
(
SELECT b.Match, MIN(b.ID) ID
FROM #MyTable b
GROUP BY b.Match
) x INNER JOIN
(
SELECT a.Match, COUNT(*) AS Num
FROM #MyTable a
GROUP BY a.Match
) y ON x.Match = y.Match
) t ON z.ID = t.ID;
SELECT *
FROM #MyTable;

How to write a query to produce counts for arbitrary value bands?

My table had 3 fields: id and unit. I want to count how many ids have <10, 10-49, 50-100 etc units. The final result should look like:
Category | countIds
<10 | 1516
10 - 49 | 710
50 - 99 | 632
etc.
This is the query that returns each id and how many units it has:
select id, count(unit) as numUnits
from myTable
group by id
How can I build on that query to give me the category, countIds result?
create temporary table ranges (
seq int primary key,
range_label varchar(10),
lower int,
upper int
);
insert into ranges values
(1, '<10', 0, 9),
(2, '10 - 49', 10, 49),
(3, '50 - 99', 50, 99)
etc.
select r.range_label, count(c.numUnits) as countIds
from ranges as r
join (
select id, count(unit) as numUnits
from myTable
group by id) as c
on c.numUnits between r.lower and r.upper
group by r.range_label
order by r.seq;
edit: changed sum() to count() above.
select category_bucket, count(*)
from (select case when category < 10 then "<10"
when category >= 10 and category <= 49 then "10 - 49"
when category >= 50 and category <= 99 then "50 - 99"
else "100+"
end category_bucket, num_units
from my_table)
group by category_bucket
A dynamically grouped solution is much harder.
SELECT id, countIds
FROM (
SELECT id
, 'LESS_THAN_TEN' CATEGORY
, COUNT(unit) countIds
FROM table1
GROUP BY ID
HAVING COUNT(UNIT) < 10
UNION ALL
SELECT id
, 'BETWEEN_10_AND_49' category
, COUNT(unit) countIds
FROM table1
GROUP BY ID
HAVING COUNT(UNIT) BETWEEN 10 AND 49
UNION ALL
SELECT id
, 'BETWEEN_50_AND_99' category
, COUNT(unit) countIds
FROM table1
GROUP BY id
HAVING COUNT(UNIT) BETWEEN 50 AND 99
) x
Giving an example for one range: (10 - 49)
select count(id) from
(select id, count(unit) as numUnits from myTable group by id)
where numUnits >= '10' && numUnits <= '49'
It's not precisely what you want, but you could use fixed ranges, like so:
select ' < ' || floor(id / 50) * 50, count(unit) as numUnits
from myTable
group by floor(id / 50) * 50
order by 1
Try this working sample in SQL Server TSQL
SET NOCOUNT ON
GO
WITH MyTable AS
(
SELECT 00 as Id, 1 Value UNION ALL
SELECT 05 , 2 UNION ALL
SELECT 10 , 3 UNION ALL
SELECT 15 , 1 UNION ALL
SELECT 20 , 2 UNION ALL
SELECT 25 , 3 UNION ALL
SELECT 30 , 1 UNION ALL
SELECT 35 , 2 UNION ALL
SELECT 40 , 3 UNION ALL
SELECT 45 , 1 UNION ALL
SELECT 40 , 3 UNION ALL
SELECT 45 , 1 UNION ALL
SELECT 50 , 3 UNION ALL
SELECT 55 , 1 UNION ALL
SELECT 60 , 3 UNION ALL
SELECT 65 , 1 UNION ALL
SELECT 70 , 3 UNION ALL
SELECT 75 , 1 UNION ALL
SELECT 80 , 3 UNION ALL
SELECT 85 , 1 UNION ALL
SELECT 90 , 3 UNION ALL
SELECT 95 , 1 UNION ALL
SELECT 100 , 3 UNION ALL
SELECT 105 , 1 UNION ALL
SELECT 110 , 3 UNION ALL
SELECT 115 , 1 Value
)
SELECT Category, COUNT (*) CountIds
FROM
(
SELECT
CASE
WHEN Id BETWEEN 0 and 9 then '<10'
WHEN Id BETWEEN 10 and 49 then '10-49'
WHEN Id BETWEEN 50 and 99 then '50-99'
WHEN Id > 99 then '>99'
ELSE '0' END as Category
FROM MyTable
) as A
GROUP BY Category
This will give you the following result
Category CountIds
-------- -----------
<10 2
>99 4
10-49 10
50-99 10