How to select info from row above? - sql

I want to add a column to my table that is like the following:
This is just an example of how the table is structured, the real table is more than 10.000 rows.
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
200 Underwear 0 250 *100
300 Bikes 0 250 *100
400 Profit 3
500 Cash 0 450 *400
So for every time there is a value in 'Subgroup' I want the (New_Column) to get the value [No_] from the row above
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
150 TotalSales 3
200 Underwear 0 250 *150
300 Bikes 0 250 *150
400 Profit 3
500 Cash 0 450 *400
There are cases where the table is like the above, where two "Headers" are above. And in that case I also want the first above row (150) in this case.
Is this a case for a cursor or what do you recommend?
The data is ordered by No_
--EDIT--
Starting from the first line and then running through the whole table:
Is there a way I can store the value for [No_] where [Subgroup] is ''?
And following that insert this [No_] value in the (New_Column) in each row below having value in the [Subgroup] row.
And when the [Subgroup] row is empty the process will keep going, inserting the next [No_] value in (New_Column), that is if the next line has a value in [Subgroup]
Here is a better image for what I´m trying to do:

SQL Server 2012 suggests using Window Offset Functions.
In this case : LAG
Something like this:
SELECT [No_]
,[Name]
,[Account_Type]
,[Subgroup]
,LAG([No_]) OVER(PARTITION BY [Subgroup]
ORDER BY [No_]) as [PrevValue]
FROM table
Here is an example from MS:
http://technet.microsoft.com/en-us/library/hh231256.aspx

The ROW_NUMBER function will allow you to find out what number the row is, but because it is a windowed function, you will have to use a common table expression (CTE) to join the table with itself.
WITH cte AS
(
SELECT [No_], Name, Account_Type, Subgroup, [Row] = ROW_NUMBER() OVER (ORDER BY [No_])
FROM table
)
SELECT t1.*, t2.[No_]
FROM cte t1
LEFT JOIN cte t2 ON t1.Row = t2.Row - 1
Hope this helps.

Next query will return Name of the parent row instead of the row itself, i.e. Sales for both Sales, Underwear, Bikes; and Profit for Profit, Cash:
select ISNULL(t2.Name, t1.Name)
from table t1
left join table t2 on t1.NewColumn = t2.No

So in SQL Server 2008 i created test table with 3 values in it:
create table #ttable
(
id int primary key identity,
number int,
number_prev int
)
Go
Insert Into #ttable (number)
Output inserted.id
Values (10), (20), (30);
Insert in table, that does what you need (at least if understood correctly) looks like this:
declare #new_value int;
set #new_value = 13; -- NEW value
Insert Into #ttable (number, number_prev)
Values (#new_value,
(Select Max(number) From #ttable t Where t.number < #new_value))
[This part added] And to work with subgroup- just modify the inner select to filter out it:
Select Max(number) From #ttable t
Where t.number < #new_value And Subgroup != #Subgroup

SELECT
No_
, Name
, Account_Type
, Subgroup
, ( SELECT MAX(above.No_)
FROM TableX AS above
WHERE above.No_ < a.No_
AND above.Account_Type = 3
AND a.Account_Type <> 3
) AS NewColumn
FROM
TableX AS a

Related

2 sql queries 2 different results using UNION

I have 2 queries that start with same tables but filter different columns. Both queries are unioned together so I can get a single count of people without duplication.
If I run the queries with the union commented out I have the same number of rows in each 1,953. When I run with the union I get 1,816 in one and 1,922 in the other.
My data is just an account # like 123456 in the first column and a 1/0 in the second column. Help me understand how this can happen if I am starting with the same number of rows.
Here is one of the queries
select distinct acct#,
case
when (lastFilledDate is not null and lastFilledDate<>'00/00/00') or
([Last Filled DC] is not null and [Last Filled DC]<>'00/00/00') or
(vivitrol is not null and vivitrol <>'00/00/00') or
(sublocade is not null and sublocade <>'00/00/00') or
(naltrexone is not null and naltrexone <>'00/00/00') then 1
else 0 end as result
from
(
select Acct#, DOB, [COE Contact Note], [COE-INTAKA Doc], [COE-MOM
Doc], lastFilledDate, [Last Filled DC],vivitrol,sublocade,naltrexone,
ROW_NUMBER() over (partition by Acct# order by [COE-INTAKA Doc] desc)
as apptRows
from tblAppBSCImportDashCOE2279 as main
where (([COE-MOM Doc]='Yes' and [COE Contact Note] is not null) or
[COE-MOM Doc]='No') and Appt is not null
) as sub
where apptRows=1
union
select distinct acctNo,
case
when
providerMAT='The Wright Center' and [COE-MOM Doc] is not null then
1
else 0
end as result
from
(
select acctNo, [COE-MOM Doc], MAT, providerMAT,
ROW_NUMBER() over (partition by acctNo order by COEBNMOM, [COE-MOM Doc]
desc) as apptRows
from tblAppBSCImportDashCOEHM2544 as main
where [COE-MOM Doc] is not null or COEBNMOM is not null
) as sub
where apptRows=1
results look like
acct# result
123456 1
234567 0
There is one possibility. The records you selected may result in duplicate records WITHIN each select statement. Let me try to illustrate with an example. (you can input the following query into your session to follow along)
IF OBJECT_ID('TEMPDB..#TEMP1') IS NOT NULL
DROP TABLE #TEMP1
IF OBJECT_ID('TEMPDB..#TEMP2') IS NOT NULL
DROP TABLE #TEMP2
CREATE TABLE #TEMP1(
id INT
,account INT
,amount INT
,yes_no INT
)
INSERT INTO #TEMP1 (id,account,amount,yes_no)
VALUES(1,123456,5,0)
,(2,123456,10,0)
,(3,123456,20,0)
CREATE TABLE #TEMP2(
id INT
,account INT
,amount INT
,yes_no INT
)
INSERT INTO #TEMP2 (id,account,amount,yes_no)
VALUES(4,123456,5,0)
,(5,123456,10,0)
,(6,123456,20,0)
SELECT *
FROM #TEMP1
SELECT *
FROM #TEMP2
Output of this is 2 tables with distinct records:
Now suppose I write queries that select account and the 'yes_no' column:
SELECT account,yes_no
FROM #TEMP1
SELECT account,yes_no
FROM #TEMP2
You can see that now all of the records are the same values within each select statement. So what do you think happens when I union these queries together?
SELECT account,yes_no
FROM #TEMP1
UNION
SELECT account,yes_no
FROM #TEMP2
UNION will output the distinct values of the ENTIRE OUTPUT, which also applies within each query. This is an extreme example of what I think you are experiencing. You need to include some sort of ID for each query such that it can be distinguished from other records within the query, like;
SELECT id,account,yes_no
FROM #TEMP1
UNION
SELECT id,account,yes_no
FROM #TEMP2

How to set to update a column in a table that have more than one same value

I am trying to update a temporary table in a stored procedure, so in the set clause of the update, I used an inner join to update one column, but since that column has some same value the update gets repeated. How I can get the update only for the first unique value of the column?
Here is what I've done for the update:
select
style, color,item, prodnocompany, WIP, 0 As Inventory
into
#protable
from
prodorderheader poh
inner join
prodorderdetal pod on poh.prodno = pod.rpodno
select
item, sum(QOHQTY) as Inventory
into
#Inve
from
inventory
group by
item
update p
set p.Inventory += i.Inventory
from #prod p
inner join #Inve i on p.item = i.item
I need to get the following result:
Style
item
Inventory
D3623M
123776
12
D3623M
123665
11
T3445S
122099
10
D3565W
133422
12
D3565W
133422
0
In the #Prod table there may be some repeating of style and item, because they have a different prodnocompany, but in Inventory, each item has a specific quantity on the had amount (which is QOHQTY), but what I got from the update is as below:
Style
item
prodnocompany
Inventory
D3623M
123776
234
15
D3623M
123665
211
11
T3445S
122099
122
10
D3565W
133422
456
12
D3565W
133422
432
12
How can I update for the first item if I have the same item for different pronocompanys?
As an option, add the rank to the #prod table and then take only the first (last row) for updating.
For example, so:
declare
#t table (col varchar (100), i int)
declare
#t2 table (rn int, col varchar (100), i int)
insert into #t
select 'A', 100
union all
select 'B', 100
union all
select 'C', 200
union all
select 'C', 300
insert into #t2
select DENSE_RANK()over (partition by col order by i), * from #t
select * from #t2 t
where t.rn in (select min(t_s.rn) from #t2 t_s where t_s.col=t.col group by col)

Find the reversal transactions in my table

I have a table data like the bellow
Date Amount
01022017 300
01052017 -300
03042016 200
06112016 400
05042016 -200
30012016 150
I need only the list like below (the negatives are reversals happen to those transactions).
My expected result like
Date Amount
06112016 400
30012016 150
I need to avoid the reversal transactions. But here in my table I don't have any reference column to indicate the transaction as reversal or normal transaction.
You can achieve this with a LEFT JOIN:
SELECT
m.Date
, m.Amount
FROM myTable m
LEFT JOIN myTable m2 on m.Amount = (-1 * m2.Amount)
WHERE m2.Date IS NULL
As noted in the comments, relying in the amount seems flaky to me but here is a try:
SELECT t.date, t.amount
FROM TableName t
WHERE NOT EXISTS (SELECT 1
FROM TableName t1 INNER JOIN TableName t2
ON T1.amount = -t2.amount and T1.date<=t2.date
WHERE (t.date=t1.date or t.date=t2.date) and t.amount=t1.amount
Note that this will not display amounts that appear more than twice.
based on your Assumed table data using Lag function we can do this
Sample Data :
DECLARE #Table1 TABLE
(Dates int, Amount int)
;
INSERT INTO #Table1
(Dates, Amount)
VALUES
(01022017, 300),
(01052017, -300),
(03042016, 200),
(06112016, 400),
(05042016, -200),
(30012016, 150)
;
Script :
Select t.Amount from (
select amount,
coalesce(lag(amount) over (PARTITION BY RIGHT (dates,4) order by (select null)), 0) + amount as diff
from #Table1) T
WHERE NOT EXISTS (select 1 from #Table1 where T.diff = Amount ) and T.diff <> 0
Try this one for oracle, The Problem with the below query is it reads the main table twice.
select Date,Amount from table where amount>0
minus
select Date,-1*amount as Amount from table where amount<0;

Finding Duplicate occurrences in column

I have 2 tables Customer and meter.
A customer might have multiple meternbr in meter table. Customer has a customernbr column.
I want to return customers who have more than one meternbr only. Look at the table below. I want to return only customers a and c with the meternbr also.
Customer Meter
-------- -----
a a-100
b a-101
c b-103
d c-104
c-105
If that is a single string (which i don't think is a good idea to begin with), and if your DBMS supports LEFT/SUBSTRING and INSTR you can do a LEFT or a SUBSTRING combined with a INSTR that finds where is the first '-' index and get the customers that have more than one occurrence by using GROUP BY and HAVING COUNT(*) > 1.
SELECT LEFT(meterColumn,INSTR(meterColumn,'-')-1)
FROM meter
WHERE LEFT(meterColumn,INSTR(meterColumn,'-')-1) IN (
SELECT LEFT(meterColumn,INSTR(meterColumn,'-')-1)
FROM meter
GROUP BY LEFT(meterColumn,INSTR(meterColumn,'-')-1)
HAVING COUNT(*) > 1
)
GROUP BY 1;
If those are two columns in the meter table (customerNbr) and (meterNbr), you could simply do:
SELECT customerNbr
FROM meter
GROUP BY 1
HAVING COUNT(*) > 1;
Use GROUP BY and HAVING Clause with COUNT(*) > 1.
Here's a working Sample: http://sqlfiddle.com/#!3/d1b91/17
Pasting code and results below also:
CREATE TABLES (Note: Have not placed FK Constaint for demo purposes)
CREATE TABLE Customer
(
customernbr NVARCHAR(20) NOT NULL
)
CREATE TABLE Meter
(
meternbr NVARCHAR(20) NOT NULL,
customernbr NVARCHAR(20) NOT NULL
)
INSERT DATA. (Uncomment last 2 SELECT statements if you wanna see the data)
INSERT INTO Customer VALUES
('a'),
('b'),
('c'),
('d');
INSERT INTO Meter VALUES
('a-100','a'),
('a-101','a'),
('b-103','b'),
('c-104','c'),
('c-105','c'),
('d-106','d');
--SELECT * FROM Customer;
--SELECT * FROM Meter;
RUN SELECT STATEMENT
SELECT
customernbr AS 'Customer',
meternbr AS 'Meter'
FROM Meter WHERE customernbr IN
(
SELECT customernbr
FROM Meter
GROUP BY customernbr
HAVING COUNT(*) > 1
)
SEE RESULTS :)
CUSTOMER METER
a a-100
a a-101
c c-104
c c-105

how to get query value from 1 row to use to another row?

This is example query:
payment_Type payment_value cost_type cost value
Cost I 100 Registration 40
Cost I 100 books 40
Cost I 100 Lab 40
The COST I has 3 elements Cost_Type that have their own Cost_value.
I want to manipulate like this:
payment_Type payment_value cost_type cost value Payment_by_cost_type
Cost I 100 Registration 40 40
Cost I 100 books 40 40
Cost I 100 Lab 40 20
The point is the I want to divided the payment_value into each cost_value. In the example the payment_by_cost becomes 40, 40, 20 = 100.
The lab cost_value is 40 but it can assign value is 20 because remains from the divided 2 cost type above.
Is it possible that I can use the value from Payment_by_cost_type in the next row record? I have been trying to insert the value Payment_by_cost_type to a temporary table but select cannot have insert statement.
Does anyone have any ideas on how to solve this? I've been consulting to DWH he said it must using Store procedure it cannot done by query.
I guess your table contains not only "Cost I" but other values so here is a query to output results for all groups (by Payment_type) in the table:
;with table1 as
(select
t.*,
row_number()
OVER
(PARTITION BY payment_Type order by cost_type) rn
from t
)
,table2 as
( select t4.*,isnull((select sum(cost_value) from table1
where table1.payment_type=t4.payment_type and rn<t4.rn),0) CumSum
from table1 t4
)
select payment_type,payment_value,cost_type,cost_value,
case when cost_value+CumSum<=payment_value then cost_value
else
payment_value-Cumsum
end
from table2
order by Payment_type,rn;
SQLFIDDLE demo
You need to define some kind of order for your records to define in which order the payments should be applied
Once you have done that (i'm using ID in this example)...
select *
, case
when payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)<cost_value
then payment_value-(select isnull(SUM(cost_value),0) from yourtable t2 where t2.id<t1.id)
else cost_value
end
from yourtable t1
Doing it step by step using common table expressions.
declare #t table (
payment_type varchar(20),
payment_value int,
cost_type varchar(20),
cost_value int,
cost_id int --for the ordering
)
insert #t values
('Cost I',100,'Registration',40,1),
('Cost I',100,'books',40,2),
('Cost I',100,'Lab',40,3),
('Cost 2',100,'Registration',40,4),
('Cost 2',100,'books',50,5),
('Cost 2',100,'Lab',40,6)
--get count for each payment_type to determine last row
;with payment_value_cte(payment_type,payment_value,count) as
(
select payment_type,payment_value,COUNT(*) from #t group by payment_type,payment_value
),
--use sequential index for each row in payment type
payment_value_index_cte(
payment_type ,
payment_value,
cost_type,
cost_value,
cost_id,
row) as
(
select *,ROW_NUMBER() OVER(PARTITION BY payment_type ORDER BY cost_id) from #t --assumes order is by an id
),
--get sum of each row for payment type except last row
payment_value_sum_except_last_cte(
payment_type,
payment_value,
current_sum) as
(
select pi.payment_type,pi.payment_value,SUM(cost_value)
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
where pi.row < pt.count
group by pi.payment_type,pi.payment_value
)
select
pi.payment_type,pi.payment_value,pi.cost_type,pi.cost_value,
--if last row calculate difference, else use the cost_value
case when pi.row = pt.count then pt.payment_value - pe.current_sum else pi.cost_value end [Payment_by_cost_type]
from payment_value_index_cte pi
inner join payment_value_cte pt on pt.payment_type = pi.payment_type
inner join payment_value_sum_except_last_cte pe on pe.payment_type = pi.payment_type
SELECT payment_Type, payment_value, cost_type, cost_value,
CASE WHEN ROW_NUMBER() OVER (ORDER BY(SELECT 1)) = COUNT(*) OVER (PARTITION BY payment_Type)
THEN SUM(cost_value) OVER (PARTITION BY payment_Type) - payment_value
ELSE cost_value END AS Payment_by_cost_type
FROM dbo.your_table
Demo on SQLFiddle