Unpivot Multiple Paired columns [duplicate] - sql-server

I am using SQL server 2008 and I am trying to unpivot the data. Here is the SQL code that I am using,
CREATE TABLE #pvt1 (VendorID int, Sa int, Emp1 int,Sa1 int,Emp2 int)
GO
INSERT INTO #pvt1 VALUES (1,2,4,3,9);
GO
--Unpivot the table.
SELECT distinct VendorID,Orders,Orders1
FROM
(SELECT VendorID, Emp1, Sa,Emp2,Sa1
FROM #pvt1 ) p
UNPIVOT
(Orders FOR Emp IN
(Emp1,Emp2)
)AS unpvt
UNPIVOT
(Orders1 FOR Emp1 IN
(Sa,Sa1)
)AS unpvt1;
GO
And Here is the result of the above code.
VendorID Orders Orders1
1 4 2
1 4 3
1 9 2
1 9 3
But I want my Output to be the way indicated below
VendorID Orders Orders1
1 4 2
1 9 3
The relationship from the above code is 2 is related to 4, and 3 is related to 9.
How can I achieve this?

An easier way to unpivot the data would be to use a CROSS APPLY to unpivot the columns in pairs:
select vendorid, orders, orders1
from pvt1
cross apply
(
select emp1, sa union all
select emp2, sa1
) c (orders, orders1);
See SQL Fiddle with Demo. Or you can use CROSS APPLY with the VALUES clause if you don't want to use the UNION ALL:
select vendorid, orders, orders1
from pvt1
cross apply
(
values
(emp1, sa),
(emp2, sa1)
) c (orders, orders1);
See SQL Fiddle with Demo

The answer by Taryn is indeed super useful, and I'd like to expand one aspect of it.
If you have a very un-normalized table like this, with multiple sets of columns for e.g. 4 quarters or 12 months:
+-------+------+------+------+------+------+------+-------+------+
| cYear | foo1 | foo2 | foo3 | foo4 | bar1 | bar2 | bar3 | bar4 |
+-------+------+------+------+------+------+------+-------+------+
| 2020 | 42 | 888 | 0 | 33 | one | two | three | four |
+-------+------+------+------+------+------+------+-------+------+
Then the CROSS APPLY method is easy to write and understand, when you got the hang of it. For the numbered column, use constant values.
SELECT
cYear,
cQuarter,
foo,
bar
FROM temp
CROSS APPLY
(
VALUES
(1, foo1, bar1),
(2, foo2, bar2),
(3, foo3, bar3),
(4, foo4, bar4)
) c (cQuarter, foo, bar)
Result:
+-------+----------+-----+-------+
| cYear | cQuarter | foo | bar |
+-------+----------+-----+-------+
| 2020 | 1 | 42 | one |
| 2020 | 2 | 888 | two |
| 2020 | 3 | 0 | three |
| 2020 | 4 | 33 | four |
+-------+----------+-----+-------+
SQL Fiddle

I needed composit key AND skip extras row in case when data is missing (NULLs). For ex. when x2 and y2 are possible replacement vendor and price
WITH pvt AS (SELECT * FROM (VALUES
( 1, 6, 11, 111, 12, 13, 122, 133),
( 2, 6, 21, 211, 22, 23, 222, 233),
( 3, 6, 31, 311, 32, 33, 322, 333),
( 5, 4, 41, 411, 42, NULL, 422, NULL),
( 6, 4, 51, 511, 52, NULL, 522, NULL))
s( id, s, a, b, x1, x2, y1, y2)
)
-- SELECT * FROM pvt
SELECT CONCAT('xy_',s,'_', id, postfix) as comp_id, a, b, x, y
FROM pvt
CROSS APPLY
(
VALUES
(NULL, x1, y1),
('_ext', x2, y2)
) c (postfix, x, y)
WHERE x IS NOT NULL
produces
comp_id a b x y
-------------------------------- ----------- ----------- ----------- -----------
xy_6_1 11 111 12 122
xy_6_1_ext 11 111 13 133
xy_6_2 21 211 22 222
xy_6_2_ext 21 211 23 233
xy_6_3 31 311 32 322
xy_6_3_ext 31 311 33 333
xy_4_5 41 411 42 422
xy_4_6 51 511 52 522
(8 rows affected)
from:
id s a b x1 x2 y1 y2
----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
1 6 11 111 12 13 122 133
2 6 21 211 22 23 222 233
3 6 31 311 32 33 322 333
5 4 41 411 42 NULL 422 NULL
6 4 51 511 52 NULL 522 NULL
(5 rows affected)

Related

Calculate row difference within groups

I'm looking for help with calculating the difference between consecutive ordered rows within groups in SQL (Microsoft SQL server).
I have a table like this:
ID School_ID Enrollment_Start_Date Order
1 56 1/1/2018 10
1 56 5/5/2018 24
1 56 7/7/2018 35
1 103 4/4/2019 26
1 103 3/3/2019 19
I want to calculate the difference between Order, group by ID, School_ID, and order by Enrollment_Start_Date.
so I want something like this:
ID School_ID Enrollment_Start_Date Order Diff
1 56 1/1/2018 10 10 # nothing to be subtracted from 10
1 56 5/5/2018 24 14 # 24-10
1 56 7/7/2018 35 11 # 35-24
1 103 3/3/2019 19 19 # nothing to be subtracted from 19
1 103 4/4/2019 26 7 # 26-19
I have hundreds of IDs, and each ID can have at most 6 Enrollment_Start_Date, so I'm looking for some generalizable implementations.
Use LAG(<column>) analytic function to obtain a "previous" column value specified within the OVER part, then substract current value from it and make it a positive number multiplying it by -1. If previous value isn't present (is null) then take the current value.
Pseudo code would be:
If previous_order_value exists:
-1 * (previous_order_value - current_order_value)
Else
current_order_value
where previous_order_value is based on the same id & school_id and is sorted by enrollment_start_date in ascending order
SQL Code:
select
id,
school_id,
enrollment_start_date,
[order],
coalesce(-1 * (lag([order]) over (partition by id, school_id order by enrollment_start_date ) - [order]), [order]) as diff
from yourtable
Also note, that order keyword is reserved in SQL Server, which is why your column was created with name wrapped within [ ]. I suggest using some other word for this column, if possible.
use lag() analytic function for getting difference of two row and case when for getting orginal value of order column where no difference exist
with cte as
(
select 1 as id, 56 as sclid, '2018-01-01' as s_date, 10 as orders
union all
select 1,56,'2018-05-05',24 union all
select 1,56,'2018-07-07',35 union all
select 1,103,'2019-04-04',26 union all
select 1,103,'2019-03-03',19
) select t.*,
case when ( lag([orders])over(partition by id,sclid order by s_date ) -[orders] )
is null then [orders] else
( lag([orders])over(partition by id,sclid order by s_date ) -[orders] )*(-1) end
as diff
from cte t
output
id sclid s_date orders diff
1 56 2018-01-01 10 10
1 56 2018-05-05 24 14
1 56 2018-07-07 35 11
1 103 2019-03-03 19 19
1 103 2019-04-04 26 7
demo link
Use LAG(COLUMN_NAME)
Query
SELECT id, School_ID, Enrollment_Start_Date, cOrder,
ISNULL((cOrder - (LAG(cOrder) OVER(PARTITION BY id, School_ID ORDER BY Enrollment_Start_Date))),cOrder)Diff
FROM Table1
Samle Output
| id | School_ID | Enrollment_Start_Date | cOrder | Diff |
|----|-----------|-----------------------|--------|------|
| 1 | 56 | 2018-01-01 | 10 | 10 |
| 1 | 56 | 2018-05-05 | 24 | 14 |
| 1 | 56 | 2018-07-07 | 35 | 11 |
| 1 | 103 | 2019-03-03 | 19 | 19 |
| 1 | 103 | 2019-04-04 | 26 | 7 |
SQL Fiddle Demo

Cursor within a cursor

I need your help.
I am trying to make a series of cursors within other cursors.
Below I show you the tables with which I want to make the cursors.
First make the cursor of the first table looking for record.
Second, in table 1 we have the column "ID_ASI", with that column I want to make another cursor that searches inside another table (IMAGE OF TABLE 2) all the "ID_ASI" that it finds with the same "ID_ASI".
Finally, the "ID_ASI" finding by the second step, make a new cursor that looks for all "ID_DOC" that have the same "ID_ASI".
For example,
In the second step, when making the cursor in the "ID_ASI" column, find 3 rows with the same "ID" (101), then the third step searches for all "ID_DOC" with the same "ID_ASI".
For example, "ID_ASI" 101 has 3 "ID_DOC" (value 10), 101 has 2 other values ​​(20) and finally two other values ​​(30).
The complicated thing is how to group them all in the same way, and how to put a cursor inside a cursor.
This would be the result.
Thank you for attention.
To me it looks like what you want is to join the tables together, something like
SELECT t2.*
FROM TABLE_1 t1
INNER JOIN TABLE_2 t2
ON t2.ID_ASI = t1.ID_ASI
ORDER BY t2.ID_ASI, t2.ID_DOC
SQLFiddle here
Best of luck.
Use a hierarchical query:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TABLE_1 ( ID_ASI ) AS
SELECT 101 FROM DUAL UNION ALL
SELECT 201 FROM DUAL UNION ALL
SELECT 301 FROM DUAL;
CREATE TABLE TABLE_2 (ID_ASI, ID_DOC, IMPORT, IMPORT_TO ) AS
SELECT 101, 10, NULL, 1000 FROM DUAL UNION ALL
SELECT 101, 20, NULL, 2000 FROM DUAL UNION ALL
SELECT 101, 30, NULL, 3000 FROM DUAL UNION ALL
SELECT 201, 23, NULL, 430 FROM DUAL UNION ALL
SELECT 201, 23, 430, NULL FROM DUAL UNION ALL
SELECT 104, 10, 500, NULL FROM DUAL UNION ALL
SELECT 104, 20, 2000, NULL FROM DUAL UNION ALL
SELECT 104, 10, 500, NULL FROM DUAL UNION ALL
SELECT 104, 30, 3000, NULL FROM DUAL;
Query 1:
SELECT *
FROM TABLE_2
START WITH ID_ASI IN ( SELECT ID_ASI FROM TABLE_1 )
CONNECT BY PRIOR ID_DOC = ID_DOC
AND PRIOR ID_ASI < ID_ASI
ORDER SIBLINGS BY 1, 2, 3, 4
Results:
| ID_ASI | ID_DOC | IMPORT | IMPORT_TO |
|--------|--------|--------|-----------|
| 101 | 10 | (null) | 1000 |
| 104 | 10 | 500 | (null) |
| 104 | 10 | 500 | (null) |
| 101 | 20 | (null) | 2000 |
| 104 | 20 | 2000 | (null) |
| 101 | 30 | (null) | 3000 |
| 104 | 30 | 3000 | (null) |
| 201 | 23 | 430 | (null) |
| 201 | 23 | (null) | 430 |

select query to get first row from rows having multiple id's.(without partition by)

id date amount documentNo paperID
1 2015/10/15 500 1234 34
1 2015/10/15 100 1332 33
2 2015/10/13 200 1302 21
2 2015/10/13 400 1332 33
3 2015/11/23 500 1332 43
I should get the output as:
id date amount documentNo paperID
1 2015/10/15 500 1234 34
2 2015/10/13 200 1302 21
3 2015/11/23 500 1332 43
Please suggest a simple select query to fetch only one row without partition by. Note: the date remain same for a particular id.
Try a null-self-join. Basically you are comparing each row to some other version of that row ,but, via an inequality (here I have used documentNo) you end-up with a single row that has no match.
See this SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`id` int, `date` datetime, `amount` int, `documentNo` int, `paperID` int)
;
INSERT INTO Table1
(`id`, `date`, `amount`, `documentNo`, `paperID`)
VALUES
(1, '2015-10-15 00:00:00', 500, 1234, 34),
(1, '2015-10-15 00:00:00', 100, 1332, 33),
(2, '2015-10-13 00:00:00', 200, 1302, 21),
(2, '2015-10-13 00:00:00', 400, 1332, 33),
(3, '2015-11-23 00:00:00', 500, 1332, 43)
;
Query 1:
SELECT
t1.*
FROM table1 AS t1
LEFT OUTER JOIN table1 AS t2 ON t1.id = t2.id
AND t1.date = t2.date
AND t2.documentNo < t1.documentNo
WHERE t2.ID IS NULL
Results:
| id | date | amount | documentNo | paperID |
|----|----------------------------|--------|------------|---------|
| 1 | October, 15 2015 00:00:00 | 500 | 1234 | 34 |
| 2 | October, 13 2015 00:00:00 | 200 | 1302 | 21 |
| 3 | November, 23 2015 00:00:00 | 500 | 1332 | 43 |
EDIT: There are several approaches to this problem even without windowing functions such as row_number() , here is a previous answer covering some MySQL specific alternatives.

Complex Grouping in Sql with two or three Group criteria for single table and Joins

This is my table in ms-sql 2008
Id ReceiptNo StudId Year SchoolId ClassId FeeId Paid Balance
18 1 22 1 4 1 8 50 100
19 1 22 1 4 1 9 100 2300
20 2 23 1 5 1 9 200 2200
21 2 23 1 5 1 10 100 900
22 3 22 1 4 1 8 100 0
23 3 22 1 4 1 9 100 2200
I want rows where Balance is minimum but not 0, of that specific StudId and FeeId. Actually I want to show pending Fees to User, meaning, this Student has this much balance remaining of this fee type for this Academic Year. If you observe row with Id 18 has StudId 22 and FeeId 8 and Balance 100, now row with Id 22 shows Balance 0 with same student and same fee, meaning this 22 student has paid all fees of 8 FeeId, so I don’t want this student for FeeId 8. Now the same student has paid fee with FeeId 9, row with Id 19 & 23 and has two Balance 2300 and 2200, so I want to show that StudId 22 has Balance of 2200 remaining for FeeId 9, meaning I want to show row with Id 23 and NOT the row with Id 19. Now this applies to all students for whatever Fee Id they have.
I have tried this but not getting
SELECT StudentId,FeeTypeId,MIN(FeesBalance)
FROM FeesCollectionRS
WHERE FeesBalance !=0 GROUP BY StudentId,FeeTypeId
Actually in reality my query will be some what like this
SELECT f.StudentId,f.AcademicYear,f.SchoolId,f.MstClassId,f.FeeTypeId,d.PrnNo ,dbo.GetStudNameByStudid(f.StudentId) student,
CAST(year(a.StartOfYear)as CHAR(4))+'-'+ CAST(year(a.EndOfYear)as CHAR(4)) [Academic Year],c.Name,m.FeeType,f.FeesBalance
FROM
FeesCollectionRS f JOIN AcademicYearMasterRS a
ON f.AcademicYear = a.Id JOIN ClassMasterRS c
ON f.MstClassId = c.MstClassId JOIN MstFeeType m
ON f.FeeTypeId = m.FeeTypeId JOIN MstStudentRS d
ON d.StudentId = f.StudentId
WHERE
f.AcademicYear =1 and f.FeesBalance !=0
Use a HAVING clause to filter out minimums that are zero:
SELECT StudentId, FeeTypeId, MIN(FeesBalance) MinFeesBalance
FROM FeesCollectionRS
GROUP BY StudentId, FeeTypeId
HAVING MIN(FeesBalance) > 0
This first groups to find the minimum, then returns only those groups that have the criteria in HAVING, which is like a WHERE equivalent for groups and is applied to the result of aggregations rather than column values.
How about this:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE StudentFees
(
Id INT PRIMARY KEY,
ReceiptNo INT,
StudId INT,
[Year] SMALLINT,
SchoolId INT,
ClassId INT,
FeeId INT,
Paid INT,
Balance INT
)
INSERT INTO StudentFees
VALUES (18, 1, 22, 1, 4, 1, 8, 50, 100),
(19, 1, 22, 1, 4, 1, 9, 100, 2300),
(20, 2, 23, 1, 5, 1, 9, 200, 2200),
(21, 2, 23, 1, 5, 1, 10, 100, 900),
(22, 3, 22, 1, 4, 1, 8, 100, 0),
(23, 3, 22, 1, 4, 1, 9, 100, 2200)
Query 1:
;WITH CTE
AS
(
SELECT StudId, FeeId, [Year], MAX(Id) As MaxId
FROM StudentFees
GROUP BY StudId, FeeId, [Year]
)
SELECT SF.StudId, SF.[Year],
SF.SchoolId, SF.ClassId, SF.FeeId, SF.Balance
FROM StudentFees SF
INNER JOIN CTE
ON CTE.MaxId = SF.Id
WHERE SF.Balance > 0
Results:
| StudId | Year | SchoolId | ClassId | FeeId | Balance |
|--------|------|----------|---------|-------|---------|
| 22 | 1 | 4 | 1 | 9 | 2200 |
| 23 | 1 | 5 | 1 | 9 | 2200 |
| 23 | 1 | 5 | 1 | 10 | 900 |

Running "Group By" Ordinal Counter Based on a "Flip" Column

Usually I'm decent at set-based tsql problems. But this one is beating me.
I've been working 3 days on converting a while-loop procedure into a setbased one. I've gotten to the point below.......but can't make the final jump.
I have the following rows. MyOrdinal will be "in order" ... and a second column (MyMarker) will alternate between having a value and being null. Whenever this "flip" occurs on MyMarker, I would like to increment a "group by" ordinal counter by one. Whenever the "flip" values are non-null or null, these are grouped together as a set.
I've tried several things, but it was too ugly to post. That and since moving to ORM, I don't spend as much time in the tsql anymore.
declare #Holder table ( MyOrdinal int not null , MyMarker int , MyGroupNumber int )
INSERT INTO #Holder (MyOrdinal, MyMarker)
Select 1 , 1
union all Select 2, 2
union all Select 3, null
union all Select 4, 3
union all Select 5, 4
union all Select 6, 5
union all Select 7, 6
union all Select 8, 7
union all Select 9, 8
union all Select 10, 9
union all Select 11, 10
union all Select 12, 11
union all Select 13, 12
union all Select 14, 13
union all Select 15, 14
union all Select 16, 15
union all Select 17, null
union all Select 18, null
union all Select 19, null
union all Select 20, 16
union all Select 21, 17
union all Select 22, 18
union all Select 23, null
union all Select 24, null
union all Select 25, 19
union all Select 26, 20
union all Select 27, null
union all Select 28, 21
Select * from #Holder
Desired Output
| MyOrdinal | MyMarker | MyGroupNumber |
|-----------|----------|---------------|
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | null | 2 |
| 4 | 3 | 3 |
| 5 | 4 | 3 |
| 6 | 5 | 3 |
| 7 | 6 | 3 |
| 8 | 7 | 3 |
| 9 | 8 | 3 |
| 10 | 9 | 3 |
| 11 | 10 | 3 |
| 12 | 11 | 3 |
| 13 | 12 | 3 |
| 14 | 13 | 3 |
| 15 | 14 | 3 |
| 16 | 15 | 3 |
| 17 | null | 4 |
| 18 | null | 4 |
| 19 | null | 4 |
| 20 | 16 | 5 |
| 21 | 17 | 5 |
| 22 | 18 | 5 |
| 23 | null | 6 |
| 24 | null | 6 |
| 25 | 19 | 7 |
| 26 | 20 | 7 |
| 27 | null | 8 |
| 28 | 21 | 9 |
Try this one:
First, this assigns a same ROW_NUMBER for continuous Non-NULL MyMarker. ROW_NUMBER is NULL for NULL MyMarkers. After that, you want to add a ROW_NUMBER for NULL MyMarkers such that the value is between the previous NON-NULL and the next NON-NULL. Then use DENSE_RANK to finally assign MyGroupNumber:
SQL Fiddle
;WITH Cte AS(
SELECT *,
RN = ROW_NUMBER() OVER(ORDER BY MyOrdinal) - MyMarker + 1
FROM #Holder
),
CteApply AS(
SELECT
t.MyOrdinal,
t.MyMarker,
MyGroupNumber =
CASE
WHEN RN IS NULL THEN x.NewRN
ELSE RN
END
FROM Cte t
OUTER APPLY(
SELECT TOP 1 RN * 1.1 AS NewRN
FROM Cte
WHERE
t.MyOrdinal > MyOrdinal
AND MyMarker IS NOT NULL
ORDER BY MyOrdinal DESC
)x
)
SELECT
MyOrdinal,
MyMarker,
MyGroupNumber = DENSE_RANK() OVER(ORDER BY MyGroupNumber)
FROM CteApply
For Sql Server 2012:
select *, sum(b) over(order by myordinal)
from(select *,
case when (lag(mymarker) over(order by myordinal) is not null
and mymarker is null) or
(lag(mymarker) over(order by myordinal) is null
and mymarker is not null)
then 1 else 0 end as b
from #Holder) t
First you mark rows with 1 where there is a change from null to not null or from not null to null. Other columns are marked as 0. Then running sum of all rows till current.
Fiddle http://sqlfiddle.com/#!6/9eecb/5015
For Sql Server 2008:
with cte1 as (select *,
case when (select max(enddate) from t ti
where ti.ruleid = t.ruleid and ti.startdate < t.startdate) = startdate
then 0 else 1 end as b
from t),
cte2 as(select *, sum(b) over(partition by ruleid order by startdate) as s
from cte1)
select RuleID,
Name,
min(startdate),
case when count(*) = count(enddate)
then max(enddate) else null end from cte2
group by s, ruleid, name
Fiddle http://sqlfiddle.com/#!6/4191d/6

Resources