How can I find duplicate consecutive values in this table? - sql

Say I have a table which I query like so:
select date, value from mytable order by date
and this gives me results:
date value
02/26/2009 14:03:39 1
02/26/2009 14:10:52 2 (a)
02/26/2009 14:27:49 2 (b)
02/26/2009 14:34:33 3
02/26/2009 14:48:29 2 (c)
02/26/2009 14:55:17 3
02/26/2009 14:59:28 4
I'm interested in the rows of this result set where the value is the same as the one in the previous or next row, like row b which has value=2 the same as row (a). I don't care about rows like row (c) which has value=2 but does not come directly after a row with value=2. How can I query the table to give me all rows like (a) and (b) only? This is on Oracle, if it matters.

Use the lead and lag analytic functions.
create table t3 (d number, v number);
insert into t3(d, v) values(1, 1);
insert into t3(d, v) values(2, 2);
insert into t3(d, v) values(3, 2);
insert into t3(d, v) values(4, 3);
insert into t3(d, v) values(5, 2);
insert into t3(d, v) values(6, 3);
insert into t3(d, v) values(7, 4);
select d, v, case when v in (prev, next) then '*' end match, prev, next from (
select
d,
v,
lag(v, 1) over (order by d) prev,
lead(v, 1) over (order by d) next
from
t3
)
order by
d
;
Matching neighbours are marked with * in the match column,

This is a simplified version of #Bob Jarvis' answer, the main difference being the use of just one subquery instead of four,
with f as (select row_number() over(order by d) rn, d, v from t3)
select
a.d, a.v,
case when a.v in (prev.v, next.v) then '*' end match
from
f a
left join
f prev
on a.rn = prev.rn + 1
left join
f next
on a.rn = next.rn - 1
order by a.d
;

As #Janek Bogucki has pointed out LEAD and LAG are probably the easiest way to accomplish this - but just for fun let's try to do it by using only basic join operations:
SELECT mydate, VALUE FROM
(SELECT a.mydate, a.value,
CASE WHEN a.value = b.value THEN '*' ELSE NULL END AS flag1,
CASE WHEN a.value = c.value THEN '*' ELSE NULL END AS flag2
FROM
(SELECT ROWNUM AS outer_rownum, mydate, VALUE
FROM mytable
ORDER BY mydate) a
LEFT OUTER JOIN
(select ROWNUM-1 AS inner_rownum, mydate, VALUE
from mytable
order by myDATE) b
ON b.inner_rownum = a.outer_rownum
LEFT OUTER JOIN
(select ROWNUM+1 AS inner_rownum, mydate, VALUE
from mytable
order by myDATE) c
ON c.inner_rownum = a.outer_rownum
ORDER BY a.mydate)
WHERE flag1 = '*' OR
flag2 = '*';
Share and enjoy.

Related

sql generate code based on three column values

I have three columns
suppose
row no column1 column2 column3
1 A B C
2 A B C
3 D E F
4 G H I
5 G H C
I want to generate code by combining these three column values
For Eg.
1)ABC001
2)ABC002
3)DEF001
4)GHI001
5)GHC001
by checking combination of three columns
logic is that
if values of three columns are same then like first time it shows 'ABC001'
and 2nd time it shows 'ABC002'
You can try this:
I dont know what you want for logic with 00, but you can add them manuel or let the rn decide for you
declare #mytable table (rowno int,col1 nvarchar(50),col2 nvarchar(50),col3 nvarchar(50)
)
insert into #mytable
values
(1,'A', 'B', 'C'),
(2,'A', 'B', 'C'),
(3,'D', 'E', 'F'),
(4,'G', 'H', 'I'),
(5,'G', 'H', 'C')
Select rowno,col1,col2,col3,
case when rn >= 10 and rn < 100 then concatcol+'0'+cast(rn as nvarchar(50))
when rn >= 100 then concatcol+cast(rn as nvarchar(50))
else concatcol+'00'+cast(rn as nvarchar(50)) end as ConcatCol from (
select rowno,col1,col2,col3
,Col1+col2+col3 as ConcatCol,ROW_NUMBER() over(partition by col1,col2,col3 order by rowno) as rn from #mytable
) x
order by rowno
My case when makes sure when you hit number 10 it writes ABC010 and when it hits above 100 it writes ABC100 else if its under 10 it writes ABC001 and so on.
Result
TSQL: CONCAT(column1,column2,column3,RIGHT(REPLICATE("0", 3) + LEFT(row_no, 3), 3))
You should combine your columns like below :
SELECT CONVERT(VARCHAR(MAX), ROW_NUMBER() OVER(ORDER BY
(
SELECT NULL
)))+') '+DATA AS Data
FROM
(
SELECT column1+column2+column3+'00'+CONVERT(VARCHAR(MAX), ROW_NUMBER() OVER(PARTITION BY column1,
column2,
column3 ORDER BY
(
SELECT NULL
))) DATA
FROM <table_name>
) T;
Result :
1)ABC001
2)ABC002
3)DEF001
4)GHI001
5)GHC001
MySQL:
CONCAT(column1,column2,column3,LPAD(row_no, 3, '0'))
[you will need to enclose the 'row no' in ticks if there is a space in the name of the field instead of underscore.]

ORACLE sum inside a case statement

Hi I need the result of this. so if a entityID matches to a value I need the sum of certain column.I am getting an expression missing error. Can someone point me to where the error is?
Thanks.
SELECT
p.jobTitle,
p.department,
p.person,
ufr.meets,
ufr.exceeds,
CASE
WHEN ufr.entityid = 'AHT' THEN (AD.acdcalls + AD.daacdcalls)
WHEN ufr.entityid = 'ACW' THEN (AD.acdcalls + AD.daacdcalls)
WHEN ufr.entityid = 'Adherence' THEN SUM(AA.totalSched)
WHEN ufr.entityid = 'Conformance' THEN SUM(AS.minutes)
ELSE null
END as weight,
(weight * meets) AS weightedMeets,
(weight * exceeds) AS weightedExceeds
FROM M_PERSON p
JOIN A_TMP5408_UNFLTRDRESULTSAG ufr
ON ufr.department = p.department AND ufr.jobTitle = p.jobTitle
LEFT JOIN M_AvayaDAgentChunk AD
ON AD.person = p.person and ufr.split = AD.split
LEFT JOIN M_AgentAdherenceChunk AA
ON AA.person = p.person
LEFT JOIN M_AgentScheduleChunk AS
ON AS.person = p.person
GROUP BY
p.person,
p.department,
p.jobTitle,
ufr.meets,
ufr.exceeds,
weight,
weightedMeets,
weightedExceeds
As well as the issues mentioned by #GordonLinoff (that AS is a keyword) and #DCookie (you need entityid in the group-by):
you also need acdcalls and daacdcalls in the group-by (unless you can aggregate those);
you can't refer to a column alias in the same level of query, so (weight * meets) AS weightedMeets isn't allowed - you've just define what weight is, in the same select list. You need to use an inline view, or a CTE, if you don't want to repeat the case logic.
I think this does what you want:
SELECT
jobTitle,
department,
person,
meets,
exceeds,
weight,
(weight * meets) AS weightedMeets,
(weight * exceeds) AS weightedExceeds
FROM
(
SELECT
MP.jobTitle,
MP.department,
MP.person,
ufr.meets,
ufr.exceeds,
CASE
WHEN ufr.entityid = 'AHT' THEN (MADAC.acdcalls + MADAC.daacdcalls)
WHEN ufr.entityid = 'ACW' THEN (MADAC.acdcalls + MADAC.daacdcalls)
WHEN ufr.entityid = 'Adherence' THEN SUM(MAAC.totalSched)
WHEN ufr.entityid = 'Conformance' THEN SUM(MASC.minutes)
ELSE null
END as weight
FROM M_PERSON MP
JOIN A_TMP5408_UNFLTRDRESULTSAG ufr
ON ufr.department = MP.department AND ufr.jobTitle = MP.jobTitle
LEFT JOIN M_AvayaDAgentChunk MADAC
ON MADAC.person = MP.person and ufr.split = MADAC.split
LEFT JOIN M_AgentAdherenceChunk MAAC
ON MAAC.person = MP.person
LEFT JOIN M_AgentScheduleChunk MASC
ON MASC.person = MP.person
GROUP BY
MP.person,
MP.department,
MP.jobTitle,
ufr.meets,
ufr.exceeds,
ufr.entityid,
MADAC.acdcalls,
MADAC.daacdcalls
);
Your fist two case branches could be combined since the calculation is the same, but will work either way.
In addition to the alias issue identified by Gordon, I think you'll find you need to use an aggregate function in all the THEN clauses of your CASE statement, and that you need to GROUP BY ufr.entityid as well. Otherwise you'll start getting ora-00979 errors (not a GROUP BY expression). If you don't want the aggregate function in all clauses, then you'll have to group by the expressions you're summing as well.
Small illustration:
CREATE TABLE tt (ID varchar2(32), sub_id varchar2(32), x NUMBER, y NUMBER);
INSERT INTO tt VALUES ('ID1', 'A', 1, 6);
INSERT INTO tt VALUES ('ID1', 'B', 1, 7);
INSERT INTO tt VALUES ('ID2', 'A', 2, 6);
INSERT INTO tt VALUES ('ID2', 'B', 2, 7);
INSERT INTO tt VALUES ('ID3', 'A', 3, 6);
INSERT INTO tt VALUES ('ID3', 'B', 3, 7);
INSERT INTO tt VALUES ('ID3', 'C', 3, 8);
SELECT ID, CASE WHEN sub_id = 'A' THEN SUM(y)
WHEN sub_id = 'B' THEN SUM(x)
ELSE (x + y) END tst
FROM tt
GROUP BY ID
ORA-00979: not a GROUP BY expression (points at sub_id in WHEN)
SELECT ID, CASE WHEN sub_id = 'A' THEN SUM(y)
WHEN sub_id = 'B' THEN SUM(x)
ELSE (x + y) END tst
FROM tt
GROUP BY ID, sub_id
ORA-00979: not a GROUP BY expression (points at x in ELSE)
SQL> SELECT ID, CASE WHEN sub_id = 'A' THEN SUM(y)
2 WHEN sub_id = 'B' THEN SUM(x)
3 ELSE SUM(x + y) END tst
4 FROM tt
5 GROUP BY ID, sub_id;
ID TST
-------------------------------- ----------
ID1 6
ID3 6
ID3 3
ID1 1
ID2 6
ID2 2
ID3 11

Delete rows in table that are sum of other rows per group

Group rows by T, and in each group find the row that is the largest or smallest (if values are negative) sum of other rows from that group, and delete that row (one for each group), if group does not have enough elements to find sum or enough but none of the rows indicates sum of others nothing happens
CREATE TABLE Test (
T varchar(10),
V int
);
INSERT INTO Test
VALUES ('A', 4),
('B', -5),
('C', 5),
('A', 2),
('B', -1),
('C', 10),
('A', 2),
('B', -4),
('C', 5),
('D', 0);
expected result:
A 2
A 2
B -1
B -4
C 5
C 5
D 0
Like the comments, the requirements seem strange. The below code assumes that the summing is already pre-populated and merely removes the largest/smallest as long as the highest value is not 0.
if object_id('tempdb..#test') is not null
drop table #test
CREATE TABLE #Test (
T varchar(10),
V int
);
INSERT INTO #Test
VALUES ('A', 4), ('B', -5), ('C', 5), ('A', 2), ('B', -1), ('C', 10), ('A', 2), ('B', -4), ('C', 5), ('D', 0);
if object_id('tempdb..#test2') is not null
drop table #test2
SELECT
T,
V,
ABS(V) as absV
INTO #TEST2
FROM #TEST
SELECT * FROM #TEST2
if object_id('tempdb..#max') is not null
drop table #max
SELECT
T,
MAX(absV) AS MaxAbsV
INTO #Max
FROM #TEST2
GROUP BY T
HAVING MAX(AbsV) != 0
DELETE #TEST2
FROM #TEST2
INNER JOIN #MAX ON #TEST2.T = #MAX.T AND #TEST2.absV = #Max.MaxAbsV
SELECT * FROM #TEST2
ORDER BY T ASC
; with cte as
(
select T, V,
R = row_number() over (partition by T order by ABS(V) desc),
C = count(*) over (partition by T)
from Test
)
delete c
from cte c
inner join
(
select T, S = sum(V)
from cte
where R <> 1
group by T
) s on c.T = s.T
where c.C >= 3
and c.R = 1
and c.V = s.S
Using ABS and NOT Exists
DECLARE #Test TABLE (
T varchar(10),
V int
);
INSERT INTO #Test
VALUES ('A', 4), ('B', -5), ('C', 5), ('A', 2), ('B', -1), ('C', 10), ('A', 2), ('B', -4), ('C', 5), ('D', 0);
;WITH CTE as (
select T,max(ABS(v ))v from #Test
WHERE V <> 0
GROUP BY T )
SELECT T,V FROM #Test T where NOT exists (Select 1 FROM cte WHERE T = T.T AND v = ABS(T.V) )
ORDER BY T.T
Determine first if the rows are positive or negative by checking if SUM(V) is positive. And then determine if the smallest or largest value is equal to the SUM of the other rows, by subtracting from SUM(V) the MIN(V) if negative or MAX(V) if positive:
DELETE t
FROM Test t
INNER JOIN (
SELECT
T,
SUM(V) - CASE WHEN SUM(V) >= 0 THEN MAX(V) ELSE MIN(V) END AS ToDelete
FROM Test
GROUP BY T
HAVING COUNT(*) >= 3
) a
ON a.T = t.T
AND a.ToDelete = t.V
ONLINE DEMO
You can use the below query to get the required output :-
select * into #t1 from test
select * from
(
select TT.T as T,TT.V as V
from test TT
JOIN
(select T,max(abs(V)) as V from #t1
group by T) P
on TT.T=P.T
where abs(TT.V) <> P.V
UNION ALL
select A.T as T,A.V as V from test A
JOIN(
select T,count(T) as Tcount from test
group by T
having count(T)=1) B on A.T=B.T
) X order by T
drop table #t1
You are looking for a value per group that is the sum of all the group's other values. E.g. 4 of (2,2,4) or -5 of (-5,-4,-1).
This is usually only one record per group. But it can be multiple times the same number. Here are examples for ties: (0,0) or (-2,2,4,4), or (-2,-2,4,4,4) or (-10,3,3,3,3,4).
As you see, you are looking in any way for values that equal half of the group's total sum. (Of course. We are looking for n+n, where one n is in one record and the other n is the sum of all the other records.)
The only special case is when there is only one value in the group which is zero. That we don't want to delete of course.
Here is an update statement that cannot deal with ties, but would delete all maximum values instead of just one:
delete from test
where 2 * v =
(
select case when count(*) = 1 then null else sum(v) end
from test fullgroup
where fullgroup.t = test.t
);
In order to deal with ties you would need artificial row numbers, so as to delete only one record of all candidates.
with candidates as
(
select t, v, row_number() over (partition by t order by t) as rn
from
(
select
t, v,
sum(v) over (partition by t) as sumv,
count(*) over (partition by t) as cnt
from test
) comparables
where sumv = 2 * v and cnt > 1
)
delete
from candidates
where rn = 1;
SQL fiddle: http://sqlfiddle.com/#!6/6d97e/1
See if below query helps:
DELETE [Audit].[dbo].[Test] FROM [Audit].[dbo].[Test] as AA
INNER JOIN (select T,
CASE
WHEN MAX(V) < 0 THEN MIN(V)
WHEN MIN(V) > 0 THEN MAX(V) ELSE MAX(V)
END as MAX_V,
CASE
WHEN SUM(V) > 0 THEN SUM(V) - MAX(V)
WHEN SUM(V) < 0 THEN SUM(V) - MIN(V) ELSE SUM(V)
END as SUM_V_REST
from [Audit].[dbo].[Test]
Group by T
Having Count(V) > 1) as BB ON AA.T = BB.T and AA.V = BB.MAX_V

SQL difficult calculation

Asume we have a table with columns A,B,C, where A has data type of a String, B and C are both Integers. I need to make another table from this one with 4 columns - A, B, sum(C) and D, where D is the sum of values in C with the same value in column A (as this one) and value in B less by 1 (than this one). For example, for given values "a" at A and 1 at B it should return (in column D) the sum of all of the values in C where value at A is "a" and value at B is zero.
I think you are looking for something similar to this query:
declare #t table (a varchar(1), b int, c int)
insert #t values
('a', 1, 1),
('a', 2, 2),
('b', 3, 3),
('a', 4, 4)
select t.a, t.b,
(
select sum(t1.c)
from #t t1
) c,
(
select isnull(sum(t2.c), 0)
from #t t2
where t2.a = t.a and t2.b = t.b - 1
)
from #t t

Creating a somewhat complicated cartesian result

I have a table called "where_clauses" which contains a bunch of conditions I would like to use for building dynamic queries. I would like to know all possible queries I could perform using this data. Here is my "where_clauses" data...
INSERT INTO where_clauses (id,col_name,clause) VALUES (1,'x','x < 1');
INSERT INTO where_clauses (id,col_name,clause) VALUES (2,'x','x < 2');
INSERT INTO where_clauses (id,col_name,clause) VALUES (3,'x','x < 3');
INSERT INTO where_clauses (id,col_name,clause) VALUES (4,'y','y < 1');
INSERT INTO where_clauses (id,col_name,clause) VALUES (5,'y','y < 2');
INSERT INTO where_clauses (id,col_name,clause) VALUES (6,'y','y < 3');
INSERT INTO where_clauses (id,col_name,clause) VALUES (7,'z','z < 1');
Ideally I would like the "all possible queries" in the form of an array of ids. For example, the "all possible queries" result would be...
{1}
{1,4}
{1,4,7}
{1,5}
{1,5,7}
{1,6}
{1,6,7}
{2}
{2,4}
{2,4,7}
{2,5}
{2,5,7}
{2,6}
{2,6,7}
{3}
{3,4}
{3,4,7}
{3,5}
{3,5,7}
{3,6}
{3,6,7}
{4}
{4,7}
{5}
{5,7}
{6}
{6,7}
{7}
Note that im throwing out joining on equal columns. What is a query that would give all possible where_clauses?
This is the sort of problem that the new WITH RECURSIVE is intended to solve. The following generalizes to any number of column names (not just x, y, z).
WITH RECURSIVE subq(a, x) AS
( VALUES (ARRAY[]::int[], NULL) /* initial */
UNION ALL
SELECT subq.a || id, col_name FROM subq JOIN where_clauses
ON x IS NULL OR x < col_name )
SELECT a FROM subq
WHERE x IS NOT NULL; /* discard the initial empty array */
SELECT string_to_array(TRIM(x || ',' || y || ',' || z, ','), ',')
FROM (
WITH sq AS (
SELECT a.id x, b.id y, c.id z
FROM where_clauses a, where_clauses b, where_clauses c
WHERE a.col_name != b.col_name AND
a.col_name != c.col_name AND
b.col_name != c.col_name AND
a.id < b.id AND
b.id < c.id
)
SELECT x, y, z FROM sq
UNION ALL
SELECT distinct x, y, null::int FROM sq
UNION ALL
SELECT distinct y, z, null::int FROM sq
UNION ALL
SELECT distinct x, null::int, null::int FROM sq
UNION ALL
SELECT distinct y, null::int, null::int FROM sq
UNION ALL
SELECT distinct z, null::int, null::int FROM sq
) ORDER BY 1;
Does above query helps you out ?
Try this code, it selects three columns, those that are not used for a clause are left NULL, you could concatenate or manipulate that result further:
--all possibilities with only one clause
SELECT
id AS ID1, NULL ID2, NULL AS ID3
FROM where_clauses
--all possibilities with two clauses (xy,xz,yz)
UNION
SELECT
WC1.id AS ID1, WC2.id AS ID2, NULL AS ID3
FROM where_clauses WC1
CROSS JOIN where_clauses WC2
WHERE
WC1.col_name != WC2.col_name
AND WC1.id > WC2.id
--all possibilities with an x and a y and a z clause
UNION
SELECT
WC1.id AS ID1, WC2.id AS ID2, WC3.id AS ID3
FROM where_clauses WC1
CROSS JOIN where_clauses WC2
CROSS JOIN where_clauses WC3
WHERE
WC1.col_name != WC2.col_name
AND WC1.id > WC2.id
AND WC1.col_name != WC3.col_name
AND WC1.id > WC3.id
AND WC2.col_name != WC3.col_name
AND WC2.id > WC3.id
Here is a fiddle.
EDIT: modified fiddle slightly